small fixes to some cogs. added translation cog from Trusty with changes for a friend

This commit is contained in:
Brandon 2022-03-07 15:51:04 -05:00
parent 9540c3c652
commit ef3868d231
12 changed files with 1609 additions and 21 deletions

View file

@ -100,7 +100,7 @@ class EveryoneEmoji(commands.Cog):
name = name[1]
img = await resp_2.read()
else:
async with self.session.get(url) as resp:
async with self.session.get(str(url)) as resp:
if resp.status != 200:
await ctx.send("Emoji not found.")
return

View file

@ -29,13 +29,25 @@ class ImageMagic(commands.Cog):
# original by Flame442, edited for Wand by ScriptPony
if not ctx.message.attachments and not link:
async for msg in ctx.channel.history(limit=10):
for a in msg.attachments:
path = urllib.parse.urlparse(a.url).path
link = a.url
break
if link:
break
# first check for reply message
if ctx.message.reference:
msg = ctx.message.reference.resolved
if msg is None:
msg = await ctx.channel.fetch_message(ctx.message.reference.message_id)
if msg and msg.attachments:
for a in msg.attachments:
path = urllib.parse.urlparse(a.url).path
link = a.url
break
if not link:
async for msg in ctx.channel.history(limit=10):
for a in msg.attachments:
path = urllib.parse.urlparse(a.url).path
link = a.url
break
if link:
break
if not link:
raise ImageFindError("Please provide an attachment.")
if link: # linked image
@ -94,13 +106,13 @@ class ImageMagic(commands.Cog):
try:
img, name = await asyncio.wait_for(task, timeout=60)
except asyncio.TimeoutError:
await ctx.send("The image took too long to process.")
await ctx.reply("The image took too long to process.", mention_author=False)
return
try:
await ctx.send(file=discord.File(BytesIO(img.make_blob()), name))
await ctx.reply(file=discord.File(BytesIO(img.make_blob()), name), mention_author=False)
except discord.errors.HTTPException:
await ctx.send("That image is too large.")
await ctx.reply("That image is too large.", mention_author=False)
return
@commands.group()
@ -125,7 +137,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
await self._command_body(
ctx,
@ -148,7 +160,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
await self._command_body(ctx, args=(self._distortion, img, "implode", (amount * intensity,)))
@ -165,7 +177,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
await self._command_body(ctx, args=(self._distortion, img, "swirl", (intensity,)))
@ -179,7 +191,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
await self._command_body(ctx, args=(self._distortion, img, "charcoal", (1.5, 0.5)))
@ -193,7 +205,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
await self._command_body(ctx, args=(self._distortion, img, "sketch", (0.5, 0.0, 98.0)))
@ -207,7 +219,7 @@ class ImageMagic(commands.Cog):
try:
img = await self._get_image(ctx, link)
except ImageFindError as e:
return await ctx.send(e)
return await ctx.reply(e, mention_author=False)
h = img.height
w = img.width

View file

@ -25,9 +25,14 @@ class ReactPoll(commands.Cog):
self.config = Config.get_conf(self, identifier=9675846083, force_registration=True)
self.config.register_global(poll_sessions={})
self.loop = bot.loop
self.loop.create_task(self.load_polls())
self.poll_task = self.loop.create_task(self.poll_closer())
self.load_task = asyncio.create_task(self.load_polls())
self.poll_task = asyncio.create_task(self.poll_closer())
def cog_unload(self):
if self.load_task:
self.load_task.cancel()
if self.poll_task:
self.poll_task.cancel()
async def poll_closer(self):
await self.bot.wait_until_ready()

View file

@ -1,6 +1,5 @@
from redbot.core import bank, commands, checks, Config
from redbot.core.utils.chat_formatting import *
from redbot.core.utils.predicates import MessagePredicate
from redbot.core.data_manager import cog_data_path
from aitextgen import aitextgen

13
translate/__init__.py Normal file
View file

@ -0,0 +1,13 @@
import json
from pathlib import Path
from .translate import Translate
with open(Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot):
cog = Translate(bot)
await cog.init()
bot.add_cog(cog)

578
translate/api.py Normal file
View file

@ -0,0 +1,578 @@
import asyncio
import logging
import re
import time
from copy import deepcopy
from typing import Dict, List, Mapping, Optional, Tuple, Union, cast
import aiohttp
import discord
from discord.ext.commands.converter import Converter
from discord.ext.commands.errors import BadArgument
from redbot.core import Config, VersionInfo, commands, version_info
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from .errors import GoogleTranslateAPIError
from .flags import FLAGS
BASE_URL = "https://translation.googleapis.com"
_ = Translator("Translate", __file__)
log = logging.getLogger("red.trusty-cogs.Translate")
FLAG_REGEX = re.compile(r"|".join(rf"{re.escape(f)}" for f in FLAGS.keys()))
class FlagTranslation(Converter):
"""
This will convert flags and languages to the correct code to be used by the API
Guidance code on how to do this from:
https://github.com/Rapptz/discord.py/blob/rewrite/discord/ext/commands/converter.py#L85
https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/mod/mod.py#L24
"""
async def convert(self, ctx: commands.Context, argument: str) -> List[str]:
result = []
if argument in FLAGS:
result = FLAGS[argument]["code"].upper()
else:
for lang in FLAGS:
if FLAGS[lang]["name"].lower() in argument.lower():
result = FLAGS[lang]["code"]
break
if FLAGS[lang]["country"].lower() in argument.lower():
result = FLAGS[lang]["code"]
break
if not FLAGS[lang]["code"]:
continue
if FLAGS[lang]["code"] in argument.lower() and len(argument) == 2:
result = FLAGS[lang]["code"]
break
if not result:
raise BadArgument('Language "{}" not found'.format(argument))
return result
class GoogleTranslateAPI:
config: Config
bot: Red
cache: dict
_key: Optional[str]
_guild_counter: Dict[int, Dict[str, int]]
_global_counter: Dict[str, int]
def __init__(self, *_args):
self.config: Config
self.bot: Red
self.cache: dict
self._key: Optional[str]
self._guild_counter: Dict[int, Dict[str, int]]
self._global_counter: Dict[str, int]
async def cleanup_cache(self) -> None:
if version_info >= VersionInfo.from_str("3.2.0"):
await self.bot.wait_until_red_ready()
else:
await self.bot.wait_until_ready()
while self is self.bot.get_cog("Translate"):
# cleanup the cache every 10 minutes
self.cache["translations"] = []
await asyncio.sleep(600)
async def save_usage(self) -> None:
if version_info >= VersionInfo.from_str("3.2.0"):
await self.bot.wait_until_red_ready()
else:
await self.bot.wait_until_ready()
while self is self.bot.get_cog("Translate"):
# Save usage stats every couple minutes
await self._save_usage_stats()
await asyncio.sleep(120)
async def _save_usage_stats(self):
async with self.config.count() as count:
for key, value in self._global_counter.items():
count[key] = value
for guild_id, data in self._guild_counter.items():
async with self.config.guild_from_id(guild_id).count() as count:
for key, value in data.items():
count[key] = value
async def add_detect(self, guild: Optional[discord.Guild]):
if guild:
log.debug(f"adding detect to {guild.name}")
if guild.id not in self._guild_counter:
self._guild_counter[guild.id] = await self.config.guild(guild).count()
self._guild_counter[guild.id]["detect"] += 1
if not self._global_counter:
self._global_counter = await self.config.count()
self._global_counter["detect"] += 1
async def add_requests(self, guild: Optional[discord.Guild], message: str):
if guild:
log.debug(f"Adding requests to {guild.name}")
if guild.id not in self._guild_counter:
self._guild_counter[guild.id] = await self.config.guild(guild).count()
self._guild_counter[guild.id]["requests"] += 1
self._guild_counter[guild.id]["characters"] += len(message)
if not self._global_counter:
self._global_counter = await self.config.count()
self._global_counter["requests"] += 1
self._global_counter["characters"] += len(message)
async def _get_google_api_key(self) -> Optional[str]:
key = {}
if not self._key:
try:
key = await self.bot.get_shared_api_tokens("google_translate")
except AttributeError:
# Red 3.1 support
key = await self.bot.db.api_tokens.get_raw("google_translate", default={})
self._key = key.get("api_key")
return self._key
async def _bw_list_cache_update(self, guild: discord.Guild) -> None:
self.cache["guild_blacklist"][guild.id] = await self.config.guild(guild).blacklist()
self.cache["guild_whitelist"][guild.id] = await self.config.guild(guild).whitelist()
async def check_bw_list(
self,
guild: discord.Guild,
channel: discord.TextChannel,
member: Union[discord.Member, discord.User],
) -> bool:
can_run = True
if guild.id not in self.cache["guild_blacklist"]:
self.cache["guild_blacklist"][guild.id] = await self.config.guild(guild).blacklist()
if guild.id not in self.cache["guild_whitelist"]:
self.cache["guild_whitelist"][guild.id] = await self.config.guild(guild).whitelist()
whitelist = self.cache["guild_whitelist"][guild.id]
blacklist = self.cache["guild_blacklist"][guild.id]
if whitelist:
can_run = False
if channel.id in whitelist:
can_run = True
if channel.category_id and channel.category_id in whitelist:
can_run = True
if member.id in whitelist:
can_run = True
for role in getattr(member, "roles", []):
if role.is_default():
continue
if role.id in whitelist:
can_run = True
return can_run
else:
if channel.id in blacklist:
can_run = False
if channel.category_id and channel.category_id in blacklist:
can_run = False
if member.id in blacklist:
can_run = False
for role in getattr(member, "roles", []):
if role.is_default():
continue
if role.id in blacklist:
can_run = False
return can_run
async def detect_language(self, text: str) -> List[List[Dict[str, str]]]:
"""
Detect the language from given text
"""
params = {"q": text, "key": self._key}
url = BASE_URL + "/language/translate/v2/detect"
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as resp:
data = await resp.json()
if "error" in data:
log.error(data["error"]["message"])
raise GoogleTranslateAPIError(data["error"]["message"])
return data["data"]["detections"]
async def translation_embed(
self,
author: Union[discord.Member, discord.User],
translation: Tuple[str, str, str],
requestor: Optional[Union[discord.Member, discord.User]] = None,
) -> discord.Embed:
em = discord.Embed(colour=author.colour, description=translation[0])
em.set_author(name=author.display_name + _(" said:"), icon_url=str(author.avatar_url))
detail_string = _("{_from} to {_to} | Requested by ").format(
_from=translation[1].upper(), _to=translation[2].upper()
)
if requestor:
detail_string += str(requestor)
else:
detail_string += str(author)
em.set_footer(text=detail_string)
return em
async def translate_text(self, from_lang: str, target: str, text: str) -> Optional[str]:
"""
request to translate the text
"""
formatting = "text"
params = {
"q": text,
"target": target,
"key": self._key,
"format": formatting,
"source": from_lang,
}
url = BASE_URL + "/language/translate/v2"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as resp:
data = await resp.json()
except Exception:
return None
if "error" in data:
log.error(data["error"]["message"])
raise GoogleTranslateAPIError(data["error"]["message"])
if "data" in data:
translated_text: str = data["data"]["translations"][0]["translatedText"]
return translated_text
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""
Translates the message based off reactions
with country flags
"""
if version_info >= VersionInfo.from_str("3.2.0"):
await self.bot.wait_until_red_ready()
else:
await self.bot.wait_until_ready()
if not message.guild:
return
if message.author.bot:
return
if not await self._get_google_api_key():
return
author = cast(discord.Member, message.author)
channel = cast(discord.TextChannel, message.channel)
links = await self.config.guild(channel.guild).autosend()
link_channels = [int(r) for r in links.keys()]
guild = message.guild
if version_info >= VersionInfo.from_str("3.4.0"):
if await self.bot.cog_disabled_in_guild(self, guild):
return
if not await self.check_bw_list(guild, channel, author):
return
if not await self.config.guild(guild).text() and not channel.id in link_channels:
return
if guild.id not in self.cache["guild_messages"]:
if not await self.config.guild(guild).text() and not channel.id in link_channels:
return
else:
self.cache["guild_messages"].append(guild.id)
if not await self.local_perms(guild, author):
return
if not await self.global_perms(author):
return
if not await self.check_ignored_channel(message):
return
flag = FLAG_REGEX.search(message.clean_content)
if flag:
await self.translate_message(message, flag.group())
elif channel.id in link_channels:
await self.translate_automessage(message, links)
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
"""
Translates the message based off reactions
with country flags
"""
if version_info >= VersionInfo.from_str("3.2.0"):
await self.bot.wait_until_red_ready()
else:
await self.bot.wait_until_ready()
if payload.message_id in self.cache["translations"]:
return
if str(payload.emoji) not in FLAGS:
return
if not await self._get_google_api_key():
return
channel = self.bot.get_channel(id=payload.channel_id)
if not channel:
return
try:
guild = channel.guild
except AttributeError:
return
if guild is None:
return
if version_info >= VersionInfo.from_str("3.4.0"):
if await self.bot.cog_disabled_in_guild(self, guild):
return
reacted_user = guild.get_member(payload.user_id)
if reacted_user.bot:
return
if not await self.check_bw_list(guild, channel, reacted_user):
return
if guild.id not in self.cache["guild_reactions"]:
if not await self.config.guild(guild).reaction():
return
else:
self.cache["guild_reactions"].append(guild.id)
if not await self.local_perms(guild, reacted_user):
return
if not await self.global_perms(reacted_user):
return
try:
message = await channel.fetch_message(id=payload.message_id)
except (discord.errors.NotFound, discord.Forbidden):
return
if not await self.check_ignored_channel(message, reacted_user):
return
await self.translate_message(message, str(payload.emoji), reacted_user)
async def translate_automessage(self, message: discord.Message, links: dict) -> None:
guild = cast(discord.Guild, message.guild)
channel = cast(discord.TextChannel, message.channel)
# remove sent channel from Links
try:
del links[str(channel.id)]
except:
pass
if message.embeds != []:
if message.embeds[0].description:
to_translate = cast(str, message.embeds[0].description)
else:
to_translate = None
else:
to_translate = message.clean_content
if not to_translate:
for l_id, l_lang in links.items():
ch = guild.get_channel(int(l_id))
if not ch:
continue
if message.attachments:
files = [await a.to_file() for a in message.attachments]
else:
files = None
if message.embeds:
links = "\n".join([e.url for e in message.embeds])
else:
links = ""
await ch.send(f"**{message.author.display_name} sent:**\n{links}", files=files)
return
try:
detected_lang = await self.detect_language(to_translate)
await self.add_detect(guild)
except GoogleTranslateAPIError:
return
except Exception:
log.exception("Error detecting language")
return
original_lang = detected_lang[0][0]["language"]
for l_id, target in links.items():
ch = guild.get_channel(int(l_id))
if not ch:
continue
try:
if target == original_lang:
translated_text = to_translate
else:
translated_text = await self.translate_text(original_lang, target, to_translate)
await self.add_requests(guild, to_translate)
except Exception:
log.exception(f"Error translating message {guild=} {channel=}")
return
if not translated_text:
log.exception(f"Message not translated to {l_lang} {guild=} {channel=}")
return
author = message.author
from_lang = original_lang.upper()
to_lang = target.upper()
translation = (translated_text, from_lang, to_lang)
if message.attachments:
files = [await a.to_file() for a in message.attachments]
else:
files = None
if ch.permissions_for(guild.me).embed_links:
em = await self.translation_embed(author, translation)
translated_msg = await ch.send(embed=em, files=files)
else:
msg = _("{author} said:\n{translated_text}").format(author=author, translated_text=translated_text)
translated_msg = await ch.send(msg, files=files)
async def translate_message(
self,
message: discord.Message,
flag: str,
reacted_user: Optional[discord.Member] = None,
) -> None:
guild = cast(discord.Guild, message.guild)
channel = cast(discord.TextChannel, message.channel)
if message.id in self.cache["cooldown_translations"]:
if str(flag) in self.cache["cooldown_translations"][message.id]["past_flags"]:
return
if not self.cache["cooldown_translations"][message.id]["multiple"]:
return
if time.time() < self.cache["cooldown_translations"][message.id]["wait"]:
delete_after = self.cache["cooldown_translations"][message.id]["wait"] - time.time()
await channel.send(_("You're translating too many messages!"), delete_after=delete_after)
return
to_translate = None
if message.embeds != []:
if message.embeds[0].description:
to_translate = cast(str, message.embeds[0].description)
else:
to_translate = message.clean_content
if not to_translate:
return
num_emojis = 0
for reaction in message.reactions:
if reaction.emoji == str(flag):
num_emojis = reaction.count
if num_emojis > 1:
return
target = FLAGS[str(flag)]["code"]
try:
detected_lang = await self.detect_language(to_translate)
await self.add_detect(guild)
except GoogleTranslateAPIError:
return
except Exception:
log.exception("Error detecting language")
return
original_lang = detected_lang[0][0]["language"]
if target == original_lang:
return
try:
translated_text = await self.translate_text(original_lang, target, to_translate)
await self.add_requests(guild, to_translate)
except Exception:
log.exception(f"Error translating message {guild=} {channel=}")
return
if not translated_text:
return
author = message.author
from_lang = detected_lang[0][0]["language"].upper()
to_lang = target.upper()
if from_lang == to_lang:
# don't post anything if the detected language is the same
return
translation = (translated_text, from_lang, to_lang)
if message.id not in self.cache["cooldown_translations"]:
if not self.cache["cooldown"]:
self.cache["cooldown"] = await self.config.cooldown()
cooldown = deepcopy(self.cache["cooldown"])
else:
cooldown = self.cache["cooldown_translations"][message.id]
cooldown["wait"] = time.time() + cooldown["timeout"]
cooldown["past_flags"].append(str(flag))
self.cache["cooldown_translations"][message.id] = cooldown
if channel.permissions_for(guild.me).embed_links:
em = await self.translation_embed(author, translation, reacted_user)
if version_info >= VersionInfo.from_str("3.4.6"):
translated_msg = await channel.send(embed=em, reference=message, mention_author=False)
else:
translated_msg = await channel.send(embed=em)
else:
msg = _("{author} said:\n{translated_text}").format(author=author, translate_text=translated_text)
translated_msg = await channel.send(msg)
if not cooldown["multiple"]:
self.cache["translations"].append(translated_msg.id)
async def local_perms(self, guild: discord.Guild, author: discord.Member) -> bool:
"""Check the user is/isn't locally whitelisted/blacklisted.
https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/release/3.0.0/redbot/core/global_checks.py
"""
try:
return await self.bot.allowed_by_whitelist_blacklist(
author, who_id=author.id, guild_id=guild.id, role_ids=[r.id for r in author.roles]
)
except AttributeError:
if await self.bot.is_owner(author):
return True
elif guild is None:
return True
guild_settings = self.bot.db.guild(guild)
local_blacklist = await guild_settings.blacklist()
local_whitelist = await guild_settings.whitelist()
_ids = [r.id for r in author.roles if not r.is_default()]
_ids.append(author.id)
if local_whitelist:
return any(i in local_whitelist for i in _ids)
return not any(i in local_blacklist for i in _ids)
async def global_perms(self, author: discord.Member) -> bool:
"""Check the user is/isn't globally whitelisted/blacklisted.
https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/release/3.0.0/redbot/core/global_checks.py
"""
try:
return await self.bot.allowed_by_whitelist_blacklist(author)
except AttributeError:
if await self.bot.is_owner(author):
return True
whitelist = await self.bot.db.whitelist()
if whitelist:
return author.id in whitelist
return author.id not in await self.bot.db.blacklist()
async def check_ignored_channel(
self, message: discord.Message, reacting_user: Optional[discord.Member] = None
) -> bool:
"""
https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/release/3.0.0/redbot/cogs/mod/mod.py#L1273
"""
if version_info >= VersionInfo.from_str("3.3.6"):
ctx = await self.bot.get_context(message)
if reacting_user:
ctx.author = reacting_user
return await self.bot.ignored_channel_or_guild(ctx)
# everything below this can be removed at a later date when support
# for previous versions are no longer required.
channel = cast(discord.TextChannel, message.channel)
guild = channel.guild
author = cast(discord.Member, message.author)
if reacting_user:
author = reacting_user
mod = self.bot.get_cog("Mod")
if mod is None:
return True
perms = channel.permissions_for(author)
surpass_ignore = (
isinstance(channel, discord.abc.PrivateChannel)
or perms.manage_guild
or await self.bot.is_owner(author)
or await self.bot.is_admin(author)
)
if surpass_ignore:
return True
guild_ignored = await mod.settings.guild(guild).ignored()
chann_ignored = await mod.settings.channel(channel).ignored()
return not (guild_ignored or chann_ignored and not perms.manage_channels)
@commands.Cog.listener()
async def on_red_api_tokens_update(self, service_name: str, api_tokens: Mapping[str, str]) -> None:
if service_name != "google_translate":
return
self._key = None

58
translate/converters.py Normal file
View file

@ -0,0 +1,58 @@
import re
from typing import Union
import discord
from discord.ext.commands.converter import IDConverter
from discord.ext.commands.errors import BadArgument
from redbot.core import commands
from redbot.core.i18n import Translator
_ = Translator("Translate", __file__)
class ChannelUserRole(IDConverter):
"""
This will check to see if the provided argument is a channel, user, or role
Guidance code on how to do this from:
https://github.com/Rapptz/discord.py/blob/rewrite/discord/ext/commands/converter.py#L85
https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/mod/mod.py#L24
"""
async def convert(
self, ctx: commands.Context, argument: str
) -> Union[discord.TextChannel, discord.Role, discord.Member]:
guild = ctx.guild
result = None
id_match = self._get_id_match(argument)
channel_match = re.match(r"<#([0-9]+)>$", argument)
member_match = re.match(r"<@!?([0-9]+)>$", argument)
role_match = re.match(r"<@&([0-9]+)>$", argument)
for converter in ["channel", "role", "member"]:
if converter == "channel":
match = id_match or channel_match
if match:
channel_id = match.group(1)
result = guild.get_channel(int(channel_id))
else:
result = discord.utils.get(guild.text_channels, name=argument)
if converter == "member":
match = id_match or member_match
if match:
member_id = match.group(1)
result = guild.get_member(int(member_id))
else:
result = guild.get_member_named(argument)
if converter == "role":
match = id_match or role_match
if match:
role_id = match.group(1)
result = guild.get_role(int(role_id))
else:
result = discord.utils.get(guild._roles.values(), name=argument)
if result:
break
if not result:
msg = _("{arg} is not a valid channel, user or role.").format(arg=argument)
raise BadArgument(msg)
return result

4
translate/errors.py Normal file
View file

@ -0,0 +1,4 @@
class GoogleTranslateAPIError(Exception):
"""Raised when google translate has an oopsie"""
pass

277
translate/flags.py Normal file
View file

@ -0,0 +1,277 @@
FLAGS = {
"🇦🇩": {"code": "ca", "country": "Andorra", "name": "Catalan"},
"🇦🇪": {"code": "ar", "country": "United Arab Emirates", "name": "Arabic"},
"🇦🇫": {"code": "ps", "country": "Afghanistan", "name": "Pashto"},
"🇦🇬": {"code": "en", "country": "Antigua and Barbuda", "name": "English"},
"🇦🇮": {"code": "en", "country": "Anguilla", "name": "English"},
"🇦🇱": {"code": "sq", "country": "Albania", "name": "Albanian"},
"🇦🇲": {"code": "hy", "country": "Armenia", "name": "Armenian"},
"🇦🇴": {"code": "pt", "country": "Angola", "name": "Portuguese"},
"🇦🇶": {"code": "en", "country": "Antarctica", "name": "English"},
"🇦🇷": {"code": "es", "country": "Argentina", "name": "Spanish"},
"🇦🇸": {"code": "en", "country": "American Samoa", "name": "English"},
"🇦🇹": {"code": "de", "country": "Austria", "name": "German"},
"🇦🇺": {"code": "en", "country": "Australia", "name": "English"},
"🇦🇼": {"code": "nl", "country": "Aruba", "name": "Dutch"},
"🇦🇽": {"code": "sv", "country": "Åland Islands", "name": "Swedish"},
"🇦🇿": {"code": "az", "country": "Azerbaijan", "name": "Azerbaijani"},
"🇧🇦": {"code": "bs", "country": "Bosnia and Herzegovina", "name": "Bosnian"},
"🇧🇧": {"code": "en", "country": "Barbados", "name": "English"},
"🇧🇩": {"code": "bn", "country": "Bangladesh", "name": "Bengali"},
"🇧🇪": {"code": "nl", "country": "Belgium", "name": "Dutch"},
"🇧🇫": {"code": "fr", "country": "Burkina Faso", "name": "French"},
"🇧🇬": {"code": "bg", "country": "Bulgaria", "name": "Bulgarian"},
"🇧🇭": {"code": "ar", "country": "Bahrain", "name": "Arabic"},
"🇧🇮": {"code": "fr", "country": "Burundi", "name": "French"},
"🇧🇯": {"code": "fr", "country": "Benin", "name": "French"},
"🇧🇱": {"code": "fr", "country": "Saint Barthélemy", "name": "French"},
"🇧🇲": {"code": "en", "country": "Bermuda", "name": "English"},
"🇧🇳": {"code": "ms", "country": "Brunei Darussalam", "name": "Malay"},
"🇧🇴": {"code": "es", "country": "Bolivia (Plurinational State of)", "name": "Spanish"},
"🇧🇶": {"code": "nl", "country": "Bonaire, Sint Eustatius and Saba", "name": "Dutch"},
"🇧🇷": {"code": "pt", "country": "Brazil", "name": "Portuguese"},
"🇧🇸": {"code": "en", "country": "Bahamas", "name": "English"},
"🇧🇹": {"code": "dz", "country": "Bhutan", "name": "Dzongkha"},
"🇧🇻": {"code": "no", "country": "Bouvet Island", "name": "Norwegian"},
"🇧🇼": {"code": "en", "country": "Botswana", "name": "English"},
"🇧🇾": {"code": "be", "country": "Belarus", "name": "Belarusian"},
"🇧🇿": {"code": "en", "country": "Belize", "name": "English"},
"🇨🇦": {"code": "en", "country": "Canada", "name": "English"},
"🇨🇨": {"code": "en", "country": "Cocos (Keeling) Islands", "name": "English"},
"🇨🇩": {"code": "fr", "country": "Congo (Democratic Republic of the)", "name": "French"},
"🇨🇫": {"code": "fr", "country": "Central African Republic", "name": "French"},
"🇨🇬": {"code": "fr", "country": "Congo", "name": "French"},
"🇨🇭": {"code": "de", "country": "Switzerland", "name": "German"},
"🇨🇮": {"code": "fr", "country": "Côte d'Ivoire", "name": "French"},
"🇨🇰": {"code": "en", "country": "Cook Islands", "name": "English"},
"🇨🇱": {"code": "es", "country": "Chile", "name": "Spanish"},
"🇨🇲": {"code": "en", "country": "Cameroon", "name": "English"},
"🇨🇳": {"code": "zh", "country": "China", "name": "Chinese"},
"🇨🇴": {"code": "es", "country": "Colombia", "name": "Spanish"},
"🇨🇷": {"code": "es", "country": "Costa Rica", "name": "Spanish"},
"🇨🇺": {"code": "es", "country": "Cuba", "name": "Spanish"},
"🇨🇻": {"code": "pt", "country": "Cabo Verde", "name": "Portuguese"},
"🇨🇼": {"code": "nl", "country": "Curaçao", "name": "Dutch"},
"🇨🇽": {"code": "en", "country": "Christmas Island", "name": "English"},
"🇨🇾": {"code": "el", "country": "Cyprus", "name": "Greek (modern)"},
"🇨🇿": {"code": "cs", "country": "Czech Republic", "name": "Czech"},
"🇩🇪": {"code": "de", "country": "Germany", "name": "German"},
"🇩🇯": {"code": "fr", "country": "Djibouti", "name": "French"},
"🇩🇰": {"code": "da", "country": "Denmark", "name": "Danish"},
"🇩🇲": {"code": "en", "country": "Dominica", "name": "English"},
"🇩🇴": {"code": "es", "country": "Dominican Republic", "name": "Spanish"},
"🇩🇿": {"code": "ar", "country": "Algeria", "name": "Arabic"},
"🇪🇨": {"code": "es", "country": "Ecuador", "name": "Spanish"},
"🇪🇪": {"code": "et", "country": "Estonia", "name": "Estonian"},
"🇪🇬": {"code": "ar", "country": "Egypt", "name": "Arabic"},
"🇪🇭": {"code": "es", "country": "Western Sahara", "name": "Spanish"},
"🇪🇷": {"code": "ti", "country": "Eritrea", "name": "Tigrinya"},
"🇪🇸": {"code": "es", "country": "Spain", "name": "Spanish"},
"🇪🇹": {"code": "am", "country": "Ethiopia", "name": "Amharic"},
"🇪🇺": {
"code": "EU",
"emoji": "🇪🇺",
"message": "Not Found",
"name": "European Union",
"status": 404,
"title": "flag for European Union",
"unicode": "U+1F1EA U+1F1FA",
"country": "N/A",
},
"🇫🇮": {"code": "fi", "country": "Finland", "name": "Finnish"},
"🇫🇯": {"code": "en", "country": "Fiji", "name": "English"},
"🇫🇰": {"code": "en", "country": "Falkland Islands (Malvinas)", "name": "English"},
"🇫🇲": {"code": "en", "country": "Micronesia (Federated States of)", "name": "English"},
"🇫🇴": {"code": "fo", "country": "Faroe Islands", "name": "Faroese"},
"🇫🇷": {"code": "fr", "country": "France", "name": "French"},
"🇬🇦": {"code": "fr", "country": "Gabon", "name": "French"},
"🇬🇧": {
"code": "en",
"country": "United Kingdom of Great Britain and Northern Ireland",
"name": "English",
},
"🇬🇩": {"code": "en", "country": "Grenada", "name": "English"},
"🇬🇪": {"code": "ka", "country": "Georgia", "name": "Georgian"},
"🇬🇫": {"code": "fr", "country": "French Guiana", "name": "French"},
"🇬🇬": {"code": "en", "country": "Guernsey", "name": "English"},
"🇬🇭": {"code": "en", "country": "Ghana", "name": "English"},
"🇬🇮": {"code": "en", "country": "Gibraltar", "name": "English"},
"🇬🇱": {"code": "kl", "country": "Greenland", "name": "Kalaallisut"},
"🇬🇲": {"code": "en", "country": "Gambia", "name": "English"},
"🇬🇳": {"code": "fr", "country": "Guinea", "name": "French"},
"🇬🇵": {"code": "fr", "country": "Guadeloupe", "name": "French"},
"🇬🇶": {"code": "es", "country": "Equatorial Guinea", "name": "Spanish"},
"🇬🇷": {"code": "el", "country": "Greece", "name": "Greek (modern)"},
"🇬🇸": {
"code": "en",
"country": "South Georgia and the South Sandwich Islands",
"name": "English",
},
"🇬🇹": {"code": "es", "country": "Guatemala", "name": "Spanish"},
"🇬🇺": {"code": "en", "country": "Guam", "name": "English"},
"🇬🇼": {"code": "pt", "country": "Guinea-Bissau", "name": "Portuguese"},
"🇬🇾": {"code": "en", "country": "Guyana", "name": "English"},
"🇭🇰": {"code": "en", "country": "Hong Kong", "name": "English"},
"🇭🇲": {"code": "en", "country": "Heard Island and McDonald Islands", "name": "English"},
"🇭🇳": {"code": "es", "country": "Honduras", "name": "Spanish"},
"🇭🇷": {"code": "hr", "country": "Croatia", "name": "Croatian"},
"🇭🇹": {"code": "fr", "country": "Haiti", "name": "French"},
"🇭🇺": {"code": "hu", "country": "Hungary", "name": "Hungarian"},
"🇮🇩": {"code": "id", "country": "Indonesia", "name": "Indonesian"},
"🇮🇪": {"code": "ga", "country": "Ireland", "name": "Irish"},
"🇮🇱": {"code": "he", "country": "Israel", "name": "Hebrew (modern)"},
"🇮🇲": {"code": "en", "country": "Isle of Man", "name": "English"},
"🇮🇳": {"code": "hi", "country": "India", "name": "Hindi"},
"🇮🇴": {"code": "en", "country": "British Indian Ocean Territory", "name": "English"},
"🇮🇶": {"code": "ar", "country": "Iraq", "name": "Arabic"},
"🇮🇷": {"code": "fa", "country": "Iran (Islamic Republic of)", "name": "Persian (Farsi)"},
"🇮🇸": {"code": "is", "country": "Iceland", "name": "Icelandic"},
"🇮🇹": {"code": "it", "country": "Italy", "name": "Italian"},
"🇯🇪": {"code": "en", "country": "Jersey", "name": "English"},
"🇯🇲": {"code": "en", "country": "Jamaica", "name": "English"},
"🇯🇴": {"code": "ar", "country": "Jordan", "name": "Arabic"},
"🇯🇵": {"code": "ja", "country": "Japan", "name": "Japanese"},
"🇰🇪": {"code": "en", "country": "Kenya", "name": "English"},
"🇰🇬": {"code": "ky", "country": "Kyrgyzstan", "name": "Kyrgyz"},
"🇰🇭": {"code": "km", "country": "Cambodia", "name": "Khmer"},
"🇰🇮": {"code": "en", "country": "Kiribati", "name": "English"},
"🇰🇲": {"code": "ar", "country": "Comoros", "name": "Arabic"},
"🇰🇳": {"code": "en", "country": "Saint Kitts and Nevis", "name": "English"},
"🇰🇵": {"code": "ko", "country": "Korea (Democratic People's Republic of)", "name": "Korean"},
"🇰🇷": {"code": "ko", "country": "Korea (Republic of)", "name": "Korean"},
"🇰🇼": {"code": "ar", "country": "Kuwait", "name": "Arabic"},
"🇰🇾": {"code": "en", "country": "Cayman Islands", "name": "English"},
"🇰🇿": {"code": "kk", "country": "Kazakhstan", "name": "Kazakh"},
"🇱🇦": {"code": "lo", "country": "Lao People's Democratic Republic", "name": "Lao"},
"🇱🇧": {"code": "ar", "country": "Lebanon", "name": "Arabic"},
"🇱🇨": {"code": "en", "country": "Saint Lucia", "name": "English"},
"🇱🇮": {"code": "de", "country": "Liechtenstein", "name": "German"},
"🇱🇰": {"code": "si", "country": "Sri Lanka", "name": "Sinhalese"},
"🇱🇷": {"code": "en", "country": "Liberia", "name": "English"},
"🇱🇸": {"code": "en", "country": "Lesotho", "name": "English"},
"🇱🇹": {"code": "lt", "country": "Lithuania", "name": "Lithuanian"},
"🇱🇺": {"code": "fr", "country": "Luxembourg", "name": "French"},
"🇱🇻": {"code": "lv", "country": "Latvia", "name": "Latvian"},
"🇱🇾": {"code": "ar", "country": "Libya", "name": "Arabic"},
"🇲🇦": {"code": "ar", "country": "Morocco", "name": "Arabic"},
"🇲🇨": {"code": "fr", "country": "Monaco", "name": "French"},
"🇲🇩": {"code": "ro", "country": "Moldova (Republic of)", "name": "Romanian"},
"🇲🇪": {"code": "sr", "country": "Montenegro", "name": "Serbian"},
"🇲🇫": {"code": "en", "country": "Saint Martin (French part)", "name": "English"},
"🇲🇬": {"code": "fr", "country": "Madagascar", "name": "French"},
"🇲🇭": {"code": "en", "country": "Marshall Islands", "name": "English"},
"🇲🇰": {
"code": "mk",
"country": "Macedonia (the former Yugoslav Republic of)",
"name": "Macedonian",
},
"🇲🇱": {"code": "fr", "country": "Mali", "name": "French"},
"🇲🇲": {"code": "my", "country": "Myanmar", "name": "Burmese"},
"🇲🇳": {"code": "mn", "country": "Mongolia", "name": "Mongolian"},
"🇲🇴": {"code": "zh", "country": "Macao", "name": "Chinese"},
"🇲🇵": {"code": "en", "country": "Northern Mariana Islands", "name": "English"},
"🇲🇶": {"code": "fr", "country": "Martinique", "name": "French"},
"🇲🇷": {"code": "ar", "country": "Mauritania", "name": "Arabic"},
"🇲🇸": {"code": "en", "country": "Montserrat", "name": "English"},
"🇲🇹": {"code": "mt", "country": "Malta", "name": "Maltese"},
"🇲🇺": {"code": "en", "country": "Mauritius", "name": "English"},
"🇲🇻": {"code": "dv", "country": "Maldives", "name": "Divehi"},
"🇲🇼": {"code": "en", "country": "Malawi", "name": "English"},
"🇲🇽": {"code": "es", "country": "Mexico", "name": "Spanish"},
"🇲🇾": {"code": None, "country": "Malaysia", "name": "Malaysian"},
"🇲🇿": {"code": "pt", "country": "Mozambique", "name": "Portuguese"},
"🇳🇦": {"code": "en", "country": "Namibia", "name": "English"},
"🇳🇨": {"code": "fr", "country": "New Caledonia", "name": "French"},
"🇳🇪": {"code": "fr", "country": "Niger", "name": "French"},
"🇳🇫": {"code": "en", "country": "Norfolk Island", "name": "English"},
"🇳🇬": {"code": "en", "country": "Nigeria", "name": "English"},
"🇳🇮": {"code": "es", "country": "Nicaragua", "name": "Spanish"},
"🇳🇱": {"code": "nl", "country": "Netherlands", "name": "Dutch"},
"🇳🇴": {"code": "no", "country": "Norway", "name": "Norwegian"},
"🇳🇵": {"code": "ne", "country": "Nepal", "name": "Nepali"},
"🇳🇷": {"code": "en", "country": "Nauru", "name": "English"},
"🇳🇺": {"code": "en", "country": "Niue", "name": "English"},
"🇳🇿": {"code": "en", "country": "New Zealand", "name": "English"},
"🇴🇲": {"code": "ar", "country": "Oman", "name": "Arabic"},
"🇵🇦": {"code": "es", "country": "Panama", "name": "Spanish"},
"🇵🇪": {"code": "es", "country": "Peru", "name": "Spanish"},
"🇵🇫": {"code": "fr", "country": "French Polynesia", "name": "French"},
"🇵🇬": {"code": "en", "country": "Papua New Guinea", "name": "English"},
"🇵🇭": {"code": "tl", "country": "Philippines", "name": "Tagalog (Filipino)"},
"🇵🇰": {"code": "en", "country": "Pakistan", "name": "English"},
"🇵🇱": {"code": "pl", "country": "Poland", "name": "Polish"},
"🇵🇲": {"code": "fr", "country": "Saint Pierre and Miquelon", "name": "French"},
"🇵🇳": {"code": "en", "country": "Pitcairn", "name": "English"},
"🇵🇷": {"code": "es", "country": "Puerto Rico", "name": "Spanish"},
"🇵🇸": {"code": "ar", "country": "Palestine, State of", "name": "Arabic"},
"🇵🇹": {"code": "pt", "country": "Portugal", "name": "Portuguese"},
"🇵🇼": {"code": "en", "country": "Palau", "name": "English"},
"🇵🇾": {"code": "es", "country": "Paraguay", "name": "Spanish"},
"🇶🇦": {"code": "ar", "country": "Qatar", "name": "Arabic"},
"🇷🇪": {"code": "fr", "country": "Réunion", "name": "French"},
"🇷🇴": {"code": "ro", "country": "Romania", "name": "Romanian"},
"🇷🇸": {"code": "sr", "country": "Serbia", "name": "Serbian"},
"🇷🇺": {"code": "ru", "country": "Russian Federation", "name": "Russian"},
"🇷🇼": {"code": "rw", "country": "Rwanda", "name": "Kinyarwanda"},
"🇸🇦": {"code": "ar", "country": "Saudi Arabia", "name": "Arabic"},
"🇸🇧": {"code": "en", "country": "Solomon Islands", "name": "English"},
"🇸🇨": {"code": "fr", "country": "Seychelles", "name": "French"},
"🇸🇩": {"code": "ar", "country": "Sudan", "name": "Arabic"},
"🇸🇪": {"code": "sv", "country": "Sweden", "name": "Swedish"},
"🇸🇬": {"code": "en", "country": "Singapore", "name": "English"},
"🇸🇭": {
"code": "en",
"country": "Saint Helena, Ascension and Tristan da Cunha",
"name": "English",
},
"🇸🇮": {"code": "sl", "country": "Slovenia", "name": "Slovene"},
"🇸🇯": {"code": "no", "country": "Svalbard and Jan Mayen", "name": "Norwegian"},
"🇸🇰": {"code": "sk", "country": "Slovakia", "name": "Slovak"},
"🇸🇱": {"code": "en", "country": "Sierra Leone", "name": "English"},
"🇸🇲": {"code": "it", "country": "San Marino", "name": "Italian"},
"🇸🇳": {"code": "fr", "country": "Senegal", "name": "French"},
"🇸🇴": {"code": "so", "country": "Somalia", "name": "Somali"},
"🇸🇷": {"code": "nl", "country": "Suriname", "name": "Dutch"},
"🇸🇸": {"code": "en", "country": "South Sudan", "name": "English"},
"🇸🇹": {"code": "pt", "country": "Sao Tome and Principe", "name": "Portuguese"},
"🇸🇻": {"code": "es", "country": "El Salvador", "name": "Spanish"},
"🇸🇽": {"code": "nl", "country": "Sint Maarten (Dutch part)", "name": "Dutch"},
"🇸🇾": {"code": "ar", "country": "Syrian Arab Republic", "name": "Arabic"},
"🇸🇿": {"code": "en", "country": "Swaziland", "name": "English"},
"🇹🇨": {"code": "en", "country": "Turks and Caicos Islands", "name": "English"},
"🇹🇩": {"code": "fr", "country": "Chad", "name": "French"},
"🇹🇫": {"code": "fr", "country": "French Southern Territories", "name": "French"},
"🇹🇬": {"code": "fr", "country": "Togo", "name": "French"},
"🇹🇭": {"code": "th", "country": "Thailand", "name": "Thai"},
"🇹🇯": {"code": "tg", "country": "Tajikistan", "name": "Tajik"},
"🇹🇰": {"code": "en", "country": "Tokelau", "name": "English"},
"🇹🇱": {"code": "pt", "country": "Timor-Leste", "name": "Portuguese"},
"🇹🇲": {"code": "tk", "country": "Turkmenistan", "name": "Turkmen"},
"🇹🇳": {"code": "ar", "country": "Tunisia", "name": "Arabic"},
"🇹🇴": {"code": "en", "country": "Tonga", "name": "English"},
"🇹🇷": {"code": "tr", "country": "Turkey", "name": "Turkish"},
"🇹🇹": {"code": "en", "country": "Trinidad and Tobago", "name": "English"},
"🇹🇻": {"code": "en", "country": "Tuvalu", "name": "English"},
"🇹🇼": {"code": "zh", "country": "Taiwan", "name": "Chinese"},
"🇹🇿": {"code": "sw", "country": "Tanzania, United Republic of", "name": "Swahili"},
"🇺🇦": {"code": "uk", "country": "Ukraine", "name": "Ukrainian"},
"🇺🇬": {"code": "en", "country": "Uganda", "name": "English"},
"🇺🇲": {"code": "en", "country": "United States Minor Outlying Islands", "name": "English"},
"🇺🇸": {"code": "en", "country": "United States of America", "name": "English"},
"🇺🇾": {"code": "es", "country": "Uruguay", "name": "Spanish"},
"🇺🇿": {"code": "uz", "country": "Uzbekistan", "name": "Uzbek"},
"🇻🇦": {"code": "la", "country": "Holy See", "name": "Latin"},
"🇻🇨": {"code": "en", "country": "Saint Vincent and the Grenadines", "name": "English"},
"🇻🇪": {"code": "es", "country": "Venezuela (Bolivarian Republic of)", "name": "Spanish"},
"🇻🇬": {"code": "en", "country": "Virgin Islands (British)", "name": "English"},
"🇻🇮": {"code": "en", "country": "Virgin Islands (U.S.)", "name": "English"},
"🇻🇳": {"code": "vi", "country": "Viet Nam", "name": "Vietnamese"},
"🇻🇺": {"code": "bi", "country": "Vanuatu", "name": "Bislama"},
"🇼🇫": {"code": "fr", "country": "Wallis and Futuna", "name": "French"},
"🇼🇸": {"code": "sm", "country": "Samoa", "name": "Samoan"},
"🇾🇪": {"code": "ar", "country": "Yemen", "name": "Arabic"},
"🇾🇹": {"code": "fr", "country": "Mayotte", "name": "French"},
"🇿🇦": {"code": "af", "country": "South Africa", "name": "Afrikaans"},
"🇿🇲": {"code": "en", "country": "Zambia", "name": "English"},
"🇿🇼": {"code": "en", "country": "Zimbabwe", "name": "English"},
}

29
translate/info.json Normal file
View file

@ -0,0 +1,29 @@
{
"author" : [
"Aziz",
"TrustyJAID",
"brandons209"
],
"description" : "Add flag emojis to messages to translate to that language or translate messages by command.",
"disabled" : false,
"end_user_data_statement" : "This cog does not persistently store data or metadata about users. However, this cog does pass user data to an external API for the purposes of analyzing and translating languages.",
"hidden" : true,
"install_msg" : "1. Go to Google Developers Console and log in with your Google account. (https://console.developers.google.com/)\n2. You should be prompted to create a new project (name does not matter).\n3. Click on Enable APIs and Services at the top.\n4. In the list of APIs choose or search for Cloud Translate API and click on it. Choose Enable.\n5. Click on Credentials on the left navigation bar.\n6. Click on Create Credential at the top.\n7. At the top click the link for \"API key\".\n8. No application restrictions are needed. Click Create at the bottom.\n9. You now have a key to add to `[p]translateset` Note: This cog may end up costing lots of money to use up to $20 per 1 million characters.",
"max_bot_version" : "0.0.0",
"min_bot_version" : "3.1.8",
"min_python_version" : [
3,
7,
2
],
"name" : "Translate",
"permissions" : [],
"required_cogs" : {},
"requirements" : [],
"short" : "Translate messages using google translate!",
"tags" : [
"utility",
"translation"
],
"type" : "COG"
}

157
translate/locales/fr-FR.po Normal file
View file

@ -0,0 +1,157 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-02-09 00:37+0100\n"
"PO-Revision-Date: 2019-02-09 01::03+0100\n"
"Last-Translator: Predä <EMAIL@ADDRESS>\n"
"Language-Team: FRENCH <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 2.2\n"
#: translate.py:62
#, docstring
msgid ""
"\n"
" Translate messages using google translate\n"
" "
msgstr ""
"\n"
" Traduire des messages en utilisant Google Traduction\n\n"
" "
#: translate.py:90
msgid " said:"
msgstr " a dit :"
#: translate.py:91
msgid " to "
msgstr " vers "
#: translate.py:91
msgid "Requested by "
msgstr "Demandé par "
#: translate.py:185
msgid "said:"
msgstr "a dit :"
#: translate.py:305
#, docstring
msgid ""
"\n"
" Translate messages with google translate\n"
"\n"
" `to_language` is the language you would like to translate\n"
" `message` is the message to translate\n"
" "
msgstr ""
"\n"
" Traduire des messages avec Google Traduction\n"
"\n"
" `to_language` est la langue que vous souhaitez traduire\n\n"
" `message` est le message que vous voulez traduire\n\n"
" "
#: translate.py:313
msgid "The bot owner needs to set an api key first!"
msgstr "Le propriétaire du bot doit d'abord définir la clé API !"
#: translate.py:340
#, docstring
msgid ""
"\n"
" Toggle the bot auto translating\n"
" "
msgstr ""
"\n"
" Activer la traduction automatique du bot\n\n"
" "
#: translate.py:349
#, docstring
msgid ""
"\n"
" Toggle translations to flag emoji reactions\n"
" "
msgstr ""
"\n"
" Activer la traduction en utilisant des drapeaux dans les réactions\n\n"
" "
#: translate.py:355 translate.py:372
msgid "on"
msgstr "activée"
#: translate.py:357 translate.py:374
msgid "off"
msgstr "désactivée"
#: translate.py:359
msgid "Reaction translations have been turned "
msgstr "La traduction en se servant des réactions a été "
#: translate.py:366
#, docstring
msgid ""
"\n"
" Toggle translations with flag emojis in text\n"
" "
msgstr ""
"\n"
" Activer la traduction en utilisant des émojis de drapeaux dans le texte\n"
" "
#: translate.py:376
msgid "Flag emoji translations have been turned "
msgstr "La traduction en utilisant des émojis de drapeaux a été "
#: translate.py:382
#, docstring
msgid ""
"\n"
" You must get an API key from google to set this up\n"
"\n"
" Note: Using this cog costs money, current rates are $20 per 1 million characters.\n"
"\n"
" 1. Go to Google Developers Console and log in with your Google account. \n"
" (https://console.developers.google.com/)\n"
" 2. You should be prompted to create a new project (name does not matter).\n"
" 3. Click on Enable APIs and Services at the top.\n"
" 4. In the list of APIs choose or search for Cloud Translate API and click on it. \n"
" Choose Enable.\n"
" 5. Click on Credentials on the left navigation bar.\n"
" 6. Click on Create Credential at the top.\n"
" 7. At the top click the link for \"API key\".\n"
" 8. No application restrictions are needed. Click Create at the bottom.\n"
" 9. You now have a key to add to `[p]translateset`\n"
" "
msgstr ""
"\n"
" Vous devez obtenir une clé d'API de Google pour configurer ceci\n"
"\n"
" Remarque : L'utilisation de ce cog coûte de l'argent, les taux actuels sont d'environ 17€ pour 1 million de caractères.\n"
"\n"
" 1. Allez sur la console développeurs Google, et connectez-vous avec votre compte Google. \n\n"
" (https://console.developers.google.com/)\n\n"
" 2. Vous devriez être invité à créer un nouveau projet (le nom n'a pas d'importance).\n\n"
" 3. Cliquez sur \"Activer des API et des services\" en haut de la page.\n\n"
" 4. Dans la liste des APIs, choisissez ou recherchez Cloud Translate API, cliquez dessus. \n\n"
" Choisissez \"Activer\".\n\n"
" 5. Cliquez sur \"Identifiants\" dans la barre de navigation en bas à gauche.\n\n"
" 6. Cliquez sur \"Créer des identifiants\" en haut de la page.\n\n"
" 7. En haut cliquez sur le lien pour \"Clé API\".\n\n"
" 8. Aucunes restrictions d'applications sont nécessaires. Cliquez sur \"Créer\" en bas de la page.\n\n"
" 9. Vous avez maintenant une clé à ajouter sur `[p]translateset`\n\n"
" "
#: translate.py:400
msgid "API key set."
msgstr "Clé API définie."

456
translate/translate.py Normal file
View file

@ -0,0 +1,456 @@
import discord
import logging
from typing import Optional, Union
from discord.ext.commands.errors import BadArgument
from redbot.core import Config, checks, commands, version_info, VersionInfo
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import humanize_list, error
from .api import FlagTranslation, GoogleTranslateAPI
from .converters import ChannelUserRole
from .errors import GoogleTranslateAPIError
"""
Translator cog
Cog credit to aziz#5919 for the idea and
Links
Wiki https://goo.gl/3fxjSA
GitHub https://goo.gl/oQAQde
Support the developer https://goo.gl/Brchj4
Invite the bot to your guild https://goo.gl/aQm2G7
Join the official development guild https://discord.gg/uekTNPj
"""
BASE_URL = "https://translation.googleapis.com"
_ = Translator("Translate", __file__)
log = logging.getLogger("red.trusty-cogs.Translate")
@cog_i18n(_)
class Translate(GoogleTranslateAPI, commands.Cog):
"""
Translate messages using Google Translate
"""
__author__ = ["Aziz", "TrustyJAID"]
__version__ = "2.3.7"
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 156434873547585, force_registration=True)
default_guild = {
"reaction": False,
"text": False,
"whitelist": [],
"blacklist": [],
"count": {"characters": 0, "requests": 0, "detect": 0},
"autosend": {},
}
default = {
"cooldown": {"past_flags": [], "timeout": 0, "multiple": False},
"count": {"characters": 0, "requests": 0, "detect": 0},
}
self.config.register_guild(**default_guild)
self.config.register_global(**default)
self.cache = {
"translations": [],
"cooldown_translations": {},
"guild_messages": [],
"guild_reactions": [],
"cooldown": {},
"guild_blacklist": {},
"guild_whitelist": {},
"autolangs": {},
}
self._key: Optional[str] = None
self._clear_cache = self.bot.loop.create_task(self.cleanup_cache())
self._save_loop = self.bot.loop.create_task(self.save_usage())
self._guild_counter = {}
self._global_counter = {}
def format_help_for_context(self, ctx: commands.Context) -> str:
"""
Thanks Sinbad!
"""
pre_processed = super().format_help_for_context(ctx)
return f"{pre_processed}\n\nCog Version: {self.__version__}"
async def red_delete_data_for_user(self, **kwargs):
"""
Nothing to delete
"""
return
async def init(self) -> None:
try:
key = await self.config.api_key()
except AttributeError:
return
try:
central_key = await self.bot.get_shared_api_tokens("google_translate")
except AttributeError:
# Red 3.1 support
central_key = await self.bot.db.api_tokens.get_raw("google_translate", default={})
if not central_key:
try:
await self.bot.set_shared_api_tokens("google_translate", api_key=key)
except AttributeError:
await self.bot.db.api_tokens.set_raw("google_translate", value={"api_key": key})
await self.config.api_key.clear()
self._global_counter = await self.config.count()
all_guilds = await self.config.all_guilds()
for g_id, data in all_guilds.items():
self._guild_counter[g_id] = data["count"]
@commands.command()
async def translate(
self,
ctx: commands.Context,
to_language: FlagTranslation,
*,
message: Union[discord.Message, str],
) -> None:
"""
Translate messages with Google Translate
`<to_language>` is the language you would like to translate
`<message>` is the message to translate, this can be words you want
to translate, a channelID-messageID from SHIFT + clicking a message and copying ID,
a message ID from the current channel, or message link
"""
if not await self._get_google_api_key():
msg = _("The bot owner needs to set an api key first!")
await ctx.send(msg)
return
author = ctx.message.author
requestor = ctx.message.author
msg = ctx.message
if isinstance(message, discord.Message):
msg = message
author = message.author
message = message.clean_content
try:
detected_lang = await self.detect_language(message)
await self.add_detect(ctx.guild)
except GoogleTranslateAPIError as e:
await ctx.send(str(e))
return
from_lang = detected_lang[0][0]["language"]
original_lang = detected_lang[0][0]["language"]
if to_language == original_lang:
return await ctx.send(
_("I cannot translate `{from_lang}` to `{to}`").format(from_lang=from_lang, to=to_language)
)
try:
translated_text = await self.translate_text(original_lang, to_language, message)
await self.add_requests(ctx.guild, message)
except GoogleTranslateAPIError as e:
await ctx.send(str(e))
return
if ctx.channel.permissions_for(ctx.me).embed_links:
translation = (translated_text, from_lang, to_language)
em = await self.translation_embed(author, translation, requestor)
if version_info >= VersionInfo.from_str("3.4.6") and msg.channel.id == ctx.channel.id:
await ctx.send(embed=em, reference=msg, mention_author=False)
else:
await ctx.send(embed=em)
else:
if version_info >= VersionInfo.from_str("3.4.6") and msg.channel.id == ctx.channel.id:
await ctx.send(translated_text, reference=msg, mention_author=False)
else:
await ctx.send(translated_text)
@commands.group()
async def translateset(self, ctx: commands.Context) -> None:
"""
Toggle the bot auto translating
"""
pass
@translateset.command(name="auto")
async def translate_auto(self, ctx: commands.Context, languages: str, *links: discord.TextChannel) -> None:
"""
Set channels to auto translate messages from and to
All channels will be linked together, that is every channel's messages will be translated to every other channel.
Languages should be the **receive language** that every message sent to it should be translated to.
**languages** should be a comma seperated list of languages with no spaces matching the links!
"""
langs = [l.strip() for l in languages.strip().split(",")]
if len(langs) != len(links):
return await ctx.send(error("The number of lanuages and link channels don't match!"), delete_after=30)
await self.config.guild(ctx.guild).autosend.set({l.id: lang for l, lang in zip(links, langs)})
await ctx.tick()
@translateset.command(name="stats")
async def translate_stats(self, ctx: commands.Context, guild_id: Optional[int]):
"""
Shows translation usage
"""
if guild_id and not await self.bot.is_owner(ctx.author):
return await ctx.send(_("That is only available for the bot owner."))
elif guild_id and await self.bot.is_owner(ctx.author):
if not (guild := self.bot.get_guild(guild_id)):
return await ctx.send(_("Guild `{guild_id}` not found.").format(guild_id=guild_id))
else:
guild = ctx.guild
tr_keys = {
"requests": _("API Requests:"),
"detect": _("API Detect Language:"),
"characters": _("Characters requested:"),
}
count = (
self._guild_counter[guild.id] if guild.id in self._guild_counter else await self.config.guild(guild).count()
)
gl_count = self._global_counter if self._global_counter else await self.config.count()
msg = _("__Global Usage__:\n")
for key, value in gl_count.items():
msg += tr_keys[key] + f" **{value}**\n"
msg += _("__{guild} Usage__:\n").format(guild=guild.name)
for key, value in count.items():
msg += tr_keys[key] + f" **{value}**\n"
await ctx.maybe_send_embed(msg)
@translateset.group(aliases=["blocklist"])
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def blacklist(self, ctx: commands.Context) -> None:
"""
Set blacklist options for translations
blacklisting supports channels, users, or roles
"""
pass
@translateset.group(aliases=["allowlist"])
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def whitelist(self, ctx: commands.Context) -> None:
"""
Set whitelist options for translations
whitelisting supports channels, users, or roles
"""
pass
@whitelist.command(name="add")
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def whitelist_add(self, ctx: commands.Context, *channel_user_role: ChannelUserRole) -> None:
"""
Add a channel, user, or role to translation whitelist
"""
if len(channel_user_role) < 1:
return await ctx.send(_("You must supply 1 or more channels users or roles to be whitelisted."))
for obj in channel_user_role:
if obj.id not in await self.config.guild(ctx.guild).whitelist():
async with self.config.guild(ctx.guild).whitelist() as whitelist:
whitelist.append(obj.id)
await self._bw_list_cache_update(ctx.guild)
msg = _("`{list_type}` added to translation whitelist.")
list_type = humanize_list([c.name for c in channel_user_role])
await ctx.send(msg.format(list_type=list_type))
@whitelist.command(name="remove", aliases=["rem", "del"])
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def whitelist_remove(self, ctx: commands.Context, *channel_user_role: ChannelUserRole) -> None:
"""
Remove a channel, user, or role from translation whitelist
"""
if len(channel_user_role) < 1:
return await ctx.send(
_("You must supply 1 or more channels, users, " "or roles to be removed from the whitelist")
)
for obj in channel_user_role:
if obj.id in await self.config.guild(ctx.guild).whitelist():
async with self.config.guild(ctx.guild).whitelist() as whitelist:
whitelist.remove(obj.id)
await self._bw_list_cache_update(ctx.guild)
msg = _("`{list_type}` removed from translation whitelist.")
list_type = humanize_list([c.name for c in channel_user_role])
await ctx.send(msg.format(list_type=list_type))
@whitelist.command(name="list")
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def whitelist_list(self, ctx: commands.Context) -> None:
"""
List Channels, Users, and Roles in the servers translation whitelist.
"""
whitelist = []
for _id in await self.config.guild(ctx.guild).whitelist():
try:
whitelist.append(await ChannelUserRole().convert(ctx, str(_id)))
except BadArgument:
continue
whitelist_s = ", ".join(x.name for x in whitelist)
await ctx.send(_("`{whitelisted}` are currently whitelisted.").format(whitelisted=whitelist_s))
@blacklist.command(name="add")
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def blacklist_add(self, ctx: commands.Context, *channel_user_role: ChannelUserRole) -> None:
"""
Add a channel, user, or role to translation blacklist
"""
if len(channel_user_role) < 1:
return await ctx.send(_("You must supply 1 or more channels users or roles to be blacklisted."))
for obj in channel_user_role:
if obj.id not in await self.config.guild(ctx.guild).blacklist():
async with self.config.guild(ctx.guild).blacklist() as blacklist:
blacklist.append(obj.id)
await self._bw_list_cache_update(ctx.guild)
msg = _("`{list_type}` added to translation blacklist.")
list_type = humanize_list([c.name for c in channel_user_role])
await ctx.send(msg.format(list_type=list_type))
@blacklist.command(name="remove", aliases=["rem", "del"])
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def blacklist_remove(self, ctx: commands.Context, *channel_user_role: ChannelUserRole) -> None:
"""
Remove a channel, user, or role from translation blacklist
"""
if len(channel_user_role) < 1:
return await ctx.send(
_("You must supply 1 or more channels, users, " "or roles to be removed from the blacklist")
)
for obj in channel_user_role:
if obj.id in await self.config.guild(ctx.guild).blacklist():
async with self.config.guild(ctx.guild).blacklist() as blacklist:
blacklist.remove(obj.id)
await self._bw_list_cache_update(ctx.guild)
msg = _("`{list_type}` removed from translation blacklist.")
list_type = humanize_list([c.name for c in channel_user_role])
await ctx.send(msg.format(list_type=list_type))
@blacklist.command(name="list")
@checks.mod_or_permissions(manage_messages=True)
@commands.guild_only()
async def blacklist_list(self, ctx: commands.Context) -> None:
"""
List Channels, Users, and Roles in the servers translation blacklist.
"""
blacklist = []
for _id in await self.config.guild(ctx.guild).blacklist():
try:
blacklist.append(await ChannelUserRole().convert(ctx, str(_id)))
except BadArgument:
continue
blacklist_s = ", ".join(x.name for x in blacklist)
await ctx.send(_("`{blacklisted}` are currently blacklisted.").format(blacklisted=blacklist_s))
@translateset.command(aliases=["reaction", "reactions"])
@checks.mod_or_permissions(manage_channels=True)
@commands.guild_only()
async def react(self, ctx: commands.Context) -> None:
"""
Toggle translations to flag emoji reactions
"""
guild = ctx.message.guild
toggle = not await self.config.guild(guild).reaction()
if toggle:
verb = _("on")
else:
verb = _("off")
if guild.id in self.cache["guild_reactions"]:
self.cache["guild_reactions"].remove(guild.id)
await self.config.guild(guild).reaction.set(toggle)
msg = _("Reaction translations have been turned ")
await ctx.send(msg + verb)
@translateset.command(aliases=["multi"])
@checks.is_owner()
@commands.guild_only()
async def multiple(self, ctx: commands.Context) -> None:
"""
Toggle multiple translations for the same message
This will also ignore the translated message from
being translated into another language
"""
toggle = not await self.config.cooldown.multiple()
if toggle:
verb = _("on")
else:
verb = _("off")
await self.config.cooldown.multiple.set(toggle)
self.cache["cooldown"] = await self.config.cooldown()
msg = _("Multiple translations have been turned ")
await ctx.send(msg + verb)
@translateset.command(aliases=["cooldown"])
@checks.is_owner()
@commands.guild_only()
async def timeout(self, ctx: commands.Context, time: int) -> None:
"""
Set the cooldown before a message can be reacted to again
for translation
`<time>` Number of seconds until that message can be reacted to again
Note: If multiple reactions are not allowed the timeout setting
is ignored until the cache cleanup ~10 minutes.
"""
await self.config.cooldown.timeout.set(time)
self.cache["cooldown"] = await self.config.cooldown()
msg = _("Translation timeout set to {time}s.").format(time=time)
await ctx.send(msg)
@translateset.command(aliases=["flags"])
@checks.mod_or_permissions(manage_channels=True)
@commands.guild_only()
async def flag(self, ctx: commands.Context) -> None:
"""
Toggle translations with flag emojis in text
"""
guild = ctx.message.guild
toggle = not await self.config.guild(guild).text()
if toggle:
verb = _("on")
else:
verb = _("off")
if guild.id in self.cache["guild_messages"]:
self.cache["guild_messages"].remove(guild.id)
await self.config.guild(guild).text.set(toggle)
msg = _("Flag emoji translations have been turned ")
await ctx.send(msg + verb)
@translateset.command()
@checks.is_owner()
async def creds(self, ctx: commands.Context) -> None:
"""
You must get an API key from Google to set this up
Note: Using this cog costs money, current rates are $20 per 1 million characters.
"""
msg = _(
"1. Go to Google Developers Console and log in with your Google account."
"(https://console.developers.google.com/)\n"
"2. You should be prompted to create a new project (name does not matter).\n"
"3. Click on Enable APIs and Services at the top.\n"
"4. In the list of APIs choose or search for Cloud Translate API and click on it."
"Choose Enable.\n"
"5. Click on Credentials on the left navigation bar.\n"
"6. Click on Create Credential at the top.\n"
'7. At the top click the link for "API key".\n'
"8. No application restrictions are needed. Click Create at the bottom.\n"
"9. You now have a key to add to \n"
"`{prefix}set api google_translate api_key,YOUR_KEY_HERE`\n"
).format(prefix=ctx.prefix)
await ctx.maybe_send_embed(msg)
def cog_unload(self):
self._clear_cache.cancel()
self._save_loop.cancel()
self.bot.loop.create_task(self._save_usage_stats())
__unload = cog_unload