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:
286
translator/commands.py
Normal file
286
translator/commands.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# -*- 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))
|
||||
|
||||
1
translator/languages/__init__.py
Normal file
1
translator/languages/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
8
translator/languages/abyssal.py
Normal file
8
translator/languages/abyssal.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'azg','b':'braz','c':'kraz','d':'dorg','e':'ezg','f':'fraz','g':'gor','h':'hath','i':'ix','j':'jraz','k':'kral','l':'laz','m':'maz','n':'naz','o':'oz','p':'praz','q':'qor','r':'raz','s':'saz','t':'taz','u':'uzg','v':'vraz','w':'waz','x':'xul','y':'yaz','z':'zaz'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Abyssal',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/angelic.py
Normal file
8
translator/languages/angelic.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'adriel','b':'baraqiel','c':'camael','d':'divinus','e':'elohim','f':'fanuel','g':'gloria','h':'hesed','i':'israfel','j':'jophiel','k':'kyrie','l':'lux','m':'michael','n':'netzach','o':'ophaniel','p':'peniel','q':'qadish','r':'raphael','s':'seraph','t':'tiferet','u':'uriel','v':'virtues','w':'wele\'el','x':'xathanael','y':'yesod','z':'zadkiel'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Angelic',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
9
translator/languages/aquan.py
Normal file
9
translator/languages/aquan.py
Normal file
@@ -0,0 +1,9 @@
|
||||
MAP = {'a':'aqua','b':'blub','c':'\'cress','d':'drop','e':'eelee','f':'flow','g':'glur','h':'hydro','i':'ishi','j':'\'jyr','k':'\'kyr','l':'luu','m':'\'myr','n':'\'nyr','o':'oro','p':'ploop','q':'\'qyr','r':'\'ryp','s':'sh\'l','t':'tide','u':'\'urn','v':'\'vyr','w':'wash','x':'\'xyr','y':'\'yyr','z':'\'zyr'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Aquan',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
|
||||
8
translator/languages/aquatic.py
Normal file
8
translator/languages/aquatic.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'abyss','b':'brine','c':'coral','d':'depth','e':'eel','f':'fin','g':'gurgle','h':'hydro','i':'ink','j':'jelly','k':'krill','l':'lagoon','m':'murk','n':'naut','o':'ocean','p':'pearl','q':'quatic','r':'reef','s':'salt','t':'tide','u':'urchin','v':'void','w':'wave','x':'xiph','y':'yacht','z':'zone'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Aquatic',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/arachnid.py
Normal file
8
translator/languages/arachnid.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'arr','b':'\'brach','c':'ch\'t','d':'\'drach','e':'\'err','f':'\'fune','g':'\'goss','h':'\'harr','i':'\'itt','j':'\'jarr','k':'klik\'','l':'\'lar','m':'\'marr','n':'\'narr','o':'\'orr','p':'\'parr','q':'\'qarr','r':'\'rarr','s':'skitter\'','t':'th\'k','u':'\'urr','v':'\'varr','w':'web\'','x':'\'xarr','y':'\'yarr','z':'\'zarr'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Arachnid',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/avain.py
Normal file
8
translator/languages/avain.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'aara','b':'\'bree','c':'chir\'','d':'\'dree','e':'eek','f':'\'flutter','g':'gree\'','h':'hrooa','i':'ii\'','j':'\'jakk','k':'kree\'','l':'\'liri','m':'\'meeka','n':'\'neer','o':'\'oroo','p':'pip\'','q':'\'qree','r':'\'reea','s':'\'skraw','t':'tweet\'','u':'\'urr','v':'\'vree','w':'\'warble','x':'\'xee','y':'\'yari','z':'\'zeer'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Avian',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/celestial.py
Normal file
8
translator/languages/celestial.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'ana','b':'\'bara','c':'\'cera','d':'\'dona','e':'\'elara','f':'\'fana','g':'\'gala','h':'\'hylia','i':'\'iana','j':'\'jana','k':'\'kana','l':'\'lora','m':'\'mara','n':'\'nara','o':'\'ora','p':'\'pera','q':'\'qana','r':'\'ria','s':'\'sera','t':'\'tara','u':'\'ura','v':'\'vara','w':'\'wana','x':'\'xara','y':'\'yana','z':'\'zara'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Celestial',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/common.py
Normal file
8
translator/languages/common.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# This language is handled by its 'rule' type in the main cog.
|
||||
# It doesn't need a map.
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Common',
|
||||
'type': 'rule'
|
||||
}
|
||||
8
translator/languages/construct.py
Normal file
8
translator/languages/construct.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'auto','b':'bolt','c':'clank','d':'diode','e':'engine','f':'forge','g':'gear','h':'hydro','i':'iron','j':'joint','k':'kinetic','l':'link','m':'motor','n':'node','o':'optic','p':'piston','q':'quantum','r':'rivet','s':'servo','t':'titan','u':'unit','v':'volt','w':'whirr','x':'xenon','y':'yoke','z':'zinc'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Construct',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/devilish.py
Normal file
8
translator/languages/devilish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'ayl','b':'\'baal','c':'\'cress','d':'\'drev','e':'\'eyl','f':'\'fane','g':'\'gyl','h':'hysh\'','i':'\'iyl','j':'\'jex','k':'\'krys','l':'\'lyl','m':'\'mal','n':'\'nyl','o':'\'oyl','p':'\'prax','q':'\'qyl','r':'\'ryl','s':'\'shayd','t':'\'trys','u':'\'uyl','v':'\'vyl','w':'\'wryl','x':'\'xyl','y':'\'yyl','z':'\'zyll'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Devilish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/draconic.py
Normal file
8
translator/languages/draconic.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'ax','b':'baxis','c':'caex','d':'drak','e':'ess','f':'faex','g':'gix','h':'heth','i':'ir','j':'jyss','k':'kex','l':'lix','m':'maex','n':'nex','o':'oth','p':'pex','q':'qexis','r':'rax','s':'syth','t':'thrax','u':'ur','v':'vyx','w':'wess','x':'xis','y':'yth','z':'zix'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Draconic',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/dwarvish.py
Normal file
8
translator/languages/dwarvish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'az','b':'bar','c':'krag','d':'dur','e':'ek','f':'fol','g':'grum','h':'hur','i':'in','j':'jor','k':'kaz','l':'lur','m':'mor','n':'nur','o':'ok','p':'por','q':'qur','r':'ruk','s':'son','t':'thor','u':'um','v':'val','w':'wor','x':'xor','z':'zul'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Dwarvish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/elemental.py
Normal file
8
translator/languages/elemental.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'aer','b':'breeze','c':'cinder','d':'dust','e':'ember','f':'flow','g':'gust','h':'hail','i':'ignis','j':'jet','k':'kinetic','l':'lava','m':'mist','n':'nova','o':'ozone','p':'pyre','q':'quake','r':'rain','s':'stone','t':'terra','u':'umbra','v':'vapor','w':'wave','x':'xenon','y':'yon','z':'zephyr'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Elemental',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
11
translator/languages/elvish.py
Normal file
11
translator/languages/elvish.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
MAP = {'a':'ael','b':'\'eth','c':'cal','d':'dor','e':'elen','f':'fae','g':'\'gan','h':'h<EFBFBD>r','i':'ia','j':'yel','k':'\'ken','l':'lael','m':'mel','n':'n<EFBFBD>n','o':'oia','p':'\'pes','q':'qen','r':'rae','s':'sil','t':'t<EFBFBD>','u':'ui','v':'vae','w':'win','x':'\'xal','y':'yl','z':'z<EFBFBD>r'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Elvish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
|
||||
8
translator/languages/feline.py
Normal file
8
translator/languages/feline.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'meow','b':'brrt','c':'chrr','d':'drrt','e':'eek','f':'frrt','g':'grrowl','h':'hiss','i':'mii','j':'jrr','k':'krr','l':'lrr','m':'mrow','n':'nyah','o':'oww','p':'purr','q':'qrr','r':'rrr','s':'sss','t':'trill','u':'urr','v':'vrr','w':'wrr','x':'xrr','y':'yowl','z':'zzz'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Feline',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/fiendish.py
Normal file
8
translator/languages/fiendish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'az\'ael','b':'ba\'al','c':'cre\'z','d':'dre\'th','e':'esh\'','f':'fiir\'','g':'gre\'th','h':'ha\'el','i':'i\'z','j':'je\'th','k':'krez\'','l':'le\'th','m':'morn\'','n':'ne\'th','o':'o\'z','p':'pre\'th','q':'qe\'th','r':'re\'th','s':'se\'th','t':'te\'th','u':'u\'z','v':'ve\'th','w':'we\'th','x':'xith\'','y':'ye\'th','z':'zael\''}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Fiendish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/gnomish.py
Normal file
8
translator/languages/gnomish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'akk','b':'bink','c':'clank','d':'dink','e':'enk','f':'fizz','g':'giz','h':'hink','i':'ink','j':'jink','k':'kink','l':'link','m':'mink','n':'nink','o':'onk','p':'sprok','q':'qink','r':'rink','s':'sprock','t':'tink','u':'unk','v':'vink','w':'whirr','x':'xink','y':'yink','z':'zink'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Gnomish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/goblin.py
Normal file
8
translator/languages/goblin.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'az','b':'bik','c':'clik','d':'dik','e':'ek','f':'fiz','g':'gib','h':'hik','i':'ik','j':'jik','k':'krik','l':'lik','m':'mik','n':'nik','o':'ok','p':'pik','q':'qik','r':'rik','s':'snik','t':'tik','u':'uk','v':'vik','w':'wik','x':'xik','y':'yik','z':'zik'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Goblin',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/halftongue.py
Normal file
8
translator/languages/halftongue.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'apple','b':'bramble','c':'crumpet','d':'dale','e':'elderberry','f':'fiddle','g':'garden','h':'hearth','i':'iris','j':'jam','k':'kettle','l':'lazy','m':'meadow','n':'nimble','o':'oats','p':'pudding','q':'quaint','r':'river','s':'sunny','t':'tater','u':'underhill','v':'vine','w':'willow','x':'extra','y':'yarn','z':'zesty'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Half-Tongue',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/herbilore.py
Normal file
8
translator/languages/herbilore.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'aloe','b':'bark','c':'clover','d':'dand','e':'elder','f':'fern','g':'groot','h':'herb','i':'ivy','j':'juni','k':'kelp','l':'leaf','m':'moss','n':'nettle','o':'oak','p':'petal','q':'quin','r':'root','s':'sprout','t':'thyme','u':'ursi','v':'vine','w':'willow','x':'xylem','y':'yarrow','z':'zinni'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Herbilore',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/infernal.py
Normal file
8
translator/languages/infernal.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'az\'','b':'\'baal','c':'\'krez','d':'\'drak','e':'ez\'','f':'\'fel','g':'\'gor','h':'\'hath','i':'iz\'','j':'\'jaz','k':'\'kraz','l':'\'laz','m':'\'mor','n':'\'naz','o':'oz\'','p':'\'paz','q':'\'qaz','r':'\'raz','s':'\'saz','t':'\'taz','u':'uz\'','v':'\'vaz','w':'\'waz','x':'\'xaz','y':'\'yaz','z':'\'zaz'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Infernal',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
9
translator/languages/kitsune.py
Normal file
9
translator/languages/kitsune.py
Normal file
@@ -0,0 +1,9 @@
|
||||
MAP = {'a':'ka','b':'be','c':'chi','d':'de','e':'e','f':'fu','g':'ga','h':'hi','i':'i','j':'ji','k':'ki','l':'ru','m':'ma','n':'na','o':'o','p':'pe','q':'kyu','r':'re','s':'sa','t':'to','u':'u','v':'ve','w':'wa','x':'za','y':'ya','z':'ze'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Kitsune',
|
||||
'type': 'greedy',
|
||||
'map': MAP,
|
||||
'separator': ''
|
||||
}
|
||||
9
translator/languages/leet.py
Normal file
9
translator/languages/leet.py
Normal file
@@ -0,0 +1,9 @@
|
||||
MAP = {'a':'4','b':'8','c':'(','d':')','e':'3','f':'|=','g':'6','h':'#','i':'1','j':']','k':'|<','l':'1','m':'/\\/\\','n':'/\\/','o':'0','p':'|D','q':'(,)','r':'|2','s':'5','t':'7','u':'|_|','v':'\\/','w':'\\/\\/','x':'><','y':'`/','z':'2'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Leet Speak',
|
||||
'type': 'special',
|
||||
'map': MAP
|
||||
|
||||
}
|
||||
8
translator/languages/lizardfolk.py
Normal file
8
translator/languages/lizardfolk.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'ax','b':'\'bax','c':'\'caz','d':'\'daz','e':'\'ex','f':'\'fax','g':'\'gaz','h':'h\'ss','i':'\'ix','j':'\'jax','k':'sk\'ex','l':'\'lax','m':'\'max','n':'\'nax','o':'\'ox','p':'\'pax','q':'\'qax','r':'\'rax','s':'s\'lith','t':'\'char','u':'\'ux','v':'\'vax','w':'\'wax','x':'\'xax','y':'\'yax','z':'\'zax'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Lizardfolk',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/morsecode.py
Normal file
8
translator/languages/morsecode.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'.-','b':'-...','c':'-.-.','d':'-..','e':'.','f':'..-.','g':'--.','h':'....','i':'..','j':'.---','k':'-.-','l':'.-..','m':'--','n':'-.','o':'---','p':'.--.','q':'--.-','r':'.-.','s':'...','t':'-','u':'..-','v':'...-','w':'.--','x':'-..-','y':'-.--','z':'--..','1':'.----','2':'..---','3':'...--','4':'....-','5':'.....','6':'-....','7':'--...','8':'---..','9':'----.','0':'-----',' ':'/'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Morse Code',
|
||||
'type': 'special',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/myconid.py
Normal file
8
translator/languages/myconid.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'agaric','b':'bolete','c':'cap','d':'decay','e':'enoki','f':'fungi','g':'gill','h':'hyphae','i':'indigo','j':'jelly','k':'kombu','l':'lichen','m':'myco\'','n':'nidur','o':'oyster','p':'puff','q':'quorn','r':'rhizo','s':'spore\'','t':'thallus','u':'umbra','v':'velvet','w':'wart','x':'xero','y':'yeast','z':'zoospore'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Myconid',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/ogrish.py
Normal file
8
translator/languages/ogrish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'ug','b':'blud','c':'crag','d':'dug','e':'eeg','f':'fug','g':'gron','h':'hug','i':'ig','j':'jug','k':'krug','l':'lug','m':'mush','n':'nug','o':'og','p':'pug','q':'qug','r':'rug','s':'slog','t':'thok','u':'urk','v':'vog','w':'wug','x':'xug','y':'yug','z':'zug'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Ogrish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/orcish.py
Normal file
8
translator/languages/orcish.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'agh','b':'bug','c':'karg','d':'dur','e':'egh','f':'fug','g':'grol','h':'hosh','i':'izg','j':'jug','k':'krunk','l':'lug','m':'mog','n':'nog','o':'ogg','p':'pug','q':'qug','r':'ruk','s':'snaga','t':'tusk','u':'uruk','v':'vug','w':'warg','x':'xug','y':'yag','z':'zug'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Orcish',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/piglatin.py
Normal file
8
translator/languages/piglatin.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# This language is handled by its 'rule' type in the main cog.
|
||||
# It doesn't need a map.
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Pig Latin',
|
||||
'type': 'rule'
|
||||
}
|
||||
8
translator/languages/ratfolk.py
Normal file
8
translator/languages/ratfolk.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'skree','b':'bite','c':'claw','d':'dark-thing','e':'eek','f':'filth','g':'gnaw','h':'hiss','i':'itch','j':'junk','k':'kill','l':'long-tail','m':'muck','n':'nest-thing','o':'rot-stink','p':'plague','q':'quick-quick','r':'rust','s':'skitter','t':'twitch','u':'under-thing','v':'vermin','w':'waste','x':'pox','y':'yes-yes','z':'zap-tail'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Ratfolk',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/scorpion.py
Normal file
8
translator/languages/scorpion.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'sk\'','b':'t\'k','c':'k\'ss','d':'d\'th','e':'e\'sk','f':'f\'t','g':'g\'th','h':'h\'k','i':'i\'s','j':'j\'t','k':'k\'t','l':'l\'k','m':'m\'k','n':'n\'t','o':'o\'s','p':'p\'k','q':'q\'t','r':'r\'k','s':'s\'k','t':'t\'s','u':'u\'s','v':'v\'t','w':'w\'k','x':'x\'s','y':'y\'k','z':'z\'t'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Scorpion',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
10
translator/languages/sinary.py
Normal file
10
translator/languages/sinary.py
Normal file
@@ -0,0 +1,10 @@
|
||||
MAP = {'a':'a!','b':'7b','c':'c#','d':'d4','e':'3e','f':'f^','g':'g&','h':'h8','i':'(i','j':'j0','k':'_k','l':'l2','m':'=m','n':'n+','o':'5o','p':'-p','q':'q{','r':'}r','s':'[s','t':'t]','u':'|u','v':':v','w':'"w','x':'x<','y':'>y','z':'?z'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Sinary',
|
||||
'type': 'generic',
|
||||
'map': MAP,
|
||||
'chunk_size': 2,
|
||||
'separator': ''
|
||||
}
|
||||
8
translator/languages/spiritual.py
Normal file
8
translator/languages/spiritual.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'\'aura','b':'\'breth','c':'\'ciel','d':'\'dion','e':'\'ethys','f':'\'fey','g':'\'glyn','h':'\'hymn','i':'\'ia','j':'\'jora','k':'\'kye','l':'\'lume','m':'\'mana','n':'\'nima','o':'\'omni','p':'\'pria','q':'\'qia','r':'\'reth','s':'\'seren','t':'\'thyme','u':'\'umbra','v':'\'vym','w':'\'wisp','x':'\'xia','y':'\'yara','z':'\'zion'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Spiritual',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/succubus.py
Normal file
8
translator/languages/succubus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'ah\'','b':'\'bel','c':'\'chae','d':'\'des','e':'\'esh','f':'\'fey','g':'\'gis','h':'hah\'','i':'\'ish','j':'\'jo','k':'\'ka','l':'\'lis','m':'\'mah','n':'\'nah','o':'oh\'','p':'\'pah','q':'\'qia','r':'\'rah','s':'\'sha','t':'\'thae','u':'uh\'','v':'\'vi','w':'\'wah','x':'\'xi','y':'\'yah','z':'\'zah'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Succubus',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/sylvan.py
Normal file
8
translator/languages/sylvan.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'ani','b':'bri','c':'cae','d':'dae','e':'eni','f':'fae','g':'gra','h':'hae','i':'ia','j':'jae','k':'kae','l':'lor','m':'mae','n':'nem','o':'olo','p':'pae','q':'qae','r':'rae','s':'sae','t':'tae','u':'uni','v':'vae','w':'wae','x':'xae','y':'yae','z':'zae'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Sylvan',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/undercommon.py
Normal file
8
translator/languages/undercommon.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'velk','b':'xund','c':'k\'yorl','d':'d\'ruth','e':'e\'trorn','f':'faer','g':'gol','h':'h\'chak','i':'i\'lith','j':'j\'lar','k':'k\'lar','l':'lil','m':'m\'lar','n':'nind','o':'olath','p':'p\'lar','q':'qu\'ellar','r':'ril','s':'sorn','t':'\'lar','u':'uss','v':'v\'lar','w':'wyl','x':'x\'lar','y':'y\'lar','z':'z\'ress'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Undercommon',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
8
translator/languages/uwu.py
Normal file
8
translator/languages/uwu.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# This language is handled by its 'rule' type in the main cog.
|
||||
# It doesn't need a map.
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'UwU',
|
||||
'type': 'rule'
|
||||
}
|
||||
10
translator/languages/valspiren.py
Normal file
10
translator/languages/valspiren.py
Normal file
@@ -0,0 +1,10 @@
|
||||
MAP = {'a':'ak','b':'ba','c':'ce','d':'di','e':'ek','f':'fo','g':'gu','h':'ha','i':'ik','j':'je','k':'ki','l':'lo','m':'mu','n':'na','o':'ok','p':'pe','q':'qi','r':'ro','s':'su','t':'ta','u':'uk','v':'ve','w':'wi','x':'xo','y':'yk','z':'zu'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Valspiren',
|
||||
'type': 'generic',
|
||||
'map': MAP,
|
||||
'chunk_size': 2,
|
||||
'separator': ''
|
||||
}
|
||||
8
translator/languages/voidtouched.py
Normal file
8
translator/languages/voidtouched.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MAP = {'a':'a\'th','b':'b\'zoth','c':'c\'thun','d':'d\'gol','e':'e\'th','f':'f\'thagn','g':'g\'noth','h':'h\'zoth','i':'i\'th','j':'j\'th','k':'k\'th','l':'l\'th','m':'m\'th','n':'n\'th','o':'o\'th','p':'p\'th','q':'qor\'','r':'r\'lyeh','s':'s\'th','t':'t\'th','u':'u\'th','v':'v\'lath','w':'w\'th','x':'x\'thul','y':'y\'th','z':'zy\'th'}
|
||||
|
||||
def get_language():
|
||||
return {
|
||||
'name': 'Voidtouched',
|
||||
'type': 'greedy',
|
||||
'map': MAP
|
||||
}
|
||||
196
translator/logic.py
Normal file
196
translator/logic.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import random
|
||||
from redbot.core.utils.chat_formatting import box
|
||||
|
||||
def reverse_map(m): return {v: k for k, v in m.items()}
|
||||
|
||||
class TranslationLogicMixin:
|
||||
"""This class holds all the translation logic for the cog."""
|
||||
|
||||
def _escape_regex(self, s):
|
||||
return re.escape(s)
|
||||
|
||||
def _generic_translator(self, text, lang_map, char_separator):
|
||||
# Regex to find custom emojis
|
||||
emoji_regex = re.compile(r"<a?:\w+:\d+>")
|
||||
|
||||
# Find all emojis and store them
|
||||
emojis = emoji_regex.findall(text)
|
||||
|
||||
# Replace emojis with a unique placeholder
|
||||
placeholder = "||EMOJI||"
|
||||
text_with_placeholders = emoji_regex.sub(placeholder, text)
|
||||
|
||||
# Translate the text with placeholders
|
||||
word_separator = ' ' if char_separator == '' else ' '
|
||||
words = text_with_placeholders.split(' ')
|
||||
translated_words = []
|
||||
emoji_counter = 0
|
||||
|
||||
for word in words:
|
||||
if placeholder in word:
|
||||
# If a word contains a placeholder, it might be part of the emoji code that got split.
|
||||
# We simply re-insert the emoji from our list.
|
||||
translated_words.append(emojis[emoji_counter])
|
||||
emoji_counter += 1
|
||||
else:
|
||||
translated_word = char_separator.join([lang_map.get(char.lower(), char) for char in word])
|
||||
translated_words.append(translated_word)
|
||||
|
||||
return word_separator.join(translated_words)
|
||||
|
||||
def _generic_decoder(self, text, reverse_map, chunk_size):
|
||||
result = ""
|
||||
text_no_space = text.replace(' ','')
|
||||
for i in range(0, len(text_no_space), chunk_size):
|
||||
chunk = text_no_space[i:i+chunk_size]
|
||||
result += reverse_map.get(chunk, '?')
|
||||
return result
|
||||
|
||||
def _greedy_decoder(self, text, reverse_map):
|
||||
syllables = sorted(reverse_map.keys(), key=len, reverse=True)
|
||||
regex = re.compile('|'.join(map(self._escape_regex, syllables)))
|
||||
matches = regex.findall(text.replace(' ', ''))
|
||||
return "".join([reverse_map.get(m, '?') for m in matches])
|
||||
|
||||
def _leet_decoder(self, text, reverse_map):
|
||||
decoded_text = text
|
||||
sorted_keys = sorted(reverse_map.keys(), key=len, reverse=True)
|
||||
for key in sorted_keys:
|
||||
decoded_text = decoded_text.replace(key, reverse_map[key])
|
||||
return decoded_text
|
||||
|
||||
def _morse_decoder(self, text, reverse_map):
|
||||
words = text.split(' ')
|
||||
decoded_words = []
|
||||
for word in words:
|
||||
chars = word.split(' ')
|
||||
decoded_words.append("".join([reverse_map.get(c, '?') for c in chars]))
|
||||
return " ".join(decoded_words)
|
||||
|
||||
def _pig_latin_translator(self, text):
|
||||
vowels = "aeiou"
|
||||
translated_words = []
|
||||
for word in text.split(' '):
|
||||
if not word: continue
|
||||
match = re.match(r"^([^a-zA-Z0-9]*)(.*?)([^a-zA-Z0-9]*)$", word)
|
||||
leading_punct, clean_word, trailing_punct = match.groups()
|
||||
|
||||
if not clean_word:
|
||||
translated_words.append(word)
|
||||
continue
|
||||
|
||||
if clean_word and clean_word[0].lower() in vowels:
|
||||
translated_word = clean_word + "way"
|
||||
else:
|
||||
first_vowel_index = -1
|
||||
for i, char in enumerate(clean_word):
|
||||
if char.lower() in vowels:
|
||||
first_vowel_index = i
|
||||
break
|
||||
if first_vowel_index == -1:
|
||||
translated_word = clean_word + "ay"
|
||||
else:
|
||||
translated_word = clean_word[first_vowel_index:] + clean_word[:first_vowel_index] + "ay"
|
||||
translated_words.append(leading_punct + translated_word + trailing_punct)
|
||||
return " ".join(translated_words)
|
||||
|
||||
def _pig_latin_decoder(self, text):
|
||||
decoded_words = []
|
||||
for word in text.split(' '):
|
||||
if not word: continue
|
||||
match = re.match(r"^([^a-zA-Z0-9]*)(.*?)([^a-zA-Z0-9]*)$", word)
|
||||
leading_punct, clean_word, trailing_punct = match.groups()
|
||||
|
||||
if not clean_word:
|
||||
decoded_words.append(word)
|
||||
continue
|
||||
|
||||
original_word = ""
|
||||
if clean_word.lower().endswith("way"):
|
||||
original_word = clean_word[:-3]
|
||||
elif clean_word.lower().endswith("ay"):
|
||||
base_word = clean_word[:-2]
|
||||
last_consonant_block_index = -1
|
||||
for i in range(len(base_word) - 1, -1, -1):
|
||||
if base_word[i].lower() not in "aeiou":
|
||||
last_consonant_block_index = i
|
||||
else:
|
||||
break
|
||||
if last_consonant_block_index != -1:
|
||||
while last_consonant_block_index > 0 and base_word[last_consonant_block_index-1].lower() not in "aeiou":
|
||||
last_consonant_block_index -= 1
|
||||
consonants = base_word[last_consonant_block_index:]
|
||||
stem = base_word[:last_consonant_block_index]
|
||||
original_word = consonants + stem
|
||||
else:
|
||||
original_word = base_word
|
||||
else:
|
||||
original_word = clean_word
|
||||
decoded_words.append(leading_punct + original_word + trailing_punct)
|
||||
return " ".join(decoded_words)
|
||||
|
||||
def _uwu_translator(self, text):
|
||||
text = text.lower()
|
||||
text = text.replace('l', 'ww')
|
||||
text = text.replace('r', 'w')
|
||||
text = text.replace('na', 'nya').replace('ne', 'nye').replace('ni', 'nyi').replace('no', 'nyo').replace('nu', 'nyu')
|
||||
text = text.replace('ove', 'uv')
|
||||
words = text.split(' ')
|
||||
uwu_words = []
|
||||
for word in words:
|
||||
if len(word) > 3 and random.random() < 0.3:
|
||||
uwu_words.append(f"{word[0]}-{word}")
|
||||
else:
|
||||
uwu_words.append(word)
|
||||
text = " ".join(uwu_words)
|
||||
if random.random() < 0.5:
|
||||
emoticons = [' uwu', ' owo', ' >w<', ' ^-^', ' ;;']
|
||||
text += random.choice(emoticons)
|
||||
return text
|
||||
|
||||
def _uwu_decoder(self, text):
|
||||
emoticons = [' uwu', ' owo', ' >w<', ' ^-^', ' ;;']
|
||||
for emo in emoticons:
|
||||
if text.endswith(emo):
|
||||
text = text[:-len(emo)]
|
||||
text = re.sub(r'(\w)-(\w+)', r'\2', text)
|
||||
text = text.replace('uv', 'ove')
|
||||
text = text.replace('nyu', 'nu').replace('nyo', 'no').replace('nyi', 'ni').replace('nye', 'ne').replace('nya', 'na')
|
||||
text = text.replace('ww', 'l')
|
||||
text = text.replace('w', 'r')
|
||||
return text
|
||||
|
||||
async def _find_language(self, query: str):
|
||||
"""Finds languages by key or name, case-insensitively."""
|
||||
query = query.lower()
|
||||
exact_key_match = [key for key in self.all_languages if key.lower() == query]
|
||||
if exact_key_match:
|
||||
return exact_key_match
|
||||
|
||||
exact_name_match = [key for key, lang in self.all_languages.items() if lang['name'].lower() == query]
|
||||
if exact_name_match:
|
||||
return exact_name_match
|
||||
|
||||
partial_matches = [
|
||||
key for key, lang in self.all_languages.items()
|
||||
if query in key.lower() or query in lang['name'].lower()
|
||||
]
|
||||
return partial_matches
|
||||
|
||||
async def _auto_translate_to_common(self, text: str) -> list:
|
||||
"""Helper to find all possible translations for a given text."""
|
||||
possible_translations = []
|
||||
for lang_key, lang_obj in self.all_languages.items():
|
||||
if lang_key == 'common':
|
||||
continue
|
||||
try:
|
||||
decoded_text = lang_obj['from_func'](text)
|
||||
re_encoded_text = lang_obj['to_func'](decoded_text)
|
||||
if re_encoded_text == text:
|
||||
possible_translations.append(f"**As {lang_obj['name']}:**\n{box(decoded_text)}")
|
||||
except Exception:
|
||||
continue
|
||||
return possible_translations
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
28
translator/views.py
Normal file
28
translator/views.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import discord
|
||||
|
||||
class DismissView(discord.ui.View):
|
||||
def __init__(self, author: discord.Member):
|
||||
super().__init__(timeout=1800) # 30 minute timeout
|
||||
self.author = author
|
||||
self.message: discord.Message = None
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||
if interaction.user.id != self.author.id:
|
||||
await interaction.response.send_message("You are not authorized to dismiss this message.", ephemeral=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
@discord.ui.button(label="Dismiss", style=discord.ButtonStyle.grey, emoji="??")
|
||||
async def dismiss_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.message.delete()
|
||||
self.stop()
|
||||
|
||||
async def on_timeout(self):
|
||||
if self.message:
|
||||
try:
|
||||
await self.message.delete()
|
||||
except (discord.NotFound, discord.Forbidden):
|
||||
pass # Message was already deleted or permissions are missing
|
||||
self.stop()
|
||||
|
||||
Reference in New Issue
Block a user