以下要講下魔改 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 都用緊,所以不會輕易就會出改動。

同埋可能有更多更多的魔改有待發掘 !! 等大家繼續去發現 !!


過去文章
2025 (9)
4 (5)
3 (1)
2 (3)
2024 (25)
11 (3)
10 (3)
9 (1)
3 (18)
2022 (6)
10 (1)
6 (2)
5 (1)
3 (1)
1 (1)
2021 (21)
11 (7)
7 (1)
6 (2)
5 (2)
4 (6)
3 (2)
2 (1)
2020 (92)
12 (1)
11 (2)
10 (4)
9 (10)
8 (5)
7 (1)
6 (3)
5 (1)
4 (4)
3 (25)
2 (7)
1 (29)
2019 (57)
12 (25)
11 (7)
9 (25)