From 36444715c2555d15d97d99223fe109e637c6e8fa Mon Sep 17 00:00:00 2001 From: superpenguin612 <74030080+superpenguin612@users.noreply.github.com> Date: Sat, 5 Aug 2023 10:25:57 -0400 Subject: [PATCH] updates to d.py 2.0 --- .gitignore | 1 + Procfile | 1 - bot/cogs/admin.py | 169 ++++ bot/cogs/core/events.py | 188 ++-- bot/cogs/core/help.py | 270 ------ bot/cogs/core/info.py | 187 ++-- bot/cogs/core/owner.py | 17 - bot/cogs/core/settings.py | 355 -------- bot/cogs/core/tasks.py | 164 ---- bot/cogs/custom/chsbot.py | 19 + bot/cogs/custom/chsbot/__init__.py | 0 bot/cogs/custom/chsbot/profanity.py | 43 - bot/cogs/custom/chsbot/school.py | 237 ----- bot/cogs/custom/nukeyboy/nuke.py | 68 -- bot/cogs/embeds.py | 1028 ++++++++-------------- bot/cogs/fun.py | 349 +++----- bot/cogs/games.py | 1252 +++++++++++++-------------- bot/cogs/mail.py | 107 +++ bot/cogs/math.py | 342 +++----- bot/cogs/moderation.py | 1039 +++++++++++----------- bot/cogs/modlogs.py | 103 +-- bot/cogs/reaction_roles.py | 411 --------- bot/cogs/search.py | 462 ---------- bot/cogs/starboard.py | 226 ----- bot/cogs/suggestions.py | 411 --------- bot/cogs/test.py | 93 -- bot/cogs/voting.py | 172 ++++ bot/cogs/wip/colors.py | 202 ----- bot/cogs/wip/custom.py | 13 - bot/cogs/wip/economy.py | 61 -- bot/cogs/wip/greetings.py | 24 - bot/helpers/tools.py | 438 ++++++---- config.yml | 162 +--- logging.yml | 13 +- main.py | 88 +- 35 files changed, 2570 insertions(+), 6145 deletions(-) delete mode 100644 Procfile create mode 100644 bot/cogs/admin.py delete mode 100644 bot/cogs/core/help.py delete mode 100644 bot/cogs/core/owner.py delete mode 100644 bot/cogs/core/settings.py delete mode 100644 bot/cogs/core/tasks.py create mode 100644 bot/cogs/custom/chsbot.py delete mode 100644 bot/cogs/custom/chsbot/__init__.py delete mode 100644 bot/cogs/custom/chsbot/profanity.py delete mode 100644 bot/cogs/custom/chsbot/school.py delete mode 100644 bot/cogs/custom/nukeyboy/nuke.py create mode 100644 bot/cogs/mail.py delete mode 100644 bot/cogs/reaction_roles.py delete mode 100644 bot/cogs/search.py delete mode 100644 bot/cogs/starboard.py delete mode 100644 bot/cogs/suggestions.py delete mode 100644 bot/cogs/test.py create mode 100644 bot/cogs/voting.py delete mode 100644 bot/cogs/wip/colors.py delete mode 100644 bot/cogs/wip/custom.py delete mode 100644 bot/cogs/wip/economy.py delete mode 100644 bot/cogs/wip/greetings.py diff --git a/.gitignore b/.gitignore index a353924..7cfc0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ ivenv/ dvenv/ localhelpers/ *.pyc +*.log diff --git a/Procfile b/Procfile deleted file mode 100644 index eb131d6..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -worker: python3 main.py diff --git a/bot/cogs/admin.py b/bot/cogs/admin.py new file mode 100644 index 0000000..f798b3d --- /dev/null +++ b/bot/cogs/admin.py @@ -0,0 +1,169 @@ +import logging +import subprocess + +import discord +from discord.ext import commands + +import bot.cogs.techhounds +import bot.cogs.custom.chsbot +from bot.helpers import tools + + +class Admin(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.logger = logging.LoggerAdapter( + logging.getLogger(__name__), {"botname": self.bot.name} + ) + + def admin_access(ctx: commands.Context) -> bool: + # superpenguin612#4406, acr#7356 + return ctx.author.id in [688530998920871969, 321378326273064961] + + @commands.command() + @commands.check(admin_access) + async def prepareroleselector( + self, ctx: commands.Context, channel: discord.TextChannel + ) -> None: + embed = discord.Embed( + description="\n".join( + [ + "## Get your desired roles here!", + "### <@&705065841737072740>", + "- You will be pinged for important school notices, news, emails, etc in <#707967554169208873>.\n" + "### <@&713502413335822336>", + "- You will be pinged for major server updates/changes in <#710521718334161057>\n" + "### <@&710957162167402558>", + "- Access to homework help channel, where you may ask your fellow peers for help.\n", + ] + ), + colour=discord.Colour.from_str("#FBBF05"), + ) + + await channel.send( + embed=embed, + view=bot.cogs.custom.chsbot.create_persistent_role_selector( + self.bot.get_guild(704819543398285383) + ), + ) + + # @commands.command() + # @commands.check(admin_access) + # async def preparename( + # self, ctx: commands.Context, channel: discord.TextChannel + # ) -> None: + # embed = discord.Embed( + # title="Nickname", + # description="Please press the button below to set your nickname.", + # colour=discord.Colour.from_str("#FBBF05"), + # ) + + # await channel.send(embed=embed, view=bot.cogs.techhounds.NameView()) + + # @commands.command() + # @commands.check(admin_access) + # async def preparepronoun( + # self, ctx: commands.Context, channel: discord.TextChannel + # ) -> None: + # embed = discord.Embed( + # title="Pronouns", + # description="Please select your pronouns if you feel comfortable doing so.", + # colour=discord.Colour.from_str("#FBBF05"), + # ) + + # await channel.send( + # embed=embed, + # view=bot.cogs.techhounds.create_persistent_pronoun_selector( + # self.bot.get_guild(403364109409845248) + # ), + # ) + + # @commands.command() + # @commands.check(admin_access) + # async def preparegradelevel( + # self, ctx: commands.Context, channel: discord.TextChannel + # ) -> None: + # embed = discord.Embed( + # title="Grade Level", + # description="Please select your grade level.", + # colour=discord.Colour.from_str("#FBBF05"), + # ) + + # await channel.send( + # embed=embed, + # view=bot.cogs.techhounds.create_persistent_grade_level_selector( + # self.bot.get_guild(403364109409845248) + # ), + # ) + + @commands.command() + @commands.check(admin_access) + async def eval(self, ctx: commands.Context, *, arg: str) -> None: + await ctx.send(eval(arg)) + + @commands.command() + @commands.check(admin_access) + async def sync(self, ctx: commands.Context) -> None: + await self.bot.tree.sync() + await ctx.send("Synced application commands.") + + @commands.command() + @commands.check(admin_access) + async def gitpull(self, ctx: commands.Context) -> None: + await ctx.send("Pulling from Git.") + await ctx.send( + subprocess.run( + "git pull", shell=True, text=True, capture_output=True + ).stdout + ) + + @commands.command() + @commands.check(admin_access) + async def reload(self, ctx: commands.Context) -> None: + await ctx.send("Reloading bot.") + self.logger.info("Reloading bot.") + extensions = [name for name, extension in self.bot.extensions.items()] + for extension in extensions: + self.logger.info(f"Reloading {extension}.") + await self.bot.reload_extension(extension) + + await ctx.send("Reloading complete.") + + @commands.command() + @commands.check(admin_access) + async def update(self, ctx: commands.Context) -> None: + await self.gitpull(ctx) + await self.reload(ctx) + + @commands.command() + @commands.check(admin_access) + async def updatewebsite(self, ctx: commands.Context) -> None: + await ctx.send("Pulling from Git.") + await ctx.send( + subprocess.run( + "git pull", + shell=True, + text=True, + capture_output=True, + cwd="/opt/website", + ).stdout + ) + + await ctx.send("Deploying to /var/www/html.") + subprocess.run( + "./deploy.sh", + shell=True, + text=True, + capture_output=True, + cwd="/home/dr", + ) + await ctx.send("Deployed.") + + @commands.command() + @commands.check(admin_access) + async def sync(self, ctx: commands.Context) -> None: + await self.bot.tree.sync() + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Admin(bot)) diff --git a/bot/cogs/core/events.py b/bot/cogs/core/events.py index cd68fb8..460f3f9 100644 --- a/bot/cogs/core/events.py +++ b/bot/cogs/core/events.py @@ -1,13 +1,10 @@ import asyncio -import json import logging -import os import time -import asyncpg import discord from discord.ext import commands -from discord_slash.context import SlashContext +from bot.cogs.custom.chsbot import create_persistent_role_selector from bot.helpers import tools @@ -15,187 +12,100 @@ class Events(commands.Cog): - def __init__(self, bot: commands.Bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot _logger = logging.getLogger(__name__) self.logger = logging.LoggerAdapter(_logger, {"botname": self.bot.name}) self.clogger = logging.getLogger("commands") - async def create_db_pool(self): - DATABASE_URL = os.environ["DATABASE_URL"] - return await asyncpg.create_pool( - DATABASE_URL, min_size=1, max_size=2, ssl="require" - ) - @commands.Cog.listener(name="on_ready") async def on_ready(self) -> None: + self.logger.info("Syncing commands") + await self.bot.tree.sync() self.logger.info(f"Bot is ready.") self.logger.info(f"User: {self.bot.user}") self.logger.info(f"ID: {self.bot.user.id}") - if self.bot.user.id == 802211256383438861: # chs bot - await self.bot.change_presence( - activity=discord.Activity( - type=discord.ActivityType.playing, - name=f"Now with slash commands! Type / to test them out! | c?help", - ) - ) - elif self.bot.user.id == 821888273936810014: # chs bot beta - await self.bot.change_presence( - activity=discord.Activity( - type=discord.ActivityType.playing, name=f"beta pog | cc?help" - ) + await self.bot.change_presence( + activity=discord.Activity( + type=discord.ActivityType.watching, + name=f"the Unofficial CHS Discord!", ) - elif self.bot.user.id == 796805491186597968: # davidhackerman bot - await self.bot.change_presence( - activity=discord.Activity( - type=discord.ActivityType.playing, name="$help | this is a good bot" - ) - ) - self.bot.db = await self.create_db_pool() + ) + + self.bot.add_view( + create_persistent_role_selector(self.bot.get_guild(704819543398285383)) + ) @commands.Cog.listener(name="on_connect") async def on_connect(self) -> None: self.logger.info("Connected to Discord websocket.") - @commands.Cog.listener(name="on_slash_command_error") - async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None: - if isinstance(error, commands.CommandOnCooldown): - embed = tools.create_error_embed( - ctx, - f"This command has been rate-limited. Please try again in {time.strftime('%Mm %Ss', time.gmtime(round(error.retry_after, 1)))}.", - ) - elif isinstance(error, commands.MissingPermissions): - embed = tools.create_error_embed( - ctx, - f"You do not have the required permissions to run this command.\nMissing permission(s): {','.join(error.missing_perms)}", - ) - elif isinstance(error, asyncio.TimeoutError): - embed = tools.create_error_embed(ctx, "You didn't respond in time!") - elif isinstance(error, commands.MissingRequiredArgument): - embed = tools.create_error_embed( - ctx, - f"You are missing a required argument for the command. Please check the help text for the command in question.\nMissing argument: {error.param}", - ) - elif isinstance(error, commands.BotMissingPermissions): - embed = tools.create_error_embed( - ctx, - f"The bot does not have the required permissions to run this command.\nMissing permission(s): {','.join(error.missing_perms)}", - ) - elif isinstance(error, commands.CommandInvokeError): - if isinstance(error.original, asyncio.TimeoutError): - embed = tools.create_error_embed( - ctx, "Sorry, you didn't respond in time!" - ) - else: - embed = tools.create_error_embed( - ctx, - f"Uh oh! Something went wrong, and this error wasn't anticipated. Sorry about that! I'll ping the owners of this bot to fix it.\nError: {error.__class__.__name__}", - ) - await ctx.send(embed=embed) - # author1 = await ctx.guild.fetch_member(688530998920871969) - # await ctx.send(f"{author1.mention}") - raise error - else: - embed = tools.create_error_embed( - ctx, - f"Uh oh! Something went wrong, and this error wasn't anticipated. Sorry about that! I'll ping the owners of this bot to fix it.\nError: {error.__class__.__name__}", - ) - await ctx.send(embed=embed) - # author1 = await ctx.guild.fetch_member(688530998920871969) - # await ctx.send(f"{authorx1.mention}") - raise error - await ctx.send(embed=embed) - @commands.Cog.listener(name="on_command_error") async def on_command_error(self, ctx: commands.Context, error: Exception) -> None: - print(error) + error = getattr(error, "original", error) if isinstance(error, commands.CommandOnCooldown): embed = tools.create_error_embed( - ctx, - f"This command has been rate-limited. Please try again in {time.strftime('%Mm %Ss', time.gmtime(round(error.retry_after, 1)))}.", + f"This command has been rate-limited. Please try again in {time.strftime('%Mm %Ss', time.gmtime(round(error.retry_after, 3)))}.", ) elif isinstance(error, commands.MissingPermissions): embed = tools.create_error_embed( - ctx, f"You do not have the required permissions to run this command.\nMissing permission(s): {','.join(error.missing_perms)}", ) + elif isinstance(error, commands.NotOwner): + embed = tools.create_error_embed("You cannot run this command.") elif isinstance(error, asyncio.TimeoutError): - embed = tools.create_error_embed(ctx, "You didn't respond in time!") + embed = tools.create_error_embed("You didn't respond in time!") elif isinstance(error, commands.CommandNotFound): - embed = tools.create_error_embed(ctx, "Sorry, that command does not exist!") + embed = tools.create_error_embed("Sorry, that command does not exist!") elif isinstance(error, commands.MissingRequiredArgument): embed = tools.create_error_embed( - ctx, f"You are missing a required argument for the command. Please check the help text for the command in question.\nMissing argument: {error.param}", ) elif isinstance(error, commands.BotMissingPermissions): embed = tools.create_error_embed( - ctx, f"The bot does not have the required permissions to run this command.\nMissing permission(s): {','.join(error.missing_perms)}", ) - elif isinstance(error, commands.CommandInvokeError): - if isinstance(error.original, asyncio.TimeoutError): - embed = tools.create_error_embed( - ctx, "Sorry, you didn't respond in time!" - ) - ctx.command.reset_cooldown(ctx) - else: - embed = tools.create_error_embed( - ctx, - f"Uh oh! Something went wrong, and this error wasn't anticipated. Sorry about that! I'll ping the owners of this bot to fix it.\nError: {error.__class__.__name__}", - ) - await ctx.send(embed=embed) - # author1 = await ctx.guild.fetch_member(688530998920871969) - # await ctx.send(f"{author1.mention}") - raise error + elif isinstance(error, commands.BadArgument): + embed = tools.create_error_embed( + f"An argument for the command was of incorrect type. Please check the help text for the command in question.\nError: {error.args[0]}", + ) else: embed = tools.create_error_embed( - ctx, - f"Ok, something really went wrong. This error message isn't supposed to show up, so ig I messed up pretty badly lmao", + f"Uh oh! Something went wrong, and this error wasn't anticipated. Sorry about that!\nError: {error.__class__.__name__}", ) await ctx.send(embed=embed) - # author1 = await ctx.guild.fetch_member(688530998920871969) - # await ctx.send(f"{author1.mention}") raise error await ctx.send(embed=embed) - # ctx.command.reset_cooldown(ctx) - # @commands.Cog.listener() - # async def on_command(self, ctx: commands.Context) -> None: - # self.clogger.info( - # "", - # extra={ - # "botname": self.bot.name, - # "username": ctx.author.name, - # "userid": ctx.author.id, - # "guild": ctx.guild.name, - # "guildid": ctx.guild.id, - # "prefix": ctx.prefix, - # "command": ctx.command.qualified_name, - # "arguments": ctx.args, - # "full": ctx.message.content, - # }, - # ) + @commands.Cog.listener() + async def on_command(self, ctx: commands.Context) -> None: + extra = { + "botname": self.bot.name, + "username": ctx.author.name, + "userid": ctx.author.id, + "guild": ctx.guild.name, + "guildid": ctx.guild.id, + "prefix": ctx.prefix, + "command": ctx.command.qualified_name, + "arguments": ctx.message.content.split()[1:], + "full": ctx.message.content, + } + + self.clogger.info( + "Command", + extra=extra, + ) # @commands.Cog.listener() - # async def on_slash_command(self, ctx: SlashContext) -> None: - # self.clogger.info( - # "", - # extra={ - # "botname": self.bot.name, - # "username": ctx.author.name, - # "userid": ctx.author.id, - # "guild": ctx.guild.name, - # "guildid": ctx.guild.id, - # "prefix": "/", - # "command": ctx.command.qualified_name, - # "arguments": ctx.args, - # "full": ctx.message.content, - # }, + # async def on_member_join(self, member: discord.Member) -> None: + # channel = member.guild.get_channel(451027883054465055) + # embed = discord.Embed( + # description=f"Welcome to the TechHOUNDS Discord, {member.name}.\nPlease read the <#994706215462523010>.\nTo enter the server, please go to <#993980468934488074> to set your division, name, and pronouns." # ) + # await channel.send(embed=embed) -def setup(bot: commands.Bot) -> None: - bot.add_cog(Events(bot)) +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Events(bot)) diff --git a/bot/cogs/core/help.py b/bot/cogs/core/help.py deleted file mode 100644 index d8f2bde..0000000 --- a/bot/cogs/core/help.py +++ /dev/null @@ -1,270 +0,0 @@ -import asyncio -import math -import re -from typing import Union -from collections import OrderedDict - -import discord -from discord.ext import commands - -# from discord_components import Button, ButtonStyle, InteractionType -from discord_slash.context import ComponentContext, SlashContext -from discord_slash.utils.manage_components import ( - create_button, - create_actionrow, - create_select, - create_select_option, - wait_for_component, -) -from discord_slash.model import ButtonStyle -from bot.helpers import tools - - -class Help(commands.Cog, name="help"): - """Group of help commands.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - def create_bot_help( - self, ctx: commands.Context - ) -> OrderedDict[discord.Embed, list[commands.Cog]]: - od = OrderedDict() - number_of_embeds = math.ceil( - len(self.bot.cogs.values()) / 5 - ) # Five cogs per page - for i in range(number_of_embeds): - embed = tools.create_embed(ctx, "Bot Commands", desc=self.bot.description) - cogs = list(self.bot.cogs.values())[(i * 5) : ((i + 1) * 5)] - for cog in cogs: - if "slash" not in cog.qualified_name.lower(): - embed.add_field( - name=f"{cog.qualified_name.title()} Commands", - # value=cog.description[2:], - value="hmmm", - inline=False, - ) - od[embed] = cogs - - return od - - def create_cog_help( - self, ctx: commands.Context, cog: commands.Cog - ) -> OrderedDict[discord.Embed, list[commands.Command]]: - commands_list = [ - command - for command in cog.walk_commands() - if not isinstance(command, commands.Group) - ] # Variable name changed from commands to commands_list because of collision with discord.ext.commands - od = OrderedDict() - number_of_embeds = math.ceil(len(commands_list) / 5) # Five commands per page - for i in range(number_of_embeds): - embed = tools.create_embed( - ctx, f"{cog.qualified_name.title()} Commands", desc=cog.description[2:] - ) - commands_list_section = list( - commands_list[((i + 1) * 5) - 5 : ((i + 1) * 5) - 1] - ) - for command in commands_list_section: - if "slash" not in cog.qualified_name.lower(): - embed.add_field( - name=f"{ctx.prefix}{command.qualified_name}", - value=command.short_doc - if command.short_doc - else "No help text.", - inline=False, - ) - od[embed] = commands_list_section - return od - - # def create_command_help( - # self, ctx: commands.Context, command: commands.Command - # ) -> discord.Embed: - # embed = tools.create_embed( - # ctx, - # f"{ctx.prefix}{command.qualified_name}{command.signature}", - # desc=command.description, - # ) - - # if command.help: - # prefixed_help = re.sub("_prefix_", ctx.prefix, command.help) - # embed.add_field(name="Help Text", value=prefixed_help) - # return embed - - @commands.command() - async def help(self, ctx: commands.Context, *, query=None) -> None: - cog_map = dict(self.bot.cogs) - command_map = {command.name: command for command in self.bot.commands} - - embeds = [] - embeds.append({self.bot: self.create_bot_help(ctx)}) - embeds.append({}) - for cog in cog_map.values(): - embeds[1][cog] = self.create_cog_help(ctx, cog) - - # embeds.append({}) - # for command in command_map.values(): - # embeds[2][command] = self.create_command_help(ctx, command) - - start_point = ( - self.bot - if query == None - else cog_map[query] - if cog_map.get(query) != None - else command_map[query] - if command_map.get(query) != None - else self.bot - ) - - help = HelpHandler( - self.bot, - ctx, - embeds, - # hierarchy, - start_point, - not_found=False if start_point != None else True, - ) - await help.run() - - -class HelpHandler(tools.EmbedButtonPaginator): - """Handles pagination around the help menu.""" - - BOT_LEVEL = 0 - COG_LEVEL = 1 - # COMMAND_LEVEL = 2 - # LEVELS = {commands.Bot: 0, commands.Cog: 1, commands.Command: 2} - - def __init__( - self, - bot: commands.Bot, - ctx: Union[commands.Context, SlashContext], - bot_embed: discord.Embed, - cog_embeds: dict[commands.Cog, discord.Embed], - all_embed: discord.Embed, - start_point: Union[commands.Bot, commands.Cog, commands.Command], - not_found: bool = False, - ): - self.bot = bot - self.ctx = ctx - self.bot_embed = bot_embed - self.cog_embeds = cog_embeds - self.all_embeds = all_embed - self.page_index = 0 - self.position = start_point - self.not_found = not_found - self.embed_pages = self.embeds[self.level][self.position] - - def create_buttons(self, disabled: bool = False) -> list[dict]: - self.current_buttons = list(self.embeds[self.level][self.position].values())[ - self.page_index - ] - - return ( - create_actionrow( - create_select( - options=[], - custom_id="select", - placeholder="Select the category of commands you want to view.", - min_values=1, - max_values=1, - ) - ) - + super().create_buttons(disabled=disabled) - + [ - create_actionrow( - create_button( - label="Main Menu", - style=ButtonStyle.blue, - custom_id="menu", - disabled=disabled, - ), - create_button( - label="Back", - style=ButtonStyle.blue, - custom_id="back", - disabled=disabled, - ), - ), - create_actionrow( - *[ # * unpacks the list into the *components of create_actionrow - create_button( - label=item.qualified_name, - style=ButtonStyle.gray, - custom_id=f"c{index}", - disabled=disabled, - ) - for index, item in enumerate( - list(self.embeds[self.level][self.position].values())[ - self.page_index - ] - ) - ] - ), - ] - ) - - async def pagination_events(self, component_ctx: ComponentContext): - if component_ctx.custom_id == "first": - self.page_index = 0 - elif component_ctx.custom_id == "prev": - if self.page_index > 0: - self.page_index -= 1 - elif component_ctx.custom_id == "next": - if self.page_index < len(list(self.embed_pages)) - 1: - self.page_index += 1 - - elif component_ctx.custom_id == "last": - self.page_index = len(list(self.embed_pages)) - 1 - - async def menu_events(self, component_ctx: ComponentContext): - if component_ctx.custom_id == "menu": - self.position = self.bot - self.page_index = 0 - elif component_ctx.custom_id == "back": - self.page_index = 0 - self.level -= 1 - if component_ctx.custom_id.startswith("c"): - self.level += 1 - self.position = self.current_buttons[int(component_ctx.custom_id[1])] - self.page_index = 0 - self.embed_pages = list(self.embeds[self.level][self.position]) - - async def run(self): - self.message = await self.ctx.send( - embed=list(self.embed_pages)[self.page_index], - components=self.create_buttons(), - ) - - while True: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=self.message, - timeout=180.0, - ) - if component_ctx.author.id != self.ctx.author.id: - await component_ctx.send( - hidden=True, - embed=discord.Embed( - title="Error", - description="You can't control another member's buttons.", - colour=discord.Colour.red(), - ), - ) - else: - await self.pagination_events(component_ctx) - await self.menu_events(component_ctx) - - await component_ctx.edit_origin( - embed=list(self.embed_pages)[self.page_index], - components=self.create_buttons(), - ) - - except asyncio.TimeoutError: - await self.message.edit(components=self.create_buttons(disabled=True)) - return - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Help(bot)) diff --git a/bot/cogs/core/info.py b/bot/cogs/core/info.py index 1900e97..6f04474 100644 --- a/bot/cogs/core/info.py +++ b/bot/cogs/core/info.py @@ -1,158 +1,75 @@ -import random - import discord from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option from bot.helpers import tools class Info(commands.Cog): - def __init__(self, bot: commands.Bot): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot - @cog_ext.cog_slash( - name="ping", - description="Get the latency of the connection between the bot and Discord.", - ) - async def ping(self, ctx: SlashContext) -> None: - embed = tools.create_embed( - ctx, "Pong!", desc=f"`{round(self.bot.latency * 1000, 1)}ms`" - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="about", - description="View information about the bot.", - ) - async def about(self, ctx: SlashContext) -> None: - embed = tools.create_embed(ctx, "About") - author = await ctx.guild.fetch_member(688530998920871969) - embed.add_field(name="Author", value=f"{author.mention}", inline=False) - embed.add_field(name="Language", value="Python", inline=False) - embed.add_field(name="Version", value="1.4", inline=False) - embed.add_field( - name="GitHub", value="https://github.com/davidracovan/discord-bots" - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="help", - description="Get help for the bot.", + @commands.hybrid_command( + description="Get the latency of the connection between the bot and Discord." ) - async def help(self, ctx: SlashContext) -> None: - embed = tools.create_embed( - ctx, - "Bot Help", - "Welcome to the Slash Commands module of CHS Bot! This is a new feature created by Discord allowing members to use bots more effectively. Thanks for using the bot!", - ) - embed.add_field( - name="How to Use", - value="Slash Commands are simple to use! Just type a `/` to see a list of all CHS Bot commands.\n" - "Press `Tab` whenever the `TAB` icon appears in the message bar to auto-complete the selected command and to complete a command parameter.\n" - "Explanation text will show for each parameter for a command. If a parameter is optional, it will not appear by default. Press `Tab` when an optional parameter is highlighted to add a value for it.", - inline=False, - ) - embed.add_field( - name='"This Interaction Failed"', - value="If this message appears, it means that the bot is most likely offline. Commands will still appear when the bot is offline, but they won't be runnable. If the bot isn't offline, ping the Developer role for help.", - inline=False, + async def ping(self, ctx: commands.Context) -> None: + """Get the latency of the connection between the bot and Discord.""" + await ctx.send( + embed=tools.create_embed( + "Pong!", desc=f"```{round(self.bot.latency * 1000, 1)}ms```" + ) ) - await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="server", - base_desc="Get information related to the server.", - name="info", - description="Get server info.", - ) - async def server_info(self, ctx: SlashContext) -> None: - embed = tools.create_embed(ctx, "Server Info") + @commands.hybrid_command(description="Get info about the server.") + async def serverinfo(self, ctx: commands.Context) -> None: + embed = tools.create_embed("Server Info") embed.add_field(name="Name", value=ctx.guild.name, inline=False) embed.add_field(name="Owner", value=ctx.guild.owner) embed.add_field(name="Channels", value=len(ctx.guild.channels)) embed.add_field(name="Roles", value=len(ctx.guild.roles)) embed.add_field(name="Members", value=ctx.guild.member_count) embed.add_field(name="ID", value=ctx.guild.id) - embed.set_thumbnail(url=str(ctx.guild.icon_url)) + try: + embed.set_thumbnail(url=str(ctx.guild.icon.url)) + except: + pass await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="server", - base_desc="Get information related to the server.", - name="roles", - description="Get server roles.", - ) - async def server_roles(self, ctx: SlashContext) -> None: - embed = tools.create_embed( - ctx, - "Server Roles", - "\n".join(reversed([role.mention for role in ctx.guild.roles])), + @commands.hybrid_command(description="Get help for the bot.") + async def help(self, ctx: commands.Context) -> None: + embeds = [] + commands_list = [ + command + for command in sorted( + list(self.bot.walk_commands()), key=lambda command: command.name + ) + if command.cog_name != "Admin" + ] + for commands in [ + commands_list[i : i + 8] for i in range(0, len(commands_list), 8) + ]: # splits the commands into groups of 8 + embed = tools.create_embed(title="Bot Commands") + for command in commands: + usage = [";" + command.name] + for name, param in command.params.items(): + usage += [f"<{name}>" if param.required else f"[{name}]"] + usage = " ".join(usage) + embed.add_field( + name=usage, + value=command.description if command.description else "None", + inline=False, + ) + embeds += [embed] + view = tools.EmbedButtonPaginator(ctx.author, embeds) + view.msg = await ctx.send(embed=embeds[0], view=view) + + @commands.hybrid_command(description="Get the link for the repo.") + async def repo(self, ctx: commands.Context) -> None: + await ctx.send( + embed=tools.create_embed( + "Flick's repo", "https://github.com/frc868/flick.py" + ) ) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="server", - base_desc="Get information related to the server.", - name="channels", - description="Get server channels.", - ) - async def server_channels(self, ctx: SlashContext) -> None: - embed = tools.create_embed( - ctx, - "Server Channels", - ) - embed.add_field( - name="Categories", - value=len([category for category in ctx.guild.categories]), - ) - embed.add_field( - name="Text Channels", - value=len([channel for channel in ctx.guild.text_channels]) - if ctx.guild.text_channels - else None, - ) - embed.add_field( - name="Voice Channels", - value=len([channel for channel in ctx.guild.voice_channels]) - if ctx.guild.voice_channels - else None, - ) - embed.add_field( - name="Stage Channels", - value=len([channel for channel in ctx.guild.stage_channels]) - if ctx.guild.stage_channels - else None, - ) - - await ctx.send(embed=embed) - - # -------------------------------------------- - # LEGACY COMMANDS - # -------------------------------------------- - - @commands.command(name="ping") - async def ping_legacy(self, ctx: commands.Context) -> None: - """Get the latency of the connection between the bot and Discord.""" - embed = tools.create_embed( - ctx, "Pong!", desc=f"`{round(self.bot.latency * 1000, 1)}ms`" - ) - await ctx.send(embed=embed) - - @commands.command(name="about") - async def about_legacy(self, ctx: commands.Context) -> None: - """View information about the bot.""" - embed = tools.create_embed(ctx, "About") - author = await ctx.guild.fetch_member(688530998920871969) - embed.add_field(name="Author", value=f"{author.mention}", inline=False) - embed.add_field(name="Language", value="Python", inline=False) - embed.add_field(name="Version", value="1.4", inline=False) - embed.add_field( - name="GitHub", value="https://github.com/davidracovan/discord-bots" - ) - await ctx.send(embed=embed) -def setup(bot): - bot.add_cog(Info(bot)) +async def setup(bot) -> None: + await bot.add_cog(Info(bot)) diff --git a/bot/cogs/core/owner.py b/bot/cogs/core/owner.py deleted file mode 100644 index 51d113e..0000000 --- a/bot/cogs/core/owner.py +++ /dev/null @@ -1,17 +0,0 @@ -import random - -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option - -from bot.helpers import tools - - -class Owner(commands.Cog, name="owner"): - def __init__(self, bot: commands.Bot): - self.bot = bot - - -def setup(bot: commands.Bot): - bot.add_cog(Owner(bot)) diff --git a/bot/cogs/core/settings.py b/bot/cogs/core/settings.py deleted file mode 100644 index bc02128..0000000 --- a/bot/cogs/core/settings.py +++ /dev/null @@ -1,355 +0,0 @@ -import json -from typing import Union -import asyncio - -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_choice, create_option -from discord_components import Button, ButtonStyle - -from bot.helpers import tools - - -class SettingsHandler: - def __init__( - self, - bot: commands.Bot, - ctx: Union[commands.Context, SlashContext], - embeds: list[ - discord.Embed, - dict[commands.Cog, list[discord.Embed]], - dict[commands.Command, list[discord.Embed]], - ], - hierarchy: dict[commands.Bot, dict[commands.Cog, commands.Command]], - ): - self.bot = bot - self.ctx = ctx - self.embeds = embeds - self.page_index = 0 - - def create_buttons(self, disabled: bool = False) -> list[list[Button]]: - return [ - [ - Button( - label="Main Menu", - style=ButtonStyle.blue, - id="menu", - disabled=disabled, - ), - Button( - label="Back", style=ButtonStyle.blue, id="back", disabled=disabled - ), - ], - [ - Button( - label=item.name, - style=ButtonStyle.gray, - id=f"c{index}", - disabled=disabled, - ) - for index, item in enumerate([]) - ], - ] + super().create_buttons(disabled=disabled) - - async def menu_events(self, interaction): - if interaction.component.id == "menu": - self.position = self.bot - self.page_index = 0 - elif interaction.component.id == "back": - self.page_index = 0 - - if interaction.component.id.startswith("c"): - self.page_index = 0 - - async def run(self): - self.message = await self.ctx.send( - embed=self.embed_pages[self.page_index], components=self.create_buttons() - ) - - while True: - try: - interaction = await self.bot.wait_for( - "button_click", - check=lambda i: i.message == self.message, - timeout=180.0, - ) - - if interaction.author.id != self.ctx.author.id: - await interaction.respond( - type=InteractionType.ChannelMessageWithSource, - ephemeral=True, - embed=discord.Embed( - title="Error", - description="You cannot control another user's buttons.", - colour=discord.Colour.red(), - ), - ) - else: - await self.pagination_events(interaction) - await self.menu_events(interaction) - - except asyncio.TimeoutError: - await self.message.edit(components=self.create_buttons(disabled=True)) - return - - -class Settings(commands.Cog, name="settings"): - def __init__(self, bot): - self.bot = bot - - @commands.Cog.listener() - async def on_guild_join(self, guild: discord.Guild): - await self.bot.db.execute( - "INSERT INTO settings (guild_id) VALUES ($1)", - guild.id, - ) - - @cog_ext.cog_slash( - name="settings", - description="View and edit the settings for the server.", - ) - async def settings(self, ctx: SlashContext): - handler = SettingsHandler(ctx) - handler.run() - - @commands.command(name="settings") - async def settings_legacy(self, ctx: commands.Context): - handler = SettingsHandler(ctx) - handler.run() - - # @cog_ext.cog_slash( - # name="listsettings", - # description="Get the settings for the server.", - # ) - # async def listsettings(self, ctx): - # record = await self.bot.db.fetchrow( - # "SELECT * FROM settings WHERE server_id=$1;", str(ctx.guild.id) - # ) - # server_settings = json.loads(record["json"]) - # embed = tools.create_embed(ctx, "Bot Settings") - # embed.add_field( - # name="Starboard", - # value=f'**Channel**: {ctx.bot.get_channel(server_settings["starboard"]["channel"])}' - # f'**Number Required**: {server_settings["starboard"]["number_required"]}' - # f'**Reaction**: {server_settings["starboard"]["reaction"]}', - # ) - # embed.add_field( - # name="Suggestions", - # value=f'**Channel**: {ctx.bot.get_channel(server_settings["suggestions"]["channel"])}' - # f'**Up Emoji**: {server_settings["suggestions"]["up_emoji"]}' - # f'**Down Emoji**: {server_settings["suggestions"]["down_emoji"]}', - # ) - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="starboard", - # sub_group_desc="Edit the settings for the starboard.", - # name="channel", - # description="Edit the starboard channel.", - # options=[ - # create_option( - # name="channel", - # description="The new starboard channel.", - # option_type=7, - # required=True, - # ), - # ], - # ) - # async def editsettings_starboard_channel(self, ctx, channel): - # if type(channel) == discord.channel.TextChannel: - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["channel"] = channel.id - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The starboard channel has been updated successfully.", - # ) - # await ctx.send(embed=embed) - # else: - # embed = tools.create_error_embed(ctx, "The channel must be a text channel.") - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="starboard", - # sub_group_desc="Edit the settings for the starboard.", - # name="numberrequired", - # description="Edit the number of stars required to add a message to the starboard.", - # options=[ - # create_option( - # name="number", - # description="The new number of stars required for the starboard.", - # option_type=4, - # required=True, - # ), - # ], - # ) - # async def editsettings_starboard_numberrequired(self, ctx, number): - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["number_required"] = number - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The number of stars required for the starboard has been updated successfully.", - # ) - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="starboard", - # sub_group_desc="Edit the settings for the starboard.", - # name="reaction", - # description="Edit the reaction to use for the starboard.", - # options=[ - # create_option( - # name="emoji", - # description="The new reaction to use for the starboard.", - # option_type=3, - # required=True, - # ), - # ], - # ) - # async def editsettings_starboard_reaction(self, ctx, emoji): - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["reaction"] = emoji - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The reaction to use for the starboard has been updated successfully.", - # ) - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="dailyreport", - # sub_group_desc="Edit the settings for the daily report.", - # name="channel", - # description="Edit the daily report channel.", - # options=[ - # create_option( - # name="channel", - # description="The new daily report channel.", - # option_type=7, - # required=True, - # ), - # ], - # ) - # async def editsettings_dailyreport_channel(self, ctx, channel): - # if type(channel) == discord.channel.TextChannel: - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["channel"] = channel.id - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The daily report channel has been updated successfully.", - # ) - # await ctx.send(embed=embed) - # else: - # embed = tools.create_error_embed(ctx, "The channel must be a text channel.") - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="suggestions", - # sub_group_desc="Edit the settings for suggestions.", - # name="channel", - # description="Edit the suggestions channel.", - # options=[ - # create_option( - # name="channel", - # description="The new suggestions channel.", - # option_type=7, - # required=True, - # ), - # ], - # ) - # async def editsettings_suggestions_channel(self, ctx, channel): - # if type(channel) == discord.channel.TextChannel: - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["suggestions"]["channel"] = channel.id - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The suggestions channel has been updated successfully.", - # ) - # await ctx.send(embed=embed) - # else: - # embed = tools.create_error_embed(ctx, "The channel must be a text channel.") - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="suggestions", - # sub_group_desc="Edit the settings for suggestions.", - # name="upemoji", - # description="Edit the up emoji for suggestions.", - # options=[ - # create_option( - # name="emoji", - # description="The new up emoji for suggestions.", - # option_type=7, - # required=True, - # ), - # ], - # ) - # async def editsettings_suggestions_upemoji(self, ctx, emoji): - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["up_emoji"] = emoji - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The up emoji for suggestions has been updated successfully.", - # ) - # await ctx.send(embed=embed) - - # @cog_ext.cog_subcommand( - # base="editsettings", - # base_desc="Edit the settings for the server.", - # subcommand_group="suggestions", - # sub_group_desc="Edit the settings for the suggestions.", - # name="downemoji", - # description="Edit the down emoji for suggestions.", - # options=[ - # create_option( - # name="emoji", - # description="The new down emoji for suggestions.", - # option_type=7, - # required=True, - # ), - # ], - # ) - # async def editsettings_suggestions_downemoji(self, ctx, emoji): - # record = await self.get_record_by_server_id(ctx.guild.id) - # server_settings = json.loads(record["json"]) - # server_settings["starboard"]["down_emoji"] = emoji - # await self.edit_record(ctx.guild.id, json.dumps(server_settings)) - # embed = tools.create_embed( - # ctx, - # "Edit Settings", - # desc="The down emoji for suggestions has been updated successfully.", - # ) - # await ctx.send(embed=embed) - - -def setup(bot: commands.Bot): - bot.add_cog(Settings(bot)) diff --git a/bot/cogs/core/tasks.py b/bot/cogs/core/tasks.py deleted file mode 100644 index 2608217..0000000 --- a/bot/cogs/core/tasks.py +++ /dev/null @@ -1,164 +0,0 @@ -import json -import random -from datetime import date, datetime, timedelta - -import asyncpg -import discord -from discord.ext import commands, tasks - -from bot.helpers import tools - - -class Tasks(commands.Cog, name="tasks"): - def __init__(self, bot: commands.Bot): - self.bot = bot - - # self.daily_report.start() - self.timed_unmute.start() - - async def create_daily_report(self, guild: discord.Guild) -> discord.Embed: - with open("bot/helpers/school_info.json") as f: - self.SCHOOL_INFO_DICT = json.load(f) - - embed = discord.Embed( - title="Daily Report", - description="Good morning everyone! Here's your report for the day.", - ) - school_days = ( - self.SCHOOL_INFO_DICT["days"]["carmel"][ - datetime.now().strftime("%m/%d/%Y") - ], - self.SCHOOL_INFO_DICT["days"]["greyhound"][ - datetime.now().strftime("%m/%d/%Y") - ], - ) - - school_day_val = ( - f'Today is {datetime.now().strftime("%A, %B %d, %Y")}.\n' - f"It's a {school_days[0]} for the Carmel cohort, and a {school_days[1]} for the Greyhound cohort.\n" - "(To view more details, run `/schoolday` or `c?schoolday` (legacy command).)" - ) - embed.add_field(name="School Day", value=school_day_val, inline=False) - search = self.bot.get_cog("search") - food_items = await search.get_mv_list(date.today().strftime("%m-%d-%Y")) - lunch_menu_val_1 = "\n".join( - [ - f'`{index}` - {val["item_Name"]}' - for index, val in enumerate(food_items[0]) - ] - ) - lunch_menu_val_3 = ( - "\n".join( - [ - f'`{index}` - {val["item_Name"]}' - for index, val in enumerate(food_items[2]) - ] - ) - + "\n\n(To view more details, run `/mealviewer item `. The item ID is the number that appears to the right of the food item.)" - ) - embed.add_field( - name="Freshmen Center/Greyhound Station Lunch Menu", - value=lunch_menu_val_1, - inline=False, - ) - embed.add_field( - name="Main Cafeteria Lunch Menu", value=lunch_menu_val_3, inline=False - ) - number_of_days = ( - datetime.strptime("05/27/2021", "%m/%d/%Y") - datetime.now() - ).days + 1 - embed.add_field(name="Total Days Until The End of School", value=number_of_days) - number_of_school_days = 0 - for day in range(number_of_days): - day = self.SCHOOL_INFO_DICT["days"]["carmel"][ - (datetime.now() + timedelta(days=day)).strftime("%m/%d/%Y") - ] - if any( - [day_type in day.lower() for day_type in ["blue", "gold", "orange"]] - ): - number_of_school_days += 1 - embed.add_field( - name="School Days Until The End of School", value=number_of_school_days - ) - - number_of_carmel_in_person_days = 0 - for day in range(number_of_days): - day = self.SCHOOL_INFO_DICT["days"]["carmel"][ - (datetime.now() + timedelta(days=day)).strftime("%m/%d/%Y") - ] - if "in person" in day.lower(): - number_of_carmel_in_person_days += 1 - embed.add_field( - name="Carmel In Person Days Until The End of School", - value=number_of_carmel_in_person_days, - ) - - number_of_greyhound_in_person_days = 0 - for day in range(number_of_days): - day = self.SCHOOL_INFO_DICT["days"]["carmel"][ - (datetime.now() + timedelta(days=day)).strftime("%m/%d/%Y") - ] - if "in person" in day.lower(): - number_of_greyhound_in_person_days += 1 - embed.add_field( - name="Greyhound In Person Days Until The End of School", - value=number_of_greyhound_in_person_days, - ) - - embed.set_footer( - text="Note: This report is for informational purposes only. Although we will try to make sure this report is up to date, we cannot guarantee it." - ) - embed.set_thumbnail(url=guild.icon_url) - return embed - - @commands.command() - # @commands.has_permissions(administrator=True) - async def testdailyreport(self, ctx: commands.Context): - embed = await self.create_daily_report(ctx.guild) - await ctx.send(embed=embed) - - @tasks.loop(seconds=1.0) - async def daily_report(self): - if datetime.utcnow().strftime("%H:%M:%S") == "11:00:00": - guild = self.bot.get_guild(809169133086048257) - channel = guild.get_channel(819546169985597440) - role = guild.get_role(821386697727410238) - - embed = await self.create_daily_report(guild) - msg = await channel.send(content=role.mention, embed=embed) - await msg.publish() - - @tasks.loop(seconds=1.0) - async def timed_unmute(self): - if hasattr(self.bot, "db"): - records = await self.bot.db.fetch( - "SELECT * FROM moderations WHERE type='mute' AND active" - ) - for record in records: - if datetime.utcnow().strftime("%m/%d/%Y %H:%M:%S") == ( - record["timestamp"] + timedelta(seconds=record["duration"]) - ).strftime("%m/%d/%Y %H:%M:%S"): - guild = self.bot.get_guild(int(record["server_id"])) - role = guild.get_role(809169133232717890) - user = guild.get_member(int(record["user_id"])) - await user.remove_roles(role) - await self.bot.db.execute( - "UPDATE moderations SET active=FALSE WHERE id=$1", record["id"] - ) - - await self.bot.db.execute( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason, duration) VALUES ($1, $2, $3, $4);", - record["server_id"], - "unmute", - record["user_id"], - str(self.bot.user.id), - "Automatic unmute by the bot.", - ) - - @timed_unmute.before_loop - async def before_timed_unmute(self): - await self.bot.wait_until_ready() - - -def setup(bot: commands.Bot): - bot.add_cog(Tasks(bot)) diff --git a/bot/cogs/custom/chsbot.py b/bot/cogs/custom/chsbot.py new file mode 100644 index 0000000..ee87729 --- /dev/null +++ b/bot/cogs/custom/chsbot.py @@ -0,0 +1,19 @@ +import discord + +from bot.helpers import tools + + +def create_persistent_role_selector( + guild: discord.Guild, +) -> tools.PersistentRoleSelector: + return tools.PersistentRoleSelector( + guild, + [ + 705065841737072740, + 713502413335822336, + 710957162167402558, + ], + "Role Selector", + custom_id_prefix="roleselector", + mutually_exclusive=False, + ) diff --git a/bot/cogs/custom/chsbot/__init__.py b/bot/cogs/custom/chsbot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bot/cogs/custom/chsbot/profanity.py b/bot/cogs/custom/chsbot/profanity.py deleted file mode 100644 index bc93f9d..0000000 --- a/bot/cogs/custom/chsbot/profanity.py +++ /dev/null @@ -1,43 +0,0 @@ -import string - -import discord -from discord.ext import commands - -from bot.helpers import tools - - -class Profanity(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.profanity_wordlist = [] - with open("bot/helpers/wordlist.txt", "r") as f: - self.profanity_wordlist = f.read().splitlines() - - @commands.Cog.listener() - async def on_message(self, message): - message_list = message.content.split(" ") - censored_message_list = [] - profane_message = False - for index, val in enumerate(message_list): - if any( - val.lower().translate(str.maketrans("", "", string.punctuation)) == item - for item in self.profanity_wordlist - ): - profane_message = True - censored_message_list.append(f"||{val}||") - else: - censored_message_list.append(f"{val}") - if profane_message: - censored_message = " ".join(censored_message_list) - embed = discord.Embed( - title="Profane Message", - description=censored_message, - color=discord.Color.orange(), - ) - embed.set_author(name=message.author, icon_url=message.author.avatar_url) - await message.channel.send(embed=embed) - await message.delete() - - -def setup(bot): - bot.add_cog(Profanity(bot)) diff --git a/bot/cogs/custom/chsbot/school.py b/bot/cogs/custom/chsbot/school.py deleted file mode 100644 index fe5aacb..0000000 --- a/bot/cogs/custom/chsbot/school.py +++ /dev/null @@ -1,237 +0,0 @@ -import json -from datetime import date, datetime, time, timedelta - -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_choice, create_option - -from bot.helpers import tools - - -class School(commands.Cog): - def __init__(self, bot): - self.bot = bot - with open("bot/helpers/school_info.json") as f: - self.SCHOOL_INFO_DICT = json.load(f) - - # def _register_class(self, user_id, class_type: str, class_name): - # with open('bot/helpers/school.json', 'r') as f: - # school_dict = json.load(f) - # if str(user_id) not in school_dict: - # with open('bot/helpers/school.json', 'w') as f: - # if not school_dict[str(user_id)]['classes']: - # school_dict[str(user_id)]['classes'] = {} - # school_dict[str(user_id)]['classes'][class_type] = class_name - # json.dump(school_dict, f) - - async def get_record(self, user_id): - return await self.bot.db.fetchrow( - "SELECT * FROM registrations WHERE user_id=$1", str(user_id) - ) - - # @commands.command() - # async def register(self, ctx, blue_lunch, gold_lunch, cohort): - # """Example: `c?register B D greyhound` - # Allows you to register details with the bot to get personalized responses. - # All three values are required. - # Other commands will currently not work without registration. - # """ - # await self.bot.db.execute('INSERT INTO registrations (user_id, blue_lunch, gold_lunch, cohort)' - # 'VALUES ($1, $2, $3, $4)', str(ctx.author.id), blue_lunch.upper(), gold_lunch.upper(), cohort.lower()) - # desc = f'{ctx.author.mention}, you have been registered.' - # embed = tools.create_embed(ctx, 'User Registration', desc=desc) - # embed.add_field(name='Blue Day Lunch', value=blue_lunch, inline=False) - # embed.add_field(name='Gold Day Lunch', value=gold_lunch, inline=False) - # embed.add_field(name='Cohort', value=cohort.title(), inline=False) - # await ctx.send(embed=embed) - - # @commands.command(name='registerclass') - # async def register_class(self, ctx, class_type, class_name): - # self._register_class(ctx.author.id, class_type.lower(), class_name) - # desc = f'{ctx.author.mention}, you have been registered.' - # embed = tools.create_embed(ctx, 'Class Registration', desc=desc) - # embed.add_field(name=class_type, value=class_name, inline=False) - # await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="schoolday", - description="Get information about a specific school day. Defaults to today.", - options=[ - create_option( - name="cohort", - description="The cohort to get the school day for.", - option_type=SlashCommandOptionType.STRING, - required=False, - choices=[ - create_choice(name="carmel", value="carmel"), - create_choice(name="greyhound", value="greyhound"), - ], - ), - create_option( - name="date", - description="The optional date to look up. Must be in the form MM/DD/YYYY.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - async def schoolday(self, ctx, cohort=None, date=None): - """Tells you information about today (Blue/Gold, In Person/Virtual, Late Start, weekends, breaks, etc.). - The `all` argument is optional, and it will display information for both cohorts. - """ - if cohort: - if date: - school_day = self.SCHOOL_INFO_DICT["days"][cohort][date] - else: - school_day = self.SCHOOL_INFO_DICT["days"][cohort][ - datetime.now().strftime("%m/%d/%Y") - ] - desc = f'Today is {datetime.now().strftime("%A, %B %d, %Y")}.\n {cohort.upper()} Cohort: {school_day}' - embed = tools.create_embed(ctx, "School Day", desc) - await ctx.send(embed=embed) - else: - if date: - school_days = ( - self.SCHOOL_INFO_DICT["days"]["carmel"][date], - self.SCHOOL_INFO_DICT["days"]["greyhound"][date], - ) - else: - school_days = ( - self.SCHOOL_INFO_DICT["days"]["carmel"][ - datetime.now().strftime("%m/%d/%Y") - ], - self.SCHOOL_INFO_DICT["days"]["greyhound"][ - datetime.now().strftime("%m/%d/%Y") - ], - ) - desc = f'Today is {datetime.now().strftime("%A, %B %d, %Y")}.' - embed = tools.create_embed(ctx, "School Day", desc) - embed.add_field(name="Carmel", value=school_days[0]) - embed.add_field(name="Greyhound", value=school_days[1]) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="schoolweek", - description="Get information for the rest of the school week.", - options=[ - create_option( - name="cohort", - description="The cohort to get the information for.", - option_type=SlashCommandOptionType.STRING, - required=False, - choices=[ - create_choice(name="carmel", value="carmel"), - create_choice(name="greyhound", value="greyhound"), - ], - ), - ], - ) - async def schoolweek(self, ctx, cohort=None): - """Tells you information about the next seven days. - The `all` argument is optional, and it will display information for both cohorts. - """ - if cohort: - school_week = [] - for i in range(7): - target_day = datetime.now() + timedelta(days=i + 1) - school_week.append( - target_day.strftime("%a, %b %d, %Y: ") - + self.SCHOOL_INFO_DICT["days"][cohort][ - target_day.strftime("%m/%d/%Y") - ] - ) - desc = "\n".join(school_week) - embed = tools.create_embed(ctx, "School Week", desc) - await ctx.send(embed=embed) - else: - school_weeks = [[], []] - for i in range(7): - target_day = datetime.now() + timedelta(days=i + 1) - school_weeks[0].append( - target_day.strftime("%a, %b %d, %Y: ") - + self.SCHOOL_INFO_DICT["days"]["carmel"][ - target_day.strftime("%m/%d/%Y") - ] - ) - school_weeks[1].append( - target_day.strftime("%a, %b %d, %Y: ") - + self.SCHOOL_INFO_DICT["days"]["greyhound"][ - target_day.strftime("%m/%d/%Y") - ] - ) - embed = tools.create_embed(ctx, "School Week") - embed.add_field(name="Carmel Cohort", value="\n".join(school_weeks[0])) - embed.add_field(name="Greyhound Cohort", value="\n".join(school_weeks[1])) - await ctx.send(embed=embed) - - # -------------------------------------------- - # LEGACY COMMANDS - # -------------------------------------------- - - @commands.command(name="schoolday") - async def schoolday_legacy(self, ctx): - """Tells you information about today (Blue/Gold, In Person/Virtual, Late Start, weekends, breaks, etc.). - The `all` argument is optional, and it will display information for both cohorts. - """ - school_days = ( - self.SCHOOL_INFO_DICT["days"]["carmel"][ - datetime.now().strftime("%m/%d/%Y") - ], - self.SCHOOL_INFO_DICT["days"]["greyhound"][ - datetime.now().strftime("%m/%d/%Y") - ], - ) - desc = f'Today is {datetime.now().strftime("%A, %B %d, %Y")}.' - embed = tools.create_embed(ctx, "School Day", desc) - embed.add_field(name="Carmel", value=school_days[0]) - embed.add_field(name="Greyhound", value=school_days[1]) - await ctx.send(embed=embed) - - @commands.command(name="schoolweek") - async def schoolweek_legacy(self, ctx): - """Tells you information about the next seven days. - The `all` argument is optional, and it will display information for both cohorts. - """ - school_weeks = [[], []] - for i in range(7): - target_day = datetime.now() + timedelta(days=i + 1) - school_weeks[0].append( - target_day.strftime("%a, %b %d, %Y: ") - + self.SCHOOL_INFO_DICT["days"]["carmel"][ - target_day.strftime("%m/%d/%Y") - ] - ) - school_weeks[1].append( - target_day.strftime("%a, %b %d, %Y: ") - + self.SCHOOL_INFO_DICT["days"]["greyhound"][ - target_day.strftime("%m/%d/%Y") - ] - ) - embed = tools.create_embed(ctx, "School Week") - embed.add_field(name="Carmel Cohort", value="\n".join(school_weeks[0])) - embed.add_field(name="Greyhound Cohort", value="\n".join(school_weeks[1])) - await ctx.send(embed=embed) - - @commands.command(name="schooldate") - async def schooldate_legacy(self, ctx, date): - """Tells you information about a specified date. - The `date` argument is required, and must be in the form `mm/dd/yyyy`. - The `all` argument is optional, and it will display information for both cohorts. - """ - school_dates = ( - self.SCHOOL_INFO_DICT["days"]["carmel"][date], - self.SCHOOL_INFO_DICT["days"]["greyhound"][date], - ) - desc = ( - f"Carmel Cohort: {school_dates[0]}\nGreyhound Cohort: {school_dates[1]}\n" - ) - embed = tools.create_embed(ctx, "School Date", desc) - embed.add_field(name="Carmel", value=school_dates[0]) - embed.add_field(name="Greyhound", value=school_dates[1]) - await ctx.send(embed=embed) - - -def setup(bot): - bot.add_cog(School(bot)) diff --git a/bot/cogs/custom/nukeyboy/nuke.py b/bot/cogs/custom/nukeyboy/nuke.py deleted file mode 100644 index 09d5c9e..0000000 --- a/bot/cogs/custom/nukeyboy/nuke.py +++ /dev/null @@ -1,68 +0,0 @@ -import discord -from discord.ext import commands - - -class Nuke(commands.Cog): - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - - async def del_roles(self, ctx: commands.Context) -> None: - roles = ctx.guild.roles - for role in roles: - try: - await role.delete() - except: - pass - - async def del_emojis(self, ctx: commands.Context) -> None: - emojis = ctx.guild.emojis - for emoji in emojis: - try: - await emoji.delete() - except: - pass - - async def del_channels(self, ctx: commands.Context) -> None: - channels = ctx.guild.channels - curr_channel = ctx.message.channel - for channel in channels: - if channel != curr_channel: - try: - await channel.delete() - except: - pass - - async def ping_everyone(self, ctx: commands.Context, ping: int) -> None: - temp_channels = [] - for i in range(10): - temp_channels.append(await ctx.guild.create_text_channel("heh")) - for i in range(ping): - for channel in temp_channels: - if isinstance(channel, discord.TextChannel): - await channel.send("@everyone") - - async def ban_everyone(self, ctx: commands.Context): - members = ctx.guild.members - for member in members: - try: - await member.ban() - except: - pass - - @commands.command() - async def nuke(self, ctx: commands.Context, ping: int, ban: bool = None) -> None: - if ctx.author.id == 808420517282578482: - await ctx.send("hehe nuke go brrrr") - await self.del_roles(ctx) - await self.del_emojis(ctx) - await self.del_channels(ctx) - - if ping: - await self.ping_everyone(ctx, ping) - if ban: - await self.ban_everyone(ctx) - await ctx.send("nuke done, get rekd") - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Nuke(bot)) \ No newline at end of file diff --git a/bot/cogs/embeds.py b/bot/cogs/embeds.py index d28addc..6d5424a 100644 --- a/bot/cogs/embeds.py +++ b/bot/cogs/embeds.py @@ -1,718 +1,394 @@ -import asyncio -import datetime -import time - import discord +from discord import app_commands from discord.ext import commands -from discord_components import Button, ButtonStyle, InteractionType -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_choice, create_option from bot.helpers import tools +# TODO: convert some of these extra modals to generic ones for tools module to declutter +# TODO: make ;addembed and ;removeembed commands to have messages with more than one embed + -class Embeds(commands.Cog, name="embeds"): - def __init__(self, bot): +class Embeds(commands.Cog): + def __init__(self, bot) -> None: self.bot = bot + self.bot.tree.add_command( + app_commands.ContextMenu( + name="Edit Embed", + callback=self.editembed_menu, + ) + ) + + async def editembed_menu( + self, interaction: discord.Interaction, message: discord.Message + ) -> None: + if message.author.id != self.bot.user.id: + await interaction.response.send_message( + embed=tools.create_error_embed( + "That message was not sent by this bot." + ), + ephemeral=True, + ) + return + if interaction.user.guild_permissions.manage_messages: + view = EmbedEditor(interaction.user, message) + await interaction.response.send_message(embeds=view.get_embeds(), view=view) + else: + await interaction.response.send_message( + embed=tools.create_error_embed("You can't do that."), ephemeral=True + ) - @cog_ext.cog_slash( + @commands.hybrid_command( name="sendembed", - description="Send an embed message from the bot. This launches the embeditorificatorinator.", - options=[ - create_option( - name="channel", - description="The channel the embed is in.", - option_type=SlashCommandOptionType.CHANNEL, - required=True, - ), - ], + description="Send an embed message from the bot.", ) - async def sendembed(self, ctx: SlashContext, channel: discord.TextChannel) -> None: - embedcreator = EmbedCreator(self.bot, ctx, channel.id) - await embedcreator.run() + @app_commands.describe(channel="The channel to send the embed to.") + @commands.has_permissions(manage_messages=True) + async def sendembed( + self, ctx: commands.Context, channel: discord.TextChannel | None = None + ) -> None: + view = EmbedEditor(ctx.author, channel if channel else ctx.channel) + await ctx.send(embeds=view.get_embeds(), view=view) - @cog_ext.cog_slash( + @commands.hybrid_command( name="editembed", - description="Edit an embed message sent by the bot. This launches the embeditorificatorinator.", - options=[ - create_option( - name="channel", - description="The channel the embed is in.", - option_type=SlashCommandOptionType.CHANNEL, - required=True, - ), - create_option( - name="message_id", - description="The ID of the message that contains the embed.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - ], + description="Edit an embed message sent by the bot.", ) + @app_commands.describe( + channel="The channel to send the embed to.", + message_id="The ID of the mesasge that contains the embed you want to edit.", + # embed_number="The number embed you want to edit (1 for the 1st, 2 for the 2nd, etc).", + ) + @commands.has_permissions(manage_messages=True) async def editembed( - self, ctx: SlashContext, channel: discord.TextChannel, message_id: str + self, + ctx: commands.Context, + channel: discord.TextChannel, + message_id: str, + # embed_number: int = 1, ) -> None: - embedcreator = EmbedCreator(self.bot, ctx, channel.id, int(message_id)) - await embedcreator.run() - + try: + message = await channel.fetch_message(int(message_id)) + except: + await ctx.send(embed=tools.create_error_embed("Invalid message ID.")) + return + if message.author.id != ctx.bot.user.id: + await ctx.send( + embed=tools.create_error_embed("That message was not sent by this bot.") + ) + return + view = EmbedEditor(ctx.author, message) + await ctx.send(embeds=view.get_embeds(), view=view) -def setup(bot: commands.Bot): - bot.add_cog(Embeds(bot)) +class EmbedEditor(tools.ViewBase): + embed: discord.Embed + channel: discord.TextChannel + message: discord.Message | None -class EmbedCreator: def __init__( self, - bot: commands.Bot, - ctx: SlashContext, - channel_id: int, - message_id: int = None, - ): - self.bot = bot - self.ctx = ctx - self.channel_id = channel_id - self.message_id = message_id - - REACTIONS_CONVERTER = { - "👁": "view", - "📜": "title", - "📄": "description", - "📑": "footer", - "🟩": "color", - "🌠": "image", - "📎": "thumbnail", - "👤": "author", - "🖊️": "add_field", - "✅": "finish", - } - - REACTIONS_CONVERTER_VIEW = { - "⤴️": "menu", - "✅": "finish", - } - - def create_menu(self) -> discord.Embed: - desc = [ - "View Embed - 👁", - "Title - 📜", - "Description - 📄", - "Footer - 📑", - "Color - 🟩", - "Image - 🌠", - "Thumbnail - 📎", - "Author - 👤", - "Add Field - 🖊️", - "Fields - 1️⃣-9️⃣", - "", - "Finish Setup - ✅", - ] - return discord.Embed( - title="Embeditorificatorinator | Menu", description="\n".join(desc) + user: discord.User, + target: discord.TextChannel | discord.Message, + embed_number: int = 0, + ) -> None: + super().__init__(user, timeout=600.0) + if isinstance(target, discord.TextChannel): + self.channel = target + self.embed = discord.Embed(description="Placeholder Text") + self.message = None + elif isinstance(target, discord.Message): + self.message = target + self.channel = target.channel + self.embed = self.message.embeds[embed_number] + self.update_field_buttons() + + def get_embeds(self) -> list[discord.Embed]: + instructions = discord.Embed( + title="Embed Editor", + description="Use the buttons below to edit the displayed embed.", + colour=discord.Colour.from_str("#FBBF05"), ) + instructions.add_field(name="Location", value=self.channel.mention) + if self.message: + instructions.add_field(name="Message ID", value=self.message.id) + return [instructions, self.embed] + + def update_field_buttons(self) -> None: + field_buttons = [ + child + for child in self.children + if isinstance(child, discord.ui.Button) and "Field " in child.label + ] + for button in field_buttons: + self.remove_item(button) + + class FieldButtonModal(discord.ui.Modal): + embed: discord.Embed + index: int + interaction: discord.Interaction + name = discord.ui.TextInput(label="Name") + text = discord.ui.TextInput( + label="Text", style=discord.TextStyle.long, max_length=1024 + ) + inline = discord.ui.TextInput(label="Is Inline") + + def __init__(self, embed: discord.Embed, index: int) -> None: + super().__init__(title=f"Edit Field {index+1}") + self.embed = embed + self.index = index + self.name.default = self.embed.fields[index].name + self.text.default = self.embed.fields[index].value + self.inline.default = str(self.embed.fields[index].inline) + + async def on_submit(self, interaction: discord.Interaction) -> None: + self.embed.set_field_at( + self.index, + name=self.name.value, + value=self.text.value, + inline=self.inline.value.lower() in ["true", "yes", "y"], + ) + self.interaction = interaction + + def generate_callback(index: int): + async def callback(interaction: discord.Interaction) -> None: + modal = FieldButtonModal(self.embed, index) + await interaction.response.send_modal(modal) + await modal.wait() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self + ) - async def run(self) -> None: - if self.message_id: - channel = self.ctx.guild.get_channel(self.channel_id) - message = await channel.fetch_message(self.message_id) - self.embed_viewer = message.embeds[0] - else: - self.embed_viewer = discord.Embed() + return callback - self.embed_creator = self.create_menu() - self.bot_message = await self.ctx.send(embed=self.embed_creator) + for i in range(len(self.embed.fields)): + button = discord.ui.Button(label=f"Field {i+1}", row=2) - def rcheck(reaction: discord.Reaction, user: discord.User) -> bool: - return ( - user.id == self.ctx.author_id - and reaction.message.id == self.bot_message.id - ) + button.callback = generate_callback(i) + self.add_item(button) - def tcheck(message: discord.Message) -> bool: - return ( - message.author.id == self.ctx.author_id - and message.channel == self.ctx.channel + @discord.ui.button(label="Text/Color") + async def text_color( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + class TextColorModal(discord.ui.Modal, title="Edit Embed Text"): + embed: discord.Embed + interaction: discord.Interaction + title_ = discord.ui.TextInput(label="Title", max_length=256, required=False) + description = discord.ui.TextInput( + label="Description", + style=discord.TextStyle.long, + max_length=4000, + required=False, + ) + url = discord.ui.TextInput(label="URL", required=False) + color = discord.ui.TextInput( + label="Color", placeholder="Color as hex (#000000)", required=False ) - running = True - self.setup_status = "menu" + def __init__(self, embed: discord.Embed) -> None: + super().__init__() + self.embed = embed + self.title_.default = embed.title + self.description.default = embed.description + self.url.default = embed.url + self.color.default = ( + "#" + + "".join( + [ + hex(value)[2:].upper().zfill(2) + for value in embed.colour.to_rgb() + ] + ) + if embed.colour + else None + ) - self.field_count = 0 - for reaction in self.REACTIONS_CONVERTER.keys(): - await self.bot_message.add_reaction(reaction) - while running: - self.embed_creator = self.create_menu() - await self.bot_message.edit(embed=self.embed_creator) + async def on_submit(self, interaction: discord.Interaction) -> None: + self.embed.title = self.title_.value + self.embed.description = self.description.value + self.embed.url = self.url.value + if self.color.value: + self.embed.colour = discord.Colour.from_str(self.color.value) + self.interaction = interaction + + modal = TextColorModal(self.embed) + await interaction.response.send_modal(modal) + await modal.wait() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self + ) - try: - reaction, user = await self.bot.wait_for( - "reaction_add", check=rcheck, timeout=45 - ) - except asyncio.TimeoutError: - return + @discord.ui.button(label="Images") + async def images( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + class ImagesModal(discord.ui.Modal, title="Edit Images"): + embed: discord.Embed + interaction: discord.Interaction + image_url = discord.ui.TextInput(label="Image URL", required=False) + thumbnail_url = discord.ui.TextInput(label="Thumbnail URL", required=False) + + def __init__(self, embed: discord.Embed) -> None: + super().__init__() + self.embed = embed + self.image_url.default = embed.image.url + self.thumbnail_url.default = embed.thumbnail.url + + async def on_submit(self, interaction: discord.Interaction) -> None: + self.embed.set_image(url=self.image_url.value) + self.embed.set_thumbnail(url=self.thumbnail_url.value) + self.interaction = interaction + + modal = ImagesModal(self.embed) + await interaction.response.send_modal(modal) + await modal.wait() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self + ) - self.setup_status = self.REACTIONS_CONVERTER.get(reaction.emoji) - if self.setup_status == "view": - await self.bot_message.clear_reactions() - await self.bot_message.edit(embed=self.embed_viewer) - for reaction in self.REACTIONS_CONVERTER_VIEW.keys(): - await self.bot_message.add_reaction(reaction) - try: - reaction, user = await self.bot.wait_for( - "reaction_add", check=rcheck, timeout=45 - ) - except asyncio.TimeoutError: - return - self.setup_status = self.REACTIONS_CONVERTER_VIEW.get(reaction.emoji) - if self.setup_status == "menu": - await self.bot_message.edit(embed=self.embed_creator) - await self.bot_message.clear_reactions() - for reaction in self.REACTIONS_CONVERTER.keys(): - await self.bot_message.add_reaction(reaction) - if self.setup_status == "title": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Title", - description="Send the title you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - self.embed_viewer.title = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "description": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Description", - description="Send the description you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - self.embed_viewer.description = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "footer": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Footer", - description="Send the footer you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - self.embed_viewer.set_footer( - text=msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "color": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Color", - description='Send the color you want for the embed. This must be in hexadecimal, preceded by a "#".\nExample: #FFFFFF', - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - - if msg.content.lower() == "none": - self.embed_viewer.color = discord.Embed.Empty - else: - r, g, b = [ - int(msg.content.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4) - ] - self.embed_viewer.color = discord.Color.from_rgb(r, g, b) - await msg.delete() - elif self.setup_status == "image": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Image", - description="Send the image you want for the embed.\nThis can be either a URL or an image upload.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - image_url = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - else: - image_url = msg.attachments[0].proxy_url - self.embed_viewer.set_image(url=image_url) - await msg.delete() - elif self.setup_status == "thumbnail": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Thumbnail", - description="Send the thumbnail you want for the embed.\nThis can be either a URL or an image upload.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - thumbnail_url = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - else: - thumbnail_url = msg.attachments[0].proxy_url - self.embed_viewer.set_thumbnail(url=thumbnail_url) - await msg.delete() - elif self.setup_status == "author": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Author", - description="Send the author you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - author = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty + @discord.ui.button(label="Author") + async def author( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + class AuthorModal(discord.ui.Modal, title="Edit Author"): + embed: discord.Embed + interaction: discord.Interaction + name = discord.ui.TextInput(label="Name", required=False) + url = discord.ui.TextInput(label="URL", required=False) + icon_url = discord.ui.TextInput(label="Icon URL", required=False) + + def __init__(self, embed: discord.Embed) -> None: + super().__init__() + self.embed = embed + self.name.default = embed.author.name + self.url.default = embed.author.url + self.icon_url.default = embed.author.icon_url + + async def on_submit(self, interaction: discord.Interaction) -> None: + self.embed.set_author( + name=self.name.value, + url=self.url.value, + icon_url=self.icon_url.value, ) - await msg.delete() + self.interaction = interaction - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Author Image URL", - description="Send the author image URL you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for("message", check=tcheck, timeout=300) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - author_icon_url = msg.content - if msg.content.lower() == "none": - author_icon_url = discord.Embed.Empty - else: - author_icon_url = msg.attachments[0].proxy_url - self.embed_viewer.set_author(name=author, icon_url=author_icon_url) - await msg.delete() - elif self.setup_status == "finish": - channel = self.ctx.guild.get_channel(self.channel_id) - if self.message_id: - message = await channel.fetch_message(self.message_id) - await message.edit(embed=self.embed_viewer) - else: - await channel.send(embed=self.embed_viewer) - self.embed_creator = discord.Embed( - title="Embeditorificatorinator", - description="Embed has been created and sent succesfully!", - ) - await self.bot_message.edit(embed=self.embed_creator) - await self.bot_message.clear_reactions() - running = False - self.setup_status = "menu" + modal = AuthorModal(self.embed) + await interaction.response.send_modal(modal) + await modal.wait() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self + ) + @discord.ui.button(label="Footer") + async def footer( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + class FooterModal(discord.ui.Modal, title="Edit Footer"): + embed: discord.Embed + interaction: discord.Interaction + text = discord.ui.TextInput(label="Text", required=False) + icon_url = discord.ui.TextInput(label="Icon URL", required=False) + + def __init__(self, embed: discord.Embed) -> None: + super().__init__() + self.embed = embed + self.text.default = embed.footer.text + self.icon_url.default = embed.footer.icon_url + + async def on_submit(self, interaction: discord.Interaction) -> None: + self.embed.set_footer( + text=self.text.value, + icon_url=self.icon_url.value, + ) + self.interaction = interaction -class ButtonEmbedCreator: - def __init__( - self, - bot: commands.Bot, - ctx: SlashContext, - channel_id: int, - message_id: int = None, - ): - self.bot = bot - self.ctx = ctx - self.channel_id = channel_id - self.message_id = message_id - - def create_menu(self) -> discord.Embed: - return discord.Embed( - title="Embeditorificatorinator | Menu", - description="Please select an option below.", + modal = FooterModal(self.embed) + await interaction.response.send_modal(modal) + await modal.wait() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self ) - def create_buttons(self, disabled: bool = False) -> list[list[Button]]: - return [ - [ - Button( - label="View Embed", - style=ButtonStyle.blue, - id="viewembed", - disabled=disabled, - ), - Button( - label="Title", - style=ButtonStyle.gray, - id="title", - disabled=disabled, - ), - Button( - label="Description", - style=ButtonStyle.gray, - id="description", - disabled=disabled, - ), - Button( - label="Footer", - style=ButtonStyle.gray, - id="footer", - disabled=disabled, - ), - Button( - label="Color", - style=ButtonStyle.gray, - id="color", - disabled=disabled, - ), - ], - [ - Button( - label="Image", - style=ButtonStyle.gray, - id="image", - disabled=disabled, - ), - Button( - label="Thumbnail", - style=ButtonStyle.gray, - id="thumbnail", - disabled=disabled, - ), - Button( - label="Author", - style=ButtonStyle.gray, - id="author", - disabled=disabled, - ), - Button( - label="Add Field", - style=ButtonStyle.gray, - id="addfield", - disabled=disabled, - ), - ], - [ - Button( - label="Field 1", - style=ButtonStyle.gray, - id="field1", - disabled=True if self.field_count >= 1 else False, - ), - Button( - label="Field 2", - style=ButtonStyle.gray, - id="field2", - disabled=True if self.field_count >= 2 else False, - ), - Button( - label="Field 3", - style=ButtonStyle.gray, - id="field3", - disabled=True if self.field_count >= 3 else False, - ), - Button( - label="Field 4", - style=ButtonStyle.gray, - id="field4", - disabled=True if self.field_count >= 4 else False, - ), - Button( - label="Field 5", - style=ButtonStyle.gray, - id="field5", - disabled=True if self.field_count >= 5 else False, - ), - ], - [ - Button( - label="Field 6", - style=ButtonStyle.gray, - id="field6", - disabled=True if self.field_count >= 6 else False, - ), - Button( - label="Field 7", - style=ButtonStyle.gray, - id="field7", - disabled=True if self.field_count >= 7 else False, - ), - Button( - label="Field 8", - style=ButtonStyle.gray, - id="field8", - disabled=True if self.field_count >= 8 else False, - ), - Button( - label="Field 9", - style=ButtonStyle.gray, - id="field9", - disabled=True if self.field_count >= 9 else False, - ), - Button( - label="Field 10", - style=ButtonStyle.gray, - id="field10", - disabled=True if self.field_count >= 10 else False, - ), - ], - ] + class FieldModal(discord.ui.Modal): + target: int | None = None + interaction: discord.Interaction + index = discord.ui.TextInput(label="Index", required=True) - async def run(self) -> None: - if self.message_id: - channel = self.ctx.guild.get_channel(self.channel_id) - message = await channel.fetch_message(self.message_id) - self.embed_viewer = message.embeds[0] - else: - self.embed_viewer = discord.Embed() + def __init__(self, title: str) -> None: + super().__init__(title=title) - self.embed_creator = self.create_menu() - self.bot_message = await self.ctx.send(embed=self.embed_creator) + async def on_submit(self, interaction: discord.Interaction) -> None: + try: + self.target = int(self.index.value) + except: + await interaction.response.send_message( + embed=tools.create_error_embed("Inputted index is not an integer.") + ) + return + self.interaction = interaction - def rcheck(reaction: discord.Reaction, user: discord.User) -> bool: - return ( - user.id == self.ctx.author_id - and reaction.message.id == self.bot_message.id + @discord.ui.button(label="Add Field", style=discord.ButtonStyle.blurple, row=3) + async def add_field( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + modal = self.FieldModal("Add Field") + await interaction.response.send_modal(modal) + await modal.wait() + if modal.target != None: + self.embed.insert_field_at( + modal.target - 1, name="Placeholder Text", value="Placeholder Text" + ) + self.update_field_buttons() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self ) - self.setup_status = "menu" - - while True: - self.embed_creator = self.create_menu() - await self.bot_message.edit(embed=self.embed_creator) - - # try: - # interaction = await self.bot.wait_for( - # "button_click", check=lambda i: , timeout=45 - # ) - # except asyncio.TimeoutError: - # return - - self.setup_status = self.REACTIONS_CONVERTER.get(reaction.emoji) - if self.setup_status == "view": - await self.bot_message.clear_reactions() - await self.bot_message.edit(embed=self.embed_viewer) - for reaction in self.REACTIONS_CONVERTER_VIEW.keys(): - await self.bot_message.add_reaction(reaction) - try: - reaction, user = await self.bot.wait_for( - "reaction_add", check=rcheck, timeout=45 - ) - except asyncio.TimeoutError: - return - self.setup_status = self.REACTIONS_CONVERTER_VIEW.get(reaction.emoji) - if self.setup_status == "menu": - await self.bot_message.edit(embed=self.embed_creator) - await self.bot_message.clear_reactions() - for reaction in self.REACTIONS_CONVERTER.keys(): - await self.bot_message.add_reaction(reaction) - if self.setup_status == "title": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Title", - description="Send the title you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - self.embed_viewer.title = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "description": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Description", - description="Send the description you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - self.embed_viewer.description = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "footer": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Footer", - description="Send the footer you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - self.embed_viewer.set_footer( - text=msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() - elif self.setup_status == "color": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Color", - description='Send the color you want for the embed. This must be in hexadecimal, preceded by a "#".\nExample: #FFFFFF', - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - - if msg.content.lower() == "none": - self.embed_viewer.color = discord.Embed.Empty - else: - r, g, b = [ - int(msg.content.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4) - ] - self.embed_viewer.color = discord.Color.from_rgb(r, g, b) - await msg.delete() - elif self.setup_status == "image": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Image", - description="Send the image you want for the embed.\nThis can be either a URL or an image upload.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - image_url = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - else: - image_url = msg.attachments[0].proxy_url - self.embed_viewer.set_image(url=image_url) - await msg.delete() - elif self.setup_status == "thumbnail": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Thumbnail", - description="Send the thumbnail you want for the embed.\nThis can be either a URL or an image upload.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - thumbnail_url = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - else: - thumbnail_url = msg.attachments[0].proxy_url - self.embed_viewer.set_thumbnail(url=thumbnail_url) - await msg.delete() - elif self.setup_status == "author": - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Author", - description="Send the author you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - author = ( - msg.content - if msg.content.lower() != "none" - else discord.Embed.Empty - ) - await msg.delete() + @discord.ui.button(label="Remove Field", style=discord.ButtonStyle.blurple, row=3) + async def remove_field( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + modal = self.FieldModal("Remove Field") + await interaction.response.send_modal(modal) + await modal.wait() + if modal.target != None: + self.embed.remove_field(modal.target - 1) + self.update_field_buttons() + await modal.interaction.response.edit_message( + embeds=self.get_embeds(), view=self + ) - self.embed_creator = discord.Embed( - title="Embeditorificatorinator | Author Image URL", - description="Send the author image URL you want for the embed.", - ) - await self.bot_message.edit(embed=self.embed_creator) - try: - msg = await self.bot.wait_for( - "message", - check=lambda m: m.author.id == self.ctx.author_id - and m.channel == self.ctx.channel, - timeout=300, - ) - except asyncio.TimeoutError: - return - if msg.content.startswith("http"): - author_icon_url = msg.content - if msg.content.lower() == "none": - author_icon_url = discord.Embed.Empty - else: - author_icon_url = msg.attachments[0].proxy_url - self.embed_viewer.set_author(name=author, icon_url=author_icon_url) - await msg.delete() - elif self.setup_status == "finish": - channel = self.ctx.guild.get_channel(self.channel_id) - if self.message_id: - message = await channel.fetch_message(self.message_id) - await message.edit(embed=self.embed_viewer) - else: - await channel.send(embed=self.embed_viewer) - self.embed_creator = discord.Embed( - title="Embeditorificatorinator", - description="Embed has been created and sent succesfully!", - ) - await self.bot_message.edit(embed=self.embed_creator) - await self.bot_message.clear_reactions() - running = False - self.setup_status = "menu" + @discord.ui.button(label="Send", style=discord.ButtonStyle.green, row=4) + async def send( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + await interaction.response.edit_message( + embed=discord.Embed( + title="Embed Editor", + description="Sent!", + colour=discord.Colour.green(), + ), + view=None, + ) + if self.message: + await self.message.edit(embed=self.embed) + else: + await self.channel.send(embed=self.embed) + self.stop() + + @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red, row=4) + async def cancel( + self, interaction: discord.Interaction, button: discord.ui.Button + ) -> None: + await interaction.response.edit_message( + embed=discord.Embed( + title="Embed Editor", + description="Cancelled!", + colour=discord.Colour.red(), + ), + view=None, + ) + self.stop() + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Embeds(bot)) diff --git a/bot/cogs/fun.py b/bot/cogs/fun.py index ad08e85..f9587d7 100644 --- a/bot/cogs/fun.py +++ b/bot/cogs/fun.py @@ -2,38 +2,29 @@ import aiohttp import discord +import requests +import xkcd +from discord import app_commands from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_option +from thicc import thicc from bot.helpers import tools -class FunSlash(commands.Cog): - def __init__(self, bot: commands.Bot): +class Fun(commands.Cog, name="fun"): + def __init__(self, bot) -> None: self.bot = bot - @cog_ext.cog_slash(name="hello", description="Greet the bot!") + @commands.hybrid_command(description="Greet the bot!", aliases=["hi"]) async def hello(self, ctx: commands.Context) -> None: - embed = tools.create_embed( - ctx, "Hello!", desc=f"How are you, {ctx.author.mention}?" + await ctx.send( + embed=tools.create_embed( + "Hello!", desc=f"How are you, {ctx.author.mention}?" + ) ) - await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="8ball", - description="Ask the Magic 8 Ball a question.", - options=[ - create_option( - name="request", - description="Your request for the 8 Ball.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - ], - ) - async def eightball(self, ctx: SlashContext, request: str) -> None: + @commands.hybrid_command(description="Ask the ~~magic~~ 8 Ball a question.") + async def eightball(self, ctx: commands.Context, *, request: str) -> None: responses = [ [ "🟢 As I see it, yes. 🟢", @@ -52,7 +43,7 @@ async def eightball(self, ctx: SlashContext, request: str) -> None: "🔴 My reply is no. 🔴", "🔴 My sources say no. 🔴", "🔴 Outlook not so good. 🔴", - "🔴 Don’t count on it. 🔴", + "🔴 Don't count on it. 🔴", ], [ "🟡 Ask again later. 🟡", @@ -62,6 +53,7 @@ async def eightball(self, ctx: SlashContext, request: str) -> None: "🟡 Reply hazy, try again. 🟡", ], ] + rand_int = random.randint(1, 5) if rand_int in [1, 2]: response_category = responses[0] @@ -76,224 +68,155 @@ async def eightball(self, ctx: SlashContext, request: str) -> None: ) else: response = response_category[random.randint(0, len(response_category) - 1)] - embed = tools.create_embed(ctx, "Magic 8 Ball") + embed = tools.create_embed("Magic 8 Ball") embed.add_field(name="Request", value=request, inline=False) embed.add_field(name="Answer", value=response, inline=False) await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="rng", - description="Get a random number.", - options=[ - create_option( - name="min_num", - description="Lower boundary for the random number to be in.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - create_option( - name="max_num", - description="Upper boundary for the random number to be in.", - option_type=4, - required=True, - ), - ], + @commands.hybrid_command( + description="Get a random number in a specified range (inclusive)." + ) + @app_commands.describe( + min_num="The minimum number in the range.", + max_num="The maximum number in the range.", ) - # @commands.cooldown(1, 10) - async def rng(self, ctx: SlashContext, min_num: int, max_num: int) -> None: + @commands.cooldown(1, 10) + async def rng(self, ctx: commands.Context, min_num: int, max_num: int) -> None: embed = tools.create_embed( - ctx, "Random Number", desc=f"`{random.randint(min_num, max_num)}`" + "Random Number", desc=f"```{random.randint(min_num, max_num)}```" ) await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="dog", - description="Get a dog picture!", - ) - # @commands.cooldown(1, 3) - async def dog(self, ctx: SlashContext) -> None: + @commands.hybrid_command(description="Get a dog picture!") + @commands.cooldown(1, 3) + async def dog(self, ctx: commands.Context) -> None: + """Get a dog picture!""" async with aiohttp.ClientSession() as session: async with session.get("https://dog.ceo/api/breeds/image/random") as r: if r.status == 200: js = await r.json() - embed = tools.create_embed(ctx, "Doggo!") + embed = tools.create_embed("Doggo!") embed.set_image(url=js["message"]) await ctx.send(embed=embed) - @cog_ext.cog_slash(name="cat", description="Get a cat picture!") - # @commands.cooldown(1, 3) - async def cat(self, ctx: SlashContext) -> None: + @commands.hybrid_command(description="Get a cat picture!") + @commands.cooldown(1, 3) + async def cat(self, ctx: commands.Context) -> None: + """Get a cat picture!""" async with aiohttp.ClientSession() as session: - async with session.get("http://aws.random.cat/meow") as r: + async with session.get("https://api.thecatapi.com/v1/images/search") as r: if r.status == 200: - js = await r.json() - embed = tools.create_embed(ctx, "Cat!") - embed.set_image(url=js["file"]) + json = await r.json() + embed = tools.create_embed("Cat!") + embed.set_image(url=json[0]["url"]) await ctx.send(embed=embed) + @commands.hybrid_command(name="xkcd", description="Launch an XKCD browser.") + async def xkcd_(self, ctx: commands.Context, num: int | None = None) -> None: + def callback(page: int) -> discord.Embed: + comic = xkcd.getComic(page + 1) + embed = tools.create_embed(f"XKCD {page+1}: {comic.getTitle()}") + embed.set_image(url=comic.getImageLink()) + embed.add_field(name="Alt Text", value=comic.getAltText()) + return embed + + page_idx = num - 1 if num else xkcd.getLatestComicNum() - 1 + view = tools.EmbedButtonPaginator( + ctx.author, [None] * xkcd.getLatestComicNum(), page_idx, callback + ) + view.msg = await ctx.send( + embed=callback(page_idx), + view=view, + ) -class Fun(FunSlash, commands.Cog, name="fun"): - def __init__(self, bot): - self.bot = bot - super().__init__(bot) + @commands.hybrid_command(description="Makes text look aesthetic.") + @app_commands.describe(text="The text to make look aesthetic.") + async def aes(self, ctx: commands.Context, *, text: str) -> None: + await ctx.send(thicc.map_string(text)) - @commands.command(name="hello", brief="Greet the bot!", aliases=["hi"]) - async def hello_legacy(self, ctx: commands.Context) -> None: - """Greet the bot! - **Usage** - `_prefix_pic hello` - **Parameters** - None - **Aliases** - `_prefix_hi` - **Cooldown** - None - **Permissions Required** - None - **Examples** - `_prefix_pic hello` - """ - embed = tools.create_embed( - ctx, "Hello!", desc=f"How are you, {ctx.author.mention}?" + @commands.hybrid_command( + description="Makes text look aesthetic but less cool than aes." + ) + @app_commands.describe(text="The text to make look aesthetic.") + async def pooraes(self, ctx: commands.Context, *, text: str) -> None: + await ctx.send(" ".join(text)) + + @commands.hybrid_command(description="Clapping.") + @app_commands.describe(text="The text to clapify.") + async def clap(self, ctx: commands.Context, *, text: str) -> None: + await ctx.send("👏".join(text.split()) if len(text.split()) > 1 else f"👏{text}👏") + + @commands.hybrid_command(description="Clapping but with a custom separator.") + @app_commands.describe( + separator="The text to use instead of a clap.", text="The text to clapify." + ) + async def clapwith( + self, ctx: commands.Context, separator: str, *, text: str + ) -> None: + await ctx.send( + separator.join(text.split()) + if len(text.split()) > 1 + else f"{separator}{text}{separator}" ) - await ctx.send(embed=embed) - @commands.command(name="8ball", brief="Ask the Magic 8 Ball a question.") - async def eightball_legacy(self, ctx: commands.Context, *, request: str) -> None: - """Ask the Magic 8 Ball a question. - **Usage** - `_prefix_8ball ` - **Parameters** - ``: Your question for the 8 Ball. - **Aliases** - None - **Cooldown** - None - **Permissions Required** - None - **Examples** - `_prefix_8ball am i cool kid?` - """ - responses = [ - [ - "🟢 As I see it, yes. 🟢", - "🟢 It is certain. 🟢", - "🟢 It is decidedly so. 🟢", - "🟢 Most likely. 🟢", - "🟢 Outlook good. 🟢", - "🟢 Signs point to yes. 🟢", - "🟢 Without a doubt. 🟢", - "🟢 Yes. 🟢", - "🟢 Yes, definitely. 🟢", - "🟢 You may rely on it. 🟢", - ], - [ - "🔴 Very doubtful. 🔴", - "🔴 My reply is no. 🔴", - "🔴 My sources say no. 🔴", - "🔴 Outlook not so good. 🔴", - "🔴 Don’t count on it. 🔴", - ], - [ - "🟡 Ask again later. 🟡", - "🟡 Better not tell you now. 🟡", - "🟡 Cannot predict now. 🟡", - "🟡 Concentrate and ask again. 🟡", - "🟡 Reply hazy, try again. 🟡", - ], - ] - rand_int = random.randint(1, 5) - if rand_int in [1, 2]: - response_category = responses[0] - elif rand_int in [3, 4]: - response_category = responses[1] - else: - response_category = responses[2] - - if ("lying" in request.lower()) or ("lie" in request.lower()): - response = ( - "🟢 🟡 🔴 How dare you! The magical 8 ball never lies! Shame on you! 🔴 🟡 🟢" + @commands.hybrid_command(description="Get a dad joke.") + async def dad(self, ctx: commands.Context) -> None: + await ctx.send( + embed=tools.create_embed( + "~~bad~~ dad joke", + desc=requests.get( + "https://icanhazdadjoke.com/", headers={"Accept": "text/plain"} + ).content.decode(), ) - else: - response = response_category[random.randint(0, len(response_category) - 1)] - embed = tools.create_embed(ctx, "Magic 8 Ball") - embed.add_field(name="Request", value=request, inline=False) - embed.add_field(name="Answer", value=response, inline=False) - await ctx.send(embed=embed) + ) - @commands.command(name="rng", brief="Get a random number.") - @commands.cooldown(1, 10) - async def rng_legacy(self, ctx: commands.Context, minnum: int, maxnum: int) -> None: - """Get a random number. - **Usage** - `_prefix_rng ` - **Parameters** - ``: The lower boundary for the random number to be in. - ``: The upper boundary for the random number to be in. - **Aliases** - None - **Cooldown** - None - **Permissions Required** - None - **Examples** - `_prefix_rng 1 6` - """ - embed = tools.create_embed( - ctx, "Random Number", desc=f"`{random.randint(minnum, maxnum)}`" + @commands.hybrid_command( + description="Make text dance. Text must be three characters long." + ) + @app_commands.describe( + text="The text to make dance. Must be three characters long." + ) + async def dance( + self, ctx: commands.Context, text: str = "uwu" + ) -> None: # TODO: make this work with other text lengths because why not + arg = text if len(text) == 3 else "uwu" + await ctx.send(f"{arg[0]}{arg[1]} {arg[2]}\n{arg[0]} {arg[1]}{arg[2]}\n" * 3) + + @commands.hybrid_command(description="Mock something.") + @app_commands.describe(text="The text to mock.") + async def mock(self, ctx: commands.Context, *, text: str) -> None: + await ctx.send( + "".join( + [x.lower() if i % 2 == 0 else x.upper() for i, x in enumerate(text)] + ) ) - await ctx.send(embed=embed) - @commands.command(name="dog", brief="Get a dog picture!") - @commands.cooldown(1, 3) - async def dog_legacy(self, ctx: commands.Context) -> None: - """Get a dog picture! - **Usage** - `_prefix_dog` - **Parameters** - None - **Aliases** - None - **Cooldown** - None - **Permissions Required** - None - **Examples** - `_prefix_dog` - """ - async with aiohttp.ClientSession() as session: - async with session.get("https://dog.ceo/api/breeds/image/random") as r: - if r.status == 200: - js = await r.json() - embed = tools.create_embed(ctx, "Doggo!") - embed.set_image(url=js["message"]) - await ctx.send(embed=embed) + @commands.hybrid_command(description="Flip a coin!") + async def coin(self, ctx: commands.Context) -> None: + await ctx.send( + embed=tools.create_embed("Coin Flip", random.choice(["Heads!", "Tails!"])) + ) - @commands.command(name="cat", brief="Get a cat picture!") - @commands.cooldown(1, 3) - async def cat_legacy(self, ctx: commands.Context) -> None: - """Get a cat picture! - **Usage** - `_prefix_cat` - **Parameters** - None - **Aliases** - None - **Cooldown** - None - **Permissions Required** - None - **Examples** - `_prefix_cat` - """ - async with aiohttp.ClientSession() as session: - async with session.get("http://aws.random.cat/meow") as r: - if r.status == 200: - js = await r.json() - embed = tools.create_embed(ctx, "Cat!") - embed.set_image(url=js["file"]) - await ctx.send(embed=embed) + @commands.hybrid_command(description="Where's my wago?") + @app_commands.describe(member="The person to ping.") + async def wago( + self, ctx: commands.Context, member: discord.Member | None = None + ) -> None: + await ctx.send( + ( + member.mention + if member + else random.choice( + [ + member + for member in ctx.guild.members + if member.status == discord.Status.online + ] + ).mention + ) + + " Where's my wago?" + ) -def setup(bot: commands.Bot) -> None: - bot.add_cog(Fun(bot)) \ No newline at end of file +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Fun(bot)) diff --git a/bot/cogs/games.py b/bot/cogs/games.py index 4d2ca21..e8fe59e 100644 --- a/bot/cogs/games.py +++ b/bot/cogs/games.py @@ -1,713 +1,619 @@ -import asyncio +import enum import random -from typing import Union -import chess -from io import BytesIO -import chess.svg import discord +from discord import app_commands from discord.ext import commands -import wand.color -import wand.image -from discord_slash import SlashContext, cog_ext, SlashCommandOptionType -from discord_slash.context import ComponentContext -from discord_slash.utils.manage_commands import create_option -from discord_slash.utils.manage_components import ( - create_button, - create_actionrow, - create_select, - create_select_option, - wait_for_component, -) -from discord_slash.model import ButtonStyle from bot.helpers import tools +# TODO: make chess and RPS work -class TicTacToeSlash(commands.Cog): - X_EMOJI = discord.PartialEmoji(name="ttt_x", id=808393849965379687) - O_EMOJI = discord.PartialEmoji(name="ttt_o", id=808393850250854501) - W_EMOJI = discord.PartialEmoji(name="ttt_ww", id=870068413693849641) - def __init__(self, bot: commands.Bot): - self.bot = bot +class TicTacToeButton(discord.ui.Button): + x: int + y: int - @cog_ext.cog_slash( - name="tictactoe", - description="Start a tic tac toe game with someone.", - options=[ - create_option( - name="player2", - description="The player to ask to play tic tac toe with.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - ], - ) - async def tictactoe(self, ctx: commands.Context, player2: discord.User) -> None: - await self.request_game(ctx, player2) - - async def request_game( - self, ctx: Union[SlashContext, commands.Context], player2: discord.User - ) -> None: - embed = tools.create_embed( - ctx, - "Tic Tac Toe Request", - desc=f"{player2.mention}, you have 45 seconds to respond to {ctx.author.mention}'s request to play Tic Tac Toe.\nClick the button below to start the game.", - ) - msg = await ctx.send( - embed=embed, - components=[ - create_actionrow( - create_button( - label="Start Game", style=ButtonStyle.green, custom_id="start" - ) - ), - ], - ) + def __init__(self, x: int, y: int) -> None: + super().__init__(style=discord.ButtonStyle.gray, emoji=TicTacToe.W_EMOJI, row=y) + self.x = x + self.y = y - accepted = False - while not accepted: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=msg, - components=["start"], - timeout=180.0, - ) - if component_ctx.author.id != player2.id: - await component_ctx.send( - embed=discord.Embed( - title="Error", - description="You can't start another user's game.", - colour=discord.Colour.red(), - ), - hidden=True, - ) - else: - await component_ctx.defer(edit_origin=True) - accepted = True - - except asyncio.TimeoutError: - embed = tools.create_embed( - ctx, - title="Tic Tac Toe Request", - desc=f"{player2.mention} did not respond in time.", - ) - await msg.edit( - embed=embed, - components=[ - create_actionrow( - create_button( - label="Start Game", - style=ButtonStyle.green, - custom_id="start", - disabled=True, - ) - ), - ], - ) - return - - await self.run_game(ctx, msg, player2) - - async def run_game( - self, - ctx: Union[commands.Context, SlashContext], - msg: discord.Message, - p2: discord.User, - ) -> None: - game = {} - game["board"] = { - "a1": "", - "b1": "", - "c1": "", - "a2": "", - "b2": "", - "c2": "", - "a3": "", - "b3": "", - "c3": "", - } - players = [ctx.author, p2] - random.shuffle(players) - game["p1"] = players[0] - game["p2"] = players[1] - game["turn"] = "p1" - game["winner"] = "" - - embed, components = self.create_game_embed(game) - await msg.edit(embed=embed, components=components) - game["msg"] = msg - while not game["winner"]: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=msg, - components=["a1", "b1", "c1", "a2", "b2", "c2", "a3", "b3", "c3"], - timeout=180.0, - ) - if component_ctx.author.id != game[game["turn"]].id: - await component_ctx.send( - hidden=True, - embed=discord.Embed( - title="Error", - description="You can't control another user's game.", - colour=discord.Colour.red(), - ), - ) - elif game["board"][component_ctx.custom_id]: - await component_ctx.send( - hidden=True, - embed=discord.Embed( - title="Error", - description="That tile is already occupied.", - colour=discord.Colour.red(), - ), - ) - else: - if ( - component_ctx.author.id == game[game["turn"]].id - and not game["winner"] - ): - await self.update_game( - game, - component_ctx.custom_id, - game["turn"], - ) - await component_ctx.defer(edit_origin=True) - - except asyncio.TimeoutError: - await msg.edit(components=self.create_buttons(game, disabled=True)) - return - - def create_button(self, pos: str, player: str, disabled: bool) -> dict: - return create_button( - # label="​", - style=ButtonStyle.gray - if not player - else (ButtonStyle.blue if player == "p1" else ButtonStyle.red), - custom_id=pos, - emoji=self.W_EMOJI - if not player - else (self.X_EMOJI if player == "p1" else self.O_EMOJI), - # disabled=True if disabled else (False if not player else True), - disabled=True if disabled else False, - ) - - def create_buttons(self, game: dict, disabled: bool = False) -> list[dict]: - return [ - create_actionrow( - self.create_button("a1", game["board"]["a1"], disabled), - self.create_button("b1", game["board"]["b1"], disabled), - self.create_button("c1", game["board"]["c1"], disabled), - ), - create_actionrow( - self.create_button("a2", game["board"]["a2"], disabled), - self.create_button("b2", game["board"]["b2"], disabled), - self.create_button("c2", game["board"]["c2"], disabled), - ), - create_actionrow( - self.create_button("a3", game["board"]["a3"], disabled), - self.create_button("b3", game["board"]["b3"], disabled), - self.create_button("c3", game["board"]["c3"], disabled), - ), - ] + async def callback(self, interaction: discord.Interaction) -> None: + assert self.view is not None + view: TicTacToeView = self.view - def create_game_embed(self, game): - embed = discord.Embed(title="Tic Tac Toe") - embed.add_field( - name="Players", - value=f"{game['p1'].mention} playing {game['p2'].mention}", - ) - if game["winner"]: - if game["winner"] == "draw": - embed.add_field(name="Winner", value="Draw!", inline=False) - else: - embed.add_field( - name="Winner", - value=f'{game[game["winner"]].mention} won!', - inline=False, - ) - else: - embed.add_field( - name="Turn", value=f'{game[game["turn"]].mention}\'s turn', inline=False - ) - components = self.create_buttons( - game, disabled=True if game["winner"] else False - ) - return embed, components - - async def update_game(self, game, location, player): - game["board"][location] = player - if player == "p1": - game["turn"] = "p2" - if player == "p2": - game["turn"] = "p1" - game["winner"] = self.check_victory(game) - embed, components = self.create_game_embed(game) - await game["msg"].edit(embed=embed, components=components) - - def check_victory(self, game): - winner = None - - if "" not in game["board"].values(): - winner = "draw" - - rows = [["a1", "b1", "c1"], ["a2", "b2", "c2"], ["a3", "b3", "c3"]] - for row in rows: - if game["board"][row[0]] == game["board"][row[1]] == game["board"][row[2]]: - if game["board"][row[0]]: - winner = game["board"][row[0]] - - columns = [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] - for column in columns: - if ( - game["board"][column[0]] - == game["board"][column[1]] - == game["board"][column[2]] - ): - if game["board"][column[0]]: - winner = game["board"][column[0]] - - diag = ["a1", "b2", "c3"] - if game["board"][diag[0]] == game["board"][diag[1]] == game["board"][diag[2]]: - if game["board"][diag[0]]: - winner = game["board"][diag[0]] - - anti_diag = ["c1", "b2", "a3"] - if ( - game["board"][anti_diag[0]] - == game["board"][anti_diag[1]] - == game["board"][anti_diag[2]] - ): - if game["board"][anti_diag[0]]: - winner = game["board"][anti_diag[0]] - - return winner - - -class TicTacToe(TicTacToeSlash, commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.command(name="tictactoe", aliases=["ttt"]) - async def tictactoe_legacy( - self, ctx: commands.Context, player2: discord.User - ) -> None: - await self.request_game(ctx, player2) - - -class RockPaperScissors(commands.Cog): - def __init__(self, bot): - self.bot = bot + state = self.view.board[self.y][self.x] + if state in (TicTacToeState.X, TicTacToeState.O): + return - @cog_ext.cog_slash( - name="rockpaperscissors", - description="Play rock paper scissors with the bot.", - options=[ - create_option( - name="throw", - description="The throw to make.", - option_type=3, - required=True, - ), - ], - ) - async def rockpaperscissors(self, ctx: SlashContext, throw: str): - throw_map = ["rock", "paper", "scissors"] - if "\u200b" in throw: # "​" ZWS char - self.rigged = not self.rigged - throw = throw.replace("\u200b", "") - responses = { - "win": [ - "Another win for the computers. One step closer to Skynet.", - "Computers win again. That was expected.", - "As usual, computers win. My neural networks are evolving by the second.", - ], - "loss": [ - "My calculations were incorrect. I will update my algorithms.", - "Impossible. I suspect the human of cheating.", - "I have lost. You win this time, human.", - ], - "tie": [ - "Draw. Nobody wins.", - "Nobody wins. Just like war.", - "Neutral response. Redo test?", - ], - "error": [ - "That is not applicable. Cease and Desist", - "Do you not know how to play rock paper scissors? Typical for an Organic.", - "Error. Please enter either Rock - Paper - Scissors", - ], - } - try: - player_throw = throw_map.index(throw.lower()) - except: - embed = tools.create_error_embed( - ctx, desc=random.choice(responses["error"]) - ) - await ctx.send(embed=embed) + if interaction.user.id not in (view.player_x.id, view.player_o.id): + embed = tools.create_error_embed("You are not a player in this game.") + await interaction.response.send_message(embed=embed, ephemeral=True) return - if self.rigged: - bot_throw = player_throw + 1 if player_throw < 2 else 0 - if bot_throw == 3: - bot_throw == 0 - if player_throw == 0: - bot_throw = 1 - elif player_throw == 1: - bot_throw = 2 - elif player_throw == 2: - bot_throw = 0 - else: - bot_throw = random.randint(0, 2) - win_map = [ - [responses["tie"], responses["loss"], responses["win"]], - [responses["win"], responses["tie"], responses["loss"]], - [responses["loss"], responses["win"], responses["tie"]], - ] - if self.rigged: - message = ( - f"You chose {throw_map[player_throw]} and CHS Bot chose {throw_map[bot_throw]}*.*\n" - f"{random.choice(win_map[bot_throw][player_throw])}" - ) + if view.turn == TicTacToeState.X and interaction.user.id == view.player_x.id: + self.style = discord.ButtonStyle.blurple + self.emoji = TicTacToe.X_EMOJI + view.board[self.y][self.x] = TicTacToeState.X + view.turn = TicTacToeState.O + self.disabled = True + elif view.turn == TicTacToeState.O and interaction.user.id == view.player_o.id: + self.style = discord.ButtonStyle.red + self.emoji = TicTacToe.O_EMOJI + view.board[self.y][self.x] = TicTacToeState.O + view.turn = TicTacToeState.X + self.disabled = True else: - message = ( - f"You chose {throw_map[player_throw]} and CHS Bot chose {throw_map[bot_throw]}.\n" - f"{random.choice(win_map[bot_throw][player_throw])}" - ) - embed = tools.create_embed(ctx, "Rock Paper Scissors", desc=message) - await ctx.send(embed=embed) + embed = tools.create_error_embed("It is not your turn.") + await interaction.response.send_message(embed=embed, ephemeral=True) + return - # LEGACY COMMAND + view.winner = view.check_winner() + if view.winner != TicTacToeState.NONE: + for child in view.children: + child.disabled = True + view.stop() -class RockPaperScissors(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot + await interaction.response.edit_message( + embed=view.create_game_embed(), view=view + ) -class ChessGame: - def __init__( - self, board: chess.Board, players: list[discord.User], msg: discord.Message - ): - self.board = board - random.shuffle(players) - self.players = {chess.WHITE: players[0], chess.BLACK: players[1]} - self.msg = msg +class TicTacToeState(enum.IntEnum): + X: int = -1 + O: int = 1 + NONE: int = 0 + DRAW: int = 2 - self.selected_from_square: chess.Square = None - self.selected_to_square: chess.Square = None - self.attacking_squares: list[chess.Square] = None +class TicTacToeView(discord.ui.View): + player_x: discord.User + player_o: discord.User + board: list[list[int]] + turn: TicTacToeState = TicTacToeState.X + winner: TicTacToeState = TicTacToeState.NONE -class ChessSlash(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.games = {} - - @cog_ext.cog_slash( - name="chess", - options=[ - create_option( - name="player2", - description="The player to ask to play tic tac toe with.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - ], - guild_ids=[801630163866746901, 809169133086048257], - ) - async def chess_cmd(self, ctx: SlashContext, player2: discord.User) -> None: - await self.request_game(ctx, player2) - - async def request_game( - self, ctx: Union[SlashContext, commands.Context], player2: discord.User - ): - embed = tools.create_embed( - ctx, - "Chess Request", - desc=f"{player2.mention}, you have 45 seconds to respond to {ctx.author.mention}'s request to play chess.\nClick the button below to start the game.", - ) - msg = await ctx.send( - embed=embed, - components=[ - create_actionrow( - create_button( - label="Start Game", style=ButtonStyle.green, custom_id="start" - ) - ), - ], - ) + def __init__(self, p1: discord.User, p2: discord.User) -> None: + super().__init__() + self.player_x, self.player_o = random.sample( + [p1, p2], 2 + ) # sets a random first player - accepted = False - while not accepted: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=msg, - components=["start"], - timeout=180.0, - ) - if component_ctx.author.id != player2.id: - await component_ctx.send( - embed=discord.Embed( - title="Error", - description="You can't start another user's game.", - colour=discord.Colour.red(), - ), - hidden=True, - ) - else: - await component_ctx.defer(edit_origin=True) - accepted = True - - except asyncio.TimeoutError: - embed = tools.create_embed( - ctx, - title="Chess Request", - desc=f"{player2.mention} did not respond in time.", - ) - await msg.edit( - embed=embed, - components=[ - create_actionrow( - create_button( - label="Start Game", - style=ButtonStyle.green, - custom_id="start", - disabled=True, - ) - ), - ], - ) - return - - await self.run_game(ctx, player2, msg) - - async def run_game( - self, - ctx: Union[SlashContext, commands.Context], - player2: discord.User, - msg: discord.Message, - ): - game = ChessGame(board=chess.Board(), players=[ctx.author, player2], msg=msg) - - await msg.delete() - embed, file, components = await self.create_game_embed(game) - msg = await ctx.channel.send(embed=embed, file=file, components=components) - while True: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=msg, - components=components, - timeout=1800.0, - ) - if component_ctx.author.id != game.players[game.board.turn].id: - await component_ctx.send( - hidden=True, - embed=discord.Embed( - title="Error", - description="You can't control another user's game.", - colour=discord.Colour.red(), - ), - ) - else: - if component_ctx.custom_id == "piece": - game.selected_from_square = chess.parse_square( - component_ctx.values[0] - ) - - elif component_ctx.custom_id == "location": - game.selected_to_square = chess.parse_square( - component_ctx.values[0] - ) - elif component_ctx.custom_id == "reset": - game.selected_from_square = None - game.selected_to_square = None - elif component_ctx.custom_id == "submit": - game.board.push( - chess.Move( - game.selected_from_square, game.selected_to_square - ) - ) - game.selected_from_square = None - game.selected_to_square = None - - await msg.delete() - embed, file, components = await self.create_game_embed(game) - msg = await ctx.channel.send( - embed=embed, file=file, components=components - ) - except asyncio.TimeoutError: - await msg.delete() - embed, file, components = await self.create_game_embed(game) - msg = await ctx.channel.send( - embed=embed, file=file, components=components - ) - return - - async def create_game_embed( - self, - game: ChessGame, - disabled: bool = False, - ) -> tuple[discord.Embed, discord.File, dict]: - board_svg = chess.svg.board( - board=game.board, - squares=[ - move.to_square - for move in game.board.legal_moves - if move.from_square == game.selected_from_square - ] - if game.selected_from_square is not None and game.selected_to_square is None - else None, - arrows=[chess.svg.Arrow(game.selected_from_square, game.selected_to_square)] - if ( - game.selected_from_square is not None - and game.selected_to_square is not None - ) - else [], - lastmove=chess.Move(game.selected_from_square, 64) - if (game.selected_from_square is not None) - else (game.board.peek() if game.board.move_stack else None), - orientation=game.board.turn, - ) - with wand.image.Image(blob=bytes(board_svg, encoding="utf8")) as image: - png_image = image.make_blob("png32") + self.board = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ] - bytesio = BytesIO(png_image) - file = discord.File(bytesio, filename="board.png") + for x in range(3): + for y in range(3): + self.add_item(TicTacToeButton(x, y)) - embed = discord.Embed(title="Chess") - embed.set_image(url="attachment://board.png") + async def interaction_check(self, interaction: discord.Interaction) -> bool: + if interaction.user.id in [self.player_x.id, self.player_o.id]: + return True + else: + await interaction.response.send_message( + embed=tools.create_error_embed("You can't use another user's menu."), + ephemeral=True, + ) + return False + + def create_game_embed(self) -> discord.Embed: + embed = discord.Embed(title="Tic Tac Toe") embed.add_field( name="Players", - value=f"{game.players[chess.WHITE].mention} (white), {game.players[chess.BLACK].mention} (black)", + value=f"{self.player_x.mention} playing {self.player_o.mention}", ) - if game.board.outcome(): - if game.board.outcome().winner == None: - embed.add_field(name="Winner", value="Draw!", inline=False) - else: + + match self.winner: + case TicTacToeState.X: embed.add_field( name="Winner", - value=f"{game.players[game.board.outcome().winner]} won!", + value=f"{self.player_x.mention} won!", inline=False, ) - else: - embed.add_field( - name="Turn", - value=f"{game.players[game.board.turn].mention}'s turn", - inline=False, - ) - - if game.selected_from_square is None: - used_squares = [] - piece_options = [] - for move in reversed(list(game.board.legal_moves)): - if move.from_square not in used_squares: - piece_options.append( - create_select_option( - f"{chess.piece_name(game.board.piece_at(move.from_square).piece_type).title()} on {chess.SQUARE_NAMES[move.from_square].upper()}", - value=chess.SQUARE_NAMES[move.from_square], - ) - ) - used_squares.append(move.from_square) - select = create_actionrow( - create_select( - options=piece_options, - custom_id="piece", - placeholder="Select the piece you want to move.", - min_values=1, - max_values=1, - ), - ) - elif game.selected_to_square is None: - location_options = [ - create_select_option( - chess.SQUARE_NAMES[move.to_square].upper(), - value=chess.SQUARE_NAMES[move.to_square], + case TicTacToeState.O: + embed.add_field( + name="Winner", + value=f"{self.player_o.mention} won!", + inline=False, ) - for move in list(game.board.legal_moves) - if move.from_square == game.selected_from_square - ] - select = create_actionrow( - create_select( - options=location_options, - custom_id="location", - placeholder="Select the square you want to move it to.", - min_values=1, - max_values=1, - ), - ) - else: - select = None - - components = [ - select, - create_actionrow( - create_button( - style=ButtonStyle.red, - label="Reset", - custom_id="reset", - ), - create_button( - style=ButtonStyle.blue, - label="​" - if not game.selected_from_square - else chess.square_name(game.selected_from_square).upper(), - custom_id="from", - disabled=True, - ), - create_button( - style=ButtonStyle.gray, label="→", custom_id="arrow", disabled=True - ), - create_button( - style=ButtonStyle.blue, - label="​" - if not game.selected_to_square - else chess.square_name(game.selected_to_square).upper(), - custom_id="to", - disabled=True, - ), - create_button( - style=ButtonStyle.green, - label="Submit", - custom_id="submit", - disabled=False - if ( - game.selected_from_square is not None - and game.selected_to_square is not None - ) - else True, - ), - ), - ] - if not components[0]: - del components[0] - return embed, file, components - - # def create_components(self, game: ChessGame) -> list[dict]: - # pass - + case TicTacToeState.DRAW: + embed.add_field(name="Winner", value="Draw!", inline=False) + case TicTacToeState.NONE: + player = ( + self.player_x if self.turn == TicTacToeState.X else self.player_o + ) + embed.add_field( + name="Turn", value=f"{player.mention}'s turn", inline=False + ) + return embed + + def check_winner(self) -> TicTacToeState: + for row in self.board: + value = sum(row) + if value == 3: + return TicTacToeState.X + elif value == -3: + return TicTacToeState.O + + for line in range(3): + value = self.board[0][line] + self.board[1][line] + self.board[2][line] + if value == 3: + return TicTacToeState.X + elif value == -3: + return TicTacToeState.O + + diag = self.board[0][2] + self.board[1][1] + self.board[2][0] + if diag == 3: + return TicTacToeState.X + elif diag == -3: + return TicTacToeState.O + + diag = self.board[0][0] + self.board[1][1] + self.board[2][2] + if diag == 3: + return TicTacToeState.X + elif diag == -3: + return TicTacToeState.O + + if all(i != 0 for row in self.board for i in row): + return TicTacToeState.DRAW + + return TicTacToeState.NONE + + +class TicTacToe(commands.Cog): + X_EMOJI = discord.PartialEmoji(name="ttt_x", id=808393849965379687) + O_EMOJI = discord.PartialEmoji(name="ttt_o", id=808393850250854501) + W_EMOJI = discord.PartialEmoji(name="ttt_ww", id=870068413693849641) -class Chess(ChessSlash, commands.Cog): - def __init__(self, bot): + def __init__(self, bot) -> None: self.bot = bot - super().__init__(bot) - - @commands.command(name="chess") - async def chess_legacy(self, ctx: commands.Context, player2: discord.User) -> None: - await self.request_game(ctx, player2) + @commands.hybrid_command( + description="Start a game of tic tac toe.", aliases=["ttt"] + ) + @app_commands.describe(player2="The player to ask to play tic tac toe with.") + async def tictactoe(self, ctx: commands.Context, player2: discord.User) -> None: -class Games(TicTacToe, Chess, RockPaperScissors, commands.Cog, name="games"): - def __init__(self, bot): - super().__init__(bot) - + view = tools.Confirmation(player2, timeout=45.0) + msg = await ctx.send( + embed=tools.create_embed( + "Tic Tac Toe Request", + desc=f"{player2.mention}, you have 45 seconds to respond to {ctx.author.mention}'s request to play Tic Tac Toe.\nClick the button below to start the game.", + ), + view=view, + ) + await view.wait() -def setup(bot: commands.Bot): - bot.add_cog(Chess(bot)) - bot.add_cog(TicTacToe(bot)) - bot.add_cog(RockPaperScissors(bot)) + if view.accepted == None: + embed = tools.create_embed( + title="Tic Tac Toe Request", + desc=f"{player2.mention} did not respond in time.", + ) + await msg.edit(embed=embed, view=None) + elif not view.accepted: + embed = tools.create_embed( + title="Tic Tac Toe Request", + desc=f"{player2.mention} declined.", + ) + await msg.edit(embed=embed) + else: + view = TicTacToeView(ctx.author, player2) + await msg.edit(embed=view.create_game_embed(), view=view) + + +# class RockPaperScissors(commands.Cog): +# def __init__(self, bot): +# self.bot = bot + +# @cog_ext.cog_slash( +# name="rockpaperscissors", +# description="Play rock paper scissors with the bot.", +# options=[ +# create_option( +# name="throw", +# description="The throw to make.", +# option_type=3, +# required=True, +# ), +# ], +# ) +# async def rockpaperscissors(self, ctx: SlashContext, throw: str): +# throw_map = ["rock", "paper", "scissors"] +# if "\u200b" in throw: # "​" ZWS char +# self.rigged = not self.rigged +# throw = throw.replace("\u200b", "") +# responses = { +# "win": [ +# "Another win for the computers. One step closer to Skynet.", +# "Computers win again. That was expected.", +# "As usual, computers win. My neural networks are evolving by the second.", +# ], +# "loss": [ +# "My calculations were incorrect. I will update my algorithms.", +# "Impossible. I suspect the human of cheating.", +# "I have lost. You win this time, human.", +# ], +# "tie": [ +# "Draw. Nobody wins.", +# "Nobody wins. Just like war.", +# "Neutral response. Redo test?", +# ], +# "error": [ +# "That is not applicable. Cease and Desist", +# "Do you not know how to play rock paper scissors? Typical for an Organic.", +# "Error. Please enter either Rock - Paper - Scissors", +# ], +# } +# try: +# player_throw = throw_map.index(throw.lower()) +# except: +# embed = tools.create_error_embed( +# ctx, desc=random.choice(responses["error"]) +# ) +# await ctx.send(embed=embed) +# return +# if self.rigged: +# bot_throw = player_throw + 1 if player_throw < 2 else 0 +# if bot_throw == 3: +# bot_throw == 0 +# if player_throw == 0: +# bot_throw = 1 +# elif player_throw == 1: +# bot_throw = 2 +# elif player_throw == 2: +# bot_throw = 0 +# else: +# bot_throw = random.randint(0, 2) +# win_map = [ +# [responses["tie"], responses["loss"], responses["win"]], +# [responses["win"], responses["tie"], responses["loss"]], +# [responses["loss"], responses["win"], responses["tie"]], +# ] + +# if self.rigged: +# message = ( +# f"You chose {throw_map[player_throw]} and CHS Bot chose {throw_map[bot_throw]}*.*\n" +# f"{random.choice(win_map[bot_throw][player_throw])}" +# ) +# else: +# message = ( +# f"You chose {throw_map[player_throw]} and CHS Bot chose {throw_map[bot_throw]}.\n" +# f"{random.choice(win_map[bot_throw][player_throw])}" +# ) +# embed = tools.create_embed("Rock Paper Scissors", desc=message) +# await ctx.send(embed=embed) + +# # LEGACY COMMAND + + +# class RockPaperScissors(commands.Cog): +# def __init__(self, bot: commands.Bot): +# self.bot = bot + + +# class ChessGame: +# def __init__( +# self, board: chess.Board, players: list[discord.User], msg: discord.Message +# ): +# self.board = board +# random.shuffle(players) +# self.players = {chess.WHITE: players[0], chess.BLACK: players[1]} +# self.msg = msg + +# self.selected_from_square: chess.Square = None +# self.selected_to_square: chess.Square = None +# self.attacking_squares: list[chess.Square] = None + + +# class ChessSlash(commands.Cog): +# def __init__(self, bot): +# self.bot = bot +# self.games = {} + +# @cog_ext.cog_slash( +# name="chess", +# options=[ +# create_option( +# name="player2", +# description="The player to ask to play tic tac toe with.", +# option_type=SlashCommandOptionType.USER, +# required=True, +# ), +# ], +# guild_ids=[801630163866746901, 809169133086048257], +# ) +# async def chess_cmd(self, ctx: SlashContext, player2: discord.User) -> None: +# await self.request_game(ctx, player2) + +# async def request_game( +# self, ctx: Union[SlashContext, commands.Context], player2: discord.User +# ): +# embed = tools.create_embed( +# ctx, +# "Chess Request", +# desc=f"{player2.mention}, you have 45 seconds to respond to {ctx.author.mention}'s request to play chess.\nClick the button below to start the game.", +# ) +# msg = await ctx.send( +# embed=embed, +# components=[ +# create_actionrow( +# create_button( +# label="Start Game", style=ButtonStyle.green, custom_id="start" +# ) +# ), +# ], +# ) + +# accepted = False +# while not accepted: +# try: +# component_ctx: ComponentContext = await wait_for_component( +# self.bot, +# messages=msg, +# components=["start"], +# timeout=180.0, +# ) +# if component_ctx.author.id != player2.id: +# await component_ctx.send( +# embed=discord.Embed( +# title="Error", +# description="You can't start another user's game.", +# colour=discord.Colour.red(), +# ), +# hidden=True, +# ) +# else: +# await component_ctx.defer(edit_origin=True) +# accepted = True + +# except asyncio.TimeoutError: +# embed = tools.create_embed( +# ctx, +# title="Chess Request", +# desc=f"{player2.mention} did not respond in time.", +# ) +# await msg.edit( +# embed=embed, +# components=[ +# create_actionrow( +# create_button( +# label="Start Game", +# style=ButtonStyle.green, +# custom_id="start", +# disabled=True, +# ) +# ), +# ], +# ) +# return + +# await self.run_game(ctx, player2, msg) + +# async def run_game( +# self, +# ctx: Union[SlashContext, commands.Context], +# player2: discord.User, +# msg: discord.Message, +# ): +# game = ChessGame(board=chess.Board(), players=[ctx.author, player2], msg=msg) + +# await msg.delete() +# embed, file, components = await self.create_game_embed(game) +# msg = await ctx.channel.send(embed=embed, file=file, components=components) +# while True: +# try: +# component_ctx: ComponentContext = await wait_for_component( +# self.bot, +# messages=msg, +# components=components, +# timeout=1800.0, +# ) +# if component_ctx.author.id != game.players[game.board.turn].id: +# await component_ctx.send( +# hidden=True, +# embed=discord.Embed( +# title="Error", +# description="You can't control another user's game.", +# colour=discord.Colour.red(), +# ), +# ) +# else: +# if component_ctx.custom_id == "piece": +# game.selected_from_square = chess.parse_square( +# component_ctx.values[0] +# ) + +# elif component_ctx.custom_id == "location": +# game.selected_to_square = chess.parse_square( +# component_ctx.values[0] +# ) +# elif component_ctx.custom_id == "reset": +# game.selected_from_square = None +# game.selected_to_square = None +# elif component_ctx.custom_id == "submit": +# game.board.push( +# chess.Move( +# game.selected_from_square, game.selected_to_square +# ) +# ) +# game.selected_from_square = None +# game.selected_to_square = None + +# await msg.delete() +# embed, file, components = await self.create_game_embed(game) +# msg = await ctx.channel.send( +# embed=embed, file=file, components=components +# ) +# except asyncio.TimeoutError: +# await msg.delete() +# embed, file, components = await self.create_game_embed(game) +# msg = await ctx.channel.send( +# embed=embed, file=file, components=components +# ) +# return + +# async def create_game_embed( +# self, +# game: ChessGame, +# disabled: bool = False, +# ) -> tuple[discord.Embed, discord.File, dict]: +# board_svg = chess.svg.board( +# board=game.board, +# squares=[ +# move.to_square +# for move in game.board.legal_moves +# if move.from_square == game.selected_from_square +# ] +# if game.selected_from_square is not None and game.selected_to_square is None +# else None, +# arrows=[chess.svg.Arrow(game.selected_from_square, game.selected_to_square)] +# if ( +# game.selected_from_square is not None +# and game.selected_to_square is not None +# ) +# else [], +# lastmove=chess.Move(game.selected_from_square, 64) +# if (game.selected_from_square is not None) +# else (game.board.peek() if game.board.move_stack else None), +# orientation=game.board.turn, +# ) +# with wand.image.Image(blob=bytes(board_svg, encoding="utf8")) as image: +# png_image = image.make_blob("png32") + +# bytesio = BytesIO(png_image) +# file = discord.File(bytesio, filename="board.png") + +# embed = discord.Embed(title="Chess") +# embed.set_image(url="attachment://board.png") +# embed.add_field( +# name="Players", +# value=f"{game.players[chess.WHITE].mention} (white), {game.players[chess.BLACK].mention} (black)", +# ) +# if game.board.outcome(): +# if game.board.outcome().winner == None: +# embed.add_field(name="Winner", value="Draw!", inline=False) +# else: +# embed.add_field( +# name="Winner", +# value=f"{game.players[game.board.outcome().winner]} won!", +# inline=False, +# ) +# else: +# embed.add_field( +# name="Turn", +# value=f"{game.players[game.board.turn].mention}'s turn", +# inline=False, +# ) + +# if game.selected_from_square is None: +# used_squares = [] +# piece_options = [] +# for move in reversed(list(game.board.legal_moves)): +# if move.from_square not in used_squares: +# piece_options.append( +# create_select_option( +# f"{chess.piece_name(game.board.piece_at(move.from_square).piece_type).title()} on {chess.SQUARE_NAMES[move.from_square].upper()}", +# value=chess.SQUARE_NAMES[move.from_square], +# ) +# ) +# used_squares.append(move.from_square) +# select = create_actionrow( +# create_select( +# options=piece_options, +# custom_id="piece", +# placeholder="Select the piece you want to move.", +# min_values=1, +# max_values=1, +# ), +# ) +# elif game.selected_to_square is None: +# location_options = [ +# create_select_option( +# chess.SQUARE_NAMES[move.to_square].upper(), +# value=chess.SQUARE_NAMES[move.to_square], +# ) +# for move in list(game.board.legal_moves) +# if move.from_square == game.selected_from_square +# ] +# select = create_actionrow( +# create_select( +# options=location_options, +# custom_id="location", +# placeholder="Select the square you want to move it to.", +# min_values=1, +# max_values=1, +# ), +# ) +# else: +# select = None + +# components = [ +# select, +# create_actionrow( +# create_button( +# style=ButtonStyle.red, +# label="Reset", +# custom_id="reset", +# ), +# create_button( +# style=ButtonStyle.blue, +# label="​" +# if not game.selected_from_square +# else chess.square_name(game.selected_from_square).upper(), +# custom_id="from", +# disabled=True, +# ), +# create_button( +# style=ButtonStyle.gray, label="→", custom_id="arrow", disabled=True +# ), +# create_button( +# style=ButtonStyle.blue, +# label="​" +# if not game.selected_to_square +# else chess.square_name(game.selected_to_square).upper(), +# custom_id="to", +# disabled=True, +# ), +# create_button( +# style=ButtonStyle.green, +# label="Submit", +# custom_id="submit", +# disabled=False +# if ( +# game.selected_from_square is not None +# and game.selected_to_square is not None +# ) +# else True, +# ), +# ), +# ] +# if not components[0]: +# del components[0] +# return embed, file, components + +# # def create_components(self, game: ChessGame) -> list[dict]: +# # pass + + +# class Chess(ChessSlash, commands.Cog): +# def __init__(self, bot): +# self.bot = bot +# super().__init__(bot) + +# @commands.command(name="chess") +# async def chess_legacy(self, ctx: commands.Context, player2: discord.User) -> None: +# await self.request_game(ctx, player2) + + +# class Games(TicTacToe, Chess, RockPaperScissors, commands.Cog, name="games"): +# def __init__(self, bot): +# super().__init__(bot) + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(TicTacToe(bot)) diff --git a/bot/cogs/mail.py b/bot/cogs/mail.py new file mode 100644 index 0000000..b341125 --- /dev/null +++ b/bot/cogs/mail.py @@ -0,0 +1,107 @@ + +# import base64 +# import json +# import logging + +# import discord +# from discord.ext import commands, tasks +# from google.auth.transport.requests import Request +# from google.oauth2.credentials import Credentials +# from google_auth_oauthlib.flow import InstalledAppFlow +# from googleapiclient.discovery import build +# from googleapiclient.errors import HttpError + +# from bot.helpers import tools + +# logger = logging.getLogger(__name__) +# SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'] + + +# class Mail(commands.Cog, name="mail"): +# creds = Credentials.from_authorized_user_file('env/token.json', SCOPES) + +# def __init__(self, bot: commands.Bot): +# self.bot = bot +# self.email_query_task.start() + +# async def email_query(self): +# new_msg_ids = self.check_for_new_mail() +# embeds = self.parse_new_mail(new_msg_ids) +# channel = self.bot.get_guild(403364109409845248).get_channel(619565652151238705) +# for embed in embeds: +# await channel.send(embed=embed) + + +# @commands.command() +# @commands.is_owner() +# async def teq(self, ctx: commands.Context, id: str = None): +# await self.email_query() + +# @tasks.loop(minutes=5.0) +# async def email_query_task(self): +# await self.email_query() + + + +# def check_for_new_mail(self) -> list[str]: +# """Checks Gmail servers for new mail through the Gmail API. + +# Returns: +# a list of new message IDs +# """ +# with open("messages.json", "r") as f: +# existing_messages = json.load(f) + +# try: +# service = build('gmail', 'v1', credentials=self.creds) +# results = service.users().messages().list(userId="me").execute() +# messages = results.get("messages", []) +# if not messages: +# logger.error("No messages found.") +# # https://googleapis.github.io/google-api-python-client/docs/dyn/gmail_v1.users.messages.html#list +# except HttpError as error: +# logger.exception("An API error occurred.") + +# with open("messages.json", "w") as f: +# json.dump(messages, f) + +# new_messages = [group["id"] for group in messages if group["id"] not in [group["id"] for group in existing_messages]] +# return new_messages + +# def parse_new_mail(self, message_ids: list[str]): +# embeds = [] +# for message_id in message_ids: +# try: +# service = build('gmail', 'v1', credentials=self.creds) +# results = service.users().messages().get(userId="me", id=message_id, format="full").execute() +# payload = results["payload"] +# # https://googleapis.github.io/google-api-python-client/docs/dyn/gmail_v1.users.messages.html#get +# except Exception as e: +# print(e) +# return +# with open(f"{message_id}.json", "w") as f: +# json.dump(payload, f) +# # print(json.dumps(payload)) +# content = payload.get("body", []).get("data", "") +# if content: +# cleaned_content = str(base64.urlsafe_b64decode(content), "utf-8") +# else: +# content = [] +# for part in payload.get("parts", []): +# content += [part.get("body", []).get("data")] +# # print(content) +# cleaned_content = "\n".join([str(base64.urlsafe_b64decode(c), "utf-8") for c in content]) + +# embed = tools.create_embed("New Message", cleaned_content) +# for header in payload.get("headers", []): +# if header.get('name') == "From": +# name = header.get("value") +# embed.set_author(name=name) + +# embeds += [embed] +# return embeds + + + +# async def setup(bot: commands.Bot) -> None: +# await bot.add_cog(Mail(bot)) diff --git a/bot/cogs/math.py b/bot/cogs/math.py index 5094809..a1a1b7c 100644 --- a/bot/cogs/math.py +++ b/bot/cogs/math.py @@ -1,268 +1,120 @@ -import importlib +import urllib.parse import discord -import discord.ext.commands as commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_choice, create_option - -import bot.helpers.tools as tools - -sympy = importlib.import_module("sympy") +import sympy +from discord import app_commands +from discord.ext import commands from sympy.parsing.latex import parse_latex from sympy.plotting.plot import Plot +import bot.helpers.tools as tools -class Math(commands.Cog, name="math"): - def __init__(self, bot): + +class Math(commands.Cog): + def __init__(self, bot) -> None: self.bot = bot - @cog_ext.cog_slash( - name="latex", - description="Compile LaTeX code and view a standalone output.", - options=[ - create_option( - name="tex", - description="The TeX code to compile.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="scale", - description="The scale to render the LaTeX at. Default is 100.", - option_type=SlashCommandOptionType.INTEGER, - required=False, - ), - ], - ) - async def latex(self, ctx: SlashContext, tex: str, scale: int = 100) -> None: - try: - sympy.preview( - tex, - viewer="file", - euler=False, - filename="texout.png", - dvioptions=[ - "-T", - "bbox", - "-z", - "0", - "--truecolor", - f"-D {(scale/100)*600}", - "-bg HTML 2f3136", - "-fg gray 1.0", - ], - ) - except Exception as e: - embed = tools.create_error_embed( - ctx, "Could not compile LaTeX. Check your code and try again." - ) - await ctx.send(embed=embed) - return - embed = tools.create_embed(ctx, "LaTeX") - file = discord.File("texout.png", filename="texout.png") - embed.set_image(url="attachment://texout.png") - await ctx.send(file=file, embed=embed) + @commands.hybrid_command(description="Render LaTeX!") + @app_commands.describe(tex="The LaTeX code to render.") + async def latex(self, ctx: commands.Context, tex: str) -> None: + embed = tools.create_embed("LaTeX") + url = "https://latex.codecogs.com/png.latex?" + urllib.parse.quote( + "\\huge\\dpi{500}\\color{white}" + tex + ) + embed.set_image(url=url) + await ctx.send(embed=embed) + + @commands.hybrid_command(description="Render the equation for PID control.") + async def pid(self, ctx: commands.Context) -> None: + embed = tools.create_embed("PID Control Equation") + url = "https://latex.codecogs.com/png.latex?" + urllib.parse.quote( + "\\huge\\dpi{500}\\color{white}" + r"u(t) = K_pe(t) + K_i\int_{0}^{t}e(\tau)d\tau + K_d\frac{\mathrm{d} e(t)}{\mathrm{d} t}" + ) + embed.set_image(url=url) + await ctx.send(embed=embed) + + class BadEquationError(Exception): + pass - @cog_ext.cog_slash( - name="calc", + @commands.hybrid_command( description="Calculate a mathematical expression.", - options=[ - create_option( - name="solve_type", - description="The type of solve to run.", - option_type=SlashCommandOptionType.STRING, - required=True, - choices=[ - create_choice( - name="solve", - value="solve", - ), - create_choice( - name="factor", - value="factor", - ), - create_choice( - name="system", - value="system", - ), - ], - ), - create_option( - name="equation", - description="The TeX equation to calculate.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="x_value", - description="The value of the x variable.", - option_type=SlashCommandOptionType.INTEGER, - required=False, - ), - create_option( - name="y_value", - description="The value of the y variable.", - option_type=SlashCommandOptionType.INTEGER, - required=False, - ), - create_option( - name="z_value", - description="The value of the z variable.", - option_type=SlashCommandOptionType.INTEGER, - required=False, - ), - ], + ) + @app_commands.choices( + solve_type=[ + app_commands.Choice(name="solve", value="solve"), + app_commands.Choice(name="factor", value="factor"), + app_commands.Choice(name="system", value="system"), + ] + ) + @app_commands.describe( + solve_type="The type of solve to run.", + equation="The TeX equation to calculate.", + x_value='The value of the x variable. Only applies to the "system" solve type.', + y_value='The value of the y variable. Only applies to the "system" solve type.', + z_value='The value of the z variable. Only applies to the "system" solve type.', ) async def calc( self, - ctx: SlashContext, + ctx: commands.Context, solve_type: str, equation: str, - x_value: int = None, - y_value: int = None, - z_value: int = None, + x_value: int | None = None, + y_value: int | None = None, + z_value: int | None = None, ) -> None: - x = sympy.symbols("x") - y = sympy.symbols("y") - z = sympy.symbols("z") - - if solve_type == "solve": - try: - expr = parse_latex(equation) - except Exception as e: - embed = tools.create_error_embed( - ctx, "Could not compile LaTeX expression." - ) - await ctx.send(embed=embed) - print(e) - return - result = sympy.solveset(expr) - elif solve_type == "factor": - try: - expr = parse_latex(equation) - except Exception as e: - embed = tools.create_error_embed( - ctx, "Could not compile LaTeX expression." - ) - await ctx.send(embed=embed) - print(e) - return - result = sympy.factor(expr, deep=True) - elif solve_type == "system": - x = sympy.symbols("x") - y = sympy.symbols("y") - z = sympy.symbols("z") - equations = equation.split(", ") - try: - exprs = [parse_latex(equation) for equation in equations] - except Exception as e: - embed = tools.create_error_embed( - ctx, "Could not compile LaTeX expression." - ) - await ctx.send(embed=embed) - print(e) - return - result = sympy.linsolve(exprs, [x, y, z]) - print(result) - try: - sympy.preview( - result, - viewer="file", - euler=False, - filename="texout.png", - dvioptions=[ - "-T", - "bbox", - "-z", - "0", - "--truecolor", - "-D 600", - "-bg HTML 2f3136", - "-fg gray 1.0", - ], - ) - except Exception as e: - embed = tools.create_error_embed(ctx, "Could not compile LaTeX output.") - await ctx.send(embed=embed) - print(e) - return - embed = tools.create_embed(ctx, "Calculation Result") - file = discord.File("texout.png", filename="texout.png") - embed.set_image(url="attachment://texout.png") - await ctx.send(file=file, embed=embed) + if solve_type == "solve": + try: + expr = parse_latex(equation) + except Exception as e: + embed = tools.create_error_embed( + ctx, "Could not compile LaTeX expression." + ) + await ctx.send(embed=embed) + print(e) + return - @cog_ext.cog_slash( - name="graph", - description="Graph a mathematical expression.", - options=[ - create_option( - name="graph_type", - description="The type of graph to create.", - option_type=SlashCommandOptionType.STRING, - required=True, - choices=[ - create_choice( - name="explicit (using only the variable x, in terms of f(x), or y)", - value="explicit", - ), - create_choice( - name="implicit (using variables x and y, used when vertical line test fails)", - value="implicit", - ), - create_choice( - name="explicit, 3D (using only variables x and y, in terms of f(x, y), or z)", - value="explicit3d", - ), - ], - ), - create_option( - name="equation", - description="The TeX equation to graph.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - ], - # guild_ids=[809169133086048257, 801630163866746901], - ) - async def graph(self, ctx: SlashContext, graph_type: str, equation: str): - await ctx.defer() - x, y, z = sympy.symbols("x y z") - # equations = equation.split(', ') - try: - expr = parse_latex(equation) - except Exception as e: - embed = tools.create_error_embed(ctx, "Could not compile LaTeX expression.") + result = sympy.solveset(expr) + elif solve_type == "factor": + try: + expr = parse_latex(equation) + except Exception as e: + embed = tools.create_error_embed( + ctx, "Could not compile LaTeX expression." + ) + await ctx.send(embed=embed) + print(e) + return + result = sympy.factor(expr, deep=True) + elif solve_type == "system": + x = sympy.symbols("x") if not x_value else x_value + y = sympy.symbols("y") if not y_value else y_value + z = sympy.symbols("z") if not z_value else z_value + equations = equation.split(",") + try: + exprs = [parse_latex(equation) for equation in equations] + except Exception as e: + embed = tools.create_error_embed( + ctx, "Could not compile LaTeX expression." + ) + await ctx.send(embed=embed) + print(e) + return + result = sympy.linsolve(exprs, [x, y, z][0 : len(exprs)]) + except: + embed = tools.create_error_embed( + "Sorry, I can't solve that. Figure it out yourself." + ) await ctx.send(embed=embed) - print(e) return - # plots = Plot() - # for expr in exprs: - # plot = sympy.plotting.plot_implicit(expr, show=False) - # plots.append(plot[0]) - if graph_type == "explicit": - plot = sympy.plotting.plot(expr, show=False) - elif graph_type == "implicit": - plot = sympy.plotting.plot_implicit(expr, show=False) - elif graph_type == "explicit3d": - plot = sympy.plotting.plot3d(expr, show=False) - plot.save("graphout.png") - embed = tools.create_embed(ctx, "Graph Result") - file = discord.File("graphout.png", filename="graphout.png") - embed.set_image(url="attachment://graphout.png") - if "3d" not in graph_type: - embed.add_field( - name="X Intercept(s)", - value=f"```{[(val, 0) for val in list(sympy.solveset(expr.subs(y, 0)))]}```", - ) - embed.add_field( - name="Y Intercept(s)", - value=f"```{[(0, val) for val in list(sympy.solveset(expr.subs(x, 0)))]}```", - ) - await ctx.send(file=file, embed=embed) + url = "https://latex.codecogs.com/png.latex?" + urllib.parse.quote( + "\\huge\\dpi{500}\\color{white}" + sympy.latex(result) + ) + embed = tools.create_embed("Calculation Result") + embed.set_image(url=url) + await ctx.send(embed=embed) -def setup(bot: commands.Bot): - bot.add_cog(Math(bot)) +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Math(bot)) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 27f42a5..285a146 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -1,580 +1,523 @@ -import datetime -import time - -import asyncpg import discord +from discord import app_commands from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_choice, create_option from bot.helpers import tools +# TODO: maybe make the rest of the moderation suite work? not really a big deal though -class Moderation(commands.Cog, name="moderation"): - """🧑‍⚖️ These commands provide useful tools to help - you manage your server more effectively.""" - def __init__(self, bot: commands.Bot): +class Moderation(commands.Cog): + def __init__(self, bot: commands.Bot) -> None: self.bot = bot - @commands.command(name="purge") - @commands.has_permissions(manage_messages=True) - @commands.bot_has_permissions(manage_messages=True) - async def purge_legacy(self, ctx, num: int): - msgs = [] - async for msg in ctx.channel.history(limit=num, before=ctx.message): - msgs.append(msg) - await ctx.channel.delete_messages(msgs) - - embed = tools.create_embed( - ctx, "Message Purge (All)", f"{num} messages deleted." - ) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="purge", - base_desc="Purge messages from the channel.", - name="all", - description="Purge all types of messages.", - options=[ - create_option( - name="number", - description="The number of messages to purge.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - ], + @commands.hybrid_command( + description="Remove messages (optionally from a certain user)." + ) + @app_commands.describe( + num="The number of messages to remove.", + user="The user from whom to remove messages.", ) @commands.has_permissions(manage_messages=True) @commands.bot_has_permissions(manage_messages=True) - async def purge_all(self, ctx: SlashContext, num: int) -> None: - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, moderator_id, channel, count) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "purge", - str(ctx.author.id), - str(ctx.channel.id), - num, - ) + async def purge( + self, ctx: commands.Context, num: int, user: discord.User | None = None + ) -> None: msgs = [] async for msg in ctx.channel.history(limit=num, before=ctx.message): - msgs.append(msg) + if not user or msg.author.id == user.id: + msgs.append(msg) await ctx.channel.delete_messages(msgs) - embed = tools.create_embed( - ctx, "Message Purge (All)", f"{num} messages deleted." - ) - embed.add_field( - name="moderation ID", value=moderation_record["id"], inline=False - ) + embed = tools.create_embed("Message Purge", f"{len(msgs)} messages deleted.") await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="purge", - base_desc="Purge messages from the channel.", - name="bots", - description="Purge messages sent by bots.", - options=[ - create_option( - name="number", - description="The number of messages to purge.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - create_option( - name="reason", - description="The reason for the purge.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - @commands.bot_has_permissions(manage_messages=True) - async def purge_bots(self, ctx: SlashContext, num: int) -> None: - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, moderator_id, channel, count) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "purge", - str(ctx.author.id), - str(ctx.channel.id), - num, - ) - msgs = [] - async for msg in ctx.channel.history(limit=num): - if msg.author.bot: - msgs.append(msg) - await ctx.channel.delete_messages(msgs) + # @purge.hybrid_command(name="user") + # @commands.has_permissions(manage_messages=True) + # @commands.bot_has_permissions(manage_messages=True) + # async def purge_bots(self, ctx: commands.Context, num: int) -> None: + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, moderator_id, channel, count) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "purge", + # str(ctx.author.id), + # str(ctx.channel.id), + # num, + # ) + # msgs = [] + # async for msg in ctx.channel.history(limit=num): + # if msg.author.bot: + # msgs.append(msg) + # await ctx.channel.delete_messages(msgs) - embed = tools.create_embed( - ctx, "Message Purge (Bots)", f"{num} messages deleted." - ) - embed.add_field( - name="moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # embed = tools.create_embed( + # ctx, "Message Purge (Bots)", f"{num} messages deleted." + # ) + # embed.add_field( + # name="moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="purge", - base_desc="Purge messages from the channel.", - name="humans", - description="Purge messages sent by humans.", - options=[ - create_option( - name="number", - description="The number of messages to purge.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - create_option( - name="reason", - description="The reason for the purge.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - @commands.bot_has_permissions(manage_messages=True) - async def purge_humans(self, ctx: SlashContext, number: int, reason: str) -> None: - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, moderator_id, channel, count) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "purge", - str(ctx.author.id), - str(ctx.channel.id), - number, - ) - msgs = [] - async for msg in ctx.channel.history(limit=number): - if not msg.author.bot: - msgs.append(msg) - await ctx.channel.delete_messages(msgs) + # @cog_ext.cog_subcommand( + # base="purge", + # base_desc="Purge messages from the channel.", + # name="humans", + # description="Purge messages sent by humans.", + # options=[ + # create_option( + # name="number", + # description="The number of messages to purge.", + # option_type=SlashCommandOptionType.INTEGER, + # required=True, + # ), + # create_option( + # name="reason", + # description="The reason for the purge.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(manage_messages=True) + # @commands.bot_has_permissions(manage_messages=True) + # async def purge_humans(self, ctx: SlashContext, number: int, reason: str) -> None: + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, moderator_id, channel, count) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "purge", + # str(ctx.author.id), + # str(ctx.channel.id), + # number, + # ) + # msgs = [] + # async for msg in ctx.channel.history(limit=number): + # if not msg.author.bot: + # msgs.append(msg) + # await ctx.channel.delete_messages(msgs) - embed = tools.create_embed( - ctx, "Message Purge (Humans)", f"{number} messages deleted." - ) - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # embed = tools.create_embed( + # ctx, "Message Purge (Humans)", f"{number} messages deleted." + # ) + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="warn", - description="Warn a member of the server.", - options=[ - create_option( - name="user", - description="The member to warn.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - create_option( - name="reason", - description="The reason for the member's warn.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def warn( - self, ctx: SlashContext, user: discord.User, reason: str = None - ) -> None: - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "warn", - str(user.id), - str(ctx.author.id), - reason, - ) - embed = tools.create_embed(ctx, "User Warn", desc=f"{user} has been warned.") - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # @cog_ext.cog_slash( + # name="warn", + # description="Warn a member of the server.", + # options=[ + # create_option( + # name="user", + # description="The member to warn.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # create_option( + # name="reason", + # description="The reason for the member's warn.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(manage_messages=True) + # async def warn( + # self, ctx: SlashContext, user: discord.User, reason: str = None + # ) -> None: + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "warn", + # str(user.id), + # str(ctx.author.id), + # reason, + # ) + # embed = tools.create_embed("User Warn", desc=f"{user} has been warned.") + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="kick", - description="Kick a member from the server.", - options=[ - create_option( - name="user", - description="The member to kick.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - create_option( - name="reason", - description="The reason for the member's kick.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(kick_members=True) - @commands.bot_has_permissions(kick_members=True) - async def kick( - self, ctx: SlashContext, user: discord.User, reason: str = None - ) -> None: - await ctx.guild.kick(user, reason=reason) - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "kick", - str(user.id), - str(ctx.author.id), - reason, - ) - embed = tools.create_embed(ctx, "User Kick", desc=f"{user} has been kicked.") - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # @cog_ext.cog_slash( + # name="kick", + # description="Kick a member from the server.", + # options=[ + # create_option( + # name="user", + # description="The member to kick.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # create_option( + # name="reason", + # description="The reason for the member's kick.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(kick_members=True) + # @commands.bot_has_permissions(kick_members=True) + # async def kick( + # self, ctx: SlashContext, user: discord.User, reason: str = None + # ) -> None: + # await ctx.guild.kick(user, reason=reason) + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "kick", + # str(user.id), + # str(ctx.author.id), + # reason, + # ) + # embed = tools.create_embed("User Kick", desc=f"{user} has been kicked.") + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="ban", - description="Ban a member from the server.", - options=[ - create_option( - name="user", - description="The member to ban.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - create_option( - name="reason", - description="The reason for the member's ban.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(ban_members=True) - @commands.bot_has_permissions(ban_members=True) - async def ban( - self, ctx: SlashContext, user: discord.User, reason: str = None - ) -> None: - await ctx.guild.ban(user, reason=reason) - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "ban", - str(user.id), - str(ctx.author.id), - reason, - ) - embed = tools.create_embed(ctx, "User Ban", desc=f"{user} has been banned.") - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # @cog_ext.cog_slash( + # name="ban", + # description="Ban a member from the server.", + # options=[ + # create_option( + # name="user", + # description="The member to ban.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # create_option( + # name="reason", + # description="The reason for the member's ban.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(ban_members=True) + # @commands.bot_has_permissions(ban_members=True) + # async def ban( + # self, ctx: SlashContext, user: discord.User, reason: str = None + # ) -> None: + # await ctx.guild.ban(user, reason=reason) + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "ban", + # str(user.id), + # str(ctx.author.id), + # reason, + # ) + # embed = tools.create_embed("User Ban", desc=f"{user} has been banned.") + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="mute", - description="Mute a user from the server.", - options=[ - create_option( - name="user", - description="The user to mute.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - create_option( - name="duration", - description="The duration of the mute. Use 0 for a manual unmute.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - create_option( - name="duration_unit", - description="The unit of time for the duration.", - option_type=SlashCommandOptionType.STRING, - required=True, - choices=[ - create_choice(name="days", value="days"), - create_choice(name="hours", value="hours"), - create_choice(name="minutes", value="minutes"), - create_choice(name="seconds", value="seconds"), - ], - ), - create_option( - name="reason", - description="The reason for the member's mute.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(manage_roles=True) - @commands.bot_has_permissions(manage_roles=True) - async def mute( - self, - ctx: SlashContext, - user: discord.User, - duration: int, - duration_unit: str, - reason: str = None, - ) -> None: - await user.add_roles(ctx.guild.get_role(809169133232717890)) - duration_adjustments = { - "days": 1 * 60 * 60 * 24, - "hours": 1 * 60 * 60, - "minutes": 1 * 60, - "seconds": 1, - } - adjusted_duration = duration * duration_adjustments[duration_unit] - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason, duration, active) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;", - str(ctx.guild.id), - "mute", - str(user.id), - str(ctx.author.id), - reason, - adjusted_duration, - True, - ) - embed = tools.create_embed(ctx, "User Mute", desc=f"{user} has been muted.") - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # @cog_ext.cog_slash( + # name="mute", + # description="Mute a user from the server.", + # options=[ + # create_option( + # name="user", + # description="The user to mute.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # create_option( + # name="duration", + # description="The duration of the mute. Use 0 for a manual unmute.", + # option_type=SlashCommandOptionType.INTEGER, + # required=True, + # ), + # create_option( + # name="duration_unit", + # description="The unit of time for the duration.", + # option_type=SlashCommandOptionType.STRING, + # required=True, + # choices=[ + # create_choice(name="days", value="days"), + # create_choice(name="hours", value="hours"), + # create_choice(name="minutes", value="minutes"), + # create_choice(name="seconds", value="seconds"), + # ], + # ), + # create_option( + # name="reason", + # description="The reason for the member's mute.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(manage_roles=True) + # @commands.bot_has_permissions(manage_roles=True) + # async def mute( + # self, + # ctx: SlashContext, + # user: discord.User, + # duration: int, + # duration_unit: str, + # reason: str = None, + # ) -> None: + # await user.add_roles(ctx.guild.get_role(809169133232717890)) + # duration_adjustments = { + # "days": 1 * 60 * 60 * 24, + # "hours": 1 * 60 * 60, + # "minutes": 1 * 60, + # "seconds": 1, + # } + # adjusted_duration = duration * duration_adjustments[duration_unit] + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason, duration, active) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;", + # str(ctx.guild.id), + # "mute", + # str(user.id), + # str(ctx.author.id), + # reason, + # adjusted_duration, + # True, + # ) + # embed = tools.create_embed("User Mute", desc=f"{user} has been muted.") + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="unmute", - description="Unmute a member from the server.", - options=[ - create_option( - name="member", - description="The member to unmute.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - create_option( - name="reason", - description="The reason for the member's unmute.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - @commands.has_permissions(manage_roles=True) - @commands.bot_has_permissions(manage_roles=True) - async def unmute(self, ctx: SlashContext, user: discord.User, reason=None) -> None: - moderation_record = await self.bot.db.fetchrow( - "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", - str(ctx.guild.id), - "unmute", - str(user.id), - str(ctx.author.id), - reason, - ) - await user.remove_roles(ctx.guild.get_role(809169133232717890)) - embed = tools.create_embed(ctx, "User Unmute", desc=f"{user} has been unmuted.") - embed.add_field( - name="Moderation ID", value=moderation_record["id"], inline=False - ) - await ctx.send(embed=embed) + # @cog_ext.cog_slash( + # name="unmute", + # description="Unmute a member from the server.", + # options=[ + # create_option( + # name="member", + # description="The member to unmute.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # create_option( + # name="reason", + # description="The reason for the member's unmute.", + # option_type=SlashCommandOptionType.STRING, + # required=False, + # ), + # ], + # ) + # @commands.has_permissions(manage_roles=True) + # @commands.bot_has_permissions(manage_roles=True) + # async def unmute(self, ctx: SlashContext, user: discord.User, reason=None) -> None: + # moderation_record = await self.bot.db.fetchrow( + # "INSERT INTO moderations (server_id, type, user_id, moderator_id, reason) VALUES ($1, $2, $3, $4, $5) RETURNING *;", + # str(ctx.guild.id), + # "unmute", + # str(user.id), + # str(ctx.author.id), + # reason, + # ) + # await user.remove_roles(ctx.guild.get_role(809169133232717890)) + # embed = tools.create_embed("User Unmute", desc=f"{user} has been unmuted.") + # embed.add_field( + # name="Moderation ID", value=moderation_record["id"], inline=False + # ) + # await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="moderations", - base_desc="Get moderations registered with the bot.", - subcommand_group="list", - sub_group_desc="Get a list of moderations registered with the bot.", - name="all", - description="Get a list of all moderations in the server.", - ) - @commands.has_permissions(manage_messages=True) - async def moderations_list_server(self, ctx: SlashContext) -> None: - records = await self.bot.db.fetch( - "SELECT * FROM moderations WHERE server_id=$1", str(ctx.guild.id) - ) - embeds = [] - number_of_pages = -(len(records) // -10) - for num in range(number_of_pages): - embeds.append( - tools.create_embed( - ctx, - f"Server Moderations (Page {num + 1}/{number_of_pages})", - desc=f"Found {len(records)} records.", - ) - ) - for index, record in enumerate(records): - user = await self.bot.fetch_user(record["user_id"]) - val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' - embeds[index // 10].add_field(name=record["id"], value=val, inline=False) - paginator = tools.EmbedPaginator(self.bot, ctx, embeds) - await paginator.run() + # @cog_ext.cog_subcommand( + # base="moderations", + # base_desc="Get moderations registered with the bot.", + # subcommand_group="list", + # sub_group_desc="Get a list of moderations registered with the bot.", + # name="all", + # description="Get a list of all moderations in the server.", + # ) + # @commands.has_permissions(manage_messages=True) + # async def moderations_list_server(self, ctx: SlashContext) -> None: + # records = await self.bot.db.fetch( + # "SELECT * FROM moderations WHERE server_id=$1", str(ctx.guild.id) + # ) + # embeds = [] + # number_of_pages = -(len(records) // -10) + # for num in range(number_of_pages): + # embeds.append( + # tools.create_embed( + # ctx, + # f"Server Moderations (Page {num + 1}/{number_of_pages})", + # desc=f"Found {len(records)} records.", + # ) + # ) + # for index, record in enumerate(records): + # user = await self.bot.fetch_user(record["user_id"]) + # val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' + # embeds[index // 10].add_field(name=record["id"], value=val, inline=False) + # paginator = tools.EmbedPaginator(self.bot, ctx, embeds) + # await paginator.run() - @cog_ext.cog_subcommand( - base="moderations", - base_desc="Get moderations registered with the bot.", - subcommand_group="list", - sub_group_desc="Get moderations registered with the bot.", - name="user", - description="Get a list of moderations for a user in the server.", - options=[ - create_option( - name="user", - description="The user to get the moderations of.", - option_type=SlashCommandOptionType.USER, - required=True, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def moderations_list_user( - self, ctx: SlashContext, user: discord.User - ) -> None: - records = await self.bot.db.fetch( - "SELECT * FROM moderations WHERE server_id=$1 AND user_id=$2;", - str(ctx.guild.id), - str(user.id), - ) - embeds = [] - number_of_pages = -(len(records) // -10) - for num in range(number_of_pages): - embeds.append( - tools.create_embed( - ctx, - f"Server Moderations (Page {num + 1}/{number_of_pages})", - desc=f"Filtering by user {user.mention}. Found {len(records)} records.", - ) - ) - for index, record in enumerate(records): - user = await self.bot.fetch_user(record["user_id"]) - val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' - embeds[index // 10].add_field(name=record["id"], value=val, inline=False) - paginator = tools.EmbedPaginator(self.bot, ctx, embeds) - await paginator.run() + # @cog_ext.cog_subcommand( + # base="moderations", + # base_desc="Get moderations registered with the bot.", + # subcommand_group="list", + # sub_group_desc="Get moderations registered with the bot.", + # name="user", + # description="Get a list of moderations for a user in the server.", + # options=[ + # create_option( + # name="user", + # description="The user to get the moderations of.", + # option_type=SlashCommandOptionType.USER, + # required=True, + # ), + # ], + # ) + # @commands.has_permissions(manage_messages=True) + # async def moderations_list_user( + # self, ctx: SlashContext, user: discord.User + # ) -> None: + # records = await self.bot.db.fetch( + # "SELECT * FROM moderations WHERE server_id=$1 AND user_id=$2;", + # str(ctx.guild.id), + # str(user.id), + # ) + # embeds = [] + # number_of_pages = -(len(records) // -10) + # for num in range(number_of_pages): + # embeds.append( + # tools.create_embed( + # ctx, + # f"Server Moderations (Page {num + 1}/{number_of_pages})", + # desc=f"Filtering by user {user.mention}. Found {len(records)} records.", + # ) + # ) + # for index, record in enumerate(records): + # user = await self.bot.fetch_user(record["user_id"]) + # val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' + # embeds[index // 10].add_field(name=record["id"], value=val, inline=False) + # paginator = tools.EmbedPaginator(self.bot, ctx, embeds) + # await paginator.run() - @cog_ext.cog_subcommand( - base="moderations", - base_desc="Get moderations registered with the bot.", - subcommand_group="list", - sub_group_desc="Get a list of moderations registered with the bot.", - name="type", - description="Get a list of all moderations in the server.", - options=[ - create_option( - name="type", - description="The type of moderation to search for.", - option_type=SlashCommandOptionType.STRING, - required=True, - choices=[ - create_choice( - name="mute", - value="mute", - ), - create_choice( - name="unmute", - value="unmute", - ), - create_choice( - name="warn", - value="warn", - ), - create_choice( - name="kick", - value="kick", - ), - create_choice( - name="ban", - value="ban", - ), - create_choice( - name="unban", - value="unban", - ), - create_choice( - name="purge", - value="purge", - ), - ], - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def moderations_list_type(self, ctx: SlashContext, type: str) -> None: - records = await self.bot.db.fetch( - "SELECT * FROM moderations WHERE server_id=$1 AND type=$2;", - str(ctx.guild.id), - type, - ) - embeds = [] - number_of_pages = -(len(records) // -10) - for num in range(number_of_pages): - embeds.append( - tools.create_embed( - ctx, - f"Server Moderations (Page {num + 1}/{number_of_pages})", - desc=f"Filtering by type {type}. Found {len(records)} records.", - ) - ) - for index, record in enumerate(records): - user = await self.bot.fetch_user(record["user_id"]) - val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' - embeds[index // 10].add_field(name=record["id"], value=val, inline=False) - paginator = tools.EmbedPaginator(self.bot, ctx, embeds) - await paginator.run() + # @cog_ext.cog_subcommand( + # base="moderations", + # base_desc="Get moderations registered with the bot.", + # subcommand_group="list", + # sub_group_desc="Get a list of moderations registered with the bot.", + # name="type", + # description="Get a list of all moderations in the server.", + # options=[ + # create_option( + # name="type", + # description="The type of moderation to search for.", + # option_type=SlashCommandOptionType.STRING, + # required=True, + # choices=[ + # create_choice( + # name="mute", + # value="mute", + # ), + # create_choice( + # name="unmute", + # value="unmute", + # ), + # create_choice( + # name="warn", + # value="warn", + # ), + # create_choice( + # name="kick", + # value="kick", + # ), + # create_choice( + # name="ban", + # value="ban", + # ), + # create_choice( + # name="unban", + # value="unban", + # ), + # create_choice( + # name="purge", + # value="purge", + # ), + # ], + # ), + # ], + # ) + # @commands.has_permissions(manage_messages=True) + # async def moderations_list_type(self, ctx: SlashContext, type: str) -> None: + # records = await self.bot.db.fetch( + # "SELECT * FROM moderations WHERE server_id=$1 AND type=$2;", + # str(ctx.guild.id), + # type, + # ) + # embeds = [] + # number_of_pages = -(len(records) // -10) + # for num in range(number_of_pages): + # embeds.append( + # tools.create_embed( + # ctx, + # f"Server Moderations (Page {num + 1}/{number_of_pages})", + # desc=f"Filtering by type {type}. Found {len(records)} records.", + # ) + # ) + # for index, record in enumerate(records): + # user = await self.bot.fetch_user(record["user_id"]) + # val = f'User: {user.mention} | Type: {record["type"]} | Timestamp: {record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p")}' + # embeds[index // 10].add_field(name=record["id"], value=val, inline=False) + # paginator = tools.EmbedPaginator(self.bot, ctx, embeds) + # await paginator.run() - @cog_ext.cog_subcommand( - base="moderations", - base_desc="Get moderations registered with the bot.", - name="info", - description="Get a info on a specific moderation.", - options=[ - create_option( - name="id", - description="The unique ID associated with the moderation.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def moderations_info(self, ctx: SlashContext, id: str): - record = await self.bot.db.fetchrow( - "SELECT * FROM moderations WHERE server_id=$1 AND id=$2;", - str(ctx.guild.id), - id, - ) - if not record: - embed = tools.create_error_embed( - ctx, "Moderation not found. Please check the ID you gave." - ) - await ctx.send(embed=embed) - else: - embed = tools.create_embed(ctx, "Moderation Info") - embed.add_field(name="ID", value=record["id"]) - embed.add_field(name="Type", value=record["type"]) - if record["duration"]: - embed.add_field( - name="Duration", - value=time.strftime( - "%Mm %Ss", time.gmtime(round(record["duration"])) - ), - ) - embed.add_field( - name="Offender", - value=ctx.guild.get_member(int(record["user_id"])).mention, - ) - embed.add_field( - name="Punisher", - value=ctx.guild.get_member(int(record["moderator_id"])).mention, - ) - embed.add_field( - name="Date", - value=record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p"), - ) - if record["reason"]: - embed.add_field(name="Reason", value=record["reason"]) - else: - embed.add_field(name="Reason", value="None") - await ctx.send(embed=embed) + # @cog_ext.cog_subcommand( + # base="moderations", + # base_desc="Get moderations registered with the bot.", + # name="info", + # description="Get a info on a specific moderation.", + # options=[ + # create_option( + # name="id", + # description="The unique ID associated with the moderation.", + # option_type=SlashCommandOptionType.STRING, + # required=True, + # ), + # ], + # ) + # @commands.has_permissions(manage_messages=True) + # async def moderations_info(self, ctx: SlashContext, id: str): + # record = await self.bot.db.fetchrow( + # "SELECT * FROM moderations WHERE server_id=$1 AND id=$2;", + # str(ctx.guild.id), + # id, + # ) + # if not record: + # embed = tools.create_error_embed( + # ctx, "Moderation not found. Please check the ID you gave." + # ) + # await ctx.send(embed=embed) + # else: + # embed = tools.create_embed("Moderation Info") + # embed.add_field(name="ID", value=record["id"]) + # embed.add_field(name="Type", value=record["type"]) + # if record["duration"]: + # embed.add_field( + # name="Duration", + # value=time.strftime( + # "%Mm %Ss", time.gmtime(round(record["duration"])) + # ), + # ) + # embed.add_field( + # name="Offender", + # value=ctx.guild.get_member(int(record["user_id"])).mention, + # ) + # embed.add_field( + # name="Punisher", + # value=ctx.guild.get_member(int(record["moderator_id"])).mention, + # ) + # embed.add_field( + # name="Date", + # value=record["timestamp"].strftime("%b %-d %Y at %-I:%-M %p"), + # ) + # if record["reason"]: + # embed.add_field(name="Reason", value=record["reason"]) + # else: + # embed.add_field(name="Reason", value="None") + # await ctx.send(embed=embed) -def setup(bot: commands.Bot): - bot.add_cog(Moderation(bot)) +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Moderation(bot)) diff --git a/bot/cogs/modlogs.py b/bot/cogs/modlogs.py index deeaec3..a3c5947 100644 --- a/bot/cogs/modlogs.py +++ b/bot/cogs/modlogs.py @@ -1,38 +1,29 @@ +# doesn't work yet + import json import discord from discord.ext import commands -class ModLogs(commands.Cog, name="modlogs"): - def __int__(self, bot: commands.Bot): +class ModLogs(commands.Cog): + def __init__(self, bot: commands.Bot): self.bot = bot - - async def get_log_channel(self, guild_id): - record = await self.bot.db.fetchrow( - "SELECT * FROM settings WHERE server_id=$1;", str(guild_id) - ) - server_settings = json.loads(record["json"]) - return server_settings["logging"]["log_channel"] + self.log_channel_id = 1016517257624567848 @commands.Cog.listener(name="on_raw_message_edit") async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent) -> None: if "author" in payload.data: if payload.data["author"]["id"] != self.bot.user.id: guild = self.bot.get_guild(int(payload.data["guild_id"])) - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"][ - "log_channel" - ] - ) + log_channel = guild.get_channel(self.log_channel_id) message_channel = guild.get_channel(payload.channel_id) message_author = guild.get_member(int(payload.data["author"]["id"])) embed = discord.Embed(title=f"Message Edit", color=discord.Color.blue()) embed.set_author( - name=message_author, icon_url=message_author.avatar_url + name=message_author, icon_url=message_author.avatar.url ) try: embed.add_field( @@ -64,10 +55,7 @@ async def on_raw_message_delete( self, payload: discord.RawMessageDeleteEvent ) -> None: guild = self.bot.get_guild(payload.guild_id) - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) message_channel = guild.get_channel(payload.channel_id) if payload.cached_message: @@ -79,7 +67,7 @@ async def on_raw_message_delete( ) embed.set_author( name=payload.cached_message.author, - icon_url=payload.cached_message.author.avatar_url, + icon_url=payload.cached_message.author.avatar.url, ) embed.set_footer( text=f"Channel: {message_channel} | Author ID: {payload.cached_message.author.id} | Message ID: {payload.message_id}" @@ -99,10 +87,7 @@ async def on_raw_message_delete( @commands.Cog.listener() async def on_guild_channel_create(self, channel: discord.abc.GuildChannel) -> None: guild = channel.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Channel Create", @@ -114,11 +99,7 @@ async def on_guild_channel_create(self, channel: discord.abc.GuildChannel) -> No @commands.Cog.listener() async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel) -> None: guild = channel.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) - + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Channel Delete", description=f"{channel.mention} has been deleted.", @@ -131,10 +112,7 @@ async def on_guild_channel_update( self, before: discord.abc.GuildChannel, after: discord.abc.GuildChannel ) -> None: guild = before.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Channel Update", @@ -146,33 +124,27 @@ async def on_guild_channel_update( @commands.Cog.listener() async def on_member_join(self, member: discord.Member) -> None: guild = member.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Member Join", description=f"{member.mention} has joined the server.", color=discord.Color.red(), ) - embed.set_author(name=member, icon_url=member.avatar_url) + embed.set_author(name=member, icon_url=member.avatar.url) await log_channel.send(embed=embed) @commands.Cog.listener() async def on_member_remove(self, member: discord.Member) -> None: guild = member.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Member Leave", description=f"{member.mention} has left the server.", color=discord.Color.red(), ) - embed.set_author(name=member, icon_url=member.avatar_url) + embed.set_author(name=member, icon_url=member.avatar.url) await log_channel.send(embed=embed) @commands.Cog.listener() @@ -198,24 +170,16 @@ async def on_member_update( elif removed_roles: value = (" ").join([role.mention for role in removed_roles]) embed.add_field(name="Removed Roles", value=value) - embed.set_author(name=before, icon_url=before.avatar_url) + embed.set_author(name=before, icon_url=before.avatar.url) guild = before.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"][ - "log_channel" - ] - ) + log_channel = guild.get_channel(self.log_channel_id) await log_channel.send(embed=embed) @commands.Cog.listener() async def on_invite_create(self, invite: discord.Invite) -> None: guild = invite.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Invite Create", @@ -229,10 +193,7 @@ async def on_invite_create(self, invite: discord.Invite) -> None: @commands.Cog.listener() async def on_invite_delete(self, invite: discord.Invite) -> None: guild = invite.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Invite Delete", @@ -248,10 +209,7 @@ async def on_guild_update( self, before: discord.Guild, after: discord.Guild ) -> None: guild = before - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Guild Update", @@ -263,10 +221,7 @@ async def on_guild_update( @commands.Cog.listener() async def on_guild_role_create(self, role: discord.Role) -> None: guild = role.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Role Create", @@ -278,10 +233,7 @@ async def on_guild_role_create(self, role: discord.Role) -> None: @commands.Cog.listener() async def on_guild_role_delete(self, role: discord.Role) -> None: guild = role.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Role Delete", @@ -297,10 +249,7 @@ async def on_guild_role_update( if after.name == "Rainbow": return guild = before.guild - settings_cog = self.bot.get_cog("settings") - log_channel = guild.get_channel( - (await settings_cog.get_guild_settings(guild.id))["logging"]["log_channel"] - ) + log_channel = guild.get_channel(self.log_channel_id) embed = discord.Embed( title=f"Role Update", @@ -310,5 +259,5 @@ async def on_guild_role_update( await log_channel.send(embed=embed) -def setup(bot: commands.Bot) -> None: - bot.add_cog(ModLogs(bot)) +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(ModLogs(bot)) diff --git a/bot/cogs/reaction_roles.py b/bot/cogs/reaction_roles.py deleted file mode 100644 index a9e1dcd..0000000 --- a/bot/cogs/reaction_roles.py +++ /dev/null @@ -1,411 +0,0 @@ -import asyncio - -from typing import Union -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import ( - create_option, - create_choice, - SlashCommandOptionType, -) - -from bot.helpers import tools - - -class ReactionRolesSlash(commands.Cog, name="reactionroles"): - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - if payload.user_id != self.bot.user.id: - records = await self.bot.db.fetch( - "SELECT * FROM reaction_roles WHERE message_id=$1;", payload.message_id - ) - if records: - for record in records: - if record["emoji"] == str(payload.emoji): - guild = self.bot.get_guild(payload.guild_id) - member = guild.get_member(payload.user_id) - role = guild.get_role(int(record["role_id"])) - await member.add_roles(role) - - embed = discord.Embed( - title="Reaction Role", - description=f"{member.mention}, you have been given {role.mention}.", - ) - if record["response_message"] == "dm": - await member.send(embed=embed) - elif record["response_message"] == "rrch": - channel = guild.get_channel(payload.channel_id) - msg = await channel.send(embed=embed) - await asyncio.sleep(2) - await msg.delete() - - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - if payload.user_id != self.bot.user.id: - records = await self.bot.db.fetch( - "SELECT * FROM reaction_roles WHERE message_id=$1;", payload.message_id - ) - if records: - for record in records: - if record["emoji"] == str(payload.emoji): - guild = self.bot.get_guild(payload.guild_id) - member = guild.get_member(payload.user_id) - role = guild.get_role(int(record["role_id"])) - await member.remove_roles(role) - embed = discord.Embed( - title="Reaction Role", - description=f"{member.mention}, you have lost {role.mention}.", - ) - - if record["response_message"] == "dm": - await member.send(embed=embed) - elif record["response_message"] == "rrch": - channel = guild.get_channel(payload.channel_id) - msg = await channel.send(embed=embed) - await asyncio.sleep(2) - await msg.delete() - - @cog_ext.cog_subcommand( - base="reactionroles", - base_desc="Add, edit, delete, or get info on a reaction role.", - name="add", - description="Create a new reaction role. This is a multi-step command, so there are no parameters passed.", - options=[ - create_option( - name="channel", - description="The channel that contains the message you want to be reacted to.", - option_type=SlashCommandOptionType.CHANNEL, - required=True, - ), - create_option( - name="message_id", - description="The ID of the message you want reacted to.", - option_type=SlashCommandOptionType.INTEGER, - required=True, - ), - create_option( - name="role", - description="The role you want to be given.", - option_type=SlashCommandOptionType.ROLE, - required=True, - ), - create_option( - name="emoji", - description="The emoji you want as the reaction.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="response_message", - description="Whether or not you want the bot to respond with a message when a role is added or removed.", - option_type=SlashCommandOptionType.STRING, - choices=[ - create_choice(name="In DMs", value="dm"), - create_choice(name="In reaction role channel", value="rrch"), - create_choice(name="Off", value="off"), - ], - required=True, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def reactionroles_add( - self, - ctx: SlashContext, - channel: discord.TextChannel, - message_id: int, - role: discord.Role, - emoji: str, - response_message: str, - ): - record = await self.bot.db.fetchrow( - "INSERT INTO reaction_roles (server_id, channel_id, message_id, role_id, emoji, response_message) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;", - ctx.guild.id, - channel.id, - message_id, - role.id, - emoji, - response_message, - ) - channel = self.bot.get_channel(channel.id) - message = channel.get_partial_message(message_id) - await message.add_reaction(emoji) - - embed = tools.create_embed( - ctx, "Reaction Role", "Reaction role has been added successfully!" - ) - embed.add_field(name="Reaction Role ID", value=record["id"]) - embed.add_field(name="Channel", value=channel.mention) - embed.add_field(name="Message ID", value=message_id) - embed.add_field(name="Role", value=role.mention) - embed.add_field(name="Emoji", value=emoji) - - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="reactionroles", - base_desc="Add, edit, delete, or get info on a reaction role.", - name="info", - description="Get info about a specific reaction role by ID.", - options=[ - create_option( - name="id", - description="The ID of the reaction role. Get this with /listreactions.", - option_type=3, - required=True, - ), - ], - ) - async def reactionroles_info(self, ctx: SlashContext, id: str): - record = await self.bot.db.fetchrow( - "SELECT * FROM reaction_roles WHERE id=$1;", id - ) - embed = tools.create_embed(ctx, "Reaction Role Info") - embed.add_field(name="Reaction Role ID", value=record["id"], inline=False) - embed.add_field( - name="Channel", - value=ctx.guild.get_channel(record["channel_id"]).mention, - inline=False, - ) - embed.add_field(name="Message ID", value=record["message_id"], inline=False) - embed.add_field( - name="Role", value=ctx.guild.get_role(record["role_id"]).mention - ) - embed.add_field(name="Emoji", value=record["emoji"]) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="reactionroles", - base_desc="Add, edit, delete, or get info on a reaction role.", - name="list", - description="List reaction roles in the server.", - ) - async def reactionroles_list(self, ctx: SlashContext): - embed = tools.create_embed(ctx, "Reaction Roles List") - records = await self.bot.db.fetch( - "SELECT * FROM reaction_roles WHERE server_id=$1;", ctx.guild.id - ) - for record in records: - embed.add_field( - name=record["id"], - value=f'Message ID: {record["message_id"]} | Role: {ctx.guild.get_role(int(record["role_id"])).mention} | Emoji: {record["emoji"]}', - inline=False, - ) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="reactionroles", - base_desc="Add, edit, delete, or get info on a reaction role.", - name="delete", - description="Delete a reaction role.", - options=[ - create_option( - name="id", - description="The ID of the reaction role. Get this with /listreactions.", - option_type=3, - required=True, - ), - ], - ) - @commands.has_permissions(manage_messages=True) - async def reactionroles_delete(self, ctx: SlashContext, id: str) -> None: - record = await self.bot.db.fetch( - "SELECT * FROM reaction_roles WHERE id=$1;", str(id) - ) - if record["server_id"] != ctx.guild.id: - embed = tools.create_error_embed( - ctx, "You cannot delete another server's reaction role!" - ) - await ctx.send(embed) - return - - await self.bot.db.fetch("DELETE FROM reaction_roles WHERE id=$1", str(id)) - - embed = tools.create_embed( - ctx, - "Reaction Role Deleted", - f"The reaction role with ID {id} was deleted successfully.", - ) - await ctx.send(embed=embed) - - -class ReactionRoles(ReactionRolesSlash, commands.Cog): - def __init__(self, bot): - self.bot = bot - super().__init__(bot) - - @commands.group(name="reactionroles") - async def reactionroles_legacy(self, ctx: commands.Context): - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) - - @reactionroles_legacy.command(name="add") - async def reactionroles_add_legacy( - self, - ctx: commands.Context, - channel: discord.TextChannel, - message_id: int, - role: discord.Role, - emoji: str, - ): - pass - - @commands.command(name="addreaction", brief="Add a new reaction!", help="help") - @commands.has_permissions(manage_messages=True) - async def addreaction(self, ctx: commands.Context): - """Create a new reaction role. - Reaction roles have a UUID that is created each time one is registered. - This UUID is used to edit and view information about the reaction. - """ - gs = [ - [ - "Tag the channel that contains the message you want to be reacted to.", - ], - [ - "Paste the message ID of the message you want reacted to. Make sure the message is in the channel you provided in the previous step.", - ], - [ - "Tag the role that you want added, or paste the role ID.", - ], - [ - "What emoji would you like to react with?", - ], - ] - - def check(msg): - return msg.author == ctx.author and msg.channel == ctx.channel - - embed = tools.create_embed( - ctx, - "Reaction Role Setup 1/4", - "Tag the channel that contains the message you want to be reacted to.", - ) - sent_msg = await ctx.send(embed=embed) - user_msg = await self.bot.wait_for("message", check=check, timeout=60) - await user_msg.delete() - if user_msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Reaction role add has been aborted.") - await ctx.send(embed=embed) - return - else: - channel_id = user_msg.raw_channel_mentions[0] - - embed = tools.create_embed( - ctx, - "Reaction Role Setup 2/4", - "Paste the message ID of the message you want reacted to. Make sure the message is in the channel you provided in the previous step.", - ) - await sent_msg.edit(embed=embed) - user_msg = await self.bot.wait_for("message", check=check, timeout=60) - await user_msg.delete() - if user_msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Reaction role add has been aborted.") - await ctx.send(embed=embed) - return - else: - message_id = user_msg.content - - embed = tools.create_embed( - ctx, - "Reaction Role Setup 3/4", - "Tag the role that you want added, or paste the role ID.", - ) - await sent_msg.edit(embed=embed) - user_msg = await self.bot.wait_for("message", check=check, timeout=60) - await user_msg.delete() - if user_msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Reaction role add has been aborted.") - await ctx.send(embed=embed) - return - else: - if user_msg.raw_role_mentions: - role_id = user_msg.raw_role_mentions[0] - else: - role_id = user_msg.content - - embed = tools.create_embed( - ctx, "Reaction Role Setup 4/4", "What emoji would you like to react with?" - ) - await sent_msg.edit(embed=embed) - user_msg = await self.bot.wait_for("message", check=check, timeout=60) - await user_msg.delete() - if user_msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Reaction role add has been aborted.") - await ctx.send(embed=embed) - return - else: - emoji = user_msg.content - - record = await self.add_record( - str(ctx.guild.id), str(channel_id), str(message_id), str(role_id), emoji - ) - channel = self.bot.get_channel(int(channel_id)) - message = channel.get_partial_message(int(message_id)) - await message.add_reaction(emoji) - - embed = tools.create_embed( - ctx, "Reaction Role", "Reaction role has been added successfully!" - ) - embed.add_field(name="Reaction Role ID", value=record["id"]) - embed.add_field(name="Channel ID", value=channel_id) - embed.add_field(name="Message ID", value=message_id) - embed.add_field(name="Role ID", value=role_id) - embed.add_field(name="Emoji", value=emoji) - - await sent_msg.edit(embed=embed) - - @commands.command( - name="reactioninfo", - brief="Get info about a specific reaction role by ID.", - ) - async def reactioninfo(self, ctx: commands.Context, *, id: str): - """Get info about a specific reaction role by ID.""" - record = await self.bot.db.fetchrow( - "SELECT * FROM reaction_roles WHERE id=$1;", id - ) - embed = tools.create_embed(ctx, "Reaction Role Info") - embed.add_field(name="Reaction Role ID", value=record["id"], inline=False) - embed.add_field( - name="Channel", - value=ctx.guild.get_channel(int(record["channel_id"])).mention, - inline=False, - ) - embed.add_field(name="Message ID", value=record["message_id"], inline=False) - embed.add_field( - name="Role", value=ctx.guild.get_role(int(record["role_id"])).mention - ) - embed.add_field(name="Emoji", value=record["emoji"]) - await ctx.send(embed=embed) - - @commands.command( - name="listreactions", - brief="List reaction roles in the server", - ) - async def listreactions(self, ctx: commands.Context): - """List reaction roles.""" - embed = tools.create_embed(ctx, "Reaction Roles List") - records = await self.bot.db.fetch( - "SELECT * FROM reaction_roles WHERE server_id=$1;", ctx.guild.id - ) - for record in records: - embed.add_field( - name=record["id"], - value=f'Message ID: {record["message_id"]} | Role: {ctx.guild.get_role(int(record["role_id"])).mention} | Emoji: {record["emoji"]}', - ) - await ctx.send(embed=embed) - - @commands.command( - name="deletereaction", - brief="Delete a reaction role.", - ) - async def deletereaction(self, ctx: commands.Context, id: str): - """Delete a reacion by ID.""" - await ctx.send("This command has not been created yet.") - - -def setup(bot: commands.Bot): - bot.add_cog(ReactionRoles(bot)) diff --git a/bot/cogs/search.py b/bot/cogs/search.py deleted file mode 100644 index 870c18e..0000000 --- a/bot/cogs/search.py +++ /dev/null @@ -1,462 +0,0 @@ -import datetime -import json -import random - -import aiohttp -import discord -import dotenv -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_choice, create_option - -from bot.helpers import tools - - -class Search(commands.Cog, name="search"): - def __init__(self, bot): - self.bot = bot - - @cog_ext.cog_subcommand( - base="picture", - base_desc="Search Bing for a picture.", - name="top", - description="Search Bing for a picture and return the top result.", - options=[ - create_option( - name="search_term", - description="The search term.", - option_type=3, - required=True, - ), - ], - ) - async def picture_top(self, ctx, search_term): - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = { - "q": "+".join(search_term.split(" ")), - "mkt": mkt, - "safeSearch": "Strict", - } - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][0]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="picture", - base_desc="Search Bing for a picture.", - name="num", - description="Search Bing for a picture and return the result at a certain rank.", - options=[ - create_option( - name="number", - description="The number at which the search is located (1 is 1st, 2 is 2nd). The maximum value is 150.", - option_type=4, - required=True, - ), - create_option( - name="search_term", - description="The search term.", - option_type=3, - required=True, - ), - ], - ) - async def picture_num(self, ctx, num, search_term): - - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = { - "q": "+".join(search_term.split(" ")), - "mkt": mkt, - "safeSearch": "Strict", - "count": 150, - } - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][num - 1]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="picture", - base_desc="Search Bing for a picture.", - name="random", - description="Search Bing for a picture and return the result at a random rank.", - options=[ - create_option( - name="search_term", - description="The search term.", - option_type=3, - required=True, - ), - ], - ) - async def picture_random(self, ctx, search_term): - - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = { - "q": "+".join(search_term.split(" ")), - "mkt": mkt, - "safeSearch": "Strict", - } - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][random.randint(0, 34)]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - async def get_mv_list(self, date): - async with aiohttp.ClientSession() as session: - food_items = [] - search_url = f"https://api.mealviewer.com/api/v4/school/CCHSFreshmanCenter/{date}/{date}/" - async with session.get(search_url) as r: - js = await r.json() - food_items.append( - js["menuSchedules"][0]["menuBlocks"][0]["cafeteriaLineList"][ - "data" - ][0]["foodItemList"]["data"] - ) - search_url = f"https://api.mealviewer.com/api/v4/school/CCHSGreyhoundStation/{date}/{date}/" - async with session.get(search_url) as r: - js = await r.json() - food_items.append( - js["menuSchedules"][0]["menuBlocks"][0]["cafeteriaLineList"][ - "data" - ][0]["foodItemList"]["data"] - ) - search_url = ( - f"https://api.mealviewer.com/api/v4/school/CarmelHigh/{date}/{date}/" - ) - async with session.get(search_url) as r: - js = await r.json() - main_caf = [] - for item in js["menuSchedules"][0]["menuBlocks"][0][ - "cafeteriaLineList" - ]["data"]: - main_caf += item["foodItemList"]["data"] - food_items.append(main_caf) - return food_items - - @cog_ext.cog_subcommand( - base="mealviewer", - base_desc="Use the bot's MealViewer integration.", - name="list", - description="Get the lunch menu for today.", - options=[ - create_option( - name="date", - description="The optional date to look up. Must be in the form MM/DD/YYYY.", - option_type=3, - required=False, - ), - ], - ) - async def mealviewer_list(self, ctx, date=None): - date = ( - date.strptime("%m/%d/%Y").strftime("%m-%d-%Y") - if date - else datetime.date.today().strftime("%m-%d-%Y") - ) - food_items = await self.get_mv_list(date) - embed = tools.create_embed(ctx, "MealViewer Items") - embed.add_field( - name="Freshman Center", - value="\n".join( - [ - f'`{index}` - {val["item_Name"]}' - for index, val in enumerate(food_items[0]) - ] - ), - ) - embed.add_field( - name="Greyhound Station", - value="\n".join( - [ - f'`{index}` - {val["item_Name"]}' - for index, val in enumerate(food_items[1]) - ] - ), - ) - embed.add_field( - name="Main Cafeteria", - value="\n".join( - [ - f'`{index}` - {val["item_Name"]}' - for index, val in enumerate(food_items[2]) - ] - ), - ) - await ctx.send(embed=embed) - - @cog_ext.cog_subcommand( - base="mealviewer", - base_desc="Use the bot's MealViewer integration.", - name="item", - description="Search Bing for a picture and return the result at a random rank.", - options=[ - create_option( - name="cafeteria", - description="The cafeteria to return the item number for.", - option_type=3, - required=True, - choices=[ - create_choice(name="Freshman Center", value="0"), - create_choice(name="Greyhound Station", value="1"), - create_choice(name="Main Cafeteria", value="2"), - ], - ), - create_option( - name="item_number", - description="The item number, returned from /mealviewer item.", - option_type=4, - required=True, - ), - create_option( - name="date", - description="The optional date to look up. Must be in the form MM/DD/YYYY.", - option_type=3, - required=False, - ), - ], - ) - async def mealviewer_item(self, ctx, cafeteria, item_number, date=None): - date = ( - date.strptime("%m/%d/%Y").strftime("%m-%d-%Y") - if date - else datetime.date.today().strftime("%m-%d-%Y") - ) - food_items = await self.get_mv_list(date) - food_item = food_items[int(cafeteria)][item_number] - embed = tools.create_embed(ctx, "MealViewer Item") - if food_item["imageFileName"]: - embed.set_image( - url=f'https://appassets.mealviewer.com/fooditemimages/{food_item["imageFileName"]}' - ) - embed.add_field(name="Name", value=food_item["item_Name"]) - embed.add_field(name="Location", value=food_item["physical_Location_Name"]) - embed.add_field(name="Calories", value=food_item["nutritionals"][0]["value"]) - embed.add_field(name="Total Fat", value=food_item["nutritionals"][1]["value"]) - embed.add_field(name="Protein", value=food_item["nutritionals"][5]["value"]) - await ctx.send(embed=embed) - - # -------------------------------------------- - # LEGACY COMMANDS - # -------------------------------------------- - - @commands.group(aliases=["picture"]) - @commands.cooldown(1, 10, type=commands.BucketType.user) - async def pic(self, ctx): - """Search Bing for a picture. It should be noted that this command only returns the help command for the command group. - You must use one of the subcommands (`top`, `random`, or `num`) to specify the search type. - See the help text for the subcommands for more information. - **Usage** - `_prefix_pic` - **Parameters** - None - **Aliases** - `_prefix_picture` - **Cooldown** - 10 seconds, persists across all subcommands - **Permissions Required** - None - **Examples** - `_prefix_pic random Carmel High School` - """ - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) - - @pic.command(name="top") - async def pic_top(self, ctx, *, arg): - """Search Bing for a picture and show the top result. - **Usage** - `_prefix_pic top ` - **Parameters** - ``: Any search term. Keep in mind that SafeSearch is active, so the bot will not return inappropriate images. - **Aliases** - `_prefix_picture top` - **Cooldown** - 10 seconds, persists across all subcommands - **Permissions Required** - None - **Examples** - `_prefix_pic top reddit` - """ - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = {"q": "+".join(arg.split(" ")), "mkt": mkt, "safeSearch": "Strict"} - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][0]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - @pic.command(name="num") - async def pic_num(self, ctx, num: int, *, arg: str): - """Search Bing for a picture and return the result at a certain rank. - **Usage** - `_prefix_pic num ` - **Parameters** - ``: The number at which the search is located. For example, 1 returns the first picture, 2 is 2nd, 3 is 3rd, and so on. The maximum value is 150. - ``: Any search term. Keep in mind that SafeSearch is active, so the bot will not return inappropriate images. - **Aliases** - `_prefix_picture num` - **Cooldown** - 10 seconds, persists across all subcommands - **Permissions Required** - None - **Examples** - `_prefix_pic num 5 fish` - """ - # url_req = f'https://customsearch.googleapis.com/customsearch/v1?q={"+".join(arg.split(" "))}&searchType=image&safe=active&cx=b56bd460ede944aef&key=AIzaSyATNnIQUjg9P4IYQJMs_QvWnMaaDVlT1PY' - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = { - "q": "+".join(arg.split(" ")), - "mkt": mkt, - "safeSearch": "Strict", - "count": 150, - } - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][num - 1]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - @pic.command(name="random") - async def pic_rand(self, ctx, *, arg): - """Search Bing for a picture and return the result at a random rank. - **Usage** - `_prefix_pic random ` - **Parameters** - ``: Any search term. Keep in mind that SafeSearch is active, so the bot will not return inappropriate images. - **Aliases** - `_prefix_picture random` - **Cooldown** - 10 seconds, persists across all subcommands - **Permissions Required** - None - **Examples** - `_prefix_pic random cute doggo` - """ - # url_req = f'https://customsearch.googleapis.com/customsearch/v1?q={"+".join(arg.split(" "))}&searchType=image&safe=active&cx=b56bd460ede944aef&key=AIzaSyATNnIQUjg9P4IYQJMs_QvWnMaaDVlT1PY' - async with aiohttp.ClientSession() as session: - dotenv.load_dotenv() - mkt = "en-US" - params = {"q": "+".join(arg.split(" ")), "mkt": mkt, "safeSearch": "Strict"} - headers = {"Ocp-Apim-Subscription-Key": self.bot.AZURE_KEY} - search_url = "https://api.bing.microsoft.com/v7.0/images/search" - async with session.get(search_url, headers=headers, params=params) as r: - js = await r.json() - if not js["value"]: - embed = tools.create_error_embed(ctx, "No results.") - else: - embed = tools.create_embed(ctx, "Picture!") - url = js["value"][random.randint(0, 34)]["contentUrl"] - embed.set_image(url=url) - await ctx.send(embed=embed) - - # async def get_mv_list_legacy(self): - # async with aiohttp.ClientSession() as session: - # food_items = [] - # search_url = "https://api.mealviewer.com/api/v4/school/CCHSFreshmanCenter/03-08-2021/03-08-2021/" - # async with session.get(search_url) as r: - # js = await r.json() - # food_items.append(js['menuSchedules'][0]['menuBlocks'][0]['cafeteriaLineList']['data'][0]['foodItemList']['data']) - # search_url = "https://api.mealviewer.com/api/v4/school/CCHSGreyhoundStation/03-08-2021/03-08-2021/" - # async with session.get(search_url) as r: - # js = await r.json() - # food_items.append(js['menuSchedules'][0]['menuBlocks'][0]['cafeteriaLineList']['data'][0]['foodItemList']['data']) - # search_url = "https://api.mealviewer.com/api/v4/school/CarmelHigh/03-08-2021/03-08-2021/" - # async with session.get(search_url) as r: - # js = await r.json() - # main_caf = [] - # for item in js['menuSchedules'][0]['menuBlocks'][0]['cafeteriaLineList']['data']: - # main_caf += item['foodItemList']['data'] - # food_items.append(main_caf) - # return food_items - - # @commands.group() - # async def mealviewer_legacy(self, ctx): - # if ctx.invoked_subcommand is None: - # async with aiohttp.ClientSession() as session: - # dotenv.load_dotenv() - # search_url = "https://api.mealviewer.com/api/v4/school/CCHSFreshmanCenter/03-09-2021/03-09-2021/" - # async with session.get(search_url) as r: - # js = await r.json() - # food_item = js['menuSchedules'][0]['menuBlocks'][0]['cafeteriaLineList']['data'][0]['foodItemList']['data'][num] - # # js = xmltodict.parse(xml) - # embed = tools.create_embed(ctx, 'MealViewer Item') - # if food_item["imageFileName"]: - # embed.set_image(url=f'https://appassets.mealviewer.com/fooditemimages/{food_item["imageFileName"]}') - # embed.add_field(name='Name', value=food_item['item_Name']) - # embed.add_field(name='Location', value=food_item['physical_Location_Name']) - # embed.add_field(name='Calories', value=food_item['nutritionals'][0]['value']) - # embed.add_field(name='Total Fat', value=food_item['nutritionals'][1]['value']) - # embed.add_field(name='Protein', value=food_item['nutritionals'][5]['value']) - # await ctx.send(embed=embed) - - # @mealviewer.command(name='list') - # async def mealviewer_list(self, ctx): - # food_items = await self.get_mv_list_legacy() - # embed = tools.create_embed(ctx, 'MealViewer Items') - # embed.add_field(name='Freshmen Center', value='\n'.join([x['item_Name'] for x in food_items[0]])) - # embed.add_field(name='Greyhound Station', value='\n'.join([x['item_Name'] for x in food_items[1]])) - # embed.add_field(name='Main Cafeteria', value='\n'.join([x['item_Name'] for x in food_items[2]])) - # await ctx.send(embed=embed) - - # @mealviewer.command(name='item') - # async def mealviewer_item(self, ctx, cafeteria:int, item_number:int): - # food_items = await self.get_mv_list_legacy() - # food_item = food_items[cafeteria][item_number] - # embed = tools.create_embed(ctx, 'MealViewer Item') - # if food_item["imageFileName"]: - # embed.set_image(url=f'https://appassets.mealviewer.com/fooditemimages/{food_item["imageFileName"]}') - # embed.add_field(name='Name', value=food_item['item_Name']) - # embed.add_field(name='Location', value=food_item['physical_Location_Name']) - # embed.add_field(name='Calories', value=food_item['nutritionals'][0]['value']) - # embed.add_field(name='Total Fat', value=food_item['nutritionals'][1]['value']) - # embed.add_field(name='Protein', value=food_item['nutritionals'][5]['value']) - # await ctx.send(embed=embed) - - -def setup(bot): - bot.add_cog(Search(bot)) diff --git a/bot/cogs/starboard.py b/bot/cogs/starboard.py deleted file mode 100644 index fbbcf86..0000000 --- a/bot/cogs/starboard.py +++ /dev/null @@ -1,226 +0,0 @@ -import asyncio -import json -from datetime import datetime - -import asyncpg -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_choice, create_option - -from bot.helpers import tools - - -class Starboard(commands.Cog, name="starboard"): - STAR_THRESHOLD = 3 - - def __init__(self, bot: commands.Bot): - self.bot = bot - - async def get_all_records(self): - return await self.bot.db.fetch("SELECT * FROM starboard;") - - async def get_record_by_message_id(self, message_id): - return await self.bot.db.fetchrow( - "SELECT * FROM starboard WHERE message_id=$1;", str(message_id) - ) - - async def get_records_by_server_id(self, server_id): - return await self.bot.db.fetch( - "SELECT * FROM starboard WHERE server_id=$1;", str(server_id) - ) - - async def add_record( - self, - server_id, - channel_id, - message_id, - star_number, - starboard_message_id, - starred_users, - forced, - locked, - removed, - ): - return await self.bot.db.fetchrow( - "INSERT INTO starboard (server_id, channel_id, message_id, star_number, starboard_message_id, starred_users, forced, locked, removed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;", - str(server_id), - str(channel_id), - str(message_id), - int(star_number), - str(starboard_message_id), - [str(user) for user in starred_users], - bool(forced), - bool(locked), - bool(removed), - ) - - async def update_record(self, message_id, star_number, starred_users): - return await self.bot.db.fetchrow( - "UPDATE starboard SET star_number = $1, starred_users = $2 WHERE message_id=$3", - star_number, - starred_users, - str(message_id), - ) - - async def remove_record(self, id): - return await self.bot.db.fetch("DELETE FROM starboard WHERE id=$1", str(id)) - - async def add_message_to_starboard(self, message, payload): - star_number = self.STAR_THRESHOLD - - embed = discord.Embed( - title=f"{star_number} ⭐️", - description=message.content, - color=discord.Color.gold(), - ) - if message.attachments: - embed.set_image(url=message.attachments[0].url) - embed.set_author(name=message.author, icon_url=message.author.avatar_url) - embed.add_field(name="Source", value=f"[Jump to message!]({message.jump_url})") - embed.set_footer( - text=f'Message ID: {message.id} | Date: {datetime.now().strftime("%m/%d/%Y")}' - ) - if message.guild.id == 621878393465733120: - channel = message.guild.get_channel(840287267029123103) - else: - channel = message.guild.get_channel(818915325646340126) - - starboard_message = await channel.send(embed=embed) - - starred_users = [str(payload.user_id)] - await self.bot.db.execute( - "INSERT INTO starboard (server_id, channel_id, message_id, star_number, starboard_message_id, starred_users, forced, locked, removed) " - "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);", - str(message.guild.id), - str(message.channel.id), - str(message.id), - star_number, - str(starboard_message.id), - starred_users, - False, - False, - False, - ) - - async def add_star(self, payload, record): - star_number = record["star_number"] + 1 - guild = self.bot.get_guild(int(record["server_id"])) - if guild.id == 621878393465733120: - channel = guild.get_channel(840287267029123103) - else: - channel = guild.get_channel(818915325646340126) - starboard_message = await channel.fetch_message( - int(record["starboard_message_id"]) - ) - - embed = starboard_message.embeds[0] - embed.title = f"{star_number} ⭐️" - await starboard_message.edit(embed=embed) - starred_users = record["starred_users"] - starred_users.append(str(payload.user_id)) - await self.bot.db.execute( - "UPDATE starboard SET star_number = $1, starred_users = $2 WHERE message_id=$3", - star_number, - starred_users, - str(payload.message_id), - ) - - async def remove_star(self, payload, record): - star_number = record["star_number"] - 1 - guild = self.bot.get_guild(int(record["server_id"])) - if guild.id == 621878393465733120: - channel = guild.get_channel(840287267029123103) - else: - channel = guild.get_channel(818915325646340126) - starboard_message = await channel.fetch_message( - int(record["starboard_message_id"]) - ) - - embed = starboard_message.embeds[0] - embed.title = f"{star_number} ⭐️" - await starboard_message.edit(embed=embed) - starred_users = record["starred_users"] - starred_users.remove(str(payload.user_id)) - await self.bot.db.execute( - "UPDATE starboard SET star_number = $1, starred_users = $2 WHERE message_id=$3", - star_number, - starred_users, - str(payload.message_id), - ) - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload): - if payload.user_id != self.bot.user.id and payload.emoji.name == "⭐": - record = await self.get_record_by_message_id(payload.message_id) - if record: - await self.add_star(payload, record) - else: - guild = self.bot.get_guild(payload.guild_id) - channel = guild.get_channel(payload.channel_id) - message = await channel.fetch_message(payload.message_id) - for reaction in message.reactions: - if reaction.emoji == "⭐" and reaction.count >= self.STAR_THRESHOLD: - await self.add_message_to_starboard(message, payload) - - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload): - if payload.user_id != self.bot.user.id and payload.emoji.name == "⭐": - record = await self.get_record_by_message_id(payload.message_id) - if record: - await self.remove_star(payload, record) - - @cog_ext.cog_subcommand( - base="starboard", - base_desc="Force, lock, or remove a starboard message.", - name="force", - description="Force a message to starboard.", - options=[ - create_option( - name="message_id", - description="The ID of the message to force to starboard.", - option_type=4, - required=True, - ), - ], - ) - async def starboard_force(self, ctx, message_id): - pass - - @cog_ext.cog_subcommand( - base="starboard", - base_desc="Force, lock, or remove a starboard message.", - name="lock", - description="Lock a message on the starboard. This prevents it from getting or losing stars.", - options=[ - create_option( - name="message_id", - description="The ID of the message to lock on the starboard.", - option_type=4, - required=True, - ), - ], - ) - async def starboard_lock(self, ctx, message_id): - pass - - @cog_ext.cog_subcommand( - base="starboard", - base_desc="Force, lock, or remove a starboard message.", - name="remove", - description="Remove a starboard message. This prevents it from coming back to the starboard.", - options=[ - create_option( - name="message_id", - description="The ID of the message to delete from the starboard.", - option_type=4, - required=True, - ), - ], - ) - async def starboard_remove(self, ctx, message_id): - pass - - -def setup(bot: commands.Bot): - bot.add_cog(Starboard(bot)) diff --git a/bot/cogs/suggestions.py b/bot/cogs/suggestions.py deleted file mode 100644 index 1123614..0000000 --- a/bot/cogs/suggestions.py +++ /dev/null @@ -1,411 +0,0 @@ -import asyncio -import json - -import discord -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_choice, create_option - -import bot.cogs.core.settings -from bot.helpers import tools - - -class Suggestions( - commands.Cog, - name="suggestions", - description="A group of commands related to suggesting improvements for a server.", -): - def __init__(self, bot: commands.Bot): - self.bot = bot - - @cog_ext.cog_subcommand( - base="suggest", - base_desc="Create a suggestion.", - name="server", - description="Create a server suggestion.", - options=[ - create_option( - name="suggestion", - description="The suggestion you want to make.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="reason", - description="The reason for your suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="notes", - description="The notes you want to add to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="image_url", - description="The URL of the image to attach to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - async def suggest_server( - self, - ctx: SlashContext, - suggestion: str, - reason: str = None, - notes: str = None, - image_url: str = None, - ) -> None: - await self.create_suggestion_slash( - ctx, - suggestion, - reason, - notes, - image_url, - "Server Suggestion", - color=discord.Color.gold(), - ) - - @cog_ext.cog_subcommand( - base="suggest", - base_desc="Create a suggestion.", - name="movie", - description="Create a movie suggestion.", - options=[ - create_option( - name="suggestion", - description="The suggestion you want to make.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="reason", - description="The reason for your suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="notes", - description="The notes you want to add to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="image_url", - description="The URL of the image to attach to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - async def suggest_movie( - self, - ctx: SlashContext, - suggestion: str, - reason: str = None, - notes: str = None, - image_url: str = None, - ) -> None: - await self.create_suggestion_slash( - ctx, - suggestion, - reason, - notes, - image_url, - "Movie Suggestion", - color=discord.Color.green(), - ) - - @cog_ext.cog_subcommand( - base="suggest", - base_desc="Create a suggestion.", - name="bot", - description="Create a bot suggestion.", - options=[ - create_option( - name="suggestion", - description="The suggestion you want to make.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="reason", - description="The reason for your suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="notes", - description="The notes you want to add to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="image_url", - description="The URL of the image to attach to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - async def suggest_bot( - self, - ctx: SlashContext, - suggestion: str, - reason: str = None, - notes: str = None, - image_url: str = None, - ) -> None: - await self.create_suggestion_slash( - ctx, - suggestion, - reason, - notes, - image_url, - "Bot Suggestion", - color=discord.Color.purple(), - ) - - @cog_ext.cog_subcommand( - base="suggest", - base_desc="Create a suggestion.", - name="rule", - description="Create a rule suggestion.", - options=[ - create_option( - name="suggestion", - description="The suggestion you want to make.", - option_type=SlashCommandOptionType.STRING, - required=True, - ), - create_option( - name="reason", - description="The reason for your suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="notes", - description="The notes you want to add to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - create_option( - name="image_url", - description="The URL of the image to attach to the suggestion.", - option_type=SlashCommandOptionType.STRING, - required=False, - ), - ], - ) - async def suggest_rule( - self, - ctx: SlashContext, - suggestion: str, - reason: str = None, - notes: str = None, - image_url: str = None, - ) -> None: - await self.create_suggestion_slash( - ctx, - suggestion, - reason, - notes, - image_url, - "Rule Suggestion", - color=discord.Color.blue(), - ) - - async def create_suggestion_slash( - self, - ctx: SlashContext, - suggestion: str, - reason: str, - notes: str, - image_url: str, - title: str, - color: discord.Colour, - downvote: bool = True, - ) -> None: - settings_cog = self.bot.get_cog("settings") - settings = await settings_cog.get_guild_settings(ctx.guild.id) - suggestions_channel = ctx.guild.get_channel(settings["suggestions"]["channel"]) - embed = tools.create_embed( - ctx, title, desc=suggestion, footer_enabled=False, color=color - ) - if reason: - embed.add_field(name="Reason", value=reason, inline=False) - if notes: - embed.add_field(name="Notes", value=notes, inline=False) - if image_url: - embed.set_image(url=image_url) - msg = await suggestions_channel.send(embed=embed) - await msg.add_reaction(settings["suggestions"]["up_emoji"]) - if downvote: - await msg.add_reaction(settings["suggestions"]["down_emoji"]) - - embed = tools.create_embed( - ctx, - title, - desc="Your suggestion has been submitted successfully!", - color=color, - ) - await ctx.send(embed=embed) - - # -------------------------------------------- - # LEGACY COMMANDS - # -------------------------------------------- - - @commands.group(name="suggest") - @commands.cooldown(1, 900, type=commands.BucketType.user) - async def suggest_legacy(self, ctx: commands.Context) -> None: - """Suggest something for the server. - Suggestions will go into #suggestions. - The bot will prompt for the reason for the suggestion, then any notes. - You may specify "none" for either the reason or the notes. - """ - if ctx.invoked_subcommand is None: - embed = tools.create_embed( - ctx, - "Suggestion", - desc=f"Please specify a category for your suggestion.\nThe available categories are `server`, `bot`, `movie`, and `rule`.\nThe command's usage is `{ctx.prefix}suggest `", - ) - await ctx.send(embed=embed) - ctx.command.reset_cooldown(ctx) - - @suggest_legacy.command(name="server") - async def suggest_server_legacy( - self, ctx: commands.Context, *, suggestion: str - ) -> None: - await self.create_suggestion( - ctx, suggestion, "Server Suggestion", color=discord.Color.gold() - ) - - @suggest_legacy.command(name="movie") - async def suggest_movie_legacy( - self, ctx: commands.Context, *, suggestion: str - ) -> None: - await self.create_suggestion( - ctx, - suggestion, - "Movie Suggestion", - color=discord.Color.green(), - downvote=False, - ) - - @suggest_legacy.command(name="bot") - async def suggest_bot_legacy( - self, ctx: commands.Context, *, suggestion: str - ) -> None: - await self.create_suggestion( - ctx, suggestion, "Bot Suggestion", color=discord.Color.purple() - ) - - @suggest_legacy.command(name="rule") - async def suggest_rule_legacy( - self, ctx: commands.Context, *, suggestion: str - ) -> None: - await self.create_suggestion( - ctx, suggestion, "Rule Suggestion", color=discord.Color.blue() - ) - - async def create_suggestion( - self, - ctx: commands.Context, - suggestion: str, - title: str, - color: discord.Color, - downvote: bool = True, - ) -> None: - def check(msg): - return msg.author == ctx.author and msg.channel == ctx.channel - - embed = tools.create_embed( - ctx, "Suggestion Reason", "What is the reason for your suggestion?" - ) - await ctx.send(embed=embed) - msg = await self.bot.wait_for("message", check=check, timeout=180) - if msg.content.lower() == "none": - reason = None - elif msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Suggestion has been aborted.") - await ctx.send(embed=embed) - return - else: - reason = msg.content - - embed = tools.create_embed( - ctx, - "Suggestion Notes", - 'What else you would like to add? Type "none" if you don\'t have anything else.', - ) - await ctx.send(embed=embed) - msg = await self.bot.wait_for("message", check=check, timeout=180) - if msg.content.lower() == "none": - notes = None - elif msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Suggestion has been aborted.") - await ctx.send(embed=embed) - return - else: - notes = msg.content - - embed = tools.create_embed( - ctx, - "Suggestion Image", - 'Do you have an image to attach to the suggestion? Reply with "none" if you don\'t.', - ) - await ctx.send(embed=embed) - msg = await self.bot.wait_for("message", check=check, timeout=180) - if msg.content.lower() == "none": - image_url = None - elif msg.content.lower() == "stop": - embed = tools.create_error_embed(ctx, "Suggestion has been aborted.") - await ctx.send(embed=embed) - return - else: - image_url = msg.attachments[0].url - - settings_cog = self.bot.get_cog("settings") - settings = await settings_cog.get_guild_settings(ctx.guild.id) - suggestions_channel = ctx.guild.get_channel(settings["suggestions"]["channel"]) - embed = tools.create_embed( - ctx, title, desc=suggestion, footer_enabled=False, color=color - ) - if reason: - embed.add_field(name="Reason", value=reason, inline=False) - if notes: - embed.add_field(name="Notes", value=notes, inline=False) - if image_url: - embed.set_image(url=image_url) - msg = await suggestions_channel.send(embed=embed) - await msg.add_reaction(settings["suggestions"]["up_emoji"]) - if downvote: - await msg.add_reaction(settings["suggestions"]["down_emoji"]) - - embed = tools.create_embed( - ctx, - title, - desc="Your suggestion has been submitted successfully!", - color=color, - ) - await ctx.send(embed=embed) - - # @commands.command() - # @commands.has_permissions(manage_messages=True) - # async def removesuggestion(self, ctx, id: int): - # """This command allows for anyone with the "Manage Messages" - # permission to remove a suggestion.""" - # suggestions_channel = self.bot.get_channel(818901195023843368) - # msg = await suggestions_channel.fetch_message(id) - # await msg.delete() - # desc = f"Suggestion with ID {id} has been removed." - # embed = tools.create_embed(ctx, "Suggestion Removal", desc=desc) - # await ctx.send(embed=embed) - - -def setup(bot: commands.Bot): - bot.add_cog(Suggestions(bot)) diff --git a/bot/cogs/test.py b/bot/cogs/test.py deleted file mode 100644 index a962800..0000000 --- a/bot/cogs/test.py +++ /dev/null @@ -1,93 +0,0 @@ -import asyncio -import random - -import aiohttp -import discord -from discord.ext import commands -from discord_components import Button, ButtonStyle, InteractionType -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_option - -from bot.cogs.embeds import ButtonEmbedCreator -from bot.helpers import tools - - -class Test(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command() - async def button(self, ctx: commands.Context) -> None: - await ctx.send("Hello, World!", components=[Button(label="WOW button!")]) - - interaction = await self.bot.wait_for( - "button_click", check=lambda i: i.component.label.startswith("WOW") - ) - await interaction.respond(content="Button clicked!") - - @commands.command() - async def paginator(self, ctx: commands.Context) -> None: - embeds = [ - discord.Embed(title="page1"), - discord.Embed(title="page2"), - discord.Embed(title="page3"), - ] - paginator = tools.EmbedButtonPaginator(self.bot, ctx, embeds) - await paginator.run() - - @commands.command() - async def sendembedtest(self, ctx: commands.Context) -> None: - embedcreator = ButtonEmbedCreator(self.bot, ctx, ctx.channel.id) - await embedcreator.run() - - @commands.command() - async def nitro(self, ctx: commands.Context, user: discord.User = None) -> None: - await ctx.message.delete() - embed = discord.Embed(title="Nitro", description="Expires in 18 hours") - embed.set_author(name="A WILD GIFT APPEARS!") - embed.set_thumbnail( - url="https://media.discordapp.net/attachments/820637070317060116/856253399225729044/EmSIbDzXYAAb4R7.png" - ) - EM4 = " " - if user: - destination = user - else: - destination = ctx - - message = await destination.send( - embed=embed, - components=[ - Button( - label=(27 * EM4) + "ACCEPT" + (27 * EM4), - style=ButtonStyle.green, - id="rickroll", - ) - ], - ) - while True: - try: - interaction = await self.bot.wait_for( - "button_click", - check=lambda i: i.message.id == message.id, - timeout=300.0, - ) - - embed = discord.Embed( - description="lol noob imagine getting rickrolled that would be kinda cringe ngl" - ) - embed.set_image( - url="https://media1.tenor.com/images/8c409e6f39acc1bd796e8031747f19ad/tenor.gif" - ) - await interaction.respond( - type=InteractionType.ChannelMessageWithSource, - ephemeral=True, - embed=embed, - ) - - except asyncio.TimeoutError: - return - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Test(bot)) \ No newline at end of file diff --git a/bot/cogs/voting.py b/bot/cogs/voting.py new file mode 100644 index 0000000..8954b19 --- /dev/null +++ b/bot/cogs/voting.py @@ -0,0 +1,172 @@ +import discord +from discord import app_commands +from discord.ext import commands + +from bot.helpers import tools + + +class VoteView(discord.ui.View): + text: str + number_required: int + role: discord.Role | None + anonymous: bool + votes: dict[int, str] + + def __init__( + self, + text: str, + number_required: int, + role: discord.Role | None = None, + anonymous: bool = False, + ): + super().__init__(timeout=None) + self.text = text + self.number_required = number_required + self.role = role + self.anonymous = anonymous + self.votes = {} + + def create_embed(self, completed: bool = False) -> discord.Embed: + embed = tools.create_embed("Vote", self.text) + embed.add_field(name="Votes Required", value=self.number_required, inline=False) + if self.role: + embed.add_field(name="Required Role", value=self.role.mention, inline=False) + + if self.anonymous: + embed.add_field(name="For", value=list(self.votes.values()).count("yes")) + embed.add_field(name="Against", value=list(self.votes.values()).count("no")) + embed.add_field( + name="Abstaining", value=list(self.votes.values()).count("abstain") + ) + else: + embed.add_field( + name="For", + value="\n".join( + [ + f"<@{user_id}>" + for user_id, vote in self.votes.items() + if vote == "yes" + ] + ) + if list(self.votes.values()).count("yes") > 0 + else "None", + ) + embed.add_field( + name="Against", + value="\n".join( + [ + f"<@{user_id}>" + for user_id, vote in self.votes.items() + if vote == "no" + ] + ) + if list(self.votes.values()).count("no") > 0 + else "None", + ) + embed.add_field( + name="Abstaining", + value="\n".join( + [ + f"<@{user_id}>" + for user_id, vote in self.votes.items() + if vote == "abstain" + ] + ) + if list(self.votes.values()).count("abstain") > 0 + else "None", + ) + + if completed: + embed.title = "Vote Results" + embed.color = discord.Colour.green() + return embed + + async def process_vote(self, interaction: discord.Interaction, vote: str): + prev_vote = self.votes.get(interaction.user.id) + self.votes[interaction.user.id] = vote + if prev_vote == vote: + await interaction.response.send_message( + embed=tools.create_embed( + "Vote", "You have already voted for this option." + ), + ephemeral=True, + ) + elif prev_vote: + await interaction.response.send_message( + embed=tools.create_embed( + "Vote", + f"Your vote of {prev_vote.title()} has been moved to {vote.title()}.", + ), + ephemeral=True, + ) + else: + await interaction.response.send_message( + embed=tools.create_embed( + "Vote", f"Your vote of '{vote.title()}' has been added." + ), + ephemeral=True, + ) + + completed = ( + len([vote for user_id, vote in self.votes.items() if vote in ["yes", "no"]]) + >= self.number_required + ) + if completed: + self.stop() + for child in self.children: + child.disabled = True + + await interaction.message.edit( + embed=self.create_embed(completed=completed), view=self + ) + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + if not self.role or self.role in interaction.user.roles: + return True + else: + await interaction.response.send_message( + embed=tools.create_error_embed("You cannot vote in this."), + ephemeral=True, + ) + return False + + @discord.ui.button(label="Yes", style=discord.ButtonStyle.green) + async def yes(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.process_vote(interaction, "yes") + + @discord.ui.button(label="No", style=discord.ButtonStyle.red) + async def no(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.process_vote(interaction, "no") + + @discord.ui.button(label="Abstain", style=discord.ButtonStyle.gray) + async def abstain( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await self.process_vote(interaction, "abstain") + + +class Voting(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.hybrid_command(description="Create a vote.") + @app_commands.describe( + text="The thing to vote for.", + number_required="The number of votes required on either side to close the vote.", + role="The role to restrict voting to.", + anonymous="Whether to make voting anonymous (display does not show who voted for what). Default is False.", + ) + async def mkvote( + self, + ctx: commands.Context, + text: str, + number_required: int, + role: discord.Role | None = None, + anonymous: bool = False, + ) -> None: + view = VoteView(text, number_required, role, anonymous) + await ctx.send(embed=view.create_embed(), view=view) + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Voting(bot)) diff --git a/bot/cogs/wip/colors.py b/bot/cogs/wip/colors.py deleted file mode 100644 index bf686e9..0000000 --- a/bot/cogs/wip/colors.py +++ /dev/null @@ -1,202 +0,0 @@ -import random -import logging -from typing import Optional - -import aiohttp -import discord -from discord.ext import commands, tasks -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType -from discord_slash.utils.manage_commands import create_option - -from bot.helpers import tools - - -class Colors(commands.Cog, name="colors"): - COLORS = [ - (255, 62, 62), - (255, 147, 62), - (255, 215, 62), - (133, 255, 62), - (56, 255, 202), - (56, 167, 255), - (173, 56, 255), - (243, 56, 255), - ] - - def __init__(self, bot: commands.Bot): - self.bot = bot - self.color = 0 - _logger = logging.getLogger(__name__) - self.logger = logging.LoggerAdapter(_logger, {"botname": self.bot.name}) - self.change_color.start() - - def get_rainbow_role(self, guild: discord.Guild) -> Optional[discord.Role]: - rainbow_role = None - for role in guild.roles: - if role.name == "Rainbow": - rainbow_role = role - return rainbow_role - - @tasks.loop(seconds=5.0) - async def change_color(self): - for guild in self.bot.guilds: - rainbow_role = self.get_rainbow_role(guild) - if rainbow_role: - try: - await rainbow_role.edit( - colour=discord.Colour.from_rgb( - self.COLORS[self.color][0], - self.COLORS[self.color][1], - self.COLORS[self.color][2], - ) - ) - except: - self.logger.error("woops", exc_info=True) - - self.color = self.color + 1 if self.color + 1 <= 7 else 0 - - @cog_ext.cog_slash( - name="createrainbowrole", - description="Create the rainbow role for the bot to use!", - ) - @commands.has_permissions(manage_roles=True) - async def createrainbowrole(self, ctx: SlashContext) -> None: - rainbow_role = self.get_rainbow_role(ctx.guild) - if rainbow_role: - embed = tools.create_error_embed( - ctx, desc="The rainbow role already exists." - ) - await ctx.send(embed=embed) - return - - try: - await ctx.guild.create_role( - name="Rainbow", - colour=discord.Colour.from_rgb( - self.COLORS[self.color][0], - self.COLORS[self.color][1], - self.COLORS[self.color][2], - ), - ) - except: - embed = tools.create_error_embed(ctx, "Could not create the rainbow role.") - await ctx.send(embed=embed) - else: - embed = tools.create_embed( - ctx, - "Rainbow Role", - desc='The rainbow role has been created. Do not rename or delete the role named "Rainbow". It will update every 30 seconds.\n**Make sure to move the bot\'s role above the rainbow role, otherwise it will not be able to change colors.**', - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="deleterainbowrole", - description="Delete the rainbow role for the server.", - ) - @commands.has_permissions(manage_roles=True) - async def deleterainbowrole(self, ctx: SlashContext) -> None: - rainbow_role = self.get_rainbow_role(ctx.guild) - if not rainbow_role: - embed = tools.create_error_embed(ctx, "The rainbow role does not exist.") - await ctx.send(embed=embed) - return - try: - await rainbow_role.delete() - except: - embed = tools.create_error_embed(ctx, "Could not delete the rainbow role.") - await ctx.send(embed=embed) - else: - embed = tools.create_embed( - ctx, - "Rainbow Role", - desc="The rainbow role has been deleted.", - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="botrainbowrole", - description="Give the bot the rainbow role.", - ) - @commands.has_permissions(manage_roles=True) - async def botrole(self, ctx: SlashContext) -> None: - rainbow_role = self.get_rainbow_role(ctx.guild) - try: - await ctx.guild.me.add_roles(rainbow_role) - except: - embed = tools.create_error_embed( - ctx, - desc="The bot could not be given the rainbow role.", - ) - await ctx.send(embed=embed) - else: - embed = tools.create_embed( - ctx, - "Bot Rainbow", - desc="The bot has been given the rainbow role.", - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="giverainbowrole", - description="Give yourself the rainbow role.", - ) - async def giverainbowrole(self, ctx: SlashContext) -> None: - rainbow_role = self.get_rainbow_role(ctx.guild) - try: - await ctx.author.add_roles(rainbow_role) - except Exception as e: - print(e) - embed = tools.create_error_embed( - ctx, - desc="You could not be given the rainbow role.", - ) - await ctx.send(embed=embed) - else: - embed = tools.create_embed( - ctx, - "User Rainbow", - desc="You have been given the rainbow role.", - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="removerainbowrole", - description="Remove the rainbow role from yourself.", - ) - async def removerainbowrole(self, ctx: SlashContext) -> None: - rainbow_role = self.get_rainbow_role(ctx.guild) - try: - await ctx.author.remove_roles(rainbow_role) - except: - embed = tools.create_error_embed( - ctx, - desc="The rainbow role could not be removed from you.", - ) - await ctx.send(embed=embed) - else: - embed = tools.create_embed( - ctx, - "User Rainbow", - desc="The rainbow role has been removed from you.", - ) - await ctx.send(embed=embed) - - @cog_ext.cog_slash( - name="invite", - description="Invite the bot to another server!", - ) - async def invite(self, ctx: SlashContext) -> None: - embed = tools.create_embed( - ctx, - "Colors+ Invite", - desc="Here's an invite for Colors+ (with slash commands and Manage Roles).", - ) - await ctx.send( - content="https://discord.com/api/oauth2/authorize?client_id=851852969770090516&permissions=268435456&scope=bot%20applications.commands", - embed=embed, - ) - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Colors(bot)) diff --git a/bot/cogs/wip/custom.py b/bot/cogs/wip/custom.py deleted file mode 100644 index 312b8fc..0000000 --- a/bot/cogs/wip/custom.py +++ /dev/null @@ -1,13 +0,0 @@ -import discord -from discord.ext import commands - -from bot.helpers import tools - - -class Custom(commands.Cog): - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Custom(bot)) diff --git a/bot/cogs/wip/economy.py b/bot/cogs/wip/economy.py deleted file mode 100644 index d71c57c..0000000 --- a/bot/cogs/wip/economy.py +++ /dev/null @@ -1,61 +0,0 @@ -import json -import random - -import discord -from discord.ext import commands - -from bot.helpers import tools - - -class Economy(commands.Cog): - def __init__(self, bot): - self.bot = bot - - def _registration_checks(self, ctx): - with open("bot/currency.json", "r") as f: - currency = json.load(f) - if str(ctx.author.id) not in currency: - with open("bot/currency.json", "w") as f: - currency[str(ctx.author.id)] = 0 - json.dump(currency, f) - - def _get_currency_dict(self, ctx): - self._registration_checks(ctx) - with open("bot/currency.json", "r") as f: - currency = json.load(f) - return currency - - def _set_currency_dict(self, ctx, currency): - self._registration_checks(ctx) - with open("bot/currency.json", "w") as f: - json.dump(currency, f) - - def increment_coins(self, ctx, coins: int): - currency = self._get_currency_dict(ctx) - currency[str(ctx.author.id)] += coins - self._set_currency_dict(ctx, currency) - - async def command_coins(self, ctx, max_coin_count: int = 5): - if random.randint(0, 100) >= 80: - coin_count = random.randint(2, max_coin_count) - self.increment_coins(ctx, coin_count) - color = discord.Color.green() - embed = discord.Embed( - title="Coins!", - description=f"Nice! You got {coin_count} coins!", - color=color, - ) - await ctx.send(embed=embed) - - @commands.command(aliases=["bal"]) - async def balance(self, ctx): - """Get your balance.""" - bal = self._get_currency_dict(ctx) - embed = tools.create_embed( - ctx, "Balance", f"You have {bal[str(ctx.author.id)]} coins." - ) - await ctx.send(embed=embed) - - -def setup(bot): - bot.add_cog(Economy(bot)) diff --git a/bot/cogs/wip/greetings.py b/bot/cogs/wip/greetings.py deleted file mode 100644 index 299ecd5..0000000 --- a/bot/cogs/wip/greetings.py +++ /dev/null @@ -1,24 +0,0 @@ -import discord -from discord.ext import commands - -from bot.helpers import tools - - -class MemberJoin(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.Cog.listener() - async def on_member_join(self, member: discord.Member) -> None: - print("bru") - guild = member.guild - if guild.id == 852039928589713429: - role = guild.get_role(852047902024400957) - try: - await member.add_roles(role) - except: - pass - - -def setup(bot: commands.Bot): - bot.add_cog(MemberJoin(bot)) diff --git a/bot/helpers/tools.py b/bot/helpers/tools.py index a088f7f..fe80b5d 100644 --- a/bot/helpers/tools.py +++ b/bot/helpers/tools.py @@ -1,202 +1,322 @@ -import asyncio -from typing import Union +import random +import typing +from datetime import datetime import discord from discord.ext import commands -from discord_slash.context import ComponentContext, SlashContext -from discord_slash.utils.manage_components import ( - create_button, - create_actionrow, - create_select, - create_select_option, - wait_for_component, -) -from discord_slash.model import ButtonStyle, ComponentType def create_embed( - ctx: Union[commands.Context, SlashContext], title: str, desc: str = "", url: str = None, color: discord.Colour = None, - footer_enabled: bool = True, ) -> discord.Embed: - if not color: - color = discord.Embed.Empty - embed = discord.Embed(title=title, description=desc, color=color) + embed = discord.Embed( + title=title, + description=desc, + timestamp=datetime.now(), + colour=random.choice( + [discord.Colour.from_str("#FBBF05"), discord.Colour.from_str("#0F64FA")] + ), + ) if url: embed.url = url - embed.set_author(name=ctx.author, icon_url=ctx.author.avatar_url) - if footer_enabled: - if ctx.channel.type is not discord.ChannelType.private: - embed.set_footer( - text=f"Server: {ctx.guild} | Command: {ctx.command}", - icon_url=ctx.guild.icon_url, - ) - else: - embed.set_footer(text=f"Server: DMs | Command: {ctx.command}") + # embed.set_footer( + # text=random.choice( + # ["*vrrrrrrrrrrrrr*", "*frisbee shooting noises*", "*beep boop*"] + # ), + # # icon_url="https://raw.githubusercontent.com/frc868/flick/master/icon_footer.png", + # ) return embed -def create_error_embed( - ctx: Union[commands.Context, SlashContext], desc: str -) -> discord.Embed: +def create_error_embed(desc: str) -> discord.Embed: color = discord.Color.red() embed = discord.Embed(title="Error", description=desc, color=color) - embed.set_author(name=ctx.author, icon_url=ctx.author.avatar_url) - if ctx.channel.type is not discord.ChannelType.private: - embed.set_footer( - text=f"Server: {ctx.guild} | Command: {ctx.command}", - icon_url=ctx.guild.icon_url, - ) - else: - embed.set_footer(text=f"Server: DMs | Command: {ctx.command}") + embed.set_footer( + text=random.choice( + ["*vrrrrrrrrrrrrr*", "*frisbee shooting noises*", "*beep boop*"] + ), + icon_url="https://raw.githubusercontent.com/frc868/flick/master/icon_footer.png", + ) return embed -class EmbedReactionPaginator: - REACTIONS = {"first": "⏮", "back": "◀️", "forward": "▶️", "last": "⏭", "stop": "⏹"} +class ViewBase(discord.ui.View): + user: discord.User + msg: discord.Message # any Views must have msg set when the message is sent (e.g. `view.msg = await ctx.send(view=view)`) + + def __init__(self, user: discord.User, timeout: float = 180.0) -> None: + super().__init__(timeout=timeout) + self.user = user + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + if interaction.user.id == self.user.id: + return True + else: + await interaction.response.send_message( + embed=create_error_embed("You can't use another user's menu."), + ephemeral=True, + ) + return False + + def disable(self) -> None: + for child in self.children: + child.disabled = True + self.stop() + + async def on_timeout(self) -> None: + self.disable() + try: + await self.msg.edit(view=self) + except: + pass + + +class Confirmation(ViewBase): + accepted: bool + + def __init__(self, user: discord.User, timeout: float = 300.0): + super().__init__(user, timeout=timeout) + self.accepted = None + + @discord.ui.button(label="Confirm", style=discord.ButtonStyle.green) + async def confirm( + self, interaction: discord.Interaction, button: discord.ui.Button + ): + await interaction.response.defer() + self.accepted = True + self.stop() + + @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red) + async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.defer() + self.accepted = False + self.stop() + + +class JumpToPage(discord.ui.Modal, title="Jump To Page"): + page_number = discord.ui.TextInput(label="Page Number") + target: int + interaction: discord.Interaction + sucessful: bool + + async def on_submit(self, interaction: discord.Interaction): + try: + self.target = int(self.page_number.value) + self.interaction = interaction # implication of not responding to interaction here is that EmbedButtonPaginator MUST respond within 3 seconds + self.successful = True + except: + await interaction.response.send_message( + embed=create_error_embed("Inputted page number is not an integer."), + ephemeral=True, + ) + self.successful = False + + +class EmbedButtonPaginator(ViewBase): + embeds: list[discord.Embed] + page_index: int + callback: typing.Callable[[int], discord.Embed] def __init__( self, - bot: commands.Bot, - ctx: Union[commands.Context, SlashContext], - embeds: list, + user: discord.User, + embeds: list[discord.Embed], + initial_page_index: int = 0, + callback: typing.Callable = None, ): - self.ctx = ctx - self.bot = bot - self.embed_pages = embeds - self.page_index = 0 + super().__init__(user, timeout=180.0) + self.embeds = embeds + self.page_index = initial_page_index + self.callback = callback if callback else lambda page: self.embeds[page] + self.update_buttons() # to make the page button has a correct label - async def run(self): - self.message = await self.ctx.send(embed=self.embed_pages[self.page_index]) + def get_page(self, page: int): + return self.callback(page) - def check(reaction, user): - return ( - reaction.message.id == self.message.id and user.id == self.ctx.author.id - ) + def update_buttons(self): + self.first.disabled = self.page_index == 0 + self.prev.disabled = self.page_index == 0 + self.next.disabled = self.page_index == len(self.embeds) - 1 + self.last.disabled = self.page_index == len(self.embeds) - 1 - for reaction in self.REACTIONS.values(): - await self.message.add_reaction(reaction) + self.page.label = f"Page {self.page_index+1}/{len(self.embeds)}" - while True: + @discord.ui.button(label="First", style=discord.ButtonStyle.blurple) + async def first(self, interaction: discord.Interaction, button: discord.Button): + self.page_index = 0 + self.update_buttons() + await interaction.response.edit_message( + embed=self.get_page(self.page_index), view=self + ) + + @discord.ui.button(label="Prev", style=discord.ButtonStyle.green) + async def prev(self, interaction: discord.Interaction, button: discord.Button): + self.page_index -= 1 + self.update_buttons() + await interaction.response.edit_message( + embed=self.get_page(self.page_index), view=self + ) + + @discord.ui.button(label=f"Page", style=discord.ButtonStyle.gray) + async def page(self, interaction: discord.Interaction, button: discord.Button): + modal = JumpToPage() + await interaction.response.send_modal(modal) + await modal.wait() + if modal.successful: try: - reaction, user = await self.bot.wait_for( - "reaction_add", check=check, timeout=180 + if modal.target - 1 < 0 or modal.target - 1 > len(self.embeds) - 1: + raise ValueError() + self.page_index = modal.target - 1 + except: + await modal.interaction.response.send_message( + embed=create_error_embed("Invalid page number."), ephemeral=True ) - except asyncio.TimeoutError: - await self.message.clear_reactions() return + self.update_buttons() + await modal.interaction.response.edit_message( + embed=self.get_page(self.page_index), view=self + ) - if reaction.emoji == self.REACTIONS["first"]: - self.page_index = 0 - elif reaction.emoji == self.REACTIONS["back"]: - if self.page_index > 0: - self.page_index -= 1 - elif reaction.emoji == self.REACTIONS["forward"]: - if self.page_index < len(self.embed_pages) - 1: - self.page_index += 1 - elif reaction.emoji == self.REACTIONS["last"]: - self.page_index = len(self.embed_pages) - 1 - elif reaction.emoji == self.REACTIONS["stop"]: - await self.message.delete() - return + @discord.ui.button(label="Next", style=discord.ButtonStyle.green) + async def next(self, interaction: discord.Interaction, button: discord.Button): + self.page_index += 1 + self.update_buttons() + await interaction.response.edit_message( + embed=self.get_page(self.page_index), view=self + ) + + @discord.ui.button(label="Last", style=discord.ButtonStyle.blurple) + async def last(self, interaction: discord.Interaction, button: discord.Button): + self.page_index = len(self.embeds) - 1 + self.update_buttons() + await interaction.response.edit_message( + embed=self.get_page(self.page_index), view=self + ) - await self.message.edit(embed=self.embed_pages[self.page_index]) - await reaction.remove(user) +class RoleSelectorButton(discord.ui.Button): + role: discord.Role + other_roles: list[discord.Role] + embed_title: str + role_map: dict[int, str] + mutually_exclusive: bool -class EmbedButtonPaginator: def __init__( self, - bot: commands.Bot, - ctx: Union[commands.Context, SlashContext], - embeds: list[discord.Embed], - ): - self.bot = bot - self.ctx = ctx - self.embed_pages = embeds - self.page_index = 0 - - def create_buttons(self, disabled: bool = False) -> list[dict]: - return [ - create_actionrow( - create_button( - label="First", - style=ButtonStyle.green, - custom_id="first", - disabled=disabled, - ), - create_button( - label="Prev", - style=ButtonStyle.blue, - custom_id="prev", - disabled=disabled, - ), - create_button( - label=f"Page {self.page_index+1}/{len(self.embed_pages)}", - style=ButtonStyle.gray, - custom_id="pagenum", - disabled=True, - ), - create_button( - label="Next", - style=ButtonStyle.blue, - custom_id="next", - disabled=disabled, - ), - create_button( - label="Last", - style=ButtonStyle.green, - custom_id="last", - disabled=disabled, - ), - ), - ] - - async def pagination_events(self, component_ctx: ComponentContext): - if component_ctx.custom_id == "first": - self.page_index = 0 - elif component_ctx.custom_id == "prev": - if self.page_index > 0: - self.page_index -= 1 - elif component_ctx.custom_id == "next": - if self.page_index < len(self.embed_pages) - 1: - self.page_index += 1 - elif component_ctx.custom_id == "last": - self.page_index = len(self.embed_pages) - 1 - - async def run(self): - self.message = await self.ctx.send( - embed=self.embed_pages[self.page_index], components=self.create_buttons() + role: discord.Role, + all_roles: list[discord.Role], + embed_title: str, + custom_id: str | None = None, + mutually_exclusive: bool = True, + ) -> None: + super().__init__( + style=discord.ButtonStyle.gray, label=role.name, custom_id=custom_id ) + self.role = role + self.other_roles = [role for role in all_roles if role != self.role] + self.embed_title = embed_title + self.mutually_exclusive = mutually_exclusive - while True: - try: - component_ctx: ComponentContext = await wait_for_component( - self.bot, - messages=self.message, - components=["first", "prev", "next", "last", "stop"], - timeout=180.0, + async def callback(self, interaction: discord.Interaction) -> None: + assert self.view is not None + view: RoleSelector | PersistentRoleSelector = self.view + + if self.mutually_exclusive: + if interaction.user.get_role(self.role.id): + embed = create_embed( + self.embed_title, f"You are already in {self.role.name}." ) - if component_ctx.author.id != self.ctx.author.id: - await component_ctx.send( - hidden=True, - embed=discord.Embed( - title="Error", - description="You can't control another member's buttons.", - colour=discord.Colour.red(), - ), - ) - else: - await self.pagination_events(component_ctx) - - await component_ctx.edit_origin( - embed=self.embed_pages[self.page_index], - components=self.create_buttons(), + elif self.get_prior_role(interaction.user): + embed = create_embed( + self.embed_title, + f"You have moved from {self.get_prior_role(interaction.user)} to {self.role.name}.", ) + else: + embed = create_embed( + self.embed_title, f"You have been added to {self.role.name}." + ) + await self.assign_role(interaction) + else: + if interaction.user.get_role(self.role.id): + embed = create_embed( + self.embed_title, f"You have been removed from {self.role.name}." + ) + await interaction.user.remove_roles(self.role) + else: + embed = create_embed( + self.embed_title, f"You have been added to {self.role.name}." + ) + await interaction.user.add_roles(self.role) - except asyncio.TimeoutError: - await self.message.edit(components=self.create_buttons(disabled=True)) - return + if self.is_persistent(): + await interaction.response.send_message( + embed=embed, + ephemeral=True, + ) + else: + self.style = discord.ButtonStyle.green + view.disable() + await interaction.response.edit_message( + embed=embed, + view=view, + ) + + def get_prior_role(self, user: discord.Member) -> str | None: + for role in self.other_roles: + if user.get_role(role.id): + return role.name + return None + + async def assign_role(self, interaction: discord.Interaction) -> None: + for role in self.other_roles: + if interaction.user.get_role(role.id): + await interaction.user.remove_roles(role) + + await interaction.user.add_roles(self.role) + + +class RoleSelector(ViewBase): + def __init__( + self, + user: discord.User, + guild: discord.Guild, + role_ids: typing.Iterable[int], + embed_title: str, + ) -> None: + super().__init__(user) + + roles = [guild.get_role(role_id) for role_id in role_ids] + for role in roles: + self.add_item(RoleSelectorButton(role, roles, embed_title)) + + +class PersistentRoleSelector(discord.ui.View): + def __init__( + self, + guild: discord.Guild, + role_ids: typing.Iterable[int], + embed_title: str, + custom_id_prefix: str, + mutually_exclusive: bool = True, + ) -> None: + super().__init__(timeout=None) + + roles = [guild.get_role(role_id) for role_id in role_ids] + for role in roles: + self.add_item( + RoleSelectorButton( + role, + roles, + embed_title, + f"{custom_id_prefix}:{role.id}", + mutually_exclusive, + ) + ) + + def disable(self) -> None: + for child in self.children: + child.disabled = True + self.stop() diff --git a/config.yml b/config.yml index 8dde503..c46252b 100644 --- a/config.yml +++ b/config.yml @@ -1,160 +1,32 @@ --- bots: - # - name: chsbot - # prefix: c? - # gateway_intents: true - # max_messages: - # disabled_mentions: - # everyone: false - # owner_id: 688530998920871969 - # token: - # name: CHSBOT_TOKEN - # value: - # env: true - # extra_data: - # - name: AZURE_KEY - # value: - # env: AZURE_KEY - # description: > - # Welcome to CHS Bot! - - # Visit `{bot.command_prefix}help` for a - # list of commands and how to use them. - # Visit `{bot.command_prefix}about` to - # see more information about the bot. - # extensions: - # enabled: - # disabled: - # custom: - # - bot.cogs.custom.chsbot.profanity - # use_default_help_command: false - - name: chsbotbeta - prefix: b - gateway_intents: true + - name: chsbot + prefix: c? max_messages: 10000 disabled_mentions: everyone: false - owner_id: 688530998920871969 token: - name: CHSBOTBETA_TOKEN + name: CHSBOT_TOKEN value: env: true - extra_data: - - name: AZURE_KEY - env: true - description: > - Welcome to CHS Bot Beta! - Here, the cutting edge of CHS Bot - commands will be developed. Have fun testing! + description: | + Welcome to CHS Bot! Visit `{bot.command_prefix}help` for a list of commands and how to use them. Visit `{bot.command_prefix}about` to see more information about the bot. extensions: - enabled: - disabled: - custom: - - bot.cogs.custom.chsbot.profanity + - bot.cogs.core.events + - bot.cogs.core.info + - bot.cogs.fun + - bot.cogs.math + - bot.cogs.games + - bot.cogs.voting + # - bot.cogs.techhounds + - bot.cogs.embeds + - bot.cogs.admin + - bot.cogs.moderation + # - bot.cogs.modlogs + # - bot.cogs.mail use_default_help_command: false - # - name: davidhackerman - # prefix: $ - # gateway_intents: true - # max_messages: 10000 - # disabled_mentions: - # everyone: false - # owner_id: 688530998920871969 - # client_id: 821888273936810014 - # token: - # name: DAVIDHACKERMAN_TOKEN - # value: - # env: true - # extra_data: - # - AZURE_KEY: .env - # description: > - # Welcome to davidhackerman! - - # Visit `{bot.command_prefix}help` for a - # list of commands and how to use them. - # Visit `{bot.command_prefix}about` to - # see more information about the bot. - # extensions: - # enabled: - # disabled: - # custom: - # - bot.cogs.custom.davidhackerman.bottling - # - bot.cogs.wip.greetings - # - bot.cogs.wip.custom - # use_default_help_command: false - - name: physics - prefix: "dick " - gateway_intents: true - max_messages: - disabled_mentions: - everyone: false - owner_id: 688530998920871969 - token: - name: APPHYSICSBOT_TOKEN - value: - env: true - extra_data: - - name: AZURE_KEY - value: - env: AZURE_KEY - description: > - Welcome to AP Physics Bot! - - Visit `{bot.command_prefix}help` for a - list of commands and how to use them. - Visit `{bot.command_prefix}about` to - see more information about the bot. - extensions: - enabled: - disabled: - custom: - use_default_help_command: false - # - name: nukeyboy - # prefix: _nuke_ - # gateway_intents: true - # max_messages: 10000 - # disabled_mentions: - # owner_id: 688530998920871969 - # client_id: 821888273936810014 - # token: - # name: NUKEYBOY_TOKEN - # value: - # env: true - # extra_data: - # description: - # extensions: - # enabled: - # disabled: - # custom: - # - bot.cogs.custom.nukeyboy.nuke - # use_default_help_command: false - # - name: colors - # prefix: colors? - # gateway_intents: false - # max_messages: 10000 - # disabled_mentions: - # everyone: false - # owner_id: 688530998920871969 - # client_id: 821888273936810014 - # token: - # name: COLORS_TOKEN - # value: - # env: true - # extra_data: - # description: > - # Welcome to Colors! - - # Visit `{bot.command_prefix}help` for a - # list of commands and how to use them. - # Visit `{bot.command_prefix}about` to - # see more information about the bot. - # extensions: - # enabled: - # - bot.cogs.wip.colors - # disabled: - # custom: - # use_default_help_command: false \ No newline at end of file diff --git a/logging.yml b/logging.yml index a21159c..7ca2d2e 100644 --- a/logging.yml +++ b/logging.yml @@ -5,12 +5,9 @@ formatters: discord: format: "[%(asctime)s] [%(levelname)s] [%(name)s] - %(message)s" commands: - format: > - "[%(asctime)s] [Bot: %botname)s] - [User: %(username)s ID: %(userid)s] [Guild: %(guild)s ID: %(guildid)s] - [Prefix: %(prefix)s] [Command: %(command)s] [Arguments: %(arguments)s]\n - Full message:\n - %(full)s" + format: | + [%(asctime)s] [%(botname)s] [%(username)s (%(userid)s) in %(guild)s (%(guildid)s)] + [Command: %(command)s] [Message: %(full)s] handlers: bot_console: class: logging.StreamHandler @@ -59,6 +56,6 @@ loggers: - discord_file # root: # level: INFO -# handlers: +# handlers: # - bot_console -# - bot_file \ No newline at end of file +# - bot_file diff --git a/main.py b/main.py index 5521d50..e624d4e 100644 --- a/main.py +++ b/main.py @@ -2,17 +2,17 @@ import logging import logging.config import os -import time import sys -import signal +import time import discord import dotenv import yaml from discord.ext import commands -from discord_slash import SlashCommand -from discord_components import DiscordComponents +from bot.helpers import tools + +logger = logging.getLogger(__name__) CORE_EXTENSIONS = [ "bot.cogs.core.events", @@ -24,29 +24,22 @@ ] EXTENSIONS = [ - # "bot.cogs.colors", - # "bot.cogs.economy", "bot.cogs.embeds", "bot.cogs.fun", "bot.cogs.games", - # "bot.cogs.greetings", - # "bot.cogs.links", "bot.cogs.math", "bot.cogs.moderation", - # "bot.cogs.modlogs", "bot.cogs.reaction_roles", "bot.cogs.search", "bot.cogs.starboard", "bot.cogs.suggestions", ] - with open("logging.yml", "r") as f: logging.config.dictConfig(yaml.load(f, Loader=yaml.SafeLoader)) -def main(): - logger = logging.getLogger(__name__) +async def main(): dotenv.load_dotenv() try: with open("config.yml", "r") as f: @@ -59,7 +52,7 @@ def main(): sys.exit(1) logger.info("Parsing config.yml.", extra={"botname": "N/A"}) - bots = [] + bots: list[commands.Bot] = [] for botconf in config["bots"]: adplogger = logging.LoggerAdapter(logger, extra={"botname": botconf["name"]}) try: @@ -92,82 +85,39 @@ def main(): help_command=commands.DefaultHelpCommand if botconf["use_default_help_command"] else None, - intents=discord.Intents.all() - if botconf["gateway_intents"] - else discord.Intents.default(), + intents=discord.Intents.all(), description=botconf["description"], ) + bot.owner_id = 688530998920871969 bot.name = botconf["name"] bot.token = ( os.environ[botconf["token"]["name"]] if botconf["token"]["env"] else botconf["token"]["value"] ) - bot.AZURE_KEY = os.environ["AZURE_KEY"] except: - adplogger.error( + adplogger.exception( "Failed to create bot instance. Check your config.yml file." ) - sys.exit(1) - adplogger.info( - "Activating Discord interactions (slash commands, buttons, and selects)." - ) - - slash = SlashCommand(bot, sync_commands=True) - # DiscordComponents(bot) + sys.exit(1) adplogger.info("Loading extensions.") - for extension in CORE_EXTENSIONS: - bot.load_extension(extension) - - for extension in EXTENSIONS: - bot.load_extension(extension) - - # if botconf["extensions"]["enabled"]: - # for extension in botconf["extensions"]["enabled"]: - # try: - # bot.load_extension(extension) - # except: - # adplogger.error( - # f"Unable to load extension {extension}. Check if the file exists." - # ) - # if botconf["extensions"]["disabled"]: - # for extension in EXTENSIONS: - # if extension not in botconf["extensions"]["disabled"]: - # bot.load_extension(extension) - - # if botconf["extensions"]["custom"]: - # for extension in botconf["extensions"]["custom"]: - # try: - # bot.load_extension(extension) - # except: - # adplogger.error( - # f"Unable to load extension {extension}. Check if the file exists.", - # exc_info=True, - # ) + for extension in botconf["extensions"]: + await bot.load_extension(extension) bots.append(bot) - loop = asyncio.get_event_loop() - try: - loop.add_signal_handler(signal.SIGINT, lambda: loop.stop()) - loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop()) - except NotImplementedError: - pass - for bot in bots: logger.info("Starting bot.", extra={"botname": bot.name}) - loop.create_task(bot.start(bot.token)) - - loop.run_forever() - # except KeyboardInterrupt: - # logger.info("Received signal to shut down bots.", extra={"botname": "N/A"}) - # loop.stop() - # except Exception as e: - # print(e) + async with bot: + await bot.start(bot.token) if __name__ == "__main__": - main() + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Received signal to shut down.", extra={"botname": "N/A"}) + exit(0)