寒訓DC BOT教學

CHAPTER 1

使って必要な工具達は、何でしょうか?

\(\downarrow\)

その一 - Discord

Discord是一款專為社群設計的免費網路即時通話軟體與數位發行平台

使用者之間可以在聊天頻道通過資訊、圖片、影片和音訊進行互動。

- Wikipedia

その二 - Repl.it

~~online compiler

その三 - Github

Coder の本当の友達だ

主要著重在"版本管理"此重點上

也是備份的方法之一

隨時隨地都可以編錯 淪為被專案壓榨的coder

CHAPTER 2

DC BOTと言う物は、何ですか?

\(\downarrow\)

機器人列表

指令使用範例

使用現成

優點

  • 快速
  • 替代品
  • 方便設定

缺點

  • 不完美
  • 可惡的課金機制

自己做

優點

  • 超級完美

缺點

  • 建立速度慢
  • 超耗時間

CHAPTER 3

How to set up a DC Bot?

\(\downarrow\)

建置骨架

Step 1.

創建角色!

Step 2.

賦予天職!

Step 3.

解除搖光限制 (Intents)!

Step 4.

邀請到伺服器中!

魂作り時間だよ~

CHAPTER 3.1

Type 1 - event

\(\downarrow\)

術語解說

  • token
  • intents
  • client
  • prefix

requirements.txt

discord.py

Basic framework

from discord.ext import commands
import discord

intents = discord.Intents.all()
bot = commands.Bot(command_prefix=<your prefix>: str, intents=intents)

# your program goes here


bot.run(<your token>: str)

Event

@bot.event
  • 當觸發特定條件時被呼叫的修飾器

  • 特定條件取決於函式名稱

  • 當機器人準備好時呼叫

@bot.event
async def on_ready():
    print("~~~~~~>> Bot is online <<~~~~~~")
  • 當新成員加入時呼叫

@bot.event
async def on_member_join(member):
    await member.send('歡迎加入伺服器!')
  • 當成員離開/被踢掉時呼叫

@bot.event
async def on_member_remove(member):
    welcome_channel = bot.get_channel(<channel id>: int)
    await channel.send(f'我們懷念 {member.name}')
  • 當有人傳訊息時呼叫

@bot.event
async def on_message(message):
    if message.content == 'apple':
      await message.channel.send('I like apple! ^~^')

動手做時間!

避免誤觸

if message.author == bot.user:
  return
if message.author.bot:
  return 
@bot.event
async def on_message(message):
    if message.author == bot.user:
        return 
    
    if message.author.bot:
        await message.channel.send('Yes')
    else:
        await message.channel.send('No')

小さい宿題!

  • tokenの守る方を考えさせてください

CHAPTER 3.2

Type 2 - command

\(\downarrow\)

Command

@bot.command()
  • 指令修飾器

  • 自定義的事件

  • 功能取決於程式碼

  •  透過 <prefix>+函式名稱 召喚

基本回傳物件

  • context (ctx for short)

基本配備

  • ping
bot.latency  # return in seconds

やってみなさい!

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

限制指令使用權限

@bot.command()
async def for_admin(ctx):
    if ctx.author.bot:
        return 
    
    is_admin = bool(False)
    for role in ctx.author.roles:
        if role.name == 'admin':
            is_admin = True
            break
    if is_admin == False:
        await ctx.send('You are not admin!')
        return 
    
    await ctx.send('You are admin!')
@bot.command()
@commands.has_any_role('admin1', 'admin2')
async def for_admin(ctx):
  await ctx.send('You are admin!')

多認識 discord.py

的物件們吧

CHAPTER 3.2.5

Cog Extension

\(\downarrow\)

舒適主義至上

  • 程式碼可讀性

  • 程式碼精簡

  • 整齊的工作區

}

cogsという物

\(\leftarrow\) main.py (repl.it)

\(\leftarrow\) !!

突來的解答

tokenの守る方 \(\rightarrow\) .env file

  • 儲存於專案資料夾根目錄
  • 儲存環境變數用
  • key-value
TOKEN=<your token>

.env 中

main.py (repl.it) / bot.py 中

import os

# ...

bot.run(os.environ.get("TOKEN"))

When pushing to Github

  • 建立 .gitignore 檔於專案資料夾根目錄
# Token
.env

A cog is an organization of DC Bot functions

  • 整合同性質的函式
  • 乾淨的 main.py(repl.it) / bot.py

回歸cogs吧

栗子

  • kick.py \(\rightarrow\) 踢人程式碼

  • lecture.py \(\rightarrow\) 課堂程式碼

  • main.py \(\rightarrow\) 主功能程式碼

  • query.py \(\rightarrow\) 資料查詢程式碼

  • quiz.py \(\rightarrow\) 懸賞題程式碼

  • react.py \(\rightarrow\) 與成員互動的程式碼

  • task.py \(\rightarrow\) 背景自動程式碼

分塊的方式

Step 1.

Step 2.

from discord.ext import commands
import discord


class Event(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    @commands.Cog.listener()  # bot.event
    async def on_ready(self):
        print('---> I\'m online! <---')

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

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

in cogs/event.py

in main.py(repl.it) / bot.py

from discord.ext import commands
import discord
import os

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

for filename in os.listdir('./cogs'):
    if filename.endswith('.py'):
        bot.load_extension(f'cogs.{filename[:-3]}')

bot.run(os.environ.get("TOKEN"))

無可避免的解釋時間

# ...
for filename in os.listdir('./cogs'):
    if filename.endswith('.py'):
        bot.load_extension(f'cogs.{filename[:-3]}')
# ...
class Event(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    # ...
 
def setup(bot):
    bot.add_cog(Event(bot))

(1)

(2)

(3)

main.py

event.py

  1. 先由 main.py 呼叫 load_extension()

  2. 呼叫目標檔中的 setup(),並以main.py中的
    變數 bot 作為 argument 傳入

  3. setup() 利用 Event 此類別中的建構子
    宣告在接下來此類別中會用到的變數 self.bot

  • event、command 裝飾器名稱

  • bot 改為 self.bot

  • 於函式中加入 self 作為第一個 argument

Step 3.

要改的東西

麻煩的 __init__()

簡化步驟:繼承

in core/classes.py

import discord
from discord.ext import commands

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

in cogs/event.py

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


class Event(Cog_Extension):
    @commands.Cog.listener()  # bot.event
    async def on_ready(self):
        print('---> I\'m online! <---')

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

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

Other functions for cog extension

@bot.command()
async def load(ctx, msg):
    try:
        bot.load_extension(f'cogs.{msg}')
        await ctx.send(f':white_check_mark: Extension {msg} loaded.')
    except:
        await ctx.send(f':exclamation: There are no extension called {msg}!')
@bot.command()
async def unload(ctx, msg):
    try:
        bot.unload_extension(f'cogs.{msg}')
        await ctx.send(f':white_check_mark: Extension {msg} unloaded.')
    except:
        await ctx.send(f':exclamation: There are no extension called {msg}!')
@bot.command()
async def reload(ctx, msg):
    if msg != '*':
        try:
            bot.reload_extension(f'cogs.{msg}')
            await ctx.send(f':white_check_mark: Extension {msg} reloaded.')
        except:
            await ctx.send(f':exclamation: There are no extension called {msg}!')
    else:
        for filename in os.listdir('./cogs'):
            if filename.endswith('.py'):
                bot.reload_extension(f'cogs.{filename[:-3]}')

load

unload

reload

CHAPTER 3.3

Type 3 - group

\(\downarrow\)

Group

將區塊再分更小吧
@commands.group()
async def <group name>(self, ctx):
  pass

@<group name>.command()
async def <cmd name>(self, ctx):
  # ...

# ...

CHAPTER 4

24/7

\(\downarrow\)

requirements.txt

小小擴充包

keep_alive.py

from flask import Flask
from threading import Thread


app = Flask('')

@app.route('/')
def show_panel():
	return 'Bot is alive!'
  

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

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

小小修改

from discord.ext import commands
import discord
import keep_alive

intents = discord.Intents.all()
bot = commands.Bot(command_prefix=<your prefix>: str, intents=intents)

# your program goes here

keep_alive.keep_alive()

bot.run(<your token>: str)

in main.py(repl.it) / bot.py

UptimeRobot.com

原理

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

bot.run(<your token>: str)

\(\downarrow\)

\(\downarrow\)

\(\rightarrow\)

CHAPTER 5.1

.json

\(\downarrow\)

JavaScript Object Notation (JSON)

將結構化資料 (structured data) 呈現為 JavaScript 物件的標準格式,常用於網站上的資料呈現、傳輸

  • key-value 特性

~我們感謝維基百科/(_ _)\~

此地用途

舒適主義又出現了!
  • 文本可讀性
  • 整合整合再整合

栗子

糖炒的呦

{
  "join": {
      "opening": {
          "morning": [
              "早上好呀 ><",
              "不知道你有沒有睡飽呢?"
          ],
          "noon": [
              "中午好呀 ><",
              "等等要不要去睡個午覺呢?"
          ],
          "after_noon": [
              "下午好呀 ><",
              "不知道你剛剛有沒有睡了個飽滿的午覺?"
          ],
          "evening": [
              "晚上好呀 ><",
              "等一下要早早睡覺呦!"
          ],
          "night": [
              "姆姆...我好愛睡呦&^&~",
              "你也不要熬夜!",
              "(或是你已經起床惹 ><"
          ],
          "main": [
              "歡迎來到SQCS的群組!",
              "我是負責管理伺服器的機器人 - **Q ちゃん!**",
              "讓我為你說明伺服器中現在的相關設定",
              "現在伺服器中有著名為**天命系統**的東西運行著",
              "詳細的原因可以看一下這篇 hackmd",
              "https://hackmd.io/@Quantum-GrAyee/rkalZ7wiP"
          ]
      },
      "hackmd_read": [
        "姆姆, 你應該看完了吧?那我要繼續說惹",
        "總而言之,我將負責建立你的個人檔案 \\\\^~^",
        "但因最近伺服器中可能會有學測生",
        "或是有點忙碌的參與者",
        "可能沒有空來參加相關的活動",
        "不知道你是不是其中的一員呢...?(y/n)"
      ],
      "df_1": [
        "辛苦你了!",
        "先在這邊預祝你進行得順利 \\\\^~^",
        "那我就先將你的 `deep freeze` 狀態改為**是**囉!"
      ],
      "df_0": [
        "那太好了,在閒暇時段要多多參與伺服器中的活動呦!\\\\^~^",
        "那我就先將你的 `deep freeze` 狀態設為**否**囉!"
      ],
      "invalid_syntax": [
        "姆姆!",
        "不要傳給我我看不懂的符號啦 (氣><",
        "我就先認定你很閒囉!"
      ],
      "time_out": [
        "齁!都不回我話 (氣><",
        "那我就先認定你很閒囉!",
        "記得來參加群組內的活動呦!"
      ],
      "contact_method": [
        "> 日後如果之後要進行修改的話",
        "> 可以跟我的創造者",
        "> -- **phantom0174(小灰灰總召)**說一聲!"
      ],
      "fl_create_finish": [
        "久等了,我幫你創建好了屬於你的資料了!",
        "我好像也沒有甚麼要對你說的了",
        "那就祝你在SQCS的世界中玩得開心,掰掰 ><",
        "> 日後可使用指令 `mv!query all me`",
        "> 查詢個人的搖光資料"
      ]
  },
  "kick": {
      "kick_single": [
          "姆姆...",
          "看來你好像因為下列原因被管理者踢出伺服器惹 qwq"
      ],
      "kick_all": [
          "齁呦",
          "你的違反指數爆表惹啦><"
      ],
      "re_join": [
          "醬子不優呦 ><",
          "> Q ちゃん 偷偷給你一個加回來的機會><",
          "> https://discord.gg/KjWMRewQB2"
      ]
  },
  "lecture": {
      "start": {
          "pt_1": [
              ":loudspeaker: @everyone",
              "大家大家><"
          ],
          "pt_2": [
              ":bulb: 等等搶答時記得用 `&` 作為訊息的前綴",
              "我才看得到你說惹什麼咚咚!"
          ]
      },
      "end": {
          "main": [
              ":loudspeaker: @everyone",
              "感謝今天有來參加講座的人 :partying_face:",
              "你們都很棒><"
          ],
          "reactions": [
              "但人數太少惹qwq,之後繼續加油!",
              "人數好像有多一點點了,再多多呼朋引伴吧!",
              "耶耶!人數終於超過上限的一半惹!繼續加油!",
              "差一點點,就一點點就要到上限惹!加油加油!",
              "耶耶\\\\^~^\nQ ちゃん 超級超級~ 滿意今天的參與度呦 ><"
          ]
      }
  },
  "main": {
      "mibu": {
          "pt_1": "> 肯定是做了什麼好事"
      }
  },
  "quiz": {
      "repeat_answer": [
          "哼,還想偷偷傳新的答案呀",
          "都被我發現惹 >^<"
      ],
      "get_answer": [
          "耶耶!我已經吃到你好吃的答案惹><",
          "記得下星期要再餵我呦!"
      ],
      "invalid_syntax": {
          "pt_1": [
              "齁呦我看不懂你傳惹什麼啦><",
              "我來教教你\\\\^~^"
          ],
          "pt_2": [
              "> 不然你的答案會被其他人看光光呦><",
              "都了解了嗎?",
              "那再傳一次吧!><"
          ]
      },
      "answer_tut": [
          "> 如果正確答案依題序為 `abe`",
          "> 請在懸賞區中輸入 `||abe||`"
      ],
      "start": {
          "pt_1": [
              ":loudspeaker: @everyone",
              "有個新的懸賞(餵食)活動開始了呦!",
              "為了避免你傳的訊息被其他人看光光 *~*",
              "請記得使用 `||<答案>||` 的格式傳送訊息 \\\\^~^!"
          ],
          "pt_2": [
              "如果我不小心吃掉惹不該吃的訊息",
              "請立刻 @總召 或是直接私訊他們!",
              "大家都要把我餵飽呦 >< 祝好運!",
              "> 還在忙的總召可能等一下才會把問題傳上來 ><",
              "> 不過或許是他們有點怠惰也說不定(?"
          ]
      },
      "end": {
          "main": {
              "pt_1": [
                  ":loudspeaker: @everyone",
                  "懸賞(餵食)活動結束惹!"
              ],
              "pt_2": [
                  "不知道大家都有沒有答對呢?><",
                  "對了對了"
              ]
          },
          "reactions": [
              "太少了啦,連填牙縫都不夠惹qwq\n大家再加油吧!",
              "零食好吃!不過我想要吃多一點 ><",
              "食物好像有多一點點了!但好像只有宵夜分量呢 >^<",
              "耶耶!開胃菜正在朝著我走來!",
              "我看到副菜正在不遠處!",
              "終於吃到主餐了 ><",
              "耶耶 這片生菜感覺很好玩的樣子(x",
              "終於有甜點可以吃惹!Yummy yummy >^<",
              "大家終於把我餵飽了,感謝感謝 ><"
          ]
      }
  }
}

humanity_extension.json

@commands.Cog.listener()
async def on_member_join(self, member):
  if member.bot:
    return

  time_status = await func.get_time_title(func.now_time_info('hour'))

  msg = '\n'.join(rsp["join"]["opening"][time_status]) + '\n'
  msg += '\n'.join(rsp["join"]["opening"]["main"])
  await member.send(msg)
  await asyncio.sleep(60)

  msg = '\n'.join(rsp["join"]["hackmd_read"])
  await member.send(msg)

  def check(message):
    return message.channel == member.dm_channel and message.author == member

  try:
    deep_freeze_status = (await self.bot.wait_for('message', check=check, timeout=60.0)).content

    if deep_freeze_status == 'y':
      msg = '\n'.join(rsp["join"]["df_1"])
      deep_freeze_status = 1
      elif deep_freeze_status == 'n':
        msg = '\n'.join(rsp["join"]["df_0"])
        deep_freeze_status = 0
        else:
          msg = '\n'.join(rsp["join"]["invalid_syntax"])
          deep_freeze_status = 0
          except asyncio.TimeoutError:
            msg = '\n'.join(rsp["join"]["time_out"])
            deep_freeze_status = 0

            # another \n for last un-inserted \n
            msg += '\n' + '\n'.join(rsp["join"]["contact_method"])

            await member.send(msg)

            # create personal fluctlight data
            start_time = time.time()

            fluctlight_client = MongoClient(link)["LightCube"]
            fluctlight_cursor = fluctlight_client["light-cube-info"]

            member_fluctlight = {"_id": member.id,
                                 "score": 0,
                                 "du": 0,
                                 "oc_auth": 0,
                                 "sc_auth": 0,
                                 "lvl_ind": 0,
                                 "mdu": 0,
                                 "odu": 0,
                                 "odu_time": time.time(),
                                 "contrib": 0,
                                 "week_active": 0,
                                 "deep_freeze": deep_freeze_status}

            try:
              fluctlight_cursor.insert_one(member_fluctlight)
              except:
                fluctlight_cursor.delete_one({"_id": member.id})
                fluctlight_cursor.insert_one(member_fluctlight)

                end_time = time.time()

                msg = '\n'.join(rsp["join"]["fl_create_finish"])
                await member.send(msg)
                await member.send(f'順帶一提,我用了 {round(end_time - start_time, 2)} (sec) 建立你的檔案><!')

整合於此或其他地區

{
  "Cmd_channel": "743677861000380527",
  "pic": [
    "https://i.imgur.com/uomJXkr.jpg",
    "https://i.imgur.com/1XwWvXC.png",
    "https://i.imgur.com/26skltl.png"
  ],
  "department_id": [
    "743512536695177327",
    "745600386030764084",
    "743512574938710078",
    "743670948384866357",
    "760747391484952577",
    "760746923208212530"
  ]
}

Or something like this

CHAPTER 5.2

Database

\(\downarrow\)

類型

  • 階層式資料庫

  • 網狀式資料庫

  • 關聯式資料庫

  • 物件導向式資料庫

非關聯式資料庫(NoSQL(Not Only SQL))

混用關聯式資料庫和NoSQL資料庫來達成最佳的儲存效果,通常是透過簡單的API來新增、更新或刪除資料庫中的內容

requirements.txt

リンクスタート!

物件簡介

  • 不同於SQL,沒有row的概念
  • _id:每個物件必定會有的column
    如果不對其進行賦值,將會是5位元的隨機值。
data = {"_id": 5, "name": "星期五晚上固定講座", "status": 0, "population": 12}

據說對_id進行賦值是毒瘤寫法

Data types

  • Min key (internal type)
  • Null
  • Numbers (32-bit integer, 64-bit integer, double)
  • Symbol, String
  • Object
  • Array
  • Binary data
  • Object ID
  • Boolean
  • Date, timestamp
  • Regular expression
  • Max key (internal type)

Api 語法

import pymongo
from pymongo import MongoClient
link = <your link>
client = MongoClient(link)[<database name>: str]
cursor = client[<collection name>: str]

cursor 就是對單一 collection 進行操作的物件

常用的cursor語法

# 尋找單一物件
# 回傳單一物件
cursor.find_one({<條件>}, {<要回傳的column>: 1})

# 尋找符合條件的所有物件
# 回傳一個包含所有符合條件物件的cursor物件,可使用list()將其打回2維陣列
cursor.find_one({<條件>}, {<要回傳的column>: 1})

# 對單一物件的值進行修改
cursor.update_one({<條件>}, {<改變方式>: {<改變的物件>: <改變值>}})

# 對符合條件的複數物件修改數值
cursor.update_many({<條件>}, {<改變方式>: {<改變的物件>: <改變值>}})

# 對於 one 和 many 都通用的
cursor.update({<條件>}, {<改變方式>: {<改變的物件>: <改變值>}})

# 刪除符合條件的單一物件
cursor.delete_one({<條件>})

# 刪除符合條件的所有物件
cursor.delete_many({<條件>})

CHAPTER 6

Practical use and how to improve.

\(\downarrow\)

石礫

\(\uparrow\) Click them ! \(\uparrow\)

小小實作時間

想要升級?

  • 多實做

  • 多查google

  • 多看影片

  • 多學python

  • 多問

Copy of Copy of 寒訓DC BOT教學

By Richard Lai

Copy of Copy of 寒訓DC BOT教學

  • 68