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"): 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"): await ctx.send(translated_text, reference=msg, mention_author=False) else: await ctx.send(translated_text)
async def _get_latest_vers(cogname: str) -> Vers: data: dict async with aiohttp.ClientSession() as session: async with session.get(f"https://api.vexcodes.com/v2/vers/{cogname}", timeout=3) as r: data = await r.json() latest_utils = data["utils"][:7] latest_cog = VersionInfo.from_str(data.get(cogname, "0.0.0")) async with session.get("https://pypi.org/pypi/Red-DiscordBot/json", timeout=3) as r: data = await r.json() latest_red = VersionInfo.from_str(data.get("info", {}).get("version", "0.0.0")) return Vers(cogname, latest_cog, latest_utils, latest_red)
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)
def red_version(min_ver: str = None, max_ver: str = None) -> None: """Runtime Red version check This may be preferable if you'd like to check the bot's version independently of Downloader. Example ------- >>> def setup(bot): ... # If Red is on 3.1.0 or 3.1.1, this will raise a CogLoadError. ... # Otherwise, this is comparable to a no-op. ... # The above isn't applicable on 3.0.x, as simply importing swift_libs will raise ... # a variety of import errors. ... red_version("3.1.2") ... # ... Parameters ----------- min_ver: :class:`str` Minimum expected bot version max_ver: :class:`str` Maximum expected bot version; you shouldn't need to use this in most cases Raises ------ CogLoadError """ if not min_ver and not max_ver: raise RuntimeError("Neither minimum nor maximum versions were passed") if min_ver: min_ver = VersionInfo.from_str(min_ver) if max_ver: max_ver = VersionInfo.from_str(max_ver) if min_ver and max_ver and min_ver > max_ver: raise RuntimeError( "Minimum version is greater than the maximum version") err = translate.lazy( "checks", (("red_outdated_or_new" if min_ver != max_ver else "red_not_exact") if min_ver and max_ver else "red_outdated" if min_ver else "red_too_new"), min=min_ver, max=max_ver, current=_red_ver, ) if min_ver and min_ver > _red_ver: raise CogLoadError(str(err)) if max_ver and max_ver < _red_ver: raise CogLoadError(str(err))
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None: """Request a conversion by reaction""" if version_info >= VersionInfo.from_str("3.2.0"): await self.bot.wait_until_red_ready() else: await self.bot.wait_until_ready() 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 reacted_user = guild.get_member(payload.user_id) if reacted_user.bot: return try: message: discord.Message = await channel.fetch_message(id=payload.message_id) except (discord.errors.NotFound, discord.Forbidden): return if str(payload.emoji) != await self._getConfig(guild, 'emoji'): return msg = await self._convert(message.content, reacted_user) if msg: await channel.send(embed = msg.embed)
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)
async def maybe_change_avatar(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("TrustyAvatar"): new_avatar = choice([s for s in self.statuses]) new_status = self.statuses.get(new_avatar, None) status, activity, url = await self.get_activity(new_status) owner = await self.get_bot_owner() is_streaming = type( owner.activity) == discord.ActivityType.streaming if await self.config.streaming(): if is_streaming: await self.change_activity(None, owner.activity) log.debug("Changing to owner is streaming status.") if await self.config.status() and not is_streaming: # we don't want to override the streaming status if the owner is streaming await self.change_activity(status, activity) log.debug("Changing to random status.") if await self.config.avatar(): await self.change_avatar(url) log.debug("changing avatar to {}".format(new_avatar)) await asyncio.sleep(randint(1800, 3600))
async def _handle_message_search(self, message: discord.Message): if await self.bot.is_automod_immune(message.author): return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, message.guild): return find = INVITE_RE.findall(message.clean_content) guild = message.guild if find and await self.config.guild(message.guild).all_invites(): try: await message.delete() except discord.errors.Forbidden: log.error( _("I tried to delete an invite link posted in {guild} " "but lacked the permission to do so").format( guild=guild.name)) return if whitelist := await self.config.guild(message.guild).whitelist(): for i in find: invite = await self.bot.fetch_invite(i) if invite.guild.id == message.guild.id: continue if invite.guild.id not in whitelist: try: await message.delete() except discord.errors.Forbidden: log.error( _("I tried to delete an invite link posted in {guild} " "but lacked the permission to do so").format( guild=guild.name)) return return
async def is_outdated(): red_pypi = "https://pypi.python.org/pypi/BotExBotBase" async with aiohttp.ClientSession() as session: async with session.get("{}/json".format(red_pypi)) as r: data = await r.json() new_version = data["info"]["version"] return VersionInfo.from_str(new_version) > red_version_info, new_version
async def start_stream(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() api = None base_sleep = 300 count = 1 while self.run_stream: if not await self.config.api.consumer_key(): # Don't run the loop until tokens are set await asyncio.sleep(base_sleep) continue tweet_list = list(self.accounts) if not tweet_list: await asyncio.sleep(base_sleep) continue if not api: api = await self.authenticate() if self.mystream is None: await self._start_stream(tweet_list, api) elif self.mystream and not getattr(self.mystream, "running"): count += 1 await self._start_stream(tweet_list, api) log.debug(f"tweets waiting {base_sleep * count} seconds.") await asyncio.sleep(base_sleep * count)
def sl_version(min_ver: str, cog: str = None): """Check if the version of swift_libs loaded matches the requested version Example ------- >>> def setup(bot): ... # On 1.0.x, this will raise a CogLoadError explaining how ... # the bot owner can update their loaded swift_libs version. ... sl_version("1.1.0") ... # ... Arguments ---------- min_ver: str The minimum swift_libs version your cog requires to load cog: Optional[str] Your cog's package name. If this is provided, your cog will be offered to be reloaded if ``[p]swiftlibs reload`` is invoked. """ if VersionInfo.from_str(min_ver) > _sl_ver: if cog: # noinspection PyProtectedMember from .setup import require_update require_update.add(cog) raise CogLoadError( translate("checks.sl_outdated", current=_sl_ver, expected=min_ver))
async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): """ Handle messages edited with links """ if payload.cached_message: guild = payload.cached_message.guild else: guild = self.bot.get_guild(int(payload.data["guild_id"])) if guild is None: return chan = guild.get_channel(payload.channel_id) if chan is None: return if version_info >= VersionInfo.from_str("3.4.0"): if await self.bot.cog_disabled_in_guild(self, guild): return guild_settings = await self.config.guild(guild).all() if guild_settings["blacklist"] or guild_settings[ "whitelist"] or guild_settings["all_invites"]: try: msg = await chan.fetch_message(payload.message_id) except (discord.errors.Forbidden, discord.errors.NotFound): return await self._handle_message_search(msg)
def test_process_info_file(installable): for k, v in INFO_JSON.items(): if k == "type": assert installable.type == InstallableType.COG elif k in ("min_bot_version", "max_bot_version"): assert getattr(installable, k) == VersionInfo.from_str(v) else: assert getattr(installable, k) == v
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 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)
def test_process_lib_info_file(library_installable): for k, v in LIBRARY_INFO_JSON.items(): if k == "type": assert library_installable.type == InstallableType.SHARED_LIBRARY elif k in ("min_bot_version", "max_bot_version"): assert getattr(library_installable, k) == VersionInfo.from_str(v) elif k == "hidden": # libraries are always hidden, even if False assert library_installable.hidden is True else: assert getattr(library_installable, k) == v
def _get_current_vers(curr_cog_ver: str, qual_name: str) -> Vers: with open(Path(__file__).parent / "commit.json") as fp: data = json.load(fp) latest_utils = data.get("latest_commit", "Unknown")[:7] return Vers( qual_name, VersionInfo.from_str(curr_cog_ver), latest_utils, cur_red_version, )
async def rotominfo(self, ctx): """ Red's info command modified for Rotom """ author_repo = "https://github.com/Twentysix26" org_repo = "https://github.com/Cog-Creators" red_repo = org_repo + "/Red-DiscordBot" red_pypi = "https://pypi.python.org/pypi/Red-DiscordBot" support_server_url = "https://discord.gg/red" dpy_repo = "https://github.com/Rapptz/discord.py" python_url = "https://www.python.org/" rotom_repo = "https://github.com/yamikaitou/rotom" since = datetime.datetime(2016, 1, 2, 0, 0) days_since = (datetime.datetime.utcnow() - since).days dpy_version = "[{}]({})".format(discord.__version__, dpy_repo) python_version = "[{}.{}.{}]({})".format(*sys.version_info[:3], python_url) red_version = "[{}]({})".format(__version__, red_pypi) app_info = await self.bot.application_info() owner = app_info.owner custom_info = await self.bot._config.custom_info() async with aiohttp.ClientSession() as session: async with session.get("{}/json".format(red_pypi)) as r: data = await r.json() outdated = VersionInfo.from_str(data["info"]["version"]) > red_version_info about = ( "This is an instance of [Red, an open source Discord bot]({}) " "(which was created by [Twentysix]({}) and [improved by many]({})).\n" "Red is backed by a passionate community who contributes and " "creates content for everyone to enjoy. [Join us today]({}) " "and help us improve!\n" "Red has been bringing joy since 02 Jan 2016 (over {} days ago!)\n\n" "Rotom is built using Cogs with Red as the underlying framework.\n" "While a lot of what makes up Rotom is custom designed for specific Pokemon Go servers, it is open-sourced.\n" "You can view it and contribute back on [GitHub]({})" ).format(red_repo, author_repo, org_repo, support_server_url, days_since, rotom_repo) embed = discord.Embed(color=(await ctx.embed_colour())) embed.add_field(name=("Instance owned by"), value=str(owner)) embed.add_field(name="Python", value=python_version) embed.add_field(name="discord.py", value=dpy_version) embed.add_field(name=("Red version"), value=red_version) if outdated: embed.add_field( name=("Outdated"), value=("Yes, {} is available").format(data["info"]["version"]) ) if custom_info: embed.add_field(name=_("About this instance"), value=custom_info, inline=False) embed.add_field(name=("About Red & Rotom"), value=about, inline=False) try: await ctx.send(embed=embed) except discord.HTTPException: await ctx.send(("I need the `Embed links` permission to send this"))
async def startup(self): if version_info >= VersionInfo.from_str("3.2.0"): await self.bot.wait_until_red_ready() else: await self.bot.wait_until_ready() conf = await self.config.all() maybe_channel = conf.get("restart_channel", None) await self.config.restart_channel.clear() # Using the walrus operator we can check if the channel is originally None # Or that we cannot find that channel if maybe_channel is None or (ch := self.bot.get_channel(maybe_channel)) is None: return
async def group_welcome(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 not self.bot.is_closed(): # log.debug("Checking for new welcomes") for guild_id, members in self.joined.items(): await self.send_member_join(members, self.bot.get_guild(guild_id)) self.joined = {} await asyncio.sleep(300)
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) 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(): return if guild.id not in self.cache["guild_messages"]: if not await self.config.guild(guild).text(): 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 not flag: return await self.translate_message(message, flag.group())
async def check_for_new_followers(self) -> None: # Checks twitch every minute for new followers 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("Twitch"): follow_accounts = await self.config.twitch_accounts() clip_accounts = await self.config.twitch_clips() for account in follow_accounts: await self.check_followers(account) await self.check_clips() await asyncio.sleep(60)
async def get_latest_redbot_version(): """Check PyPI for the latest update to Red-DiscordBot.""" url = "https://pypi.org/pypi/Red-DiscordBot/json" async with aiohttp.ClientSession() as session: try: async with session.get(url) as resp: if resp.status != 200: raise aiohttp.ServerConnectionError data = await resp.json() return VersionInfo.from_str(data["info"]["version"]) except aiohttp.ServerConnectionError: log.warning( "PyPI seems to be having some issues at the moment while checking for the latest Red-DiscordBot " "update. If this keeps happening, and PyPI is indeed up, consider opening a bug report for this." )
async def out_of_date_check(cogname: str, currentver: str) -> None: """Send a log at warning level if the cog is out of date.""" try: async with cog_ver_lock: vers = await _get_latest_vers(cogname) if VersionInfo.from_str(currentver) < vers.cog: log.warning( f"Your {cogname} cog, from Vex, is out of date. You can update your cogs with the " "'cog update' command in Discord.") else: log.debug(f"{cogname} cog is up to date") except Exception as e: log.debug( f"Something went wrong checking if {cogname} cog is up to date. See below.", exc_info=e) # really doesn't matter if this fails so fine with debug level return
async def check_for_new_followers(self) -> None: # Checks twitch every minute for new followers 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("Twitch"): check_accounts = await self.config.twitch_accounts() for account in check_accounts: followed = await self.get_profile_from_id(account["id"]) followers, total = await self.get_new_followers(account["id"]) for follow in reversed(followers): if follow.from_id not in account["followers"]: try: profile = await self.get_profile_from_id(follow.from_id) except Exception: log.error( f"Error getting twitch profile {follow.from_id}", exc_info=True ) log.info( f"{profile.login} Followed! {followed.display_name} " f"has {total} followers now." ) em = await self.make_follow_embed(followed, profile, total) for channel_id in account["channels"]: channel = self.bot.get_channel(id=channel_id) if not channel: continue if channel.permissions_for(channel.guild.me).embed_links: await channel.send(embed=em) else: text_msg = ( f"{profile.display_name} has just " f"followed {account.display_name}!" ) await channel.send(text_msg) check_accounts.remove(account) account["followers"].append(follow.from_id) check_accounts.append(account) await self.config.twitch_accounts.set(check_accounts) await asyncio.sleep(60)
async def init(self): if version_info >= VersionInfo.from_str("3.2.0"): await self.bot.wait_until_red_ready() else: await self.bot.wait_until_ready() try: if await self.config.version() < "1.1.0": prefixes = await self.bot.get_valid_prefixes() prefix = re.sub(rf"<@!?{self.bot.user.id}>", f"@{self.bot.user.name}", prefixes[0]) msg = ("The Conversions cog is now using a couple of API's " "that require API keys. Please use `{prefix}stockapi` " "to continue using the stock, gold, etc. commands. " "Please use `{prefix}cryptoapi` to continue using " "the cryptocurrency commands.").format(prefix=prefix) self.bot.loop.create_task(self.bot.send_to_owners(msg)) await self.config.version.set("1.1.0") except Exception: log.exception("There was an exception loading the cog.", exec_info=True) else: self._ready.set()
async def get_manifest(self) -> None: """ Checks if the manifest is up to date and downloads if it's not """ if version_info >= VersionInfo.from_str("3.2.0"): await self.bot.wait_until_red_ready() else: await self.bot.wait_until_ready() try: headers = await self.build_headers() except Destiny2MissingAPITokens: return manifest_data = await self.request_url( f"{BASE_URL}/Destiny2/Manifest/", headers=headers) locale = get_locale() if locale in manifest_data: manifest = manifest_data["jsonWorldContentPaths"][locale] elif locale[:-3] in manifest_data: manifest = manifest_data["jsonWorldContentPaths"][locale[:-3]] else: manifest = manifest_data["jsonWorldContentPaths"]["en"] if await self.config.manifest_version() != manifest_data["version"]: async with aiohttp.ClientSession() as session: async with session.get(f"https://bungie.net/{manifest}", headers=headers) as resp: data = await resp.json() for key, value in data.items(): path = cog_data_path(self) / f"{key}.json" with path.open(encoding="utf-8", mode="w") as f: json.dump(value, f, indent=4, sort_keys=False, separators=(",", " : ")) await self.config.manifest_version.set(manifest_data["version"]) return manifest_data["version"]
async def initialize(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() try: for guild_id in await self.config.all_guilds(): guild = self.bot.get_guild(int(guild_id)) if guild_id not in self.event_cache: self.event_cache[guild_id] = {} if guild is None: continue data = await self.config.guild(guild).events() for user_id, event_data in data.items(): try: event = await Event.from_json(event_data, guild) except (TypeError, KeyError, discord.errors.Forbidden): log.error("Error loading events", exc_info=True) continue if event is None: return self.event_cache[guild_id][event.message.id] = event except Exception as e: log.error("Error loading events", exc_info=e)
async def check_ignored_channel(self, message: discord.Message) -> 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) return await self.bot.ignored_channel_or_guild(ctx) channel = cast(discord.TextChannel, message.channel) guild = cast(discord.Guild, channel.guild) author = cast(discord.Member, message.author) 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)
from redbot.core import VersionInfo __version__ = "0.0.3a0" version_info = VersionInfo.from_str(__version__)