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