From f5766e03909fa370923c3c747769e222b8ae904d Mon Sep 17 00:00:00 2001 From: Corban-Lee Jones Date: Thu, 13 Jun 2024 23:13:29 +0100 Subject: [PATCH] optimising existing code --- src/extensions/tasks.py | 95 ++++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/src/extensions/tasks.py b/src/extensions/tasks.py index cd0aa5d..f82ef1e 100644 --- a/src/extensions/tasks.py +++ b/src/extensions/tasks.py @@ -11,7 +11,7 @@ from os import getenv from time import process_time import aiohttp -from discord import TextChannel +from discord import TextChannel, Embed from discord import app_commands from discord.ext import commands, tasks from discord.errors import Forbidden @@ -118,14 +118,18 @@ class TaskCog(commands.Cog): log.error(error) - async def process_subscription(self, api, session, sub: Subscription): + async def process_subscription(self, api: API, session: aiohttp.ClientSession, sub: Subscription): + """ + Process a given Subscription. + """ + log.debug("processing subscription '%s' '%s' for '%s'", sub.id, sub.name, sub.guild_id) if not sub.active: log.debug("skipping sub because it's active flag is 'False'") return - channels = [self.bot.get_channel(subchannel.channel_id) for subchannel in await sub.get_channels(api)] + channels: list[TextChannel] = [self.bot.get_channel(subchannel.channel_id) for subchannel in await sub.get_channels(api)] if not channels: log.warning("No channels to send this to") return @@ -142,10 +146,74 @@ class TaskCog(commands.Cog): if not articles: log.debug("No articles found") - for article in articles: - await self.process_article(api, session, sub.id, filters, channels, article) + embeds = await self.get_articles_as_embeds(api, session, sub.id, filters, articles) + await self.send_embeds_in_chunks(embeds, channels) + + async def get_articles_as_embeds( + self, + api: API, + session: aiohttp.ClientSession, + sub_id: int, + filters: list[dict], + articles: list[Article] + ) -> list[Embed]: + """ + Process articles and return their respective embeds. + """ + + embeds = [] + for article in articles: + embed = await self.process_article(api, session, sub_id, filters, article) + if embed: + embeds.append(embed) + + return embeds + + async def send_embeds_in_chunks(self, embeds: list[Embed], channels: list[TextChannel], embed_limit=10): + """ + Send embeds to a list of `TextChannel` in chunks of `embed_limit` size. + """ + + log.debug("about to send %s embeds") + + for i in range(0, len(embeds), embed_limit): + embeds_chunk = embeds[i:i + embed_limit] + + log.debug("sending chunk of %s embeds", len(embeds_chunk)) + + for channel in channels: + await self.try_send_embeds(embeds, channel) + + async def try_send_embeds(self, embeds: list[Embed], channel: TextChannel): + """ + Attempt to send embeds to a given `TextChannel`. Gracefully handles errors. + """ + + try: + await channel.send(embeds=embeds) + + except Forbidden: + log.debug( + "Forbidden from sending embed to channel '%s', guild '%s'", + channel.id, channel.guild.id + ) + + except Exception as exc: + log.error(exc) + + async def process_article( + self, + api: API, + session: aiohttp.ClientSession, + sub_id: int, + filters: list[dict], + article: Article + ) -> Embed | None: + """ + Process a given Article. + Returns an Embed representing the given Article. + """ - async def process_article(self, api, session, sub_id: int, filters: list[dict], channels: list[SubChannel], article: Article): log.debug("processing article '%s' '%s'", article.guid, article.title) blocked = any(self.filter_article(_filter, article) for _filter in filters) @@ -160,22 +228,17 @@ class TaskCog(commands.Cog): blocked=blocked ) log.debug("successfully tracked %s", article.guid) - except aiohttp.ClientResponseError as error: - log.error(error) + except aiohttp.ClientResponseError as error: if error.status == 409: log.debug("It looks like this article already exists, skipping") + else: + log.error(error) return - if blocked: - return - - log.debug("attempting to send embed to %s channel(s)", len(channels)) - - embed = await article.to_embed(session) - for channel in channels: - await channel.send(embed=embed) + if not blocked: + return await article.to_embed(session) def filter_article(self, _filter: dict, article: Article) -> bool: """