iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
Software Development

selenium爬蟲應用至discord bot系列 第 29

[DAY29]Discord bot取得網頁消息 - 3

  • 分享至 

  • xImage
  •  

昨天我們已經將爬蟲的部分寫完了,今天要來寫Discord bot的部分。

├─ color_fetch.js #獲取文字顏色✅
├─ fetch.js #抓取網頁內容✅
├─ fetch.py #抓取網頁內容✅
└─ split.py #從網頁內容中分出粗體文字的部分✅

為了方便我先將可能用到的檔案標示出來。

├─ cmds #Cog檔
│  ├─ event.py #bot的event事件
│  ├─ main.py #bot的主要指令
│  ├─ react.py #bot的指令響應訊息
│  └─ task.py #bot的指令任務
├─ core
│  ├─ bot_event.py #這裡放一些bot主要的event事件
│  └─ classes.py #這裡放一些需要重複使用class設定
│
├─ bot.py #bot本體
└─ setting.json #bot的設定檔

接著我們先來建置一下bot本體
bot.py

import discord
from discord.ext import commands
import json
import os

# 讀取設定檔
with open("setting.json",'r',encoding="utf8") as jset:
    setdata = json.load(jset)

# 建立 Discord bot 物件
intents= discord.Intents.all()  
bot = commands.Bot(command_prefix="^^", intents=intents)

# 函式用來載入所有擴充套件,有Cog檔時才需使用
async def load_cog():
    for filename in os.listdir("./cmds"):
        if filename.endswith(".py"):
            extension_name = f"cmds.{filename[:-3]}"
            try:
                await bot.reload_extension(extension_name)
            except commands.ExtensionNotLoaded:
                await bot.load_extension(extension_name)

# Bot 啟動時要執行的事件
@bot.event
async def on_ready():
    channel = bot.get_channel(setdata["channel_id"])
    await channel.send("Ready!")

    # 載入所有指令程式檔案
    await load_cog()
    
    # 將斜槓指令註冊至Discord,如果發生錯誤回傳設定好的訊息與錯誤訊息
    try:
        synced = await bot.tree.sync()
        print(f"Synced {synced} commands")
    except Exception as e:
        print("An error occurred while syncing: ", e)

# 重新載入所有擴充套件
@bot.hybrid_command(name="reload_all")
async def reload_all(ctx):
    await load_cog()
    await ctx.send("Reload all done.")

# 載入單一擴充套件
@bot.command()  
async def load(ctx, extension):
    await bot.load_extension(f"cmds.{extension}")
    await ctx.send(f"Loaded {extension} done.")

# 卸載單一擴充套件 
@bot.command()
async def unload(ctx, extension):
    await bot.unload_extension(f"cmds.{extension}")
    await ctx.send(f"UnLoaded {extension} done.")

# 重新載入單一擴充套件
@bot.command()
async def reload(ctx, extension):
    await bot.reload_extension(f"cmds.{extension}")
    await ctx.send(f"ReLoaded {extension} done.")

if __name__ == "__main__":
    bot.run(setdata["TOKEN"])

這樣一個基本的bot就已經架構好了,而load_cog函式和其他讀取Cog的程式其實應該要Cog建好之後才要加的,這裡為了方便展示就先將程式加進來。

而程式中你也可以將所有的指令都換成斜槓指令,@bot.command()替換為@bot.hybrid_command()即可,但要記得必須註冊await bot.tree.sync()


在bot主程式bot.py中我們有用setting.json去讀一些資料,當然你也可依照自己需求增加

{
    "TOKEN": "YOUR TOKEN",
    "channel_id": 993457414236020776
}

而bot的一些主要程式你也可以分別放到不同的地方,例如將主要的event事件放到core資料夾底下的bot_event.py。

bot_event.py

from discord.ext import commands

def setup(bot: commands.Bot):
    # 在 Bot 物件註冊 on_message 事件
    @bot.event
    async def on_message(mes):
        # 檢查訊息內容是否以 '^test' 開頭,如果是,刪除該訊息
        if mes.content.startswith('^^test'):
            await mes.delete()
        # 處理指令的方法
        await bot.process_commands(mes)

接著需要將bot與這個額外建立的bot指令連接,因此需要在主程式bot.py中導入這個class並把當前的bot傳給這個class

bot.py

from core.bot_event import setup as setup_event

...

setup_event(bot)

在主程式加入以上指令即可把不同部分的bot指令做連接。


而Cog也可以簡單地將指令去做分類,而Cog檔在使用時都必須初始化設定

from discord.ext import commands

class MyCog(commands.Cog):

  def __init__(self, bot):
    self.bot = bot

但若是Cog檔複數個時這個設定就必須打很多次,為了節省步驟可以將這些常用的設定加到core資料夾底下的classes.py裡,之後就只需要繼承classes.py裡的類別即可。

classes.py

from discord.ext import commands

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

接著在其他Cog檔就只需要引用這個Class並繼承即可

event.py

from core.classes import Cog_Extension

class Event(Cog_Extension):
    pass

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

Cog檔中setup函式是必須要有的要記得加上。

另外三個Cog檔也相同

main.py

from discord.ext import commands
from core.classes import Cog_Extension

class Main(Cog_Extension):
    # ping指令,回傳機器人延遲
    @commands.command()
    async def ping(self, ctx):
        await ctx.send(f'{self.bot.latency*1000:.0f} ms')

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

react.py

from discord.ext import commands
from core.classes import Cog_Extension

class React(Cog_Extension):
    # 清除訊息指令
    @commands.command()
    async def clear(self, ctx, num: int):
        await ctx.channel.purge(limit=num+1)

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

以上是做刪除訊息的指令,這裡選用purge這個方法,使得可以批量刪除近期訊息。

此外也可以針對不同Cog額外做一些初始化設定,但要記得子類別做初始化設定時會將父類別的設定也覆蓋掉因此在設定時需要重新將父類別的設定讀回來。如下:

task.py

from datetime import datetime, timedelta, timezone
from discord.ext import tasks
from core.classes import Cog_Extension
import asyncio

# 設定時區
utc_plus_8 = timezone(timedelta(hours=8))

class Task(Cog_Extension):
    # 初始化設定
    def __init__(self, *args, **kwargs):
        # 將父類別的初始化設定重新寫入
        super().__init__(*args, **kwargs)
        # 子類別自己的初始化設定
        # 實作互斥鎖(Mutex)
        self.lock = asyncio.Lock()
        # task開始
        self.send_test.start()

    # 當Cog檔被卸載時task也被停止
    async def cog_unload(self):
        self.send_test.stop()

    # 每秒執行一次
    @tasks.loop(seconds=1)
    async def send_test(self):
        async with self.lock:
            # 每秒檢查當前時間是否為XX時XX分10秒
            now = datetime.now(utc_plus_8)
            if now.second == 10:
                channel = self.bot.get_channel(993333414446020776)
                await channel.send("test")
    

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

在這個Cog檔中我們新增了一些初始化設定,這裡設定的互斥鎖的作用是確保同一時間只有一個執行緒可以進入特定的程式碼區塊。

並且使用了async with語法可以保證同一時間只有一個send_test執行緒可以進入送訊息的區塊。

因此上面程式會在每次台灣時間到秒數10的時候傳送一次test訊息到指定頻道,當然頻道也可從setting.json讀取。


到此,我們就將bot的基礎架構與其中的一些不同的用法架構完了。最後一天我們就可將我們已經寫好的爬蟲程式加入到bot裡,讓Discord能夠輸入指令回傳網站最新的一則消息。


上一篇
[DAY28]Discord bot取得網頁消息 - 2
下一篇
[DAY30]Discord bot取得網頁消息 - 4
系列文
selenium爬蟲應用至discord bot30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言