diff --git a/emoji/core.py b/emoji/core.py index 5df8b50..0d90b67 100644 --- a/emoji/core.py +++ b/emoji/core.py @@ -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 diff --git a/imagemagic/imagemagic.py b/imagemagic/imagemagic.py index 0a5f1f6..fde77d5 100644 --- a/imagemagic/imagemagic.py +++ b/imagemagic/imagemagic.py @@ -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 diff --git a/reactpoll/reactpoll.py b/reactpoll/reactpoll.py index 7d25304..fe636b3 100644 --- a/reactpoll/reactpoll.py +++ b/reactpoll/reactpoll.py @@ -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() diff --git a/scriptgen/script.py b/scriptgen/script.py index e4997ce..7f3fb17 100644 --- a/scriptgen/script.py +++ b/scriptgen/script.py @@ -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 diff --git a/translate/__init__.py b/translate/__init__.py new file mode 100644 index 0000000..991325d --- /dev/null +++ b/translate/__init__.py @@ -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) diff --git a/translate/api.py b/translate/api.py new file mode 100644 index 0000000..388ba76 --- /dev/null +++ b/translate/api.py @@ -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 diff --git a/translate/converters.py b/translate/converters.py new file mode 100644 index 0000000..f02f60e --- /dev/null +++ b/translate/converters.py @@ -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 diff --git a/translate/errors.py b/translate/errors.py new file mode 100644 index 0000000..bd2e3be --- /dev/null +++ b/translate/errors.py @@ -0,0 +1,4 @@ +class GoogleTranslateAPIError(Exception): + """Raised when google translate has an oopsie""" + + pass diff --git a/translate/flags.py b/translate/flags.py new file mode 100644 index 0000000..ee2757e --- /dev/null +++ b/translate/flags.py @@ -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"}, +} diff --git a/translate/info.json b/translate/info.json new file mode 100644 index 0000000..50cc184 --- /dev/null +++ b/translate/info.json @@ -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" +} diff --git a/translate/locales/fr-FR.po b/translate/locales/fr-FR.po new file mode 100644 index 0000000..7d7a3d6 --- /dev/null +++ b/translate/locales/fr-FR.po @@ -0,0 +1,157 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , 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รค \n" +"Language-Team: FRENCH \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." + diff --git a/translate/translate.py b/translate/translate.py new file mode 100644 index 0000000..c9b4719 --- /dev/null +++ b/translate/translate.py @@ -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 + + `` is the language you would like to translate + `` 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 + + `