From 8c2871f89846f2c34f52061323dc7503855266ea Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Fri, 18 Oct 2019 10:39:27 +0700 Subject: Make it easier for user to search for tags #### Closes #231 Applying the algorithm for `Needles and Haystack` to find and match tag in tags, for example: ![Example](https://cdn.discordapp.com/attachments/634243438459486219/634592981915140107/unknown.png) This only applies to searching tag_name with more than 3 in length, and at least 80% of its letters are found, from left to right. There are 3 levels of checking, stop at first found: - Check if exact name ( case insensitive ) O(1) getting from a dictionary Dict[str, Tag] - Check for all tags that has 100% matching via algorithm - Check for all tags that has >= 80% matching If there are more than one hit, it will be shown as suggestions: ![Suggestions](https://cdn.discordapp.com/attachments/634243438459486219/634595369531211778/unknown.png) In order to avoid api being called multiple times, I've implemented a cache to only refresh itself when the is a gap of more than 5 minutes from the last api call to get all tags. Editing / Adding / Deleting tags will also modify the cache directly. ##### What about other solution like fuzzywuzzy? fuzzywuzzy was considered for using, but from testing, it was giving much lower scores than expected: Code used to test: ```py from fuzzywuzzy import fuzz def _fuzzy_search(search: str, target: str) -> bool: found = 0 index = 0 _search = search.lower().replace(' ', '') _target = target.lower().replace(' ', '') for letter in _search: index = _target.find(letter, index) if index == -1: break found += index > 0 # return found / len(_search) * 100 return ( found / len(_search) * 100, fuzz.ratio(search, target), fuzz.partial_ratio(search, target) ) tests = ( 'this-is-gonna-be-fun', 'this-too-will-be-fun' ) for test in tests: print(test, '->', _fuzzy_search('this too fun', test)) ``` Result from test: ```py this-is-gonna-be-fun -> (30.0, 50, 50) this-too-will-be-fun -> (90.0, 62, 58) ``` --- bot/cogs/tags.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index cd70e783a..1aea97b37 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -9,7 +9,6 @@ from bot.converters import TagContentConverter, TagNameConverter from bot.decorators import with_role from bot.pagination import LinePaginator - log = logging.getLogger(__name__) TEST_CHANNELS = ( @@ -26,6 +25,44 @@ class Tags(Cog): self.bot = bot self.tag_cooldowns = {} + self._cache = {} + self._last_fetch = None + + async def _get_tags(self, is_forced: bool = False) -> None: + """Getting all tags.""" + # Refresh only when there's a more than 5m gap from last call. + if is_forced or not self._last_fetch or time.time() - self._last_fetch > 5 * 60: + tags = await self.bot.api_client.get('bot/tags') + self._cache = {tag['title'].lower(): tag for tag in tags} + + @staticmethod + def _fuzzy_search(search: str, target: str) -> bool: + found = 0 + index = 0 + _search = search.lower().replace(' ', '') + _target = target.lower().replace(' ', '') + for letter in _search: + index = _target.find(letter, index) + if index == -1: + break + found += index > 0 + return found / len(_search) * 100 + + def _get_suggestions(self, tag_name: str, score: int) -> list: + return sorted( + (tag for tag in self._cache.values() if Tags._fuzzy_search(tag_name, tag['title']) >= score), + key=lambda tag: Tags._fuzzy_search(tag_name, tag['title']), + reverse=True + ) + + async def _get_tag(self, tag_name: str) -> list: + """Get a specific tag.""" + await self._get_tags() + found = [self._cache.get(tag_name.lower(), None)] + if not found[0]: + return self._get_suggestions(tag_name, 100) or self._get_suggestions(tag_name, 80) + return found + @group(name='tags', aliases=('tag', 't'), invoke_without_command=True) async def tags_group(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None: """Show all known tags, a single tag, or run a subcommand.""" @@ -59,17 +96,29 @@ class Tags(Cog): f"Cooldown ends in {time_left:.1f} seconds.") return + await self._get_tags() + if tag_name is not None: - tag = await self.bot.api_client.get(f'bot/tags/{tag_name}') - if ctx.channel.id not in TEST_CHANNELS: - self.tag_cooldowns[tag_name] = { - "time": time.time(), - "channel": ctx.channel.id - } - await ctx.send(embed=Embed.from_dict(tag['embed'])) + # tag = await self.bot.api_client.get(f'bot/tags/{tag_name}') + founds = await self._get_tag(tag_name) + + if len(founds) == 1: + tag = founds[0] + if ctx.channel.id not in TEST_CHANNELS: + self.tag_cooldowns[tag_name] = { + "time": time.time(), + "channel": ctx.channel.id + } + await ctx.send(embed=Embed.from_dict(tag['embed'])) + elif founds and len(tag_name) >= 3: + await ctx.send(embed=Embed( + title='Did you mean ...', + description='\n'.join(tag['title'] for tag in founds[:10]) + )) else: - tags = await self.bot.api_client.get('bot/tags') + # tags = await self.bot.api_client.get('bot/tags') + tags = self._cache.values() if not tags: await ctx.send(embed=Embed( description="**There are no tags in the database!**", @@ -105,6 +154,7 @@ class Tags(Cog): } await self.bot.api_client.post('bot/tags', json=body) + self._cache[tag_name.lower()] = await self.bot.api_client.get(f'bot/tags/{tag_name}') log.debug(f"{ctx.author} successfully added the following tag to our database: \n" f"tag_name: {tag_name}\n" @@ -134,6 +184,7 @@ class Tags(Cog): } await self.bot.api_client.patch(f'bot/tags/{tag_name}', json=body) + self._cache[tag_name.lower()] = await self.bot.api_client.get(f'bot/tags/{tag_name}') log.debug(f"{ctx.author} successfully edited the following tag in our database: \n" f"tag_name: {tag_name}\n" @@ -150,6 +201,7 @@ class Tags(Cog): async def delete_command(self, ctx: Context, *, tag_name: TagNameConverter) -> None: """Remove a tag from the database.""" await self.bot.api_client.delete(f'bot/tags/{tag_name}') + self._cache.pop(tag_name.lower(), None) log.debug(f"{ctx.author} successfully deleted the tag called '{tag_name}'") await ctx.send(embed=Embed( -- cgit v1.2.3 From 4e87b4e9c3eee762c061b8d1c17d57f172e4dd43 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 27 Oct 2019 10:56:11 +0800 Subject: Use trashcan emoji for message deletion --- bot/constants.py | 2 ++ bot/utils/messages.py | 6 +++++- config-default.yml | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bot/constants.py b/bot/constants.py index f341fb499..4737ce6a3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -254,6 +254,8 @@ class Emojis(metaclass=YAMLGetter): status_idle: str status_dnd: str + trashcan: str + bullet: str new: str pencil: str diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 549b33ca6..fe0b6b29f 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -15,7 +15,7 @@ MAX_SIZE = 1024 * 1024 * 8 # 8 Mebibytes async def wait_for_deletion( message: Message, user_ids: Sequence[Snowflake], - deletion_emojis: Sequence[str] = (Emojis.cross_mark,), + deletion_emojis: Sequence[str] = None, timeout: float = 60 * 5, attach_emojis: bool = True, client: Optional[Client] = None @@ -34,6 +34,10 @@ async def wait_for_deletion( bot = client or message.guild.me + if deletion_emojis is None: + default_emoji = bot.get_emoji(int(Emojis.trashcan)) or Emojis.cross_mark + deletion_emojis = (default_emoji,) + if attach_emojis: for emoji in deletion_emojis: await message.add_reaction(emoji) diff --git a/config-default.yml b/config-default.yml index 23dcbd44c..16842534d 100644 --- a/config-default.yml +++ b/config-default.yml @@ -32,6 +32,8 @@ style: status_dnd: "<:status_dnd:470326272082313216>" status_offline: "<:status_offline:470326266537705472>" + trashcan: "637136429717389331" + bullet: "\u2022" pencil: "\u270F" new: "\U0001F195" -- cgit v1.2.3 From 30ba84eecd7fc10c96a739dc53d3454de5d8fd6a Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 27 Oct 2019 17:24:21 +0800 Subject: Differentiate clear and delete emoji in help cog --- bot/cogs/help.py | 13 ++++++++++--- bot/utils/messages.py | 8 ++------ config-default.yml | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 9607dbd8d..63b4e89b8 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -10,20 +10,22 @@ from discord.ext.commands import Bot, CheckFailure, Cog as DiscordCog, Command, from fuzzywuzzy import fuzz, process from bot import constants -from bot.constants import Channels, STAFF_ROLES +from bot.constants import Channels, Emojis, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import ( - DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, + DELETE_EMOJI as CLEAR_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, ) +DELETE_EMOJI = Emojis.trashcan REACTIONS = { FIRST_EMOJI: 'first', LEFT_EMOJI: 'back', RIGHT_EMOJI: 'next', LAST_EMOJI: 'end', - DELETE_EMOJI: 'stop' + CLEAR_EMOJI: 'clear', + DELETE_EMOJI: 'stop', } Cog = namedtuple('Cog', ['name', 'description', 'commands']) @@ -496,6 +498,11 @@ class HelpSession: if not self.is_last_page: await self.update_page(len(self._pages)-1) + async def do_clear(self) -> None: + """Event that is called when the user clears the emojis from the pagination.""" + await self.message.clear_reactions() + await self.message.add_reaction(DELETE_EMOJI) + async def do_stop(self) -> None: """Event that is called when the user requests to stop the help session.""" await self.message.delete() diff --git a/bot/utils/messages.py b/bot/utils/messages.py index fe0b6b29f..654d71797 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -15,7 +15,7 @@ MAX_SIZE = 1024 * 1024 * 8 # 8 Mebibytes async def wait_for_deletion( message: Message, user_ids: Sequence[Snowflake], - deletion_emojis: Sequence[str] = None, + deletion_emojis: Sequence[str] = (Emojis.trashcan,), timeout: float = 60 * 5, attach_emojis: bool = True, client: Optional[Client] = None @@ -34,10 +34,6 @@ async def wait_for_deletion( bot = client or message.guild.me - if deletion_emojis is None: - default_emoji = bot.get_emoji(int(Emojis.trashcan)) or Emojis.cross_mark - deletion_emojis = (default_emoji,) - if attach_emojis: for emoji in deletion_emojis: await message.add_reaction(emoji) @@ -46,7 +42,7 @@ async def wait_for_deletion( """Check that the deletion emoji is reacted by the approprite user.""" return ( reaction.message.id == message.id - and reaction.emoji in deletion_emojis + and str(reaction.emoji) in deletion_emojis and user.id in user_ids ) diff --git a/config-default.yml b/config-default.yml index 16842534d..696ef8a7e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -32,7 +32,7 @@ style: status_dnd: "<:status_dnd:470326272082313216>" status_offline: "<:status_offline:470326266537705472>" - trashcan: "637136429717389331" + trashcan: "<:trashcan:637136429717389331>" bullet: "\u2022" pencil: "\u270F" -- cgit v1.2.3 From 56696b3b1858ad27dc7f3dce2898c7a6eb151f43 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 27 Oct 2019 17:26:27 +0800 Subject: Remove dev-test limit for filtering debugging --- bot/cogs/filtering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 1d1d74e74..11c5d7223 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -136,9 +136,9 @@ class Filtering(Cog): and not msg.author.bot # Author not a bot ) - # If we're running the bot locally, ignore role whitelist and only listen to #dev-test + # If we're running the bot locally, ignore role whitelist if DEBUG_MODE: - filter_message = not msg.author.bot and msg.channel.id == Channels.devtest + filter_message = not msg.author.bot # If none of the above, we can start filtering. if filter_message: -- cgit v1.2.3 From af0e5329a5202c52cd94f86078c4a03119f7e324 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 28 Oct 2019 02:55:43 +0800 Subject: Revert "Remove dev-test limit for filtering debugging" This reverts commit 56696b3b1858ad27dc7f3dce2898c7a6eb151f43. --- bot/cogs/filtering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 11c5d7223..1d1d74e74 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -136,9 +136,9 @@ class Filtering(Cog): and not msg.author.bot # Author not a bot ) - # If we're running the bot locally, ignore role whitelist + # If we're running the bot locally, ignore role whitelist and only listen to #dev-test if DEBUG_MODE: - filter_message = not msg.author.bot + filter_message = not msg.author.bot and msg.channel.id == Channels.devtest # If none of the above, we can start filtering. if filter_message: -- cgit v1.2.3 From a2210247a4ba9a2903f9e21bb637bd12603b486e Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 28 Oct 2019 10:47:10 +0800 Subject: Add delete emoji to pagination --- bot/cogs/help.py | 3 +-- bot/pagination.py | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 63b4e89b8..f5538ce5e 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -13,7 +13,7 @@ from bot import constants from bot.constants import Channels, Emojis, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import ( - DELETE_EMOJI as CLEAR_EMOJI, FIRST_EMOJI, LAST_EMOJI, + CLEAR_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, ) @@ -501,7 +501,6 @@ class HelpSession: async def do_clear(self) -> None: """Event that is called when the user clears the emojis from the pagination.""" await self.message.clear_reactions() - await self.message.add_reaction(DELETE_EMOJI) async def do_stop(self) -> None: """Event that is called when the user requests to stop the help session.""" diff --git a/bot/pagination.py b/bot/pagination.py index 76082f459..11f7c77fe 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -6,13 +6,16 @@ from discord import Embed, Member, Message, Reaction from discord.abc import User from discord.ext.commands import Context, Paginator +from bot import constants + FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] -DELETE_EMOJI = "\u274c" # [:x:] +CLEAR_EMOJI = "\u274c" # [:x:] +DELETE_EMOJI = constants.Emojis.trashcan # [:trashcan:] -PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI] +PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, CLEAR_EMOJI, DELETE_EMOJI] log = logging.getLogger(__name__) @@ -131,7 +134,7 @@ class LinePaginator(Paginator): # Reaction is on this message reaction_.message.id == message.id, # Reaction is one of the pagination emotes - reaction_.emoji in PAGINATION_EMOJI, + str(reaction_.emoji) in PAGINATION_EMOJI, # Reaction was not made by the Bot user_.id != ctx.bot.user.id, # There were no restrictions @@ -203,10 +206,14 @@ class LinePaginator(Paginator): log.debug("Timed out waiting for a reaction") break # We're done, no reactions for the last 5 minutes - if reaction.emoji == DELETE_EMOJI: - log.debug("Got delete reaction") + if reaction.emoji == CLEAR_EMOJI: + log.debug("Got clear reaction") break + if str(reaction.emoji) == DELETE_EMOJI: + log.debug("Got delete reaction") + return await message.delete() + if reaction.emoji == FIRST_EMOJI: await message.remove_reaction(reaction.emoji, user) current_page = 0 @@ -342,7 +349,7 @@ class ImagePaginator(Paginator): # Reaction is on the same message sent reaction_.message.id == message.id, # The reaction is part of the navigation menu - reaction_.emoji in PAGINATION_EMOJI, + str(reaction_.emoji) in PAGINATION_EMOJI, # The reactor is not a bot not member.bot )) @@ -388,11 +395,16 @@ class ImagePaginator(Paginator): # Deletes the users reaction await message.remove_reaction(reaction.emoji, user) - # Delete reaction press - [:x:] - if reaction.emoji == DELETE_EMOJI: + # Clear reaction press - [:x:] + if reaction.emoji == CLEAR_EMOJI: log.debug("Got delete reaction") break + # Delete reaction press - [:trashcan:] + if str(reaction.emoji) == DELETE_EMOJI: + log.debug("Got delete reaction") + return await message.delete() + # First reaction press - [:track_previous:] if reaction.emoji == FIRST_EMOJI: if current_page == 0: -- cgit v1.2.3 From bab7fc2e4600bd89a62ad07dd8177de0ff943343 Mon Sep 17 00:00:00 2001 From: Kieran Siek Date: Mon, 28 Oct 2019 12:37:24 +0800 Subject: Apply suggestions from code review Fix incorrect docstring and comment Co-Authored-By: Mark --- bot/pagination.py | 2 +- bot/utils/messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index 11f7c77fe..8e3329c4b 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -397,7 +397,7 @@ class ImagePaginator(Paginator): # Clear reaction press - [:x:] if reaction.emoji == CLEAR_EMOJI: - log.debug("Got delete reaction") + log.debug("Got clear reaction") break # Delete reaction press - [:trashcan:] diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 654d71797..7ab35257c 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -39,7 +39,7 @@ async def wait_for_deletion( await message.add_reaction(emoji) def check(reaction: Reaction, user: Member) -> bool: - """Check that the deletion emoji is reacted by the approprite user.""" + """Check that the deletion emoji is reacted by the appropriate user.""" return ( reaction.message.id == message.id and str(reaction.emoji) in deletion_emojis -- cgit v1.2.3 From d4ff83e6d2ffdbde4b934c37b24f61a8dc6c4ebb Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 28 Oct 2019 13:01:47 +0800 Subject: Fix linting error --- bot/pagination.py | 2 +- bot/utils/messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index 8e3329c4b..a7938fe85 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -397,7 +397,7 @@ class ImagePaginator(Paginator): # Clear reaction press - [:x:] if reaction.emoji == CLEAR_EMOJI: - log.debug("Got clear reaction") + log.debug("Got clear reaction") break # Delete reaction press - [:trashcan:] diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 7ab35257c..022f79599 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -39,7 +39,7 @@ async def wait_for_deletion( await message.add_reaction(emoji) def check(reaction: Reaction, user: Member) -> bool: - """Check that the deletion emoji is reacted by the appropriate user.""" + """Check that the deletion emoji is reacted by the appropriate user.""" return ( reaction.message.id == message.id and str(reaction.emoji) in deletion_emojis -- cgit v1.2.3 From bed913eb358f3effa592cab507f329ce8f50171a Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 31 Oct 2019 13:07:55 +0100 Subject: Re-post attachments Before sending the attachments to API for logging, we now re-post them in the channel that have the id stored in the constant Guild.attachment_repost (it needs to be configured). These new links will never expires. --- bot/cogs/antispam.py | 4 ---- bot/cogs/moderation/modlog.py | 19 ++++++++++++++++++- bot/constants.py | 1 + config-default.yml | 1 + 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 1340eb608..a450c18ce 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -1,4 +1,3 @@ -import asyncio import logging from collections.abc import Mapping from dataclasses import dataclass, field @@ -243,9 +242,6 @@ class AntiSpam(Cog): async def _process_deletion_context(self, context_id: int) -> None: """Processes the Deletion Context queue.""" - log.trace("Sleeping before processing message deletion queue.") - await asyncio.sleep(10) - if context_id not in self.message_deletion_queue: log.error(f"Started processing deletion queue for context `{context_id}`, but it was not found!") return diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 88f2b6c67..6d4b66644 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -2,6 +2,7 @@ import asyncio import logging import typing as t from datetime import datetime +from io import BytesIO import discord from dateutil.relativedelta import relativedelta @@ -53,7 +54,8 @@ class ModLog(Cog, name="ModLog"): 'author': message.author.id, 'channel_id': message.channel.id, 'content': message.content, - 'embeds': [embed.to_dict() for embed in message.embeds] + 'embeds': [embed.to_dict() for embed in message.embeds], + 'attachments': await self.reupload_attachments(message) if message.attachments else [], } for message in messages ] @@ -116,6 +118,21 @@ class ModLog(Cog, name="ModLog"): return await self.bot.get_context(log_message) # Optionally return for use with antispam + async def reupload_attachments( + self, + message: discord.Message, + channel_id: int = GuildConstant.attachment_repost + ) -> t.List[str]: + """Re-upload message's attachments to the the channel_id and return the list of re-posted attachments URLs.""" + channel = self.bot.get_channel(channel_id) + out = [] + for attachment in message.attachments: + buffer = BytesIO() + await attachment.save(buffer, use_cached=True) + reupload = await channel.send(file=discord.File(buffer, filename=attachment.filename)) + out.append(reupload.attachments[0].url) + return out + @Cog.listener() async def on_guild_channel_create(self, channel: GUILD_CHANNEL) -> None: """Log channel create event to mod log.""" diff --git a/bot/constants.py b/bot/constants.py index 838fe7a79..9582fea96 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -398,6 +398,7 @@ class Guild(metaclass=YAMLGetter): id: int ignored: List[int] staff_channels: List[int] + attachment_repost: int class Keys(metaclass=YAMLGetter): diff --git a/config-default.yml b/config-default.yml index 4638a89ee..9d2ee7941 100644 --- a/config-default.yml +++ b/config-default.yml @@ -127,6 +127,7 @@ guild: staff_channels: [*ADMINS, *ADMIN_SPAM, *MOD_SPAM, *MODS, *HELPERS, *ORGANISATION, *DEFCON] ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] + attachment_repost: *MODLOG roles: admin: &ADMIN_ROLE 267628507062992896 -- cgit v1.2.3 From ded5bbe5bb6547b50647a8ed040e9803bd58a76e Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 15 Nov 2019 10:33:09 +0100 Subject: Use a context manager for the buffer Co-authored-by: Shirayuki Nekomata --- bot/cogs/moderation/modlog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 6d4b66644..3c6731de6 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -127,10 +127,10 @@ class ModLog(Cog, name="ModLog"): channel = self.bot.get_channel(channel_id) out = [] for attachment in message.attachments: - buffer = BytesIO() - await attachment.save(buffer, use_cached=True) - reupload = await channel.send(file=discord.File(buffer, filename=attachment.filename)) - out.append(reupload.attachments[0].url) + with BytesIO() as buffer: + await attachment.save(buffer, use_cached=True) + reupload: discord.Message = await channel.send(file=discord.File(buffer, filename=attachment.filename)) + out.append(reupload.attachments[0].url) return out @Cog.listener() -- cgit v1.2.3 From 173c5c256307b088165d6f3070336056fa673f8f Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Tue, 19 Nov 2019 09:28:10 +0100 Subject: Re-indent arguments --- bot/cogs/moderation/modlog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 3c6731de6..945d7822b 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -119,9 +119,9 @@ class ModLog(Cog, name="ModLog"): return await self.bot.get_context(log_message) # Optionally return for use with antispam async def reupload_attachments( - self, - message: discord.Message, - channel_id: int = GuildConstant.attachment_repost + self, + message: discord.Message, + channel_id: int = GuildConstant.attachment_repost ) -> t.List[str]: """Re-upload message's attachments to the the channel_id and return the list of re-posted attachments URLs.""" channel = self.bot.get_channel(channel_id) -- cgit v1.2.3 From 461826cff98c66646d6f545fc1e711675ac6492f Mon Sep 17 00:00:00 2001 From: Akarys42 Date: Wed, 27 Nov 2019 18:37:16 +0100 Subject: Move attachments re-uploading to DeletionContext.add() So they are re-uploaded before being deleted --- bot/cogs/antispam.py | 41 ++++++++++++++++++++++++++++++++++++----- bot/cogs/moderation/modlog.py | 30 +++++++++++------------------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index a450c18ce..8009c9d42 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -1,11 +1,13 @@ +import asyncio import logging from collections.abc import Mapping from dataclasses import dataclass, field from datetime import datetime, timedelta +from io import BytesIO from operator import itemgetter from typing import Dict, Iterable, List, Set -from discord import Colour, Member, Message, NotFound, Object, TextChannel +from discord import Colour, File, Member, Message, NotFound, Object, TextChannel from discord.ext.commands import Bot, Cog from bot import rules @@ -40,11 +42,13 @@ class DeletionContext: """Represents a Deletion Context for a single spam event.""" channel: TextChannel + bot: Bot members: Dict[int, Member] = field(default_factory=dict) rules: Set[str] = field(default_factory=set) messages: Dict[int, Message] = field(default_factory=dict) + attachments: List[List[str]] = field(default_factory=list) - def add(self, rule_name: str, members: Iterable[Member], messages: Iterable[Message]) -> None: + async def add(self, rule_name: str, members: Iterable[Member], messages: Iterable[Message]) -> None: """Adds new rule violation events to the deletion context.""" self.rules.add(rule_name) @@ -56,6 +60,9 @@ class DeletionContext: if message.id not in self.messages: self.messages[message.id] = message + # Re-upload attachments : + self.attachments.append(await reupload_attachments(self.bot, message)) + async def upload_messages(self, actor_id: int, modlog: ModLog) -> None: """Method that takes care of uploading the queue and posting modlog alert.""" triggered_by_users = ", ".join(f"{m} (`{m.id}`)" for m in self.members.values()) @@ -68,7 +75,7 @@ class DeletionContext: # For multiple messages or those with excessive newlines, use the logs API if len(self.messages) > 1 or 'newlines' in self.rules: - url = await modlog.upload_log(self.messages.values(), actor_id) + url = await modlog.upload_log(self.messages.values(), actor_id, attachments=self.attachments) mod_alert_message += f"A complete log of the offending messages can be found [here]({url})" else: mod_alert_message += "Message:\n" @@ -180,13 +187,16 @@ class AntiSpam(Cog): # If there's no spam event going on for this channel, start a new Message Deletion Context if message.channel.id not in self.message_deletion_queue: log.trace(f"Creating queue for channel `{message.channel.id}`") - self.message_deletion_queue[message.channel.id] = DeletionContext(channel=message.channel) + self.message_deletion_queue[message.channel.id] = DeletionContext( + channel=message.channel, + bot=self.bot + ) self.queue_consumption_tasks = self.bot.loop.create_task( self._process_deletion_context(message.channel.id) ) # Add the relevant of this trigger to the Deletion Context - self.message_deletion_queue[message.channel.id].add( + await self.message_deletion_queue[message.channel.id].add( rule_name=rule_name, members=members, messages=relevant_messages @@ -242,6 +252,9 @@ class AntiSpam(Cog): async def _process_deletion_context(self, context_id: int) -> None: """Processes the Deletion Context queue.""" + log.trace("Sleeping before processing message deletion queue.") + await asyncio.sleep(10) + if context_id not in self.message_deletion_queue: log.error(f"Started processing deletion queue for context `{context_id}`, but it was not found!") return @@ -250,6 +263,24 @@ class AntiSpam(Cog): await deletion_context.upload_messages(self.bot.user.id, self.mod_log) +async def reupload_attachments( + bot: Bot, + message: Message, + channel_id: int = GuildConfig.attachment_repost +) -> List[str]: + """Re-upload message's attachments to the the channel_id and return the list of re-posted attachments URLs.""" + if not message.attachments: + return [] + channel = bot.get_channel(channel_id) + out = [] + for attachment in message.attachments: + with BytesIO() as buffer: + await attachment.save(buffer, use_cached=True) + reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) + out.append(reupload.attachments[0].url) + return out + + def validate_config(rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: """Validates the antispam configs.""" validation_errors = {} diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 945d7822b..9251b79fb 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -2,7 +2,6 @@ import asyncio import logging import typing as t from datetime import datetime -from io import BytesIO import discord from dateutil.relativedelta import relativedelta @@ -35,7 +34,12 @@ class ModLog(Cog, name="ModLog"): self._cached_deletes = [] self._cached_edits = [] - async def upload_log(self, messages: t.List[discord.Message], actor_id: int) -> str: + async def upload_log( + self, + messages: t.List[discord.Message], + actor_id: int, + attachments: t.List[t.List[str]] = None + ) -> str: """ Uploads the log data to the database via an API endpoint for uploading logs. @@ -43,6 +47,9 @@ class ModLog(Cog, name="ModLog"): Returns a URL that can be used to view the log. """ + if attachments is None: + attachments = [] + response = await self.bot.api_client.post( 'bot/deleted-messages', json={ @@ -55,9 +62,9 @@ class ModLog(Cog, name="ModLog"): 'channel_id': message.channel.id, 'content': message.content, 'embeds': [embed.to_dict() for embed in message.embeds], - 'attachments': await self.reupload_attachments(message) if message.attachments else [], + 'attachments': attachment, } - for message in messages + for message, attachment in zip(messages, attachments) ] } ) @@ -118,21 +125,6 @@ class ModLog(Cog, name="ModLog"): return await self.bot.get_context(log_message) # Optionally return for use with antispam - async def reupload_attachments( - self, - message: discord.Message, - channel_id: int = GuildConstant.attachment_repost - ) -> t.List[str]: - """Re-upload message's attachments to the the channel_id and return the list of re-posted attachments URLs.""" - channel = self.bot.get_channel(channel_id) - out = [] - for attachment in message.attachments: - with BytesIO() as buffer: - await attachment.save(buffer, use_cached=True) - reupload: discord.Message = await channel.send(file=discord.File(buffer, filename=attachment.filename)) - out.append(reupload.attachments[0].url) - return out - @Cog.listener() async def on_guild_channel_create(self, channel: GUILD_CHANNEL) -> None: """Log channel create event to mod log.""" -- cgit v1.2.3 From 4a1ca965e2b83ea8690b3d7408464c8205432482 Mon Sep 17 00:00:00 2001 From: Akarys42 Date: Wed, 27 Nov 2019 18:41:57 +0100 Subject: Add try/except for attachment saving --- bot/cogs/antispam.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 8009c9d42..3118e0a42 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -7,7 +7,7 @@ from io import BytesIO from operator import itemgetter from typing import Dict, Iterable, List, Set -from discord import Colour, File, Member, Message, NotFound, Object, TextChannel +from discord import Colour, File, HTTPException, Member, Message, NotFound, Object, TextChannel from discord.ext.commands import Bot, Cog from bot import rules @@ -274,10 +274,13 @@ async def reupload_attachments( channel = bot.get_channel(channel_id) out = [] for attachment in message.attachments: - with BytesIO() as buffer: - await attachment.save(buffer, use_cached=True) - reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) - out.append(reupload.attachments[0].url) + try: + with BytesIO() as buffer: + await attachment.save(buffer, use_cached=True) + reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) + out.append(reupload.attachments[0].url) + except (HTTPException, NotFound): + log.warning(f"Tried to re-upload attchment {attachment.id}, but it has failed.") return out -- cgit v1.2.3 From bfde96aa4ee91a800d03106855afa22b958a92a2 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 28 Nov 2019 08:32:42 +0100 Subject: Fix misspelling Co-Authored-By: Mark --- bot/cogs/antispam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 3118e0a42..0d3d0c4cc 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -280,7 +280,7 @@ async def reupload_attachments( reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) out.append(reupload.attachments[0].url) except (HTTPException, NotFound): - log.warning(f"Tried to re-upload attchment {attachment.id}, but it has failed.") + log.warning(f"Tried to re-upload attachment {attachment.id}, but it has failed.") return out -- cgit v1.2.3 From 1c3220345c7b04956b584ccd072d596b1d790a5d Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 28 Nov 2019 09:13:32 +0100 Subject: Update try/except block in reupload_attachments() --- bot/cogs/antispam.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 0d3d0c4cc..6444b1f14 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -279,8 +279,11 @@ async def reupload_attachments( await attachment.save(buffer, use_cached=True) reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) out.append(reupload.attachments[0].url) - except (HTTPException, NotFound): - log.warning(f"Tried to re-upload attachment {attachment.id}, but it has failed.") + except HTTPException: + log.warning( + f"Tried to re-upload attachment {attachment.filename} " + f"with ID {attachment.id} from message {message.id}, but it has failed." + ) return out -- cgit v1.2.3 From e669f282453b21a3e40564f4b28f9cb9454487b1 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 28 Nov 2019 09:17:19 +0100 Subject: Re-upload attachments to #attachment-log --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 9d2ee7941..6e8c01ad5 100644 --- a/config-default.yml +++ b/config-default.yml @@ -127,7 +127,7 @@ guild: staff_channels: [*ADMINS, *ADMIN_SPAM, *MOD_SPAM, *MODS, *HELPERS, *ORGANISATION, *DEFCON] ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] - attachment_repost: *MODLOG + attachment_repost: 649243850006855680 roles: admin: &ADMIN_ROLE 267628507062992896 -- cgit v1.2.3 From 17cedcb89b7b009fab68f3b1d39e968ace0a7c91 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 15:38:33 -0800 Subject: ModLog: use more generic type annotations --- bot/cogs/moderation/modlog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 9251b79fb..6fffa7170 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -35,10 +35,10 @@ class ModLog(Cog, name="ModLog"): self._cached_edits = [] async def upload_log( - self, - messages: t.List[discord.Message], - actor_id: int, - attachments: t.List[t.List[str]] = None + self, + messages: t.Iterable[discord.Message], + actor_id: int, + attachments: t.Iterable[t.List[str]] = None ) -> str: """ Uploads the log data to the database via an API endpoint for uploading logs. -- cgit v1.2.3 From e29b65c5a2b48e8870819453581c50bbcc2326bd Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:20:55 -0800 Subject: Utils: support returning URLs from send_attachments --- bot/utils/messages.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 549b33ca6..40c20c7ec 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -1,7 +1,7 @@ import asyncio import contextlib from io import BytesIO -from typing import Optional, Sequence, Union +from typing import List, Optional, Sequence, Union from discord import Client, Embed, File, Member, Message, Reaction, TextChannel, Webhook from discord.abc import Snowflake @@ -51,14 +51,15 @@ async def wait_for_deletion( await message.delete() -async def send_attachments(message: Message, destination: Union[TextChannel, Webhook]) -> None: +async def send_attachments(message: Message, destination: Union[TextChannel, Webhook]) -> List[str]: """ - Re-uploads each attachment in a message to the given channel or webhook. + Re-upload the message's attachments to the destination and return a list of their new URLs. Each attachment is sent as a separate message to more easily comply with the 8 MiB request size limit. If attachments are too large, they are instead grouped into a single embed which links to them. """ large = [] + urls = [] for attachment in message.attachments: try: # This should avoid most files that are too large, but some may get through hence the try-catch. @@ -69,7 +70,8 @@ async def send_attachments(message: Message, destination: Union[TextChannel, Web attachment_file = File(file, filename=attachment.filename) if isinstance(destination, TextChannel): - await destination.send(file=attachment_file) + msg = await destination.send(file=attachment_file) + urls.append(msg.attachments[0].url) else: await destination.send( file=attachment_file, @@ -95,3 +97,5 @@ async def send_attachments(message: Message, destination: Union[TextChannel, Web username=message.author.display_name, avatar_url=message.author.avatar_url ) + + return urls -- cgit v1.2.3 From 336c6d523031b611ebc4823583c7d8b4ed1964c2 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:23:13 -0800 Subject: Utils: use the guild's filesize_limit to determine max attachment size --- bot/utils/messages.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 40c20c7ec..fa1ee80b5 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -9,8 +9,6 @@ from discord.errors import HTTPException from bot.constants import Emojis -MAX_SIZE = 1024 * 1024 * 8 # 8 Mebibytes - async def wait_for_deletion( message: Message, @@ -62,9 +60,10 @@ async def send_attachments(message: Message, destination: Union[TextChannel, Web urls = [] for attachment in message.attachments: try: - # This should avoid most files that are too large, but some may get through hence the try-catch. # Allow 512 bytes of leeway for the rest of the request. - if attachment.size <= MAX_SIZE - 512: + # This should avoid most files that are too large, + # but some may get through hence the try-catch. + if attachment.size <= destination.guild.filesize_limit - 512: with BytesIO() as file: await attachment.save(file) attachment_file = File(file, filename=attachment.filename) -- cgit v1.2.3 From 4e414108ef3e098c24b9ab14fd09550673d87207 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:28:20 -0800 Subject: Utils: add send_attachments param to disable linking to too-large files --- bot/utils/messages.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index fa1ee80b5..232d3cba6 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -49,12 +49,17 @@ async def wait_for_deletion( await message.delete() -async def send_attachments(message: Message, destination: Union[TextChannel, Webhook]) -> List[str]: +async def send_attachments( + message: Message, + destination: Union[TextChannel, Webhook], + link_large: bool = True +) -> List[str]: """ Re-upload the message's attachments to the destination and return a list of their new URLs. - Each attachment is sent as a separate message to more easily comply with the 8 MiB request size limit. - If attachments are too large, they are instead grouped into a single embed which links to them. + Each attachment is sent as a separate message to more easily comply with the request/file size + limit. If link_large is True, attachments which are too large are instead grouped into a single + embed which links to them. """ large = [] urls = [] @@ -77,17 +82,19 @@ async def send_attachments(message: Message, destination: Union[TextChannel, Web username=message.author.display_name, avatar_url=message.author.avatar_url ) - else: + elif link_large: large.append(attachment) except HTTPException as e: - if e.status == 413: + if link_large and e.status == 413: large.append(attachment) else: raise - if large: - embed = Embed(description=f"\n".join(f"[{attachment.filename}]({attachment.url})" for attachment in large)) + if link_large and large: + desc = f"\n".join(f"[{attachment.filename}]({attachment.url})" for attachment in large) + embed = Embed(description=desc) embed.set_footer(text="Attachments exceed upload size limit.") + if isinstance(destination, TextChannel): await destination.send(embed=embed) else: -- cgit v1.2.3 From 0f71c817320c077463a483701c6d51a8fa3e2164 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:29:23 -0800 Subject: Utils: log send_attachments failures instead of raising exceptions --- bot/utils/messages.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 232d3cba6..a39521b72 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -1,5 +1,6 @@ import asyncio import contextlib +import logging from io import BytesIO from typing import List, Optional, Sequence, Union @@ -9,6 +10,8 @@ from discord.errors import HTTPException from bot.constants import Emojis +log = logging.getLogger(__name__) + async def wait_for_deletion( message: Message, @@ -64,6 +67,10 @@ async def send_attachments( large = [] urls = [] for attachment in message.attachments: + failure_msg = ( + f"Failed to re-upload attachment {attachment.filename} from message {message.id}" + ) + try: # Allow 512 bytes of leeway for the rest of the request. # This should avoid most files that are too large, @@ -84,11 +91,13 @@ async def send_attachments( ) elif link_large: large.append(attachment) + else: + log.warning(f"{failure_msg} because it's too large.") except HTTPException as e: if link_large and e.status == 413: large.append(attachment) else: - raise + log.warning(f"{failure_msg} with status {e.status}.") if link_large and large: desc = f"\n".join(f"[{attachment.filename}]({attachment.url})" for attachment in large) -- cgit v1.2.3 From c4f80a54cdc02ea150d956d77bd739cf5c9564d6 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:31:21 -0800 Subject: Utils: have send_attachments save attachments using the cached URL This makes it more likely to successfully save an attachment after it's been deleted. --- bot/utils/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/utils/messages.py b/bot/utils/messages.py index a39521b72..c4e2753e0 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -77,7 +77,7 @@ async def send_attachments( # but some may get through hence the try-catch. if attachment.size <= destination.guild.filesize_limit - 512: with BytesIO() as file: - await attachment.save(file) + await attachment.save(file, use_cached=True) attachment_file = File(file, filename=attachment.filename) if isinstance(destination, TextChannel): -- cgit v1.2.3 From 62d909b7bc0586a91c08b46c128a5eb5c5e6883e Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 20:32:23 -0800 Subject: AntiSpam: replace reupload_attachments with send_attachments from utils --- bot/cogs/antispam.py | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 6444b1f14..3a654cfaa 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -3,11 +3,10 @@ import logging from collections.abc import Mapping from dataclasses import dataclass, field from datetime import datetime, timedelta -from io import BytesIO from operator import itemgetter from typing import Dict, Iterable, List, Set -from discord import Colour, File, HTTPException, Member, Message, NotFound, Object, TextChannel +from discord import Colour, Member, Message, NotFound, Object, TextChannel from discord.ext.commands import Bot, Cog from bot import rules @@ -19,6 +18,7 @@ from bot.constants import ( STAFF_ROLES, ) from bot.converters import Duration +from bot.utils.messages import send_attachments log = logging.getLogger(__name__) @@ -60,8 +60,10 @@ class DeletionContext: if message.id not in self.messages: self.messages[message.id] = message - # Re-upload attachments : - self.attachments.append(await reupload_attachments(self.bot, message)) + # Re-upload attachments + destination = self.bot.get_channel(GuildConfig.attachment_repost) + urls = await send_attachments(message, destination, link_large=False) + self.attachments.append(urls) async def upload_messages(self, actor_id: int, modlog: ModLog) -> None: """Method that takes care of uploading the queue and posting modlog alert.""" @@ -263,30 +265,6 @@ class AntiSpam(Cog): await deletion_context.upload_messages(self.bot.user.id, self.mod_log) -async def reupload_attachments( - bot: Bot, - message: Message, - channel_id: int = GuildConfig.attachment_repost -) -> List[str]: - """Re-upload message's attachments to the the channel_id and return the list of re-posted attachments URLs.""" - if not message.attachments: - return [] - channel = bot.get_channel(channel_id) - out = [] - for attachment in message.attachments: - try: - with BytesIO() as buffer: - await attachment.save(buffer, use_cached=True) - reupload: Message = await channel.send(file=File(buffer, filename=attachment.filename)) - out.append(reupload.attachments[0].url) - except HTTPException: - log.warning( - f"Tried to re-upload attachment {attachment.filename} " - f"with ID {attachment.id} from message {message.id}, but it has failed." - ) - return out - - def validate_config(rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: """Validates the antispam configs.""" validation_errors = {} -- cgit v1.2.3 From 2af995cdf483bf1b8a927768566d9a27cf8a07cf Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 21:07:17 -0800 Subject: AntiSpam: correct a function annotation --- bot/cogs/antispam.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 3a654cfaa..1a60897c9 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -105,7 +105,7 @@ class DeletionContext: class AntiSpam(Cog): """Cog that controls our anti-spam measures.""" - def __init__(self, bot: Bot, validation_errors: bool) -> None: + def __init__(self, bot: Bot, validation_errors: Dict[str, str]) -> None: self.bot = bot self.validation_errors = validation_errors role_id = AntiSpamConfig.punishment['role_id'] @@ -265,10 +265,10 @@ class AntiSpam(Cog): await deletion_context.upload_messages(self.bot.user.id, self.mod_log) -def validate_config(rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: +def validate_config(_rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: """Validates the antispam configs.""" validation_errors = {} - for name, config in rules.items(): + for name, config in _rules.items(): if name not in RULE_FUNCTION_MAPPING: log.error( f"Unrecognized antispam rule `{name}`. " -- cgit v1.2.3 From 56c1e01e3059a7163e01df5282265c1053b419c3 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 21:36:37 -0800 Subject: ModLog: fix 0 message logs uploaded when no attachments given --- bot/cogs/moderation/modlog.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 6fffa7170..801582a6b 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -2,6 +2,7 @@ import asyncio import logging import typing as t from datetime import datetime +from itertools import zip_longest import discord from dateutil.relativedelta import relativedelta @@ -40,13 +41,7 @@ class ModLog(Cog, name="ModLog"): actor_id: int, attachments: t.Iterable[t.List[str]] = None ) -> str: - """ - Uploads the log data to the database via an API endpoint for uploading logs. - - Used in several mod log embeds. - - Returns a URL that can be used to view the log. - """ + """Upload message logs to the database and return a URL to a page for viewing the logs.""" if attachments is None: attachments = [] @@ -64,7 +59,7 @@ class ModLog(Cog, name="ModLog"): 'embeds': [embed.to_dict() for embed in message.embeds], 'attachments': attachment, } - for message, attachment in zip(messages, attachments) + for message, attachment in zip_longest(messages, attachments) ] } ) -- cgit v1.2.3 From 400f7c85da237a118024a4fb4e73802c8700fc46 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 3 Dec 2019 21:43:15 -0800 Subject: AntiSpam: remove bot field from DeletionContext The destination channel can be retrieved be accessing a message's guild. * Remove unused queue_consumption_tasks attribute. --- bot/cogs/antispam.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 1a60897c9..669c62c75 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -42,7 +42,6 @@ class DeletionContext: """Represents a Deletion Context for a single spam event.""" channel: TextChannel - bot: Bot members: Dict[int, Member] = field(default_factory=dict) rules: Set[str] = field(default_factory=set) messages: Dict[int, Message] = field(default_factory=dict) @@ -61,7 +60,7 @@ class DeletionContext: self.messages[message.id] = message # Re-upload attachments - destination = self.bot.get_channel(GuildConfig.attachment_repost) + destination = message.guild.get_channel(GuildConfig.attachment_repost) urls = await send_attachments(message, destination, link_large=False) self.attachments.append(urls) @@ -77,7 +76,7 @@ class DeletionContext: # For multiple messages or those with excessive newlines, use the logs API if len(self.messages) > 1 or 'newlines' in self.rules: - url = await modlog.upload_log(self.messages.values(), actor_id, attachments=self.attachments) + url = await modlog.upload_log(self.messages.values(), actor_id, self.attachments) mod_alert_message += f"A complete log of the offending messages can be found [here]({url})" else: mod_alert_message += "Message:\n" @@ -113,7 +112,6 @@ class AntiSpam(Cog): self.expiration_date_converter = Duration() self.message_deletion_queue = dict() - self.queue_consumption_tasks = dict() self.bot.loop.create_task(self.alert_on_validation_error()) @@ -187,15 +185,11 @@ class AntiSpam(Cog): full_reason = f"`{rule_name}` rule: {reason}" # If there's no spam event going on for this channel, start a new Message Deletion Context - if message.channel.id not in self.message_deletion_queue: - log.trace(f"Creating queue for channel `{message.channel.id}`") - self.message_deletion_queue[message.channel.id] = DeletionContext( - channel=message.channel, - bot=self.bot - ) - self.queue_consumption_tasks = self.bot.loop.create_task( - self._process_deletion_context(message.channel.id) - ) + channel = message.channel + if channel.id not in self.message_deletion_queue: + log.trace(f"Creating queue for channel `{channel.id}`") + self.message_deletion_queue[message.channel.id] = DeletionContext(channel) + self.bot.loop.create_task(self._process_deletion_context(message.channel.id)) # Add the relevant of this trigger to the Deletion Context await self.message_deletion_queue[message.channel.id].add( @@ -212,7 +206,7 @@ class AntiSpam(Cog): self.punish(message, member, full_reason) ) - await self.maybe_delete_messages(message.channel, relevant_messages) + await self.maybe_delete_messages(channel, relevant_messages) break async def punish(self, msg: Message, member: Member, reason: str) -> None: -- cgit v1.2.3 From 75f00f3574bcee36f204fc3e5806b3bd91c91829 Mon Sep 17 00:00:00 2001 From: Akarys42 Date: Sat, 14 Dec 2019 12:06:42 +0100 Subject: Relay attchments to #attachment_log --- config-default.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index b2f513bd3..0ecf88702 100644 --- a/config-default.yml +++ b/config-default.yml @@ -102,6 +102,7 @@ guild: admins: &ADMINS 365960823622991872 admin_spam: &ADMIN_SPAM 563594791770914816 announcements: 354619224620138496 + attachment_log: &ATTCH_LOG 649243850006855680 big_brother_logs: &BBLOGS 468507907357409333 bot: 267659945086812160 checkpoint_test: 422077681434099723 @@ -138,7 +139,7 @@ guild: staff_channels: [*ADMINS, *ADMIN_SPAM, *MOD_SPAM, *MODS, *HELPERS, *ORGANISATION, *DEFCON] ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] - attachment_repost: 649243850006855680 + attachment_repost: *ATTCH_LOG roles: admin: &ADMIN_ROLE 267628507062992896 -- cgit v1.2.3 From d169fbf9204b8a35ad09ab0141eedd873f8bdddf Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 30 Jan 2020 18:17:12 +0100 Subject: Add additional resources to the test readme --- tests/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/README.md b/tests/README.md index d052de2f6..c3551bd54 100644 --- a/tests/README.md +++ b/tests/README.md @@ -212,3 +212,9 @@ All in all, it's not only important to consider if all statements or branches we Another restriction of unit testing is that it tests, well, in units. Even if we can guarantee that the units work as they should independently, we have no guarantee that they will actually work well together. Even more, while the mocking described above gives us a lot of flexibility in factoring out external code, we are work under the implicit assumption that we fully understand those external parts and utilize it correctly. What if our mocked `Context` object works with a `send` method, but `discord.py` has changed it to a `send_message` method in a recent update? It could mean our tests are passing, but the code it's testing still doesn't work in production. The answer to this is that we also need to make sure that the individual parts come together into a working application. In addition, we will also need to make sure that the application communicates correctly with external applications. Since we currently have no automated integration tests or functional tests, that means **it's still very important to fire up the bot and test the code you've written manually** in addition to the unit tests you've written. + +## Additional resources + +* [Corey Schafer video about unittest](https://youtu.be/6tNS--WetLI) +* [RealPython tutorial on unittest testing](https://realpython.com/python-testing/) +* [RealPython tutorial on mocking](https://realpython.com/python-mock-library/) -- cgit v1.2.3 From e518dc2193f0f1ed31217e6097c2c6cdbc36e0e1 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 30 Jan 2020 19:04:21 +0100 Subject: Merge the note with the additional resources section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the link to Ned Batchelder’s talk and link the note to the section --- tests/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index c3551bd54..be78821bf 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,7 +2,7 @@ Our bot is one of the most important tools we have for running our community. As we don't want that tool break, we decided that we wanted to write unit tests for it. We hope that in the future, we'll have a 100% test coverage for the bot. This guide will help you get started with writing the tests needed to achieve that. -_**Note:** This is a practical guide to getting started with writing tests for our bot, not a general introduction to writing unit tests in Python. If you're looking for a more general introduction, you may like Corey Schafer's [Python Tutorial: Unit Testing Your Code with the unittest Module](https://www.youtube.com/watch?v=6tNS--WetLI) or Ned Batchelder's PyCon talk [Getting Started Testing](https://www.youtube.com/watch?v=FxSsnHeWQBY)._ +_**Note:** This is a practical guide to getting started with writing tests for our bot, not a general introduction to writing unit tests in Python. If you're looking for a more general introduction, you can take a look at the [Additional resources](#additional-resources) section at the bottom of this page._ ## Tools @@ -215,6 +215,7 @@ The answer to this is that we also need to make sure that the individual parts c ## Additional resources +* [Ned Batchelder's PyCon talk: Getting Started Testing](https://www.youtube.com/watch?v=FxSsnHeWQBY) * [Corey Schafer video about unittest](https://youtu.be/6tNS--WetLI) * [RealPython tutorial on unittest testing](https://realpython.com/python-testing/) * [RealPython tutorial on mocking](https://realpython.com/python-mock-library/) -- cgit v1.2.3 From 8647cb8db557267d1ef1a95ef97fe334256ba6af Mon Sep 17 00:00:00 2001 From: Joseph Date: Sun, 2 Feb 2020 20:11:51 +0000 Subject: Create CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..8aa168278 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @core-developers -- cgit v1.2.3 From e8c94afa3c88633a8ce30fdb849efd8b76cc1599 Mon Sep 17 00:00:00 2001 From: Thomas Petersson Date: Sun, 2 Feb 2020 21:13:18 +0100 Subject: Update config-default.yml --- config-default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config-default.yml b/config-default.yml index f842cf606..1a8aaedae 100644 --- a/config-default.yml +++ b/config-default.yml @@ -389,6 +389,7 @@ anti_malware: - '.mp3' - '.wav' - '.ogg' + - '.md' reddit: -- cgit v1.2.3 From 68709884494b755739723c725f9998d02d17dde3 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Sun, 2 Feb 2020 22:20:44 +0000 Subject: Remove prometheus related code --- Pipfile | 1 - Pipfile.lock | 297 ++++++++++++++++++++++------------------------------ bot/__main__.py | 1 - bot/bot.py | 3 - bot/cogs/metrics.py | 98 ----------------- 5 files changed, 128 insertions(+), 272 deletions(-) delete mode 100644 bot/cogs/metrics.py diff --git a/Pipfile b/Pipfile index 68362ae78..48d839fc3 100644 --- a/Pipfile +++ b/Pipfile @@ -19,7 +19,6 @@ deepdiff = "~=4.0" requests = "~=2.22" more_itertools = "~=7.2" urllib3 = ">=1.24.2,<1.25" -prometheus-async = {extras = ["aiohttp"],version = "~=19.2"} [dev-packages] coverage = "~=4.5" diff --git a/Pipfile.lock b/Pipfile.lock index ab5dfb538..279480d2a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d9349e8c704b2b2403004039856d8d75aaebc76e4aa93390c4d177f583e73b71" + "sha256": "c27d699b4aeeed204dee41f924f682ae2a670add8549a8826e58776594370582" }, "pipfile-spec": 6, "requires": { @@ -34,31 +34,21 @@ }, "aiohttp": { "hashes": [ - "sha256:00d198585474299c9c3b4f1d5de1a576cc230d562abc5e4a0e81d71a20a6ca55", - "sha256:0155af66de8c21b8dba4992aaeeabf55503caefae00067a3b1139f86d0ec50ed", - "sha256:09654a9eca62d1bd6d64aa44db2498f60a5c1e0ac4750953fdd79d5c88955e10", - "sha256:199f1d106e2b44b6dacdf6f9245493c7d716b01d0b7fbe1959318ba4dc64d1f5", - "sha256:296f30dedc9f4b9e7a301e5cc963012264112d78a1d3094cd83ef148fdf33ca1", - "sha256:368ed312550bd663ce84dc4b032a962fcb3c7cae099dbbd48663afc305e3b939", - "sha256:40d7ea570b88db017c51392349cf99b7aefaaddd19d2c78368aeb0bddde9d390", - "sha256:629102a193162e37102c50713e2e31dc9a2fe7ac5e481da83e5bb3c0cee700aa", - "sha256:6d5ec9b8948c3d957e75ea14d41e9330e1ac3fed24ec53766c780f82805140dc", - "sha256:87331d1d6810214085a50749160196391a712a13336cd02ce1c3ea3d05bcf8d5", - "sha256:9a02a04bbe581c8605ac423ba3a74999ec9d8bce7ae37977a3d38680f5780b6d", - "sha256:9c4c83f4fa1938377da32bc2d59379025ceeee8e24b89f72fcbccd8ca22dc9bf", - "sha256:9cddaff94c0135ee627213ac6ca6d05724bfe6e7a356e5e09ec57bd3249510f6", - "sha256:a25237abf327530d9561ef751eef9511ab56fd9431023ca6f4803f1994104d72", - "sha256:a5cbd7157b0e383738b8e29d6e556fde8726823dae0e348952a61742b21aeb12", - "sha256:a97a516e02b726e089cffcde2eea0d3258450389bbac48cbe89e0f0b6e7b0366", - "sha256:acc89b29b5f4e2332d65cd1b7d10c609a75b88ef8925d487a611ca788432dfa4", - "sha256:b05bd85cc99b06740aad3629c2585bda7b83bd86e080b44ba47faf905fdf1300", - "sha256:c2bec436a2b5dafe5eaeb297c03711074d46b6eb236d002c13c42f25c4a8ce9d", - "sha256:cc619d974c8c11fe84527e4b5e1c07238799a8c29ea1c1285149170524ba9303", - "sha256:d4392defd4648badaa42b3e101080ae3313e8f4787cb517efd3f5b8157eaefd6", - "sha256:e1c3c582ee11af7f63a34a46f0448fca58e59889396ffdae1f482085061a2889" + "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", + "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", + "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", + "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", + "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", + "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", + "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", + "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", + "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", + "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", + "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", + "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" ], "index": "pypi", - "version": "==3.5.4" + "version": "==3.6.2" }, "aiormq": { "hashes": [ @@ -157,26 +147,25 @@ }, "deepdiff": { "hashes": [ - "sha256:3457ea7cecd51ba48015d89edbb569358af4d9b9e65e28bdb3209608420627f9", - "sha256:5e2343398e90538edaa59c0c99207e996a3a834fdc878c666376f632a760c35a" + "sha256:b3fa588d1eac7fa318ec1fb4f2004568e04cb120a1989feda8e5e7164bcbf07a", + "sha256:ed7342d3ed3c0c2058a3fb05b477c943c9959ef62223dca9baa3375718a25d87" ], "index": "pypi", - "version": "==4.0.9" + "version": "==4.2.0" }, "discord-py": { "hashes": [ - "sha256:7c843b523bb011062b453864e75c7b675a03faf573c58d14c9f096e85984329d" + "sha256:8bfe5628d31771744000f19135c386c74ac337479d7282c26cc1627b9d31f360" ], "index": "pypi", - "version": "==1.2.5" + "version": "==1.3.1" }, "docutils": { "hashes": [ - "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", - "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", - "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], - "version": "==0.15.2" + "version": "==0.16" }, "fuzzywuzzy": { "hashes": [ @@ -202,17 +191,10 @@ }, "jinja2": { "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], - "version": "==2.10.3" - }, - "jsonpickle": { - "hashes": [ - "sha256:d0c5a4e6cb4e58f6d5406bdded44365c2bcf9c836c4f52910cc9ba7245a59dc2", - "sha256:d3e922d781b1d0096df2dad89a2e1f47177d7969b596aea806a9d91b4626b29b" - ], - "version": "==1.2" + "version": "==2.11.1" }, "logmatic-python": { "hashes": [ @@ -223,35 +205,36 @@ }, "lxml": { "hashes": [ - "sha256:00ac0d64949fef6b3693813fe636a2d56d97a5a49b5bbb86e4cc4cc50ebc9ea2", - "sha256:0571e607558665ed42e450d7bf0e2941d542c18e117b1ebbf0ba72f287ad841c", - "sha256:0e3f04a7615fdac0be5e18b2406529521d6dbdb0167d2a690ee328bef7807487", - "sha256:13cf89be53348d1c17b453867da68704802966c433b2bb4fa1f970daadd2ef70", - "sha256:217262fcf6a4c2e1c7cb1efa08bd9ebc432502abc6c255c4abab611e8be0d14d", - "sha256:223e544828f1955daaf4cefbb4853bc416b2ec3fd56d4f4204a8b17007c21250", - "sha256:277cb61fede2f95b9c61912fefb3d43fbd5f18bf18a14fae4911b67984486f5d", - "sha256:3213f753e8ae86c396e0e066866e64c6b04618e85c723b32ecb0909885211f74", - "sha256:4690984a4dee1033da0af6df0b7a6bde83f74e1c0c870623797cec77964de34d", - "sha256:4fcc472ef87f45c429d3b923b925704aa581f875d65bac80f8ab0c3296a63f78", - "sha256:61409bd745a265a742f2693e4600e4dbd45cc1daebe1d5fad6fcb22912d44145", - "sha256:678f1963f755c5d9f5f6968dded7b245dd1ece8cf53c1aa9d80e6734a8c7f41d", - "sha256:6c6d03549d4e2734133badb9ab1c05d9f0ef4bcd31d83e5d2b4747c85cfa21da", - "sha256:6e74d5f4d6ecd6942375c52ffcd35f4318a61a02328f6f1bd79fcb4ffedf969e", - "sha256:7b4fc7b1ecc987ca7aaf3f4f0e71bbfbd81aaabf87002558f5bc95da3a865bcd", - "sha256:7ed386a40e172ddf44c061ad74881d8622f791d9af0b6f5be20023029129bc85", - "sha256:8f54f0924d12c47a382c600c880770b5ebfc96c9fd94cf6f6bdc21caf6163ea7", - "sha256:ad9b81351fdc236bda538efa6879315448411a81186c836d4b80d6ca8217cdb9", - "sha256:bbd00e21ea17f7bcc58dccd13869d68441b32899e89cf6cfa90d624a9198ce85", - "sha256:c3c289762cc09735e2a8f8a49571d0e8b4f57ea831ea11558247b5bdea0ac4db", - "sha256:cf4650942de5e5685ad308e22bcafbccfe37c54aa7c0e30cd620c2ee5c93d336", - "sha256:cfcbc33c9c59c93776aa41ab02e55c288a042211708b72fdb518221cc803abc8", - "sha256:e301055deadfedbd80cf94f2f65ff23126b232b0d1fea28f332ce58137bcdb18", - "sha256:ebbfe24df7f7b5c6c7620702496b6419f6a9aa2fd7f005eb731cc80d7b4692b9", - "sha256:eff69ddbf3ad86375c344339371168640951c302450c5d3e9936e98d6459db06", - "sha256:f6ed60a62c5f1c44e789d2cf14009423cb1646b44a43e40a9cf6a21f077678a1" - ], - "index": "pypi", - "version": "==4.4.2" + "sha256:06d4e0bbb1d62e38ae6118406d7cdb4693a3fa34ee3762238bcb96c9e36a93cd", + "sha256:0701f7965903a1c3f6f09328c1278ac0eee8f56f244e66af79cb224b7ef3801c", + "sha256:1f2c4ec372bf1c4a2c7e4bb20845e8bcf8050365189d86806bad1e3ae473d081", + "sha256:4235bc124fdcf611d02047d7034164897ade13046bda967768836629bc62784f", + "sha256:5828c7f3e615f3975d48f40d4fe66e8a7b25f16b5e5705ffe1d22e43fb1f6261", + "sha256:585c0869f75577ac7a8ff38d08f7aac9033da2c41c11352ebf86a04652758b7a", + "sha256:5d467ce9c5d35b3bcc7172c06320dddb275fea6ac2037f72f0a4d7472035cea9", + "sha256:63dbc21efd7e822c11d5ddbedbbb08cd11a41e0032e382a0fd59b0b08e405a3a", + "sha256:7bc1b221e7867f2e7ff1933165c0cec7153dce93d0cdba6554b42a8beb687bdb", + "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60", + "sha256:8a0ebda56ebca1a83eb2d1ac266649b80af8dd4b4a3502b2c1e09ac2f88fe128", + "sha256:90ed0e36455a81b25b7034038e40880189169c308a3df360861ad74da7b68c1a", + "sha256:95e67224815ef86924fbc2b71a9dbd1f7262384bca4bc4793645794ac4200717", + "sha256:afdb34b715daf814d1abea0317b6d672476b498472f1e5aacbadc34ebbc26e89", + "sha256:b4b2c63cc7963aedd08a5f5a454c9f67251b1ac9e22fd9d72836206c42dc2a72", + "sha256:d068f55bda3c2c3fcaec24bd083d9e2eede32c583faf084d6e4b9daaea77dde8", + "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3", + "sha256:deadf4df349d1dcd7b2853a2c8796593cc346600726eff680ed8ed11812382a7", + "sha256:df533af6f88080419c5a604d0d63b2c33b1c0c4409aba7d0cb6de305147ea8c8", + "sha256:e4aa948eb15018a657702fee0b9db47e908491c64d36b4a90f59a64741516e77", + "sha256:e5d842c73e4ef6ed8c1bd77806bf84a7cb535f9c0cf9b2c74d02ebda310070e1", + "sha256:ebec08091a22c2be870890913bdadd86fcd8e9f0f22bcb398abd3af914690c15", + "sha256:edc15fcfd77395e24543be48871c251f38132bb834d9fdfdad756adb6ea37679", + "sha256:f2b74784ed7e0bc2d02bd53e48ad6ba523c9b36c194260b7a5045071abbb1012", + "sha256:fa071559f14bd1e92077b1b5f6c22cf09756c6de7139370249eb372854ce51e6", + "sha256:fd52e796fee7171c4361d441796b64df1acfceb51f29e545e812f16d023c4bbc", + "sha256:fe976a0f1ef09b3638778024ab9fb8cde3118f203364212c198f71341c0715ca" + ], + "index": "pypi", + "version": "==4.5.0" }, "markdownify": { "hashes": [ @@ -266,13 +249,16 @@ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", @@ -289,7 +275,9 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, @@ -331,10 +319,10 @@ }, "packaging": { "hashes": [ - "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", - "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8" + "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", + "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" ], - "version": "==20.0" + "version": "==20.1" }, "pamqp": { "hashes": [ @@ -343,23 +331,6 @@ ], "version": "==2.3.0" }, - "prometheus-async": { - "extras": [ - "aiohttp" - ], - "hashes": [ - "sha256:227f516e5bf98a0dc602348381e182358f8b2ed24a8db05e8e34d9cf027bab83", - "sha256:3cc68d1f39e9bbf16dbd0b51103d87671b3cbd1d75a72cda472cd9a35cc9d0d2" - ], - "index": "pypi", - "version": "==19.2.0" - }, - "prometheus-client": { - "hashes": [ - "sha256:71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da" - ], - "version": "==0.7.1" - }, "pycares": { "hashes": [ "sha256:050f00b39ed77ea8a4e555f09417d4b1a6b5baa24bb9531a3e15d003d2319b3f", @@ -462,10 +433,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -541,35 +512,30 @@ }, "websockets": { "hashes": [ - "sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136", - "sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6", - "sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1", - "sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538", - "sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4", - "sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908", - "sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0", - "sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d", - "sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c", - "sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d", - "sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c", - "sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb", - "sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf", - "sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e", - "sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96", - "sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584", - "sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484", - "sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d", - "sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559", - "sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff", - "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454" - ], - "version": "==6.0" - }, - "wrapt": { - "hashes": [ - "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" - ], - "version": "==1.11.2" + "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", + "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", + "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", + "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", + "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", + "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", + "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", + "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", + "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", + "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", + "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", + "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", + "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", + "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", + "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", + "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", + "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", + "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", + "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", + "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", + "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", + "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" + ], + "version": "==8.1" }, "yarl": { "hashes": [ @@ -762,10 +728,10 @@ }, "identify": { "hashes": [ - "sha256:6f44e637caa40d1b4cb37f6ed3b262ede74901d28b1cc5b1fc07360871edd65d", - "sha256:72e9c4ed3bc713c7045b762b0d2e2115c572b85abfc1f4604f5a4fd4c6642b71" + "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5", + "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96" ], - "version": "==1.4.9" + "version": "==1.4.11" }, "idna": { "hashes": [ @@ -776,11 +742,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359", - "sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8" + "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" ], "markers": "python_version < '3.8'", - "version": "==1.4.0" + "version": "==1.5.0" }, "mccabe": { "hashes": [ @@ -789,14 +755,6 @@ ], "version": "==0.6.1" }, - "more-itertools": { - "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" - ], - "index": "pypi", - "version": "==7.2.0" - }, "nodeenv": { "hashes": [ "sha256:561057acd4ae3809e665a9aaaf214afff110bbb6a6d5c8a96121aea6878408b3" @@ -805,10 +763,10 @@ }, "packaging": { "hashes": [ - "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", - "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8" + "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", + "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" ], - "version": "==20.0" + "version": "==20.1" }, "pre-commit": { "hashes": [ @@ -881,10 +839,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -902,29 +860,30 @@ }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" ], "markers": "python_version < '3.8'", - "version": "==1.4.0" + "version": "==1.4.1" }, "unittest-xml-reporting": { "hashes": [ @@ -951,10 +910,10 @@ }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", + "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" ], - "version": "==0.6.0" + "version": "==2.1.0" } } } diff --git a/bot/__main__.py b/bot/__main__.py index 61271a692..84bc7094b 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -40,7 +40,6 @@ bot.load_extension("bot.cogs.duck_pond") bot.load_extension("bot.cogs.free") bot.load_extension("bot.cogs.information") bot.load_extension("bot.cogs.jams") -bot.load_extension("bot.cogs.metrics") bot.load_extension("bot.cogs.moderation") bot.load_extension("bot.cogs.off_topic_names") bot.load_extension("bot.cogs.reddit") diff --git a/bot/bot.py b/bot/bot.py index 930aaf70e..8f808272f 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -4,7 +4,6 @@ from typing import Optional import aiohttp from discord.ext import commands -from prometheus_async.aio.web import start_http_server as start_prometheus_http_server from bot import api @@ -51,6 +50,4 @@ class Bot(commands.Bot): """Open an aiohttp session before logging in and connecting to Discord.""" self.http_session = aiohttp.ClientSession(connector=self.connector) - await start_prometheus_http_server(addr="0.0.0.0", port=9330) - log.debug("Started Prometheus server on port 9330.") await super().start(*args, **kwargs) diff --git a/bot/cogs/metrics.py b/bot/cogs/metrics.py deleted file mode 100644 index 47c3cc55e..000000000 --- a/bot/cogs/metrics.py +++ /dev/null @@ -1,98 +0,0 @@ -from collections import defaultdict - -from discord import Member, Message -from discord.ext.commands import Cog, Context -from prometheus_client import Counter, Gauge - -from bot.bot import Bot - - -class Metrics(Cog): - """ - Exports metrics for Prometheus. - - See https://github.com/prometheus/client_python for metric documentation. - """ - - PREFIX = 'pydis_bot' - - def __init__(self, bot: Bot) -> None: - self.bot = bot - - self.guild_members = Gauge( - name=f'{self.PREFIX}_guild_members', - documentation="Total members by guild by status.", - labelnames=('guild_id', 'status') - ) - self.guild_messages = Counter( - name=f'{self.PREFIX}_guild_messages', - documentation="Guild messages by guild by channel.", - labelnames=('channel_id', 'guild_id', 'channel_name') - ) - self.command_completions = Counter( - name=f'{self.PREFIX}_command_completions', - documentation="Completed commands by command, user, and guild.", - labelnames=('guild_id', 'user_id', 'user_name', 'command') - ) - - @Cog.listener() - async def on_ready(self) -> None: - """Initialize the guild member counter.""" - members_by_status = defaultdict(lambda: defaultdict(int)) - - for guild in self.bot.guilds: - if guild.large: - await self.bot.request_offline_members(guild) - for member in guild.members: - members_by_status[guild.id][member.status] += 1 - - for guild_id, members in members_by_status.items(): - for status, count in members.items(): - self.guild_members.labels(guild_id=guild_id, status=str(status)).set(count) - - @Cog.listener() - async def on_member_join(self, member: Member) -> None: - """Increment the member gauge.""" - self.guild_members.labels(guild_id=member.guild.id, status=str(member.status)).inc() - - @Cog.listener() - async def on_member_leave(self, member: Member) -> None: - """Decrement the member gauge.""" - self.guild_members.labels(guild_id=member.guild.id, status=str(member.status)).dec() - - @Cog.listener() - async def on_member_update(self, before: Member, after: Member) -> None: - """Update member gauges for the new and old status if applicable.""" - if before.status is not after.status: - self.guild_members.labels(guild_id=after.guild.id, status=str(before.status)).dec() - self.guild_members.labels(guild_id=after.guild.id, status=str(after.status)).inc() - - @Cog.listener() - async def on_message(self, message: Message) -> None: - """Increment the guild message counter.""" - self.guild_messages.labels( - channel_id=message.channel.id, - channel_name=message.channel.name, - guild_id=message.guild.id, - ).inc() - - @Cog.listener() - async def on_command_completion(self, ctx: Context) -> None: - """Increment the command completion counter.""" - if ctx.message.guild is not None: - if ctx.command.full_parent_name: - command = f'{ctx.command.full_parent_name} {ctx.command.name}' - else: - command = ctx.command.name - - self.command_completions.labels( - guild_id=ctx.message.guild.id, - user_id=ctx.author.id, - user_name=str(ctx.author), - command=command, - ).inc() - - -def setup(bot: Bot) -> None: - """Load the Metrics cog.""" - bot.add_cog(Metrics(bot)) -- cgit v1.2.3 From 6ec42d8cc7fe25e56919374c2c987d1284806470 Mon Sep 17 00:00:00 2001 From: Joseph Date: Sun, 2 Feb 2020 22:23:01 +0000 Subject: Update CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8aa168278..cf5f1590d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @core-developers +* @python-discord/core-developers -- cgit v1.2.3 From ae388820dc45f12b6486dfa793aecb9ba5f0345b Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Mon, 3 Feb 2020 13:27:07 +0000 Subject: Replace constant attachment-repost by the actual ID of #attachment-log --- bot/cogs/antispam.py | 2 +- bot/constants.py | 2 +- config-default.yml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 80f82ab91..bbc1a5359 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -61,7 +61,7 @@ class DeletionContext: self.messages[message.id] = message # Re-upload attachments - destination = message.guild.get_channel(GuildConfig.attachment_repost) + destination = message.guild.get_channel(Channels.attachment_log) urls = await send_attachments(message, destination, link_large=False) self.attachments.append(urls) diff --git a/bot/constants.py b/bot/constants.py index 9a4522262..2050359cd 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -347,6 +347,7 @@ class Channels(metaclass=YAMLGetter): admins: int admin_spam: int announcements: int + attachment_repost: int big_brother_logs: int bot: int checkpoint_test: int @@ -417,7 +418,6 @@ class Guild(metaclass=YAMLGetter): id: int ignored: List[int] staff_channels: List[int] - attachment_repost: int class Keys(metaclass=YAMLGetter): diff --git a/config-default.yml b/config-default.yml index 0ecf88702..5e85c897d 100644 --- a/config-default.yml +++ b/config-default.yml @@ -138,8 +138,7 @@ guild: verification: 352442727016693763 staff_channels: [*ADMINS, *ADMIN_SPAM, *MOD_SPAM, *MODS, *HELPERS, *ORGANISATION, *DEFCON] - ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG] - attachment_repost: *ATTCH_LOG + ignored: [*ADMINS, *MESSAGE_LOG, *MODLOG, *ATTCH_LOG] roles: admin: &ADMIN_ROLE 267628507062992896 -- cgit v1.2.3 From 891257d1d9fc903fc007272b32bf15f315fffe99 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Mon, 3 Feb 2020 13:40:49 +0000 Subject: Change typehint name for the attachment-log constant --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/constants.py b/bot/constants.py index 223ebdaea..629985bdf 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -359,7 +359,7 @@ class Channels(metaclass=YAMLGetter): admins: int admin_spam: int announcements: int - attachment_repost: int + attachment_log: int big_brother_logs: int bot: int checkpoint_test: int -- cgit v1.2.3 From 2dc728e2d43448c67d87aa32e12598e5068c4353 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Tue, 4 Feb 2020 00:53:00 +1000 Subject: Use a trailing underscore to avoid name conflicts. Previously used a leading underscore, but that's usually meant for non-used names. --- bot/cogs/antispam.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index bbc1a5359..f67ef6f05 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -260,10 +260,10 @@ class AntiSpam(Cog): await deletion_context.upload_messages(self.bot.user.id, self.mod_log) -def validate_config(_rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: +def validate_config(rules_: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: """Validates the antispam configs.""" validation_errors = {} - for name, config in _rules.items(): + for name, config in rules_.items(): if name not in RULE_FUNCTION_MAPPING: log.error( f"Unrecognized antispam rule `{name}`. " -- cgit v1.2.3 From 7f0e6733de8e2b6c3d13834916d790673547e1fb Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Tue, 4 Feb 2020 12:23:29 +0700 Subject: Fixed _last_fetch not being updated after each api call. - Changed type of `self._last_fetch` to `float` and give it the initial value of `0.0` instead of `None` - Assigned `time.time()` to `time_now` to avoid calling this function twice. - Added `self._last_fetch = time_now` after calling the api call. --- bot/cogs/tags.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 7effaf754..9e06b702c 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -27,14 +27,16 @@ class Tags(Cog): self.tag_cooldowns = {} self._cache = {} - self._last_fetch = None + self._last_fetch: float = 0.0 async def _get_tags(self, is_forced: bool = False) -> None: - """Getting all tags.""" - # Refresh only when there's a more than 5m gap from last call. - if is_forced or not self._last_fetch or time.time() - self._last_fetch > 5 * 60: + """Get all tags.""" + # refresh only when there's a more than 5m gap from last call. + time_now: float = time.time() + if is_forced or not self._last_fetch or time_now - self._last_fetch > 5 * 60: tags = await self.bot.api_client.get('bot/tags') self._cache = {tag['title'].lower(): tag for tag in tags} + self._last_fetch = time_now @staticmethod def _fuzzy_search(search: str, target: str) -> bool: -- cgit v1.2.3 From 868de4716c5b6a3120f665d460a8987bd6f16302 Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Tue, 4 Feb 2020 12:26:27 +0700 Subject: Refactored _get_suggestions following Mark's suggestions about inefficiency. - Matching scores will be calculated once now and stored in the dict `scores`. - Allow `_get_suggestions()` to go through a list of score threshold and return the first list of matching tags that's not empty and above the threshold. This avoid calling the function multiple time like before ( `self._get_suggestions(tag_name, 100) or self._get_suggestions(tag_name, 80)` for example, is calling this function twice, and is inefficient ) - Deleted commented line. - Added `typing` module for more typehints. --- bot/cogs/tags.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 9e06b702c..8d3586b19 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -1,5 +1,6 @@ import logging import time +from typing import Dict, List, Optional from discord import Colour, Embed from discord.ext.commands import Cog, Context, group @@ -39,9 +40,9 @@ class Tags(Cog): self._last_fetch = time_now @staticmethod - def _fuzzy_search(search: str, target: str) -> bool: - found = 0 - index = 0 + def _fuzzy_search(search: str, target: str) -> int: + """A simple scoring algorithm based on how many letters are found / total, with order in mind.""" + found, index = 0, 0 _search = search.lower().replace(' ', '') _target = target.lower().replace(' ', '') for letter in _search: @@ -51,19 +52,32 @@ class Tags(Cog): found += index > 0 return found / len(_search) * 100 - def _get_suggestions(self, tag_name: str, score: int) -> list: - return sorted( - (tag for tag in self._cache.values() if Tags._fuzzy_search(tag_name, tag['title']) >= score), - key=lambda tag: Tags._fuzzy_search(tag_name, tag['title']), - reverse=True - ) + def _get_suggestions(self, tag_name: str, thresholds: Optional[List[int]] = None) -> List[str]: + """Return a list of suggested tags.""" + scores: Dict[str, int] = { + tag_title: Tags._fuzzy_search(tag_name, tag['title']) + for tag_title, tag in self._cache.items() + } + + thresholds = thresholds or [100, 80] + + for threshold in thresholds: + suggestions = [ + self._cache[tag_title] + for tag_title, matching_score in scores.items() + if matching_score >= threshold + ] + if suggestions: + return suggestions + + return [] async def _get_tag(self, tag_name: str) -> list: """Get a specific tag.""" await self._get_tags() found = [self._cache.get(tag_name.lower(), None)] if not found[0]: - return self._get_suggestions(tag_name, 100) or self._get_suggestions(tag_name, 80) + return self._get_suggestions(tag_name, thresholds=[100, 80]) return found @group(name='tags', aliases=('tag', 't'), invoke_without_command=True) @@ -102,7 +116,6 @@ class Tags(Cog): await self._get_tags() if tag_name is not None: - # tag = await self.bot.api_client.get(f'bot/tags/{tag_name}') founds = await self._get_tag(tag_name) if len(founds) == 1: @@ -120,7 +133,6 @@ class Tags(Cog): )) else: - # tags = await self.bot.api_client.get('bot/tags') tags = self._cache.values() if not tags: await ctx.send(embed=Embed( -- cgit v1.2.3 From a38926fe797cdcc13d64d836776f56db09e9efd2 Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Wed, 5 Feb 2020 04:00:46 +0700 Subject: Removed non-alphabets from both search and tag_name when scoring. - Added a regex to remove non-alphabet ( `[^a-z]` with `re.IGNORECASE` ) --- bot/cogs/tags.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 8d3586b19..0e8cf0278 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -1,4 +1,5 @@ import logging +import re import time from typing import Dict, List, Optional @@ -19,6 +20,8 @@ TEST_CHANNELS = ( Channels.helpers ) +REGEX_NON_ALPHABET = re.compile(r"[^a-z]", re.IGNORECASE & re.MULTILINE) + class Tags(Cog): """Save new tags and fetch existing tags.""" @@ -43,8 +46,8 @@ class Tags(Cog): def _fuzzy_search(search: str, target: str) -> int: """A simple scoring algorithm based on how many letters are found / total, with order in mind.""" found, index = 0, 0 - _search = search.lower().replace(' ', '') - _target = target.lower().replace(' ', '') + _search = REGEX_NON_ALPHABET.sub('', search.lower()) + _target = REGEX_NON_ALPHABET.sub('', target.lower()) for letter in _search: index = _target.find(letter, index) if index == -1: -- cgit v1.2.3 From a6341b13cb3b3fc3ea95942a51478e875205abc6 Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Wed, 5 Feb 2020 04:01:41 +0700 Subject: Increased default thresholds from just [100, 80] to [100, 90, 80, 70, 60] - Since it is returning as soon as there are suggestions found for a threshold, this will give a better reflection of what the bot thinks user is searching for. --- bot/cogs/tags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 0e8cf0278..8122f739e 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -62,7 +62,7 @@ class Tags(Cog): for tag_title, tag in self._cache.items() } - thresholds = thresholds or [100, 80] + thresholds = thresholds or [100, 90, 80, 70, 60] for threshold in thresholds: suggestions = [ @@ -80,7 +80,7 @@ class Tags(Cog): await self._get_tags() found = [self._cache.get(tag_name.lower(), None)] if not found[0]: - return self._get_suggestions(tag_name, thresholds=[100, 80]) + return self._get_suggestions(tag_name) return found @group(name='tags', aliases=('tag', 't'), invoke_without_command=True) -- cgit v1.2.3 From c054790975670ee9e2b1855590d01491f3732b33 Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Wed, 5 Feb 2020 11:48:07 +0700 Subject: Removed regex, implemented a stricter letter searching. --- bot/cogs/tags.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 8122f739e..eaf307569 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -1,5 +1,4 @@ import logging -import re import time from typing import Dict, List, Optional @@ -20,8 +19,6 @@ TEST_CHANNELS = ( Channels.helpers ) -REGEX_NON_ALPHABET = re.compile(r"[^a-z]", re.IGNORECASE & re.MULTILINE) - class Tags(Cog): """Save new tags and fetch existing tags.""" @@ -46,13 +43,18 @@ class Tags(Cog): def _fuzzy_search(search: str, target: str) -> int: """A simple scoring algorithm based on how many letters are found / total, with order in mind.""" found, index = 0, 0 - _search = REGEX_NON_ALPHABET.sub('', search.lower()) - _target = REGEX_NON_ALPHABET.sub('', target.lower()) - for letter in _search: - index = _target.find(letter, index) - if index == -1: - break - found += index > 0 + _search = search.lower().replace(' ', '') + _targets = iter(target.lower()) + _target = next(_targets) + try: + for letter in _search: + index = _target.find(letter, index) + while index == -1: + _target = next(_targets) + index = _target.find(letter) + found += 1 + except StopIteration: + pass return found / len(_search) * 100 def _get_suggestions(self, tag_name: str, thresholds: Optional[List[int]] = None) -> List[str]: -- cgit v1.2.3 From 8dd66bc12ecae678c2f17819b298b60823806b95 Mon Sep 17 00:00:00 2001 From: Shirayuki Nekomata Date: Wed, 5 Feb 2020 14:03:54 +0700 Subject: Made searching even stricter by searching from start of each word - Added regex back to sub and split by non-alphabet. - Now use two pointers to move from words to words. --- bot/cogs/tags.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index eaf307569..54a51921c 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -1,4 +1,5 @@ import logging +import re import time from typing import Dict, List, Optional @@ -19,6 +20,8 @@ TEST_CHANNELS = ( Channels.helpers ) +REGEX_NON_ALPHABET = re.compile(r"[^a-z]", re.MULTILINE & re.IGNORECASE) + class Tags(Cog): """Save new tags and fetch existing tags.""" @@ -42,20 +45,19 @@ class Tags(Cog): @staticmethod def _fuzzy_search(search: str, target: str) -> int: """A simple scoring algorithm based on how many letters are found / total, with order in mind.""" - found, index = 0, 0 - _search = search.lower().replace(' ', '') - _targets = iter(target.lower()) + current, index = 0, 0 + _search = REGEX_NON_ALPHABET.sub('', search.lower()) + _targets = iter(REGEX_NON_ALPHABET.split(target.lower())) _target = next(_targets) try: - for letter in _search: - index = _target.find(letter, index) - while index == -1: - _target = next(_targets) - index = _target.find(letter) - found += 1 - except StopIteration: + while True: + while index < len(_target) and _search[current] == _target[index]: + current += 1 + index += 1 + index, _target = 0, next(_targets) + except (StopIteration, IndexError): pass - return found / len(_search) * 100 + return current / len(_search) * 100 def _get_suggestions(self, tag_name: str, thresholds: Optional[List[int]] = None) -> List[str]: """Return a list of suggested tags.""" -- cgit v1.2.3 From df023e0b03795a2a7f30d3ab523fc2998f226234 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 5 Feb 2020 22:45:18 +1000 Subject: Move tools and questions guide to under resources After the wiki pages adjustment to the resources page, these two urls are needing to be updated to point to the new correct locations for each page. Tools will be under resources, and Asking Good Questions is a guide, so will be under Guides. --- bot/cogs/site.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 2ea8c7a2e..10180ebae 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -59,7 +59,7 @@ class Site(Cog): @site_group.command(name="tools") async def site_tools(self, ctx: Context) -> None: """Info about the site's Tools page.""" - tools_url = f"{PAGES_URL}/tools" + tools_url = f"{PAGES_URL}/resources/tools" embed = Embed(title="Tools") embed.set_footer(text=f"{tools_url}") @@ -74,7 +74,7 @@ class Site(Cog): @site_group.command(name="help") async def site_help(self, ctx: Context) -> None: """Info about the site's Getting Help page.""" - url = f"{PAGES_URL}/asking-good-questions" + url = f"{PAGES_URL}resources/guides/asking-good-questions" embed = Embed(title="Asking Good Questions") embed.set_footer(text=url) -- cgit v1.2.3 From b9c691646a8be52095f644ba2cd7ffd8eb36ae6e Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 6 Feb 2020 01:01:29 +1000 Subject: Add missing slash to asking good questions url. --- bot/cogs/site.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 10180ebae..853e29568 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -74,7 +74,7 @@ class Site(Cog): @site_group.command(name="help") async def site_help(self, ctx: Context) -> None: """Info about the site's Getting Help page.""" - url = f"{PAGES_URL}resources/guides/asking-good-questions" + url = f"{PAGES_URL}/resources/guides/asking-good-questions" embed = Embed(title="Asking Good Questions") embed.set_footer(text=url) -- cgit v1.2.3 From e70b29ef9549202c85c84011d5282eec2dcb2418 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 7 Feb 2020 12:23:20 -0800 Subject: Update discord.py to 1.3.1 --- Pipfile | 2 +- Pipfile.lock | 71 ++++++++++++++++++++++++++++-------------------------------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/Pipfile b/Pipfile index 48d839fc3..318e77438 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -discord-py = "~=1.2" +discord-py = ">=1.3.1, ==1.*" aiodns = "~=2.0" logmatic-python = "~=0.1" aiohttp = "~=3.5" diff --git a/Pipfile.lock b/Pipfile.lock index 279480d2a..f7b19737c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c27d699b4aeeed204dee41f924f682ae2a670add8549a8826e58776594370582" + "sha256": "d344e66ca0980e7985dee8c168f9e0f9d7fd1b41b88778a094bacf5afb6d33bd" }, "pipfile-spec": 6, "requires": { @@ -102,41 +102,36 @@ }, "cffi": { "hashes": [ - "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", - "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", - "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", - "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", - "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", - "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", - "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", - "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", - "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", - "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", - "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", - "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", - "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", - "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", - "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", - "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", - "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", - "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", - "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", - "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", - "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", - "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", - "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", - "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", - "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", - "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", - "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", - "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", - "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", - "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", - "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", - "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", - "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" - ], - "version": "==1.13.2" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" + ], + "version": "==1.14.0" }, "chardet": { "hashes": [ @@ -757,9 +752,9 @@ }, "nodeenv": { "hashes": [ - "sha256:561057acd4ae3809e665a9aaaf214afff110bbb6a6d5c8a96121aea6878408b3" + "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212" ], - "version": "==1.3.4" + "version": "==1.3.5" }, "packaging": { "hashes": [ -- cgit v1.2.3 From 4e07c31fa018e3186e24f5d3d63b6a640821ed7a Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 7 Feb 2020 12:50:54 -0800 Subject: Pin discord.py to 1.3.x --- Pipfile | 2 +- Pipfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 318e77438..7fd3efae8 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -discord-py = ">=1.3.1, ==1.*" +discord-py = "~=1.3.1" aiodns = "~=2.0" logmatic-python = "~=0.1" aiohttp = "~=3.5" diff --git a/Pipfile.lock b/Pipfile.lock index f7b19737c..bf8ff47e9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d344e66ca0980e7985dee8c168f9e0f9d7fd1b41b88778a094bacf5afb6d33bd" + "sha256": "0a0354a8cbd25b19c61b68f928493a445e737dc6447c97f4c4b52fbf72d887ac" }, "pipfile-spec": 6, "requires": { -- cgit v1.2.3 From 9efd88047ed9201ab6bb6077de10b39490b9434f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 8 Feb 2020 07:40:26 -0800 Subject: Remove clear reaction from paginators It could be confused with the delete reaction. Clearing reactions manually is rarely a useful feature anyway. --- bot/cogs/help.py | 7 +------ bot/pagination.py | 12 +----------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index ecf14d131..fd5bbc3ca 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -14,7 +14,7 @@ from bot.bot import Bot from bot.constants import Channels, Emojis, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import ( - CLEAR_EMOJI, FIRST_EMOJI, LAST_EMOJI, + FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, ) @@ -25,7 +25,6 @@ REACTIONS = { LEFT_EMOJI: 'back', RIGHT_EMOJI: 'next', LAST_EMOJI: 'end', - CLEAR_EMOJI: 'clear', DELETE_EMOJI: 'stop', } @@ -499,10 +498,6 @@ class HelpSession: if not self.is_last_page: await self.update_page(len(self._pages)-1) - async def do_clear(self) -> None: - """Event that is called when the user clears the emojis from the pagination.""" - await self.message.clear_reactions() - async def do_stop(self) -> None: """Event that is called when the user requests to stop the help session.""" await self.message.delete() diff --git a/bot/pagination.py b/bot/pagination.py index a7938fe85..35870c040 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -12,10 +12,9 @@ FIRST_EMOJI = "\u23EE" # [:track_previous:] LEFT_EMOJI = "\u2B05" # [:arrow_left:] RIGHT_EMOJI = "\u27A1" # [:arrow_right:] LAST_EMOJI = "\u23ED" # [:track_next:] -CLEAR_EMOJI = "\u274c" # [:x:] DELETE_EMOJI = constants.Emojis.trashcan # [:trashcan:] -PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, CLEAR_EMOJI, DELETE_EMOJI] +PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI] log = logging.getLogger(__name__) @@ -206,10 +205,6 @@ class LinePaginator(Paginator): log.debug("Timed out waiting for a reaction") break # We're done, no reactions for the last 5 minutes - if reaction.emoji == CLEAR_EMOJI: - log.debug("Got clear reaction") - break - if str(reaction.emoji) == DELETE_EMOJI: log.debug("Got delete reaction") return await message.delete() @@ -395,11 +390,6 @@ class ImagePaginator(Paginator): # Deletes the users reaction await message.remove_reaction(reaction.emoji, user) - # Clear reaction press - [:x:] - if reaction.emoji == CLEAR_EMOJI: - log.debug("Got clear reaction") - break - # Delete reaction press - [:trashcan:] if str(reaction.emoji) == DELETE_EMOJI: log.debug("Got delete reaction") -- cgit v1.2.3 From a18def89e2f7808234a259a3cc15f11f1bf5db09 Mon Sep 17 00:00:00 2001 From: mosguinz Date: Wed, 12 Feb 2020 19:25:06 +0700 Subject: Fix pagniation module for "last page" reaction Fixes #746. --- bot/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/pagination.py b/bot/pagination.py index 35870c040..e82763912 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -410,7 +410,7 @@ class ImagePaginator(Paginator): log.debug("Got last page reaction, but we're on the last page - ignoring") continue - current_page = len(paginator.pages - 1) + current_page = len(paginator.pages) - 1 reaction_type = "last" # Previous reaction press - [:arrow_left: ] -- cgit v1.2.3 From 5c70f10a08c7d8621b862c9f975bc56c80607b49 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 14 Feb 2020 10:41:18 +0100 Subject: Stop scheduling expiration of permanent infractions on edit https://github.com/python-discord/bot/issues/751 The infraction edit command defined in `bot.cogs.moderation.management` contained a bug causing it to attempt to schedule an expiration task when turning a temporary infraction into a permanent infraction. Since the "expires_at" field of a permanent infractions is `None`, this caused an exception to occur in the scheduler: Traceback (most recent call last): File "/bot/bot/cogs/moderation/scheduler.py", line 415, in _scheduled_task expiry = dateutil.parser.isoparse(infraction["expires_at"]).replace(tzinfo=None) File "/usr/local/lib/python3.7/site-packages/dateutil/parser/isoparser.py", line 37, in func return f(self, str_in, *args, **kwargs) File "/usr/local/lib/python3.7/site-packages/dateutil/parser/isoparser.py", line 134, in isoparse components, pos = self._parse_isodate(dt_str) File "/usr/local/lib/python3.7/site-packages/dateutil/parser/isoparser.py", line 208, in _parse_isodate return self._parse_isodate_common(dt_str) File "/usr/local/lib/python3.7/site-packages/dateutil/parser/isoparser.py", line 213, in _parse_isodate_common len_str = len(dt_str) TypeError: object of type 'NoneType' has no len() I have solved this by adding a check that makes sure we only schedule an expiration task when the `"expires_at"` field has a truthy value (which all valid datetime strings are) using `if request_data['expires_at']`. IMPORTANT NOTE: While it's tempting to just skip the entire scheduling block for permanent infractions, it's essential to unschedule existing expiration tasks for this infraction as we're changing a temporary infraction to a permanent infraction. This commit closes #751 --- bot/cogs/moderation/management.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 0636422d3..f2964cd78 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -130,8 +130,11 @@ class ModManagement(commands.Cog): # Re-schedule infraction if the expiration has been updated if 'expires_at' in request_data: self.infractions_cog.cancel_task(new_infraction['id']) - loop = asyncio.get_event_loop() - self.infractions_cog.schedule_task(loop, new_infraction['id'], new_infraction) + + # If the infraction was not marked as permanent, schedule a new expiration task + if request_data['expires_at']: + loop = asyncio.get_event_loop() + self.infractions_cog.schedule_task(loop, new_infraction['id'], new_infraction) log_text += f""" Previous expiry: {old_infraction['expires_at'] or "Permanent"} -- cgit v1.2.3 From d955ecc449eee54cfd28504403e569f58707588e Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Mon, 17 Feb 2020 16:51:00 +1000 Subject: Change snekbox api url to internal docker domain. NGINX has been dockerised, and proxy passes now reference internal container domains rather than referencing host or external domains. This will have a few extra benefits: - Less external factors involved for resolving the service address - Can work with the same address on development envs - Snekbox can be closed down entirely so it's inaccessible from external networks. --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index fda14b511..3345e6f2a 100644 --- a/config-default.yml +++ b/config-default.yml @@ -302,7 +302,7 @@ urls: paste_service: !JOIN [*SCHEMA, *PASTE, "/{key}"] # Snekbox - snekbox_eval_api: "https://snekbox.pythondiscord.com/eval" + snekbox_eval_api: "http://snekbox:8060/eval" # Discord API URLs discord_api: &DISCORD_API "https://discordapp.com/api/v7/" -- cgit v1.2.3