feat: add multiple discord bot cogs
Adds new cogs including DataManager, Hiring, KofiShop, Logging, ModMail, MORS, ServiceReview, StaffMsg, and Translator to enhance bot functionality for data management, hiring processes, logging events, and more.
This commit is contained in:
249
translator/translator.py
Normal file
249
translator/translator.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import discord
|
||||
import pkgutil
|
||||
from redbot.core import commands, app_commands, Config
|
||||
from redbot.core.utils.chat_formatting import box
|
||||
from . import languages as lang_pkg
|
||||
from .views import DismissView
|
||||
from .logic import TranslationLogicMixin
|
||||
from .commands import CommandsMixin
|
||||
|
||||
def reverse_map(m): return {v: k for k, v in m.items()}
|
||||
|
||||
class Translator(CommandsMixin, TranslationLogicMixin, commands.Cog):
|
||||
"""A cog for translating text into various languages."""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=8675309, force_registration=True)
|
||||
self.config.register_global(languages={})
|
||||
self.config.register_user(sonas={}, proxy_enabled=False)
|
||||
self.config.register_channel(autotranslate_users={})
|
||||
|
||||
self.all_languages = {}
|
||||
self.translate_to_common_context_menu = app_commands.ContextMenu(
|
||||
name="Translate to Common",
|
||||
callback=self.translate_to_common_context,
|
||||
)
|
||||
|
||||
async def cog_load(self):
|
||||
"""Load all languages from config and build the runtime objects."""
|
||||
self.bot.tree.add_command(self.translate_to_common_context_menu)
|
||||
await self._initialize_languages()
|
||||
|
||||
async def cog_unload(self):
|
||||
"""Remove context menu on cog unload."""
|
||||
self.bot.tree.remove_command(self.translate_to_common_context_menu.name, type=self.translate_to_common_context_menu.type)
|
||||
|
||||
def _load_base_languages_from_files(self):
|
||||
"""Dynamically loads all base languages from the languages/ subfolder."""
|
||||
base_languages_data = {}
|
||||
for importer, modname, ispkg in pkgutil.iter_modules(lang_pkg.__path__, f"{lang_pkg.__name__}."):
|
||||
if not ispkg:
|
||||
try:
|
||||
module = __import__(modname, fromlist=["get_language"])
|
||||
if hasattr(module, "get_language"):
|
||||
lang_data = module.get_language()
|
||||
lang_key = modname.split('.')[-1]
|
||||
base_languages_data[lang_key] = lang_data
|
||||
except Exception as e:
|
||||
print(f"Error loading language module {modname}: {e}")
|
||||
return base_languages_data
|
||||
|
||||
def _build_runtime_languages(self, language_data):
|
||||
"""Builds the final language dict with functions from stored data."""
|
||||
runtime_langs = {}
|
||||
for key, data in language_data.items():
|
||||
lang_type = data.get("type", "greedy") # Default to greedy for custom
|
||||
|
||||
runtime_langs[key] = {"name": data["name"], "is_custom": data.get("is_custom", False), "type": lang_type}
|
||||
|
||||
if lang_type == "rule":
|
||||
if key == "common":
|
||||
runtime_langs[key]['to_func'] = lambda text: text
|
||||
runtime_langs[key]['from_func'] = lambda text: text
|
||||
elif key == "piglatin":
|
||||
runtime_langs[key]['to_func'] = self._pig_latin_translator
|
||||
runtime_langs[key]['from_func'] = self._pig_latin_decoder
|
||||
elif key == "uwu":
|
||||
runtime_langs[key]['to_func'] = self._uwu_translator
|
||||
runtime_langs[key]['from_func'] = self._uwu_decoder
|
||||
|
||||
elif lang_type == "generic":
|
||||
lang_map = data.get("map", {})
|
||||
chunk_size = data.get("chunk_size", 2)
|
||||
char_separator = data.get("separator", "")
|
||||
word_separator = ' ' if char_separator == '' else ' '
|
||||
runtime_langs[key]['to_func'] = lambda text, m=lang_map, s=char_separator: self._generic_translator(text, m, s)
|
||||
runtime_langs[key]['from_func'] = lambda text, rm=reverse_map(lang_map), cs=chunk_size, ws=word_separator: " ".join([self._generic_decoder(word, rm, cs) for word in text.split(ws)])
|
||||
|
||||
elif lang_type == "special":
|
||||
if key == "leet":
|
||||
lang_map = data.get("map", {})
|
||||
runtime_langs[key]['to_func'] = lambda text, m=lang_map: self._generic_translator(text, m, "")
|
||||
runtime_langs[key]['from_func'] = lambda text, rm=reverse_map(lang_map): self._leet_decoder(text, rm)
|
||||
elif key == "morse":
|
||||
lang_map = data.get("map", {})
|
||||
runtime_langs[key]['to_func'] = lambda text, m=lang_map: self._generic_translator(text, m, " ")
|
||||
runtime_langs[key]['from_func'] = lambda text, rm=reverse_map(lang_map): self._morse_decoder(text, rm)
|
||||
|
||||
else: # Greedy is the default
|
||||
lang_map = data.get("map", {})
|
||||
char_separator = data.get("separator", " ")
|
||||
word_separator = ' ' if char_separator == '' else ' '
|
||||
runtime_langs[key]['to_func'] = lambda text, m=lang_map, s=char_separator: self._generic_translator(text, m, s)
|
||||
runtime_langs[key]['from_func'] = lambda text, rm=reverse_map(lang_map), ws=word_separator: " ".join([self._greedy_decoder(word, rm) for word in text.split(ws)])
|
||||
return runtime_langs
|
||||
|
||||
async def _initialize_languages(self):
|
||||
"""Merge default and custom languages from config."""
|
||||
base_languages_data = self._load_base_languages_from_files()
|
||||
|
||||
stored_languages_data = await self.config.languages()
|
||||
if not stored_languages_data:
|
||||
await self.config.languages.set(base_languages_data)
|
||||
final_data = base_languages_data
|
||||
else:
|
||||
final_data = base_languages_data.copy()
|
||||
for key, data in stored_languages_data.items():
|
||||
if data.get("is_custom"):
|
||||
final_data[key] = data
|
||||
await self.config.languages.set(final_data)
|
||||
|
||||
self.all_languages = self._build_runtime_languages(final_data)
|
||||
|
||||
async def translate_to_common_context(self, interaction: discord.Interaction, message: discord.Message):
|
||||
"""Translate a message to Common."""
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
possible_translations = await self._auto_translate_to_common(message.content)
|
||||
|
||||
if not possible_translations:
|
||||
await interaction.followup.send("Could not find a valid translation for this message.", ephemeral=True)
|
||||
else:
|
||||
await interaction.followup.send("\n\n".join(possible_translations), ephemeral=True)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message: discord.Message):
|
||||
if message.author.bot or not message.guild:
|
||||
return
|
||||
|
||||
prefix = (await self.bot.get_prefix(message))[0]
|
||||
if message.content.startswith(prefix):
|
||||
return
|
||||
|
||||
if message.reference and message.content.lower() == 'translate':
|
||||
try:
|
||||
replied_message = await message.channel.fetch_message(message.reference.message_id)
|
||||
text_to_translate = replied_message.content
|
||||
possible_translations = await self._auto_translate_to_common(text_to_translate)
|
||||
|
||||
if not possible_translations:
|
||||
response_msg = "Could not find a valid translation for the replied message."
|
||||
else:
|
||||
response_msg = "\n\n".join(possible_translations)
|
||||
|
||||
view = DismissView(author=message.author)
|
||||
sent_message = await message.reply(
|
||||
response_msg,
|
||||
view=view,
|
||||
allowed_mentions=discord.AllowedMentions.none()
|
||||
)
|
||||
view.message = sent_message
|
||||
|
||||
if message.channel.permissions_for(message.guild.me).manage_messages:
|
||||
await message.delete()
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
user_settings = await self.config.user(message.author).all()
|
||||
if not user_settings['sonas']:
|
||||
return
|
||||
|
||||
content = message.content
|
||||
matched_sona = None
|
||||
|
||||
for sona_data in user_settings['sonas'].values():
|
||||
if 'claws' in sona_data:
|
||||
start_claw, end_claw = sona_data['claws']
|
||||
elif 'brackets' in sona_data:
|
||||
start_claw, end_claw = sona_data['brackets']
|
||||
else:
|
||||
continue
|
||||
|
||||
if not content.startswith(start_claw): continue
|
||||
if end_claw and end_claw != "":
|
||||
if not content.endswith(end_claw) or len(content) < len(start_claw) + len(end_claw):
|
||||
continue
|
||||
matched_sona = sona_data
|
||||
break
|
||||
|
||||
if matched_sona:
|
||||
if not user_settings['proxy_enabled']:
|
||||
msg = (
|
||||
f"{message.author.mention}, it looks like you tried to proxy as **{matched_sona['display_name']}**, "
|
||||
f"but your proxy is turned off. You can re-enable it with the `{prefix}proxy` command."
|
||||
)
|
||||
try:
|
||||
view = DismissView(author=message.author)
|
||||
sent_message = await message.channel.send(msg, view=view, allowed_mentions=discord.AllowedMentions(users=True))
|
||||
view.message = sent_message
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
return
|
||||
|
||||
if 'claws' in matched_sona:
|
||||
start_claw, end_claw = matched_sona['claws']
|
||||
else:
|
||||
start_claw, end_claw = matched_sona['brackets']
|
||||
|
||||
text_to_translate = content[len(start_claw):-len(end_claw)] if end_claw else content[len(start_claw):]
|
||||
lang_key = matched_sona['language']
|
||||
|
||||
else:
|
||||
autotranslate_users = await self.config.channel(message.channel).autotranslate_users()
|
||||
user_id_str = str(message.author.id)
|
||||
if user_id_str not in autotranslate_users:
|
||||
return
|
||||
|
||||
sona_key = autotranslate_users[user_id_str]
|
||||
sonas = user_settings.get('sonas', {})
|
||||
if sona_key not in sonas:
|
||||
return
|
||||
|
||||
matched_sona = sonas[sona_key]
|
||||
text_to_translate = content
|
||||
lang_key = matched_sona['language']
|
||||
|
||||
to_lang_obj = self.all_languages.get(lang_key)
|
||||
if not to_lang_obj: return
|
||||
|
||||
try:
|
||||
translated_text = to_lang_obj['to_func'](text_to_translate)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
webhook = None
|
||||
if message.channel.permissions_for(message.guild.me).manage_webhooks:
|
||||
webhooks = await message.channel.webhooks()
|
||||
webhook = next((wh for wh in webhooks if wh.name == "Translator Cog Webhook"), None)
|
||||
if webhook is None:
|
||||
webhook = await message.channel.create_webhook(name="Translator Cog Webhook")
|
||||
|
||||
if webhook:
|
||||
if message.channel.permissions_for(message.guild.me).manage_messages:
|
||||
try:
|
||||
await message.delete()
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
display_name = matched_sona.get("display_name", message.author.display_name)
|
||||
avatar_url = matched_sona.get("avatar_url") or message.author.display_avatar.url
|
||||
|
||||
await webhook.send(
|
||||
content=translated_text,
|
||||
username=display_name,
|
||||
avatar_url=avatar_url
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user