diff --git a/.gitignore b/.gitignore index b110280..7a6ed10 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ __pycache__/ users.yml db/data/ db/migrations/100-dump.sql +.aider* diff --git a/config/JSON/strings.json b/config/JSON/strings.json index d57b40d..93abaf3 100644 --- a/config/JSON/strings.json +++ b/config/JSON/strings.json @@ -20,6 +20,24 @@ "invite_description": "Thanks for inviting me to your server!", "invite_button_text": "Invite Lumi", + "intro_no_guild": "you're not in a server that supports introductions.", + "intro_no_guild_author": "Server Not Supported", + "intro_no_channel": "the introduction channel is not set, please contact a moderator.", + "intro_no_channel_author": "Channel Not Set", + "intro_too_long": "your answer was too long, please keep it below 200 characters.", + "intro_too_long_author": "Answer Too Long", + "intro_timeout": "you took too long to answer the question, please try again.", + "intro_timeout_author": "Timeout", + "intro_service_name": "Introduction Service", + "intro_question_footer": "Type your answer below.", + "intro_start": "this command will serve as a questionnaire for your entry to {0}. Please keep your answers \"PG-13\" and don't abuse this command.", + "intro_start_footer": "Click the button below to start", + "intro_stopped": "the introduction command was stopped.", + "intro_stopped_author": "Introduction Stopped", + "intro_preview_field": "**{0}:** {1}\n\n", + "intro_post_confirmation": "your introduction has been posted in {0}!", + "intro_content": "Introduction by {0}", + "error_hierarchy": "❌ | **{0}** you can't perform this action because the target user is equal or higher than you in the role hierarchy.", "error_missing_permissions": "❌ | **{0}** you are missing permissions to run this command.", @@ -29,24 +47,6 @@ "ping": "\uD83C\uDFD3 | **{0}** I'm online.", "restarting": "Restarting Lumi...", "restart_error": "Error executing the script: {0}", - "intro_muted": "REMOVED STRING", - "intro_no_perms": "It seems that you don't have permission to do that!", - "intro_start_descr": "This command will serve as a questionnaire for you entry to {0}. Please keep your answers \"PG-13\"", - "intro_start_short": "Click the blue button to use the short form, this one has 6 questions.", - "intro_start_extended": "The green button will start the extended introduction, this one takes a bit longer but gives a more detailed portrayal of you.", - "intro_start_footer": "Please don't abuse this command.", - "intro_nickname": "How would you like to be identified in the server?", - "intro_age": "How old are you?", - "intro_location": "Where do you live?", - "intro_pronouns": "What are your preferred pronouns?", - "intro_likes": "Likes & interests.", - "intro_dislikes": "Dislikes.", - "intro_languages": "Which languages do you speak?", - "intro_sexuality": "What's your sexuality?", - "intro_rel_status": "What's your relationship status?", - "intro_extras": "EXTRAS: job status, zodiac sign, hobbies, etc. Tell us about yourself!", - "intro_recorded": "Recorded answer: {0}", - "intro_display_content": "Introduction by {0}", "award_error": "Something went wrong. Check console.", "give_error": "Something funky happened. Let Tess know.", "gambling_error": "Something went wrong during the gambling command: {0}", diff --git a/lib/constants.py b/lib/constants.py index 7675a30..a7c23ff 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -20,9 +20,9 @@ class Constants: INVITE_LINK = "https://discord.com/oauth2/authorize?client_id=1038050427272429588&permissions=8&scope=bot" # KRC - KRC_GUILD_ID = 719227135151046699 - KRC_INTRO_CHANNEL_ID = 973619250507972618 - KRC_QUESTION_MAPPING = resources["guild_specific"]["question_mapping"] + KRC_GUILD_ID: int = 719227135151046699 + KRC_INTRO_CHANNEL_ID: int = 973619250507972618 + KRC_QUESTION_MAPPING: dict[str, str] = resources["guild_specific"]["question_mapping"] # logo LUMI_LOGO_TRANSPARENT = art["logo"]["transparent"] diff --git a/lib/embed_builder.py b/lib/embed_builder.py index d294975..800cd89 100644 --- a/lib/embed_builder.py +++ b/lib/embed_builder.py @@ -24,7 +24,7 @@ class EmbedBuilder: description = f"**{ctx.author.name}** {description}" if not author_icon_url: - author_icon_url = ctx.author.avatar.url + author_icon_url = ctx.author.display_avatar.url if not footer_text: footer_text = "Luminara" if not footer_icon_url: @@ -45,12 +45,12 @@ class EmbedBuilder: embed.set_thumbnail(url=thumbnail_url) return embed - @staticmethod def create_error_embed( ctx, title=None, author_text=None, + author_icon_url=None, description=None, footer_text=None, show_name=True, @@ -61,7 +61,7 @@ class EmbedBuilder: ctx, title=title, author_text=author_text, - author_icon_url=CONST.CROSS_ICON, + author_icon_url=author_icon_url or CONST.CROSS_ICON, description=description, color=CONST.COLOR_ERROR, footer_text=footer_text, @@ -76,6 +76,7 @@ class EmbedBuilder: ctx, title=None, author_text=None, + author_icon_url=None, description=None, footer_text=None, show_name=True, @@ -86,7 +87,7 @@ class EmbedBuilder: ctx, title=title, author_text=author_text, - author_icon_url=CONST.CHECK_ICON, + author_icon_url=author_icon_url or CONST.CHECK_ICON, description=description, color=CONST.COLOR_DEFAULT, footer_text=footer_text, @@ -101,6 +102,7 @@ class EmbedBuilder: ctx, title=None, author_text=None, + author_icon_url=None, description=None, footer_text=None, show_name=True, @@ -111,7 +113,7 @@ class EmbedBuilder: ctx, title=title, author_text=author_text, - author_icon_url=CONST.EXCLAIM_ICON, + author_icon_url=author_icon_url or CONST.EXCLAIM_ICON, description=description, color=CONST.COLOR_DEFAULT, footer_text=footer_text, @@ -126,6 +128,7 @@ class EmbedBuilder: ctx, title=None, author_text=None, + author_icon_url=None, description=None, footer_text=None, show_name=True, @@ -136,7 +139,7 @@ class EmbedBuilder: ctx, title=title, author_text=author_text, - author_icon_url=CONST.WARNING_ICON, + author_icon_url=author_icon_url or CONST.WARNING_ICON, description=description, color=CONST.COLOR_WARNING, footer_text=footer_text, diff --git a/lib/embeds/error.py b/lib/embeds/error.py index 3a5d935..4fba64f 100644 --- a/lib/embeds/error.py +++ b/lib/embeds/error.py @@ -202,21 +202,6 @@ class MiscErrors: return embed - @staticmethod - def intro_no_guild(ctx, client_side=False): - embed = clean_error_embed(ctx) - - if not client_side: - embed.description += "you're not in a server that supports introductions." - else: - embed.description += "I'm not in a server that supports introductions." - - embed.set_footer( - text="this will be updated soon, stay tuned", icon_url=CONST.EXCLAIM_ICON - ) - - return embed - class HelpErrors: @staticmethod @@ -231,39 +216,3 @@ class HelpErrors: ) return embed - - -class IntroErrors: - @staticmethod - def timeout(ctx): - embed = clean_error_embed(ctx) - embed.description += "you ran out of time to answer this question." - embed.set_footer( - text=f"Please do {formatter.get_prefix(ctx)}{formatter.get_invoked_name(ctx)} again", - icon_url=exclaim_icon, - ) - - return embed - - @staticmethod - def too_long(ctx): - embed = clean_error_embed(ctx) - embed.description += ( - "your answer was too long, please keep it below 200 characters." - ) - embed.set_footer( - text=f"Please do {formatter.get_prefix(ctx)}{formatter.get_invoked_name(ctx)} again", - icon_url=CONST.EXCLAIM_ICON, - ) - - return embed - - @staticmethod - def intro_no_channel(ctx): - embed = clean_error_embed(ctx) - embed.description += "the introduction channel is missing." - embed.set_footer( - text="Please contact a server administrator", icon_url=CONST.EXCLAIM_ICON - ) - - return embed diff --git a/lib/embeds/info.py b/lib/embeds/info.py index 3521136..76e8d55 100644 --- a/lib/embeds/info.py +++ b/lib/embeds/info.py @@ -48,14 +48,7 @@ class MiscInfo: embed.set_footer(text=f"Latency: {round(1000 * client.latency)}ms", icon_url=exclaim_icon) return embed - - @staticmethod - def invite(ctx): - embed = clean_info_embed(ctx) - embed.description += "thanks for inviting me to your server!" - - return embed - + @staticmethod def set_prefix(ctx, prefix): embed = clean_info_embed(ctx) diff --git a/lib/embeds/intro.py b/lib/embeds/intro.py index 76c0c1a..83dd8fe 100644 --- a/lib/embeds/intro.py +++ b/lib/embeds/intro.py @@ -11,51 +11,10 @@ def clean_intro_embed(ctx): return embed -class Questions: - @staticmethod - def question(ctx, text): - embed = clean_intro_embed(ctx) - embed.description += text - embed.set_footer(text="Type your answer below", icon_url=CONST.EXCLAIM_ICON) - return embed class General: - @staticmethod - def start(ctx, channel): - embed = clean_intro_embed(ctx) - embed.description = ( - (embed.description or "") - + f"this command will serve as a questionnaire for your entry to {channel.mention}. " - f'Please keep your answers "PG-13" and don\'t abuse this command.' - ) - embed.set_footer(text="Click the button below to start", icon_url=CONST.EXCLAIM_ICON) - - return embed - - @staticmethod - def clicked_stop(ctx): - embed = clean_intro_embed(ctx) - embed.description = ( - embed.description or "" - ) + " the introduction command was stopped." - return embed - - @staticmethod - def preview(ctx, answer_mapping: dict): - embed = discord.Embed( - color=discord.Color.blurple(), description="" - ) # Corrected color - embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar.url) - description = "" - - for key, answer in answer_mapping.items(): - description += f"**{key}:** {answer}\n\n" - - embed.description = description - return embed - @staticmethod def post_confirmation(ctx, channel): embed = clean_intro_embed(ctx) diff --git a/modules/misc/introduction.py b/modules/misc/introduction.py index 48ae881..a6c5bd4 100644 --- a/modules/misc/introduction.py +++ b/modules/misc/introduction.py @@ -1,58 +1,73 @@ import asyncio -from loguru import logger import discord from discord.ext import bridge -from config.parser import JsonCache from lib.interactions.introduction import ( IntroductionStartButtons, IntroductionFinishButtons, ) -from lib.embeds.error import MiscErrors, IntroErrors -from lib.embeds.intro import General, Questions from typing import Optional - -resources = JsonCache.read_json("resources") +from lib.constants import CONST +from lib.embed_builder import EmbedBuilder async def cmd(self, ctx: bridge.Context) -> None: - """ - Introduction command for v2 - heavily optimized. + guild: Optional[discord.Guild] = self.client.get_guild(CONST.KRC_GUILD_ID) + member = guild.get_member(ctx.author.id) if guild else None - Args: - self (LumiBot): The instance of the LumiBot. - ctx (bridge.Context): The context of the command invocation. - """ - # For now, this command is only supported in one guild. - # Therefore, we check if the user is in that guild. - guild: Optional[discord.Guild] = self.client.get_guild( - int(resources["guild_specific"]["guild_id"]) - ) - - if guild is None: - await ctx.respond(embed=MiscErrors.intro_no_guild(ctx)) + if guild is None or member is None: + await ctx.respond( + embed=EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["intro_no_guild_author"], + description=CONST.STRINGS["intro_no_guild"], + footer_text=CONST.STRINGS["intro_service_name"], + ) + ) return - # A list of questions and corresponding field names - # This won't be hardcoded in the future (db update) - question_mapping: dict[str, str] = resources["guild_specific"]["question_mapping"] + question_mapping: dict[str, str] = CONST.KRC_QUESTION_MAPPING + channel: Optional[discord.abc.GuildChannel] = guild.get_channel( + CONST.KRC_INTRO_CHANNEL_ID + ) - # channel = await self.client.convert_to_text_channel( - # ctx, int(resources["guild_specific"]["intro_channel_id"]) - # ) - channel: Optional[discord.abc.GuildChannel] = guild.get_channel(int(resources["guild_specific"]["intro_channel_id"])) - - if channel is None or isinstance(channel, discord.ForumChannel) or isinstance(channel, discord.CategoryChannel): - await ctx.respond(embed=IntroErrors.intro_no_channel(ctx)) + if ( + channel is None + or isinstance(channel, discord.ForumChannel) + or isinstance(channel, discord.CategoryChannel) + ): + await ctx.respond( + embed=EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["intro_no_channel_author"], + description=CONST.STRINGS["intro_no_channel"], + footer_text=CONST.STRINGS["intro_service_name"], + ) + ) return view = IntroductionStartButtons(ctx) - await ctx.respond(embed=General.start(ctx, channel), view=view) + # await ctx.respond(embed=General.start(ctx, channel), view=view) + await ctx.respond( + embed=EmbedBuilder.create_embed( + ctx, + author_text=CONST.STRINGS["intro_service_name"], + description=CONST.STRINGS["intro_start"].format(channel.mention), + footer_text=CONST.STRINGS["intro_start_footer"], + ), + view=view, + ) await view.wait() if view.clickedStop: - await ctx.send(embed=General.clicked_stop(ctx)) + await ctx.send( + embed=EmbedBuilder.create_embed( + ctx, + author_text=CONST.STRINGS["intro_stopped_author"], + description=CONST.STRINGS["intro_stopped"], + ) + ) return elif view.clickedStart: @@ -65,7 +80,14 @@ async def cmd(self, ctx: bridge.Context) -> None: answer_mapping: dict[str, str] = {} for key, question in question_mapping.items(): - await ctx.send(embed=Questions.question(ctx, question)) + await ctx.send( + embed=EmbedBuilder.create_embed( + ctx, + author_text=key, + description=question, + footer_text=CONST.STRINGS["intro_question_footer"], + ) + ) try: answer: discord.Message = await self.client.wait_for( @@ -74,15 +96,39 @@ async def cmd(self, ctx: bridge.Context) -> None: answer_mapping[key] = answer.content.replace("\n", " ") if len(answer_mapping[key]) > 200: - await ctx.send(embed=IntroErrors.too_long(ctx)) + await ctx.send( + embed=EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["intro_too_long_author"], + description=CONST.STRINGS["intro_too_long"], + footer_text=CONST.STRINGS["intro_service_name"], + ) + ) return except asyncio.TimeoutError: - await ctx.send(embed=IntroErrors.timeout(ctx)) + await ctx.send( + embed=EmbedBuilder.create_error_embed( + ctx, + author_text=CONST.STRINGS["intro_timeout_author"], + description=CONST.STRINGS["intro_timeout"], + footer_text=CONST.STRINGS["intro_service_name"], + ) + ) return - # Generate a preview of the introduction, and send it on confirmation. - preview: discord.Embed = General.preview(ctx, answer_mapping) + # preview: discord.Embed = General.preview(ctx, answer_mapping) + description = "" + for key, value in answer_mapping.items(): + description += CONST.STRINGS["intro_preview_field"].format(key, value) + + preview = EmbedBuilder.create_embed( + ctx, + author_text=ctx.author.name, + author_icon_url=ctx.author.display_avatar.url, + description=description, + footer_text=CONST.STRINGS["intro_service_name"], + ) view = IntroductionFinishButtons(ctx) await ctx.send(embed=preview, view=view) @@ -90,15 +136,24 @@ async def cmd(self, ctx: bridge.Context) -> None: if view.clickedConfirm: await channel.send( - embed=preview, content=f"Introduction by {ctx.author.mention}" + embed=preview, content=CONST.STRINGS["intro_content"].format(ctx.author.mention) ) - await ctx.send(embed=General.post_confirmation(ctx, channel)) - - logger.debug( - f"Introduction by {ctx.author.name} was submitted in guild {guild.name} ({guild.id})." + await ctx.send( + embed=EmbedBuilder.create_embed( + ctx, + description=CONST.STRINGS["intro_post_confirmation"].format( + channel.mention + ), + ) ) return else: - await ctx.send(embed=General.clicked_stop(ctx)) + await ctx.send( + embed=EmbedBuilder.create_embed( + ctx, + author_text=CONST.STRINGS["intro_stopped_author"], + description=CONST.STRINGS["intro_stopped"], + ) + ) return diff --git a/requirements.txt b/requirements.txt index 5e47f8e..455c4e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ dropbox==11.36.2 mariadb==1.1.10 psutil==5.9.8 httpx==0.27.0 -loguru==0.7.2 \ No newline at end of file +loguru==0.7.2 +aider-chat \ No newline at end of file