以下要講下魔改 BlogArchive Widget,今次應該真係魔改,暫時在網站上找不到任何相關的文章和 API 講有關事情。 話說係 BlogArchive Widget 入面如果用 `HIERARCHY` 模式的話,可以得到一個 `data` 參數來畫出你需要的畫面。 因為是 `HIERARCHY` 的關係,所以 `data` 變數也是樹狀型態的。 依照[官方 Documentation](https://support.google.com/blogger/answer/47270) 所講,你可以得到以下的資料: - title: The title of the widget. - style: One of 'MENU', 'FLAT', or 'HIERARCHY'. - data: A list of each archive unit, each of which contains: - name: The name of this archive interval, e.g. "August 2006." - url: The link to the page containing posts from this interval. - post-count: How many posts there are in this interval. ### 限制 但是如果你玩過呢個 Widget 之後,發覺佢只會提供現時所在位置 Post 的相關月份內容,而不是提全部的內容給你去畫出 UI。 例如你現在是正在看 2023 年 6 月的文章,Widget 只會給你以下資料: - [2023 年](https://example.blogspot.com/2023/) - [7 月](https://example.blogspot.com/2023/07/) - [6 月](https://example.blogspot.com/2023/06/) - [你的文章 1](https://example.blogspot.com/2023/06/example-post1.html) - [你的文章 2](https://example.blogspot.com/2023/06/example-post2.html) - [4 月](https://example.blogspot.com/2023/04/) - [1 月](https://example.blogspot.com/2023/01/) - [2022 年](https://example.blogspot.com/2022/) - [5 月](https://example.blogspot.com/2022/05/) - [4 月](https://example.blogspot.com/2022/04/) 你只能夠按下 2023 年 7 月的連結,是不能夠取得 2023 年 7 月的文章列表。 ### 開始 Reverse Engineering 如果大家有玩過 Blogger 預設的 Theme,有可能會發現有一些好舊版的 Theme (千禧年主題)入面個 Blog Archive 是可以做到 Ajax 載入不同月份的文章列表。 呢個時間你可以打開 Chrome development tool 來看看到底叫了一個什麼的 Ajax 出去。你應該會看到類似下以的 Url: ```js https://example.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN ``` 然後你可以試下去不同的頁面去開一開個 Blog Archive 上面個箭咀去觸發個 Ajax 載入: ```js https://example.blogspot.com/2023/02/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN ``` 會發現到無論你在任何的 URL 上 Request 去 server 都好,只要你 Url 上 Query 上以下的參數就可以 Call `text/javascript` 的 Response 回來。 - action 暫時只發現 'getTitles' 這個值能用 - widgetId 大約是填返你 BlogArchive 個 id,試過填第二啲野會出 HTTP 400 Error - widgetType 暫時只發現 'BlogArchive' 這個值能用 - responseType 暫時只發現 'js' 這個值能用 - path 這個必需要填對才能通過,如果你要取得 2023 年 6 月的文章列表,就必需要填入 'https://example.blogspot.com/2023/06/' 這個值,填錯出會 Error - xssi_token 可以用 JS 在 runtime 時找到 xssi token ### 如何找到 XSSI Token 你可以在你的 Blogger 網站上用 Browser 按 `檢視源始碼` 來看看由 Blogger Server 最終 Generate 出來的 HTML 到底是怎樣。 我們先 Scroll 到最底,會發現到類似的 JS Code : ```js window['__wavt'] = 'ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD'; ``` 就是這個 `window['__wavt']` 變數裝住的,就是你需要的 XSSI Token。你可以在 Event `DOMContentLoaded` 後任何時間取出來使用。 ```js // DOMContentLoaded 事件處理 document.addEventListener('DOMContentLoaded', function() { // 儲起 XSSI TOKEN 備用 const XSSI_TOKEN = window['__wavt']; }); ``` ### 開始 Request 有齊上面資料後,就可以用 `axios` 出個 GET Request 比伺服器。 ```js // prepare url const url = 'https://example.blogspot.com/?action=getTitles&widgetId=BlogArchive1&widgetType=BlogArchive&responseType=js&path=https://example.blogspot.com/2023/06/&xssi_token=TOKEN'; // axios get request and extract 'data' const { data } = axios.get(url); ``` 如果成功沒有出 Error 的話,大約會得到以下的 Response : ```js try { _WidgetManager._HandleControllerResult('BlogArchive1', 'getTitles',{'path': 'https://example.blogspot.com/2023/06/', 'posts': [{'title': 'Your post title', 'url': 'https://example.blogspot.com/2023/06/your-post.html'}]}); } catch (e) { if (typeof log != 'undefined') { log('HandleControllerResult failed: ' + e); } } ``` 但是問題來了,因為取得的不是 Json 格式,不能夠直接使用,必需要使用神器 `eval()` 來加工一下。 ### JsonP 還記得先前有文章講解過 JsonP 嗎? 如果未睇過可以去睇下。 我們先整個 random function attach 去 window object 度先 : ```js // 整條 Random String 作為 Function 名 const fncName = 'fnc_' + Math.random().toString(16).slice(2, 12); // 用上面個 Random String 作為 window 的 Property, 指向一個 Function window[fncName] = (widgetId, action, data) => { // log 去 console 度 console.log(data); // 用完要 delete 返 (一次性 function) delete window[fncName]; }; ``` 然後然要搞搞個 Response 令到佢 `eval()` 時改為執後你個自訂 function。 ```js // 將 '_WidgetManager._HandleControllerResult' 改為你自訂的 function 名稱, 即係上面個 Random function 名 const jsCode = data.replace(/_WidgetManager\._HandleControllerResult/g, fncName); // 執行 eval eval(jsCode); ``` 如果成功的話,就可以在 development console 上看到 print 左條 json 出來。 ```js {'path': 'https://example.blogspot.com/2023/06/', 'posts': [{'title': 'Your post title', 'url': 'https://example.blogspot.com/2023/06/your-post.html'} ``` 有左呢條 json 的資料,就可以用佢來更新返相應的 UI 位置。 搞掂 ! 食碗麵 ! ### 總結 呢個係魔改,不知道某一日會唔會突然用唔到,不過機會都好細,因為已經有成千上萬個 Blog 都用緊,所以不會輕易就會出改動。 同埋可能有更多更多的魔改有待發掘 !! 等大家繼續去發現 !!