【教學】EP 2 - Discord.py 之 Cog 篇

在閱讀此篇文章前,作者建議:您已有程式的基礎底子,並且此篇著重於程式碼的部分,其餘皆為概略步驟。

請注意文章日期的時效性

為何要使用 Cog 寫法

在使用這種寫法前,我是既不熟悉 Python 語法,以及其特色(其實是忘記了),所以好一段時間放棄了用 Python 寫。但現在稍微略懂之後,才發現這種寫法有很多好處:

  1. 好維護
  2. 事件函式與前綴指令不會衝突
  3. 前綴指令不用一直重新啟動 Bot
  4. 指令可分類

總之,其實只需要修改幾行程式碼,就可以讓你的整體結構更清晰易懂,還不快點使用這種寫法。你可以在 discord.py Cogs 官方文檔 中找到更多詳細解說。

Cog 架構

請將你的檔案目錄修改成如下:

1
2
3
4
5
6
DiscordBot
| bot.py
|
└─cogs
event.py
main.py

也就是請在主目錄下,創建一個 cogs 資料夾,以及在其之中分別創建 event.pymain.py

  • main.py:主要在寫你的前綴指令(或是斜線指令)
  • event.py:主要在寫事件觸發函式

等等,已經有指令、有事件偵測了,那 bot.py 要幹嘛?

  • bot.py:主要提供 load、unload、reload 的指令,可以讓 Bot 不掉線,也能修改程式,並讓 Bot 收到最新的指令。

Cog 實作

Bot 檔內容

💡小技巧:可以使用 前綴指令+help 來查看可使用的指令

Load 載入

用於將新的程式檔案載入,或是之前未載入的檔案載入。

1
2
3
4
@bot.command()
async def load(ctx, extension):
await bot.load_extension(f"cogs.{extension}")
await ctx.send(f"Loaded {extension} done.")

Unload 卸載

用於若程式碼有問題,可以先卸載避免機器人出錯

1
2
3
4
@bot.command()
async def unload(ctx, extension):
await bot.unload_extension(f"cogs.{extension}")
await ctx.send(f"UnLoaded {extension} done.")

Reload 重新載入

用於更新了某個指令檔案

1
2
3
4
@bot.command()
async def reload(ctx, extension):
await bot.reload_extension(f"cogs.{extension}")
await ctx.send(f"ReLoaded {extension} done.")

完整程式碼

bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os 
import asyncio
import discord
from discord.ext import commands

intents = discord.Intents.all()
bot = commands.Bot(command_prefix = "=", intents = intents)

@bot.event
async def on_ready():
print(f"目前登入身份 --> {bot.user}")

@bot.command()
async def load(ctx, extension):
await bot.load_extension(f"cogs.{extension}")
await ctx.send(f"Loaded {extension} done.")

@bot.command()
async def unload(ctx, extension):
await bot.unload_extension(f"cogs.{extension}")
await ctx.send(f"UnLoaded {extension} done.")

@bot.command()
async def reload(ctx, extension):
await bot.reload_extension(f"cogs.{extension}")
await ctx.send(f"ReLoaded {extension} done.")

async def load_extensions():
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
await bot.load_extension(f"cogs.{filename[:-3]}")

async def main():
async with bot:
await load_extensions()
await bot.start("機器人的TOKEN")

if __name__ == "__main__":
asyncio.run(main())

Main 檔內容

main.py 通常用於存取指令(其實你想放甚麼都行,但主要以易理解的方式撰寫)。但使用 Cog 寫法的話,要注意以下幾點:

注意事項

  1. @bot.event 要改成 @commands.Cog.listener()
  2. @bot.command() 要改成 @commands.command()
  3. 原指令用 bot 在 Cog 寫法下,都要變成 self.bot
  4. 每個指令的第一個參數要是 self

基本架構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import discord
from discord.ext import commands

class Main(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot

# ...

# 這裡 setup 函式表示將 class Main 的指令加到 bot.py 中,
# 因此在 bot.py 中你會看到為何要 load_extensions() 在先,
# 而 bot.start("TOKEN") 在後。
async def setup(bot):
await bot.add_cog(Main(bot))

範例

以 ping 指令當作範例,當我們輸入 =ping 時,它就會回答當前機器人的延遲「The Bot Latency: 215ms
下方的範例,可以看到符合了注意事項的第二到四點。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import discord
from discord.ext import commands

class Main(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot

# ping
@commands.command()
async def ping(self, ctx):
await ctx.send(f"The Bot Latency: `{round(self.bot.latency * 1000)}ms`")

# 其他指令...

async def setup(bot):
await bot.add_cog(Main(bot))

Event 檔內容

這裡其實就跟 main.py 的寫法一樣,所以就不多加贅述直接寫範例。

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import discord
from discord.ext import commands

class Event(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot

# welcome guild
@commands.Cog.listener()
async def on_member_join(self, member):
wel_channel = self.bot.get_channel(你的歡迎頻道 ID) # 將字串修改為歡迎頻道 ID(整數型態)
await wel_channel.send(f"歡迎 {member.mention} 加入!")

# leave guild
@commands.Cog.listener()
async def on_member_remove(self, member):
leave_channel = self.bot.get_channel(你的離開頻道 ID) # 將字串修改為離開頻道 ID(整數型態)
await leave_channel.send(f"{member.mention} 期待我們再次相遇")

# message listener
@commands.Cog.listener()
async def on_message(self, msg):
# 防止機器人自己觸發
if msg.author == self.bot.user:
return
if msg.content == "qq" or msg.content == "QQ":
await msg.channel.send("幫 QQ")

async def setup(bot):
await bot.add_cog(Event(bot))

參考網站

  • discord.py Cogs 官方文檔
  • Python Discord Bot 進階教學 — Cog 篇
  • YouTube 頻道 Proladon