diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index 55a58e581..58a389e99 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -1,4 +1,5 @@ import asyncio +import logging import re from abc import ABC from collections import defaultdict @@ -8,6 +9,7 @@ import discord from redbot.core import Config, modlog, commands from redbot.core.bot import Red from redbot.core.i18n import Translator, cog_i18n +from redbot.core.utils._internal_utils import send_to_owners_with_prefix_replaced from .casetypes import CASETYPES from .events import Events from .kickban import KickBanMixin @@ -104,14 +106,12 @@ class Mod( await self.settings.guild(discord.Object(id=guild_id)).delete_repeats.set(val) await self.settings.version.set("1.0.0") # set version of last update if await self.settings.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 = _( "Ignored guilds and channels have been moved. " - "Please use `{prefix}moveignoredchannels` if " + "Please use `[p]moveignoredchannels` if " "you were previously using these functions." - ).format(prefix=prefix) - self.bot.loop.create_task(self.bot.send_to_owners(msg)) + ) + self.bot.loop.create_task(send_to_owners_with_prefix_replaced(self.bot, msg)) await self.settings.version.set(__version__) @commands.command() diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 197d624b6..a8908f8f7 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -2,6 +2,7 @@ import discord from redbot.core.bot import Red from redbot.core import checks, commands, Config from redbot.core.i18n import cog_i18n, Translator +from redbot.core.utils._internal_utils import send_to_owners_with_prefix_replaced from redbot.core.utils.chat_formatting import escape, pagify from .streamtypes import ( @@ -111,8 +112,6 @@ class Streams(commands.Cog): try: tokens["client_secret"] except KeyError: - prefixes = await self.bot.get_valid_prefixes() - prefix = re.sub(rf"<@!?{self.bot.user.id}>", f"@{self.bot.user.name}", prefixes[0]) message = _( "You need a client secret key to use correctly Twitch API on this cog.\n" "Follow these steps:\n" @@ -120,12 +119,12 @@ class Streams(commands.Cog): '2. Click "Manage" on your application.\n' '3. Click on "New secret".\n' "5. Copy your client ID and your client secret into:\n" - "`{prefix}set api twitch client_id " + "`[p]set api twitch client_id " "client_secret `\n\n" "Note: These tokens are sensitive and should only be used in a private channel " "or in DM with the bot." - ).format(prefix=prefix) - await self.bot.send_to_owners(message) + ) + await send_to_owners_with_prefix_replaced(self.bot, message) async with aiohttp.ClientSession() as session: async with session.post( "https://id.twitch.tv/oauth2/token", diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 0f53373dd..e1605051e 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -43,11 +43,12 @@ from .settings_caches import PrefixManager, IgnoreManager, WhitelistBlacklistMan from .rpc import RPCMixin from .utils import common_filters +from .utils._internal_utils import send_to_owners_with_prefix_replaced CUSTOM_GROUPS = "CUSTOM_GROUPS" SHARED_API_TOKENS = "SHARED_API_TOKENS" -log = logging.getLogger("redbot") +log = logging.getLogger("red") __all__ = ["RedBase", "Red", "ExitCodes"] @@ -548,18 +549,6 @@ class RedBase( last_system_info = await self._config.last_system_info() - async def notify_owners(content: str) -> None: - destinations = await self.get_owner_notification_destinations() - for destination in destinations: - prefixes = await self.get_valid_prefixes(getattr(destination, "guild", None)) - prefix = re.sub(rf"<@!?{self.bot.user.id}>", f"@{self.bot.user.name}", prefixes[0]) - try: - await destination.send(content.format(prefix=prefix)) - except Exception as _exc: - log.exception( - f"I could not send an owner notification to ({destination.id}){destination}" - ) - ver_info = list(sys.version_info[:2]) python_version_changed = False LIB_PATH = cog_data_path(raw_name="Downloader") / "lib" @@ -569,13 +558,14 @@ class RedBase( shutil.rmtree(str(LIB_PATH)) LIB_PATH.mkdir() self.loop.create_task( - notify_owners( + send_to_owners_with_prefix_replaced( + self, "We detected a change in minor Python version" " and cleared packages in lib folder.\n" "The instance was started with no cogs, please load Downloader" - " and use `{prefix}cog reinstallreqs` to regenerate lib folder." + " and use `[p]cog reinstallreqs` to regenerate lib folder." " After that, restart the bot to get" - " all of your previously loaded cogs loaded again." + " all of your previously loaded cogs loaded again.", ) ) python_version_changed = True @@ -603,11 +593,12 @@ class RedBase( if system_changed and not python_version_changed: self.loop.create_task( - notify_owners( + send_to_owners_with_prefix_replaced( + self, "We detected a possible change in machine's operating system" " or architecture. You might need to regenerate your lib folder" " if 3rd-party cogs stop working properly.\n" - "To regenerate lib folder, load Downloader and use `{prefix}cog reinstallreqs`." + "To regenerate lib folder, load Downloader and use `[p]cog reinstallreqs`.", ) ) @@ -1202,8 +1193,11 @@ class RedBase( try: await location.send(content, **kwargs) except Exception as _exc: - log.exception( - f"I could not send an owner notification to ({location.id}){location}" + log.error( + "I could not send an owner notification to %s (%s)", + location, + location.id, + exc_info=_exc, ) sends = [wrapped_send(d, content, **kwargs) for d in destinations] diff --git a/redbot/core/utils/_internal_utils.py b/redbot/core/utils/_internal_utils.py index 63fd23d33..edd0b8c2a 100644 --- a/redbot/core/utils/_internal_utils.py +++ b/redbot/core/utils/_internal_utils.py @@ -1,13 +1,15 @@ from __future__ import annotations +import asyncio import json import logging import os +import re import shutil import tarfile from datetime import datetime from pathlib import Path -from typing import List, Optional, Set, Union, TYPE_CHECKING +from typing import Awaitable, Callable, List, Optional, Set, Union, TYPE_CHECKING import discord from fuzzywuzzy import fuzz, process @@ -16,8 +18,11 @@ from redbot.core import data_manager from redbot.core.utils.chat_formatting import box if TYPE_CHECKING: + from redbot.core.bot import Red from redbot.core.commands import Command, Context +main_log = logging.getLogger("red") + __all__ = ("safe_delete", "fuzzy_command_search", "format_fuzzy_results", "create_backup") @@ -200,3 +205,63 @@ async def create_backup(dest: Path = Path.home()) -> Optional[Path]: for f in to_backup: tar.add(str(f), arcname=str(f.relative_to(data_path)), recursive=False) return backup_fpath + + +# this might be worth moving to `bot.send_to_owners` at later date + + +async def send_to_owners_with_preprocessor( + bot: Red, + content: str, + *, + content_preprocessor: Optional[ + Callable[[Red, discord.abc.Messageable, str], Awaitable[str]] + ] = None, + **kwargs, +): + """ + This sends something to all owners and their configured extra destinations. + + This acts the same as `Red.send_to_owners`, with + one added keyword argument as detailed below in *Other Parameters*. + + Other Parameters + ---------------- + content_preprocessor: Optional[Callable[[Red, discord.abc.Messageable, str], Awaitable[str]]] + Optional async function that takes + bot object, owner notification destination and message content + and returns the content that should be sent to given location. + """ + destinations = await bot.get_owner_notification_destinations() + + async def wrapped_send(bot, location, content=None, preprocessor=None, **kwargs): + try: + if preprocessor is not None: + content = await preprocessor(bot, location, content) + await location.send(content, **kwargs) + except Exception as _exc: + main_log.error( + "I could not send an owner notification to %s (%s)", + location, + location.id, + exc_info=_exc, + ) + + sends = [wrapped_send(bot, d, content, content_preprocessor, **kwargs) for d in destinations] + await asyncio.gather(*sends) + + +async def send_to_owners_with_prefix_replaced(bot: Red, content: str, **kwargs): + """ + This sends something to all owners and their configured extra destinations. + + This acts the same as `Red.send_to_owners`, with one addition - `[p]` in ``content`` argument + is replaced with a clean prefix for each specific destination. + """ + + async def preprocessor(bot: Red, destination: discord.abc.Messageable, content: str) -> str: + prefixes = await bot.get_valid_prefixes(getattr(destination, "guild", None)) + prefix = re.sub(rf"<@!?{bot.user.id}>", f"@{bot.user.name}", prefixes[0]) + return content.replace("[p]", prefix) + + await send_to_owners_with_preprocessor(bot, content, content_preprocessor=preprocessor)