Discord Bot

Discord

沒有用過discord的你就別來了吧

Discord API?

APP? Bot? API?

Pycord? JDA? discord.js ?

 

  • Discord APP (不會動)
    • 可以創建 "Bot"
      • 使用Discord API 與Discord 交互

 

官方提供的HTTP API/ WebSocket Gateway

直接對接官方API不容易

Discord API

  • 要自己處理HTTP/WS
  • 自己看docs解析JSON
  • 事件種類太多

Community 提供的 Wrapper,能更輕鬆控制Bot

Bot Libraries

  • discord.py / Pycord
  • discord.js
  • Java Discord API (JDA) (don't)
  • ... 自己刻

Discord (Central)

Bot Client

(你要寫的東西)

User

Discord API

websocket

互動

很簡單

你做的互動,會丟給Client

Client處理,丟回去。

(GW)

所以,如果要讓Bot 24/7 運行

你的Bot Client (你寫的code) 不能停下。

Create your Application/Bot

Developer Portal

Head to:

2. New Application

左邊去Bot Settings

可以更改

 

  • Icon
  • Banner
  • Username

Gateway Intents

Developer Portal

Discord Gateway會傳大量事件給你的Bot,

Intents設定可以篩選說: 你只想接收哪些事件

分成兩種:

  • Standard Intents (一般事件,預設就能用)
  • Privileged Intents (需要去Dev Portal開啟)
    • GUILD_MEMBERS
    • GUILD_PRESENCES
    • MESSAGE_CONTENT
    • ​如果Bot在 >= 100 Guilds, Bot需要審核

所以我們去開Privileged Intents。

但我們沒有

Create Invite URL

Developer Portal

左邊去OAuth2

OAuth2 URL Generator

check "bot"

再來,勾選Bot Permission

底下應該就會看到你的Invite URL了

開啟Invite URL,把Bot邀入你的Server.

///

environment setup

Bot Developement

Requirements:

  • Python 3.10 - 3.13
  • py-cord library
  • A working Text Editor

Make sure that you have your python installed

# Linux/macOS
python3 -m pip install -U py-cord

# Windows
py -3 -m pip install -U py-cord

Install py-cord Library

# Linux/macOS
python3 -m pip install -U dotenv

# Windows
py -3 -m pip install -U dotenv

Install other Libraries

///

turn your bot on

Bot Developement

回到Developer Portal: Bot

這裡有一個Token, 點Reset建立一個

(請勿外洩自己的Token, Token用於控制你的Bot)

像這樣,Copy起來。

建立.env

寫 DISCORD_TOKEN=<token>

(.env 環境變數, 避免在code內寫token)

import discord 
import os
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")

bot = discord.Bot(intents=discord.Intents.all())

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

in yor main.py (or bot.py, whatever)

讀取.env, run你的bot

執行看看,

你就能看到你的Bot是Online State了

///

一些名詞解釋

Bot Developement

  • User

  • Guild

  • Member

Discord 用戶

Discord 伺服器

Discord 伺服器內的用戶 (可以轉成User)

以Discord Bot的視角來看:

對了

python剛學兩週,qq

pycord也是

簡單講python怎麼threading

Python

插入一點

在這邊不怎麼重要就是了

async def helloworld():
	print("Hello")
    
    // time consuming tasks
    await asyncio.sleep(1)
    
    print("World")

asyncio.run(helloworld())

async 

  • 宣告 Coroutine

await 

  • 等待 Coroutine 完成
  • 只能在 Coroutine 內

Coroutine不能直接呼叫,呼叫只會拿到Coroutine物件

必須使用 await 或是交給 EventLoop !!!!!

asyncio.run(coro)

- 直接在 "同步" 啟動EventLoop執行Coro

- block

 

asyncio.create_task(coro)

- Wrap成Task,交給EventLoop執行Coro

- caller non-block

anyway

 

你只需要知道

Pycord這個Lib是非同步架構

 

提供的一些function都要有

async / await

events

Bot Developement

可以寫一些function來Listen一些發生的"Event"

  • on_ready

  • on_member_join

  • on_message

  • ...

當client完成初始化

當member加入guild

當message被發出

on_ready

@bot.event
async def on_ready():
	# your code

當client完成初始化

on_ready

@bot.event
async def on_ready():
	print("The bot is ready.")

on_member_join

@bot.event
async def on_member_join(member: discord.Member):
	# your code

當member加入guild

on_member_join

@bot.event
async def on_member_join(member: discord.Member):
    channel = bot.get_channel(1442049122986229843)
    await channel.send(f"welcome {member.mention}")

on_message

@bot.event
async def on_message(message: discord.Message):
    await message.channel.send("shut up")

on_message

喔不,不對勁

他把自己算進去了

on_message

@bot.event
async def on_message(message: discord.Message):
    if message.author == bot.user:
        return
    await message.channel.send("shut up")
    # await message.reply("shut up")

///

command

Bot Developement

@bot.command()
async def hello(ctx):
    await ctx.send("Hello!")

簡單範例

@bot.command()
async def hello(ctx, name: str):
    await ctx.send(f"Hello, {name}!")

指令參數

@hello.error
async def hello_error(ctx, err):
    if isinstance(err, commands.MissingRequiredArgument):
          await ctx.send("missing args")

參數Error Handling

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

在setup bot的時候要設定prefix

slash command

Bot Developement

@bot.slash_command()
async def ping(ctx):
    await ctx.respond("pong")

簡單範例

Alternatively...

you can specify the command name instead of using the function name.

@bot.slash_command(name="ping")
async def ping_command(ctx):
    await ctx.respond("pong")

ctx 有些什麼?

  • ctx.author
  • ctx.guild
  • ctx.channel
  • ctx.id
  • ...
  • ctx.respond()
  • ctx.defer()
  • ...

Command Args

Bot Developement

fallback

Python

插入一點

def f(name="Andy"):
	print(f"Hello {name}!")
    
f("Kevin") // "Hello Kevin!"

f() // "Hello Andy!"

= fallback

來fallback參數

def f(name=None):
	if name is None:
    	name = "Andy"
	print(f"Hello {name}!")
    
f("Kevin") // "Hello Kevin!"

f() // "Hello Andy!"

= None

來動態處理各種Behavior

  • Required (default)? Optional?
  • Default (Fallback) Value
  • List of Options
  • Description

寫法: 寫在宣告function的地方,直接是參數

@bot.slash_command(name="ping")
async def ping_command(
    ctx,
    text: str              
):
  • required
  • w/o desc

fallback 讓option變成是optional的

@bot.slash_command(name="ping")
async def ping_command(
    ctx,
    text: str = "hi"
):
  • name: text
  • type: str
  • optional (fallb: "hi")
  • no-description

(當然,也可以fallback成None)

  • optional
  • w/o desc

當你想要有description,事情會變得麻煩起來...

@bot.slash_command(name="ping")
async def ping_command(
    ctx,
    text: Option(str, "description")
):
  • required
  • w/ desc

fallback 寫法一樣

@bot.slash_command(name="ping")
async def ping_command(
    ctx,
    text: Option(str, "description") = "hi"
):
  • optional
  • w/ desc

List Options

color: Option(str, choices=["red", "blue", "green"])
  • required
  • w/o desc
color: Option(str, "choose!", choices=["red", "blue", "green"])
  • required
  • w/ desc

optional就不廢話了,一樣的fallback法

Types

  • str (STRING)
  • int (INTEGER)
  • float (NUMBER)
  • bool (BOOLEAN)
  • discord.Member (USER)
  • discord.User (USER)
  • discord.Role (ROLE)
  • discord.TextChannel (CHANNEL)
  • discord.VoiceChannel (CHANNEL)
  • discord.StageChannel (CHANNEL)
  • discord.CategoryChannel (CHANNEL)
  • discord.GuildChannel (CHANNEL)
  • discord.Emoji (STRING)

embed message

Bot Developement

embed = discord.Embed(
	title: str, 
    description: str, 
    url: str, 
    color: discord.Color, 
    timestamp: datetime.datetime
)

(the params are all optional btw)

embed = discord.Embed(
    title="Title",
    description="Description",
    color=discord.Color.red(),
    url="https://github.com/",
    timestamp=datetime.datetime.now(datetime.UTC)
)

Color

discord.Color.random()
discord.Color.red()
discord.Color.from_rgb(0xAA, 0, 0)

discord.Color.from_str("0xAA0000")
discord.Color.from_str("#AA0000")
discord.Color.from_str("0x#AA0000")
embed.set_thumbnail(url: str)

Method: set_thumbnail

Method: set_thumbnail

embed.set_thumbnail(url="https://...")

Method: set_thumbnail

file = discord.File("file.png", "filename.png")

embed.set_thumbnail(url="attachment://filename.png")

await ctx.respond(embed=embed, file=file)
embed.set_author(name: str, url: str, icon_url: str)

Method: set_author

embed.set_image(url: str)

Method: set_image

embed.set_footer(text: str, icon_url: str)

Method: set_footer

embed.add_field(name: str, value: str, inline: bool)

Method: add_field

await ctx.respond(embed=embed)

Send Embed Message

await ctx.respond(embeds = [e1, e2])

button

Bot Developement

class TestView(discord.ui.View):
    @discord.ui.button(label="Click Here", style=discord.ButtonStyle.primary)
    async def click_me(self, button: discord.ui.Button, interaction: discord.Interaction):
        # 行為
        await interaction.response.send_message("ok.", ephemeral=True)

建立一個Class, 繼承discord.ui.View

  • label: btn上的字
  • style: Button Style

裡面寫一個@discord.ui.button的 async Function

@bot.event
async def on_message(message: discord.Message):
    if message.author == bot.user:
        return

    if message.content == "hi":
         await message.channel.send(content="This is a Button", view=TestView())

找個時機把Button傳出去

像這裡是在on_message: "hi"

實作 亂玩

Made with Slides.com