├── .gitattributes
├── .gitignore
├── README.md
├── bot.py
├── cmds
├── event.py
├── main.py
├── mod.py
└── owner.py
├── core
├── check.py
├── classes.py
└── errors.py
└── example_setting.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | setting.json
3 |
4 | .vscode/
5 |
6 | __pycache__/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://GitHub.com/Naereen/)
2 | [](https://forthebadge.com)
3 |
4 | [](https://www.python.org/downloads/release/python-367/)
5 | [](https://github.com/ellerbrock/open-source-badges/)
6 |
7 |
8 | ## ⚡ Introduction 簡介
9 |
10 | ### **Discord Python Bot BackBone**
11 |
12 | **Discord Pyhon Bot 骨架**
13 |
14 | 整體架構是參考並簡化改進先前於 Discord Hack Week 19 共同開發的 [Libereus](https://github.com/Tansc161/Libereus)
15 |
16 | 旨在提供一個乾淨的基本骨架,快速的開始一隻新的機器人開發
17 |
18 | - For (初學者/開發者)
19 | - Cog 架構
20 | - Bot指令/類別/功能 分離
21 | - Error Handler 、 Logger 、 Gloable Function 、 Checker
22 |
23 |
24 |
25 | ## 📥 Installation 安裝指南
26 | > 運行環境 建議 `Python 3.6` 以上 / `discord.py 1.5` 以上
27 |
28 | 1. 下載整個專案
29 | 2. 解壓後將 `example_setting.json` 重新命名為 `setting.json` ; 自行修改設定檔裡的資料
30 | 3. 運行 `bot.py` 即可
31 |
32 |
33 |
34 | ## 🔩 Folder structure 資料夾結構
35 | ```
36 | / # 根目錄
37 | ------------------------------------
38 | - bot.py # bot 啟動主文件
39 | - example_setting.json # 設定檔
40 |
41 |
42 | /cmds # 放置所有 Cog 指令
43 | ------------------------------------
44 | - main.py #主要指令區
45 | - event.py # 所有 event 觸發性事件指令區
46 | - mod.py # 管理、控制類指令區
47 | - owner.py # 擁有者權限指令區
48 |
49 |
50 | /core #放置類別、核心通用功能
51 | ------------------------------------
52 | - classes.py # 主要類別區
53 | - check.py # 自定全域指令檢查器
54 | - error.py # 預設、自訂 錯誤管理器
55 | ```
56 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension
4 | import json
5 | import random, os, asyncio
6 |
7 | """
8 | 1.5 重大更新需加入intents 詳細請閱讀官方文件
9 | https://discordpy.readthedocs.io/en/latest/intents.html#intents-primer
10 | """
11 | # 啟用所有 intents
12 | intents = discord.Intents.all()
13 |
14 | # 讀取設定檔 load settings
15 | with open('setting.json', 'r', encoding= 'utf8') as jfile:
16 | jdata = json.load(jfile)
17 |
18 | """
19 | command_prefix: 指令前綴
20 | owner_ids: 擁有者ID
21 | """
22 | bot = commands.Bot(command_prefix= jdata['Prefix'],
23 | owner_ids= jdata['Owner_id'])
24 |
25 | # Bot完成啟動後事件
26 | @bot.event
27 | async def on_ready():
28 | print(">> Bot is online <<")
29 |
30 | # 載入cmds資料夾內所有cog
31 | for filename in os.listdir('./cmds'):
32 | if filename.endswith('.py'):
33 | bot.load_extension(f'cmds.{filename[:-3]}')
34 |
35 | if __name__ == "__main__":
36 | bot.run(jdata['TOKEN'])
37 |
38 |
--------------------------------------------------------------------------------
/cmds/event.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension, Gloable_Data
4 | from core.errors import Errors
5 | import json, datetime, asyncio
6 |
7 | with open('setting.json', 'r', encoding='utf8') as jfile:
8 | jdata = json.load(jfile)
9 |
10 | class Event(Cog_Extension):
11 |
12 | @commands.Cog.listener()
13 | async def on_command_error(self, ctx, error):
14 | '''指令錯誤觸發事件'''
15 | Gloable_Data.errors_counter += 1
16 | error_command = '{0}_error'.format(ctx.command)
17 | if hasattr(Errors, error_command): # 檢查是否有 Custom Error Handler
18 | error_cmd = getattr(Errors, error_command)
19 | await error_cmd(self, ctx, error)
20 | return
21 | else: # 使用 Default Error Handler
22 | await Errors.default_error(self, ctx, error)
23 |
24 | def setup(bot):
25 | bot.add_cog(Event(bot))
--------------------------------------------------------------------------------
/cmds/main.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension
4 | from core import check
5 | import json
6 | import os, random
7 |
8 | with open('setting.json', 'r', encoding='utf8') as jfile:
9 | jdata = json.load(jfile)
10 |
11 | class Main(Cog_Extension):
12 |
13 | '''
14 | 等待使用者回覆檢查 (需要時複製使用)
15 | async def user_respone():
16 | def check(m):
17 | return m.author == ctx.author and m.channel == ctx.channel
18 | respone = await self.bot.wait_for('message', check=check)
19 | return respone
20 |
21 | respone_msg = await user_respone
22 | '''
23 |
24 | @commands.command()
25 | async def ping(self, ctx):
26 | '''Bot 延遲'''
27 | await ctx.send(f'{round(self.bot.latency*1000)} ms')
28 |
29 |
30 | @commands.command()
31 | @check.valid_user() #檢查權限, 是否存在於效人員清單中, 否則無法使用指令
32 | async def test(self, ctx):
33 | '''有效人員 指令權限測試'''
34 | await ctx.send('Bee! Bo!')
35 |
36 |
37 | @commands.command()
38 | async def sayd(self, ctx, *, content: str):
39 | '''訊息覆誦'''
40 | if "@everyone" in content:
41 | await ctx.send(f"{ctx.author.mention} 請勿標註 `everyone` !")
42 | return
43 | else: await ctx.message.delete()
44 | await ctx.send(content)
45 |
46 |
47 | @commands.command()
48 | async def info(self, ctx):
49 | embed = discord.Embed(title="About P_Base-Bot", description="Made Bot Easier !", color=0x28ddb0)
50 | # embed.set_thumbnail(url="#")
51 | embed.add_field(name="開發者 Developers", value="Proladon#7525 (<@!149772971555160064>)", inline=False)
52 | embed.add_field(name="源碼 Source", value="[Link](https://github.com/Proladon/Proladon-DC_BaseBot)", inline=True)
53 | embed.add_field(name="協助 Support Server", value="[Link](https://discord.gg/R75DXHH)" , inline=True)
54 | embed.add_field(name="版本 Version", value="0.1.0 a", inline=False)
55 | embed.add_field(name="Powered by", value="discord.py v{}".format(discord.__version__), inline=True)
56 | embed.add_field(name="Prefix", value=jdata['Prefix'], inline=False)
57 | embed.set_footer(text="Made with ❤")
58 | await ctx.send(embed=embed)
59 |
60 |
61 | def setup(bot):
62 | bot.add_cog(Main(bot))
63 |
--------------------------------------------------------------------------------
/cmds/mod.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension, Global_Func
4 | import json
5 |
6 | with open('setting.json', 'r', encoding='utf8') as jfile:
7 | jdata = json.load(jfile)
8 |
9 | class Mod(Cog_Extension):
10 |
11 | @commands.command(aliases=['cc'])
12 | @commands.has_permissions(administrator=True)
13 | async def purge(self, ctx, num: int, reason=None):
14 | '''清理指定數量訊息'''
15 | await ctx.channel.purge(limit=num + 1)
16 |
17 | levels = {
18 | "a": "非對應頻道內容",
19 | "b": "不雅用詞"
20 | }
21 |
22 | if reason is not None:
23 | if reason in levels.keys():
24 | await ctx.send(Global_Func.code(lang='fix', msg=f'已清理 {num} 則訊息.\nReason: {levels[reason]}'))
25 | else:
26 | await ctx.send(content=Global_Func.code(lang='fix', msg=f'已清理 {num} 則訊息.\nReason: {reason}'), delete_after=5.0)
27 |
28 |
29 | def setup(bot):
30 | bot.add_cog(Mod(bot))
--------------------------------------------------------------------------------
/cmds/owner.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension
4 | import json, asyncio, os
5 |
6 | with open('setting.json', 'r', encoding='utf8') as jfile:
7 | jdata = json.load(jfile)
8 |
9 | class Owner(Cog_Extension):
10 | @commands.command()
11 | @commands.is_owner()
12 | async def load(self, ctx, extension):
13 | '''裝載 Cog'''
14 | self.bot.load_extension(f'cmds.{extension}')
15 | await ctx.send(f'Loaded {extension} done.')
16 |
17 | @commands.command()
18 | @commands.is_owner()
19 | async def unload(self, ctx, extension):
20 | '''卸載 Cog'''
21 | self.bot.unload_extension(f'cmds.{extension}')
22 | await ctx.send(f'Un - Loaded {extension} done.')
23 |
24 | @commands.command()
25 | @commands.is_owner()
26 | async def reload(self, ctx, extension):
27 | '''重新裝載 Cog'''
28 | if extension == '*':
29 | for filename in os.listdir('./cmds'):
30 | if filename.endswith('.py'):
31 | self.bot.reload_extension(f'cmds.{filename[:-3]}')
32 | await ctx.send(f'Re - Loaded All done.')
33 | else:
34 | self.bot.reload_extension(f'cmds.{extension}')
35 | await ctx.send(f'Re - Loaded {extension} done.')
36 |
37 | @commands.command()
38 | @commands.is_owner()
39 | async def shutdown(self, ctx):
40 | await ctx.send("Shutting down...")
41 | await asyncio.sleep(1)
42 | await self.bot.logout()
43 |
44 | def setup(bot):
45 | bot.add_cog(Owner(bot))
--------------------------------------------------------------------------------
/core/check.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | import json
4 |
5 | def valid_user():
6 | '''
7 | 有效人員檢查器
8 | 讀取json檔裡的 Owner_id 和 Valid_User,比對當前觸發指令的使用者是否符合以上兩者之一
9 | 回傳比對結果, 兩者皆不符合False, 符合其中一者True
10 | '''
11 | def predicate(ctx):
12 | with open('setting.json', 'r', encoding='utf8') as jfile:
13 | jdata = json.load(jfile)
14 |
15 | return ctx.message.author.id == jdata['Owner_id'] or ctx.message.author.id in jdata['Valid_User']
16 |
17 | return commands.check(predicate)
--------------------------------------------------------------------------------
/core/classes.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | import json, datetime
4 |
5 | class Cog_Extension(commands.Cog):
6 | """用於Cog繼承基本屬性"""
7 | def __init__(self, bot):
8 | self.bot = bot
9 |
10 |
11 | class Gloable_Data:
12 | """自定義全域資料"""
13 | errors_counter = 0
14 | def __init__(self, *args, **kwargs):
15 | ...
16 |
17 |
18 | class Global_Func():
19 | """自定義常用功能"""
20 |
21 | def update_jdata(self, key, data, type='default', mode='update'):
22 | '''
23 | 更新 Jdata 功能
24 | type: default / list
25 | mode: update / delete
26 | '''
27 | with open('setting.json', 'r', encoding='utf8') as jfile:
28 | jdata = json.load(jfile)
29 | if mode == 'update':
30 | if type == 'default':
31 | jdata[key] = data
32 | elif type == 'list':
33 | jdata[key].append(data)
34 | elif mode == 'delete':
35 | if type == 'list':
36 | jdata[key].remove(data)
37 |
38 | with open('setting.json', 'w', encoding='utf8') as jfile:
39 | json.dump(jdata, jfile, indent=4, ensure_ascii=False)
40 |
41 |
42 | #CodeBlock
43 | @classmethod
44 | def code(cls, lang, msg):
45 | '''CodeBlock'''
46 | return f'```{lang}\n{msg}\n```'
47 |
48 |
49 | class Logger:
50 | def log(self, ctx, data, type='error'):
51 | '''事件紀錄器'''
52 | time = datetime.datetime.now().strftime('[%Y-%m-%d] [%H:%M]')
53 | user = ctx.author.name
54 | channel = ctx.channel.name
55 | command = ctx.command
56 | if type == 'error':
57 | print(f'🔥: {time}/[{user}][{channel}][{command}]: {data}')
--------------------------------------------------------------------------------
/core/errors.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from core.classes import Cog_Extension, Logger
4 | from cmds.main import Main #導入 Main Cog
5 | import json, asyncio
6 |
7 | with open('setting.json', 'r', encoding='utf8') as jfile:
8 | jdata = json.load(jfile)
9 |
10 | class Errors():
11 | """該類別用於定義錯誤處裡邏輯"""
12 |
13 | # 自訂 Error Handler
14 | @Main.sayd.error
15 | async def sayd_error(self, ctx, error):
16 | '''Main.sayd 的指令錯誤處理'''
17 | if isinstance(error, commands.MissingRequiredArgument):
18 | err = str(error).split(" ")[0]
19 | await ctx.send(f"遺失必要參數: <`{err}`>")
20 | await ctx.send_help(ctx.command)
21 | Logger.log(self, ctx, error)
22 |
23 |
24 | # 預設 Error Handler
25 | async def default_error(self, ctx, error):
26 | '''預設錯誤處理'''
27 |
28 | # 比對觸發的error是否為 MissingRequiredArgument 的實例
29 | if isinstance(error, commands.MissingRequiredArgument):
30 | err = str(error).split(" ")[0]
31 | await ctx.send(f"遺失必要參數: <`{err}`>")
32 | await ctx.send_help(ctx.command)
33 | Logger.log(self, ctx, error)
34 |
35 | # error 內容是否為 403 Forbiddden
36 | elif "403 Forbidden" in str(error):
37 | await ctx.send("403 Forbidden,請檢查 Bot 權限")
38 | Logger.log(self, ctx, error)
39 |
40 | # 皆不符合
41 | else:
42 | await ctx.send(f'未知錯誤: {error}')
43 | Logger.log(self, ctx, error)
44 |
--------------------------------------------------------------------------------
/example_setting.json:
--------------------------------------------------------------------------------
1 | {
2 | "TOKEN": "Your Token",
3 | "Prefix": ["["],
4 | "Owner_id": [149772971555160064],
5 | "Valid_User": []
6 | }
7 |
--------------------------------------------------------------------------------