We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bot creates multiple Reddit background tasks
Juanita noticed that the bot posted and pinned the overview of the weekly top posts of the Python subreddit multiple times:

![2019-05-06_22-08-41](https://user-images.githubusercontent.com/33516116/57252083-9f5c5f80-704b-11e9-9509-ce48ad654bc2.png)

The reason is that the `on_ready` event may fire multiple times (for instance, when the bot recovers a dropped connection), creating multiple background tasks:

![2019-05-06_22-05-26](https://user-images.githubusercontent.com/33516116/57252130-bdc25b00-704b-11e9-9c58-1cbc909c60d2.png)

The code causing the bug can be found in this on ready: https://github.com/python-discord/bot/blob/7a61e582ccb866776136d969c97d23907ca160e2/bot/cogs/reddit.py#L279

The fix should be straightforward: Keep a reference to the Background task as a class attribute and check if there's already a Task running like we do at other places as well.

Since this both hits the Discord API and the Reddit API, we should probably just fix this right away and not wait until the feature freeze is over. I'll put in a PR soon.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bot/cogs/reddit.py`
Content:
```
1 import asyncio
2 import logging
3 import random
4 import textwrap
5 from datetime import datetime, timedelta
6 
7 from discord import Colour, Embed, TextChannel
8 from discord.ext.commands import Bot, Context, group
9 
10 from bot.constants import Channels, ERROR_REPLIES, Reddit as RedditConfig, STAFF_ROLES
11 from bot.converters import Subreddit
12 from bot.decorators import with_role
13 from bot.pagination import LinePaginator
14 
15 log = logging.getLogger(__name__)
16 
17 
18 class Reddit:
19     """
20     Track subreddit posts and show detailed statistics about them.
21     """
22 
23     HEADERS = {"User-Agent": "Discord Bot: PythonDiscord (https://pythondiscord.com/)"}
24     URL = "https://www.reddit.com"
25 
26     def __init__(self, bot: Bot):
27         self.bot = bot
28 
29         self.reddit_channel = None
30 
31         self.prev_lengths = {}
32         self.last_ids = {}
33 
34     async def fetch_posts(self, route: str, *, amount: int = 25, params=None):
35         """
36         A helper method to fetch a certain amount of Reddit posts at a given route.
37         """
38 
39         # Reddit's JSON responses only provide 25 posts at most.
40         if not 25 >= amount > 0:
41             raise ValueError("Invalid amount of subreddit posts requested.")
42 
43         if params is None:
44             params = {}
45 
46         response = await self.bot.http_session.get(
47             url=f"{self.URL}/{route}.json",
48             headers=self.HEADERS,
49             params=params
50         )
51 
52         content = await response.json()
53         posts = content["data"]["children"]
54 
55         return posts[:amount]
56 
57     async def send_top_posts(self, channel: TextChannel, subreddit: Subreddit, content=None, time="all"):
58         """
59         Create an embed for the top posts, then send it in a given TextChannel.
60         """
61 
62         # Create the new spicy embed.
63         embed = Embed()
64         embed.description = ""
65 
66         # Get the posts
67         posts = await self.fetch_posts(
68             route=f"{subreddit}/top",
69             amount=5,
70             params={
71                 "t": time
72             }
73         )
74 
75         if not posts:
76             embed.title = random.choice(ERROR_REPLIES)
77             embed.colour = Colour.red()
78             embed.description = (
79                 "Sorry! We couldn't find any posts from that subreddit. "
80                 "If this problem persists, please let us know."
81             )
82 
83             return await channel.send(
84                 embed=embed
85             )
86 
87         for post in posts:
88             data = post["data"]
89 
90             text = data["selftext"]
91             if text:
92                 text = textwrap.shorten(text, width=128, placeholder="...")
93                 text += "\n"  # Add newline to separate embed info
94 
95             ups = data["ups"]
96             comments = data["num_comments"]
97             author = data["author"]
98 
99             title = textwrap.shorten(data["title"], width=64, placeholder="...")
100             link = self.URL + data["permalink"]
101 
102             embed.description += (
103                 f"[**{title}**]({link})\n"
104                 f"{text}"
105                 f"| {ups} upvotes | {comments} comments | u/{author} | {subreddit} |\n\n"
106             )
107 
108         embed.colour = Colour.blurple()
109 
110         return await channel.send(
111             content=content,
112             embed=embed
113         )
114 
115     async def poll_new_posts(self):
116         """
117         Periodically search for new subreddit posts.
118         """
119 
120         while True:
121             await asyncio.sleep(RedditConfig.request_delay)
122 
123             for subreddit in RedditConfig.subreddits:
124                 # Make a HEAD request to the subreddit
125                 head_response = await self.bot.http_session.head(
126                     url=f"{self.URL}/{subreddit}/new.rss",
127                     headers=self.HEADERS
128                 )
129 
130                 content_length = head_response.headers["content-length"]
131 
132                 # If the content is the same size as before, assume there's no new posts.
133                 if content_length == self.prev_lengths.get(subreddit, None):
134                     continue
135 
136                 self.prev_lengths[subreddit] = content_length
137 
138                 # Now we can actually fetch the new data
139                 posts = await self.fetch_posts(f"{subreddit}/new")
140                 new_posts = []
141 
142                 # Only show new posts if we've checked before.
143                 if subreddit in self.last_ids:
144                     for post in posts:
145                         data = post["data"]
146 
147                         # Convert the ID to an integer for easy comparison.
148                         int_id = int(data["id"], 36)
149 
150                         # If we've already seen this post, finish checking
151                         if int_id <= self.last_ids[subreddit]:
152                             break
153 
154                         embed_data = {
155                             "title": textwrap.shorten(data["title"], width=64, placeholder="..."),
156                             "text": textwrap.shorten(data["selftext"], width=128, placeholder="..."),
157                             "url": self.URL + data["permalink"],
158                             "author": data["author"]
159                         }
160 
161                         new_posts.append(embed_data)
162 
163                 self.last_ids[subreddit] = int(posts[0]["data"]["id"], 36)
164 
165                 # Send all of the new posts as spicy embeds
166                 for data in new_posts:
167                     embed = Embed()
168 
169                     embed.title = data["title"]
170                     embed.url = data["url"]
171                     embed.description = data["text"]
172                     embed.set_footer(text=f"Posted by u/{data['author']} in {subreddit}")
173                     embed.colour = Colour.blurple()
174 
175                     await self.reddit_channel.send(embed=embed)
176 
177                 log.trace(f"Sent {len(new_posts)} new {subreddit} posts to channel {self.reddit_channel.id}.")
178 
179     async def poll_top_weekly_posts(self):
180         """
181         Post a summary of the top posts every week.
182         """
183 
184         while True:
185             now = datetime.utcnow()
186 
187             # Calculate the amount of seconds until midnight next monday.
188             monday = now + timedelta(days=7 - now.weekday())
189             monday = monday.replace(hour=0, minute=0, second=0)
190             until_monday = (monday - now).total_seconds()
191 
192             await asyncio.sleep(until_monday)
193 
194             for subreddit in RedditConfig.subreddits:
195                 # Send and pin the new weekly posts.
196                 message = await self.send_top_posts(
197                     channel=self.reddit_channel,
198                     subreddit=subreddit,
199                     content=f"This week's top {subreddit} posts have arrived!",
200                     time="week"
201                 )
202 
203                 if subreddit.lower() == "r/python":
204                     # Remove the oldest pins so that only 5 remain at most.
205                     pins = await self.reddit_channel.pins()
206 
207                     while len(pins) >= 5:
208                         await pins[-1].unpin()
209                         del pins[-1]
210 
211                     await message.pin()
212 
213     @group(name="reddit", invoke_without_command=True)
214     async def reddit_group(self, ctx: Context):
215         """
216         View the top posts from various subreddits.
217         """
218 
219         await ctx.invoke(self.bot.get_command("help"), "reddit")
220 
221     @reddit_group.command(name="top")
222     async def top_command(self, ctx: Context, subreddit: Subreddit = "r/Python"):
223         """
224         Send the top posts of all time from a given subreddit.
225         """
226 
227         await self.send_top_posts(
228             channel=ctx.channel,
229             subreddit=subreddit,
230             content=f"Here are the top {subreddit} posts of all time!",
231             time="all"
232         )
233 
234     @reddit_group.command(name="daily")
235     async def daily_command(self, ctx: Context, subreddit: Subreddit = "r/Python"):
236         """
237         Send the top posts of today from a given subreddit.
238         """
239 
240         await self.send_top_posts(
241             channel=ctx.channel,
242             subreddit=subreddit,
243             content=f"Here are today's top {subreddit} posts!",
244             time="day"
245         )
246 
247     @reddit_group.command(name="weekly")
248     async def weekly_command(self, ctx: Context, subreddit: Subreddit = "r/Python"):
249         """
250         Send the top posts of this week from a given subreddit.
251         """
252 
253         await self.send_top_posts(
254             channel=ctx.channel,
255             subreddit=subreddit,
256             content=f"Here are this week's top {subreddit} posts!",
257             time="week"
258         )
259 
260     @with_role(*STAFF_ROLES)
261     @reddit_group.command(name="subreddits", aliases=("subs",))
262     async def subreddits_command(self, ctx: Context):
263         """
264         Send a paginated embed of all the subreddits we're relaying.
265         """
266 
267         embed = Embed()
268         embed.title = "Relayed subreddits."
269         embed.colour = Colour.blurple()
270 
271         await LinePaginator.paginate(
272             RedditConfig.subreddits,
273             ctx, embed,
274             footer_text="Use the reddit commands along with these to view their posts.",
275             empty=False,
276             max_lines=15
277         )
278 
279     async def on_ready(self):
280         self.reddit_channel = self.bot.get_channel(Channels.reddit)
281 
282         if self.reddit_channel is not None:
283             self.bot.loop.create_task(self.poll_new_posts())
284             self.bot.loop.create_task(self.poll_top_weekly_posts())
285         else:
286             log.warning("Couldn't locate a channel for subreddit relaying.")
287 
288 
289 def setup(bot):
290     bot.add_cog(Reddit(bot))
291     log.info("Cog loaded: Reddit")
292
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bot/cogs/reddit.py b/bot/cogs/reddit.py
--- a/bot/cogs/reddit.py
+++ b/bot/cogs/reddit.py
@@ -31,6 +31,9 @@
         self.prev_lengths = {}
         self.last_ids = {}
 
+        self.new_posts_task = None
+        self.top_weekly_posts_task = None
+
     async def fetch_posts(self, route: str, *, amount: int = 25, params=None):
         """
         A helper method to fetch a certain amount of Reddit posts at a given route.
@@ -280,8 +283,10 @@
         self.reddit_channel = self.bot.get_channel(Channels.reddit)
 
         if self.reddit_channel is not None:
-            self.bot.loop.create_task(self.poll_new_posts())
-            self.bot.loop.create_task(self.poll_top_weekly_posts())
+            if self.new_posts_task is None:
+                self.new_posts_task = self.bot.loop.create_task(self.poll_new_posts())
+            if self.top_weekly_posts_task is None:
+                self.top_weekly_posts_task = self.bot.loop.create_task(self.poll_top_weekly_posts())
         else:
             log.warning("Couldn't locate a channel for subreddit relaying.")

{"golden_diff": "diff --git a/bot/cogs/reddit.py b/bot/cogs/reddit.py\n--- a/bot/cogs/reddit.py\n+++ b/bot/cogs/reddit.py\n@@ -31,6 +31,9 @@\n         self.prev_lengths = {}\n         self.last_ids = {}\n \n+        self.new_posts_task = None\n+        self.top_weekly_posts_task = None\n+\n     async def fetch_posts(self, route: str, *, amount: int = 25, params=None):\n         \"\"\"\n         A helper method to fetch a certain amount of Reddit posts at a given route.\n@@ -280,8 +283,10 @@\n         self.reddit_channel = self.bot.get_channel(Channels.reddit)\n \n         if self.reddit_channel is not None:\n-            self.bot.loop.create_task(self.poll_new_posts())\n-            self.bot.loop.create_task(self.poll_top_weekly_posts())\n+            if self.new_posts_task is None:\n+                self.new_posts_task = self.bot.loop.create_task(self.poll_new_posts())\n+            if self.top_weekly_posts_task is None:\n+                self.top_weekly_posts_task = self.bot.loop.create_task(self.poll_top_weekly_posts())\n         else:\n             log.warning(\"Couldn't locate a channel for subreddit relaying.\")\n", "issue": "Bot creates multiple Reddit background tasks\nJuanita noticed that the bot posted and pinned the overview of the weekly top posts of the Python subreddit multiple times:\r\n\r\n![2019-05-06_22-08-41](https://user-images.githubusercontent.com/33516116/57252083-9f5c5f80-704b-11e9-9509-ce48ad654bc2.png)\r\n\r\nThe reason is that the `on_ready` event may fire multiple times (for instance, when the bot recovers a dropped connection), creating multiple background tasks:\r\n\r\n![2019-05-06_22-05-26](https://user-images.githubusercontent.com/33516116/57252130-bdc25b00-704b-11e9-9c58-1cbc909c60d2.png)\r\n\r\nThe code causing the bug can be found in this on ready: https://github.com/python-discord/bot/blob/7a61e582ccb866776136d969c97d23907ca160e2/bot/cogs/reddit.py#L279\r\n\r\nThe fix should be straightforward: Keep a reference to the Background task as a class attribute and check if there's already a Task running like we do at other places as well.\r\n\r\nSince this both hits the Discord API and the Reddit API, we should probably just fix this right away and not wait until the feature freeze is over. I'll put in a PR soon.\r\n\n", "before_files": [{"content": "import asyncio\nimport logging\nimport random\nimport textwrap\nfrom datetime import datetime, timedelta\n\nfrom discord import Colour, Embed, TextChannel\nfrom discord.ext.commands import Bot, Context, group\n\nfrom bot.constants import Channels, ERROR_REPLIES, Reddit as RedditConfig, STAFF_ROLES\nfrom bot.converters import Subreddit\nfrom bot.decorators import with_role\nfrom bot.pagination import LinePaginator\n\nlog = logging.getLogger(__name__)\n\n\nclass Reddit:\n    \"\"\"\n    Track subreddit posts and show detailed statistics about them.\n    \"\"\"\n\n    HEADERS = {\"User-Agent\": \"Discord Bot: PythonDiscord (https://pythondiscord.com/)\"}\n    URL = \"https://www.reddit.com\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n        self.reddit_channel = None\n\n        self.prev_lengths = {}\n        self.last_ids = {}\n\n    async def fetch_posts(self, route: str, *, amount: int = 25, params=None):\n        \"\"\"\n        A helper method to fetch a certain amount of Reddit posts at a given route.\n        \"\"\"\n\n        # Reddit's JSON responses only provide 25 posts at most.\n        if not 25 >= amount > 0:\n            raise ValueError(\"Invalid amount of subreddit posts requested.\")\n\n        if params is None:\n            params = {}\n\n        response = await self.bot.http_session.get(\n            url=f\"{self.URL}/{route}.json\",\n            headers=self.HEADERS,\n            params=params\n        )\n\n        content = await response.json()\n        posts = content[\"data\"][\"children\"]\n\n        return posts[:amount]\n\n    async def send_top_posts(self, channel: TextChannel, subreddit: Subreddit, content=None, time=\"all\"):\n        \"\"\"\n        Create an embed for the top posts, then send it in a given TextChannel.\n        \"\"\"\n\n        # Create the new spicy embed.\n        embed = Embed()\n        embed.description = \"\"\n\n        # Get the posts\n        posts = await self.fetch_posts(\n            route=f\"{subreddit}/top\",\n            amount=5,\n            params={\n                \"t\": time\n            }\n        )\n\n        if not posts:\n            embed.title = random.choice(ERROR_REPLIES)\n            embed.colour = Colour.red()\n            embed.description = (\n                \"Sorry! We couldn't find any posts from that subreddit. \"\n                \"If this problem persists, please let us know.\"\n            )\n\n            return await channel.send(\n                embed=embed\n            )\n\n        for post in posts:\n            data = post[\"data\"]\n\n            text = data[\"selftext\"]\n            if text:\n                text = textwrap.shorten(text, width=128, placeholder=\"...\")\n                text += \"\\n\"  # Add newline to separate embed info\n\n            ups = data[\"ups\"]\n            comments = data[\"num_comments\"]\n            author = data[\"author\"]\n\n            title = textwrap.shorten(data[\"title\"], width=64, placeholder=\"...\")\n            link = self.URL + data[\"permalink\"]\n\n            embed.description += (\n                f\"[**{title}**]({link})\\n\"\n                f\"{text}\"\n                f\"| {ups} upvotes | {comments} comments | u/{author} | {subreddit} |\\n\\n\"\n            )\n\n        embed.colour = Colour.blurple()\n\n        return await channel.send(\n            content=content,\n            embed=embed\n        )\n\n    async def poll_new_posts(self):\n        \"\"\"\n        Periodically search for new subreddit posts.\n        \"\"\"\n\n        while True:\n            await asyncio.sleep(RedditConfig.request_delay)\n\n            for subreddit in RedditConfig.subreddits:\n                # Make a HEAD request to the subreddit\n                head_response = await self.bot.http_session.head(\n                    url=f\"{self.URL}/{subreddit}/new.rss\",\n                    headers=self.HEADERS\n                )\n\n                content_length = head_response.headers[\"content-length\"]\n\n                # If the content is the same size as before, assume there's no new posts.\n                if content_length == self.prev_lengths.get(subreddit, None):\n                    continue\n\n                self.prev_lengths[subreddit] = content_length\n\n                # Now we can actually fetch the new data\n                posts = await self.fetch_posts(f\"{subreddit}/new\")\n                new_posts = []\n\n                # Only show new posts if we've checked before.\n                if subreddit in self.last_ids:\n                    for post in posts:\n                        data = post[\"data\"]\n\n                        # Convert the ID to an integer for easy comparison.\n                        int_id = int(data[\"id\"], 36)\n\n                        # If we've already seen this post, finish checking\n                        if int_id <= self.last_ids[subreddit]:\n                            break\n\n                        embed_data = {\n                            \"title\": textwrap.shorten(data[\"title\"], width=64, placeholder=\"...\"),\n                            \"text\": textwrap.shorten(data[\"selftext\"], width=128, placeholder=\"...\"),\n                            \"url\": self.URL + data[\"permalink\"],\n                            \"author\": data[\"author\"]\n                        }\n\n                        new_posts.append(embed_data)\n\n                self.last_ids[subreddit] = int(posts[0][\"data\"][\"id\"], 36)\n\n                # Send all of the new posts as spicy embeds\n                for data in new_posts:\n                    embed = Embed()\n\n                    embed.title = data[\"title\"]\n                    embed.url = data[\"url\"]\n                    embed.description = data[\"text\"]\n                    embed.set_footer(text=f\"Posted by u/{data['author']} in {subreddit}\")\n                    embed.colour = Colour.blurple()\n\n                    await self.reddit_channel.send(embed=embed)\n\n                log.trace(f\"Sent {len(new_posts)} new {subreddit} posts to channel {self.reddit_channel.id}.\")\n\n    async def poll_top_weekly_posts(self):\n        \"\"\"\n        Post a summary of the top posts every week.\n        \"\"\"\n\n        while True:\n            now = datetime.utcnow()\n\n            # Calculate the amount of seconds until midnight next monday.\n            monday = now + timedelta(days=7 - now.weekday())\n            monday = monday.replace(hour=0, minute=0, second=0)\n            until_monday = (monday - now).total_seconds()\n\n            await asyncio.sleep(until_monday)\n\n            for subreddit in RedditConfig.subreddits:\n                # Send and pin the new weekly posts.\n                message = await self.send_top_posts(\n                    channel=self.reddit_channel,\n                    subreddit=subreddit,\n                    content=f\"This week's top {subreddit} posts have arrived!\",\n                    time=\"week\"\n                )\n\n                if subreddit.lower() == \"r/python\":\n                    # Remove the oldest pins so that only 5 remain at most.\n                    pins = await self.reddit_channel.pins()\n\n                    while len(pins) >= 5:\n                        await pins[-1].unpin()\n                        del pins[-1]\n\n                    await message.pin()\n\n    @group(name=\"reddit\", invoke_without_command=True)\n    async def reddit_group(self, ctx: Context):\n        \"\"\"\n        View the top posts from various subreddits.\n        \"\"\"\n\n        await ctx.invoke(self.bot.get_command(\"help\"), \"reddit\")\n\n    @reddit_group.command(name=\"top\")\n    async def top_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of all time from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are the top {subreddit} posts of all time!\",\n            time=\"all\"\n        )\n\n    @reddit_group.command(name=\"daily\")\n    async def daily_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of today from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are today's top {subreddit} posts!\",\n            time=\"day\"\n        )\n\n    @reddit_group.command(name=\"weekly\")\n    async def weekly_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of this week from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are this week's top {subreddit} posts!\",\n            time=\"week\"\n        )\n\n    @with_role(*STAFF_ROLES)\n    @reddit_group.command(name=\"subreddits\", aliases=(\"subs\",))\n    async def subreddits_command(self, ctx: Context):\n        \"\"\"\n        Send a paginated embed of all the subreddits we're relaying.\n        \"\"\"\n\n        embed = Embed()\n        embed.title = \"Relayed subreddits.\"\n        embed.colour = Colour.blurple()\n\n        await LinePaginator.paginate(\n            RedditConfig.subreddits,\n            ctx, embed,\n            footer_text=\"Use the reddit commands along with these to view their posts.\",\n            empty=False,\n            max_lines=15\n        )\n\n    async def on_ready(self):\n        self.reddit_channel = self.bot.get_channel(Channels.reddit)\n\n        if self.reddit_channel is not None:\n            self.bot.loop.create_task(self.poll_new_posts())\n            self.bot.loop.create_task(self.poll_top_weekly_posts())\n        else:\n            log.warning(\"Couldn't locate a channel for subreddit relaying.\")\n\n\ndef setup(bot):\n    bot.add_cog(Reddit(bot))\n    log.info(\"Cog loaded: Reddit\")\n", "path": "bot/cogs/reddit.py"}], "after_files": [{"content": "import asyncio\nimport logging\nimport random\nimport textwrap\nfrom datetime import datetime, timedelta\n\nfrom discord import Colour, Embed, TextChannel\nfrom discord.ext.commands import Bot, Context, group\n\nfrom bot.constants import Channels, ERROR_REPLIES, Reddit as RedditConfig, STAFF_ROLES\nfrom bot.converters import Subreddit\nfrom bot.decorators import with_role\nfrom bot.pagination import LinePaginator\n\nlog = logging.getLogger(__name__)\n\n\nclass Reddit:\n    \"\"\"\n    Track subreddit posts and show detailed statistics about them.\n    \"\"\"\n\n    HEADERS = {\"User-Agent\": \"Discord Bot: PythonDiscord (https://pythondiscord.com/)\"}\n    URL = \"https://www.reddit.com\"\n\n    def __init__(self, bot: Bot):\n        self.bot = bot\n\n        self.reddit_channel = None\n\n        self.prev_lengths = {}\n        self.last_ids = {}\n\n        self.new_posts_task = None\n        self.top_weekly_posts_task = None\n\n    async def fetch_posts(self, route: str, *, amount: int = 25, params=None):\n        \"\"\"\n        A helper method to fetch a certain amount of Reddit posts at a given route.\n        \"\"\"\n\n        # Reddit's JSON responses only provide 25 posts at most.\n        if not 25 >= amount > 0:\n            raise ValueError(\"Invalid amount of subreddit posts requested.\")\n\n        if params is None:\n            params = {}\n\n        response = await self.bot.http_session.get(\n            url=f\"{self.URL}/{route}.json\",\n            headers=self.HEADERS,\n            params=params\n        )\n\n        content = await response.json()\n        posts = content[\"data\"][\"children\"]\n\n        return posts[:amount]\n\n    async def send_top_posts(self, channel: TextChannel, subreddit: Subreddit, content=None, time=\"all\"):\n        \"\"\"\n        Create an embed for the top posts, then send it in a given TextChannel.\n        \"\"\"\n\n        # Create the new spicy embed.\n        embed = Embed()\n        embed.description = \"\"\n\n        # Get the posts\n        posts = await self.fetch_posts(\n            route=f\"{subreddit}/top\",\n            amount=5,\n            params={\n                \"t\": time\n            }\n        )\n\n        if not posts:\n            embed.title = random.choice(ERROR_REPLIES)\n            embed.colour = Colour.red()\n            embed.description = (\n                \"Sorry! We couldn't find any posts from that subreddit. \"\n                \"If this problem persists, please let us know.\"\n            )\n\n            return await channel.send(\n                embed=embed\n            )\n\n        for post in posts:\n            data = post[\"data\"]\n\n            text = data[\"selftext\"]\n            if text:\n                text = textwrap.shorten(text, width=128, placeholder=\"...\")\n                text += \"\\n\"  # Add newline to separate embed info\n\n            ups = data[\"ups\"]\n            comments = data[\"num_comments\"]\n            author = data[\"author\"]\n\n            title = textwrap.shorten(data[\"title\"], width=64, placeholder=\"...\")\n            link = self.URL + data[\"permalink\"]\n\n            embed.description += (\n                f\"[**{title}**]({link})\\n\"\n                f\"{text}\"\n                f\"| {ups} upvotes | {comments} comments | u/{author} | {subreddit} |\\n\\n\"\n            )\n\n        embed.colour = Colour.blurple()\n\n        return await channel.send(\n            content=content,\n            embed=embed\n        )\n\n    async def poll_new_posts(self):\n        \"\"\"\n        Periodically search for new subreddit posts.\n        \"\"\"\n\n        while True:\n            await asyncio.sleep(RedditConfig.request_delay)\n\n            for subreddit in RedditConfig.subreddits:\n                # Make a HEAD request to the subreddit\n                head_response = await self.bot.http_session.head(\n                    url=f\"{self.URL}/{subreddit}/new.rss\",\n                    headers=self.HEADERS\n                )\n\n                content_length = head_response.headers[\"content-length\"]\n\n                # If the content is the same size as before, assume there's no new posts.\n                if content_length == self.prev_lengths.get(subreddit, None):\n                    continue\n\n                self.prev_lengths[subreddit] = content_length\n\n                # Now we can actually fetch the new data\n                posts = await self.fetch_posts(f\"{subreddit}/new\")\n                new_posts = []\n\n                # Only show new posts if we've checked before.\n                if subreddit in self.last_ids:\n                    for post in posts:\n                        data = post[\"data\"]\n\n                        # Convert the ID to an integer for easy comparison.\n                        int_id = int(data[\"id\"], 36)\n\n                        # If we've already seen this post, finish checking\n                        if int_id <= self.last_ids[subreddit]:\n                            break\n\n                        embed_data = {\n                            \"title\": textwrap.shorten(data[\"title\"], width=64, placeholder=\"...\"),\n                            \"text\": textwrap.shorten(data[\"selftext\"], width=128, placeholder=\"...\"),\n                            \"url\": self.URL + data[\"permalink\"],\n                            \"author\": data[\"author\"]\n                        }\n\n                        new_posts.append(embed_data)\n\n                self.last_ids[subreddit] = int(posts[0][\"data\"][\"id\"], 36)\n\n                # Send all of the new posts as spicy embeds\n                for data in new_posts:\n                    embed = Embed()\n\n                    embed.title = data[\"title\"]\n                    embed.url = data[\"url\"]\n                    embed.description = data[\"text\"]\n                    embed.set_footer(text=f\"Posted by u/{data['author']} in {subreddit}\")\n                    embed.colour = Colour.blurple()\n\n                    await self.reddit_channel.send(embed=embed)\n\n                log.trace(f\"Sent {len(new_posts)} new {subreddit} posts to channel {self.reddit_channel.id}.\")\n\n    async def poll_top_weekly_posts(self):\n        \"\"\"\n        Post a summary of the top posts every week.\n        \"\"\"\n\n        while True:\n            now = datetime.utcnow()\n\n            # Calculate the amount of seconds until midnight next monday.\n            monday = now + timedelta(days=7 - now.weekday())\n            monday = monday.replace(hour=0, minute=0, second=0)\n            until_monday = (monday - now).total_seconds()\n\n            await asyncio.sleep(until_monday)\n\n            for subreddit in RedditConfig.subreddits:\n                # Send and pin the new weekly posts.\n                message = await self.send_top_posts(\n                    channel=self.reddit_channel,\n                    subreddit=subreddit,\n                    content=f\"This week's top {subreddit} posts have arrived!\",\n                    time=\"week\"\n                )\n\n                if subreddit.lower() == \"r/python\":\n                    # Remove the oldest pins so that only 5 remain at most.\n                    pins = await self.reddit_channel.pins()\n\n                    while len(pins) >= 5:\n                        await pins[-1].unpin()\n                        del pins[-1]\n\n                    await message.pin()\n\n    @group(name=\"reddit\", invoke_without_command=True)\n    async def reddit_group(self, ctx: Context):\n        \"\"\"\n        View the top posts from various subreddits.\n        \"\"\"\n\n        await ctx.invoke(self.bot.get_command(\"help\"), \"reddit\")\n\n    @reddit_group.command(name=\"top\")\n    async def top_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of all time from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are the top {subreddit} posts of all time!\",\n            time=\"all\"\n        )\n\n    @reddit_group.command(name=\"daily\")\n    async def daily_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of today from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are today's top {subreddit} posts!\",\n            time=\"day\"\n        )\n\n    @reddit_group.command(name=\"weekly\")\n    async def weekly_command(self, ctx: Context, subreddit: Subreddit = \"r/Python\"):\n        \"\"\"\n        Send the top posts of this week from a given subreddit.\n        \"\"\"\n\n        await self.send_top_posts(\n            channel=ctx.channel,\n            subreddit=subreddit,\n            content=f\"Here are this week's top {subreddit} posts!\",\n            time=\"week\"\n        )\n\n    @with_role(*STAFF_ROLES)\n    @reddit_group.command(name=\"subreddits\", aliases=(\"subs\",))\n    async def subreddits_command(self, ctx: Context):\n        \"\"\"\n        Send a paginated embed of all the subreddits we're relaying.\n        \"\"\"\n\n        embed = Embed()\n        embed.title = \"Relayed subreddits.\"\n        embed.colour = Colour.blurple()\n\n        await LinePaginator.paginate(\n            RedditConfig.subreddits,\n            ctx, embed,\n            footer_text=\"Use the reddit commands along with these to view their posts.\",\n            empty=False,\n            max_lines=15\n        )\n\n    async def on_ready(self):\n        self.reddit_channel = self.bot.get_channel(Channels.reddit)\n\n        if self.reddit_channel is not None:\n            if self.new_posts_task is None:\n                self.new_posts_task = self.bot.loop.create_task(self.poll_new_posts())\n            if self.top_weekly_posts_task is None:\n                self.top_weekly_posts_task = self.bot.loop.create_task(self.poll_top_weekly_posts())\n        else:\n            log.warning(\"Couldn't locate a channel for subreddit relaying.\")\n\n\ndef setup(bot):\n    bot.add_cog(Reddit(bot))\n    log.info(\"Cog loaded: Reddit\")\n", "path": "bot/cogs/reddit.py"}]}