Repository: Cog-Creators/Red-DiscordBot

Issue:
[RFC] Core red API bot.send_to_owner
We have more and more specific information which we want to get to owners, and with team users, and co-owners, we need a better way than just fetching the owner and trying to send.

I'm proposing a new method on the bot, `send_to_owner`. This will behave *like* `discord.abc.Messagable` with the args it takes, except it will do the following:

- Send to the owner and co-owner, owner only if the owner does not have a discrim of `#0000` (Currently, team users and webhooks are the only things with that as a discrim, users can't have it)
- Send to a configured channel(s)

Additionally, this would come with settings to opt out of DMs specifically from this for people who choose to only have these message in a configured channel.

### Things in the bot (currently) which should use this on initial implementation if added:

- contact
- update notifications



Discussion:
I agree with this, especially with the use of Discord Developer teams.

Resolving patch:
diff --git a/redbot/core/bot.py b/redbot/core/bot.py
--- a/redbot/core/bot.py
+++ b/redbot/core/bot.py
@@ -20,6 +20,8 @@
 
 CUSTOM_GROUPS = "CUSTOM_GROUPS"
 
+log = logging.getLogger("redbot")
+
 
 def _is_submodule(parent, child):
     return parent == child or child.startswith(parent + ".")
@@ -60,6 +62,8 @@ def __init__(self, *args, cli_flags=None, bot_dir: Path = Path.cwd(), **kwargs):
             disabled_commands=[],
             disabled_command_msg="That command is disabled.",
             api_tokens={},
+            extra_owner_destinations=[],
+            owner_opt_out_list=[],
         )
 
         self.db.register_guild(
@@ -464,6 +468,47 @@ async def verify_permissions_hooks(self, ctx: commands.Context) -> Optional[bool
                 ctx.permission_state = commands.PermState.DENIED_BY_HOOK
                 return False
 
+    async def get_owner_notification_destinations(self) -> List[discord.abc.Messageable]:
+        """
+        Gets the users and channels to send to
+        """
+        destinations = []
+        opt_outs = await self.db.owner_opt_out_list()
+        for user_id in (self.owner_id, *self._co_owners):
+            if user_id not in opt_outs:
+                user = self.get_user(user_id)
+                if user:
+                    destinations.append(user)
+
+        channel_ids = await self.db.extra_owner_destinations()
+        for channel_id in channel_ids:
+            channel = self.get_channel(channel_id)
+            if channel:
+                destinations.append(channel)
+
+        return destinations
+
+    async def send_to_owners(self, content=None, **kwargs):
+        """
+        This sends something to all owners and their configured extra destinations.
+
+        This takes the same arguments as discord.abc.Messageable.send
+
+        This logs failing sends
+        """
+        destinations = await self.get_owner_notification_destinations()
+
+        async def wrapped_send(location, content=None, **kwargs):
+            try:
+                await location.send(content, **kwargs)
+            except Exception as _exc:
+                log.exception(
+                    f"I could not send an owner notification to ({location.id}){location}"
+                )
+
+        sends = [wrapped_send(d, content, **kwargs) for d in destinations]
+        await asyncio.gather(*sends)
+
 
 class Red(RedBase, discord.AutoShardedClient):
     """
diff --git a/redbot/core/events.py b/redbot/core/events.py
--- a/redbot/core/events.py
+++ b/redbot/core/events.py
@@ -120,24 +120,12 @@ async def on_ready():
                     "but you're using {}".format(data["info"]["version"], red_version)
                 )
 
-                owners = []
-                owner = bot.get_user(bot.owner_id)
-                if owner is not None:
-                    owners.append(owner)
-
-                for co_owner in bot._co_owners:
-                    co_owner = bot.get_user(co_owner)
-                    if co_owner is not None:
-                        owners.append(co_owner)
-
-                for owner in owners:
-                    with contextlib.suppress(discord.HTTPException):
-                        await owner.send(
-                            "Your Red instance is out of date! {} is the current "
-                            "version, however you are using {}!".format(
-                                data["info"]["version"], red_version
-                            )
-                        )
+                await bot.send_to_owners(
+                    "Your Red instance is out of date! {} is the current "
+                    "version, however you are using {}!".format(
+                        data["info"]["version"], red_version
+                    )
+                )
         INFO2 = []
 
         mongo_enabled = storage_type() != "JSON"


Test patch: