Initial commit

This commit is contained in:
Nikhil Aryal 2023-03-24 03:35:19 +00:00
commit 4267bcea77
10 changed files with 320 additions and 0 deletions

19
LICENSE Normal file
View 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
View 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
View file

40
applog/logging.yaml Normal file
View 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
View 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
View 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
View 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
View file

49
core/common.py Normal file
View 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
View 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")