iT邦幫忙

2025 iThome 鐵人賽

DAY 13
1

這篇文章將引導你使用 n8n 建立一個每週自動從你過去解過的題目中,抽選 1 題來複習,並將它發送到你的 Discord 頻道

讓我們開始吧!

wokflow

步驟一:建立排程觸發

我們的目標是讓這個流程每週自動執行 1 次

  • 來到儀表板,新增一個流程「Create Workflow」

    image 0.png

  • 初始節點選擇排程「On a schedule」

    image 1.png

  • 選擇禮拜日早上 08:00

    image 2.png

步驟二:透過 HTTP 請求抓取 LeetCode 題目

接下來,我們要從 LeetCode 的 API 獲取你所有已解答(Accepted)的題目列表

  • 下個節點選擇「HTTP Request」去傳送網路請求

    image 3.png

  • 方法選擇「POST」

    • 網址是

      https://leetcode.com/graphql
      
    • 「Send Body」打開,並選擇格式為「Using JSON」

    image 4.png

  • 請求的 JSON 填入以下資訊,「USERNAME」改為自己的

    {
      "query": "query recentAcSubmissions($username: String!, $limit: Int!) { recentAcSubmissionList(username: $username, limit: $limit) { id title titleSlug timestamp } }",
      "variables": {
        "username": "USERNAME",
        "limit": 2000
      }
    }
    

步驟三:撰寫抽籤的核心邏輯

我們已經拿到了題目列表,但裡面可能包含重複的題目,而且我們只想複習那些有點久以前解過的題目

  • 下個節點選擇「Code」,來撰寫抽選機制

    image 5.png

  • 程式碼如下

    // 1. 取得從上個節點 (HTTP Request) 傳來的題目列表
    const submissions = $input.item.json.data.recentAcSubmissionList;
    
    // 2. 使用 Map 整理並去除重複的題目
    const uniqueProblemsMap = new Map();
    submissions.forEach((sub) => {
      uniqueProblemsMap.set(sub.titleSlug, {
        // 使用 .set() 方法
        title: sub.title,
        titleSlug: sub.titleSlug,
        timestamp: sub.timestamp,
      });
    });
    // 從 Map 的 values() 取出所有物件,並轉換成陣列
    const uniqueAccepted = Array.from(uniqueProblemsMap.values());
    
    // 3. 計算 30 天前的 Unix timestamp (單位:秒)
    const now = new Date(); // 取得當前時間
    const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; // 30 天的毫秒數
    const thirtyDaysAgoTimestampInSeconds = Math.floor(
      (now.getTime() - thirtyDaysInMs) / 1000
    );
    
    // 4. 進行篩選:只挑出在 30 天前解過的題目
    const oldProblems = uniqueAccepted.filter(
      (p) => parseInt(p.timestamp) < thirtyDaysAgoTimestampInSeconds
    );
    
    // 5. 決定抽籤池 (Pool) - 這是防呆機制
    // 如果有超過30天的舊題目,就從舊題目中抽籤;
    // 如果沒有(例如你是新帳號,所有題目都在30天內解的),就從全部題目中抽,避免 workflow 出錯。
    const pool = oldProblems.length > 0 ? oldProblems : uniqueAccepted;
    
    // 6. 從最終的抽籤池中隨機挑選 1 題
    const randomIndex = Math.floor(Math.random() * pool.length);
    const randomProblem = pool[randomIndex];
    
    // 7. 回傳選中的題目物件,包含所有資訊以便後續使用
    return randomProblem;
    

步驟四:將結果發送到 Discord

最後一步,就是將我們精心挑選出來的題目發送到 Discord,提醒自己該複習了

  • 接下來選擇「Discord」把抽選結果傳出去

    image 6.png

    image 7.png

  • 「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹

    image 8.png

  • 「Message」填入以下內容

    **今週 LeetCode 複習!** 🧠
    
    **隨機抽選:** [{{ $json.title }}](https://leetcode.com/problems/{{ $json.titleSlug }}/)
    **上次解題時間:** {{ DateTime.fromSeconds(parseInt($json.timestamp)).setZone('Asia/Taipei').toFormat('yyyy-MM-dd') }}
    
    
  • 接著點選上方的「Execute step」來測試一下

    image 9.png

  • Discord 看到訊息代表成功啦

    image 10.png

  • 記得要到設定的地方設定時區

    image 11.png

  • 設定為台灣時間

    image 12.png

  • 完成的流程會長這樣,記得到上方切換為「Active」哦

    image 13.png

恭喜!現在你有了一個自動化的 LeetCode 複習小幫手,每週都會貼心地提醒你該溫故知新惹

  • 最後也附上完整流程的 JSON

    {
      "name": "Weekdays LeetCode",
      "nodes": [
        {
          "parameters": {
            "rule": {
              "interval": [
                {
                  "field": "weeks",
                  "triggerAtHour": 8
                }
              ]
            }
          },
          "type": "n8n-nodes-base.scheduleTrigger",
          "typeVersion": 1.2,
          "position": [0, 0],
          "id": "e247a697-9ea9-4c94-a7c2-1f3922b5b858",
          "name": "Schedule Trigger"
        },
        {
          "parameters": {
            "method": "POST",
            "url": "https://leetcode.com/graphql",
            "sendBody": true,
            "specifyBody": "json",
            "jsonBody": "{\n    \"query\": \"query recentAcSubmissions($username: String!, $limit: Int!) { recentAcSubmissionList(username: $username, limit: $limit) { id title titleSlug timestamp } }\",\n    \"variables\": {\n        \"username\": \"YOUR_LEETCODE_USERNAME\",\n        \"limit\": 2000\n    }\n}",
            "options": {}
          },
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [220, 0],
          "id": "0b45f22a-6ec1-404d-af5c-9b6e1b5aedb8",
          "name": "HTTP Request"
        },
        {
          "parameters": {
            "jsCode": "// 1. 取得從上個節點 (HTTP Request) 傳來的題目列表\nconst submissions = $input.item.json.data.recentAcSubmissionList;\n\n// 2. 使用 Map 整理並去除重複的題目\nconst uniqueProblemsMap = new Map();\nsubmissions.forEach(sub => {\n  uniqueProblemsMap.set(sub.titleSlug, { // 使用 .set() 方法\n    title: sub.title,\n    titleSlug: sub.titleSlug,\n    timestamp: sub.timestamp\n  });\n});\n// 從 Map 的 values() 取出所有物件,並轉換成陣列\nconst uniqueAccepted = Array.from(uniqueProblemsMap.values()); \n\n// 3. 計算 30 天前的 Unix timestamp (單位:秒)\nconst now = new Date(); // 取得當前時間\nconst thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000; // 30 天的毫秒數\nconst thirtyDaysAgoTimestampInSeconds = Math.floor((now.getTime() - thirtyDaysInMs) / 1000);\n\n// 4. 進行篩選:只挑出在 30 天前解過的題目\nconst oldProblems = uniqueAccepted.filter(p => parseInt(p.timestamp) < thirtyDaysAgoTimestampInSeconds);\n\n// 5. 決定抽籤池 (Pool) - 這是防呆機制\n// 如果有超過30天的舊題目,就從舊題目中抽籤;\n// 如果沒有(例如你是新帳號,所有題目都在30天內解的),就從全部題目中抽,避免 workflow 出錯。\nconst pool = oldProblems.length > 0 ? oldProblems : uniqueAccepted;\n\n// 6. 從最終的抽籤池中隨機挑選 1 題\nconst randomIndex = Math.floor(Math.random() * pool.length);\nconst randomProblem = pool[randomIndex];\n\n// 7. 回傳選中的題目物件,包含所有資訊以便後續使用\nreturn randomProblem;"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [440, 0],
          "id": "84f4439a-e18e-49fb-868b-8c2c555632b9",
          "name": "Code"
        },
        {
          "parameters": {
            "authentication": "webhook",
            "content": "=**今週 LeetCode 複習!** 🧠\n\n**隨機抽選:** [{{ $json.title }}](https://leetcode.com/problems/{{ $json.titleSlug }}/)\n**上次解題時間:** {{ DateTime.fromSeconds(parseInt($json.timestamp)).setZone('Asia/Taipei').toFormat('yyyy-MM-dd') }}\n",
            "options": {}
          },
          "type": "n8n-nodes-base.discord",
          "typeVersion": 2,
          "position": [660, 0],
          "id": "fb300201-1a50-4dcf-805f-d12f225f2ea5",
          "name": "Discord"
        }
      ],
      "pinData": {},
      "connections": {
        "Schedule Trigger": {
          "main": [
            [
              {
                "node": "HTTP Request",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "HTTP Request": {
          "main": [
            [
              {
                "node": "Code",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Code": {
          "main": [
            [
              {
                "node": "Discord",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      },
      "active": true,
      "settings": {
        "executionOrder": "v1",
        "timezone": "Asia/Taipei",
        "callerPolicy": "workflowsFromSameOwner",
        "executionTimeout": -1
      },
      "versionId": "16b54aac-760b-40c3-8598-b525bb88d7d7",
      "meta": {
        "templateCredsSetupCompleted": true
      },
      "id": "GHWECbpBcPSCRF1A",
      "tags": []
    }
    

上一篇
[Day12]_網站健康檢查員
下一篇
[Day14]_專案錯誤通知器
系列文
告別重複瑣事: n8n workflow 自動化工作實踐17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

2
Ray
iT邦研究生 3 級 ‧ 2025-09-15 22:15:21

有圖片死掉囉~

ayao iT邦新手 4 級 ‧ 2025-09-16 08:59:36 檢舉

Ray 哇哇,大感謝,已經修正惹

我要留言

立即登入留言