n8n Workflow: 在Karakeep 打星就分享到 Twitter

上週從Pocket跳船到 Karakeep 之後,由於 IFTTT 不支援 Karakeep,之前 IFTTT 上的「打星就分享到 twitter流程」就沒得用了。

正好也想用 n8n 玩點花樣,於是就到 n8n 上試著寫個 workflow 來做這件事。

個人血淚提醒:
使用 docker compose down 時千萬不要加 “- v” (也就是不要下 docker compose down -v)。
一般來說 -v 會讓人想到 –verbose;但在 docker compose 裡是把已經建好的 volume 移除 (remove),包含之前所有輸入的資料,workflow,以及設定。

我寫到一半要加個功能,想要rebuild container時,粗心大意直接 copy & paste chatGPT 給的指令,然後兩天的心血就…消失了。後面問 chatGPT 他還理直氣壯說我又沒有說要保存 volume 資料… Orz

以下的內容是先請 AI 分析我寫的 workflow,然後我再補充。
這樣產生說明文件的方式真的很快。不過某些我覺得重要的節點(node)還是會被略過。得要手動指定或是手動加入。

workflow 拆成兩個部份:

  • Karakeep_webhook_queue.json:接收 Karakeep 更新書籤時所送出的 webhook,稍後由 Karakeep_share 批次處理。
  • Karakeep_share.json:把已經打星的書籤分享到 Twitter,並把已經分享過的書籤歸到 “shared” 列表中。

我請AI 從功能、架構與流程、重要節點的設定方式等三個面向進行分析。


1. Karakeep_webhook_queue.json

Karakeep_webhook_queue.json

功能

Karakeep_webhook_queue.json 是一個專為處理 Karakeep webhook 事件設計的 workflow。其主要目的是接收來自 Karakeep 的 webhook 請求(例如書籤更新事件),將相關數據(如 jobIdbookmarkId)緩存到文件中,並在一定時間後觸發另一個 workflow 進行批次處理。這種設計特別適用於應對可能短時間內大量觸發的 webhook 事件,通過緩存避免過載並實現批次處理。

架構與流程簡介

這個 workflow 的架構由以下核心節點組成:

  • Webhook 節點:作為入口,接收 POST 請求。
  • Set 節點:從請求中提取關鍵數據並格式化。
  • Execute Command 節點:用於文件操作,包括寫入隊列 (queue) 和設置獨占標誌。
  • IF 節點:判斷是否成為處理者。
  • Wait 節點:延遲執行以收集更多事件。
  • Execute Workflow 節點:觸發後續處理。

流程如下

  1. Webhook 節點接收 Karakeep 的 POST 請求。
  2. Set 節點提取 jobIdbookmarkId,生成 JSON 對象。
  3. Execute Command 節點將數據追加到 /files/karakeep-webhook-queue.txt,使用 flock 避免同時寫入的競爭條件 (race condition)。
  4. 另一個 Execute Command 節點嘗試設置獨占標誌 /files/karakeep-process.flag,檢查是否已有處理者在運行。
  5. 如果成為處理者,Wait 節點等待 10 秒,收集更多 webhook 事件 (當在 Karakeep 使用批次處理時)。
  6. Execute Workflow 節點觸發 “Karakeep_share” 這個 workflow,進行後續的處理。
  7. 最後清除隊列文件和獨占標誌。

重要節點的設定方式

以下說明各重要節點在 n8n 中的具體設定方式:

  • Webhook 節點
    • 節點類型n8n-nodes-base.webhook
    • 設定步驟
      1. 在 n8n 介面中添加 Webhook 節點。
      2. 設置 HTTP MethodPOST,用於接收 Karakeep 的 webhook 請求。
      3. 設置 Pathkarakeep-bookmark-update-XXXX,作為此 workflow 的唯一路徑。
      4. Authentication 中選擇 Header Auth,並配置自定義憑證(例如 API 密鑰),確保安全性。這裡的 key 要跟 Karakeep 那邊的 webhook 觸發設定一致。
    • 用途:此節點作為 workflow 的觸發點,接收 Karakeep 請求並傳遞數據給後續節點。
  • Set 節點
    • 節點類型n8n-nodes-base.set
    • 設定步驟
      1. 添加 Set 節點並選擇 Mode 為 JSON,以手動定義輸出數據。
      2. JSON Output 欄位中輸入表達式,例如:
        {"jobid": "{{ $json.body.jobId }}", "bookmarkid": "{{ $json.body.bookmarkId }}", "time": {{ $now }}, "log_str": "{{ $now.toISO() }} - {{ $json.body.jobId }} - {{ $json.body.bookmarkId }}"}
      3. 確保表達式正確引用上游 Webhook 節點的輸入數據(如 body.jobId)。
    • 用途:用於從 webhook 請求中提取並格式化關鍵數據,生成結構化的 JSON 輸出。
  • Execute Command 節點(寫入隊列)
    • 節點類型n8n-nodes-base.executeCommand
    • 設定步驟
      1. Command 欄位輸入以下 shell 命令:
        echo "{{ $json.log_str }}" | flock -x /files/karakeep-webhook-queue.txt tee -a /files/karakeep-webhook-queue.txt
      2. 確保命令中使用 $json.log_str 引用 Set 節點生成的日誌字符串。
    • 用途:通過 flock 實現文件鎖定,將資料地安全追加到隊列文件中,避免同時寫入問題。
  • Execute Command 節點(設立獨占標誌)
    • 節點類型n8n-nodes-base.executeCommand
    • 設定步驟
      1. Command 欄位輸入包含檔案檢查與鎖定邏輯的腳本,例如檢查檔案是否存在、過期處理(10 分鐘)並使用 flock 設置標誌。
      2. 確保腳本輸出 “Success” 或其他標識,以便後續 IF 節點判斷。
    • 用途:管理獨占標誌,確保同一時間只有一個 workflow 實例處理隊列。
  • IF 節點
    • 節點類型n8n-nodes-base.if
    • 設定步驟
      1. Conditions 中添加一個條件:
        • 選擇 $json.stdout(前一節點的輸出)。
        • 設置操作為 Contains,值為 "Success"
      2. 配置 True 和 False 兩條分支,分別連接後續節點。
    • 用途:根據獨占標誌設置的結果,決定是否繼續執行處理邏輯。
  • Wait 節點
    • 節點類型n8n-nodes-base.wait
    • 設定步驟
      1. Amount 欄位設置為 10,單位為 Seconds
    • 用途:暫停 10 秒後再繼續,允許收集更多 webhook 事件以進行批次處理。
  • Execute Workflow 節點
    • 節點類型n8n-nodes-base.executeWorkflow
    • 設定步驟
      1. Workflow ID 欄位選擇目標 workflow 的 ID,例如 (對應 Karakeep_share)。
      2. 啟用 Wait for Sub-Workflow 選項,設為 true,確保子流程完成後再繼續。
    • 用途:觸發另一個 workflow 來處理緩存的隊列數據。

2. Karakeep_share.json

Karakeep_share.json

功能

Karakeep_share.json 的目標是從 Karakeep 獲取標記為收藏的書籤,過濾出鏈接類型書籤,根據其內容(note 或 highlight)生成 Twitter 推文內容,然後通過 IFTTT 的webhook發送推文 (這裡我偷懶了 XD),最後將書籤移動到 “shared” 列表。 (記得先在 Karakeep 建好 shared 這個list, 大小寫有分)

架構與流程簡介

這個 workflow 的架構包括:

  • Manual Trigger / Execute Workflow Trigger 節點:觸發點。
  • HTTP Request 節點:與 Karakeep API 交互。
  • Split Out / Filter 節點:處理書籤數據。
  • Set / IF / Code 節點:生成推文。
  • Merge 節點:合併數據流。
  • HTTP Request 節點:把推文送到 IFTTT 。

流程如下

  1. 觸發 workflow。
  2. HTTP Request 節點查詢收藏書籤。
  3. Split Out 節點拆分書籤列表。
  4. Filter 節點篩選鏈接類型書籤。
  5. Set 節點提取關鍵資料。
  6. IF 節點檢查是否有 note
  7. 如果沒有 note,檢查是否有 highlight。若有,取得 highlight 內容。
  8. 如果連 highlight 也沒有,那就是最一般的情況。
  9. 用 Code 節點生成推文。
  10. 用 Merge 節點合併結果。
  11. 移動書籤並發送推文。

重要節點的設定方式

以下說明各重要節點在 n8n 中的具體設定方式:

  • HTTP Request 節點(查詢書籤)
    • 節點類型n8n-nodes-base.httpRequest
    • 設定步驟
      1. 設置 URLhttps://[karakeep site]/api/v1/bookmarks/search
      2. Authentication 中選擇 Generic Credential Type,並配置 Bearer Auth 憑證。 這裡的 API是要存取 Karakeep API 用的。需要在 Karakeep 的 user -> API 那邊建立並取得 API key。
      3. Query Parameters 中添加:
        • q=is:fav -list:shared -list:Personal
        • includeContent=false
        • limit=20
    • 用途:從 Karakeep API 獲取符合條件的書籤數據。
  • Filter 節點
    • 節點類型n8n-nodes-base.filter
    • 設定步驟
      1. Conditions 中設置條件:
        • $json.content.type 等於 "link"
    • 用途:篩選出鏈接類型的書籤,過濾掉其他類型。
  • Set 節點
    • 節點類型n8n-nodes-base.set
    • 設定步驟
      1. Assignments 中定義多個字段,例如:
        • id{{ $json.id }}
        • note{{ $json.note }}
        • content.url{{ $json.content.url }}
    • 用途:提取要分享的書籤內容資料。
  • IF 節點(檢查 note)
    • 節點類型n8n-nodes-base.if
    • 設定步驟
      1. Conditions 中設置:
        • $json.note 不為空(Not Empty)。
      2. 配置 True 和 False 分支。
    • 用途:判斷書籤是否有 note,決定推文生成方式。
  • HTTP Request 節點(取得Highlight)
    • 節點類型n8n-nodes-base.httpRequest
    • 設定步驟
      1. 設置 URLhttps://[karakeep site]/api/v1/bookmarks/{{ $json.id }}/highlights。
      2. Authentication 中選擇 Generic Credential Type,並配置 Bearer Auth 憑證。
      3. Query Parameters 中添加:
        • bookmarkId={{ $json.id }}
    • 用途:從 Karakeep API 獲取符合條件的書籤數據。
  • IF 節點(檢查 highlight)
    • 節點類型n8n-nodes-base.if
    • 設定步驟
      1. Conditions 中設置:
        • {{ $json.highlights[0].bookmarkId }} 不為空(Not Empty)。
      2. 配置 True 和 False 分支。
    • 用途:判斷書籤是否有 highlight,決定推文生成方式。
  • Code 節點(生成推文)
    • 節點類型n8n-nodes-base.code
    • 設定步驟
      1. 添加 Code 節點並設置 ModeRun Once for Each Item
      2. JS Code 欄位輸入自定義 JavaScript,例如計算字數、裁剪內容並生成推文。
    • 用途:處理數據並生成符合 Twitter 要求的推文內容。

以下是我用來產生推文的程式碼,內容有點雜亂,供參考參考。


for note:

    // 計算字數的函式
function countTwitterLength(text) {
  const URL_LENGTH = 23;
  let length = 0;

  for (let char of text) {
    length += char.charCodeAt(0) <= 0x7f ? 1 : 2;
  }

  return length;
}

const URL_LENGTH = 23;
const maxLen = 280;
const ending = '';

// note 推文格式: Note (無引號)  [讀] 標題 網址
// 計算標題長度
const tweet_title = '[讀] ' + $json.content.title;
const tweet_title_length = countTwitterLength(tweet_title);
$json.tweet_title = tweet_title;
$json.tweet_title_length = tweet_title_length;

const max_note_length = maxLen - URL_LENGTH - tweet_title_length - 3 - 3; // ... + 換行

// 裁剪 note 部份 
let result = '';
let length = 0;
const tweet_note_orig = $json.note;
const tweet_note_orig_length = countTwitterLength(tweet_note_orig);

for (let i = 0; i < tweet_note_orig.length; i++) {
  const char = tweet_note_orig[i];
  const charLen = char.charCodeAt(0) <= 0x7f ? 1 : 2;

  // 若加上這個字已經超過 maxLen,就停止,並補尾碼
  if (length + charLen > max_note_length) {
    result += ending;
    break;
  }

  result += char;
  length += charLen;
}

$json.tweet_note = result;

// 加回 標題 和 網址(每個網址算固定 23 字元)

$json.tweet_msg = $json.tweet_note + "\n\n" + $json.tweet_title + "\n" + $json.content.url;


return $json;

for hightlight:

// 計算字數的函式
function countTwitterLength(text) {
  const URL_LENGTH = 23;
  let length = 0;

  for (let char of text) {
    length += char.charCodeAt(0) <= 0x7f ? 1 : 2;
  }

  return length;
}

const URL_LENGTH = 23;
const maxLen = 280;
const ending = '';

// note 推文格式: Note (無引號)  [讀] 標題 網址
// 計算標題長度
const tweet_title = '[讀] ' + $json.content.title;
const tweet_title_length = countTwitterLength(tweet_title);
$json.tweet_title = tweet_title;
$json.tweet_title_length = tweet_title_length;

const max_note_length = maxLen - URL_LENGTH - tweet_title_length - 3 - 3 - 2; // ... + 換行 + 引號

// 裁剪 highlight 部份 
let result = '';
let length = 0;
const tweet_note_orig = $json.highlights[0].text
const tweet_note_orig_length = countTwitterLength(tweet_note_orig);

for (let i = 0; i < tweet_note_orig.length; i++) {
  const char = tweet_note_orig[i];
  const charLen = char.charCodeAt(0) <= 0x7f ? 1 : 2;

  // 若加上這個字已經超過 maxLen,就停止,並補尾碼
  if (length + charLen > max_note_length) {
    result += ending;
    break;
  }

  result += char;
  length += charLen;
}

$json.tweet_highlight = result;

// 加回 標題 和 網址(每個網址算固定 23 字元)

$json.tweet_msg = '"' + $json.tweet_highlight + '"' + "\n\n" + $json.tweet_title + "\n" + $json.content.url;


return $json;
  • Merge 節點
    • 節點類型n8n-nodes-base.merge
    • 設定步驟
      1. 添加 Merge 節點。
      2. 設置 ModeCombine,並選擇 Combine ByPosition
    • 用途:合併來自不同分支的數據流,準備後續處理。
  • HTTP Request 節點(get lists)
    • 節點類型:n8n-nodes-base.httpRequest
    • 設定步驟
      1. 設置 URL 為 https://[karakeep site]/api/v1/lists。
      2. Authentication 中選擇 Generic Credential Type,並配置 Bearer Auth 憑證。
      3. 啟用 Execute Once 選項設為 true,以避免對每條書籤數據重複請求。我只是要取得 “shared” 這個 list 的 id 而已…
      4. 可選:在 Response Format 中選擇 JSON,確保返回數據易於解析。
    • 用途:從 Karakeep API 獲取所有列表的數據,包括 “shared” 列表的 ID,用於後續將書籤移動到指定列表。
  • HTTP Request 節點(send to buffer via IFTTT)
    • 節點類型:n8n-nodes-base.httpRequest
    • 設定步驟
      1. 設置 Method 為 POST。
      2. 設置 URL 為 IFTTT 的 webhook URL,例如 https://maker.ifttt.com/trigger/[IFTTT webhook trigger]/with/key/[IFTTT_webhook_key]。
      3. Body Parameters 中添加:
        • 名稱:value1,值:{{ $json.tweet_msg }}(引用生成的推文內容)。
      4. Options 中啟用 Send Body 並選擇 JSON 格式。
    • 用途:通過 IFTTT 的 webhook 將生成的推文內容發送到 Buffer,進而在 Twitter 上發布。
  • HTTP Request 節點(move to shared list)
    • 節點類型:n8n-nodes-base.httpRequest
    • 設定步驟
      1. 設置 Method 為 PUT。
      2. 設置 URL 為 https://[karakppe site]/api/v1/lists/{{ $json.list_id }}/bookmarks/{{ $json.id }},其中 list_id 從 get lists 節點獲取,id 為書籤 ID。
      3. Authentication 中選擇 Generic Credential Type,並配置 Bearer Auth 憑證。
      4. 啟用 Never Error 選項設為 true,以避免因 API 錯誤中斷 workflow。
    • 用途:將已分享的書籤移動到 “shared” 列表,標記其完成狀態。


Future Work:

之後還有一些 future work 可以做,像是:

  • 不要透過 IFTTT 去發推文,直接從 n8n 去呼叫 twitter API。
  • 在 n8n 中加上類似 buffer.app 定時發文的功能。
  • 較長的文字不要裁剪,改用回覆的方式支援長文。
  • Thread 發文支援

這些就以後有空再實作練習囉。

發佈留言