訂閱免費電子報

不定時分享數位工具、工作管理術、職場等生活心得給你

從零打造 Threads 文分類收藏機器人(附完整程式碼)

臺灣人非常喜歡 Threads, Threads 被號稱是臺灣人最大的 LINE 群組,日活躍使用量超過了 1.3 億人次,台灣用戶流量貢獻度高達 21.83%,勇奪全球第一名!

Threads 上常常有很多好文,包含了日前很流行的「我獨自宣布」,網羅了各種實用的資訊,我自己收藏了AI 工具、各地美食、好用的美妝、很靈的算命師等等。但Threads 目前儲存的功能實在太陽春了!沒有分類日後非常難找到想要看的文章,於是就和 AI 一起開發了「Threads 文分類收藏器」!

這篇文章會直接給你程式碼讓你可以直接使用這段程式碼做一個自己的「Threads 文分類收藏器

Threads 文分類收藏器的功能有什麼?

  1. 當你在滑脆時看到實用的文章,利用小飛機分享功能,把網址發到指定 LINE 機器人帳號上
  2. 分享時打上簡單的該文標題
  3. AI 會自動判別此標題是什麼分類
  4. 自動幫你儲存網址以及分類該篇文章

整理到 Notion 後就會是這個樣子

額外增加功能!

  1. 當你打出 /list :LINE 機器人會回覆近 10 筆的收藏 Threads 文
  2. 當你打出 /tag:LINE 機器人會跳出分類按鈕,按下按鈕可看到該分類的近 10 筆 Threads 文
  3. 當你打出 *關鍵字:LINE 機器人會搜尋該關鍵字的相關 Threads 文
  4. 第 1 和第 2 的文字直接置入在 LINE 圖文選單上,按下圖文選單按鈕就可以直接找出來了喔!

我也要一個 Threads 文收藏器!前置作業

首先有 5 個前置作業要進行

  1. 建立 LINE 官方帳號取得 LINE Channel Access Token,請到這篇文章看詳細教學步驟:AI 建立 LINE 機器人前置作業:如何取得 LINE Channel Access Token 完整教學
  2. 建立 Notion 資料庫
    • 在 Notion 建立一個新的 Database,欄位至少要有
    • 標題:類型為「Title」
    • URL:類型為「URL」
    • 分類:類型為「Multi-select」
      • 你可以先把預設分類選項建好,例如:
        • 行銷、數位工具、學習資源、AI、追星、身心靈、玄學、生活、其他
        • 這是我自己的分類,請依照自身需求調整
  3. 取得這個 Database 的 Notion Database ID
    • 打開整個頁面形式的 Notion 資料庫
    • 複製網址
    • 你的網址會長這樣(範例):https://www.notion.so/你的工作區名稱/ABC12345efgh6789ijkl0123mnop4567?v=xxxxx
    • 擷取 database_id,請複製:ABC12345efgh6789ijkl0123mnop4567
    • 規則是:位於 / 後面 ?v= 前面,是一長串 32 碼英數字
    • 這一串就是你的 database_id
  4. 取得 Notion API Key,請到這篇文章看詳細教學步驟:LINE 串接 Notion 前必做設定:Notion 資料庫與 API 前置作業完整說明
  5. 準備 OpenAI API Key,,請到這篇文章看詳細教學步驟:串接 AI 前必做設定:API Key 申請、用途與費用完整說明

Threads 文收藏器修改分類

請 AI 直接幫你改分類,將下方程式碼貼到 AI,並輸入以下參考指令「我要修改此 Apps Script 程式碼其中的「分類」,調整為 AI、數位工具、職涯、美食、旅遊、其他這 6 類,只要改分類就好,其他內容不要調整,並給我完整可直接覆蓋的程式碼」,並可額外附上分類說明,再將 AI 修改好的程式碼保存下來。

程式碼
/************ 全域設定:從 Script Properties 讀取秘密 ************/
const SCRIPT_PROP = PropertiesService.getScriptProperties();

const CHANNEL_ACCESS_TOKEN = SCRIPT_PROP.getProperty('LINE_CHANNEL_ACCESS_TOKEN');
const OPENAI_API_KEY       = SCRIPT_PROP.getProperty('OPENAI_API_KEY');
const NOTION_API_KEY       = SCRIPT_PROP.getProperty('NOTION_API_KEY');
const NOTION_DATABASE_ID   = SCRIPT_PROP.getProperty('NOTION_DATABASE_ID');

// 固定分類清單
const ALLOWED_CATEGORIES = [
  '行銷',
  '數位工具',
  '學習資源',
  'AI',
  '追星',
  '身心靈',
  '玄學',
  '生活',
  '其他'
];

/************ doGet:給 LINE Verify / 瀏覽器測試用 ************/
function doGet(e) {
  return ContentService
    .createTextOutput(JSON.stringify({ status: 'ok' }))
    .setMimeType(ContentService.MimeType.JSON);
}

/************ 入口:處理 LINE Webhook ************/
function doPost(e) {
  if (!e || !e.postData || !e.postData.contents) {
    Logger.log('No postData found');
    return ContentService.createTextOutput('OK');
  }

  const body = JSON.parse(e.postData.contents);
  Logger.log('LINE body: ' + JSON.stringify(body));

  if (!body.events || body.events.length === 0) {
    return ContentService.createTextOutput('no events');
  }

  body.events.forEach(event => {
    try {
      handleEvent(event);
    } catch (err) {
      Logger.log('handleEvent error: ' + err);
      if (event.replyToken) {
        reply_message(event.replyToken, '處理訊息時發生錯誤,等等再試呦~');
      }
    }
  });

  return ContentService.createTextOutput('OK');
}

/************ 判斷並處理單一事件 ************/
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    reply_message(event.replyToken, '請先傳「標題」,再傳「網址」歐。');
    return;
  }

  const text = event.message.text.trim();
  const replyToken = event.replyToken;
  const userId = (event.source && event.source.userId) ? event.source.userId : 'unknown';

  // ✅ 忽略「請輸入 *關鍵字」所有變形,不當成標題
  if (/^請輸入\s*\*?\s*關鍵字$/i.test(text)) {
    Logger.log('Ignore keyword hint text: ' + text);
    return;
  }

  // 說明
  if (text === '說明' || text.toLowerCase() === 'help') {
    const helpMsg = [
      '📌 使用方式(分兩則訊息):',
      '',
      '第 1 則:標題',
      '第 2 則:網址',
      '',
      '例:',
      '第 1 則:用 AI 寫簡報的 5 種方法',
      '第 2 則:https://example.com/ai-slide?utm_source=fb',
      '',
      'AI 會自動幫你分類到這幾類之一:',
      '・行銷',
      '・數位工具(不含 AI 功能)',
      '・學習資源',
      '・AI',
      '・追星',
      '・身心靈(心理學、顯化)',
      '・玄學(算命、星盤、通靈)',
      '・生活',
      '・其他',
      '',
      '指令:',
      '/list → 最新 10 筆書籤(Flex)',
      '/tag → 顯示分類按鈕',
      '*AI簡報 → 搜尋「標題」包含「AI簡報」的文章(Flex)'
    ].join('\n');

    reply_message(replyToken, helpMsg);
    return;
  }

  // *關鍵字 搜尋(例如:*AI簡報)
  if (text.startsWith('*')) {
    const keyword = text.slice(1).trim();
    if (!keyword) {
      reply_message(replyToken, '請在 * 後輸入要搜尋的關鍵字,例如:*AI簡報');
      return;
    }
    const items = notionQueryBySearch(keyword, 10);
    replyBookmarkFlex(replyToken, items, '🔍 搜尋標題:「' + keyword + '」');
    return;
  }

  // 指令模式(/ 開頭)
  if (text.startsWith('/')) {
    handleCommand(text, replyToken);
    return;
  }

  /************ 一般模式:支援「同一則標題 + 網址」,以及原本兩段式流程 ************/
  const urlRegex = /(https?:\/\/\S+)/i;
  const match = text.match(urlRegex);

  const cache = CacheService.getScriptCache();
  const key = 'title_' + userId;
  const cached = cache.get(key);

  if (match) {
    // 訊息裡含有網址
    const urlInText = match[1];
    const before = text.slice(0, match.index).trim(); // URL 前面的文字

    // ✅ 情境一:沒有暫存標題,但這一則本身同時有「標題 + URL」
    if (!cached && before) {
      const title = before;
      const url = extractCleanUrl(urlInText);

      if (!url) {
        reply_message(replyToken, '網址格式有點怪,請再貼一次完整網址。');
        return;
      }

      const category = classifyCategoryWithOpenAI(url, title);
      const notionOk = createNotionPage({
        title,
        url,
        categories: [category]
      });

      if (!notionOk) {
        reply_message(replyToken, '寫入 Notion 失敗,請檢查 Notion 設定或稍後再試。');
        return;
      }

      replySavedFlex(replyToken, title, url, category);
      return;
    }

    // ✅ 情境二:本來就有暫存標題,或沒有前置文字 → 當作第二則網址處理
    handleUrlMessage(userId, text, replyToken);
    return;
  }

  // 沒有任何網址 → 視為標題
  handleTitleMessage(userId, text, replyToken);
}

/************ 判斷是否為網址(目前已不直接使用,但保留) ************/
function isUrlText(text) {
  const urlRegex = /(https?:\/\/\S+)/i;
  return urlRegex.test(text);
}

/************ 處理第一則:標題 ************/
function handleTitleMessage(userId, titleText, replyToken) {
  const cache = CacheService.getScriptCache();
  const key = 'title_' + userId;

  const data = {
    title: titleText,
    ts: Date.now()
  };

  // 暫存 6 小時
  cache.put(key, JSON.stringify(data), 6 * 60 * 60);

  reply_message(
    replyToken,
    '收到訊息 ( ˶˙º̬˙˶ )୨:\n' + titleText + '\n\n正在儲存中'
  );
}

/************ 處理第二則:網址(加上「再讀一次 Cache」的防呆) ************/
function handleUrlMessage(userId, urlText, replyToken) {
  const cache = CacheService.getScriptCache();
  const key = 'title_' + userId;
  let cached = cache.get(key);

  // 多給一次機會:若第一次沒讀到,稍微等一下再試一次
  if (!cached) {
    Utilities.sleep(300); // 0.3 秒
    cached = cache.get(key);
  }

  if (!cached) {
    reply_message(
      replyToken,
      '找不到上一則標題,請重新依序傳:\n\n第 1 則:標題\n第 2 則:網址'
    );
    return;
  }

  let data;
  try {
    data = JSON.parse(cached);
  } catch (err) {
    Logger.log('cache parse error: ' + err);
    reply_message(replyToken, '暫存標題時出了點問題,請重新傳一次標題和網址。');
    return;
  }

  const title = data.title;
  const url = extractCleanUrl(urlText);

  if (!url) {
    reply_message(replyToken, '網址格式有點怪,請再貼一次完整網址。');
    return;
  }

  // 用 AI 分類
  const category = classifyCategoryWithOpenAI(url, title);

  // 寫入 Notion
  const notionOk = createNotionPage({
    title,
    url,
    categories: [category]
  });

  if (!notionOk) {
    reply_message(replyToken, '寫入 Notion 失敗,請檢查 Notion 設定或稍後再試。');
    return;
  }

  // 清掉暫存標題
  cache.remove(key);

  // 用 Flex 回覆儲存結果
  replySavedFlex(replyToken, title, url, category);
}

/************ 抽出乾淨網址(移除 ? 後追蹤碼) ************/
function extractCleanUrl(text) {
  const urlRegex = /(https?:\/\/\S+)/i;
  const match = text.match(urlRegex);
  if (!match) return '';

  let url = match[1];

  if (url.includes('?')) {
    url = url.split('?')[0];
  }

  return url;
}

/************ 指令功能(/list /tag /search) ************/
function handleCommand(text, replyToken) {

  // /list → 最新 10 筆(Flex)
  if (text === '/list') {
    const items = notionQueryLatest(10);
    replyBookmarkFlex(replyToken, items, '📚 最新 10 筆書籤');
    return;
  }

  // /tag → 顯示九個分類按鈕(quick reply)
  if (text === '/tag') {
    replyTagMenu(replyToken);
    return;
  }

  // /tag 類別(例如:/tag AI)
  if (text.startsWith('/tag ')) {
    const tag = text.replace('/tag', '').trim();
    if (!tag) {
      reply_message(replyToken, '請輸入:/tag AI 或 /tag 行銷');
      return;
    }
    const items = notionQueryByTag(tag, 10);
    replyBookmarkFlex(replyToken, items, '🏷 分類:' + tag);
    return;
  }

  // /search 關鍵字(相容用,也走 Flex)
  if (text.startsWith('/search')) {
    const keyword = text.replace('/search', '').trim();
    if (!keyword) {
      reply_message(replyToken, '請輸入:/search AI 或直接用 *AI 搜尋也可以');
      return;
    }
    const items = notionQueryBySearch(keyword, 10);
    replyBookmarkFlex(replyToken, items, '🔍 搜尋標題:「' + keyword + '」');
    return;
  }

  reply_message(replyToken, '不認識的指令,可輸入「說明」。');
}

/************ /tag 用的分類按鈕 quick reply ************/
function replyTagMenu(replyToken) {
  const items = ALLOWED_CATEGORIES.map(function (name) {
    return {
      type: 'action',
      action: {
        type: 'message',
        label: name,
        text: '/tag ' + name
      }
    };
  });

  const payload = {
    replyToken: replyToken,
    messages: [
      {
        type: 'text',
        text: '請選擇要查看的分類:',
        quickReply: {
          items: items
        }
      }
    ]
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'post',
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    payload: JSON.stringify(payload)
  });
}

/************ 呼叫 OpenAI:只做分類 ************/
function classifyCategoryWithOpenAI(url, title) {
  try {
    const systemPrompt = '你是書籤分類助手,只能輸出 JSON。';
    const userPrompt =
      '請依照以下內容,將書籤分類到下列「唯一一個」類別之一:\n' +
      '可用類別:["行銷","數位工具","學習資源","AI","追星","身心靈","玄學","生活","其他"]\n\n' +
      '分類原則:\n' +
      '1. 「數位工具」:不包含 AI 功能的一般數位工具(如 Notion、Google 文件、排程工具等)。\n' +
      '2. 「學習資源」:不包含行銷、AI、數位工具相關的其他學習資源,例如:英文、政府補助資源等。\n' +
      '3. 「AI」:跟 AI 工具、AI 寫作、AI 程式、AI 相關教學、模型介紹有關。\n' +
      '4. 「行銷」:品牌行銷、社群經營、廣告投放、內容行銷、商業策略相關。\n' +
      '5. 「追星」:偶像、明星、演唱會、應援、粉絲活動相關。\n' +
      '6. 「身心靈」:心理學、自我覺察、顯化、心態調整、療癒等。\n' +
      '7. 「玄學」:算命、塔羅、星盤、紫微、通靈、命理等偏神祕學內容。\n' +
      '8. 「生活」:日常生活、飲食、旅遊、手作、興趣、Vlog 等。\n' +
      '9. 「其他」:以上都不適合時使用。\n\n' +
      '請只輸出 JSON,格式如下:\n' +
      '{"category":"九個類別其中之一"}\n\n' +
      '標題:' + title + '\n' +
      '網址:' + url + '\n';

    const payload = {
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: userPrompt }
      ],
      max_tokens: 50,
      temperature: 0.2
    };

    const response = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + OPENAI_API_KEY
      },
      payload: JSON.stringify(payload),
      muteHttpExceptions: true
    });

    const result = JSON.parse(response.getContentText());
    Logger.log('OpenAI classify raw: ' + JSON.stringify(result));

    const content = result.choices[0].message.content;
    Logger.log('OpenAI classify content: ' + content);

    let json;
    try {
      json = JSON.parse(content);
    } catch (err) {
      Logger.log('JSON parse error (classify): ' + err);
      return '其他';
    }

    let category = json.category || '其他';

    // 防呆:不在清單裡就歸「其他」
    if (ALLOWED_CATEGORIES.indexOf(category) === -1) {
      category = '其他';
    }

    return category;

  } catch (err) {
    Logger.log('OpenAI Error (classify): ' + err);
    return '其他';
  }
}

/************ 新增 Notion 頁面(標題 / URL / 分類) ************/
function createNotionPage(data) {
  try {
    const properties = {
      '標題': {
        title: [{ text: { content: data.title } }]
      },
      'URL': {
        url: data.url
      },
      '分類': {
        multi_select: (data.categories || []).map(name => ({ name }))
      }
    };

    const response = UrlFetchApp.fetch('https://api.notion.com/v1/pages', {
      method: 'post',
      headers: getNotionHeaders(),
      payload: JSON.stringify({
        parent: { database_id: NOTION_DATABASE_ID },
        properties
      }),
      muteHttpExceptions: true
    });

    const result = JSON.parse(response.getContentText());
    Logger.log('Notion create response: ' + JSON.stringify(result));
    return result.object === 'page';

  } catch (err) {
    Logger.log('Notion Create Error: ' + err);
    return false;
  }
}

/************ Notion 共用 header ************/
function getNotionHeaders() {
  return {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + NOTION_API_KEY,
    'Notion-Version': '2022-06-28'
  };
}

/************ Notion Query:最新 N 筆 ************/
function notionQueryLatest(limit) {
  const payload = {
    page_size: limit,
    sorts: [{ timestamp: 'created_time', direction: 'descending' }]
  };
  return fetchNotionPages(payload);
}

/************ Notion Query:分類 ************/
function notionQueryByTag(tag, limit) {
  const payload = {
    page_size: limit,
    filter: {
      property: '分類',
      multi_select: { contains: tag }
    },
    sorts: [{ timestamp: 'created_time', direction: 'descending' }]
  };
  return fetchNotionPages(payload);
}

/************ Notion Query:標題關鍵字 ************/
function notionQueryBySearch(keyword, limit) {
  const payload = {
    page_size: limit,
    filter: {
      property: '標題',
      rich_text: { contains: keyword }
    },
    sorts: [{ timestamp: 'created_time', direction: 'descending' }]
  };
  return fetchNotionPages(payload);
}

/************ Notion Query 共用方法 ************/
function fetchNotionPages(queryPayload) {
  try {
    const url = `https://api.notion.com/v1/databases/${NOTION_DATABASE_ID}/query`;

    const response = UrlFetchApp.fetch(url, {
      method: 'post',
      headers: getNotionHeaders(),
      payload: JSON.stringify(queryPayload),
      muteHttpExceptions: true
    });

    const json = JSON.parse(response.getContentText());
    Logger.log('Notion query response: ' + JSON.stringify(json));

    if (!json.results) return [];

    return json.results.map(page => {
      const props = page.properties || {};

      const titleProp = props['標題'];
      const urlProp   = props['URL'];
      const cateProp  = props['分類'];

      const title = (titleProp && titleProp.title && titleProp.title[0] && titleProp.title[0].plain_text) || '(無標題)';
      const urlValue = (urlProp && urlProp.url) || '';
      const categories = (cateProp && cateProp.multi_select || []).map(s => s.name);

      return { title, url: urlValue, categories };
    });

  } catch (err) {
    Logger.log('Notion Query Error: ' + err);
    return [];
  }
}

/************ 將書籤結果用 Flex 回覆 ************/
function replyBookmarkFlex(replyToken, items, header) {
  if (!items || !items.length) {
    reply_message(replyToken, header + '\n\n沒有資料。');
    return;
  }

  const flexContents = buildBookmarkFlexContents(items, header);
  reply_flex(replyToken, header, flexContents);
}

/************ 建立書籤清單 Flex contents(bubble / carousel) ************/
function buildBookmarkFlexContents(items, header) {
  const bubbles = items.slice(0, 10).map(function (item) {
    const title = item.title || '(無標題)';
    const cateText = (item.categories && item.categories.length)
      ? item.categories.join(' / ')
      : '無分類';
    const url = item.url || '';

    const bodyContents = [
      {
        type: 'text',
        text: title,
        weight: 'bold',
        size: 'sm',
        wrap: true
      },
      {
        type: 'text',
        text: cateText,
        size: 'xs',
        color: '#888888',
        wrap: true,
        margin: 'md'
      }
    ];

    if (url) {
      bodyContents.push({
        type: 'text',
        text: url,
        size: 'xs',
        color: '#1E88E5',
        wrap: true,
        margin: 'md'
      });
    }

    const bubble = {
      type: 'bubble',
      size: 'mega',
      header: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: header,
            size: 'xs',
            color: '#AAAAAA',
            wrap: true
          }
        ]
      },
      body: {
        type: 'box',
        layout: 'vertical',
        spacing: 'sm',
        contents: bodyContents
      }
    };

    if (url) {
      bubble.footer = {
        type: 'box',
        layout: 'vertical',
        spacing: 'sm',
        contents: [
          {
            type: 'button',
            style: 'link',
            height: 'sm',
            action: {
              type: 'uri',
              label: '開啟連結',
              uri: url
            }
          }
        ]
      };
    }

    return bubble;
  });

  if (bubbles.length === 1) {
    return bubbles[0];
  }

  return {
    type: 'carousel',
    contents: bubbles
  };
}

/************ 新增成功後的 Flex 回覆 ************/
function replySavedFlex(replyToken, title, url, category) {
  const bubble = {
    type: "bubble",
    size: "mega",
    header: {
      type: "box",
      layout: "vertical",
      contents: [
        {
          type: "text",
          text: "𖤐 已幫你存到 Notion 啦!",
          weight: "bold",
          size: "md",
          color: "#333333"
        }
      ]
    },
    body: {
      type: "box",
      layout: "vertical",
      spacing: "md",
      contents: [
        {
          type: "text",
          text: "標題",
          size: "xs",
          color: "#888888"
        },
        {
          type: "text",
          text: title,
          wrap: true,
          weight: "bold",
          size: "sm"
        },
        {
          type: "text",
          text: "分類",
          size: "xs",
          margin: "md",
          color: "#888888"
        },
        {
          type: "text",
          text: category,
          wrap: true,
          size: "sm"
        },
        {
          type: "text",
          text: "網址",
          size: "xs",
          margin: "md",
          color: "#888888"
        },
        {
          type: "text",
          text: url,
          wrap: true,
          color: "#1E88E5",
          size: "xs"
        }
      ]
    },
    footer: {
      type: "box",
      layout: "vertical",
      spacing: "sm",
      contents: [
        {
          type: "button",
          style: "link",
          height: "sm",
          action: {
            type: "uri",
            label: "開啟連結",
            uri: url
          }
        }
      ]
    }
  };

  reply_flex(replyToken, "儲存成功", bubble);
}

/************ LINE 回覆:純文字 ************/
function reply_message(replyToken, reply) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'post',
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    payload: JSON.stringify({
      replyToken,
      messages: [{ type: 'text', text: reply }]
    })
  });
}

/************ LINE 回覆:Flex ************/
function reply_flex(replyToken, altText, contents) {
  const payload = {
    replyToken: replyToken,
    messages: [
      {
        type: 'flex',
        altText: altText || '文章清單',
        contents: contents
      }
    ]
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'post',
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
    },
    payload: JSON.stringify(payload)
  });
}

建立 Threads 收藏器的 Google Apps Script 專案

  1. 接著要到 Google Apps Script 建立一個 Threads 收藏器專案
  2. 到這個網址:https://script.google.com/home
  3. 左側欄上方「+ 新專案」
  4. 把畫面上的預設程式碼全部清除
  5. 貼上上一步 AI 回覆給你的程式碼後按下儲存

Google Apps Script 左側欄上方「+ 新專案」

把畫面上的預設程式碼全部清除,貼上上一步 AI 回覆給你的程式碼後按下儲存

Google Apps Script 中設定環境變數(Script Properties)

在第一個步驟中我們取得了 LINE Channel Access Token、Notion API Key、Notion Database ID、OpenAI API Key,分別貼到 Google Apps Script 的指令碼屬性裡。

  1. 點擊左側欄下方齒輪符號
  2. 找到「新增指令碼屬性」
  3. 在「屬性」的欄位分別輸入:LINE_CHANNEL_ACCESS_TOKEN、NOTION_API_KEY、NOTION_DATABASE_ID、OPENAI_API_KEY
  4. 並在「值」的欄位輸入相對應的 LINE Channel Access Token、Notion API Key、Notion Database ID、OpenAI API Key 的值
  5. 儲存指令碼屬性

部署 Web App

  1. 點左側欄 < > 編輯器,回到程式碼頁
  2. 點擊右上「部署」
  3. 選擇「新增部署作業」
  4. 選取類型右邊齒輪「網頁應用程式」
  5. 「誰可以存取」選擇「所有人」後按下部署
  6. 需要授予存取權
  7. 跳出警告按下方「Advanced」
  8. 繼續按下方 Go to 專案名稱授予權限
  9. 複製網頁應用程式網址

點擊右上「部署」,選擇「新增部署作業」

選取類型右邊齒輪「網頁應用程式」

「誰可以存取」選擇「所有人」後按下部署

授予存取權

跳出警告按下方「Advanced」

繼續按下方 Go to 專案名稱授予權限

複製網頁應用程式網址

串接 LINE Webhook

  1. https://developers.line.biz/console/
  2. 選擇你的 LINE 機器人
  3. 選擇上方頁籤「Messaging API」
  4. 找到 Webhook URL 後點擊「Edit」
  5. 貼上網址後按下「Update」
  6. 你的 Threads 收藏器就完成了!

如何加入自己的 LINE 機器人

  1. 前往 LINE 官方帳號管理後台:https://manager.line.biz/
  2. 選擇你的 LINE 機器人
  3. 點擊左側欄「增加好友工具」
  4. 按下「建立網址」後新增網址
  5. 把網址傳給自己就可以加入自己的 LINE 機器人了!

Threads 上內容常常看過覺得好棒想收藏但沒有更好的方法,貼文就消失變得很難找,這個小工具慢慢變成專屬於你的個人書籤助手。你就能駕馭資訊、把它變成資產。

如果你喜歡我的內容,歡迎到以下社群看更多