接下來,來介紹如何用 Cog 幫助我們管理 Discord BOT 指令們。
今天這個主題算是與 bot 指令框架相關的 (對 bot 不熟的人可以去看 Day 11 的文章 XD),會介紹如何用 Cog 來幫助管理 bot 的指令。
在軟體開發中,當一個專案開始變得龐大、複雜時,往往會選擇將功能做拆分,並分門別類做管理。之後再用 import 的方式整合回主程式中。而在 discord.py
中,也有類似的設計。
當 Discord BOT 功能變得更多更複雜的時候,單純只靠一個 main.py
檔案就存放所有程式碼已經變得不太實際。這時候,除了把某些跟 discord.py
無直接關係的函數拆出去之外 (其實就是一般 python 專案的做法),也可以把多個 bot 指令放進一個 class 中,再拆出去。而這個 class,就叫做 Cog。
在 discord.py
中,有一個叫做 Cog 的 class,各位可以把它視為 bot 指令的容器 (當然,實際上的功能更多 XD)。使用時,可以整個 Cog 一起 import 到 main.py
主程式中,甚至也可以依照需求加入或移除 Cog (不必重啟主程式)。除此之外,如果再搭配 Extensions,甚至可以做到指令們的「hot-reload」!
不過,Extention 的部分就留到明天再介紹吧!
我一直查不到 Cog 這個詞到底是怎麼來的,英文直翻是「齒輪」,感覺不太合理。如果是縮寫的話,從文件上的描述也看不太出來。想來想去,唯一勉強有機會的大概是「Command group」,如果各位讀者知道的話,歡迎留言跟我說一下!
先來看一下 Cog 該怎麼使用。文件上有條列式的整理出幾點重要的規則:
在看範例之前,先來看一下 Cog 架構
from discord.ext import commands
class MyCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# 在這邊加 command、listener
如同前面所述,自定義的 cog 都是 Cog
的 subclass,而且在 init 的時候,需要傳入 Bot
。後續就再依序球繼續加各種 command、listener。
# ping.py
from discord.ext import commands
class MyCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command()
async def ping(ctx: commands.Context):
await ctx.send("pong")
# main.py
import discord
from discord.ext import commands
from ping import MyCog
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='', intents=intents)
@bot.command()
async def load(ctx: commands.Context):
await bot.add_cog(MyCog(bot))
await ctx.send("Ping cog loaded", ephemeral=True)
@bot.command()
async def unload(ctx: commands.Context):
await bot.remove_cog("MyCog")
await ctx.send("Ping cog unloaded", ephemeral=True)
@bot.event
async def on_command_error(ctx: commands.Context, error: commands.CommandError):
await ctx.send(f"發生錯誤了: {error}")
bot.run('token')
執行後,如果直接輸入 ping
,會觸發 CommandNotFound
的錯誤。
但是,如果輸入 load
,就可以使用 ping
了。
此時如果再輸入 unload
,就又回到無法使用 ping
的狀態。
這個範例有兩個關鍵,分別是在 load
觸發的 add_cog
,以及在 unload
觸發的 remove_cog
。
add_cog
顧名思義,就是把 cog 加入到 bot 中。要帶入參數就是 cog 物件。如果只想針對特定的伺服器,可以再加上 guild
或 guilds
參數進行設定。
remove_cog
同理,就是把 cog 從 bot 中移除。需要特別注意的是,remove_cog
要帶入的參數是 cog 名稱,格式是字串 (str
)。同樣地,如果只想針對特定的伺服器,可以再加上 guild
或 guilds
參數進行設定。
原則上,在 remove_cog
需要使用到的 cog 名稱,就是 class 的名稱。但如果有需要的話,也可以另外設定:
class MyCog(commands.Cog, name='My Cog'):
pass
既然都已經把 command 等都整理到一個 class 中,那麼自然也就可以在裡面添加一些屬性或其他自定義的函數。
來看一下這個範例:
class Greetings(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._last_member = None
@commands.command()
async def hello(self, ctx, *, member: discord.Member = None):
"""Says hello"""
member = member or ctx.author
if self._last_member is None or self._last_member.id != member.id:
await ctx.send(f'Hello {member.name}~')
else:
await ctx.send(f'Hello {member.name}... This feels familiar.')
self._last_member = member
執行效果如下:
可以看到,連續輸入兩次一樣的 hello
指令,會得到不同的結果。而背後的原因也很簡單,就是有把最後一次呼叫這個指令的人紀錄在 self._last_member
中。每次呼叫的時候,就會去比對是否與 self._last_member
是同一人,再根據結果做出對應的回應。
所以,藉由 Cog 這樣的 class,就可以讓我們更好地去儲存各種狀態,方便後續的使用。
當然,即使不用 Cog,也還是有辦法可以做到相同的效果,只是如果有使用 Cog,就可以把儲存的資訊區隔開來,避免互相汙染,同時也更方便進行管理。
今天介紹了 Cog 的基本使用,包含:建立、載入、移除。明天會繼續介紹 Cog 的好夥伴 Extension。