在團隊合作中,及時進行 Code Review 是確保程式碼品質與專案進度的關鍵。然而,隨著專案日漸龐大,待審核的 Pull Request (PR) 很容易被忽略
今天,我們將一步步教你如何建立一個自動化流程,讓系統在每天固定的時間檢查 GitHub 倉庫,找出待審核的 PR,並將彙整好的提醒訊息自動發送到指定的 Discord 頻道
來到儀表板,新增一個流程「Create Workflow」

初始節點選擇排程「On a schedule」
選擇「Cron」來下指令
指令填寫底下的內容設定為工作日的每天 10 點觸發
0 10 * * 1-5
都要記得在設定裡面去調整時區
選擇台灣時間
接著下個節點選擇「GitHub」
選擇「Get pull request of a repository」
接著建立並設定憑證
到 GitHub 設定的 Token 頁面,新增一個 Token
https://github.com/settings/tokens
權限打開
把 Token 複製起來
User 寫上自己 GitHub 的名字
填上想追蹤的 REPO 網址
有時候我們不需要提醒剛發出的 PR,而是希望專注在那些已經等待一段時間的。這時就可以用「Filter」節點來篩選
下個節點選擇「Filter」來過濾資料
判斷資料為最後活動時間
{{ $json.updated_at }}
選擇「Date & Time」的「is before」,後面內容寫
{{ $now.minus({ days: 1 }) }}
下個節點選擇「Code」來彙整 PR 資料
撰寫底下的程式碼
const items = $input.all();
// 如果沒有任何 PR,就直接結束,不往下執行
if (items.length === 0) {
return [];
}
// 訊息的開頭
let message = `**📢 Code Review 每日提醒!**\n\n共有 ${items.length} 則 PR 等待審核中:\n`;
// 透過 for 迴圈,將每一筆 PR 的資訊都加到 message 字串中
for (const item of items) {
const pr = item.json;
const reviewers =
pr.requested_reviewers.length > 0
? pr.requested_reviewers.map((r) => r.login).join(", ")
: "⚠️ **尚未指定**";
message += `\n------------------------------------\n`;
message += `**標題:** [${pr.title}](${pr.html_url})\n`;
message += `**作者:** ${pr.user.login}\n`;
message += `**待審核:** ${reviewers}\n`;
}
// 最後,回傳一個包含單一訊息的物件
return [
{
json: {
digestMessage: message,
},
},
];
下個節點選擇 Discord 來傳送訊息
「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹
Message 填入底下變數
{
{
$json.digestMessage;
}
}
接著點選上方的「Execute step」來測試一下
Discord 看到訊息代表成功啦
完成的流程會長這樣,記得到上方切換為「Active」哦
最後也附上完整流程的 JSON
{
"name": "Code Review",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 10 * * 1-5"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [0, 0],
"id": "68dd4969-837d-4514-b697-c6ee5022f74f",
"name": "Schedule Trigger"
},
{
"parameters": {
"resource": "repository",
"operation": "getPullRequests",
"owner": {
"__rl": true,
"value": "https://github.com/[YOUR_GITHUB_OWNER]",
"mode": "url"
},
"repository": {
"__rl": true,
"value": "https://github.com/[YOUR_GITHUB_OWNER]/[YOUR_GITHUB_REPO]",
"mode": "url"
},
"getRepositoryPullRequestsFilters": {}
},
"type": "n8n-nodes-base.github",
"typeVersion": 1.1,
"position": [220, 0],
"id": "31b1cc64-fd64-463a-a248-175257fb34b6",
"name": "GitHub",
"webhookId": "[REDACTED_WEBHOOK_ID]",
"credentials": {
"githubApi": {
"id": "[REDACTED_CREDENTIAL_ID]",
"name": "[REDACTED_CREDENTIAL_NAME]"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "22038ef4-d866-4d86-b94f-edda9186d59c",
"leftValue": "={{ $json.updated_at }}",
"rightValue": "={{ $now.minus({ days: 1 }) }}",
"operator": {
"type": "dateTime",
"operation": "before"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [440, 0],
"id": "8a8d50bc-7863-4ca6-81c-09a6f65c5151",
"name": "Filter"
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\n// 如果沒有任何 PR,就直接結束,不往下執行\nif (items.length === 0) {\n return [];\n}\n\n// 訊息的開頭\nlet message = `**📢 Code Review 每日提醒!**\\n\\n共有 ${items.length} 則 PR 等待審核中:\\n`;\n\n// 透過 for 迴圈,將每一筆 PR 的資訊都加到 message 字串中\nfor (const item of items) {\n const pr = item.json;\n const reviewers = pr.requested_reviewers.length > 0 \n ? pr.requested_reviewers.map(r => r.login).join(', ') \n : '⚠️ **尚未指定**';\n\n message += `\\n------------------------------------\\n`;\n message += `**標題:** [${pr.title}](${pr.html_url})\\n`;\n message += `**作者:** ${pr.user.login}\\n`;\n message += `**待審核:** ${reviewers}\\n`;\n}\n\n// 最後,回傳一個包含單一訊息的物件\nreturn [{\n json: {\n digestMessage: message\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [660, 0],
"id": "6013e0ab-ec37-4418-a642-ebc5d480f39d",
"name": "Code"
},
{
"parameters": {
"authentication": "webhook",
"content": "={{ $json.digestMessage }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [880, 0],
"id": "78e7dfa0-a736-4e85-98a5-87903249df62",
"name": "Discord",
"webhookId": "[REDACTED_WEBHOOK_ID]",
"credentials": {
"discordWebhookApi": {
"id": "[REDACTED_CREDENTIAL_ID]",
"name": "[REDACTED_CREDENTIAL_NAME]"
}
}
}
],
"pinData": {},
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "GitHub",
"type": "main",
"index": 0
}
]
]
},
"GitHub": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"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"
},
"versionId": "36ad75db-9169-4aaf-89b2-33068e43f8b0",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "[REDACTED_INSTANCE_ID]"
},
"id": "po8AkJzMqVhNfUgv",
"tags": []
}