Day22 - Discord Bot 整合

Discord Bot 整合

前言

前一篇對於 Discord 已經具有一定基礎後,當然要來做一點小東西了,當然不會太難,大多都是前面所寫過跟使用過的東西哩。

專案準備

前面當然要準備一下專案,為了方便你練習,我也會盡可能先提供好相關的程式碼跟指令,所以你可以先輸入以下指令來建立專案:

1
2
3
4
5
6
mkdir example-discord-bot-crawler
cd example-discord-bot-crawler
npm init -y
touch index.js
touch register.js
touch .env

接下來就是安裝可能會需要使用的套件

1
npm i discord.js cheerio dotenv

實作

接下來前面基礎的程式碼我就不重複說明了,直接貼上來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// index.js
require('dotenv').config(); // 引入 dotenv

const {
Client,
GatewayIntentBits,
Partials,
Events,
} = require('discord.js');

const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
partials: [
Partials.Channel,
],
});

client.once(Events.ClientReady, () => {
console.log('Ready!');
});

client.login(process.env.DISCORD_TOKEN);
1
2
3
# .env
DISCORD_TOKEN=你的 Discord Token
DISCORD_CLIENT_ID=你的 Discord Client ID

我相信你看到我安裝 cheerio 就大概知道我們預期要做什麼了,沒錯,我們要來爬蟲了!

那麼我們這次需求要做什麼呢?

  • 爬取 IsRayNotArray 的文章標題與連結
  • 當使用者輸入 !blog 時,機器人會回傳一篇文章的標題與連結
  • 每天晚上九點自動發送一篇文章的標題與連結

爬取文章標題與連結

那麼爬蟲方面的話,其實也是前面寫過的範例程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// crawler.js
const fs = require('fs');
const cheerio = require('cheerio');

const getData = async (url) => {
try {
const response = await fetch(url);
const data = await response.text();
return data;
} catch (error) {
console.log(error);
}
}

const crawler = async () => {
const target = 'https://israynotarray.com/'; // 目標網址
const html = await getData(target);

const $ = cheerio.load(html);

const postTitleLink = $('.post-title-link');

const data = [];

postTitleLink.each((index, element) => {
const title = $(element).text();
const url = $(element).attr('href');
data.push({
title,
url: `${target}${url}`, // 補上網域
});
});

return data;
}

module.exports = crawler;

只是最後我們把它導出,這樣我們就可以在其他檔案使用了。

註冊指令

接下來為了實現可以輸入 !blog 時,機器人會回傳一篇文章的標題與連結,我們就需要註冊一個指令,這邊我們就使用前面所寫過的 register.js 來註冊指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// register.js
require('dotenv').config(); // 引入 dotenv

const {
REST,
Routes
} = require('discord.js');

const list = [
{
name: 'blog',
description: '取得首頁第一頁隨機文章',
},
];

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);

const registerCommands = async (commands) => {
try {
console.log('Started refreshing application (/) commands.');

await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commands });

console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
}

registerCommands(list);

其實註冊指令也非常簡單,絕大部分都是不需要調整的,只需要調整 list 這個陣列就可以了,這邊我們只需要註冊一個指令,所以就只有一個物件,如果你要註冊多個指令,就可以在陣列中新增物件。

完成以上後,你可以先輸入 node register.js 來註冊指令,如果沒有錯誤的話,你就可以在 Discord 看到你剛剛註冊的指令了。

Note
查看方式很簡單,只需要在 Discord 中輸入 / 就可以看到你剛剛註冊的指令了;如果沒看到指令的話,你可以嘗試按下 Ctrl + R(Windows)或 Cmd + R(Mac)重新整理 Discord。

機器人

首先我們會需要使用一個事件,也就是 interactionCreate 事件,這個事件會在使用者與機器人互動時觸發,例如:使用者輸入指令、點擊按鈕等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// index.js
const {
Client,
GatewayIntentBits,
Partials,
Events,
} = require('discord.js');

// ... 略過其他程式碼

client.on(Events.InteractionCreate, async (interaction) => {
console.log(interaction);
});

// ... 略過其他程式碼
client.login(process.env.DISCORD_TOKEN);

接下來請你先輸入 node index.js 啟動機器人,並試著在 Discord 上輸入 /blog,你會看到終端機上輸出了一個物件,這個物件就是 interaction,我們可以透過這個物件來判斷使用者輸入的指令是什麼

interaction

接下來我們必須判斷使用者輸入的指令是否為 /blog,如果是的話,我們就要回傳一篇文章的標題與連結,如果你不這樣做的話,當你有多個指令時,就會無法正確判斷使用者輸入的指令是什麼,而判斷方式很簡單,在 interaction 物件有一個 commandName 的屬性,這個屬性就是使用者輸入的指令名稱,所以我們只需要判斷這個屬性是否為 blog 就可以了

1
2
3
4
5
client.on(Events.InteractionCreate, async (interaction) => {
if (interaction.commandName === 'blog') {
// ...做一些事情
}
});

接下來當然就是要引入我們的爬蟲程式碼了,這邊我們就直接引入 crawler.js,然後呼叫 crawler 函式,這個函式會回傳一個陣列,裡面包含了文章的標題與連結,我們只需要隨機取出一個就可以了,程式碼方面也不用擔心,我會補上註解說明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// index.js
const crawler = require('./crawler'); // 引入爬蟲程式碼

// ... 略過其他程式碼

client.on(Events.InteractionCreate, async (interaction) => {
// 判斷使用者輸入的指令是否為 /blog
if (interaction.commandName === 'blog') {
// 呼叫爬蟲程式碼,並取得資料
const data = await crawler();
// 依據資料長度隨機產生一個索引值
const randomIndex = Math.floor(Math.random() * data.length);
// 依照隨機產生的索引值取得資料
const randomData = data[randomIndex];
// 回傳資料給使用者
await interaction.reply(`文章標題:${randomData.title}\n文章連結:${randomData.url}`);
}
});

接下來我們就可以來啟動機器人了,請輸入 node index.js,然後在 Discord 上輸入 /blog,你就會看到機器人回傳了一篇文章的標題與連結了!而且也是隨機的!

blog

Note
當使用指令呼叫後,務必要使用 interaction.reply 來回傳資料給使用者,如果你使用 interaction.channel.send 的話,會發生錯誤,這是因為 interaction.channel.send 是用來回傳訊息的,而不是用來回傳指令的。

定時發送

接下來就是要來實作每天晚上九點自動發送一篇文章的標題與連結,這邊我們會使用到 cron 這個套件,這個套件可以讓我們定時執行程式碼,所以我們可以透過這個套件來實作。

首先我們先來安裝 cron

1
npm i cron

接著建立一個 postCron.js

1
touch postCron.js

並輸入以下程式碼

1
2
3
4
5
6
7
8
// postCron.js
const cron = require('cron');

const job = new cron.CronJob('0 21 * * *', () => {
console.log('每天晚上九點執行一次');
});

job.start();

你可以先改成 * * * * *,這樣就會每分鐘執行一次,這樣你就可以先測試一下,看看有沒有正確執行了,若沒有問題的話,我們要將程式碼改成以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// postCron.js
const cron = require('cron');
const crawler = require('./crawler'); // 引入爬蟲程式碼

// 接收 client 作為參數
const postCron = (client) => {
// 建立 job
const job = new cron.CronJob('* * * * *', async () => {
// 呼叫爬蟲程式碼,並取得資料
const data = await crawler();
// 依據資料長度隨機產生一個索引值
const randomIndex = Math.floor(Math.random() * data.length);
// 依照隨機產生的索引值取得資料
const randomData = data[randomIndex];
// 取得頻道
const channel = client.channels.cache.get('頻道 ID'); // 你可以將頻道 ID 改成使用 process.env.DISCORD_CHANNEL_ID 來取得
// 發送訊息
await channel.send(`每日晚間九點隨機推薦 Ray 的一篇文章:[${randomData.title}](${randomData.url})`);
});

// 回傳 job
return job;
}


module.exports = postCron;

Note
頻道 ID 的取得方式很簡單,只需要在 Discord 上右鍵點擊頻道,然後點擊「複製 ID」就可以了。

接著回到 index.js,引入 postCron.js,並在 client.once(Events.ClientReady, () => { ... }) 中啟動 job

1
2
3
4
5
6
7
8
9
// index.js
const postCron = require('./postCron'); // 引入 postCron

// ... 略過其他程式碼

client.once(Events.ClientReady, () => {
console.log('Ready!');
postCron(client).start();
});

接下來當你啟動機器人之後,每天晚上九點就會自動發送一篇文章的標題與連結了!

你可以看到我將 client 作為參數傳入 postCron,這樣的好處是,我們可以在 postCron 中使用 client,這樣就可以取得頻道,並發送訊息了。

Note
如果你想要在本機測試的話,請記得要把 0 21 * * * 改成 * * * * *,這樣就會每分鐘執行一次了;如果覺得 cron.js 中的程式碼跟 index.js 的程式碼過於雷同,你可以嘗試抽出來作為一個函式使用。

透過以上範例,你可以將你前面所學到的東西整合起來唷!那麼這一篇也差不多要告一個段落了,我們下一篇見。

Note
我先前也有分享過「用 OpenAI GPT-3 建立一個 Discord 聊天機器人」,若有興趣的話也可以參考唷。