Repository: python-discord/bot

Issue:
WatchChannels intermittently stop working
You've probably noticed that the watch channels (bigbrother & talent-pool) sometimes stop working until the cog is reloaded. After searching the logs, I've found this unretrieved exception:

```
00077298 | Apr 07 13:11:27 pd.beardfist.com Bot: |                        asyncio |    ERROR | Task exception was never retrieved
00077299 | future: <Task finished coro=<BigBrother.consume_messages() done, defined at /bot/bot/cogs/bigbrother.py:174> exception=Forbidden('Forbidden (status code: 403): cannot retrieve attachment',)>
00077300 | Traceback (most recent call last):
00077301 |   File "/bot/bot/cogs/bigbrother.py", line 194, in consume_messages
00077302 |     await self.log_message(msg, channel)
00077303 |   File "/bot/bot/cogs/bigbrother.py", line 288, in log_message
00077304 |     await messages.send_attachments(message, destination)
00077305 |   File "/bot/bot/utils/messages.py", line 99, in send_attachments
00077306 |     await attachment.save(file)
00077307 |   File "/bot/.venv/src/discord-py/discord/message.py", line 102, in save
00077308 |     data = await self._http.get_attachment(self.url)
00077309 |   File "/bot/.venv/src/discord-py/discord/http.py", line 225, in get_attachment
00077310 |     raise Forbidden(resp, 'cannot retrieve attachment')
00077311 | discord.errors.Forbidden: Forbidden (status code: 403): cannot retrieve attachment
```

My guess is that this is the root of the problem, since it stops the `consume_messages()` Task. 

This should be fixed in the rewrite of the WatchChannels that's planned for the Django branch of the bot. See #317.


Discussion:
This seems like something we shouldn’t put off fixing. It’s not a new feature and we shouldn’t have to regularly restart the cog to keep it functioning.
That's true. I'll put in a fix for it asap.

Resolving patch:
diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py
--- a/bot/cogs/bigbrother.py
+++ b/bot/cogs/bigbrother.py
@@ -6,7 +6,7 @@
 from typing import List, NamedTuple, Optional, Union
 
 from aiohttp import ClientError
-from discord import Color, Embed, Guild, Member, Message, TextChannel, User
+from discord import Color, Embed, Guild, Member, Message, TextChannel, User, errors
 from discord.ext.commands import Bot, Context, command, group
 
 from bot.constants import (
@@ -43,6 +43,7 @@ def __init__(self, bot: Bot):
         self.channel_queues = defaultdict(lambda: defaultdict(deque))  # { user_id: { channel_id: queue(messages) }
         self.last_log = [None, None, 0]  # [user_id, channel_id, message_count]
         self.consuming = False
+        self.consume_task = None
         self.infraction_watch_prefix = "bb watch: "  # Please do not change or we won't be able to find old reasons
         self.nomination_prefix = "Helper nomination: "
 
@@ -166,7 +167,19 @@ async def on_message(self, msg: Message):
 
         if msg.author.id in self.watched_users:
             if not self.consuming:
-                self.bot.loop.create_task(self.consume_messages())
+                self.consume_task = self.bot.loop.create_task(self.consume_messages())
+
+            if self.consuming and self.consume_task.done():
+                # This should never happen, so something went wrong
+
+                log.error("The consume_task has finished, but did not reset the self.consuming boolean")
+                e = self.consume_task.exception()
+                if e:
+                    log.exception("The Exception for the Task:", exc_info=e)
+                else:
+                    log.error("However, an Exception was not found.")
+
+                self.consume_task = self.bot.loop.create_task(self.consume_messages())
 
             log.trace(f"Received message: {msg.content} ({len(msg.attachments)} attachments)")
             self.channel_queues[msg.author.id][msg.channel.id].append(msg)
@@ -195,7 +208,7 @@ async def consume_messages(self):
 
         if self.channel_queues:
             log.trace("Queue not empty; continue consumption.")
-            self.bot.loop.create_task(self.consume_messages())
+            self.consume_task = self.bot.loop.create_task(self.consume_messages())
         else:
             log.trace("Done consuming messages.")
             self.consuming = False
@@ -285,7 +298,14 @@ async def log_message(message: Message, destination: TextChannel):
 
             await destination.send(content)
 
-        await messages.send_attachments(message, destination)
+        try:
+            await messages.send_attachments(message, destination)
+        except (errors.Forbidden, errors.NotFound):
+            e = Embed(
+                description=":x: **This message contained an attachment, but it could not be retrieved**",
+                color=Color.red()
+            )
+            await destination.send(embed=e)
 
     async def _watch_user(self, ctx: Context, user: User, reason: str, channel_id: int):
         post_data = {


Test patch: