# -*- coding: utf-8 -*- import discord import json from typing import Optional from redbot.core import commands, app_commands from redbot.core.utils.chat_formatting import box, pagify class CommandsMixin: """This class holds all the commands for the cog.""" @commands.hybrid_command(aliases=["ts"]) @app_commands.describe( to_language="The language to translate to.", text="The text to translate. For prefix commands, wrap multi-word text in quotes.", from_language="[Optional] The language to translate from. Defaults to Common." ) async def translate(self, ctx: commands.Context, to_language: str, text: str, from_language: Optional[str] = None): """Translates text from one language to another.""" from_lang_name = from_language if from_language else "common" from_matches = await self._find_language(from_lang_name) if not from_matches: return await ctx.send(f"Could not find the 'from' language: `{from_lang_name}`") if len(from_matches) > 1: possible = [f"`{self.all_languages[k]['name']}` (`{k}`)" for k in from_matches] return await ctx.send(f"Multiple 'from' languages found for `{from_lang_name}`. Please be more specific:\n" + ", ".join(possible)) from_lang_key = from_matches[0] to_matches = await self._find_language(to_language) if not to_matches: return await ctx.send(f"Could not find the 'to' language: `{to_language}`") if len(to_matches) > 1: possible = [f"`{self.all_languages[k]['name']}` (`{k}`)" for k in to_matches] return await ctx.send(f"Multiple 'to' languages found for `{to_language}`. Please be more specific:\n" + ", ".join(possible)) to_lang_key = to_matches[0] from_lang_obj = self.all_languages[from_lang_key] to_lang_obj = self.all_languages[to_lang_key] try: common_text = from_lang_obj['from_func'](text) translated_text = to_lang_obj['to_func'](common_text) except Exception as e: await ctx.send(f"An error occurred during translation: `{e}`") return webhook = None if ctx.guild and ctx.guild.me.guild_permissions.manage_webhooks: for wh in await ctx.channel.webhooks(): if wh.name == "Translator Cog Webhook": webhook = wh break if webhook is None: webhook = await ctx.channel.create_webhook(name="Translator Cog Webhook") if webhook: if ctx.interaction: await ctx.interaction.response.defer(ephemeral=True) await webhook.send(content=translated_text, username=ctx.author.display_name, avatar_url=ctx.author.display_avatar.url) await ctx.interaction.followup.send("Translation sent.", ephemeral=True) else: if ctx.channel.permissions_for(ctx.guild.me).manage_messages: try: await ctx.message.delete() except discord.Forbidden: pass await webhook.send(content=translated_text, username=ctx.author.display_name, avatar_url=ctx.author.display_avatar.url) else: embed = discord.Embed(title=f"Translation to {to_lang_obj['name']}", color=await ctx.embed_color()) embed.add_field(name="Original Text", value=box(text), inline=False) embed.add_field(name="Translated Text", value=box(translated_text), inline=False) await ctx.send(embed=embed) @commands.hybrid_command() async def languages(self, ctx: commands.Context): """Lists all available languages.""" sorted_langs = sorted(self.all_languages.values(), key=lambda x: x['name']) lang_list = [f"* `{lang['name']}`" for lang in sorted_langs] output = "Available Languages:\n" + "\n".join(lang_list) pages = [box(page) for page in pagify(output, page_length=1000)] await ctx.send_interactive(pages, box_lang="md") @commands.group(aliases=["px"], invoke_without_command=True) async def proxy(self, ctx: commands.Context): """Toggles your translation proxy on or off.""" current_setting = await self.config.user(ctx.author).proxy_enabled() new_setting = not current_setting await self.config.user(ctx.author).proxy_enabled.set(new_setting) status = "enabled" if new_setting else "disabled" await ctx.send(f"Proxying is now `{status}`.") @proxy.command(name="list") async def proxy_list(self, ctx: commands.Context): """Shows your currently registered sonas.""" sonas = await self.config.user(ctx.author).sonas() if not sonas: return await ctx.send("You have no sonas registered.") msg = "Your registered sonas:\n" for name, data in sonas.items(): display_name = data.get("display_name", name) lang_name = self.all_languages.get(data['language'], {}).get('name', 'Unknown Language') if 'claws' in data: start_claw, end_claw = data['claws'] elif 'brackets' in data: # Fallback for old data start_claw, end_claw = data['brackets'] else: continue claw_info = f"Starts with `{start_claw}`" if not end_claw else f"`{start_claw}` and `{end_claw}`" msg += f" - **{display_name}** (Internal Name: `{name}`): Translates to `{lang_name}`. Claws: {claw_info}\n" for page in pagify(msg): await ctx.send(page) @proxy.command(name="add", aliases=["+"]) async def proxy_add(self, ctx: commands.Context, name: str, language: str, display_name: str, start_claw: str, end_claw: str = "", avatar: Optional[str] = None): """Registers a new sona with a name and avatar.""" matches = await self._find_language(language) if not matches: return await ctx.send(f"Language `{language}` not found.") if len(matches) > 1: possible = [f"`{self.all_languages[k]['name']}` (`{k}`)" for k in matches] return await ctx.send(f"Multiple languages found for `{language}`. Please be more specific:\n" + ", ".join(possible)) lang_key = matches[0] sona_key = name.lower() avatar_url = avatar if not avatar_url and ctx.message.attachments: avatar_url = ctx.message.attachments[0].url async with self.config.user(ctx.author).sonas() as sonas: if sona_key in sonas: return await ctx.send(f"A sona named `{name}` already exists.") sonas[sona_key] = { "display_name": display_name, "avatar_url": avatar_url, "language": lang_key, "claws": [start_claw, end_claw] } await ctx.send(f"Sona `{name}` registered as `{display_name}` to translate to `{self.all_languages[lang_key]['name']}`.") @proxy.command(name="remove", aliases=["-"]) async def proxy_remove(self, ctx: commands.Context, *, name: str): """Removes a sona.""" sona_key = name.lower() async with self.config.user(ctx.author).sonas() as sonas: if sona_key not in sonas: return await ctx.send(f"No sona named `{name}` found.") del sonas[sona_key] await ctx.send(f"Sona `{name}` has been removed.") @proxy.group() async def sona(self, ctx: commands.Context): """Commands for managing a sona's appearance.""" pass @proxy.group(name="auto") async def proxy_auto(self, ctx: commands.Context): """Manage automatic translation in a channel.""" pass @proxy_auto.command(name="set") async def proxy_auto_set(self, ctx: commands.Context, sona_name: str): """Sets a sona to automatically translate your messages in this channel.""" sona_key = sona_name.lower() sonas = await self.config.user(ctx.author).sonas() if sona_key not in sonas: return await ctx.send(f"No sona named `{sona_name}` found. Please register it first.") async with self.config.channel(ctx.channel).autotranslate_users() as autotranslate_users: autotranslate_users[str(ctx.author.id)] = sona_key await ctx.send(f"Autotranslation enabled for you in this channel as **{sonas[sona_key]['display_name']}**.") @proxy_auto.command(name="off") async def proxy_auto_off(self, ctx: commands.Context): """Disables autotranslation for you in this channel.""" async with self.config.channel(ctx.channel).autotranslate_users() as autotranslate_users: if str(ctx.author.id) in autotranslate_users: del autotranslate_users[str(ctx.author.id)] await ctx.send("Autotranslation has been disabled for you in this channel.") else: await ctx.send("You do not have autotranslation enabled in this channel.") @sona.command(name="name") async def sona_name(self, ctx: commands.Context, name: str, *, display_name: str): """Changes the display name of a sona.""" sona_key = name.lower() async with self.config.user(ctx.author).sonas() as sonas: if sona_key not in sonas: return await ctx.send(f"No sona named `{name}` found.") sonas[sona_key]["display_name"] = display_name await ctx.send(f"Sona `{name}`'s display name has been changed to `{display_name}`.") @sona.command(name="avatar") async def sona_avatar(self, ctx: commands.Context, name: str, url: Optional[str] = None): """Changes the avatar of a sona. You can either provide a direct image URL or upload an image with the command. """ sona_key = name.lower() if not url and not ctx.message.attachments: return await ctx.send("You must provide an image URL or upload an image.") avatar_url = url if ctx.message.attachments: avatar_url = ctx.message.attachments[0].url async with self.config.user(ctx.author).sonas() as sonas: if sona_key not in sonas: return await ctx.send(f"No sona named `{name}` found.") sonas[sona_key]["avatar_url"] = avatar_url await ctx.send(f"Sona `{name}`'s avatar has been updated.") @commands.group(aliases=["tset"]) @commands.has_permissions(manage_guild=True) async def translatorset(self, ctx: commands.Context): """Admin commands for the Translator cog.""" pass @translatorset.group(name="language", aliases=["lang"]) async def translatorset_language(self, ctx: commands.Context): """Manage custom languages.""" pass @translatorset_language.command(name="add", aliases=["+"]) async def translatorset_language_add(self, ctx: commands.Context, name: str, *, json_map: str): """Adds a new custom language.""" lang_key = name.lower() if lang_key in self.all_languages: return await ctx.send(f"A language with the key `{lang_key}` already exists.") try: lang_map = json.loads(json_map) if not isinstance(lang_map, dict): raise ValueError("JSON map must be an object/dictionary.") except (json.JSONDecodeError, ValueError) as e: return await ctx.send(f"Invalid JSON map provided: `{e}`") new_lang_data = {'name': name.capitalize(), 'type': 'greedy', 'map': lang_map, 'is_custom': True} async with self.config.languages() as languages: languages[lang_key] = new_lang_data await self._initialize_languages() await ctx.send(f"Custom language `{name}` added successfully.") @translatorset_language.command(name="remove", aliases=["-"]) async def translatorset_language_remove(self, ctx: commands.Context, name: str): """Removes a custom language.""" lang_key = name.lower() current_data = await self.config.languages() lang_obj = current_data.get(lang_key) if not lang_obj or not lang_obj.get('is_custom', False): return await ctx.send(f"No custom language named `{name}` found.") async with self.config.languages() as languages: if lang_key in languages: del languages[lang_key] await self._initialize_languages() await ctx.send(f"Custom language `{name}` removed.") @translatorset_language.command(name="listcustom") async def translatorset_language_listcustom(self, ctx: commands.Context): """Lists all custom-added languages.""" custom_langs = [f"`{lang['name']}`" for lang in self.all_languages.values() if lang.get('is_custom')] if not custom_langs: return await ctx.send("There are no custom languages.") await ctx.send("Custom Languages:\n" + ", ".join(custom_langs)) @translatorset_language.command(name="listbase") async def translatorset_language_listbase(self, ctx: commands.Context): """Lists all base (built-in) languages.""" base_langs = [f"`{lang['name']}`" for lang in self.all_languages.values() if not lang.get('is_custom')] await ctx.send("Base Languages:\n" + ", ".join(base_langs))