├── .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 | [![ForTheBadge built-with-love](http://ForTheBadge.com/images/badges/built-with-love.svg)](https://GitHub.com/Naereen/) 2 | [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) 3 | 4 | [![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-367/) 5 | [![Open Source Love svg1](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](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 | --------------------------------------------------------------------------------