# Render 部署 Discord Bot
## 簡介 + 碎碎念
最近剛好有機會幫忙朋友架設一個 `Bot` 所以藉此來撰寫一下文章。
網路上也有看見有大大使用 [`fly.io`](https://home.gamer.com.tw/artwork.php?sn=5808333) 的方式架設 `Bot`,但是由於本人自身是要架設 `Discord Bot` 的緣故,估計寬頻會使用的不少,所以就找了 [`Render`](https://render.com/) 來架設。
由於前一陣子微服務正好導入公司,有碰到 `Docker` 的部分,所以就直接將程式包成 `image` 到 `Render` 中佈署。
## 使用程式、平台以及工具
那麼開始介紹如何使用 `Render` 佈署 `Discord Bot` 之前,先告知會使用的佈署網站、工具以及程式:
- Discord Developer Portal
- Node.js
- Docker Desktop
- Render
- UptimeRobot
# Discord Developer Portal
首先我們先到 [`Discord Developer Portal`](https://discord.com/developers/applications) 去做新增的動作,在進入之前會要先登入 `Discord`。
### Step 1. 開始建立
先按下 **`New Application`** 新增一個應用程式,接著會跳出這個畫面輸入 **`Name`** 應用程式的名字,勾選同意後按下 **`Create`**。
![image](https://hackmd.io/_uploads/BkkuRiOpa.png)
![image](https://hackmd.io/_uploads/SyAg1hu6p.png)
### Step 2. 設置 Token
我們先到 **`Bot`** 接著按下 **`Reset Token`** ,這個 `Token` 日後會用到,請把它記起來或許要用到的時候再 **`Reset Token`** 也可以。
因為需要接收伺服器上的所有訊息記得把 **`Token`** 底下 **`MESSAGE CONTENT INTENT`** 這個項目的開關打開。
![image](https://hackmd.io/_uploads/B1bye3uTa.png)
![image](https://hackmd.io/_uploads/SyblbWoTa.png)
### Step 3. Bot 的權限設定
再到 **`OAuth2`** 勾選 **`Bot`** 的選項。
接著往下滑會有機器人能夠做的權限,如果什麼權限都要勾選 **`Administrator`** 即可。
之後底下會出現邀請連結,將他複製起來貼到瀏覽器上。
![image](https://hackmd.io/_uploads/SyToQ2uaa.png)
![image](https://hackmd.io/_uploads/Hy1OV2Op6.png)
![image](https://hackmd.io/_uploads/B1GEB3_a6.png)
### Step 4. 加入機器人
選擇伺服器,按下繼續之後確認授權,成功後會看到機器人加入伺服器
![image](https://hackmd.io/_uploads/BJDOr3_p6.png)
![image](https://hackmd.io/_uploads/B1fJU2OTp.png)
![image](https://hackmd.io/_uploads/SJ4lPh_pa.png)
# Node.js
再來來到對一般人來說比較艱難的地方,也就是程式編碼的部分。
這邊先提及要下載的東西:
- [VS Coode](https://code.visualstudio.com/download)
- [Node.js](https://nodejs.org/en)
**`VS Code`** 是我們俗稱的編程軟體,但大多我們是直接稱 **`整合開發環境(IDE)`** ,總之這是要用來寫程式的應用程式。
**`Node.js`** 則是我們要用的程式語言,它是由 **`javascript`** 所延伸出來的架構,是能夠架在伺服器上的前端,它也衍伸了很多其他框架的東西,但在這不一一介紹。
---
### Step 1. 確認 Node.js
兩樣東西裝好之後,再重新啟動電腦,我們先到搜尋打 **`PowerShell`** 。
它是 `Windows` 上類似命令提示字元的東西,但是不同的是按 **`↑`** 的方向鍵能夠喚起之前打過的指令。
接著在上面輸入 **`node -v`** 確認是不是有安裝好 **`Node.js`**。
![image](https://hackmd.io/_uploads/ryp3jBYTa.png)
![image](https://hackmd.io/_uploads/SkdX3LtT6.png)
### Step 2. 開始使用VS Code
**假設你有開發經驗可以略過此步驟。**
如果你安裝完 **`VS Code`** 之後,應該會在桌面看到它,先把它打開安裝中文包。
步驟如下:
- 點開左排由上至下第 5 個
- 輸入 **`chinese`**
- 找到語言包後按下安裝
![image](https://hackmd.io/_uploads/Bk8p6LFTT.png)
接著開啟資料夾之前建議先在自己熟悉的磁區建立 **`DiscordBot`** 的資料夾。
> 因為 **`Node.js`** 套件所占用的容量其實比想像的還大,建議不要直接在 **`桌面`** 或 **`磁區:C`** 開始編寫。
按下開啟資料夾後,尋找自己剛剛建立的 **`DiscordBot`** 按下選擇資料夾。
![image](https://hackmd.io/_uploads/H1i31wtp6.png)
再到上方尋找 **`終端機`** → **`新增終端`** 在底下輸入以下指令按下 **`Enter`** :
```shell
npm install discord.js
```
![image](https://hackmd.io/_uploads/ryWHxvt6a.png)
![image](https://hackmd.io/_uploads/ByTN-PtT6.png)
### Step 3. 開始寫 Code 前的調整
安裝完成後,我們先點 **`DISCORDBOT`** 的區域一下,再按下上方的新增檔案,命名為 **`discordbot.js`** 這個檔案將會是撰寫程式的地方。
![image](https://hackmd.io/_uploads/BJ0tGvYa6.png)
![image](https://hackmd.io/_uploads/B1u7mPtTT.png)
再來我們要建立執行這個程式的方法,點開 `package.json` 新增以下啟動的內容:
```json=
"scripts":{
"start" : "node discordbot.js"
},
```
之後我們要啟動程式就用上方的 `偵錯` 來啟動程式,當然也可以在 `終端機` 底下直接輸入:
```shell
node discordbot.js
```
![image](https://hackmd.io/_uploads/HkQAeuFTa.png)
### Step 4-0. 寫機器人的 Code
接著開始在 `discordbot.js` 中開始寫程式,我們先建立一開始需要的東西:
```js=
// 引用參數說明
// Client 用來做登入以及機器人的行為註冊
// GatewayIntentBits 是告訴 Client 說要引用什麼樣類別的事件
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents:
[
// 基本事件
GatewayIntentBits.Guilds,
// 排程事件
GatewayIntentBits.GuildScheduledEvents,
// 聊天室如果有動作的事件
GatewayIntentBits.GuildMessages,
// 接收聊天室內容的事件 → 需要到 Discord Developer Portal 把 MESSAGE CONTENT INTENT 打開
GatewayIntentBits.MessageContent,
// 接收到反應的事件
GatewayIntentBits.GuildMessageReactions
]
});
```
程式碼內容的 `//` 是一種提醒以及一種解釋程式碼用的文字。
程式中作了什麼事情我在這大略解釋,如果沒有想看可以跳過這段文字,直接複製上面的往下做就可以了。
---
### Step 4-1. 程式碼的解釋
> 在一開始,我們需要與 `discord` 的伺服器做溝通。
所以需要 `Client` 幫我們做機器人的聯繫,而 `Client` 它需要規則,所以將 `GatewayIntentBits` 也叫了出來。
`require` 是我們用來引用程式的方式,前面所提到的 `Client`、`GatewayIntentBits` 都來自我們一開始安裝的 `discord.js` 這個程式中。
前贅詞 `const` 可以參考這位大大的文章:[談var、let、const差異之var你這個矯情的賤人](https://hackmd.io/@bookbasketball/SJ1lqTzSt)
```js
const { Client, GatewayIntentBits } = require('discord.js');
```
接續我們建立一個新的 `client` 變數,讓它去儲存一個全新的 `Client`。
`Client` 內的 `intents` 則是去設定它要引用的東西,讓我們在撰寫的時候才能夠用這些事件。
```js
const client = new Client({
intents:
[
// 基本事件
GatewayIntentBits.Guilds,
// 排程事件
GatewayIntentBits.GuildScheduledEvents,
// 聊天室如果有動作的事件
GatewayIntentBits.GuildMessages,
// 接收聊天室內容的事件 → 需要到 Discord Developer Portal 把 MESSAGE CONTENT INTENT 打開
GatewayIntentBits.MessageContent,
// 接收到反應的事件
GatewayIntentBits.GuildMessageReactions
]
});
```
---
### Step 4-2. 讓機器人登入
接著我們在底下加上這一段。
這邊如果忘記自己原本的 `Token` 再到 `Step 2. 設置 Token` 地方重新設定複製就可以了。
> 記得隨時存檔 Ctrl + S !!!
接著我們到 `package.json` 中按下 `偵錯` ,等待一段時間後,機器人就會上線囉。
```js
// Discord Developer Portal 上 Bot 的 Token
client.login("這邊不能複製貼上請貼你機器人的 Token 喔");
```
![image](https://hackmd.io/_uploads/Sy-y-iFTa.png)
![image](https://hackmd.io/_uploads/H1kUzsKa6.png)
![image](https://hackmd.io/_uploads/Sy3iMjK6T.png)
> 如果要停止程式
停止程式的方式在這邊:
![image](https://hackmd.io/_uploads/r1a_X2YT6.png)
---
### Step 4-3. 回話的文字建立
如果說我們是要建立一個回話機器人,那麼就需要所謂的資料,這些資料我們先暫時性的存在 `json` 中。
我們再次新增一個檔案,將它取名為 `chatreturn.json` 讀取回話以及關鍵字的檔案。
```json!
{
"messages":
[
{
"keyword" : "關鍵字自行輸入",
"botmessage": "機器人要回的話自行輸入喔喔喔"
},
{
"keyword" : "關鍵字自行輸入",
"botmessage": "機器人要回的話自行輸入喔喔喔"
}
]
}
```
![image](https://hackmd.io/_uploads/BJp-rot66.png)
---
### Step 4-4. 機器人的回話事件
建立好檔案後,我們接著在在上方引入剛剛建立好的 `chatreturn.json`
```js
const { messages } = require("./chatreturn.json");
```
![image](https://hackmd.io/_uploads/BkZaIiFap.png)
接著再 `client` 的底下開始我們的事件撰寫。
> 底下解釋可迴避
```js
// 如果接收到新訊息
client.on('messageCreate', async msg => {
// 不要接收機器人的訊息
if (msg.author.bot)
return;
// 尋找發訊息的人有沒有這個關鍵字
let reply = messages.filter(item => msg.content.includes(item.keyword));
// 如果有的話 reply 的內容一定大於 0
if (reply.length > 0) {
// 讓機器人回話
// reply[0] 是指定資料內的第一筆
// botmessage 則是剛剛我們設定的欄位 儲存機器人的回話
msg.reply(reply[0].botmessage);
}
});
```
![image](https://hackmd.io/_uploads/H10UYiFaa.png)
> 解釋開始
**`client`** 內有 **`on`** 的函式,我們註冊了 **`discord.js`** 中的 **`messageCreate`** 事件,並且以 **`async`** 非同步的方式去實行,回傳的物件都在 **`msg`** 這裡面。
接著用 **`if`** 判斷 **`msg.author.bot`** 也就是回話的是不是機器人,是的話跳出。
剩下的解釋如 **`//`** 裡頭的內容一樣。
`msg.reply(reply[0].botmessage);` 會讓機器人回覆當前所再頻道使用者的話語。
![image](https://hackmd.io/_uploads/Hk_jioYpp.png)
> 解釋結束
### Step 4-5. 設置機器人狀態
我們把狀態寫到 `messageCreate` 事件的上方中。
```js
client.on('ready', () => {
// 設定我們的機器人狀態
// activities 的 name 是狀態的名稱
// status 是表示在線上或者忙碌中 目前是 online 也就是常常看到的綠燈
client.user.setPresence({ activities: [{ name: '被編程中' }], status: 'online' });
});
```
![image](https://hackmd.io/_uploads/H1xGTnta6.png)
接著我們再去 `package.json` 中按一次 `偵錯`,試試我們現在的機器人。
![image](https://hackmd.io/_uploads/HkiCAiFpT.png)
![explorer_0FjrnLrtmc](https://hackmd.io/_uploads/SkKiCoFpT.gif)
# Docker Desktop
艱難的 `Node.js` 的章節結束了,其實機器人也算完成一半了。
接著是我們將自己打包的部分,在此之前呢,先不要關掉你的 `VS Code` 我們還要用到它喔,因為還需要微調。
---
### Step 1. 登入 Docker Hub
那麼第一步我們先打開已經安裝好的 **`Docker Desktop`** 然後進行登入。
如果沒有帳號,也可以直接用 `Google` 帳號登入。
登入完按下開啟 **`Docker Desktop`** 它就會開始進行同步了。
![image](https://hackmd.io/_uploads/SJzMV2t6p.png)
![image](https://hackmd.io/_uploads/HJ9rN3ta6.png)
### Step 2. 確認指令
確認好我們的 **`Docker Desktop`** 啟動好之後呢,我們再次回到 `VS Code` 裡頭確認一下指令,在 `終端機` 中輸入:
```shell
docker -v
```
![image](https://hackmd.io/_uploads/rynYB2Y66.png)
### Step 3. 建立 Dockerfile
知道 `docker` 這個指令可以使用後,我們開始去做這個專案的 `Dockerfile`。
直接新增檔案叫做 `Dockerfile` 就可以囉。
![image](https://hackmd.io/_uploads/BJgDIhF66.png)
點入檔案裡頭之後,開始寫我們的內容,這邊我直接附上文件內容:
```dockerfile=
# node 版本
From node:20.11.1
# 建立 Container 內的資料夾
RUN mkdir -p /usr/src/bot
# 指定 Container 執行的資料夾
WORKDIR /usr/src/bot
# 複製 package.json 至執行的資料夾裡
COPY package.json /usr/src/bot
# 安裝最新的 npm 套件
RUN npm install -g npm@latest
# 將所有 node js 的元件安裝起來
RUN npm install
# 複製所有檔案到執行的資料夾中
COPY . /usr/src/bot
# 指定容器內部的 Port
EXPOSE 3000
# 執行指令
# 組起來是 node --expose-gc discordbot.js
CMD ["node" , "--expose-gc" , "discordbot.js"]
```
### Step 4. 微調程式碼
先說明調整的緣由,因為要佈署的平台免費方案只能使用 **`Web Service`** 無法使用背景執行程式之類的,所以我們需要架設一個只有入口的網站,讓它覺得是一個網站服務。
調整 **`package.json`** 將原本的 **`start`** 內容改為 :
```shell
node --expose-gc discordbot.js
```
![image](https://hackmd.io/_uploads/B1o20pY66.png)
然後需要先安裝 **`express`** 這個程式。
```shell
npm install express
```
安裝完畢後,我們開始引用 **`express`** 並做出一個網站入口。
底下加入入口的程式碼以及定時檢查記憶體。
```js
const express = require("express");
```
![image](https://hackmd.io/_uploads/rypazTFpp.png)
![image](https://hackmd.io/_uploads/Hkzrl0YaT.png)
最後所有的程式碼:
```js=
// 引用參數說明
// Client 用來做登入以及機器人的行為註冊
// GatewayIntentBits 是告訴 Client 說要引用什麼樣類別的事件
const { Client, GatewayIntentBits } = require('discord.js');
const { messages } = require("./chatreturn.json");
const express = require("express");
const client = new Client({
intents:
[
//基本事件
GatewayIntentBits.Guilds,
//排程事件
GatewayIntentBits.GuildScheduledEvents,
// 聊天室如果有動作的事件
GatewayIntentBits.GuildMessages,
// 接收聊天室內容的事件 → 需要到 Discord Developer Portal 把 MESSAGE CONTENT INTENT 打開
GatewayIntentBits.MessageContent,
// 接收到反應的事件
GatewayIntentBits.GuildMessageReactions
]
});
client.on('ready', () => {
// 設定我們的機器人狀態
// activities 的 name 是狀態的名稱
// status 是表示在線上或者忙碌中 目前是 online 也就是常常看到的綠燈
client.user.setPresence({ activities: [{ name: '被編程中' }], status: 'online' });
});
// 如果接收到新訊息
client.on('messageCreate', async msg => {
// 不要接收機器人的訊息
if (msg.author.bot)
return;
// 尋找發訊息的人有沒有這個關鍵字
let reply = messages.filter(item => msg.content.includes(item.keyword));
// 如果有的話 reply 的內容一定大於 0
if (reply.length > 0) {
// 讓機器人回話
// reply[0] 是指定資料內的第一筆
// botmessage 則是剛剛我們設定的欄位 儲存機器人的回話
msg.reply(reply[0].botmessage);
}
});
// Discord Developer Portal 上 Bot 的 Token
client.login("這邊的 Token 要自己加喔喔喔喔");
// 使用環境變數或預設port 3000
const PORT = process.env.PORT || 3000;
// 引用 express
const app = express();
// 首頁入口顯示 DiscordBot
app.get('/', (req, res) => {
// 系統記錄用的訊息
// 之後用 UptimeRobot 用的
console.log("uptimeRobot enter");
res.send("DiscordBot");
});
// 網頁的健康檢查 Render 內設定時使用的
app.get('/healthz', (req, res) => {
res.status(200).send('OK');
});
// 啟用網站
app.listen(PORT, () => {
// 系統訊息來看是不是真的執行了
console.log("start Server");
// 定時 1 分鐘檢查佔用的記憶體
// 因為 Render 有限制 512 MB 需要定時清理不需要的記憶體
setInterval(() => {
// 取得記憶體的使用容量
let mUsage = process.memoryUsage();
// 加總記憶體容量
let memorySum = mUsage.rss + mUsage.heapUsed + mUsage.heapTotal + mUsage.external + mUsage.arrayBuffers;
// 換算成 MB
let memoryMB = (memorySum/(1024*1024)).toFixed(2) + " MB";
// 顯示日期以及佔用的記憶體
console.log(`Live...${memoryMB} ` + new Date());
// 回收記憶體
gc();
}, 60000);
});
```
### Step 5. 將程式碼變成 image(映像檔)
首先我們先確認自己的 **`docker hub`** 的名稱。
按下 **`Docker Desktop`** 右上角的 **`AccountSettings`**。
![image](https://hackmd.io/_uploads/rymhV0YaT.png)
![image](https://hackmd.io/_uploads/BJxgr0Y6a.png)
接著在 `VS Code` 中的 `終端機` 下指令:
```shell
docker build -t 輸入你的名稱/discord-bot:v1 .
```
- 名稱:請看上圖的位置自己的名稱是什麼
- discord-bot:是可以自己命名的 `image` 名稱,不一定要叫這個名字
- v1:這個表示 `image` 的版本,我們從第 1 版開始
確認好內容後按下 `Enter` 執行。
![image](https://hackmd.io/_uploads/Sy7LUAK66.png)
再來將我們的 `image` 上傳到 `docker hub` 裡頭,跟上方的指令差不多。
```shell
docker push 輸入你的名稱/discord-bot:v1
```
![image](https://hackmd.io/_uploads/BJz9KAY6T.png)
# Render
我們將程式上傳 `docker hub` 之後,已經完成 80% 了。
再來要開始登入 `Render` 來創建平台上的服務。
![image](https://hackmd.io/_uploads/B1Smj0tTa.png)
![image](https://hackmd.io/_uploads/BJ8SoRKpT.png)
再來將剛剛 `輸入你的名稱/discord-bot:v1` 名稱再打一次。
![image](https://hackmd.io/_uploads/ryb0oAtap.png)
![image](https://hackmd.io/_uploads/SJRSnCF6T.png)
![image](https://hackmd.io/_uploads/rJDs3AYpT.png)
接著我們可以去看平台上的 Log 查看是不是真的有建立起來
![image](https://hackmd.io/_uploads/Bkgm6RFap.png)
![image](https://hackmd.io/_uploads/SJhr60Kp6.png)
# UptimeRobe
首先我們先把網站網址複製起來
![image](https://hackmd.io/_uploads/ryHq6AFpa.png)
接著我們到 [`UptimeRobe`](https://uptimerobot.com/) 上註冊一個監測網站的帳號
![image](https://hackmd.io/_uploads/S1_vC0YTp.png)
![image](https://hackmd.io/_uploads/r1tOR0Y6p.png)
![image](https://hackmd.io/_uploads/B1dsCAKaa.png)
註冊完畢後將我們剛剛的網址複製到建立的監聽機器人上。
![image](https://hackmd.io/_uploads/SyjACRKpT.png)
![image](https://hackmd.io/_uploads/HJOmJkq66.png)
![image](https://hackmd.io/_uploads/B18sJycpp.png)
都架設好了之後,我們可以看看 `UptimeRobot` 是不是真的有進到網站來看。
![image](https://hackmd.io/_uploads/BJmWgyqTp.png)
其餘功能如果有編程經驗的人大概會想到要怎麼去玩它,這裡就只教如何回話而已。
當然它也可以當作排程機器人。
以上謝謝大家的觀看。
最後的 Demo 成果,我去把機器人的名字跟頭像改了。
![explorer_4Z2x7fZVE3](https://hackmd.io/_uploads/rJ3-zyq6p.gif)
# 參考
- [message.content doesn't have any value in Discord.js](https://stackoverflow.com/questions/73036854/message-content-doesnt-have-any-value-in-discord-js)
- [Gateway Intents
](https://discordjs.guide/popular-topics/intents.html#enabling-intents)
- [Python Discord Bot 基礎教學](https://hackmd.io/@smallshawn95/python_discord_bot_base)
- [關於從 Heroku 跳到 Render 這件事情](https://israynotarray.com/other/20221213/3036227586/)