內部鏈接
- [[技術折騰 xLog 1 可行性探索]]
- [[技術折騰 xLog 2 深入理解 xlog 的鑑權]]
- [[技術折騰 xLog 3 實現一個 obsidian 插件]]
- [[技術折騰 xLog 4 用 unStorage 封裝 xLogDriver]]
- [[速通 - xLog 背後的 CrossBell SDK]]
- [[從官方 XLOG Obsidian 插件中能學到什麼]]
- [[開發 Obsidian Sync To Xlog 插件之 處理 obsidian 的圖片]]
- [[速通 - CrossBell 的開源作品]]
- [[速通 Obsidian Docs - 側重插件開發]]
本文繼續探索,承接 [[技術折騰 xLog 1 可行性探索]] 文章的內容。
本文嘗試實現 xlog 文章的管理,實現自己內容的增刪改查,基於官方的 openapi。本文價值 5 個幣的讚賞,嘻嘻
背景#
如果要實現 xlog 文章的管理操作,這就需要鑑權,如何證明你是你,你能修改某個人的文章?在 web2 領域,我們一般會通過帳號密碼生成 jwt 模擬用戶,或者通過 oauth 獲得 access-token 授權操作特定的權限範圍。
我們的技術前提是任意運行時環境,同時支持瀏覽器和 node.js 等不同運行環境,所以不考慮了網頁端小狐狸 metamask 方式授權。
技術實現#
經過一番探索,和官方成員的 diygod 的指導,目前至少有兩種方式實現數據授權管理,私鑰和授權碼,推薦第二種。
透過 Private Key#
首先是透過 private key,錢包的私鑰。這種是萬能的我證明是我的的方案,官方 sdk 也支持透過 private key 實現鑑權。
比如這裡 https://crossbell.js.org/#md:connect-with-private-key
private key 從哪裡來呢?舉例小狐狸:
在帳戶詳情裡,有個 Show Private Key。配合官方 SDK 可以實現鑑權。
請務必注意:private key 非常機密,使用它也非常危險,這裡只做技術介紹。
對於用戶和開發者,更推薦下面的方案,授權 token 方案。
透過 siwe 授權#
SIWE 是 sign in with ethereum
的縮寫。
透過 siwe 授權有以下幾個步驟:
- 開啟 siwe 功能
- 獲取 token
- 透過 api 獲得授權
開啟 siwe 功能#
首先訪問自己的 xlog 控制台 https://xlog.app/dashboard/ 在左側菜單欄最後一列選擇設置。
選擇 xSettings
,會有下圖的展示:
我對 web3 不是很熟悉,口語化表達,圓框內的解釋可以這樣理解,主要是 Sync Operator 部分:
- 操作授權。
- 同步操作。透過開啟 Sync Operator 可以允許透過其他方式同步內容到 Crossbell 也就是 xlog
一旦開啟,我們就擁有了透過代碼同步內容到 xlog 的能力。
獲取 token#
目前獲取 token 的最簡單方案是在控制台中這樣操作:
在 xsetting 頁面,單擊右鍵選擇檢查,或者開啟開發者模式,或者按 F12,推薦使用 Chrome。會彈出一個模塊,選擇 控制台 Console,複製下面代碼到控制台,按回車,會返回一長串文字。
JSON.parse(localStorage.getItem('connect-kit:account')).state.wallet.siwe.token
這段代碼的作用是獲取 siwe 的 token 信息,應該還有其他方案獲取,目前這種方案最簡單。
輸入這段代碼返回的內容就是需要的 token 信息,請自行保存好。
請注意不要兩端的單引號,只要內容。(算了,我做個防呆設計,做個程序兼容吧。)
請複製控制台返回這一長串內容,這個叫鑑權 token,插件裡會用到。
對開發者來說,在 node.js 項目中透過 .env 存儲,在 github 中透過 secret 存儲。避免明文暴露。
api 實踐#
補充:也推薦使用 crossbell 的 sdk,效果是一致的。
有了剛才獲得的 token,我們訪問 https://indexer.crossbell.io/docs#/Siwe 官方提供的文檔,我們所需要的所有東西都在這裡了。
翻到頁面最頂部,有個綠色的 Authorize 按鈕,點擊彈出一個彈窗,把剛才的 token 填寫到 siwe 內容裡
點擊授權,這時候我們在每次請求的 header 裡會添加 Authorization: Bearer ${token}
比如我們使用 GET /v1/siwe/account
就可以獲取當前我們是誰了。
剩下的事情就簡單了,有這樣幾個接口可以自行實踐:
PUT /v1/siwe/contract/characters/{characterId}/notes
給用戶新建文章POST /v1/siwe/contract/characters/{characterId}/notes/{noteId}/metadata
更新指定 noteId 的文章Delete /v1/siwe/contract/characters/{characterId}/notes/{noteId}
刪除指定的文章
提供幾個代碼片段,經過幾次嘗試,一下代碼片段有效。
首先是創建文章:
const title = '文章標題'
const content = '文章內容'
const base = "https://indexer.crossbell.io";
const ottoCharId = ''
const token = ''
const createPost = async () => {
const url = `/v1/siwe/contract/characters/${ottoCharId}/notes`;
const finalUrl = base + url;
axios
.request({
method: "put",
url: finalUrl,
headers: {
Authorization: `Bearer ${token}`,
},
data: {
metadata: {
tags: ["post"],
type: "note",
title: title,
content: content,
summary: "",
sources: ["xlog"],
attributes: [
{
value: "play-xlog-01", // 這裡是自定義 slug
trait_type: "xlog_slug",
},
],
attachments: [
{
name: "cover", // cover
address: "",
mime_type: "",
},
],
},
locked: false,
linkItemType: null,
},
})
.then((res) => {
console.log(2, res.data);
})
.catch((err) => {
console.log(3, err);
});
};
然後是更新文章,注意這裡的 mode 從字面意思看 replace 比較簡單
const updatePost = async () => {
const noteId = 29;
const mode = "replace"; //'merge'
const updatePost =
base +
`/v1/siwe/contract/characters/${ottoCharId}/notes/${noteId}/metadata`;
axios
.request({
method: "post",
url: updatePost,
headers: {
Authorization: `Bearer ${token}`,
},
data: {
metadata: {
tags: ["post", "新想法"],
type: "note",
title: title,
content: content,
sources: ["xlog"],
// summary: "",
attributes: [
{
value: "play-xlog-01",
trait_type: "xlog_slug",
},
],
attachments: [
{
name: "cover",
address: "",
mime_type: "",
},
],
},
mode,
},
})
.then((res) => {
console.log(JSON.stringify(res.data, null, 2));
})
.catch((err) => {
console.log(3, err);
});
};
最後是刪除文章
const deletePost = async () => {
const characterId = '';
const noteId = '';
const deletePost =
base + `/v1/siwe/contract/characters/${characterId}/notes/${noteId}`;
axios
.request({
method: "delete",
url: deletePost,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
console.log(2, res.data);
})
.catch((err) => {
console.log(3, err);
});
};
踩坑經驗#
創建文章的 body,和獲取列表頁的返回體不一樣,主要是 content 是否多包一層,也就是請求參數和入庫的數據不一樣,稍微有點坑,最終創建文章的參數可用。
官方對入參比較寬容,啥參數都要,笑死,錯誤參數會導致列表頁展示不出來,排查好久眼都花了,還是萬分小心,仔細核對,等熟悉了官方源碼,得加一加限制。
展望#
有了上述的 api 實踐內容,我們未來可以做到以下事情:
- 編寫 node 腳本,實現文章的創建和更新
- 比如在 github actions 裡,每次推送了 markdown 可以調用函數,同步到 xlog
- 比如在 obsidian 裡編寫插件,單擊右鍵上傳到 xlog
- 接入自己的 cms 後台,進行文章管理
接下來,我會改造我的 obsidian 插件,實現上傳文件到 xlog
敬請期待。