diff --git a/src/assets/fonts/impact.ttf b/src/assets/fonts/impact.ttf new file mode 100644 index 0000000..2675688 Binary files /dev/null and b/src/assets/fonts/impact.ttf differ diff --git a/src/cogs/funreplies.py b/src/cogs/funreplies.py index 8392fdc..bb37f5d 100644 --- a/src/cogs/funreplies.py +++ b/src/cogs/funreplies.py @@ -26,11 +26,10 @@ class FunReplies(commands.Cog): 2000, 9, 11 ) # Set initial datetime far in the past to allow triggering right after boot self.previous_invokations = { - "borgerlønn": initial_datetime, - "olof palme": initial_datetime, - "lesgo": initial_datetime, - "neuf": initial_datetime, - "yeet": initial_datetime, + 'borgerlønn': initial_datetime, + 'olof palme': initial_datetime, + 'lesgo': initial_datetime, + 'yeet': initial_datetime } async def reply_to_triggers(self, message: discord.Message): @@ -47,47 +46,26 @@ class FunReplies(commands.Cog): message_content = message.content.lower() - if re.search(r"(^|\W)borgerlønn(\W|$)", message_content, flags=re.IGNORECASE): - if (datetime.now() - self.previous_invokations["borgerlønn"]).seconds > self.cooldown_seconds: - await message.reply("<@267415183931080715> DE SNAKKER OM BORGERLØNN") - self.previous_invokations["borgerlønn"] = datetime.now() + if re.search(r'(^|\W)borgerlønn(\W|$)', message_content, flags=re.IGNORECASE): + if (datetime.now() - self.previous_invokations['borgerlønn']).seconds > self.cooldown_seconds: + await message.reply('<@267415183931080715> DE SNAKKER OM BORGERLØNN') + self.previous_invokations['borgerlønn'] = datetime.now() - elif re.search(r"(^|\W)olof palme(\W|$)", message_content, flags=re.IGNORECASE): - if (datetime.now() - self.previous_invokations["olof palme"]).seconds > self.cooldown_seconds: - await message.reply("Jeg vet hvem som drepte Olof Palme 👀") - self.previous_invokations["olof palme"] = datetime.now() + elif re.search(r'(^|\W)olof palme(\W|$)', message_content, flags=re.IGNORECASE): + if (datetime.now() - self.previous_invokations['olof palme']).seconds > self.cooldown_seconds: + await message.reply('Jeg vet hvem som drepte Olof Palme 👀') + self.previous_invokations['olof palme'] = datetime.now() - elif re.search(r"(\W|\s)*le+s+go+(\s|\W)*", message_content): - if (datetime.now() - self.previous_invokations["lesgo"]).seconds > self.cooldown_seconds: - await message.reply( - "https://cdn.discordapp.com/attachments/750052141346979850/" - + "824764933513281596/3xhpwbakz2361.png" - ) - self.previous_invokations["lesgo"] = datetime.now() + elif re.search(r'(\W|\s)*le+s+go+(\s|\W)*', message_content): + if (datetime.now() - self.previous_invokations['lesgo']).seconds > self.cooldown_seconds: + await message.reply('https://cdn.discordapp.com/attachments/750052141346979850/' + + '824764933513281596/3xhpwbakz2361.png') + self.previous_invokations['lesgo'] = datetime.now() - elif re.search( - r"^((er det|hvor)\s+)*(noen|folk|mange)\s+på\s+(rommet|neuf|kontoret)\?*$", - message_content, - flags=re.IGNORECASE, - ): - if (datetime.now() - self.previous_invokations["neuf"]).seconds > self.cooldown_seconds: - # Choose a random user to ping - pingable = ["<@554977854971183125>", "<@276061121776058370>"] - to_be_pinged = random.choice(pingable) - await message.reply(f"Ja, {to_be_pinged} bor der") - - self.previous_invokations["neuf"] = datetime.now() - - elif re.search(r"(^|\W)+ye+et($|\W)+", message_content, flags=re.IGNORECASE): - if (datetime.now() - self.previous_invokations["yeet"]).seconds > self.cooldown_seconds: - await message.reply("<:Nei:826593267642662912>") - self.previous_invokations["yeet"] = datetime.now() - - elif ( - re.search(r"https?://(www\.)?instagram\.com/reel/.*", message_content, flags=re.IGNORECASE) - and message.author.id == 205741213050077185 - ): - await message.reply("||Bruk yt-dlp REEEE||\nhttps://media.tenor.com/Fm0bJgHucO8AAAAd/epic-embed-fail.gif") + elif re.search(r'(^|\W)+ye+et($|\W)+', message_content, flags=re.IGNORECASE): + if (datetime.now() - self.previous_invokations['yeet']).seconds > self.cooldown_seconds: + await message.reply('<:Nei:826593267642662912>') + self.previous_invokations['yeet'] = datetime.now() async def setup(bot: commands.Bot): diff --git a/src/cogs/the.py b/src/cogs/the.py new file mode 100644 index 0000000..021de83 --- /dev/null +++ b/src/cogs/the.py @@ -0,0 +1,152 @@ +import traceback +from typing import Any +import discord +from discord import app_commands +from discord.ext import commands +from PIL import Image, ImageDraw, ImageFont, ImageSequence +import aiohttp +import io + + +class The(commands.Cog): + """ + This cog lets you make a "the" barnacle boy laser eyes meme + """ + + def __init__(self, bot: commands.Bot): + self.bot = bot + self.background_url = 'https://i.kym-cdn.com/photos/images/newsfeed/001/777/063/d42.png' + self.x = 30 + self.y = 340 + + async def fetch_image(self, url: str) -> io.BytesIO: + """ + Fetch the image from the URL + + Parameters + ---------- + url (str): url + + Returns + ---------- + (io.BytesIO): data buffer + """ + if not url: + return None + + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status != 200: + return None + data = io.BytesIO(await resp.read()) + return data + + def outline_text( + self, + draw: ImageDraw, + text: str, + font: ImageFont.FreeTypeFont, + x: int, + y: int, + thickness: int, + ) -> None: + """ + Outlines given text + """ + + for dx in range(-thickness, thickness + 1): + for dy in range(-thickness, thickness + 1): + if dx != 0 or dy != 0: + draw.text((x + dx, y + dy), text, font=font, fill="black") + draw.text((x, y), text, font=font, fill="white") + + @app_commands.command() + async def the(self, interaction: discord.Interaction, top_text: str = "", image_url: str = "", bottom_text: str = ""): + """ + Responds with a barnacle boy laser eyes THE meme with the given captions + + Parameters + ---------- + interaction (discord.Interaction): Slash command context object + caption (str): top text + image_url (str): image url + bottom_text (str): bottom text + + Returns + ---------- + (discord.File): an image + """ + await interaction.response.defer() + gif = False + try: + bg_data = await self.fetch_image(self.background_url) + background = Image.open(bg_data) + + image_data = await self.fetch_image(image_url) + + if image_data: + image = Image.open(image_data) + + if image.format.lower() == "gif": + gif = True + frames = [] + for frame in ImageSequence.Iterator(image): + frame = frame.convert("RGBA").resize((200, 200), Image.ANTIALIAS) + new_frame = background.copy().convert("RGBA") + new_frame.alpha_composite(frame, (self.x, self.y)) + frames.append(new_frame) + + draw = ImageDraw.Draw(frames[0], "RGBA") + else: + image = image.convert("RGBA").resize((200, 200), Image.ANTIALIAS) + background = background.convert("RGBA") + background.alpha_composite(image, (self.x, self.y)) + + draw = ImageDraw.Draw(background, "RGBA") + + font = ImageFont.truetype("./src/assets/fonts/impact.ttf", 120) + + if top_text: + text_x, text_y = (210, -10) + self.outline_text(draw, top_text.upper(), font, text_x, text_y, thickness=5) + + if bottom_text: + text_x, text_y = (150, 550) + self.outline_text(draw, bottom_text.upper(), font, text_x, text_y, thickness=5) + + if gif: + with io.BytesIO() as output: + frames[0].save( + output, + format="GIF", + save_all=True, + append_images=frames[1:], + duration=image.info["duration"], + loop=0, + transparency=image.info.get("transparency", 0), + disposal=2, # Use 'restore to background color' disposal method + ) + output.seek(0) + await interaction.followup.send(file=discord.File(output, "result.gif")) + else: + with io.BytesIO() as output: + background.save(output, format="PNG") + output.seek(0) + await interaction.followup.send(file=discord.File(output, "result.png")) + + except Exception as e: + traceback.print_exc() + await interaction.followup.send("An error occurred while processing the images.") + + +async def setup(bot): + """ + Add the cog to the bot on extension load + + Parameters + ---------- + bot (commands.Bot): Bot instance + """ + + await bot.add_cog(The(bot)) + diff --git a/src/config/config.yaml.example b/src/config/config.yaml.example index efed68d..1d9715e 100644 --- a/src/config/config.yaml.example +++ b/src/config/config.yaml.example @@ -1,5 +1,7 @@ config_mode: dev +dev_guild: 747542543750660178 + # Bot settings bot: token: diff --git a/src/run.py b/src/run.py index 0c763d5..6968521 100644 --- a/src/run.py +++ b/src/run.py @@ -9,6 +9,16 @@ from discord.ext import commands from logger import BotLogger + +UIO_GAMING_GUILD_ID = 747542543750660178 +DATABASE_RELIANT_COGS = { + "birthday.py", + "gullkorn.py", + "mc_whitelist.py", + "word_cloud.py", +} + + # Load config file with codecs.open("./src/config/config.yaml", "r", encoding="utf8") as f: config = yaml.load(f, Loader=yaml.SafeLoader) @@ -26,15 +36,26 @@ class Bot(commands.Bot): self.logger = BotLogger().logger # Initialize logger + self.guild_id = config.get("dev_guild", UIO_GAMING_GUILD_ID) + # Connect to database - db = config.get("database", {}) - self.db_connection = psycopg2.connect( - host=db["host"], - dbname=db["dbname"], - user=db["username"], - password=db["password"], - ) - self.db_connection.autocommit = True + db = config.get('database') + + if db is not None: + self.db_connection = psycopg2.connect( + host=db['host'], + dbname=db['dbname'], + user=db['username'], + password=db['password'], + ) + self.db_connection.autocommit = True + else: + self.db_connection = None + self.logger.warning( + "No database credentials specified. " + "Disabling db reliant cogs:\n{DATABASE_RELIANT_COGS}" + ) + # Fetch misc config values self.mc_rcon_password = config["minecraft"]["rcon_password"] @@ -45,9 +66,14 @@ class Bot(commands.Bot): self.config_mode = config.get("config_mode") async def setup_hook(self): + cog_files = listdir('./src/cogs') + + if self.db_connection is None: + cog_files = set(cog_files) - DATABASE_RELIANT_COGS + # Load cogs - for file in listdir("./src/cogs"): - if file.endswith(".py"): + for file in cog_files: + if file.endswith('.py'): name = file[:-3] await bot.load_extension(f"cogs.{name}") @@ -55,8 +81,8 @@ class Bot(commands.Bot): if self.config_mode == "prod": await self.tree.sync() else: - self.tree.copy_global_to(guild=discord.Object(id=412646636771344395)) - await self.tree.sync(guild=discord.Object(id=412646636771344395)) + self.tree.copy_global_to(guild=discord.Object(id=self.guild_id)) + await self.tree.sync(guild=discord.Object(id=self.guild_id)) # Create bot instance