generated from InValidFire/discord-bot-template
	Initial commit
This commit is contained in:
		
						commit
						10532c0c27
					
				
					 10 changed files with 320 additions and 0 deletions
				
			
		
							
								
								
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | MIT License Copyright (c) <year> <copyright holders> | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is furnished | ||||||
|  | to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice (including the next | ||||||
|  | paragraph) shall be included in all copies or substantial portions of the | ||||||
|  | Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||||
|  | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | ||||||
|  | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||||||
|  | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF | ||||||
|  | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||||
							
								
								
									
										3
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | # Discord Bot Template | ||||||
|  | 
 | ||||||
|  | This template is used by my Discord Bots to speed up the process of getting started. | ||||||
							
								
								
									
										0
									
								
								applog/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								applog/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										40
									
								
								applog/logging.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								applog/logging.yaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | version: 1 | ||||||
|  | disable_existing_loggers: True | ||||||
|  | formatters: | ||||||
|  |     simple: | ||||||
|  |         format: "%(asctime)s - [%(threadName)s|%(levelname)s] - %(message)s" | ||||||
|  | handlers: | ||||||
|  |     console: | ||||||
|  |         class: logging.StreamHandler | ||||||
|  |         level: DEBUG | ||||||
|  |         formatter: simple | ||||||
|  |         stream: ext://sys.stdout | ||||||
|  |     info_file_handler: | ||||||
|  |         class: logging.handlers.RotatingFileHandler | ||||||
|  |         level: INFO | ||||||
|  |         formatter: simple | ||||||
|  |         filename: applog/info.log | ||||||
|  |         maxBytes: 10485760 # 10MB | ||||||
|  |         backupCount: 20 | ||||||
|  |         encoding: utf8 | ||||||
|  |     error_file_handler: | ||||||
|  |         class: logging.handlers.RotatingFileHandler | ||||||
|  |         level: ERROR | ||||||
|  |         formatter: simple | ||||||
|  |         filename: applog/errors.log | ||||||
|  |         maxBytes: 10485760 # 10MB | ||||||
|  |         backupCount: 20 | ||||||
|  |         encoding: utf8 | ||||||
|  | loggers: | ||||||
|  |     system: | ||||||
|  |         level: INFO | ||||||
|  |         handlers: [ console, info_file_handler ] | ||||||
|  |         propogate: no | ||||||
|  |     discord: | ||||||
|  |         level: INFO | ||||||
|  |         handlers: [ console, info_file_handler ] | ||||||
|  |         propogate: no | ||||||
|  |     bot: | ||||||
|  |         level: INFO | ||||||
|  |         handlers: [ console, info_file_handler ] | ||||||
|  |         propogate: no | ||||||
							
								
								
									
										27
									
								
								applog/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								applog/utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | """Original Credit: https://github.com/tiangolo/fastapi/issues/290#issuecomment-500119238""" | ||||||
|  | 
 | ||||||
|  | import logging.config | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | import yaml | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def read_logging_config(default_path="logging.yaml", env_key="LOG_CFG"): | ||||||
|  |     """Load the logging config into memory""" | ||||||
|  |     path = default_path | ||||||
|  |     value = os.getenv(env_key, None) | ||||||
|  |     if value: | ||||||
|  |         path = value | ||||||
|  |     if os.path.exists(path): | ||||||
|  |         with open(path, "rt") as f: | ||||||
|  |             logging_config = yaml.safe_load(f.read()) | ||||||
|  |         return logging_config | ||||||
|  |     return None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def setup_logging(logging_config, default_level=logging.INFO): | ||||||
|  |     """Configure logging""" | ||||||
|  |     if logging_config: | ||||||
|  |         logging.config.dictConfig(logging_config) | ||||||
|  |     else: | ||||||
|  |         logging.basicConfig(level=default_level) | ||||||
							
								
								
									
										37
									
								
								bot.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								bot.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | """Bot startup code""" | ||||||
|  | import logging | ||||||
|  | from pathlib import Path | ||||||
|  | 
 | ||||||
|  | from discord.ext import commands | ||||||
|  | 
 | ||||||
|  | from applog.utils import read_logging_config, setup_logging | ||||||
|  | import core.common as common | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | discord_logger = logging.getLogger('discord') | ||||||
|  | 
 | ||||||
|  | log_config_dict = read_logging_config("applog/logging.yaml") | ||||||
|  | setup_logging(log_config_dict) | ||||||
|  | 
 | ||||||
|  | common.prompt_config("config.json", "Enter repository URL: ", "repo_link") | ||||||
|  | common.prompt_config("config.json", "Enter bot token: ", "token") | ||||||
|  | common.prompt_config("config.json", "Enter bot prefix: ", "prefix") | ||||||
|  | 
 | ||||||
|  | config, _ = common.load_config("config.json") | ||||||
|  | 
 | ||||||
|  | bot = commands.Bot(command_prefix=config['prefix']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_extensions(): | ||||||
|  |     """Gets extension list dynamically""" | ||||||
|  |     extensions = [] | ||||||
|  |     for file in Path("cogs").glob("**/*.py"): | ||||||
|  |         extensions.append(str(file).replace("/", ".").replace(".py", "")) | ||||||
|  |     return extensions | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | for ext in get_extensions(): | ||||||
|  |     bot.load_extension(ext) | ||||||
|  | 
 | ||||||
|  | logger.info("starting bot.") | ||||||
|  | bot.run(config['token']) | ||||||
							
								
								
									
										108
									
								
								cogs/system.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								cogs/system.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | ||||||
|  | """System Module from Bot Template""" | ||||||
|  | import sys | ||||||
|  | import subprocess | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from discord.ext import commands | ||||||
|  | import discord | ||||||
|  | 
 | ||||||
|  | import core.common as common | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class System(commands.Cog): | ||||||
|  |     """System Cog from Bot Template""" | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |         self.commit = subprocess.run(["git", "show", "-s", "--format=%h"], | ||||||
|  |                                      capture_output=True, | ||||||
|  |                                      encoding="utf-8", check=True).stdout.strip() | ||||||
|  | 
 | ||||||
|  |     @commands.group() | ||||||
|  |     async def bot_group(self, ctx): | ||||||
|  |         """Command group for core bot commands""" | ||||||
|  |         if ctx.invoked_subcommand is None: | ||||||
|  |             await ctx.send('No subcommand invoked.') | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     async def ping(self, ctx): | ||||||
|  |         """Ping the bot""" | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         embed = discord.Embed(title="Pong!", | ||||||
|  |                               description=str(round(self.bot.latency * 1000, 1)) + "ms", | ||||||
|  |                               colour=common.random_rgb()) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", | ||||||
|  |                          icon_url=ctx.author.avatar_url) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     @commands.is_owner() | ||||||
|  |     async def stop(self, ctx): | ||||||
|  |         """Stop the bot""" | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         embed = discord.Embed(title="Stopping Bot!", | ||||||
|  |                               color=common.random_rgb()) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", | ||||||
|  |                          icon_url=ctx.author.avatar_url) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  |         sys.exit() | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     @commands.is_owner() | ||||||
|  |     async def restart(self, ctx): | ||||||
|  |         """Restart the bot""" | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         embed = discord.Embed(title="Restarting Bot!", | ||||||
|  |                               color=common.random_rgb()) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", | ||||||
|  |                          icon_url=ctx.author.avatar_url) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  |         sys.exit(26) | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     async def version(self, ctx): | ||||||
|  |         """Get bot version""" | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         commit_date = subprocess.run(["git", "show", "-s", "--format=%ci", self.commit], | ||||||
|  |                                      capture_output=True, check=True, | ||||||
|  |                                      encoding="utf-8").stdout.strip() | ||||||
|  |         commit_msg = subprocess.run(['git', 'show', '-s', '--format=%B', self.commit], | ||||||
|  |                                     capture_output=True, check=True, | ||||||
|  |                                     encoding="utf-8").stdout.strip() | ||||||
|  |         embed = discord.Embed(title="Bot Version", | ||||||
|  |                               description="Current Bot Version", | ||||||
|  |                               color=common.random_rgb(self.commit)) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", icon_url=ctx.author.avatar_url) | ||||||
|  |         embed.add_field(name="ID", value=self.commit, inline=False) | ||||||
|  |         embed.add_field(name="Date", value=commit_date, inline=False) | ||||||
|  |         embed.add_field(name="Changelog", value=commit_msg, inline=False) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     async def repo(self, ctx): | ||||||
|  |         """Display the bot repository""" | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         embed = discord.Embed(title="Code Repository", | ||||||
|  |                               description="You can find my source code on [GitDab](" | ||||||
|  |                                           f"{common.load_config('config.json')[0]['repo_link']}).", | ||||||
|  |                               color=common.random_rgb()) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", icon_url=ctx.author.avatar_url) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  | 
 | ||||||
|  |     @bot_group.command() | ||||||
|  |     @commands.is_owner() | ||||||
|  |     async def update(self, ctx): | ||||||
|  |         """Update the bot""" | ||||||
|  |         ctx: commands.Context | ||||||
|  |         await ctx.message.delete() | ||||||
|  |         response = subprocess.run(["git", "pull"], capture_output=True, check=True, | ||||||
|  |                                   encoding="utf-8").stdout.strip() | ||||||
|  |         embed=discord.Embed(title="Update Report", description=response, color=common.random_rgb()) | ||||||
|  |         embed.set_footer(text=f"requested by {ctx.author}", icon_url=ctx.author.avatar_url) | ||||||
|  |         await ctx.send(embed=embed) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def setup(bot): | ||||||
|  |     """Initialize the cog""" | ||||||
|  |     bot.add_cog(System(bot)) | ||||||
							
								
								
									
										0
									
								
								core/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								core/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										49
									
								
								core/common.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								core/common.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | import json | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Tuple | ||||||
|  | import logging | ||||||
|  | import discord | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def load_config(conf_file) -> Tuple[dict, Path]: | ||||||
|  |     """Load data from a config file.\n | ||||||
|  |     Returns a tuple containing the data as a dict, and the file as a Path""" | ||||||
|  |     config_file = Path(conf_file) | ||||||
|  |     config_file.touch(exist_ok=True) | ||||||
|  |     if config_file.read_text() == "": | ||||||
|  |         config_file.write_text("{}") | ||||||
|  |         logger.debug("config file created.") | ||||||
|  |     with config_file.open("r") as f: | ||||||
|  |         config = json.load(f) | ||||||
|  |         logger.debug("config file loaded.") | ||||||
|  |     return config, config_file | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def prompt_config(conf_file, msg, key): | ||||||
|  |     """Ensure a value exists in the config file, if it doesn't prompt the bot owner to input via the console.""" | ||||||
|  |     logger.debug(f"checking if {key} is in config.") | ||||||
|  |     config, config_file = load_config(conf_file) | ||||||
|  |     if key not in config: | ||||||
|  |         logger.debug(f"{key} not found in config file.") | ||||||
|  |         config[key] = input(msg) | ||||||
|  |         with config_file.open("w+") as f: | ||||||
|  |             json.dump(config, f, indent=4) | ||||||
|  |             logger.debug(f"'{config[key]}' saved to config file under '{key}'.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def update_config(conf_file, key, value): | ||||||
|  |     logger.debug(f"updating config file '{conf_file}' key '{key}' to '{value}'") | ||||||
|  |     config, config_file = load_config(conf_file) | ||||||
|  |     config[key] = value | ||||||
|  |     with config_file.open("w+") as f: | ||||||
|  |         json.dump(config, f, indent=4) | ||||||
|  |         logger.debug(f"config file '{conf_file}' key '{key}' has been updated to '{value}'") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def random_rgb(seed=None): | ||||||
|  |     if seed is not None: | ||||||
|  |         random.seed(seed) | ||||||
|  |     return discord.Colour.from_rgb(random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255)) | ||||||
							
								
								
									
										37
									
								
								main.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								main.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | #!/usr/bin/env/python | ||||||
|  | """Main process for Bot program""" | ||||||
|  | import subprocess | ||||||
|  | import logging | ||||||
|  | import sys | ||||||
|  | import time | ||||||
|  | import psutil | ||||||
|  | 
 | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  | logger.setLevel(logging.DEBUG) | ||||||
|  | logger_handler = logging.StreamHandler() | ||||||
|  | logger_handler.setFormatter(logging.Formatter('[%(levelname)s] (%(name)s) - %(message)s')) | ||||||
|  | logger.addHandler(logger_handler) | ||||||
|  | 
 | ||||||
|  | logger.info("Bot Manager Started!") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def start_bot(): | ||||||
|  |     """Start the bot process""" | ||||||
|  |     bot_process = subprocess.Popen(["python3", "-B", "bot.py"], stdout=sys.stdout) | ||||||
|  |     return bot_process | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | bot = start_bot() | ||||||
|  | try: | ||||||
|  |     while True: | ||||||
|  |         if bot.poll() is not None: | ||||||
|  |             if bot.returncode == 26: | ||||||
|  |                 logger.info("exit code 26 received, restarting bot!") | ||||||
|  |                 bot = start_bot() | ||||||
|  |             else: | ||||||
|  |                 break | ||||||
|  |         time.sleep(1)  # keeps code from overworking. | ||||||
|  | except KeyboardInterrupt: | ||||||
|  |     print("Killing Bot Process") | ||||||
|  |     psutil.Process(bot.pid).kill() | ||||||
|  |     print("Killed successfully") | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue