From c08cec34ba678cd243c00be8a86982d81aac27d5 Mon Sep 17 00:00:00 2001 From: Pukimaa Date: Sun, 1 Oct 2023 14:51:22 +0000 Subject: [PATCH] feat(autotranslate): add channel linking, to send translations into a different channel Warning: There is currently no way to disable or delete a link, unless you contact the server owner! --- cogs/language/autotranslate.py | 211 +++++++++++++++++- i18n/errors/en.yml | 3 + i18n/misc/en.yml | 1 + .../V4__autotranslate_channel_linking.sql | 7 + utils.py | 2 +- 5 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 migrations/V4__autotranslate_channel_linking.sql diff --git a/cogs/language/autotranslate.py b/cogs/language/autotranslate.py index a6cab78c..5fa17468 100644 --- a/cogs/language/autotranslate.py +++ b/cogs/language/autotranslate.py @@ -1,6 +1,7 @@ import io import os import i18n +import json import discord from main import TakoBot from ftlangdetect import detect @@ -100,8 +101,214 @@ async def delete_original(self, interaction: discord.Interaction, value: bool): ephemeral=True, ) - @commands.Cog.listener() - async def on_message(self, message: discord.Message): + # TODO: Add descriptions to command and arguments + @app_commands.command() + @app_commands.guild_only() + async def link( + self, + interaction: discord.Interaction, + source_channel: discord.TextChannel | discord.Thread, + target_channel: discord.TextChannel | discord.Thread, + source_lang: str = "auto", + target_lang: str | None = None, + ): + if source_lang == target_lang: + return await interaction.response.send_message( + i18n.t( + "errors.auto_translate_same_lang", + locale=get_language(self.bot, interaction.guild_id), + ), + ephemeral=True, + ) + if source_channel == target_channel: + return await interaction.response.send_message( + i18n.t( + "errors.auto_translate_same_channel", + locale=get_language(self.bot, interaction.guild_id), + ), + ephemeral=True, + ) + data = await self.bot.db_pool.fetchval( + "SELECT autotranslate_link FROM channels WHERE channel_id = $1", + source_channel.id, + ) + if not data: + data = [] + link_data = { + "target_channel": target_channel.id, + "source_lang": source_lang, + "target_lang": target_lang, + } + if link_data in data: + return await interaction.response.send_message( + i18n.t( + "errors.auto_translate_link_exists", + locale=get_language(self.bot, interaction.guild_id), + ), + ephemeral=True, + ) + data.append(json.dumps(link_data)) + await self.bot.db_pool.execute( + "INSERT INTO channels (channel_id, autotranslate_link) VALUES ($1, $2) ON CONFLICT(channel_id) DO UPDATE SET autotranslate_link = $2", + source_channel.id, + data, + ) + await interaction.response.send_message( + i18n.t( + "misc.auto_translate_linked", + locale=get_language(self.bot, interaction.guild_id), + source_channel=source_channel.mention, + source_lang=source_lang, + target_channel=target_channel.mention, + target_lang=target_lang, + ), + ephemeral=True, + ) + + # TODO: Add command to delete links + + @commands.Cog.listener(name="on_message") + async def on_message_link(self, message: discord.Message): + try: + fetched_val = await self.bot.db_pool.fetchval( + "SELECT autotranslate_link FROM channels WHERE channel_id = $1", + message.channel.id, + ) + except AttributeError: + return + if not fetched_val: + return + for autotranslate_link in fetched_val: + if ( + not message.content + or not autotranslate_link + or message.author.id == self.bot.user.id # type: ignore + or message.webhook_id + or not message.guild + ): + continue + + data = json.loads(autotranslate_link) + source_lang = data["source_lang"] + target_lang = data["target_lang"] + target_channel: discord.abc.GuildChannel | discord.Thread = self.bot.get_channel(data["target_channel"]) # type: ignore + if target_channel == discord.abc.PrivateChannel: + continue + + attachments: list[dict] = [] + if message.attachments: + for attachment in message.attachments: + bytes = await attachment.read() + attachments.append( + { + "bytes": bytes, + "spoiler": attachment.is_spoiler(), + "filename": attachment.filename, + "description": attachment.description, + } + ) + size = 0 + boost_level = message.guild.premium_tier if hasattr(message, "guild") else 0 + size_limit = 8000000 + match boost_level: + case 2: + size_limit = 50000000 + case 3: + size_limit = 100000000 + new_attachments = [] + attachment_removed = False + for attachment in attachments: + file_size = len(attachment["bytes"]) + size += file_size + if size > size_limit: + size -= file_size + attachment_removed = True + continue + attachment_bytes = io.BytesIO(attachment["bytes"]) + new_attachments.append( + discord.File( + attachment_bytes, + spoiler=attachment["spoiler"], + filename=attachment["filename"], + description=attachment["description"], + ) + ) + attachments = new_attachments + too_large_embed, too_large_file = error_embed( + self.bot, + i18n.t("errors.too_large_title", locale=source_lang), + i18n.t("errors.too_large", locale=source_lang), + message.guild.id, + style="warning", + ) + + data = await detect( + message.content.replace("\n", " "), + path=os.path.join(os.getcwd(), "assets/lid.176.bin"), + ) + data["score"] = data["score"] * 100 + confidence = await self.bot.db_pool.fetchval( + "SELECT auto_translate_confidence FROM guilds WHERE guild_id = $1", + message.guild.id, + ) + if not confidence: + confidence = 75 + if confidence >= data["score"]: + return + if data["lang"] != source_lang: + return + + webhook_id = None + if ( + target_channel.type == discord.ChannelType.public_thread + or target_channel.type == discord.ChannelType.private_thread + ): + for webhook in await target_channel.parent.webhooks(): # type: ignore + if webhook.name == f"AutoTranslate ({self.bot.user.id})": # type: ignore + webhook_id = webhook.id + else: + for webhook in await target_channel.webhooks(): # type: ignore + if webhook.name == f"AutoTranslate ({self.bot.user.id})": # type: ignore + webhook_id = webhook.id + if not webhook_id: + if ( + target_channel.type == discord.ChannelType.public_thread + or target_channel.type == discord.ChannelType.private_thread + ): + webhook = await target_channel.parent.create_webhook(name=f"AutoTranslate ({self.bot.user.id})") # type: ignore + else: + webhook = await target_channel.create_webhook(name=f"AutoTranslate ({self.bot.user.id})") # type: ignore + else: + webhook = await self.bot.fetch_webhook(webhook_id) + + translation = await translate(message.content, target_lang, source_lang) + try: + await webhook.send( + username=f"{message.author.display_name} ({translation[1]} ➜ {target_lang})", + avatar_url=message.author.display_avatar.url, + files=attachments, # type: ignore + thread=target_channel if isinstance(target_channel, discord.Thread) else discord.utils.MISSING, # type: ignore + content=translation[0], + ) + except: + continue + + if attachment_removed: + await message.channel.send( + message.author.mention, + embed=too_large_embed, + file=too_large_file, + allowed_mentions=discord.AllowedMentions( + everyone=False, + users=[message.author], + roles=False, + replied_user=False, + ), + ) + continue + + @commands.Cog.listener(name="on_message") + async def on_message_autotranslate(self, message: discord.Message): if not message.guild: return try: diff --git a/i18n/errors/en.yml b/i18n/errors/en.yml index 55f38341..608ff2d4 100644 --- a/i18n/errors/en.yml +++ b/i18n/errors/en.yml @@ -23,3 +23,6 @@ en: not_locked: "%{channel} is currently not locked!" api_error_title: "API Error" api_error: "An error occured while trying to communicate with the API we are using.\n\nPlease try again later or visit [our support server](https://discord.gg/dfmXNTmzyp) with the following information:\n`%{error}`" + auto_translate_same_lang: "The source and target language is the same. Please choose different languages!" + auto_translate_same_channel: "The source and target channel is the same. Please choose different channels!" + auto_translate_link_exists: "There is already a translation link between these channels!" diff --git a/i18n/misc/en.yml b/i18n/misc/en.yml index d27f804d..ccc44871 100644 --- a/i18n/misc/en.yml +++ b/i18n/misc/en.yml @@ -33,6 +33,7 @@ en: auto_translate_confidence_set: "Confidence Threshold is now set to %{value}%" auto_translate_reply_style_set: "Autotranslate reply style is now set to %{style}" auto_translate_delete_original: "Deletion of the original message is now set to %{value}" + auto_translate_linked: "Succesfully linked channels. Messages from %{source_channel} (in language `%{source_lang}`) will now be translated to %{target_channel} (in language `${target_lang}`)." topic_title: "Here's your topic:" topic_footer: "Topic ID: %{id}" topic_invalid_title: "We searched far and wide. Unfortunately, no topics were found." diff --git a/migrations/V4__autotranslate_channel_linking.sql b/migrations/V4__autotranslate_channel_linking.sql new file mode 100644 index 00000000..f2f80bc8 --- /dev/null +++ b/migrations/V4__autotranslate_channel_linking.sql @@ -0,0 +1,7 @@ +-- Revises: V4 +-- Creation Date: 2023-10-01 12:59:52.869202 UTC +-- Reason: Autotranslate Channel Linking +ALTER TABLE + channels +ADD + COLUMN IF NOT EXISTS autotranslate_link TEXT []; \ No newline at end of file diff --git a/utils.py b/utils.py index c493a32b..86599c46 100644 --- a/utils.py +++ b/utils.py @@ -125,7 +125,7 @@ def delete_thumbnail(id: int, icon: str): os.remove(f"assets/thumbnails/{icon}_{id}.png") -def get_language(bot, guild_id: int | None = None, new: bool = False): +def get_language(bot, guild_id: int | None = None): """:class:`str`: Get the language of a guild. Parameters