前一篇對於 Discord 已經具有一定基礎後,當然要來做一點小東西了,當然不會太難,大多都是前面所寫過跟使用過的東西哩。
前面當然要準備一下專案,為了方便你練習,我也會盡可能先提供好相關的程式碼跟指令,所以你可以先輸入以下指令來建立專案:
mkdir example-discord-bot-crawler
cd example-discord-bot-crawler
npm init -y
touch index.js
touch register.js
touch .env
接下來就是安裝可能會需要使用的套件
npm i discord.js cheerio dotenv
接下來前面基礎的程式碼我就不重複說明了,直接貼上來:
// 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);
# .env
DISCORD_TOKEN=你的 Discord Token
DISCORD_CLIENT_ID=你的 Discord Client ID
我相信你看到我安裝 cheerio
就大概知道我們預期要做什麼了,沒錯,我們要來爬蟲了!
那麼我們這次需求要做什麼呢?
!blog
時,機器人會回傳一篇文章的標題與連結那麼爬蟲方面的話,其實也是前面寫過的範例程式碼
// 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
來註冊指令:
// 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
事件,這個事件會在使用者與機器人互動時觸發,例如:使用者輸入指令、點擊按鈕等等。
// 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
,我們可以透過這個物件來判斷使用者輸入的指令是什麼
接下來我們必須判斷使用者輸入的指令是否為 /blog
,如果是的話,我們就要回傳一篇文章的標題與連結,如果你不這樣做的話,當你有多個指令時,就會無法正確判斷使用者輸入的指令是什麼,而判斷方式很簡單,在 interaction
物件有一個 commandName
的屬性,這個屬性就是使用者輸入的指令名稱,所以我們只需要判斷這個屬性是否為 blog
就可以了
client.on(Events.InteractionCreate, async (interaction) => {
if (interaction.commandName === 'blog') {
// ...做一些事情
}
});
接下來當然就是要引入我們的爬蟲程式碼了,這邊我們就直接引入 crawler.js
,然後呼叫 crawler
函式,這個函式會回傳一個陣列,裡面包含了文章的標題與連結,我們只需要隨機取出一個就可以了,程式碼方面也不用擔心,我會補上註解說明:
// 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
,你就會看到機器人回傳了一篇文章的標題與連結了!而且也是隨機的!
Note
當使用指令呼叫後,務必要使用interaction.reply
來回傳資料給使用者,如果你使用interaction.channel.send
的話,會發生錯誤,這是因為interaction.channel.send
是用來回傳訊息的,而不是用來回傳指令的。
接下來就是要來實作每天晚上九點自動發送一篇文章的標題與連結,這邊我們會使用到 cron
這個套件,這個套件可以讓我們定時執行程式碼,所以我們可以透過這個套件來實作。
首先我們先來安裝 cron
:
npm i cron
接著建立一個 postCron.js
touch postCron.js
並輸入以下程式碼
// postCron.js
const cron = require('cron');
const job = new cron.CronJob('0 21 * * *', () => {
console.log('每天晚上九點執行一次');
});
job.start();
你可以先改成 * * * * *
,這樣就會每分鐘執行一次,這樣你就可以先測試一下,看看有沒有正確執行了,若沒有問題的話,我們要將程式碼改成以下:
// 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
// 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 聊天機器人」,若有興趣的話也可以參考唷。