iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI 自動化

用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文系列 第 14 篇

Day 14|同一個網址不重複存:先查再決定新增或更新

  • 分享至 

  • xImage
  •  

昨天講到抓不到內文時怎麼退化成只憑網址跟備註分類,今天要處理另一個更常發生的狀況:同一個網址被我重複傳進來,line-notion-bot 該怎麼辦?

會遇到這個情況的原因很單純,我常常忘記自己已經傳過某篇文章,尤其是那種在不同群組、不同聊天室看到好幾次的熱門連結。如果每次傳都無腦新增一筆,Notion 資料庫很快就會堆出一堆內容一模一樣、只有分類結果可能不同的重複頁面,時間久了根本沒辦法信任這個資料庫。

負責處理這件事的是 createNotionPage,重點在寫入之前先查一次:

async function createNotionPage(env, { title, category, tags, summary, url }) {
  // 先查有沒有相同連結的既有頁面,避免重試或重傳造成重複資料
  const existingId = await findPageByUrl(env, url);

  const properties = {
    [PROP.title]: { title: [{ text: { content: title.slice(0, 200) } }] },
    [PROP.category]: { select: { name: category } },
    [PROP.tags]: { multi_select: tags.map((t) => ({ name: t.slice(0, 50) })) },
    [PROP.url]: { url },
    [PROP.summary]: { rich_text: [{ text: { content: summary.slice(0, 1900) } }] },
  };

  if (existingId) {
    const res = await fetch(`https://api.notion.com/v1/pages/${existingId}`, {
      method: 'PATCH',
      headers: { /* ... */ },
      body: JSON.stringify({ properties }),
    });

    if (!res.ok) throw new Error(`Notion 更新失敗(${res.status})...`);

    // 內容區塊也補上這次的摘要,當作更新紀錄(不動原本的區塊)
    await appendNote(env, existingId, `(重新分類更新)${summary}`);

    const page = await res.json();
    return { url: page.url, updated: true };
  }

  // 沒查到既有頁面才新增
  const res = await fetch('https://api.notion.com/v1/pages', {
    method: 'POST',
    headers: { /* ... */ },
    body: JSON.stringify({
      parent: { database_id: env.NOTION_DATABASE_ID },
      properties,
      children: [/* 段落 + bookmark */],
    }),
  });

  const page = await res.json();
  return { url: page.url, updated: false };
}

findPageByUrl 做的事很直接,拿「連結」這個 URL 型態的欄位去查資料庫,篩選條件用 equals 完全比對:

async function findPageByUrl(env, url) {
  const res = await fetch(
    `https://api.notion.com/v1/databases/${env.NOTION_DATABASE_ID}/query`,
    {
      method: 'POST',
      headers: { /* ... */ },
      body: JSON.stringify({
        filter: { property: PROP.url, url: { equals: url } },
        page_size: 1,
      }),
    },
  );

  if (!res.ok) return null;
  const data = await res.json();
  return data.results[0]?.id || null;
}

查到就回傳那筆頁面的 ID,查不到(或查詢本身失敗)就回傳 null,後面 createNotionPage 只認這個布林值:有 ID 就走 PATCH 更新既有頁面,沒有就走 POST 新增一筆。分類、標籤、摘要這幾個欄位不管新增還更新都是同一組 properties,差別只在往哪個 API 端點送、用哪種 HTTP method。

更新的時候有個小地方我還蠻在意的,就是 appendNote 那一行。更新頁面的 properties(分類、標籤、摘要)是直接覆蓋掉舊值,但頁面內文那塊沒有跟著清掉重寫,而是在下面補一段「(重新分類更新)」開頭的新段落。原因是分類這種結構化欄位本來就該以最新一次判斷為準,沒有保留舊值的理由;但內文是我在 Notion 裡手動加註記、標待辦累積出來的東西,這些都是自己另外投入的心力,被 AI 這次重新跑分類就整段清掉太可惜,所以選擇疊加而不是覆蓋。

回頭去看 handleTextMessage 怎麼用這個 updated 標記,就知道為什麼要特地回傳這個布林值:

const saved = await createNotionPage(env, { ...result, url });

const lines = [
  saved.updated ? `🔄 ${result.title}(已更新既有筆記)` : `✅ ${result.title}`,
  ...
];

同一支函式回來,我在 LINE 上收到的訊息長得不一樣:新增是 ✅,更新是 🔄 並附註「已更新既有筆記」。這個區別不是裝飾用的,是真的在提醒我「這篇你傳過了」,讓我在滑手機的當下就能發現自己重複傳送,而不是等哪天翻 Notion 才納悶怎麼同一篇文章存了兩次還內容不一樣。

這套「用連結欄位查詢既有頁面」的邏輯,其實在專案裡出現了兩次。createNotionPage 用它來決定新增或更新,resolvePageId(處理「刪除 / 註記 / 分類 / 待辦」這些指令用的)也用幾乎一樣的查詢語法去找頁面,只是多了一個判斷:如果貼進來的是 Notion 頁面連結本身,直接從網址尾端抓 32 碼的十六進位當 page ID,不用查資料庫;只有原始文章網址才會真的打一次 API 查詢。這兩支函式沒有真的合併成一個,各自維護一份幾乎重複的查詢程式碼,好處是各自要加什麼判斷都不用擔心影響到對方,壞處就是哪天 PROP.url 這個欄位名稱要改,得記得兩邊一起改。這種程度的重複我還可以接受,換來的是兩處呼叫情境不同時,改起來互不牽連。

明天要來看 LINE 上那些「搜尋、刪除、註記、標待辦」的指令,怎麼分工:哪部分交給簡單的規則判斷就好,哪部分才真的用得到 AI。


上一篇
Day 13|抓不到內文的網站:只憑網址和備註也要存得進去
下一篇
Day 15|指令用規則、內容用 AI:用 LINE 搜尋、刪除、註記、標待辦
系列文
用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言