---
# System prepended metadata

title: Render 部署 Discord Bot
tags: [Discord Bot]

---

# 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/)