Discord Bot

lecturer: 乘一

~雜七雜八

基本架構

Bot基本架構

import discord    #導入 Discord.py
from discord.ext import commands #導入 commands 雖然目前不會用到
import os    #導入os模組

intents = discord.Intents.all()
bot = commands.Bot(command_prefix='你想要的指令前綴', intents = intents) 
#透過bot與dicord連結,並設定command的前綴(但還不會用到)

@bot.event #這是裝飾器
async def on_ready(): #當機器人完成啟動時
    print('目前登入身份:', bot.user)
    
try:
    bot.run('機器人TOKEN') #就是你剛剛拿到的TOKEN
except:
    os.system("kill 1") #修正程式錯誤(repl會莫名其妙出錯)

Event

Event

#當...的時候要做甚麼->event!

@bot.event #裝飾器
async def function_name(parameters): 
  #async 異步協程
  ...

Event

Command

Command

@bot.event
async def on_message(msg):
	await bot.process_commands(msg)
	#解決on_message 與 command 衝突的問題

@bot.command()
async def command_name(ctx): 
	#ctx上下文
	#包含各種訊息,如:發送者、頻道...
	#...

Cog

Cog 是啥?

Bot.py

on_message

on_member

ping

Cog 是啥?

Bot.py

on_message

on_member

ping

Event.py

cmd.py

Cog 是啥?

Bot.py

on_message

on_member

ping

Event.py

cmd.py

Cog 是啥?

Bot.py

on_message

on_member

ping

Event.py

cmd.py

load

Cog

先分開再說

Cog

Command

#原本
import discord 
from discord.ext import commands 

@bot.command()
async def ping(ctx):
  await ctx.send(f'{round(bot.latency*1000, 2)}ms')

Command

#改成cog
import discord 
from discord.ext import commands 

class Cmd(commands.Cog): #繼承commands.Cog
  def __init__(self, bot):
    self.bot = bot
  @commands.command() #記得改
  async def ping(self, ctx): #記得加self
    await ctx.send(f'{round(self.bot.latency*1000, 2)}ms')

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

Event

#原本event
import discord  #導入 Discord.py
from discord.ext import commands
import asyncio

@bot.event
async def on_message(msg):  #當偵測到訊息
    if msg.author == bot.user:  #避免無限循環
        return
    if msg.content.startswith("say"):  #偵測msg的開頭
        await msg.channel.send("hello")

Event

#改成cog
import discord 
from discord.ext import commands
import asyncio

class Event(commands.Cog): #繼承commands.Cog
    def __init__(self, bot):
        self.bot = bot
    @commands.Cog.listener() #改
    async def on_message(self, msg): #記得加self
        if msg.author == self.bot.user:  #避免無限循環
            return
        if msg.content.startswith("say"):  #偵測msg的開頭
            await msg.channel.send("hello!")

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

Main

import discord  #導入 Discord.py
from discord.ext import commands 
import os  #導入os模組
import asyncio

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

@bot.event  #這是裝飾器
async def on_ready():  #當機器人完成啟動時
    print('目前登入身份:', bot.user)

@bot.command()
async def load(ctx, ext):
    await bot.load_extension(f'cogs.{ext}')
    await ctx.send(f'{ext} loaded successfully.')

@bot.command()
async def unload(ctx, ext):
    await bot.unload_extension(f'cogs.{ext}')
    await ctx.send(f'{ext} unloaded successfully.')

@bot.command()
async def reload(ctx, ext):
    await bot.reload_extension(f'cogs.{ext}')
    await ctx.send(f'{ext} reloaded successfully.')

async def load_extensions(): #一開始要把所有cog載入
    for filename in os.listdir("./cogs"):
        if filename.endswith(".py"): #偵測有py的檔案
            await bot.load_extension(f"cogs.{filename[:-3]}") #去除.py

async def main():
    async with bot:
        await load_extensions()
        await bot.start(os.getenv("TOKEN"))

if __name__ == "__main__": #如果跑的是這個py
    try:
        asyncio.run(main())  #就是你剛剛拿到的TOKEN
    except:
        os.system("kill 1")  #修正程式錯誤(repl會莫名其妙出錯)

Classes.py

import discord  #導入 Discord.py
from discord.ext import commands

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

Classes.py

#每個檔案都要繼承?
class Filename(commands.Cog): 
    def __init__(self, bot):
        self.bot = bot

簡化!

#把重複的東東放到classes.py
#用的時候繼承class就好了
from core.classes import Cog_Extension
class Filename(Cog_Extension):

Uptime Robot

終於要開始上課了

repl.it 會自動斷線!??

當你把你的bot放在repl.it上面開始跑

然後睡一覺起來之後...

!?

我的bot怎麼下線了???

要怎麼解決這個問題?

很簡單!

打開就好了

ㄟˊ奇怪他怎麼不讓我開

年輕人~

發揮你的鈔能力吧!!!

這樣我就不用備課了🥳

uptime robot!

  1. 免費

  2. 自動網站監測

  3. 發送訊息提醒

uptime robot!

#新建一個檔案 keep_alive.py
#不要問我這在幹嘛
from flask import Flask
from threading import Thread

app = Flask('')

@app.route('/')
def main():
    return 'Bot is aLive!'

def run():
    app.run(host="0.0.0.0", port=8080)

def keep_alive():
    server = Thread(target=run)
    server.start()

uptime robot!

#在main.py最前面加上
import keep_alive

#在bot.run前一行加上
keep_alive.keep_alive()

#然後跑跑看?

uptime robot!

複製網址

uptime robot!

貼上網址

搞定!

Slash Command

Slash Command是什麼

反正就長這樣
我也不會解釋

Slash Command

#寫在cmd.py
from discord import app_commands
import discord  #導入 Discord.py
from discord.ext import commands
from core.classes import Cog_Extension #從你的classes.py 引入 class

class Cmd(Cog_Extension):
    @commands.command() #同步指令樹的指令
    async def sync(self, ctx):
        fmt = await ctx.bot.tree.sync()
        await ctx.send(f'Synced {len(fmt)} commands')
        
    @app_commands.command(name="slash", description="test command")
    async def slash(self, interaction: discord.Interaction):
        await interaction.response.send_message("Hello Slash!")

關於slash command

有興趣可以看一下
對我懶得做簡報
反正也沒人來聽課QAQ

Converter

本來沒有要教這個
但我覺得今天的課好像有點水

Basic Converter

@bot.command()
async def add(ctx, a: int, b: int):
#                    ^ 這個東西就是converter
    await ctx.send(a + b)

自己寫一個?

def to_upper(argument):
    return argument.upper()

@bot.command()
async def up(ctx, *, content: to_upper):
    await ctx.send(content)

Advanced Converter

import random

class Slapper(commands.Converter):
    async def convert(self, ctx, argument):
        to_slap = random.choice(ctx.guild.members)
        return f'{ctx.author} slapped {to_slap} because *{argument}*'

@bot.command()
async def slap(ctx, *, reason: Slapper):
    await ctx.send(reason)

如果需要用到ctx?

Special Converter

import typing

@bot.command()
async def union(ctx, what: typing.Union[discord.TextChannel, discord.Member]):
#如果符合discord.Textchannel就使用這個converter
#如果不符合,就試試看discord.Member
#由左至右檢查,都不符合會出現錯誤
#裡面可以放任何你要的converter
    await ctx.send(what)

typing.Union

Special Converter

import typing

@bot.command()
async def bottles(ctx, amount: typing.Optional[int] = 99, *, liquid="beer"):
#如果傳入的第一個參數不是int,
#則amount=99(如果不指定值則為None),並把第一個參數傳遞給liquid(跳過amount)
    await ctx.send(f'{amount} bottles of {liquid} on the wall!')

typing.Optional

Special Converter

from typing import Literal

@bot.command()
async def shop(ctx, buy_sell: Literal['buy', 'sell'], amount: Literal[1, 2], *, item: str):
    await ctx.send(f'{buy_sell.capitalize()}ing {amount} {item}(s)!')
#傳入值要符合buy或sell其中一個,不然會出錯
#同理,第二個值如果不是1或2就會出錯

Literal

Special Converter

@bot.command()
async def slap(ctx, members: commands.Greedy[discord.Member], *, reason='no reason'):
    slapped = ", ".join(x.name for x in members)
    await ctx.send(f'{slapped} just got slapped for {reason}')
#一直測試參數是否為discord.Member,直到不符合才會跳下一個參數

Greedy

Converter

基本上,你用不到剛剛教的東西

但它很帥,所以學一下不是壞事

順帶一提,以上內容都是抄官方文件

THE END

希望這次可以準時?

Discord Bot-2

By times1-chang

Discord Bot-2

  • 177