From c9848adde6213871435baf9db73811aa82539e4d Mon Sep 17 00:00:00 2001 From: DasMoorhuhn Date: Sat, 17 Feb 2024 22:35:14 +0100 Subject: [PATCH] Refactor bot and use cogs --- Dockerfile | 4 +- create_invite_link.py | 14 +++ docker-compose.yml | 6 +- src/__init__.py | 0 src/addons.py | 18 ++++ src/bot.py | 54 ++++++++++++ src/client.py | 22 +++++ src/cogs/__init__.py | 0 src/cogs/excuse_commands.py | 31 +++++++ src/cogs/help_commands.py | 59 +++++++++++++ src/cogs/xkcd_commands.py | 46 ++++++++++ src/main.py | 169 ------------------------------------ 12 files changed, 249 insertions(+), 174 deletions(-) create mode 100644 create_invite_link.py create mode 100644 src/__init__.py create mode 100644 src/bot.py create mode 100644 src/client.py create mode 100644 src/cogs/__init__.py create mode 100644 src/cogs/excuse_commands.py create mode 100644 src/cogs/help_commands.py create mode 100644 src/cogs/xkcd_commands.py delete mode 100644 src/main.py diff --git a/Dockerfile b/Dockerfile index 2570500..3d78d42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.12-alpine -WORKDIR /app +WORKDIR /src COPY requirements.txt requirements.txt RUN /usr/local/bin/python -m pip install --upgrade pip @@ -12,4 +12,4 @@ COPY ./src/ . RUN mkdir -p ./data/log RUN mkdir -p ./data/config -CMD [ "python3.12", "main.py" ] +CMD [ "python3.12", "bot.py" ] diff --git a/create_invite_link.py b/create_invite_link.py new file mode 100644 index 0000000..deef132 --- /dev/null +++ b/create_invite_link.py @@ -0,0 +1,14 @@ +import sys + +permissions = "274877966400" +scope = "bot+applications.commands" +# client_id = 961747147135524874 + +try: client_id = sys.argv[1] +except: + print("Put the client id from the discord bot behind this command") + exit(1) + +url = f"https://discord.com/api/oauth2/authorize?client_id={client_id}&permissions={permissions}&scope={scope}" + +print(url) diff --git a/docker-compose.yml b/docker-compose.yml index e60cb5e..66cf78c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,6 @@ services: restart: unless-stopped build: . volumes: - - ./data/log:/app/data/log - - ./data/config:/app/data/config - - ./secret.json:/app/secret.json \ No newline at end of file + - ./data/log:/src/data/log + - ./data/config:/src/data/config + - ./secret.json:/src/secret.json \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/addons.py b/src/addons.py index d052910..a179a22 100644 --- a/src/addons.py +++ b/src/addons.py @@ -1,9 +1,27 @@ import requests import json +import datetime +import time from models.xkcdComic import Comic +class DateTimeHelper: + @staticmethod + def get_time_now(): + date_now = datetime.datetime.now() + return str(date_now).split(" ")[0] + + @staticmethod + def get_unix_time(): + return time.time() + + @staticmethod + def get_date(): + date_now = datetime.datetime.now() + return str(date_now).split(" ") + + class ProgrammerExcuses: def __init__(self) -> None: self.url = "http://programmingexcuses.com" diff --git a/src/bot.py b/src/bot.py new file mode 100644 index 0000000..7773fd6 --- /dev/null +++ b/src/bot.py @@ -0,0 +1,54 @@ +import logging +import asyncio +import os +import secret_handler + +import discord +from discord import app_commands +from discord import Interaction +from discord.ext import commands +import addons +from client import Client + +bot_info = {'version': '1.1.0', 'date': '20.02.2024'} + +logger = logging.getLogger('discord') +logger.setLevel(logging.DEBUG) + +logging.getLogger('discord.http').setLevel(logging.INFO) + +handler = logging.FileHandler(filename='data/log/discord.log', encoding='utf-8', mode='a') +# dt_fmt = '%Y-%m-%d %H:%M:%S' +# formatter = logging.Formatter('[{asctime}] [{levelname:<8}] {name}: {message}', dt_fmt, style='{') +formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(name)s|:%(message)s') + +handler.setFormatter(formatter) +logger.addHandler(handler) + +bot = Client(intents=discord.Intents.all(), command_prefix="!", log=logger, bot_info=bot_info) + + +async def load_cogs(): + for filename in os.listdir("./cogs"): + if filename.endswith("py") and filename != "__init__.py": + await bot.load_extension(f"cogs.{filename[:-3]}") + + +@bot.event +async def on_ready(): + logger.info(f"Logged in as: {bot.user.name} with ID {bot.user.id}") + await load_cogs() + await asyncio.sleep(1) + synced = await bot.tree.sync() + logger.info(f"Synced {len(synced)} command(s)") + + +@bot.event +async def on_app_command_error(interaction: Interaction, error): + if isinstance(error, app_commands.MissingPermissions): + await interaction.response.send_message(content="Du hast keine Adminrechte", ephemeral=True) + else: + raise error + + +bot.run(secret_handler.get_bot_token()) diff --git a/src/client.py b/src/client.py new file mode 100644 index 0000000..d1dee31 --- /dev/null +++ b/src/client.py @@ -0,0 +1,22 @@ +from discord import Intents +from discord.ext import commands +import addons + + +class Client(commands.Bot): + """Custom bot class""" + def __init__(self, command_prefix, *, intents: Intents, log, bot_info): + super().__init__(command_prefix, intents=intents) + self.log = log + self.addons = addons + self.start_time:float = self.addons.DateTimeHelper.get_unix_time() + self.version:str = bot_info['version'] + self.date:str = bot_info['date'] + + async def startup(self): + await self.wait_until_ready() + + def get_uptime(self): + """Returns the uptime in seconds""" + time_now = self.addons.DateTimeHelper.get_unix_time() + return int(round(time_now - self.start_time, 0)) diff --git a/src/cogs/__init__.py b/src/cogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cogs/excuse_commands.py b/src/cogs/excuse_commands.py new file mode 100644 index 0000000..ec9cf75 --- /dev/null +++ b/src/cogs/excuse_commands.py @@ -0,0 +1,31 @@ +import discord +from discord import app_commands +from discord import Interaction +from discord.ext import commands +from discord.ext.commands import Cog + +import sys +sys.path.append('..') + +from src.client import Client as Bot + + +class ProgrammingExcuses(Cog): + def __init__(self, bot:Bot): + self.bot = bot + self.log = bot.log + self.programmer_excuses = bot.addons.ProgrammerExcuses() + + @commands.Cog.listener() + async def on_ready(self): + self.log.info("Done: excuse") + + @app_commands.command(name="excuse", description="Excuse") + async def excuse(self, interaction: Interaction): + self.log.info("Command: excuse") + await interaction.response.send_message(self.programmer_excuses.get_excuse()) + + +async def setup(bot:Bot): + await bot.add_cog(ProgrammingExcuses(bot)) + bot.log.info(f"Loaded excuses") diff --git a/src/cogs/help_commands.py b/src/cogs/help_commands.py new file mode 100644 index 0000000..37ceff3 --- /dev/null +++ b/src/cogs/help_commands.py @@ -0,0 +1,59 @@ +import discord +from discord import app_commands +from discord import Interaction +from discord import Embed +from discord import Color +from discord.ext import commands +from discord.ext.commands import Cog + +import sys +sys.path.append('..') + +from src.client import Client as Bot + + +class HelpCommands(Cog): + def __init__(self, bot:Bot): + self.bot = bot + self.log = bot.log + self.programmer_excuses = bot.addons.ProgrammerExcuses() + self.date_time_helper = bot.addons.DateTimeHelper + + @commands.Cog.listener() + async def on_ready(self): + self.log.info("Done: help, info") + + @app_commands.command(name="help", description="List of all Commands") + async def help(self, interaction: Interaction): + self.log.info("Command: help") + + command_list_string = "" + command_list_string += "`/info` : Get infos about the server and the Bot\n" + command_list_string += "`/help` : Get this view\n" + command_list_string += "`/get-random-comic`: Get a randowm XCCD comic\n" + command_list_string += "`/get-latest-comic`: Get latest comic from XKCD\n" + command_list_string += "`/excuse` : Get a random excuse from programmingexcuses" + + embed = Embed(title=f"Help", description="List of commands", color=Color.blue()) + embed.add_field(name="Commands", value=command_list_string, inline=True) + await interaction.response.send_message(embed=embed) + + @app_commands.command(name="info", description="Get info about this bot") + async def info(self, interaction: Interaction): + self.log.info("Command: info") + + bot_string = "" + bot_string += f"Uptime : {self.bot.get_uptime()}s\n" + bot_string += f"Version : {self.bot.version} from {self.bot.date}\n" + bot_string += f"Developer : dasmoorhuhn\n" + bot_string += f"Sourcecode: https://gitlab.com/DasMoorhuhn/tux-discord-bot" + + embed = Embed(title=f"Info", description="about this Bot", + color=Color.blue()) + embed.add_field(name="Bot", value=bot_string, inline=False) + await interaction.response.send_message(embed=embed) + + +async def setup(bot:Bot): + await bot.add_cog(HelpCommands(bot)) + bot.log.info(f"Loaded help") diff --git a/src/cogs/xkcd_commands.py b/src/cogs/xkcd_commands.py new file mode 100644 index 0000000..85ae177 --- /dev/null +++ b/src/cogs/xkcd_commands.py @@ -0,0 +1,46 @@ +import discord +from discord import app_commands +from discord import Interaction +from discord import Embed +from discord import Color +from discord.ext import commands +from discord.ext.commands import Cog + +import sys +sys.path.append('..') + +from src.client import Client as Bot + + +class XkcdCommands(Cog): + def __init__(self, bot: Bot): + self.bot = bot + self.log = bot.log + self.xkcd = bot.addons.XKCD() + + @commands.Cog.listener() + async def on_ready(self): + self.log.info("Done: get-latest-comic, get-random-comic") + + @app_commands.command(name="get-latest-comic", description="test slash command") + async def get_latest_comic(self, interaction: Interaction): + self.log.info("Command: get-latest-comic") + comic = self.xkcd.get_last_comic() + + embed = Embed(title=comic.title, color=Color.blue(), url=f"{self.xkcd.url}/{comic.num}") + embed.set_image(url=comic.img) + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='get-random-comic', description='Get a random comic from XKCD') + async def get_random_comic(self, interaction: Interaction): + self.log.info("Command: get-random-comic") + comic = self.xkcd.get_random_comic() + + embed = Embed(title=comic.title, color=Color.blue(), url=f"{self.xkcd.url}/{comic.num}") + embed.set_image(url=comic.img) + await interaction.response.send_message(embed=embed) + + +async def setup(bot): + await bot.add_cog(XkcdCommands(bot)) + bot.log.info(f"Loaded XKCD") diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 9440e3d..0000000 --- a/src/main.py +++ /dev/null @@ -1,169 +0,0 @@ -import time as t -import asyncio -import logging -import datetime -import secret_handler -from addons import ProgrammerExcuses -from addons import XKCD - -from models.xkcdComic import Comic - -import discord -from discord import Status -from discord import app_commands -from discord import Interaction -from discord import Embed -from discord import Guild -from discord import Color -from discord.activity import Game - -botVersion = "1.0.24" -botDate = "16.02.2024" - -secret = secret_handler.get_bot_token() - -# Init Logger -logger = logging.getLogger('discord') -logger.setLevel(logging.DEBUG) -handler = logging.FileHandler(filename='data/log/discord.log', encoding='utf-8', mode='a') -handler.setFormatter(logging.Formatter('%(asctime)s|%(levelname)s|%(name)s|:%(message)s')) -logger.addHandler(handler) - -# Init Addons -programmer_excuses = ProgrammerExcuses() -xkcd = XKCD() - - -class Client(discord.Client): - async def startup(self): - await self.wait_until_ready() - - -def get_date_time(): - date_now = datetime.datetime.now() - date_now = str(date_now).split(" ") - unix = t.time() - time = date_now[1] - time = time.split(".") - time = time[0] - date_now = date_now[0] - return date_now, time, unix - - -start_time = get_date_time()[2] - -bot = Client(intents=discord.Intents.all()) -tree = app_commands.CommandTree(client=bot) - - -async def change_presence(interval_in_seconds=120): - while True: - await bot.change_presence(activity=Game(name="with penguins"), status=Status.online) - await asyncio.sleep(interval_in_seconds) - - count_guilds = 0 - async for guild in bot.fetch_guilds(): - count_guilds += 1 - - await bot.change_presence(activity=Game(name=f"on {count_guilds} Servers"), status=Status.online) - await asyncio.sleep(interval_in_seconds) - - -@bot.event -async def on_ready(): - start_time = get_date_time()[2] - logger.info(f"Logged in as: {bot.user.name} with ID {bot.user.id}") - await bot.loop.create_task(change_presence()) - await bot.change_presence(activity=Game(name="with penguins"), status=Status.online) - await tree.sync() - - -@bot.event -async def on_guild_join(guild: Guild): - logger.info("Added to Guild") - await guild.system_channel.send("Hii^^") - - -@tree.command(name='excuse', description='Get a random excuse from programmingexcuses') -async def slash(interaction: Interaction): - logger.info("Command: excuse") - await interaction.response.send_message(programmer_excuses.get_excuse()) - - -@tree.command(name='get-latest-comic', description='Get latest comic from XKCD') -async def slash(interaction: Interaction): - logger.info("Command: get-latest-comic") - comic = xkcd.get_last_comic() - - embed = discord.Embed(title=comic.title, color=Color.blue(), url=f"{xkcd.url}/{comic.num}") - embed.set_image(url=comic.img) - await interaction.response.send_message(embed=embed) - - -@tree.command(name='get-random-comic', description='Get a random comic from XKCD') -async def slash(interaction: Interaction): - logger.info("Command: get-random-comic") - comic = xkcd.get_random_comic() - - embed = discord.Embed(title=comic.title, color=Color.blue(), url=f"{xkcd.url}/{comic.num}") - embed.set_image(url=comic.img) - await interaction.response.send_message(embed=embed) - - -# @tree.command(name='programmer-humor', description='Get a random Post from r/ProgrammerHumor') -# async def slash(interaction: Interaction): -# post = redditProgrammerHumor.getRandomPost() -# await interaction.response.send_message("hi") - - -@tree.command(name="info", description="get info about this bot") -async def slash(interaction: Interaction): - logger.info("Command: info") - time_now = get_date_time()[2] - uptime = time_now - start_time - - bot_string = "" - bot_string += f"Uptime : {int(round(uptime, 0))}s\n" - bot_string += f"Version : {botVersion} from {botDate}\n" - bot_string += f"Developer : dasmoorhuhn\n" - bot_string += f"Sourcecode: https://gitlab.com/DasMoorhuhn/tux-discord-bot" - - embed = discord.Embed(title=f"Info", description="about this Bot", timestamp=datetime.datetime.utcnow(), - color=Color.blue()) - # embed.set_thumbnail(url=interaction.guild.icon) - embed.add_field(name="Bot", value=bot_string, inline=False) - await interaction.response.send_message(embed=embed) - - -@tree.command(name="help", description="List of all Commands") -async def slash(interaction: Interaction): - logger.info("Command: help") - command_list_string = "" - command_list_string += "`/info` : Get infos about the server and the Bot\n" - command_list_string += "`/help` : Get this view\n" - command_list_string += "`/get-random-comic`: Get a randowm XCCD comic\n" - command_list_string += "`/get-latest-comic`: Get latest comic from XKCD\n" - command_list_string += "`/excuse` : Get a random excuse from programmingexcuses" - - embed = discord.Embed(title=f"Help", description="List of commands", color=Color.blue()) - # embed.set_thumbnail(url=interaction.guild.icon) - embed.add_field(name="Commands", value=command_list_string, inline=True) - - await interaction.response.send_message(embed=embed) - - -@tree.error -async def on_app_command_error(interaction: Interaction, error): - if isinstance(error, app_commands.MissingPermissions): - await interaction.response.send_message(content="Du hast keine Adminrechte", ephemeral=True) - else: - raise error - - -# Prevent Gateway Heartbeat Block -try: - bot.run(token=secret_handler.get_bot_token()) -except Exception as err: - raise err -finally: - logger.info("Stopped")