Gatsby 個人狀況自動更新,整合Spotify 音樂、Letterboxd電影、書本紀錄!

網頁小更新!幾個月前重新寫過整個網站(雖然看起來完全一樣,不過內裡是從頭寫過的!),今個月終於加了新內容於主頁上。這次整合了幾個我常用的媒體服務,讓我不用再手動在 About 更新最近在看的書、電影和音樂。

網上不少 creator 的個人網頁也有更加先進的整合,如 Fabrizio Rinaldi 整合了自己網頁的即時使用數據和個人 Project 的每月收入; Nev Flynn ,也在網頁會即時顯示自己正在聽的歌和現在位置。這次也向他們偷師,顯示我最近的音樂、電影和書本資訊。

➤ 立即看看我的網頁:我正在聽甚麼音樂、電影和閱讀紀錄

簡介一下製成品,這次我用了 gatsby-source-spotify取得 Spotify 的音樂資料,電影和書本則用 gatsby-source-rss-feed將各自 LetterboxdOku 的 RSS Feed 轉成我網站主頁的資訊卡,全部也是每次 Build 網頁時擷取各平台的最新資料。如果未用過 Gatsby,可以參考我先前寫過的 兩篇免費建立網誌教學

Spotify 整合最常聽的歌手和大碟

我們使用 gatsby-source-spotify 的插件,將可以獲得自己 Spotify Profile 的資訊。這個插件內裡也是 Spotify 自家官方的 API,所以資訊比較多,Personalisation API 中,可以取得用戶不同時限(一個月、六個月、長期)的數據。

  • Top Artist:最常聽的歌手
  • Top Tracks:最常聽的歌曲
  • Recent Track :最近聽的歌曲

用 Source 插件的好處是所有數據已經以 GraphQL 顯示,不用再自己處理,直接引用即可。加上支援 Gatsby Image,要顯示該歌手的圖片、單曲所屬大碟的資訊也是十分容易處理。

首先安裝插件:

yarn add gatsby-source-spotify

再於 gatsby-config.js 加入

{resolve: `gatsby-source-spotify`,options: {clientId: process.env.SPOTIFY_CLIENT_ID,clientSecret: process.env.SPOTIFY_CLIENT_SECRET, // Don't add to public repositoryrefreshToken: process.env.SPOTIFY_REFRESH_TOKEN,fetchPlaylists: false, // optional. Set to false to disable fetching of your playlistsfetchRecent: true, // optional. Set to false to disable fetching of your recently played trackstimeRanges: ['short_term', 'medium_term', 'long_term'], optional. Set time ranges to be fetched},},

記得我們需要在 env 檔案中加入 SPOTIFY_CLIENT_IDSPOTIFY_CLIENT_SECRETSPOTIFY_REFRESH_TOKEN。Client ID 和 Client Secret 均可在 Spotify Developer Dashboard 中建立一個 App 後可以使用。至於 Refresh Token,參考 Github 的指示,就需要再 Edit Settings 中加入 http://localhost:5071/spotify 到 Redirect URIs 然後再於 command line 輸入指令,就可以登入 Spotify 授權獲取資訊。

npx gatsby-source-spotify token <clientId> <clientToken>

三個 token 都成功取得以後,再次行 gatsby develop 後到 http://localhost:8000/___graphql就應該會見到 AllSpotifyTopArtist 等 Query。如我想要最常聽的歌手及他們的相片。我便可以 Query 如下。Name 就是 Artist 的名稱,image 用 Gatsby v3 的方法就可以顯示, external_urls.spotify 就可以用 Link Component 直接打開 Spotify 聆聽。

query SpotifyQuery {allSpotifyTopArtist(filter: {time_range: {eq: "short_term"}}, limit: 10) {edges {node {nameimage {localFile {childrenImageSharp {gatsbyImageData}}}external_urls {spotify}}}}}

然後建立一個新的 Music component,再用 Static Query 將 Spotify 的資料顯示(因不會變更及 Build 的時候只使用一次)。這裡我 render 了歌手的相片和名字。同樣道理也適用於單曲和大碟上。最後在顯示的頁面 Import <MusicCard/>就大功告成。

const MusicCard = () => {const data = useStaticQuery(graphql`query MusicCardQuery {allSpotifyTopArtist(sort: { fields: order }, limit: 10) {edges {node {idnameimage {localFile {childImageSharp {gatsbyImageData(width: 160, height: 160)}}}external_urls {spotify}}}}}`);return (<>{data.allSpotifyTopArtist.edges && (<div><h3>🎧 最近在聽</h3><div>{data.allSpotifyTopArtist.edges.map((artist, index) => (<div><Linkto={artist.node.external_urls.spotify}target="_blank"key={artist.node.id}><div><GatsbyImageimage={artist.node.image.localFile.childImageSharp.gatsbyImageData}alt={artist.node.name}/></div></Link><p>{artist.node.name}</p></div>))}</div></div>)}</>);};export default MusicCard;

Letterboxd 整合我的電影觀看紀錄

Letterboxd 是我很愛使用的電影平台,資料齊全,社群也十分活躍。雖然未有正式 API 開放,但本來它們為用戶提供的 RSS 也足夠使用。每個用戶本身也附有 RSS 的鏈結,網址是 https://letterboxd.com/{username}/rss/ 。如果用 Feedly 等 RSS Reader 可閱讀,本身 RSS Feed 已經有齊電影名、評分、海報、觀看日期等資訊。不過我們需要手動調整需要的資訊。

gatsby-source-rss-feed 這插件就是為 RSS Feed 而設,每次 Build 網頁的時候都會讀取 Feed 的內容。

yarn add gatsby-source-rss-feed

安裝後,我們在 gatsby-config.js 中加入:

{resolve: `gatsby-source-rss-feed`,options: {url: `https://letterboxd.com/samuelisme/rss/`,name: `Letterboxd`,parserOption: {customFields: {item: ['letterboxd:watchedDate','letterboxd:memberRating','letterboxd:filmTitle','letterboxd:filmYear','description',{ includeSnippet: true },],},},},}

這 Source 插件同樣地也會轉化成 GraphQL 的 Query。當中也支援 Custom Fields 自訂欄位,參考 RSS Feed 源碼的欄位,可以加入自訂欄位。我也命名了 RSS Feed 的名稱為 “Letterboxd”,重啟 gatsby-develop後, http://localhost:8000/___graphql 就會見到 AllFeedLetterboxd。

同樣地選擇所需的欄位,就可以自行加入到 Static Query。

Static Query 與一般的 Page Query 不同(如每篇 blog 的 template),靜態的意思是不接受 variable,由於我們從不同 Source Plugin 取得的資訊也是恆定不變,與正常網誌的 Blog post / Blog list 範本不同,所以可以使用更快更有效的 Static Query

query MovieCardPage {allFeedLetterboxd(limit: 5) {edges {node {idtitleletterboxd {watchedDatememberRating}contentlink}}}}

如何顯示海報?

由於海報是以 HTML 的形式放在 RSS Feed 的 content 裡,這裡需要少許功夫才刪淨相片的鏈結。

"content": " <p><img src=\"https://a.ltrbxd.com/resized/film-poster/3/6/9/8/3/5/369835-the-suicide-squad-0-500-0-750-crop.jpg?k=86bb8db581\"/></p> <p>Watched on Friday August 13, 2021.</p> "

我寫了個簡單的 function,將自動搜尋</p> close tag 的位置,然後刪淨相片的鏈結,另外將相片的大細修改(由 500px x 700px 改為 200px x 300px),提高載入速度。

function rssParser(htmlString) {let imgLink = null;const searchTerm = `\"/></p>`;const imgTagPosition = htmlString.indexOf(searchTerm);const elements = htmlString.slice(14, imgTagPosition); // Delete string after the img tagimgLink = elements.replace('0-500-0-750', '0-200-0-300'); // Load a smaller imageconsole.log(imgLink);return imgLink;}

最後的版本是這樣的

const MovieCard = () => {const data = useStaticQuery(graphql`query MovieCardPage {allFeedLetterboxd(limit: 5) {edges {node {idtitleletterboxd {watchedDatememberRating}contentlink}}}}`);return (<>{data.allFeedLetterboxd.edges && (<div><h3>🎬 最近在看</h3><div>{data.allFeedLetterboxd.edges.map((movie, index) => (<div key={movie.node.id}><Link to={movie.node.link} target="_blank"><imgsrc={rssParser(movie.node.content)}alt={movie.node.title}/>{/* <Text>{movie.node.title}</Text> */}</Link></div>))}</div></div>)}</>);};export default MovieCard;

最後在顯示的頁面 Import < MovieCard/ > 就大功告成。

Oku 整合我的閱讀紀錄

Oku 是新的閱讀分享平台,跟 Goodread 差不多,不過使用 Oku 的好處是,它不同閱讀狀態都有獨立的 RSS Feed,所以比較容易處理,可直接使用 Reading 的 RSS Feed 就可以。

我們同樣也是用 gatsby-source-rss-feed引用 Oku 的閱讀紀錄。可惜的是 Oku 暫未支援於 RSS 顯示封面。Oku 的 RSS 鏈結少許 tricky,感謝網友分享,在 Reading Collection 一頁,原來 View Source 就會看到自己的 RSS 鏈結。而我的是 https://oku.club/rss/collection/UfVaj

然後做的事跟 Letterboxd 一樣,於 gatsby-config.js複雜多一個 option,然後修改 URL 成自己的 RSS Feed。重啟 gatsby-develop後, http://localhost:8000/___graphql 就會見到 AllFeedOku。

{resolve: `gatsby-source-rss-feed`,options: {url: `https://oku.club/rss/collection/UfVaj`,name: `Oku`,},},

同樣用 Static Query 就可以引用書名、簡介、以及書的鏈結。

query BookCardPage {allFeedOku {edges {node {idtitlecontentSnippetcreatorguid}}}}

最後在顯示的頁面 Import <BookCard/> 就大功告成。

const BookCard = () => {const data = useStaticQuery(graphql`query BookCardPage {allFeedOku {edges {node {idtitlecontentSnippetcreatorguid}}}}`);return (<>{data.allFeedOku.edges && (<div><h3>📚 最近在讀</h3>{data.allFeedOku.edges.map((book) => (<div key={book.node.id}><Link to={book.node.guid} target="_blank">{book.node.title}</Link><p>by {book.node.creator}</p></div>))}</div>)}</>);};export default BookCard;

後話

用 RSS Feed 雖然簡單,不過其中一個缺點是非即時。往往 RSS Feed 更新也有至少幾分鐘的延遲,不過對我這用法沒有太大影響。

Gatsby 先前 2021 年 7 月其實推出了 Functions ,讓 Gatsby 這種靜態網頁也可以經 API 即時獲取最新資訊,不過至今未有太多教學,下一步值得一試是改用 Gatsby Functions 直接與 Spotify API 對話,擷取即時資訊(如我正在聽甚麼歌)。現在用 Gatsby 的 Source 插件,只有在 Build 網站時才更新一次資訊。(個人來說來也是一件好事,鼓勵自己不斷寫作才會更新網誌,當然你也可以自行設定 Webhook 自動定時 Build 網站)。

如 Demo Code 裡有任何問題和建議也可以向我提出,以上的 code 簡化關係,我也刪走所有 style,但想參考我用 Chakra UI 的話可以到 Github

Originally published at https://notes.desktopofsamuel.com.

--

--

帕慕克說過,真正的博物館是時間被轉化成空間的地方。若把我的人生策展成一個展覽,書桌上會放著一張地圖、一部相機、一部電腦、一枝筆桿和一個時鐘。網誌:notes.desktopofsamuel.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store