From aa2a4c21a63ceb2a04f8fb2895aaa51d00b153eb Mon Sep 17 00:00:00 2001 From: wlinator Date: Wed, 28 Aug 2024 10:18:50 -0400 Subject: [PATCH] Add introduction command --- locales/strings.en-US.json | 1 + modules/misc/introduction.py | 193 +++++++++++++++++++++++++++++++++++ ui/views/introduction.py | 83 +++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 modules/misc/introduction.py create mode 100644 ui/views/introduction.py diff --git a/locales/strings.en-US.json b/locales/strings.en-US.json index c4eea1b..60cddef 100644 --- a/locales/strings.en-US.json +++ b/locales/strings.en-US.json @@ -191,6 +191,7 @@ "intro_no_channel_author": "Channel Not Set", "intro_no_guild": "you're not in a server that supports introductions.", "intro_no_guild_author": "Server Not Supported", + "intro_post_confirmation_author": "Introduction Posted", "intro_post_confirmation": "your introduction has been posted in {0}!", "intro_preview_field": "**{0}:** {1}\n\n", "intro_question_footer": "Type your answer below.", diff --git a/modules/misc/introduction.py b/modules/misc/introduction.py new file mode 100644 index 0000000..b1aa928 --- /dev/null +++ b/modules/misc/introduction.py @@ -0,0 +1,193 @@ +import asyncio +from typing import Dict, Optional + +import discord +from discord.ext import commands + +from lib.const import CONST +from ui.embeds import builder +from ui.views.introduction import ( + IntroductionFinishButtons, + IntroductionStartButtons, +) + + +class Introduction(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.hybrid_command( + name="introduction", + aliases=["intro"], + usage="introduction", + ) + async def introduction(self, ctx: commands.Context[commands.Bot]) -> None: + guild: Optional[discord.Guild] = self.bot.get_guild( + CONST.INTRODUCTIONS_GUILD_ID, + ) + member: Optional[discord.Member] = ( + guild.get_member(ctx.author.id) if guild else None + ) + + if not guild or not member: + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_no_guild_author"], + description=CONST.STRINGS["intro_no_guild"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + return + + question_mapping: Dict[str, str] = CONST.INTRODUCTIONS_QUESTION_MAPPING + channel: Optional[discord.abc.GuildChannel] = guild.get_channel( + CONST.INTRODUCTIONS_CHANNEL_ID, + ) + + if not channel or isinstance( + channel, + (discord.ForumChannel, discord.CategoryChannel), + ): + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_no_channel_author"], + description=CONST.STRINGS["intro_no_channel"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + return + + view: IntroductionStartButtons | IntroductionFinishButtons = ( + IntroductionStartButtons(ctx) + ) + await ctx.send( + embed=builder.create_embed( + theme="info", + user_name=ctx.author.name, + 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.clicked_stop: + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_stopped_author"], + description=CONST.STRINGS["intro_stopped"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + return + + if view.clicked_start: + + def check(message: discord.Message) -> bool: + return message.author == ctx.author and isinstance( + message.channel, + discord.DMChannel, + ) + + answer_mapping: Dict[str, str] = {} + + for key, question in question_mapping.items(): + await ctx.send( + embed=builder.create_embed( + theme="info", + user_name=ctx.author.name, + author_text=key, + description=question, + footer_text=CONST.STRINGS["intro_question_footer"], + ), + ) + + try: + answer: discord.Message = await self.bot.wait_for( + "message", + check=check, + timeout=300, + ) + answer_content: str = answer.content.replace("\n", " ") + + if len(answer_content) > 200: + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_too_long_author"], + description=CONST.STRINGS["intro_too_long"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + return + + answer_mapping[key] = answer_content + + except asyncio.TimeoutError: + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_timeout_author"], + description=CONST.STRINGS["intro_timeout"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + return + + description: str = "".join( + CONST.STRINGS["intro_preview_field"].format(key, value) + for key, value in answer_mapping.items() + ) + + preview: discord.Embed = builder.create_embed( + theme="info", + user_name=ctx.author.name, + author_text=ctx.author.name, + author_icon_url=ctx.author.display_avatar.url, + description=description, + footer_text=CONST.STRINGS["intro_content_footer"], + ) + view = IntroductionFinishButtons(ctx) + + await ctx.send(embed=preview, view=view) + await view.wait() + + if view.clicked_confirm: + await channel.send( + embed=preview, + content=CONST.STRINGS["intro_content"].format(ctx.author.mention), + ) + await ctx.send( + embed=builder.create_embed( + theme="info", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_post_confirmation_author"], + description=CONST.STRINGS["intro_post_confirmation"].format( + channel.mention, + ), + ), + ) + else: + await ctx.send( + embed=builder.create_embed( + theme="error", + user_name=ctx.author.name, + author_text=CONST.STRINGS["intro_stopped_author"], + description=CONST.STRINGS["intro_stopped"], + footer_text=CONST.STRINGS["intro_service_name"], + ), + ) + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Introduction(bot)) diff --git a/ui/views/introduction.py b/ui/views/introduction.py new file mode 100644 index 0000000..3fa27a0 --- /dev/null +++ b/ui/views/introduction.py @@ -0,0 +1,83 @@ +from typing import Optional + +import discord +from discord.ui import Button, View + + +class IntroductionStartButtons(View): + def __init__(self, ctx) -> None: + super().__init__(timeout=60) + self.ctx = ctx + self.clicked_start: bool = False + self.clicked_stop: bool = False + self.message: Optional[discord.Message] = None + + async def on_timeout(self) -> None: + for child in self.children: + if isinstance(child, Button): + child.disabled = True + if self.message: + await self.message.edit(view=None) + + @discord.ui.button(label="Start", style=discord.ButtonStyle.primary) + async def start_button_callback( + self, + interaction: discord.Interaction, + button: Button, + ) -> None: + await interaction.response.edit_message(view=None) + self.clicked_start = True + self.stop() + + @discord.ui.button(label="Stop", style=discord.ButtonStyle.red) + async def stop_button_callback( + self, + interaction: discord.Interaction, + button: Button, + ) -> None: + await interaction.response.edit_message(view=None) + self.clicked_stop = True + self.stop() + + +class IntroductionFinishButtons(View): + def __init__(self, ctx) -> None: + super().__init__(timeout=60) + self.ctx = ctx + self.clicked_confirm: bool = False + self.message: Optional[discord.Message] = None + + async def on_timeout(self) -> None: + for child in self.children: + if isinstance(child, Button): + child.disabled = True + if self.message: + await self.message.edit(view=None) + + @discord.ui.button(label="Post it!", style=discord.ButtonStyle.green) + async def confirm_button_callback( + self, + interaction: discord.Interaction, + button: Button, + ) -> None: + await interaction.response.edit_message(view=None) + self.clicked_confirm = True + self.stop() + + @discord.ui.button(label="Stop", style=discord.ButtonStyle.red) + async def stop_button_callback( + self, + interaction: discord.Interaction, + button: Button, + ) -> None: + await interaction.response.edit_message(view=None) + self.stop() + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + if interaction.user == self.ctx.author: + return True + await interaction.response.send_message( + "You can't use these buttons.", + ephemeral=True, + ) + return False