# Discord bot 教學筆記(修改日期:2023/09/17):
## 0. 前置作業:
* ### **Discord bot 建置:**
1. 點選此 [連結](https://discord.com/developers/applications),並登入
2. 點選畫面右上角的**New Application**建立新的app(截圖時間:2022/11/22):
3. 切換至Bot頁面,移動至下面將3個選項都打開(截圖時間:2022/11/22):


4. 移動至上面,點擊下面的Reset token取得密鑰(token)(截圖時間:2022/11/22)

5. 下面那一串就是密鑰(token),先另外保存起來,等等會用到(截圖時間:2022/11/22):
(請不要外流出去,擁有密鑰(token)就等於擁有bot)

6. 切換至**OAuth2 -> URL Generator**,並勾選以下3個選項(截圖時間:2022/11/22):


7. 按下**右下角的按鈕並前往該網址**(截圖時間:2022/11/22):

8. 選擇要將Bot邀請至哪一個伺服器,並按下**繼續**(截圖時間:2022/11/22):
**(必須要有伺服器的管理權限才能夠邀請Bot)**

---
* ### **開啟Discord 開發者模式:**
1. 點擊畫面左下角的**使用者設定**(截圖時間:2023/09/17):

2. 點擊**進階**(截圖時間:2023/09/17):

4. 將**開發者模式打開**(截圖時間:2023/09/17):

---
* ### **取得Discord Bot Application ID(需開啟開發者模式):**
1. 邀請至伺服器後,對Bot按**右鍵->複製使用者ID**(截圖時間:2023/09/17):

2. 或者是在上面**Discord bot 建置**時的**General Information**頁面也能夠取得:
(截圖時間:2023/09/17)

---
* ### **取得Discord文字頻道ID(需開啟開發者模式):**
1. 在已建立的文字頻道上按**右鍵->複製頻道ID**(截圖時間:2023/09/17):

---
* ### **取得Discord 使用者ID(需開啟開發者模式):**
1. 對自己按**右鍵->複製使用者ID**(截圖時間:2023/09/17):

---
* ### **開發環境建置:**
建議使用VS Code來開發 或者是其他能夠執行 python(**3.8或以上**) 的IDE都可以,並執行以下指令來安裝discord py:(以下來自於官方文檔)
```
To install the library without full voice support, you can just run the following command:
# Linux/macOS
python3 -m pip install -U discord.py
# Windows
py -3 -m pip install -U discord.py
---------------------------------------------------------------------
Otherwise to get voice support you should run the following command:
# Linux/macOS
python3 -m pip install -U "discord.py[voice]"
# Windows
py -3 -m pip install -U discord.py[voice]
---------------------------------------------------------------------
To install the development version, do the following:
$ git clone https://github.com/Rapptz/discord.py
$ cd discord.py
$ python3 -m pip install -U .[voice]
```
### 1.第一次使用bot:
```python=
#main.py
import aiohttp
import os
from discord.ext import commands
from discord import Intents
class client(commands.Bot):
def __init__(self,**options):
super().__init__(
command_prefix="-",
intents = Intents.all(),
application_id = '請輸入bot的id(數字)',
**options
)
async def setup_hook(self):
self.session = aiohttp.ClientSession()
#載入資料夾commands裡面的py檔案
for filename in os.listdir('./commands'):
if filename.endswith('.py'):
await bot.load_extension(f'commands.{filename[:-3]}') #載入插件
async def on_ready(self): #當bot準備好時執行(不保證是第一個執行)
print('bot已上線')
if __name__ == "__main__":
bot = client()
bot.run('請輸入bot的token')
```
### 2.Cog:
有點類似於模組的概念,將監聽器、指令、參數打包在一個class裡面,以下範例建立了Apple的cog:
```python=
#Apple.py
from discord.ext import commands
class Apple(commands.Cog):
def __init__(self,bot:commands.Bot):
self.bot = bot
async def setup(bot:commands.Bot): #一定要有setup函數
await bot.add_cog(Apple(bot))
```
### 3.基本指令(以斜槓指令為主):
顧名思義就是以 斜槓(/) 來觸發指令,而不是用前綴符號來觸發指令
可參考 [連結](https://gist.github.com/AbstractUmbra/a9c188797ae194e592efe05fa129c57f) 關於斜槓指令的同步
```python=
#Apple.py
from discord.ext import commands
from discord import app_commands,Object
class Apple(commands.Cog):
def __init__(self,bot:commands.Bot):
self.bot = bot
@app_commands.command(name = "hi", description="說你好")
async def hi(self,interaction:Interaction):
await interaction.response.send_message("你好")
#如果只想要讓打指令的人看到可以加上 ephemeral=True
await interaction.response.send_message("你好",ephemeral=True)
#interaction 僅有效3秒鐘,且每一個interacion僅能回覆一次
@app_commands.command(name = "say", description="說話")
async def say(self,interaction:Interaction):
await interaction.response.defer() #可將interaction有效時間延長為15分鐘
await interaction.followup.send_message("你好")
async def setup(bot:commands.Bot):
#如需指定特定的伺服器擁有此指令的話,需添加guild/guilds參數
await bot.add_cog(Apple(bot),guild = Object(id = '伺服器ID(數字)'))
```
### 4.基本UI使用範例(按鈕、下拉式選單):
此範例演示了如何直接繼承View來實作UI畫面:
僅能置入Button、Select(2022.11.22截稿前)
```python=
#Apple.py
#使用直接繼承View
from discord import ButtonStyle
from discord.ui import Button,View
class AppleView(View):
def __init__(self):
super().__init__(timeout = None)
self.add_base_button()
def add(self,item,callback = None):
self.add_item(item) #將ui物件加入至View裡面
if callback is not None:
item.callback = callback #對ui物件添加回呼函數(當使用者跟ui物件互動時觸發)
return item
async def next(self,interaction:Interaction):
await interaction.response.defer(ephemeral=True)
self.start += int(interaction.data.get('custom_id'))
self.end += int(interaction.data.get('custom_id'))
self.ui_control()
await interaction.followup.edit_message(interaction.message.id,embeds = self.embed_list[self.start:self.end],view=self)
def add_base_button(self):
self.add(Button(style = ButtonStyle.green,label = "前十首",emoji="⏮️",custom_id="-10"),self.next)
self.add(Button(style = ButtonStyle.green,label = "下十首",emoji="⏭️",custom_id="10"),self.next)
self.ui_control()
def ui_control(self):
#透過self.children來抓取已添加的ui物件 disabled來修改物件是否啟用 False啟用 True不啟用
self.children[0].disabled = False
self.children[1].disabled = False
if self.start == 0:
self.children[0].disabled = True
if len(self.embed_list) <= self.end:
self.children[1].disabled = True
```
此範例演示了如何使用自訂class來間接繼承View實作UI畫面:
```python=
#Apple.py
#透過自訂class間接繼承
from discord import ButtonStyle,Interaction
from discord.ui import Button,View,Modal,TextInput,Select
import abc
class CustomView(View,metaclass=abc.ABCMeta): #自訂class
def __init__(self,timeout):
super().__init__(timeout = timeout)
def add(self,item,callback = None):
self.add_item(item) #將ui物件加入至View裡面
if callback is not None:
item.callback = callback #對ui物件添加回呼函數(當使用者跟ui物件互動時觸發)
return item
@abc.abstractmethod
def add_base_button(self):
return NotImplemented
@abc.abstractmethod
def ui_control(self):
return NotImplemented
class AppleView(CustomView): #繼承此自訂class
def __init__(self,embed_list):
super().__init__(timeout = None)
self.embed_list = embed_list
self.start = 0
self.end = 10
self.add_base_button()
async def next(self,interaction:Interaction):
await interaction.response.defer(ephemeral=True)
self.start += int(interaction.data.get('custom_id'))
self.end += int(interaction.data.get('custom_id'))
self.ui_control()
await interaction.followup.edit_message(interaction.message.id,embeds = self.embed_list[self.start:self.end],view=self)
def add_base_button(self):
self.add(Button(style = ButtonStyle.green,label = "上十項",emoji="⏮️",custom_id="-10"),self.next)
self.add(Button(style = ButtonStyle.green,label = "下十項",emoji="⏭️",custom_id="10"),self.next)
self.ui_control()
def ui_control(self):
#透過self.children來抓取已添加的ui物件 disabled來修改物件是否啟用 False啟用 True不啟用
self.children[0].disabled = False
self.children[1].disabled = False
if self.start == 0:
self.children[0].disabled = True
if len(self.embed_list) <= self.end:
self.children[1].disabled = True
```
將View送出:
```python=
@app_commands.command(name = "send",description = "送出View")
async def send(self,interaction:Interaction):
embed_list = []
await interaction.response.send_message(view = AppleView(embed_list))
```
下拉式選單Select:
依照官方文件上所寫僅能添加25個選項(2022.11.22截稿前)
```python=
from discord import Interaction,SelectOption,app_commands
from discord.ui import View,Select,Item
from discord.ext import commands
class FruitSelectView(View):
def __init__(self):
self.fruit = Select(placeholder="選擇你最喜歡的水果?",options=[SelectOption(
label="香蕉",
value="Banana",
emoji="🍌"
),SelectOption(
label="蘋果",
value="Apple",
emoji="🍎"
)])
self.add(self.fruit,self.select_callback)
async def select_callback(self,interaction:Interaction):
fruit = self.fruit.values[0] #所選的選項都會在values的list裡面
await interaction.respose.send_message(f"你選的水果是{fruit}")
def add(self,item:Item,callback):
self.add_item(item)
item.callback=callback
class Apple(commands.Cog):
def __init__(self):
pass
@app_commands.command(name = "fruit",description = "選取喜歡的水果")
async def fruit(self,interaction:Interaction):
await interaction.response.send_message(view = FruitSelectView())
```
Modal(彈出視窗):
僅支援置入TextInput(2022.11.22截稿前)
```python=
from discord.ui import Modal,TextInput,TextStyle
from discord import Interaction
class NameModal(Modal):
def __init__(self):
super().__init__(title = "報名表單")
self.name = TextInput(label='姓名:', style = TextStyle.short,default=f"XXX") #輸入的值會存在變數中
self.phone = TextInput(label='電話:', style = TextStyle.short,default=f"0900123123")
self.add_item(self.name)
self.add_item(self.phone)
async def on_submit(self, interaction:Interaction): #視窗提交時觸發
await interaction.response.send_message(f"姓名:{self.name}
,電話:{self.phone}")
```
送出Modal:
```python=
@app_commands.command(name = "Sign_up",description = "報名")
async def Sign_up(self,interaction:Interaction):
await interaction.response.send_modal(NameModal())
```
### 5.案例說明:
1. Q:如何使用時間戳(timestamp)來自動倒數計時?
A:透過與現在時間的相對時間,讓Discord來自動維護,達成自動倒數計時的目的,將其當作字串放入即可 [連結](https://github.com/discord/discord-api-docs/blob/ff4d9d8ea6493405a8823492338880c47fb02996/docs/Reference.md#timestamp-styles)
```txt=
<t:1624385691:t> 02:14
<t:1624385691:T> 02:14:51
<t:1624385691:d> 2021/06/23
<t:1624385691:D> 2021年6月23日
<t:1624385691:f> 2021年6月23日 02:14
<t:1624385691:F> 2021年6月23日星期三 02:14
<t:1624385691:R> 1 年前
```
---
2. Q:我想做一個四選一的單選題,我如何知道使用者按了哪一個按鈕?
A:將按鈕設定custom_id,再透過interaction.data.get('custom_id')來確定按了哪一個按鈕
```python=
async def next(self,interaction:Interaction):
self.start += int(interaction.data.get('custom_id'))
self.end += int(interaction.data.get('custom_id'))
def add_base_button(self):
self.add(Button(style = ButtonStyle.green,label = "上十項",emoji="⏮️",custom_id="-10"),self.next)
self.add(Button(style = ButtonStyle.green,label = "下十項",emoji="⏭️",custom_id="10"),self.next)
```
---
3. Q:如果我需要物件初始化時透過異步函數讀取資料,我該如何處理?
A:可以先建立一個異步函數,透過異步函數建立物件,再回傳回來
```python=
#Apple.py
class Banana():
def __init__(self):
pass
async def _init(interaction):
pass
async def Create_Banana(interaction:Interaction):
banana = Banana(interaction)
await banana._init(interaction)
return banana
class Apple(commands.Cog):
def __init__(self):
pass
@app_commands.commands(name = "create",description = "建立Banana物件")
async def create(self,interaction:Interaction):
banana = Create_Banana(interaction)
```
4.