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 請求(例如書籤更新事件),將相關數據(如 jobId 和 bookmarkId)緩存到文件中,並在一定時間後觸發另一個 workflow 進行批次處理。這種設計特別適用於應對可能短時間內大量觸發的 webhook 事件,通過緩存避免過載並實現批次處理。

架構與流程簡介

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

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

流程如下:

  1. Webhook 節點接收 Karakeep 的 POST 請求。
  2. Set 節點提取 jobId 和 bookmarkId,生成 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 Method 為 POST,用於接收 Karakeep 的 webhook 請求。
      3. 設置 Path 為 karakeep-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. 設置 URL 為 https://[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. 設置 URL 為 https://[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 節點並設置 Mode 為 Run 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. 設置 Mode 為 Combine,並選擇 Combine By 為 Position。
    • 用途:合併來自不同分支的數據流,準備後續處理。
  • 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 發文支援
  • …

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

在〈n8n Workflow: 在Karakeep 打星就分享到 Twitter〉中有 3 則留言

  1. 你好,请问可以分享 Karakeep_webhook_queue.json 和 Karakeep_share.json 这两个文件吗

    Reply

發佈留言