Repository: Cog-Creators/Red-DiscordBot

Issue:
Announcer doesn't handle the case where the server has no text channels
**What were you trying to do?**
Using the core's announcer cog.

**What were you expecting to happen?**
Sending the message to all the server the bot is in

**What actually happened?**
Some servers get the message and some not. Even if the bot has admin perm.

**How can we reproduce this issue?**
By this screenshot, the issue may be the fact that one of the server the bot is in has no txt channel.
So the fix would be ignoring a server without any txt channels and send a message to the bot owner specifying what server has no txt channel so we can force the bot to quit it.

![287b728dc2301a7d862c09708cf4dd57](https://user-images.githubusercontent.com/47830440/87888095-c32a7c00-ca2a-11ea-9031-584e0dfe0d18.png)



Discussion:

Resolving patch:
diff --git a/redbot/cogs/admin/admin.py b/redbot/cogs/admin/admin.py
--- a/redbot/cogs/admin/admin.py
+++ b/redbot/cogs/admin/admin.py
@@ -1,3 +1,4 @@
+import asyncio
 import logging
 from typing import Tuple
 
@@ -72,15 +73,45 @@ class Admin(commands.Cog):
     def __init__(self):
         self.config = Config.get_conf(self, 8237492837454039, force_registration=True)
 
-        self.config.register_global(serverlocked=False)
+        self.config.register_global(serverlocked=False, schema_version=0)
 
         self.config.register_guild(
-            announce_ignore=False,
-            announce_channel=None,  # Integer ID
-            selfroles=[],  # List of integer ID's
+            announce_channel=None, selfroles=[],  # Integer ID  # List of integer ID's
         )
 
         self.__current_announcer = None
+        self._ready = asyncio.Event()
+        asyncio.create_task(self.handle_migrations())
+        # As this is a data migration, don't store this for cancelation.
+
+    async def cog_before_invoke(self):
+        await self._ready.wait()
+
+    async def handle_migrations(self):
+
+        lock = self.config.get_guilds_lock()
+        async with lock:
+            # This prevents the edge case of someone loading admin,
+            # unloading it, loading it again during a migration
+            current_schema = await self.config.schema_version()
+
+            if current_schema == 0:
+                await self.migrate_config_from_0_to_1()
+                await self.config.schema_version.set(1)
+
+        await self._ready.set()
+
+    async def migrate_config_from_0_to_1(self):
+
+        all_guilds = await self.config.all_guilds()
+
+        for guild_id, guild_data in all_guilds.items():
+            if guild_data.get("announce_ignore", False):
+                async with self.config.guild_from_id(guild_id).all(
+                    acquire_lock=False
+                ) as guild_config:
+                    guild_config.pop("announce_channel", None)
+                    guild_config.pop("announce_ignore", None)
 
     def cog_unload(self):
         try:
@@ -320,7 +351,7 @@ async def announceset(self, ctx):
     async def announceset_channel(self, ctx, *, channel: discord.TextChannel = None):
         """
         Change the channel where the bot will send announcements.
-        
+
         If channel is left blank it defaults to the current channel.
         """
         if channel is None:
@@ -330,21 +361,11 @@ async def announceset_channel(self, ctx, *, channel: discord.TextChannel = None)
             _("The announcement channel has been set to {channel.mention}").format(channel=channel)
         )
 
-    @announceset.command(name="ignore")
-    async def announceset_ignore(self, ctx):
-        """Toggle announcements being enabled this server."""
-        ignored = await self.config.guild(ctx.guild).announce_ignore()
-        await self.config.guild(ctx.guild).announce_ignore.set(not ignored)
-        if ignored:
-            await ctx.send(
-                _("The server {guild.name} will receive announcements.").format(guild=ctx.guild)
-            )
-        else:
-            await ctx.send(
-                _("The server {guild.name} will not receive announcements.").format(
-                    guild=ctx.guild
-                )
-            )
+    @announceset.command(name="clearchannel")
+    async def announceset_clear_channel(self, ctx):
+        """Unsets the channel for announcements."""
+        await self.config.guild(ctx.guild).announce_channel.clear()
+        await ctx.tick()
 
     async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
         """
diff --git a/redbot/cogs/admin/announcer.py b/redbot/cogs/admin/announcer.py
--- a/redbot/cogs/admin/announcer.py
+++ b/redbot/cogs/admin/announcer.py
@@ -1,4 +1,5 @@
 import asyncio
+from typing import Optional
 
 import discord
 from redbot.core import commands
@@ -38,20 +39,9 @@ def cancel(self):
         """
         self.active = False
 
-    async def _get_announce_channel(self, guild: discord.Guild) -> discord.TextChannel:
+    async def _get_announce_channel(self, guild: discord.Guild) -> Optional[discord.TextChannel]:
         channel_id = await self.config.guild(guild).announce_channel()
-        channel = None
-
-        if channel_id is not None:
-            channel = guild.get_channel(channel_id)
-
-        if channel is None:
-            channel = guild.system_channel
-
-        if channel is None:
-            channel = guild.text_channels[0]
-
-        return channel
+        return guild.get_channel(channel_id)
 
     async def announcer(self):
         guild_list = self.ctx.bot.guilds
@@ -60,15 +50,16 @@ async def announcer(self):
             if not self.active:
                 return
 
-            if await self.config.guild(g).announce_ignore():
-                continue
-
             channel = await self._get_announce_channel(g)
 
-            try:
-                await channel.send(self.message)
-            except discord.Forbidden:
-                failed.append(str(g.id))
+            if channel:
+                if channel.permissions_for(g.me).send_messages:
+                    try:
+                        await channel.send(self.message)
+                    except discord.Forbidden:
+                        failed.append(str(g.id))
+                else:
+                    failed.append(str(g.id))
 
         if failed:
             msg = (


Test patch: