From 4f47903b1346537b4eb55bc09742ba0042200f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aluerie=E2=9D=A4?= Date: Sat, 28 Dec 2024 18:26:00 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=97=91=EF=B8=8FConvert=20some=20hybrid=20?= =?UTF-8?q?commands=20into=20proper=20slash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ext/community/emote_spam.py | 14 ++-- ext/community/moderation.py | 3 +- ext/educational/language/dictionary.py | 30 ++++---- ext/educational/language/translation.py | 25 ++++--- ext/educational/math/wolfram.py | 71 +++++++++--------- ext/fpc/base_classes/settings.py | 1 - ext/user_settings/timezone.py | 83 ++++++++++++--------- ext/voice/tts.py | 95 +++++++++++++++---------- 8 files changed, 178 insertions(+), 144 deletions(-) diff --git a/ext/community/emote_spam.py b/ext/community/emote_spam.py index abb5ad4f..65645913 100644 --- a/ext/community/emote_spam.py +++ b/ext/community/emote_spam.py @@ -8,6 +8,7 @@ import discord import emoji +from discord import app_commands from discord.ext import commands from bot import aluloop @@ -16,7 +17,7 @@ from ._base import CommunityCog if TYPE_CHECKING: - from bot import AluBot, AluContext + from bot import AluBot class EmoteSpam(CommunityCog): @@ -121,15 +122,18 @@ async def emote_spam(self) -> None: emote = await self.get_random_emote() await self.bot.community.emote_spam.send(f"{emote!s} {emote!s} {emote!s}") - @commands.hybrid_command() - async def do_emote_spam(self, ctx: AluContext) -> None: + @app_commands.command() + async def do_emote_spam(self, interaction: discord.Interaction[AluBot]) -> None: """Send 3x random emote into emote spam channel.""" emote = await self.get_random_emote() channel = self.community.emote_spam content = f"{emote!s} {emote!s} {emote!s}" await channel.send(content) - e = discord.Embed(colour=const.Colour.blueviolet, description=f"I sent {content} into {channel.mention}") - await ctx.reply(embed=e, ephemeral=True, delete_after=10) + embed = discord.Embed( + colour=const.Colour.blueviolet, + description=f"I sent {content} into {channel.mention}", + ) + await interaction.response.send_message(embed=embed, ephemeral=True) @aluloop(count=1) async def offline_criminal_check(self) -> None: diff --git a/ext/community/moderation.py b/ext/community/moderation.py index 26e6eeb3..f139817c 100644 --- a/ext/community/moderation.py +++ b/ext/community/moderation.py @@ -121,11 +121,10 @@ async def give_aluerie_all_perms(self, channel: discord.abc.GuildChannel) -> Non if channel.guild.id != self.community.id: return - sister_of_the_veil = self.community.sister_of_the_veil allow, deny = discord.Permissions.all(), discord.Permissions.none() all_perms = discord.PermissionOverwrite.from_pair(allow=allow, deny=deny) reason = "Give all permissions to Aluerie" - await channel.set_permissions(sister_of_the_veil, overwrite=all_perms, reason=reason) + await channel.set_permissions(self.community.sister_of_the_veil, overwrite=all_perms, reason=reason) async def setup(bot: AluBot) -> None: diff --git a/ext/educational/language/dictionary.py b/ext/educational/language/dictionary.py index eee84a06..9bdb005a 100644 --- a/ext/educational/language/dictionary.py +++ b/ext/educational/language/dictionary.py @@ -17,10 +17,10 @@ import discord import yarl from discord import app_commands -from discord.ext import commands, menus +from discord.ext import menus from lxml import html -from utils import pages +from utils import errors, pages from .._base import EducationalCog @@ -29,7 +29,7 @@ if TYPE_CHECKING: from aiohttp import ClientSession - from bot import AluContext + from bot import AluBot # from .utils.context import Context, GuildContext # from .utils.paginator import RoboPages @@ -40,11 +40,9 @@ def html_to_markdown(node: Any, *, include_spans: bool = False) -> str: text = [] - italics_marker = "_" for child in node: if child.tag == "i": - text.append(f"{italics_marker}{child.text.strip()}{italics_marker}") - italics_marker = "_" if italics_marker == "*" else "*" + text.append(f"_{child.text.strip()}_") # `_` is italics marker elif child.tag == "b": if text and text[-1].endswith("*"): text.append("\u200b") @@ -343,33 +341,33 @@ async def format_page(self, menu: pages.Paginator, entry: FreeDictionaryMeaning) class DictionaryCog(EducationalCog): - @commands.hybrid_command(name="define") + @app_commands.command(name="define") @app_commands.describe(word="The word to look up") - async def _define(self, ctx: AluContext, *, word: str) -> None: + async def _define(self, interaction: discord.Interaction[AluBot], word: str) -> None: """Looks up an English word in the dictionary.""" - result = await parse_free_dictionary_for_word(ctx.session, word=word) + result = await parse_free_dictionary_for_word(self.bot.session, word=word) if result is None: - await ctx.send("Could not find that word.", ephemeral=True) - return + msg = "Could not find that word." + raise errors.SomethingWentWrong(msg) # Check if it's a phrasal verb somehow phrase = discord.utils.find(lambda v: v.word.lower() == word.lower(), result.phrasal_verbs) if phrase is not None: embed = phrase.to_embed() - await ctx.send(embed=embed) + await interaction.response.send_message(embed=embed) return if not result.meanings: - await ctx.send("Could not find any definitions for that word.", ephemeral=True) - return + msg = "Could not find any definitions for that word." + raise errors.SomethingWentWrong(msg) # Paginate over the various meanings of the word - p = pages.Paginator(ctx, FreeDictionaryWordMeaningPageSource(result)) + p = pages.Paginator(interaction, FreeDictionaryWordMeaningPageSource(result)) await p.start() @_define.autocomplete("word") async def _define_word_autocomplete( - self, interaction: discord.Interaction, query: str + self, interaction: discord.Interaction[AluBot], query: str ) -> list[app_commands.Choice[str]]: if not query: return [] diff --git a/ext/educational/language/translation.py b/ext/educational/language/translation.py index 4ef69c8c..71e61133 100644 --- a/ext/educational/language/translation.py +++ b/ext/educational/language/translation.py @@ -20,13 +20,13 @@ * RoboDanny's translator.py (license MPL v2 from Rapptz/RoboDanny) - https://github.com/Rapptz/RoboDanny/blob/rewrite/cogs/utils/translator.py """ + from __future__ import annotations from typing import TYPE_CHECKING, Any, NamedTuple, TypedDict, override import discord from discord import app_commands -from discord.ext import commands from utils import const, errors @@ -35,7 +35,7 @@ if TYPE_CHECKING: from aiohttp import ClientSession - from bot import AluBot, AluContext + from bot import AluBot # fmt: off @@ -157,12 +157,17 @@ async def translate_context_menu_callback(self, interaction: discord.Interaction msg = "Sorry, but it seems, that this message doesn't have any text content to translate." raise errors.BadArgument(msg) - e = await self.translate_embed(text) - await interaction.response.send_message(embed=e, ephemeral=True) + embed = await self.translate_embed(text) + await interaction.response.send_message(embed=embed, ephemeral=True) + + @app_commands.command() + async def translate(self, interaction: discord.Interaction[AluBot], text: str) -> None: + """Google Translate to English, auto-detects source language. - @commands.hybrid_command() - @app_commands.describe(text="Enter text to translate") - async def translate(self, ctx: AluContext, *, text: str) -> None: - """Google Translate to English, auto-detects source language.""" - e = await self.translate_embed(text) - await ctx.reply(embed=e) + Parameters + ---------- + text + Enter text to translate + """ + embed = await self.translate_embed(text) + await interaction.response.send_message(embed=embed) diff --git a/ext/educational/math/wolfram.py b/ext/educational/math/wolfram.py index cdafd80f..94a588d2 100644 --- a/ext/educational/math/wolfram.py +++ b/ext/educational/math/wolfram.py @@ -4,7 +4,6 @@ from urllib import parse as urlparse from discord import app_commands -from discord.ext import commands from config import WOLFRAM_TOKEN from utils import const, errors @@ -12,7 +11,9 @@ from .._base import EducationalCog if TYPE_CHECKING: - from bot import AluBot, AluContext + import discord + + from bot import AluBot class WolframAlphaCog(EducationalCog, emote=const.Emote.bedNerdge): @@ -28,53 +29,45 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.simple_url = f"{base}/simple?appid={WOLFRAM_TOKEN}&background=black&foreground=white&layout=labelbar&i=" self.short_url = f"{base}/result?appid={WOLFRAM_TOKEN}&i=" - @commands.hybrid_group(name="wolfram") - async def wolfram_group(self, ctx: AluContext) -> None: - """WolframAlpha Commands.""" - await ctx.send_help(ctx.command) + wolfram_group = app_commands.Group( + name="wolfram", + description="WolframAlpha queries.", + ) - async def wa_long_worker(self, ctx: AluContext, *, query: str) -> None: - await ctx.typing() + @wolfram_group.command(name="long") + @app_commands.checks.cooldown(1, 60.0, key=lambda i: (i.guild_id, i.user.id)) + async def wolfram_long(self, interaction: discord.Interaction[AluBot], query: str) -> None: + """Get a long, detailed image-answer from WolframAlpha. + + Parameters + ---------- + query + Query for WolframAlpha. + """ + await interaction.response.defer() question_url = f"{self.simple_url}{urlparse.quote(query)}" file = await self.bot.transposer.url_to_file(question_url, filename="WolframAlpha.png") - await ctx.reply(content=f"```py\n{query}```", file=file) - - @wolfram_group.command(name="long") - @commands.cooldown(2, 10, commands.BucketType.user) - @app_commands.describe(query="Query for WolframAlpha.") - async def wolfram_long(self, ctx: AluContext, *, query: str) -> None: - """Get a long, detailed image-answer from WolframAlpha.""" - await self.wa_long_worker(ctx, query=query) - - @commands.command(name="wolf") - @commands.cooldown(2, 10, commands.BucketType.user) - async def wolfram_long_shortcut(self, ctx: AluContext, *, query: str) -> None: - """Just a txt command shortcut for `wolfram long`.""" - await self.wa_long_worker(ctx, query=query) - - async def wa_short_worker(self, ctx: AluContext, *, query: str) -> None: - await ctx.typing() + await interaction.followup.send(content=f"```py\n{query}```", file=file) + + @wolfram_group.command(name="short") + @app_commands.checks.cooldown(1, 60.0, key=lambda i: (i.guild_id, i.user.id)) + async def wolfram_short(self, interaction: discord.Interaction[AluBot], query: str) -> None: + """Get a quick, short answer from WolframAlpha. + + Parameters + ---------- + query + Query for WolframAlpha. + """ + await interaction.response.defer() question_url = f"{self.short_url}{urlparse.quote(query)}" async with self.bot.session.get(question_url) as response: if response.ok: - await ctx.reply(f"```py\n{query}```{await response.text()}") + await interaction.followup.send(f"```py\n{query}```{await response.text()}") else: msg = f"Wolfram Response was not ok, Status {response.status}," raise errors.ResponseNotOK(msg) - @wolfram_group.command(name="short", aliases=["wa"]) - @commands.cooldown(2, 10, commands.BucketType.user) - @app_commands.describe(query="Query for WolframAlpha.") - async def wolfram_short(self, ctx: AluContext, *, query: str) -> None: - """Get a quick, short answer from WolframAlpha.""" - await self.wa_short_worker(ctx, query=query) - - @commands.command(name="wa") - @commands.cooldown(2, 10, commands.BucketType.user) - async def wolfram_short_shortcut(self, ctx: AluContext, *, query: str) -> None: - """Just a txt command shortcut for `wolfram short`.""" - await self.wa_short_worker(ctx, query=query) - async def setup(bot: AluBot) -> None: """Load AluBot extension. Framework of discord.py.""" diff --git a/ext/fpc/base_classes/settings.py b/ext/fpc/base_classes/settings.py index 3d591e91..3150e391 100644 --- a/ext/fpc/base_classes/settings.py +++ b/ext/fpc/base_classes/settings.py @@ -519,7 +519,6 @@ async def setup_players(self, interaction: discord.Interaction[AluBot]) -> None: # But I think it might be confusing for end-user if I have several commands to add/remove players/characters. # One simple interactive view is enough for them granted they aren't supposed to change their preferences much. # I'm also just hesitant to completely delete all my previous work so let's at least leave one-name version - # so we can put it as hybrid command, which is easier to maintain. # TODO: YOINK DOCS FROM THERE diff --git a/ext/user_settings/timezone.py b/ext/user_settings/timezone.py index 020df282..d96ab2a2 100644 --- a/ext/user_settings/timezone.py +++ b/ext/user_settings/timezone.py @@ -7,12 +7,11 @@ import discord from discord import app_commands -from discord.ext import commands from utils.timezones import TimeZone, TimeZoneTransformer # noqa: TCH001 if TYPE_CHECKING: - from bot import AluBot, AluContext + from bot import AluBot from ._base import UserSettingsBaseCog @@ -24,32 +23,45 @@ class TimezoneSetting(UserSettingsBaseCog): async def cog_load(self) -> None: self.bot.initialize_tz_manager() - @commands.hybrid_group() - async def timezone(self, ctx: AluContext) -> None: - """Commands related to managing or retrieving timezone info.""" - await ctx.send_help(ctx.command) + timezone_group = app_commands.Group( + name="timezone", + description="Manage your timezone settings or retrieve some timezone info.", + ) - @timezone.command(name="set") - @app_commands.describe(timezone="The timezone to change to.") + @timezone_group.command(name="set") async def timezone_set( - self, ctx: AluContext, *, timezone: app_commands.Transform[TimeZone, TimeZoneTransformer] + self, + interaction: discord.Interaction[AluBot], + timezone: app_commands.Transform[TimeZone, TimeZoneTransformer], ) -> None: """Sets your timezone. This is used to convert times to your local timezone when using the reminder command and other miscellaneous commands such as birthday set. + + Parameters + ---------- + timezone + The timezone to change to. """ - await self.bot.tz_manager.set_timezone(ctx.author.id, timezone) + await self.bot.tz_manager.set_timezone(interaction.user.id, timezone) content = f"Your timezone has been set to {timezone.label} (IANA ID: {timezone.key})." - await ctx.send(content, ephemeral=True) + await interaction.response.send_message(content, ephemeral=True) - @timezone.command(name="info") - @app_commands.describe(timezone="The timezone to get info about.") + @timezone_group.command(name="info") async def timezone_info( - self, ctx: AluContext, *, timezone: app_commands.Transform[TimeZone, TimeZoneTransformer] + self, + interaction: discord.Interaction[AluBot], + timezone: app_commands.Transform[TimeZone, TimeZoneTransformer], ) -> None: - """Retrieves info about a timezone.""" + """Retrieves info about a timezone. + + Parameters + ---------- + timezone + The timezone to get info about. + """ now = datetime.datetime.now(datetime.UTC) dt = now.astimezone(tz=zoneinfo.ZoneInfo(key=timezone.key)) @@ -59,32 +71,39 @@ async def timezone_info( .add_field(name="UTC Offset", value=self.bot.tz_manager.get_utc_offset_string(timezone.key, now)) .add_field(name="IANA Database Alias", value=timezone.key) ) - await ctx.send(embed=embed) - - @timezone.command(name="get") - @app_commands.describe(user="The member to get the timezone of. Defaults to yourself.") - async def timezone_get(self, ctx: AluContext, *, user: discord.User = commands.Author) -> None: - """Shows the timezone of a user.""" - self_query = user.id == ctx.author.id - tz = await self.bot.tz_manager.get_timezone(user.id) + await interaction.response.send_message(embed=embed) + + @timezone_group.command(name="get") + async def timezone_get(self, interaction: discord.Interaction[AluBot], user: discord.User | None = None) -> None: + """Shows the timezone of a user. + + Parameters + ---------- + user + The member to get the timezone of. Defaults to yourself. + """ + person = user or interaction.user + tz = await self.bot.tz_manager.get_timezone(person.id) if tz is None: - await ctx.send(f"{user} has not set their timezone.") + await interaction.response.send_message(f"{user} has not set their timezone.") return time = discord.utils.utcnow().astimezone(zoneinfo.ZoneInfo(tz)).strftime("%Y-%m-%d %I:%M %p") - if self_query: - msg = await ctx.send(f"Your timezone is {tz!r}. The current time is {time}.") + if person.id == interaction.user.id: + await interaction.response.send_message(f"Your timezone is {tz!r}. The current time is {time}.") + msg = await interaction.original_response() await asyncio.sleep(5.0) await msg.edit(content=f"Your current time is {time}.") else: - await ctx.send(f"The current time for {user} is {time}.") + await interaction.response.send_message(f"The current time for {user} is {time}.") - @timezone.command(name="clear") - async def timezone_clear(self, ctx: AluContext) -> None: + @timezone_group.command(name="clear") + async def timezone_clear(self, interaction: discord.Interaction[AluBot]) -> None: """Clears your timezone.""" - await ctx.pool.execute("UPDATE user_settings SET timezone = NULL WHERE id=$1", ctx.author.id) - self.bot.tz_manager.get_timezone.invalidate(self, ctx.author.id) - await ctx.send("Your timezone has been cleared.", ephemeral=True) + query = "UPDATE user_settings SET timezone = NULL WHERE id=$1" + await interaction.client.pool.execute(query, interaction.user.id) + self.bot.tz_manager.get_timezone.invalidate(self, interaction.user.id) + await interaction.response.send_message("Your timezone has been cleared.", ephemeral=True) async def setup(bot: AluBot) -> None: diff --git a/ext/voice/tts.py b/ext/voice/tts.py index cdc93b1c..afe34cfb 100644 --- a/ext/voice/tts.py +++ b/ext/voice/tts.py @@ -12,7 +12,7 @@ from ._base import VoiceChatCog if TYPE_CHECKING: - from bot import AluBot, AluGuildContext + from bot import AluBot class LanguageData(NamedTuple): @@ -44,27 +44,29 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.connections: dict[int, discord.VoiceClient] = {} # guild.id to Voice we are connected to - @app_commands.guild_only() - @commands.hybrid_group(name="text-to-speech", aliases=["tts", "voice"]) - async def tts_group(self, ctx: AluGuildContext) -> None: - """Text-To-Speech commands.""" - await ctx.send_help(ctx.command) + tts_group = app_commands.Group( + name="text-to-speech", + description="Make the bot join voice channels and do some talking.", + guild_only=True, + ) async def speak_worker( self, - ctx: AluGuildContext, + interaction: discord.Interaction[AluBot], lang: LanguageData, *, text: str = "Allo", ) -> None: - voice_state = ctx.author.voice + assert isinstance(interaction.user, discord.Member) + voice_state = interaction.user.voice if not voice_state: msg = "You aren't in a voice channel!" raise errors.ErroneousUsage(msg) - voice_client = ctx.guild.voice_client + assert interaction.guild + voice_client = interaction.guild.voice_client if voice_client is not None: - vc = self.connections[ctx.guild.id] + vc = self.connections[interaction.guild.id] assert isinstance(voice_client, discord.VoiceClient) await voice_client.move_to(voice_state.channel) else: @@ -72,7 +74,7 @@ async def speak_worker( msg = "You aren't connected to a voice channel!" raise errors.ErroneousUsage(msg) vc = await voice_state.channel.connect() # Connect to the voice channel the author is in. - self.connections.update({ctx.guild.id: vc}) # Updating the cache with the guild and channel. + self.connections.update({interaction.guild.id: vc}) # Updating the cache with the guild and channel. assert isinstance(vc, discord.VoiceClient) @@ -81,58 +83,73 @@ async def speak_worker( tts.save(audio_name) vc.play(discord.FFmpegPCMAudio(audio_name)) - @tts_group.command() - @app_commands.describe(text="Enter text to speak", language="Choose language/accent") - async def speak( - self, ctx: AluGuildContext, language: LanguageCollection.Literal = "fr", *, text: str = "Allo" + @tts_group.command(name="speak") + @app_commands.describe() + async def tts_speak( + self, + interaction: discord.Interaction[AluBot], + language: LanguageCollection.Literal = "fr", + text: str = "Allo", ) -> None: - """Make Text-To-Speech request into voice-chat.""" + """Make Text-To-Speech request into voice-chat. + + Parameters + ----------- + text + Enter text for the bot to speak. + language + Choose language/accent. + """ lang: LanguageData = getattr(LanguageCollection, language) - await self.speak_worker(ctx, lang, text=text) - e = discord.Embed(title="Text-To-Speech request", description=text, colour=ctx.user.colour) - e.set_author(name=ctx.user.display_name, icon_url=ctx.user.display_avatar.url) - e.set_footer(text=f"{lang.code} Language: {lang.locale}") - await ctx.reply(embed=e) + await self.speak_worker(interaction, lang, text=text) + embed = ( + discord.Embed( + colour=interaction.user.colour, + title="Text-To-Speech request", + description=text, + ) + .set_author(name=interaction.user.display_name, icon_url=interaction.user.display_avatar.url) + .set_footer(text=f"{lang.code} Language: {lang.locale}") + ) + await interaction.response.send_message(embed=embed) @tts_group.command() - async def stop(self, ctx: AluGuildContext) -> None: + async def stop(self, interaction: discord.Interaction[AluBot]) -> None: """Stop playing current audio. Useful if somebody is abusing TTS system with annoying requests.""" + assert interaction.guild try: - vc = self.connections[ctx.guild.id] + vc = self.connections[interaction.guild.id] except KeyError: msg = "I'm not in voice channel" raise errors.ErroneousUsage(msg) if vc.is_playing(): vc.stop() - e = discord.Embed(description="Stopped", colour=ctx.user.colour) - await ctx.reply(embed=e) + embed = discord.Embed(description="Stopped", colour=interaction.user.colour) + await interaction.response.send_message(embed=embed) else: - e = discord.Embed(description="I don't think I was talking", colour=const.Colour.maroon) - await ctx.reply(embed=e, ephemeral=True) + embed = discord.Embed(description="I don't think I was talking", colour=const.Colour.maroon) + await interaction.response.send_message(embed=embed, ephemeral=True) @tts_group.command() - async def leave(self, ctx: AluGuildContext) -> None: + async def leave(self, interaction: discord.Interaction[AluBot]) -> None: """Make bot leave voice channel.""" + assert interaction.guild try: - vc = self.connections[ctx.guild.id] + vc = self.connections[interaction.guild.id] except KeyError: msg = "I'm not in a voice channel." raise errors.ErroneousUsage(msg) await vc.disconnect() - e = discord.Embed(description=f"I left {vc.channel.mention}", colour=ctx.user.colour) - await ctx.reply(embed=e) + embed = discord.Embed(description=f"I left {vc.channel.mention}", colour=interaction.user.colour) + await interaction.response.send_message(embed=embed) - @app_commands.guild_only() - @commands.hybrid_command(name="bonjour") - async def bonjour(self, ctx: AluGuildContext) -> None: + @tts_group.command(name="bonjour") + async def tts_bonjour(self, interaction: discord.Interaction[AluBot]) -> None: """`Bonjour !` into both text/voice chats.""" - try: - await self.speak_worker(ctx, LanguageCollection.fr, text="Bonjour !") - except errors.ErroneousUsage: - pass - await ctx.reply(content=f"Bonjour {const.Emote.bubuAYAYA}") + await self.speak_worker(interaction, LanguageCollection.fr, text="Bonjour !") + await interaction.response.send_message(content=f"Bonjour {const.Emote.bubuAYAYA}") @commands.Cog.listener(name="on_voice_state_update") async def leave_when_everybody_else_disconnects(