From 1b6f3d23d4c0b1f6dfe1354a3a210e589f7b4956 Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Mon, 7 Oct 2019 18:40:02 +0200 Subject: Make sure that poor code does not contains token Added a new function `is_token_in_message` in `token_remover`. This function returns a `bool` and if the code contains a token then the embed message about the poorly formatted code is not displayed. --- bot/cogs/bot.py | 3 ++- bot/cogs/token_remover.py | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 7583b2f2d..e8ac0a234 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -7,6 +7,7 @@ from typing import Optional, Tuple from discord import Embed, Message, RawMessageUpdateEvent from discord.ext.commands import Bot, Cog, Context, command, group +from bot.cogs.token_remover import TokenRemover from bot.constants import Channels, DEBUG_MODE, Guild, MODERATION_ROLES, Roles, URLs from bot.decorators import with_role from bot.utils.messages import wait_for_deletion @@ -237,7 +238,7 @@ class Bot(Cog): and len(msg.content.splitlines()) > 3 ) - if parse_codeblock: + if parse_codeblock and not TokenRemover.is_token_in_message: # if there is no token in the code on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 7dd0afbbd..8f356cf19 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -52,19 +52,8 @@ class TokenRemover(Cog): See: https://discordapp.com/developers/docs/reference#snowflakes """ - if msg.author.bot: - return - - maybe_match = TOKEN_RE.search(msg.content) - if maybe_match is None: - return - - try: - user_id, creation_timestamp, hmac = maybe_match.group(0).split('.') - except ValueError: - return - - if self.is_valid_user_id(user_id) and self.is_valid_timestamp(creation_timestamp): + if self.is_token_in_message(msg): + user_id, creation_timestamp, hmac = TOKEN_RE.search(msg.content).group(0).split('.') self.mod_log.ignore(Event.message_delete, msg.id) await msg.delete() await msg.channel.send(DELETION_MESSAGE_TEMPLATE.format(mention=msg.author.mention)) @@ -86,6 +75,23 @@ class TokenRemover(Cog): channel_id=Channels.mod_alerts, ) + def is_token_in_message(self, msg: Message) -> bool: + """Check if `msg` contains a seemly valid token.""" + if msg.author.bot: + return False + + maybe_match = TOKEN_RE.search(msg.content) + if maybe_match is None: + return False + + try: + user_id, creation_timestamp, hmac = maybe_match.group(0).split('.') + except ValueError: + return False + + if self.is_valid_user_id(user_id) and self.is_valid_timestamp(creation_timestamp): + return True + @staticmethod def is_valid_user_id(b64_content: str) -> bool: """ -- cgit v1.2.3 From 2899bac85c3c0529b354a762ba27a587a520d7cd Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Mon, 7 Oct 2019 18:51:18 +0200 Subject: minor fix --- bot/cogs/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index e8ac0a234..729550c1a 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -238,7 +238,7 @@ class Bot(Cog): and len(msg.content.splitlines()) > 3 ) - if parse_codeblock and not TokenRemover.is_token_in_message: # if there is no token in the code + if parse_codeblock and not TokenRemover.is_token_in_message(msg): # if there is no token in the code on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: -- cgit v1.2.3 From b94e8487c22d7c25ab09bb3d44c44d62e5a2b613 Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Mon, 7 Oct 2019 21:33:43 +0200 Subject: Another fix After a new bunch of test I found bugs, and this fix resolves them --- bot/cogs/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 729550c1a..b8de29f2a 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -238,7 +238,7 @@ class Bot(Cog): and len(msg.content.splitlines()) > 3 ) - if parse_codeblock and not TokenRemover.is_token_in_message(msg): # if there is no token in the code + if parse_codeblock and not TokenRemover.is_token_in_message(TokenRemover, msg): # if there is no token in the code on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: -- cgit v1.2.3 From 25d4c05b2656ce8d9454269c77d42e18fb1ba785 Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Mon, 7 Oct 2019 21:42:49 +0200 Subject: fix linting error fix linting error --- bot/cogs/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index b8de29f2a..eab253681 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -238,7 +238,7 @@ class Bot(Cog): and len(msg.content.splitlines()) > 3 ) - if parse_codeblock and not TokenRemover.is_token_in_message(TokenRemover, msg): # if there is no token in the code + if parse_codeblock and not TokenRemover.is_token_in_message(TokenRemover, msg): # no token in the msg on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: -- cgit v1.2.3 From aa0096469e12546b0eadbb4b214cd3cae3a3a80d Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Sat, 12 Oct 2019 14:54:58 +0200 Subject: Use a `classmethod` --- bot/cogs/bot.py | 2 +- bot/cogs/token_remover.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index eab253681..53221cd8b 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -238,7 +238,7 @@ class Bot(Cog): and len(msg.content.splitlines()) > 3 ) - if parse_codeblock and not TokenRemover.is_token_in_message(TokenRemover, msg): # no token in the msg + if parse_codeblock and not TokenRemover.is_token_in_message(msg): # no token in the msg on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 8f356cf19..5e83a777e 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -75,6 +75,7 @@ class TokenRemover(Cog): channel_id=Channels.mod_alerts, ) + @classmethod def is_token_in_message(self, msg: Message) -> bool: """Check if `msg` contains a seemly valid token.""" if msg.author.bot: -- cgit v1.2.3 From bc907daa428d755d7f2cb0a6b945a179d523b31d Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Sun, 20 Oct 2019 21:35:38 +0200 Subject: Add check when a message is edited --- bot/cogs/token_remover.py | 60 +++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 5e83a777e..e5b0e5b45 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -53,30 +53,44 @@ class TokenRemover(Cog): See: https://discordapp.com/developers/docs/reference#snowflakes """ if self.is_token_in_message(msg): - user_id, creation_timestamp, hmac = TOKEN_RE.search(msg.content).group(0).split('.') - self.mod_log.ignore(Event.message_delete, msg.id) - await msg.delete() - await msg.channel.send(DELETION_MESSAGE_TEMPLATE.format(mention=msg.author.mention)) - - message = ( - "Censored a seemingly valid token sent by " - f"{msg.author} (`{msg.author.id}`) in {msg.channel.mention}, token was " - f"`{user_id}.{creation_timestamp}.{'x' * len(hmac)}`" - ) - log.debug(message) - - # Send pretty mod log embed to mod-alerts - await self.mod_log.send_log_message( - icon_url=Icons.token_removed, - colour=Colour(Colours.soft_red), - title="Token removed!", - text=message, - thumbnail=msg.author.avatar_url_as(static_format="png"), - channel_id=Channels.mod_alerts, - ) + await self.take_action(msg) + + @Cog.listener() + async def on_message_edit(self, before: Message, after: Message) -> None: + """ + Check each edit for a string that matches Discord's token pattern. + + See: https://discordapp.com/developers/docs/reference#snowflakes + """ + if self.is_token_in_message(after): + await self.take_action(after) + + async def take_action(self, msg: Message) -> None: + """Remove the `msg` containing a token an send a mod_log message.""" + user_id, creation_timestamp, hmac = TOKEN_RE.search(msg.content).group(0).split('.') + self.mod_log.ignore(Event.message_delete, msg.id) + await msg.delete() + await msg.channel.send(DELETION_MESSAGE_TEMPLATE.format(mention=msg.author.mention)) + + message = ( + "Censored a seemingly valid token sent by " + f"{msg.author} (`{msg.author.id}`) in {msg.channel.mention}, token was " + f"`{user_id}.{creation_timestamp}.{'x' * len(hmac)}`" + ) + log.debug(message) + + # Send pretty mod log embed to mod-alerts + await self.mod_log.send_log_message( + icon_url=Icons.token_removed, + colour=Colour(Colours.soft_red), + title="Token removed!", + text=message, + thumbnail=msg.author.avatar_url_as(static_format="png"), + channel_id=Channels.mod_alerts, + ) @classmethod - def is_token_in_message(self, msg: Message) -> bool: + def is_token_in_message(cls, msg: Message) -> bool: """Check if `msg` contains a seemly valid token.""" if msg.author.bot: return False @@ -90,7 +104,7 @@ class TokenRemover(Cog): except ValueError: return False - if self.is_valid_user_id(user_id) and self.is_valid_timestamp(creation_timestamp): + if cls.is_valid_user_id(user_id) and cls.is_valid_timestamp(creation_timestamp): return True @staticmethod -- cgit v1.2.3 From ad1a33e80152343a81eeeabf0117ced76b83e273 Mon Sep 17 00:00:00 2001 From: Daniel Brown Date: Thu, 5 Dec 2019 10:35:55 -0600 Subject: Added optional channel parameter to !echo: - Added the option to specify a channel to have Python repeat what you said to it, as well as keeping the old functionality of having it repeat what you said in the current channel if no channel argument is given. Signed-off-by: Daniel Brown --- bot/cogs/bot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 7583b2f2d..ee0a463de 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -4,7 +4,7 @@ import re import time from typing import Optional, Tuple -from discord import Embed, Message, RawMessageUpdateEvent +from discord import Embed, Message, RawMessageUpdateEvent, TextChannel from discord.ext.commands import Bot, Cog, Context, command, group from bot.constants import Channels, DEBUG_MODE, Guild, MODERATION_ROLES, Roles, URLs @@ -71,9 +71,12 @@ class Bot(Cog): @command(name='echo', aliases=('print',)) @with_role(*MODERATION_ROLES) - async def echo_command(self, ctx: Context, *, text: str) -> None: - """Send the input verbatim to the current channel.""" - await ctx.send(text) + async def echo_command(self, ctx: Context, channel: Optional[TextChannel], *, text: str) -> None: + """Repeat the given message in either a specified channel or the current channel.""" + if channel is None: + await ctx.send(text) + else: + await channel.send(text) @command(name='embed') @with_role(*MODERATION_ROLES) -- cgit v1.2.3 From 52163d775a6a0737f32a0c291e9275a910656fab Mon Sep 17 00:00:00 2001 From: kraktus <56031107+kraktus@users.noreply.github.com> Date: Thu, 5 Dec 2019 17:49:28 +0000 Subject: Requested change Include the check about whether or not there is a token in the posted message in `parse_codeblock` boolean. --- bot/cogs/bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 53221cd8b..f79e00454 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -236,9 +236,10 @@ class Bot(Cog): ) and not msg.author.bot and len(msg.content.splitlines()) > 3 + and not TokenRemover.is_token_in_message(msg) ) - if parse_codeblock and not TokenRemover.is_token_in_message(msg): # no token in the msg + if parse_codeblock: # no token in the msg on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: -- cgit v1.2.3 From d913a91531ba6414741d745303f89cb687cf345b Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Dec 2019 19:29:33 -0800 Subject: Subclass Bot --- bot/__main__.py | 26 ++------------------------ bot/bot.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 bot/bot.py diff --git a/bot/__main__.py b/bot/__main__.py index ea7c43a12..84bc7094b 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,18 +1,11 @@ -import asyncio -import logging -import socket - import discord -from aiohttp import AsyncResolver, ClientSession, TCPConnector -from discord.ext.commands import Bot, when_mentioned_or +from discord.ext.commands import when_mentioned_or from bot import patches -from bot.api import APIClient, APILoggingHandler +from bot.bot import Bot from bot.constants import Bot as BotConfig, DEBUG_MODE -log = logging.getLogger('bot') - bot = Bot( command_prefix=when_mentioned_or(BotConfig.prefix), activity=discord.Game(name="Commands: !help"), @@ -20,18 +13,6 @@ bot = Bot( max_messages=10_000, ) -# Global aiohttp session for all cogs -# - Uses asyncio for DNS resolution instead of threads, so we don't spam threads -# - Uses AF_INET as its socket family to prevent https related problems both locally and in prod. -bot.http_session = ClientSession( - connector=TCPConnector( - resolver=AsyncResolver(), - family=socket.AF_INET, - ) -) -bot.api_client = APIClient(loop=asyncio.get_event_loop()) -log.addHandler(APILoggingHandler(bot.api_client)) - # Internal/debug bot.load_extension("bot.cogs.error_handler") bot.load_extension("bot.cogs.filtering") @@ -77,6 +58,3 @@ if not hasattr(discord.message.Message, '_handle_edited_timestamp'): patches.message_edited_at.apply_patch() bot.run(BotConfig.token) - -# This calls a coroutine, so it doesn't do anything at the moment. -# bot.http_session.close() # Close the aiohttp session when the bot finishes running diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 000000000..05734ac1d --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,30 @@ +import asyncio +import logging +import socket + +import aiohttp +from discord.ext import commands + +from bot import api + +log = logging.getLogger('bot') + + +class Bot(commands.Bot): + """A subclass of `discord.ext.commands.Bot` with an aiohttp session and an API client.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Global aiohttp session for all cogs + # - Uses asyncio for DNS resolution instead of threads, so we don't spam threads + # - Uses AF_INET as its socket family to prevent https related problems both locally and in prod. + self.http_session = aiohttp.ClientSession( + connector=aiohttp.TCPConnector( + resolver=aiohttp.AsyncResolver(), + family=socket.AF_INET, + ) + ) + + self.api_client = api.APIClient(loop=asyncio.get_event_loop()) + log.addHandler(api.APILoggingHandler(self.api_client)) -- cgit v1.2.3 From 6fe61e5919cb541a1651312a01ddf7e7f10d0f86 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Dec 2019 20:11:50 -0800 Subject: Change all Bot imports to use the subclass --- bot/cogs/alias.py | 3 ++- bot/cogs/antimalware.py | 3 ++- bot/cogs/antispam.py | 3 ++- bot/cogs/bot.py | 3 ++- bot/cogs/clean.py | 3 ++- bot/cogs/defcon.py | 3 ++- bot/cogs/doc.py | 5 +++-- bot/cogs/duck_pond.py | 3 ++- bot/cogs/error_handler.py | 3 ++- bot/cogs/eval.py | 3 ++- bot/cogs/extensions.py | 3 ++- bot/cogs/filtering.py | 3 ++- bot/cogs/free.py | 3 ++- bot/cogs/help.py | 3 ++- bot/cogs/information.py | 3 ++- bot/cogs/jams.py | 5 +++-- bot/cogs/logging.py | 3 ++- bot/cogs/moderation/__init__.py | 3 +-- bot/cogs/moderation/infractions.py | 3 ++- bot/cogs/moderation/management.py | 3 ++- bot/cogs/moderation/modlog.py | 3 ++- bot/cogs/moderation/scheduler.py | 3 ++- bot/cogs/moderation/superstarify.py | 3 ++- bot/cogs/off_topic_names.py | 3 ++- bot/cogs/reddit.py | 3 ++- bot/cogs/reminders.py | 3 ++- bot/cogs/security.py | 4 +++- bot/cogs/site.py | 3 ++- bot/cogs/snekbox.py | 3 ++- bot/cogs/sync/__init__.py | 3 +-- bot/cogs/sync/cog.py | 3 ++- bot/cogs/sync/syncers.py | 7 ++++--- bot/cogs/tags.py | 3 ++- bot/cogs/token_remover.py | 3 ++- bot/cogs/utils.py | 3 ++- bot/cogs/verification.py | 3 ++- bot/cogs/watchchannels/__init__.py | 3 +-- bot/cogs/watchchannels/bigbrother.py | 3 ++- bot/cogs/watchchannels/talentpool.py | 3 ++- bot/cogs/watchchannels/watchchannel.py | 3 ++- bot/cogs/wolfram.py | 7 ++++--- bot/interpreter.py | 4 +++- tests/helpers.py | 4 +++- 43 files changed, 92 insertions(+), 52 deletions(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 5190c559b..4ee5a2aed 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -3,8 +3,9 @@ import logging from typing import Union from discord import Colour, Embed, Member, User -from discord.ext.commands import Bot, Cog, Command, Context, clean_content, command, group +from discord.ext.commands import Cog, Command, Context, clean_content, command, group +from bot.bot import Bot from bot.cogs.extensions import Extension from bot.cogs.watchchannels.watchchannel import proxy_user from bot.converters import TagNameConverter diff --git a/bot/cogs/antimalware.py b/bot/cogs/antimalware.py index 602819191..03c1e28a1 100644 --- a/bot/cogs/antimalware.py +++ b/bot/cogs/antimalware.py @@ -1,8 +1,9 @@ import logging from discord import Embed, Message, NotFound -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog +from bot.bot import Bot from bot.constants import AntiMalware as AntiMalwareConfig, Channels, URLs log = logging.getLogger(__name__) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 1340eb608..88912038a 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -7,9 +7,10 @@ from operator import itemgetter from typing import Dict, Iterable, List, Set from discord import Colour, Member, Message, NotFound, Object, TextChannel -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog from bot import rules +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import ( AntiSpam as AntiSpamConfig, Channels, diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index ee0a463de..a2edb7576 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -5,8 +5,9 @@ import time from typing import Optional, Tuple from discord import Embed, Message, RawMessageUpdateEvent, TextChannel -from discord.ext.commands import Bot, Cog, Context, command, group +from discord.ext.commands import Cog, Context, command, group +from bot.bot import Bot from bot.constants import Channels, DEBUG_MODE, Guild, MODERATION_ROLES, Roles, URLs from bot.decorators import with_role from bot.utils.messages import wait_for_deletion diff --git a/bot/cogs/clean.py b/bot/cogs/clean.py index dca411d01..3365d0934 100644 --- a/bot/cogs/clean.py +++ b/bot/cogs/clean.py @@ -4,8 +4,9 @@ import re from typing import Optional from discord import Colour, Embed, Message, User -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import ( Channels, CleanMessages, Colours, Event, diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index bedd70c86..f062a7546 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -6,8 +6,9 @@ from datetime import datetime, timedelta from enum import Enum from discord import Colour, Embed, Member -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import Channels, Colours, Emojis, Event, Icons, Roles from bot.decorators import with_role diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index e5b3a4062..7df159fd9 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -17,6 +17,7 @@ from requests import ConnectTimeout, ConnectionError, HTTPError from sphinx.ext import intersphinx from urllib3.exceptions import ProtocolError +from bot.bot import Bot from bot.constants import MODERATION_ROLES, RedirectOutput from bot.converters import ValidPythonIdentifier, ValidURL from bot.decorators import with_role @@ -147,7 +148,7 @@ class InventoryURL(commands.Converter): class Doc(commands.Cog): """A set of commands for querying & displaying documentation.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.base_urls = {} self.bot = bot self.inventories = {} @@ -506,7 +507,7 @@ class Doc(commands.Cog): return tag.name == "table" -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Doc cog load.""" bot.add_cog(Doc(bot)) log.info("Cog loaded: Doc") diff --git a/bot/cogs/duck_pond.py b/bot/cogs/duck_pond.py index 2d25cd17e..879071d1b 100644 --- a/bot/cogs/duck_pond.py +++ b/bot/cogs/duck_pond.py @@ -3,9 +3,10 @@ from typing import Optional, Union import discord from discord import Color, Embed, Member, Message, RawReactionActionEvent, User, errors -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog from bot import constants +from bot.bot import Bot from bot.utils.messages import send_attachments log = logging.getLogger(__name__) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 49411814c..cf90e9f48 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -14,9 +14,10 @@ from discord.ext.commands import ( NoPrivateMessage, UserInputError, ) -from discord.ext.commands import Bot, Cog, Context +from discord.ext.commands import Cog, Context from bot.api import ResponseCodeError +from bot.bot import Bot from bot.constants import Channels from bot.decorators import InChannelCheckFailure diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 00b988dde..5daec3e39 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -9,8 +9,9 @@ from io import StringIO from typing import Any, Optional, Tuple import discord -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import Roles from bot.decorators import with_role from bot.interpreter import Interpreter diff --git a/bot/cogs/extensions.py b/bot/cogs/extensions.py index bb66e0b8e..4d77d8205 100644 --- a/bot/cogs/extensions.py +++ b/bot/cogs/extensions.py @@ -6,8 +6,9 @@ from pkgutil import iter_modules from discord import Colour, Embed from discord.ext import commands -from discord.ext.commands import Bot, Context, group +from discord.ext.commands import Context, group +from bot.bot import Bot from bot.constants import Emojis, MODERATION_ROLES, Roles, URLs from bot.pagination import LinePaginator from bot.utils.checks import with_role_check diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 1e7521054..2e54ccecb 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -5,8 +5,9 @@ from typing import Optional, Union import discord.errors from dateutil.relativedelta import relativedelta from discord import Colour, DMChannel, Member, Message, TextChannel -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import ( Channels, Colours, diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 82285656b..bbc9f063b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -3,8 +3,9 @@ from datetime import datetime from operator import itemgetter from discord import Colour, Embed, Member, utils -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Cog, Context, command +from bot.bot import Bot from bot.constants import Categories, Channels, Free, STAFF_ROLES from bot.decorators import redirect_output diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 9607dbd8d..6385fa467 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,11 @@ from typing import Union from discord import Colour, Embed, HTTPException, Message, Reaction, User from discord.ext import commands -from discord.ext.commands import Bot, CheckFailure, Cog as DiscordCog, Command, Context +from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from fuzzywuzzy import fuzz, process from bot import constants +from bot.bot import Bot from bot.constants import Channels, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import ( diff --git a/bot/cogs/information.py b/bot/cogs/information.py index 530453600..56bd37bec 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -9,10 +9,11 @@ from typing import Any, Mapping, Optional import discord from discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils from discord.ext import commands -from discord.ext.commands import Bot, BucketType, Cog, Context, command, group +from discord.ext.commands import BucketType, Cog, Context, command, group from discord.utils import escape_markdown from bot import constants +from bot.bot import Bot from bot.decorators import InChannelCheckFailure, in_channel, with_role from bot.utils.checks import cooldown_with_role_bypass, with_role_check from bot.utils.time import time_since diff --git a/bot/cogs/jams.py b/bot/cogs/jams.py index be9d33e3e..0c82e7962 100644 --- a/bot/cogs/jams.py +++ b/bot/cogs/jams.py @@ -4,6 +4,7 @@ from discord import Member, PermissionOverwrite, utils from discord.ext import commands from more_itertools import unique_everseen +from bot.bot import Bot from bot.constants import Roles from bot.decorators import with_role @@ -13,7 +14,7 @@ log = logging.getLogger(__name__) class CodeJams(commands.Cog): """Manages the code-jam related parts of our server.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command() @@ -108,7 +109,7 @@ class CodeJams(commands.Cog): ) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Code Jams cog load.""" bot.add_cog(CodeJams(bot)) log.info("Cog loaded: CodeJams") diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index c92b619ff..44c771b42 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -1,8 +1,9 @@ import logging from discord import Embed -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog +from bot.bot import Bot from bot.constants import Channels, DEBUG_MODE diff --git a/bot/cogs/moderation/__init__.py b/bot/cogs/moderation/__init__.py index 7383ed44e..0cbdb3aa6 100644 --- a/bot/cogs/moderation/__init__.py +++ b/bot/cogs/moderation/__init__.py @@ -1,7 +1,6 @@ import logging -from discord.ext.commands import Bot - +from bot.bot import Bot from .infractions import Infractions from .management import ModManagement from .modlog import ModLog diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 2713a1b68..7478e19ef 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -7,6 +7,7 @@ from discord.ext import commands from discord.ext.commands import Context, command from bot import constants +from bot.bot import Bot from bot.constants import Event from bot.decorators import respect_role_hierarchy from bot.utils.checks import with_role_check @@ -25,7 +26,7 @@ class Infractions(InfractionScheduler, commands.Cog): category = "Moderation" category_description = "Server moderation tools." - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): super().__init__(bot, supported_infractions={"ban", "kick", "mute", "note", "warning"}) self.category = "Moderation" diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index abfe5c2b3..feae00b7c 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -9,6 +9,7 @@ from discord.ext import commands from discord.ext.commands import Context from bot import constants +from bot.bot import Bot from bot.converters import InfractionSearchQuery from bot.pagination import LinePaginator from bot.utils import time @@ -36,7 +37,7 @@ class ModManagement(commands.Cog): category = "Moderation" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @property diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 0df752a97..35ef6cbcc 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -10,8 +10,9 @@ from dateutil.relativedelta import relativedelta from deepdiff import DeepDiff from discord import Colour from discord.abc import GuildChannel -from discord.ext.commands import Bot, Cog, Context +from discord.ext.commands import Cog, Context +from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, URLs from bot.utils.time import humanize_delta from .utils import UserTypes diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 3e0968121..937113ef4 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -7,10 +7,11 @@ from gettext import ngettext import dateutil.parser import discord -from discord.ext.commands import Bot, Context +from discord.ext.commands import Context from bot import constants from bot.api import ResponseCodeError +from bot.bot import Bot from bot.constants import Colours, STAFF_CHANNELS from bot.utils import time from bot.utils.scheduling import Scheduler diff --git a/bot/cogs/moderation/superstarify.py b/bot/cogs/moderation/superstarify.py index 9b3c62403..7631d9bbe 100644 --- a/bot/cogs/moderation/superstarify.py +++ b/bot/cogs/moderation/superstarify.py @@ -6,9 +6,10 @@ import typing as t from pathlib import Path from discord import Colour, Embed, Member -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Cog, Context, command from bot import constants +from bot.bot import Bot from bot.utils.checks import with_role_check from bot.utils.time import format_infraction from . import utils diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py index 78792240f..18d9cfb01 100644 --- a/bot/cogs/off_topic_names.py +++ b/bot/cogs/off_topic_names.py @@ -4,9 +4,10 @@ import logging from datetime import datetime, timedelta from discord import Colour, Embed -from discord.ext.commands import BadArgument, Bot, Cog, Context, Converter, group +from discord.ext.commands import BadArgument, Cog, Context, Converter, group from bot.api import ResponseCodeError +from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES from bot.decorators import with_role from bot.pagination import LinePaginator diff --git a/bot/cogs/reddit.py b/bot/cogs/reddit.py index 0d06e9c26..c76fcd937 100644 --- a/bot/cogs/reddit.py +++ b/bot/cogs/reddit.py @@ -6,9 +6,10 @@ from datetime import datetime, timedelta from typing import List from discord import Colour, Embed, TextChannel -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group from discord.ext.tasks import loop +from bot.bot import Bot from bot.constants import Channels, ERROR_REPLIES, Emojis, Reddit as RedditConfig, STAFF_ROLES, Webhooks from bot.converters import Subreddit from bot.decorators import with_role diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index 81990704b..b805b24c5 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -8,8 +8,9 @@ from typing import Optional from dateutil.relativedelta import relativedelta from discord import Colour, Embed, Message -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import Channels, Icons, NEGATIVE_REPLIES, POSITIVE_REPLIES, STAFF_ROLES from bot.converters import Duration from bot.pagination import LinePaginator diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 316b33d6b..45d0eb2f5 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -1,6 +1,8 @@ import logging -from discord.ext.commands import Bot, Cog, Context, NoPrivateMessage +from discord.ext.commands import Cog, Context, NoPrivateMessage + +from bot.bot import Bot log = logging.getLogger(__name__) diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 683613788..1d7bd03e4 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -1,8 +1,9 @@ import logging from discord import Colour, Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import URLs from bot.pagination import LinePaginator diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py index 55a187ac1..1ea61a8da 100644 --- a/bot/cogs/snekbox.py +++ b/bot/cogs/snekbox.py @@ -5,8 +5,9 @@ import textwrap from signal import Signals from typing import Optional, Tuple -from discord.ext.commands import Bot, Cog, Context, command, guild_only +from discord.ext.commands import Cog, Context, command, guild_only +from bot.bot import Bot from bot.constants import Channels, Roles, URLs from bot.decorators import in_channel from bot.utils.messages import wait_for_deletion diff --git a/bot/cogs/sync/__init__.py b/bot/cogs/sync/__init__.py index d4565f848..0da81c60e 100644 --- a/bot/cogs/sync/__init__.py +++ b/bot/cogs/sync/__init__.py @@ -1,7 +1,6 @@ import logging -from discord.ext.commands import Bot - +from bot.bot import Bot from .cog import Sync log = logging.getLogger(__name__) diff --git a/bot/cogs/sync/cog.py b/bot/cogs/sync/cog.py index aaa581f96..90d4c40fe 100644 --- a/bot/cogs/sync/cog.py +++ b/bot/cogs/sync/cog.py @@ -3,10 +3,11 @@ from typing import Callable, Iterable from discord import Guild, Member, Role from discord.ext import commands -from discord.ext.commands import Bot, Cog, Context +from discord.ext.commands import Cog, Context from bot import constants from bot.api import ResponseCodeError +from bot.bot import Bot from bot.cogs.sync import syncers log = logging.getLogger(__name__) diff --git a/bot/cogs/sync/syncers.py b/bot/cogs/sync/syncers.py index 2cc5a66e1..14cf51383 100644 --- a/bot/cogs/sync/syncers.py +++ b/bot/cogs/sync/syncers.py @@ -2,7 +2,8 @@ from collections import namedtuple from typing import Dict, Set, Tuple from discord import Guild -from discord.ext.commands import Bot + +from bot.bot import Bot # These objects are declared as namedtuples because tuples are hashable, # something that we make use of when diffing site roles against guild roles. @@ -52,7 +53,7 @@ async def sync_roles(bot: Bot, guild: Guild) -> Tuple[int, int, int]: Synchronize roles found on the given `guild` with the ones on the API. Arguments: - bot (discord.ext.commands.Bot): + bot (bot.bot.Bot): The bot instance that we're running with. guild (discord.Guild): @@ -169,7 +170,7 @@ async def sync_users(bot: Bot, guild: Guild) -> Tuple[int, int, None]: Synchronize users found in the given `guild` with the ones in the API. Arguments: - bot (discord.ext.commands.Bot): + bot (bot.bot.Bot): The bot instance that we're running with. guild (discord.Guild): diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index cd70e783a..2ece0095d 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -2,8 +2,9 @@ import logging import time from discord import Colour, Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import Channels, Cooldowns, MODERATION_ROLES, Roles from bot.converters import TagContentConverter, TagNameConverter from bot.decorators import with_role diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 5a0d20e57..7af7ed63a 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -6,9 +6,10 @@ import struct from datetime import datetime from discord import Colour, Message -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog from discord.utils import snowflake_time +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import Channels, Colours, Event, Icons diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 793fe4c1a..0ed996430 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -8,8 +8,9 @@ from typing import Tuple from dateutil import relativedelta from discord import Colour, Embed, Message, Role -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Cog, Context, command +from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES from bot.decorators import in_channel, with_role from bot.utils.time import humanize_delta diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py index b5e8d4357..74eb0dbf8 100644 --- a/bot/cogs/verification.py +++ b/bot/cogs/verification.py @@ -3,8 +3,9 @@ from datetime import datetime from discord import Colour, Message, NotFound, Object from discord.ext import tasks -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Cog, Context, command +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import ( Bot as BotConfig, diff --git a/bot/cogs/watchchannels/__init__.py b/bot/cogs/watchchannels/__init__.py index 86e1050fa..e18aea88a 100644 --- a/bot/cogs/watchchannels/__init__.py +++ b/bot/cogs/watchchannels/__init__.py @@ -1,7 +1,6 @@ import logging -from discord.ext.commands import Bot - +from bot.bot import Bot from .bigbrother import BigBrother from .talentpool import TalentPool diff --git a/bot/cogs/watchchannels/bigbrother.py b/bot/cogs/watchchannels/bigbrother.py index 49783bb09..306ed4c64 100644 --- a/bot/cogs/watchchannels/bigbrother.py +++ b/bot/cogs/watchchannels/bigbrother.py @@ -3,8 +3,9 @@ from collections import ChainMap from typing import Union from discord import User -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.cogs.moderation.utils import post_infraction from bot.constants import Channels, MODERATION_ROLES, Webhooks from bot.decorators import with_role diff --git a/bot/cogs/watchchannels/talentpool.py b/bot/cogs/watchchannels/talentpool.py index 4ec42dcc1..cc8feeeee 100644 --- a/bot/cogs/watchchannels/talentpool.py +++ b/bot/cogs/watchchannels/talentpool.py @@ -4,9 +4,10 @@ from collections import ChainMap from typing import Union from discord import Color, Embed, Member, User -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group from bot.api import ResponseCodeError +from bot.bot import Bot from bot.constants import Channels, Guild, MODERATION_ROLES, STAFF_ROLES, Webhooks from bot.decorators import with_role from bot.pagination import LinePaginator diff --git a/bot/cogs/watchchannels/watchchannel.py b/bot/cogs/watchchannels/watchchannel.py index 0bf75a924..bd0622554 100644 --- a/bot/cogs/watchchannels/watchchannel.py +++ b/bot/cogs/watchchannels/watchchannel.py @@ -10,9 +10,10 @@ from typing import Optional import dateutil.parser import discord from discord import Color, Embed, HTTPException, Message, Object, errors -from discord.ext.commands import BadArgument, Bot, Cog, Context +from discord.ext.commands import BadArgument, Cog, Context from bot.api import ResponseCodeError +from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import BigBrother as BigBrotherConfig, Guild as GuildConfig, Icons from bot.pagination import LinePaginator diff --git a/bot/cogs/wolfram.py b/bot/cogs/wolfram.py index ab0ed2472..c3c193cb9 100644 --- a/bot/cogs/wolfram.py +++ b/bot/cogs/wolfram.py @@ -7,8 +7,9 @@ import discord from dateutil.relativedelta import relativedelta from discord import Embed from discord.ext import commands -from discord.ext.commands import Bot, BucketType, Cog, Context, check, group +from discord.ext.commands import BucketType, Cog, Context, check, group +from bot.bot import Bot from bot.constants import Colours, STAFF_ROLES, Wolfram from bot.pagination import ImagePaginator from bot.utils.time import humanize_delta @@ -151,7 +152,7 @@ async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tup class Wolfram(Cog): """Commands for interacting with the Wolfram|Alpha API.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @group(name="wolfram", aliases=("wolf", "wa"), invoke_without_command=True) @@ -266,7 +267,7 @@ class Wolfram(Cog): await send_embed(ctx, message, color) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Wolfram cog load.""" bot.add_cog(Wolfram(bot)) log.info("Cog loaded: Wolfram") diff --git a/bot/interpreter.py b/bot/interpreter.py index 76a3fc293..8b7268746 100644 --- a/bot/interpreter.py +++ b/bot/interpreter.py @@ -2,7 +2,9 @@ from code import InteractiveInterpreter from io import StringIO from typing import Any -from discord.ext.commands import Bot, Context +from discord.ext.commands import Context + +from bot.bot import Bot CODE_TEMPLATE = """ async def _func(): diff --git a/tests/helpers.py b/tests/helpers.py index b2daae92d..5df796c23 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -10,7 +10,9 @@ import unittest.mock from typing import Any, Iterable, Optional import discord -from discord.ext.commands import Bot, Context +from discord.ext.commands import Context + +from bot.bot import Bot for logger in logging.Logger.manager.loggerDict.values(): -- cgit v1.2.3 From 52924051e27d34c3f7e32c281fbe8ae0587a9766 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Dec 2019 20:31:39 -0800 Subject: Override add_cog to log loading of cogs --- bot/bot.py | 5 +++++ bot/cogs/alias.py | 3 +-- bot/cogs/antimalware.py | 3 +-- bot/cogs/antispam.py | 3 +-- bot/cogs/bot.py | 3 +-- bot/cogs/clean.py | 3 +-- bot/cogs/defcon.py | 3 +-- bot/cogs/doc.py | 3 +-- bot/cogs/duck_pond.py | 3 +-- bot/cogs/error_handler.py | 3 +-- bot/cogs/eval.py | 3 +-- bot/cogs/extensions.py | 1 - bot/cogs/filtering.py | 3 +-- bot/cogs/free.py | 3 +-- bot/cogs/information.py | 3 +-- bot/cogs/jams.py | 3 +-- bot/cogs/logging.py | 3 +-- bot/cogs/moderation/__init__.py | 13 +------------ bot/cogs/off_topic_names.py | 3 +-- bot/cogs/reddit.py | 3 +-- bot/cogs/reminders.py | 3 +-- bot/cogs/security.py | 3 +-- bot/cogs/site.py | 3 +-- bot/cogs/snekbox.py | 3 +-- bot/cogs/sync/__init__.py | 7 +------ bot/cogs/tags.py | 3 +-- bot/cogs/token_remover.py | 3 +-- bot/cogs/utils.py | 3 +-- bot/cogs/verification.py | 3 +-- bot/cogs/watchchannels/__init__.py | 10 +--------- bot/cogs/wolfram.py | 3 +-- 31 files changed, 34 insertions(+), 80 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 05734ac1d..f39bfb50a 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -28,3 +28,8 @@ class Bot(commands.Bot): self.api_client = api.APIClient(loop=asyncio.get_event_loop()) log.addHandler(api.APILoggingHandler(self.api_client)) + + def add_cog(self, cog: commands.Cog) -> None: + """Adds a "cog" to the bot and logs the operation.""" + super().add_cog(cog) + log.info(f"Cog loaded: {cog.qualified_name}") diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 4ee5a2aed..c1db38462 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -148,6 +148,5 @@ class Alias (Cog): def setup(bot: Bot) -> None: - """Alias cog load.""" + """Load the Alias cog.""" bot.add_cog(Alias(bot)) - log.info("Cog loaded: Alias") diff --git a/bot/cogs/antimalware.py b/bot/cogs/antimalware.py index 03c1e28a1..28e3e5d96 100644 --- a/bot/cogs/antimalware.py +++ b/bot/cogs/antimalware.py @@ -50,6 +50,5 @@ class AntiMalware(Cog): def setup(bot: Bot) -> None: - """Antimalware cog load.""" + """Load the AntiMalware cog.""" bot.add_cog(AntiMalware(bot)) - log.info("Cog loaded: AntiMalware") diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 88912038a..f454061a6 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -277,7 +277,6 @@ def validate_config(rules: Mapping = AntiSpamConfig.rules) -> Dict[str, str]: def setup(bot: Bot) -> None: - """Antispam cog load.""" + """Validate the AntiSpam configs and load the AntiSpam cog.""" validation_errors = validate_config() bot.add_cog(AntiSpam(bot, validation_errors)) - log.info("Cog loaded: AntiSpam") diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index a2edb7576..b5642da82 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -378,6 +378,5 @@ class Bot(Cog): def setup(bot: Bot) -> None: - """Bot cog load.""" + """Load the Bot cog.""" bot.add_cog(Bot(bot)) - log.info("Cog loaded: Bot") diff --git a/bot/cogs/clean.py b/bot/cogs/clean.py index 3365d0934..c7168122d 100644 --- a/bot/cogs/clean.py +++ b/bot/cogs/clean.py @@ -212,6 +212,5 @@ class Clean(Cog): def setup(bot: Bot) -> None: - """Clean cog load.""" + """Load the Clean cog.""" bot.add_cog(Clean(bot)) - log.info("Cog loaded: Clean") diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index f062a7546..3e7350fcc 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -237,6 +237,5 @@ class Defcon(Cog): def setup(bot: Bot) -> None: - """DEFCON cog load.""" + """Load the Defcon cog.""" bot.add_cog(Defcon(bot)) - log.info("Cog loaded: Defcon") diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index 7df159fd9..9506b195a 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -508,6 +508,5 @@ class Doc(commands.Cog): def setup(bot: Bot) -> None: - """Doc cog load.""" + """Load the Doc cog.""" bot.add_cog(Doc(bot)) - log.info("Cog loaded: Doc") diff --git a/bot/cogs/duck_pond.py b/bot/cogs/duck_pond.py index 879071d1b..345d2856c 100644 --- a/bot/cogs/duck_pond.py +++ b/bot/cogs/duck_pond.py @@ -178,6 +178,5 @@ class DuckPond(Cog): def setup(bot: Bot) -> None: - """Load the duck pond cog.""" + """Load the DuckPond cog.""" bot.add_cog(DuckPond(bot)) - log.info("Cog loaded: DuckPond") diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index cf90e9f48..700f903a6 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -144,6 +144,5 @@ class ErrorHandler(Cog): def setup(bot: Bot) -> None: - """Error handler cog load.""" + """Load the ErrorHandler cog.""" bot.add_cog(ErrorHandler(bot)) - log.info("Cog loaded: Events") diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 5daec3e39..9c729f28a 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -198,6 +198,5 @@ async def func(): # (None,) -> Any def setup(bot: Bot) -> None: - """Code eval cog load.""" + """Load the CodeEval cog.""" bot.add_cog(CodeEval(bot)) - log.info("Cog loaded: Eval") diff --git a/bot/cogs/extensions.py b/bot/cogs/extensions.py index 4d77d8205..f16e79fb7 100644 --- a/bot/cogs/extensions.py +++ b/bot/cogs/extensions.py @@ -234,4 +234,3 @@ class Extensions(commands.Cog): def setup(bot: Bot) -> None: """Load the Extensions cog.""" bot.add_cog(Extensions(bot)) - log.info("Cog loaded: Extensions") diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 2e54ccecb..74538542a 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -371,6 +371,5 @@ class Filtering(Cog): def setup(bot: Bot) -> None: - """Filtering cog load.""" + """Load the Filtering cog.""" bot.add_cog(Filtering(bot)) - log.info("Cog loaded: Filtering") diff --git a/bot/cogs/free.py b/bot/cogs/free.py index bbc9f063b..49cab6172 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -99,6 +99,5 @@ class Free(Cog): def setup(bot: Bot) -> None: - """Free cog load.""" + """Load the Free cog.""" bot.add_cog(Free()) - log.info("Cog loaded: Free") diff --git a/bot/cogs/information.py b/bot/cogs/information.py index 56bd37bec..1ede95ff4 100644 --- a/bot/cogs/information.py +++ b/bot/cogs/information.py @@ -392,6 +392,5 @@ class Information(Cog): def setup(bot: Bot) -> None: - """Information cog load.""" + """Load the Information cog.""" bot.add_cog(Information(bot)) - log.info("Cog loaded: Information") diff --git a/bot/cogs/jams.py b/bot/cogs/jams.py index 0c82e7962..985f28ce5 100644 --- a/bot/cogs/jams.py +++ b/bot/cogs/jams.py @@ -110,6 +110,5 @@ class CodeJams(commands.Cog): def setup(bot: Bot) -> None: - """Code Jams cog load.""" + """Load the CodeJams cog.""" bot.add_cog(CodeJams(bot)) - log.info("Cog loaded: CodeJams") diff --git a/bot/cogs/logging.py b/bot/cogs/logging.py index 44c771b42..d1b7dcab3 100644 --- a/bot/cogs/logging.py +++ b/bot/cogs/logging.py @@ -38,6 +38,5 @@ class Logging(Cog): def setup(bot: Bot) -> None: - """Logging cog load.""" + """Load the Logging cog.""" bot.add_cog(Logging(bot)) - log.info("Cog loaded: Logging") diff --git a/bot/cogs/moderation/__init__.py b/bot/cogs/moderation/__init__.py index 0cbdb3aa6..5243cb92d 100644 --- a/bot/cogs/moderation/__init__.py +++ b/bot/cogs/moderation/__init__.py @@ -1,24 +1,13 @@ -import logging - from bot.bot import Bot from .infractions import Infractions from .management import ModManagement from .modlog import ModLog from .superstarify import Superstarify -log = logging.getLogger(__name__) - def setup(bot: Bot) -> None: - """Load the moderation extension (Infractions, ModManagement, ModLog, & Superstarify cogs).""" + """Load the Infractions, ModManagement, ModLog, and Superstarify cogs.""" bot.add_cog(Infractions(bot)) - log.info("Cog loaded: Infractions") - bot.add_cog(ModLog(bot)) - log.info("Cog loaded: ModLog") - bot.add_cog(ModManagement(bot)) - log.info("Cog loaded: ModManagement") - bot.add_cog(Superstarify(bot)) - log.info("Cog loaded: Superstarify") diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py index 18d9cfb01..bf777ea5a 100644 --- a/bot/cogs/off_topic_names.py +++ b/bot/cogs/off_topic_names.py @@ -185,6 +185,5 @@ class OffTopicNames(Cog): def setup(bot: Bot) -> None: - """Off topic names cog load.""" + """Load the OffTopicNames cog.""" bot.add_cog(OffTopicNames(bot)) - log.info("Cog loaded: OffTopicNames") diff --git a/bot/cogs/reddit.py b/bot/cogs/reddit.py index c76fcd937..bec316ae7 100644 --- a/bot/cogs/reddit.py +++ b/bot/cogs/reddit.py @@ -218,6 +218,5 @@ class Reddit(Cog): def setup(bot: Bot) -> None: - """Reddit cog load.""" + """Load the Reddit cog.""" bot.add_cog(Reddit(bot)) - log.info("Cog loaded: Reddit") diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index b805b24c5..45bf9a8f4 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -291,6 +291,5 @@ class Reminders(Scheduler, Cog): def setup(bot: Bot) -> None: - """Reminders cog load.""" + """Load the Reminders cog.""" bot.add_cog(Reminders(bot)) - log.info("Cog loaded: Reminders") diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 45d0eb2f5..c680c5e27 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -27,6 +27,5 @@ class Security(Cog): def setup(bot: Bot) -> None: - """Security cog load.""" + """Load the Security cog.""" bot.add_cog(Security(bot)) - log.info("Cog loaded: Security") diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 1d7bd03e4..2ea8c7a2e 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -139,6 +139,5 @@ class Site(Cog): def setup(bot: Bot) -> None: - """Site cog load.""" + """Load the Site cog.""" bot.add_cog(Site(bot)) - log.info("Cog loaded: Site") diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py index 1ea61a8da..da33e27b2 100644 --- a/bot/cogs/snekbox.py +++ b/bot/cogs/snekbox.py @@ -228,6 +228,5 @@ class Snekbox(Cog): def setup(bot: Bot) -> None: - """Snekbox cog load.""" + """Load the Snekbox cog.""" bot.add_cog(Snekbox(bot)) - log.info("Cog loaded: Snekbox") diff --git a/bot/cogs/sync/__init__.py b/bot/cogs/sync/__init__.py index 0da81c60e..fe7df4e9b 100644 --- a/bot/cogs/sync/__init__.py +++ b/bot/cogs/sync/__init__.py @@ -1,12 +1,7 @@ -import logging - from bot.bot import Bot from .cog import Sync -log = logging.getLogger(__name__) - def setup(bot: Bot) -> None: - """Sync cog load.""" + """Load the Sync cog.""" bot.add_cog(Sync(bot)) - log.info("Cog loaded: Sync") diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index 2ece0095d..970301013 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -161,6 +161,5 @@ class Tags(Cog): def setup(bot: Bot) -> None: - """Tags cog load.""" + """Load the Tags cog.""" bot.add_cog(Tags(bot)) - log.info("Cog loaded: Tags") diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 7af7ed63a..5d6618338 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -120,6 +120,5 @@ class TokenRemover(Cog): def setup(bot: Bot) -> None: - """Token Remover cog load.""" + """Load the TokenRemover cog.""" bot.add_cog(TokenRemover(bot)) - log.info("Cog loaded: TokenRemover") diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 0ed996430..47a59db66 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -177,6 +177,5 @@ class Utils(Cog): def setup(bot: Bot) -> None: - """Utils cog load.""" + """Load the Utils cog.""" bot.add_cog(Utils(bot)) - log.info("Cog loaded: Utils") diff --git a/bot/cogs/verification.py b/bot/cogs/verification.py index 74eb0dbf8..b32b9a29e 100644 --- a/bot/cogs/verification.py +++ b/bot/cogs/verification.py @@ -225,6 +225,5 @@ class Verification(Cog): def setup(bot: Bot) -> None: - """Verification cog load.""" + """Load the Verification cog.""" bot.add_cog(Verification(bot)) - log.info("Cog loaded: Verification") diff --git a/bot/cogs/watchchannels/__init__.py b/bot/cogs/watchchannels/__init__.py index e18aea88a..69d118df6 100644 --- a/bot/cogs/watchchannels/__init__.py +++ b/bot/cogs/watchchannels/__init__.py @@ -1,17 +1,9 @@ -import logging - from bot.bot import Bot from .bigbrother import BigBrother from .talentpool import TalentPool -log = logging.getLogger(__name__) - - def setup(bot: Bot) -> None: - """Monitoring cogs load.""" + """Load the BigBrother and TalentPool cogs.""" bot.add_cog(BigBrother(bot)) - log.info("Cog loaded: BigBrother") - bot.add_cog(TalentPool(bot)) - log.info("Cog loaded: TalentPool") diff --git a/bot/cogs/wolfram.py b/bot/cogs/wolfram.py index c3c193cb9..5d6b4630b 100644 --- a/bot/cogs/wolfram.py +++ b/bot/cogs/wolfram.py @@ -268,6 +268,5 @@ class Wolfram(Cog): def setup(bot: Bot) -> None: - """Wolfram cog load.""" + """Load the Wolfram cog.""" bot.add_cog(Wolfram(bot)) - log.info("Cog loaded: Wolfram") -- cgit v1.2.3 From a4a53f3b9d1cc9928ee03d4f0ecb8087a527e8ca Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Dec 2019 20:39:09 -0800 Subject: Fix name conflict with the Bot cog --- bot/cogs/bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index b5642da82..e795e5960 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) RE_MARKDOWN = re.compile(r'([*_~`|>])') -class Bot(Cog): +class BotCog(Cog, name="Bot"): """Bot information commands.""" def __init__(self, bot: Bot): @@ -374,9 +374,9 @@ class Bot(Cog): bot_message = await channel.fetch_message(self.codeblock_message_ids[payload.message_id]) await bot_message.delete() del self.codeblock_message_ids[payload.message_id] - log.trace("User's incorrect code block has been fixed. Removing bot formatting message.") + log.trace("User's incorrect code block has been fixed. Removing bot formatting message.") def setup(bot: Bot) -> None: """Load the Bot cog.""" - bot.add_cog(Bot(bot)) + bot.add_cog(BotCog(bot)) -- cgit v1.2.3 From 56578525ac4e5c6d20392d1208b74623c8524bcd Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 8 Dec 2019 01:40:19 -0800 Subject: Properly create and close aiohttp sessions aiohttp throws a warning when a session is created outside of a running async event loop. In aiohttp 4.0 this actually changes to an error instead of merely a warning. Since discord.py manages the event loop with client.run(), some of the "internal" coroutines of the client were overwritten in the bot subclass to be able to hook into when the bot starts and stops. Sessions of both the bot and the API client can now potentially be None if accessed before the sessions have been created. However, if called, the API client's methods will wait for a session to be ready. It will attempt to create a session as soon as the event loop starts (i.e. the bot is running). --- bot/api.py | 41 +++++++++++++++++++++++++++++++++++++++-- bot/bot.py | 34 ++++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/bot/api.py b/bot/api.py index 7f26e5305..56db99828 100644 --- a/bot/api.py +++ b/bot/api.py @@ -32,7 +32,7 @@ class ResponseCodeError(ValueError): class APIClient: """Django Site API wrapper.""" - def __init__(self, **kwargs): + def __init__(self, loop: asyncio.AbstractEventLoop, **kwargs): auth_headers = { 'Authorization': f"Token {Keys.site_api}" } @@ -42,12 +42,39 @@ class APIClient: else: kwargs['headers'] = auth_headers - self.session = aiohttp.ClientSession(**kwargs) + self.session: Optional[aiohttp.ClientSession] = None + self.loop = loop + + self._ready = asyncio.Event(loop=loop) + self._creation_task = None + self._session_args = kwargs + + self.recreate() @staticmethod def _url_for(endpoint: str) -> str: return f"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}" + async def _create_session(self) -> None: + """Create the aiohttp session and set the ready event.""" + self.session = aiohttp.ClientSession(**self._session_args) + self._ready.set() + + async def close(self) -> None: + """Close the aiohttp session and unset the ready event.""" + if not self._ready.is_set(): + return + + await self.session.close() + self._ready.clear() + + def recreate(self) -> None: + """Schedule the aiohttp session to be created if it's been closed.""" + if self.session is None or self.session.closed: + # Don't schedule a task if one is already in progress. + if self._creation_task is None or self._creation_task.done(): + self._creation_task = self.loop.create_task(self._create_session()) + async def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool) -> None: """Raise ResponseCodeError for non-OK response if an exception should be raised.""" if should_raise and response.status >= 400: @@ -60,30 +87,40 @@ class APIClient: async def get(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs) -> dict: """Site API GET.""" + await self._ready.wait() + async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp: await self.maybe_raise_for_status(resp, raise_for_status) return await resp.json() async def patch(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs) -> dict: """Site API PATCH.""" + await self._ready.wait() + async with self.session.patch(self._url_for(endpoint), *args, **kwargs) as resp: await self.maybe_raise_for_status(resp, raise_for_status) return await resp.json() async def post(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs) -> dict: """Site API POST.""" + await self._ready.wait() + async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp: await self.maybe_raise_for_status(resp, raise_for_status) return await resp.json() async def put(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs) -> dict: """Site API PUT.""" + await self._ready.wait() + async with self.session.put(self._url_for(endpoint), *args, **kwargs) as resp: await self.maybe_raise_for_status(resp, raise_for_status) return await resp.json() async def delete(self, endpoint: str, *args, raise_for_status: bool = True, **kwargs) -> Optional[dict]: """Site API DELETE.""" + await self._ready.wait() + async with self.session.delete(self._url_for(endpoint), *args, **kwargs) as resp: if resp.status == 204: return None diff --git a/bot/bot.py b/bot/bot.py index f39bfb50a..4b3b991a3 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,6 +1,6 @@ -import asyncio import logging import socket +from typing import Optional import aiohttp from discord.ext import commands @@ -16,6 +16,30 @@ class Bot(commands.Bot): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.http_session: Optional[aiohttp.ClientSession] = None + self.api_client = api.APIClient(loop=self.loop) + + log.addHandler(api.APILoggingHandler(self.api_client)) + + def add_cog(self, cog: commands.Cog) -> None: + """Adds a "cog" to the bot and logs the operation.""" + super().add_cog(cog) + log.info(f"Cog loaded: {cog.qualified_name}") + + def clear(self) -> None: + """Clears the internal state of the bot and resets the API client.""" + super().clear() + self.api_client.recreate() + + async def close(self) -> None: + """Close the aiohttp session after closing the Discord connection.""" + await super().close() + + await self.http_session.close() + await self.api_client.close() + + async def start(self, *args, **kwargs) -> None: + """Open an aiohttp session before logging in and connecting to Discord.""" # Global aiohttp session for all cogs # - Uses asyncio for DNS resolution instead of threads, so we don't spam threads # - Uses AF_INET as its socket family to prevent https related problems both locally and in prod. @@ -26,10 +50,4 @@ class Bot(commands.Bot): ) ) - self.api_client = api.APIClient(loop=asyncio.get_event_loop()) - log.addHandler(api.APILoggingHandler(self.api_client)) - - def add_cog(self, cog: commands.Cog) -> None: - """Adds a "cog" to the bot and logs the operation.""" - super().add_cog(cog) - log.info(f"Cog loaded: {cog.qualified_name}") + await super().start(*args, **kwargs) -- cgit v1.2.3 From 23acfce3521cd420a2df6eb51f036a2a54140ef6 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 8 Dec 2019 02:01:01 -0800 Subject: Fix test failures for setup log messages --- tests/bot/cogs/test_duck_pond.py | 12 ++---------- tests/bot/cogs/test_security.py | 11 +++-------- tests/bot/cogs/test_token_remover.py | 8 ++------ 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/tests/bot/cogs/test_duck_pond.py b/tests/bot/cogs/test_duck_pond.py index b801e86f1..d07b2bce1 100644 --- a/tests/bot/cogs/test_duck_pond.py +++ b/tests/bot/cogs/test_duck_pond.py @@ -578,15 +578,7 @@ class DuckPondSetupTests(unittest.TestCase): """Tests setup of the `DuckPond` cog.""" def test_setup(self): - """Setup of the cog should log a message at `INFO` level.""" + """Setup of the extension should call add_cog.""" bot = helpers.MockBot() - log = logging.getLogger('bot.cogs.duck_pond') - - with self.assertLogs(logger=log, level=logging.INFO) as log_watcher: - duck_pond.setup(bot) - - self.assertEqual(len(log_watcher.records), 1) - record = log_watcher.records[0] - self.assertEqual(record.levelno, logging.INFO) - + duck_pond.setup(bot) bot.add_cog.assert_called_once() diff --git a/tests/bot/cogs/test_security.py b/tests/bot/cogs/test_security.py index efa7a50b1..9d1a62f7e 100644 --- a/tests/bot/cogs/test_security.py +++ b/tests/bot/cogs/test_security.py @@ -1,4 +1,3 @@ -import logging import unittest from unittest.mock import MagicMock @@ -49,11 +48,7 @@ class SecurityCogLoadTests(unittest.TestCase): """Tests loading the `Security` cog.""" def test_security_cog_load(self): - """Cog loading logs a message at `INFO` level.""" + """Setup of the extension should call add_cog.""" bot = MagicMock() - with self.assertLogs(logger='bot.cogs.security', level=logging.INFO) as cm: - security.setup(bot) - bot.add_cog.assert_called_once() - - [line] = cm.output - self.assertIn("Cog loaded: Security", line) + security.setup(bot) + bot.add_cog.assert_called_once() diff --git a/tests/bot/cogs/test_token_remover.py b/tests/bot/cogs/test_token_remover.py index 3276cf5a5..a54b839d7 100644 --- a/tests/bot/cogs/test_token_remover.py +++ b/tests/bot/cogs/test_token_remover.py @@ -125,11 +125,7 @@ class TokenRemoverSetupTests(unittest.TestCase): """Tests setup of the `TokenRemover` cog.""" def test_setup(self): - """Setup of the cog should log a message at `INFO` level.""" + """Setup of the extension should call add_cog.""" bot = MockBot() - with self.assertLogs(logger='bot.cogs.token_remover', level=logging.INFO) as cm: - setup_cog(bot) - - [line] = cm.output + setup_cog(bot) bot.add_cog.assert_called_once() - self.assertIn("Cog loaded: TokenRemover", line) -- cgit v1.2.3 From 34bac05ccc6c11ea370aa14431e4d6d6cd28f1d6 Mon Sep 17 00:00:00 2001 From: Manuel Ignacio Pérez Alcolea Date: Mon, 9 Dec 2019 02:37:30 -0300 Subject: Ensure hidden_channels and bypass_roles use a list when not passed. The in_channel decorator raised 'NoneType' is not iterable when it wasn't passed, due to the default value being None but not checked against before iterating over it. This edit ensures the arguments are set to an empty list in cases where they have a value of None instead. --- bot/decorators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/decorators.py b/bot/decorators.py index 61587f406..2d18eaa6a 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -38,6 +38,9 @@ def in_channel( Hidden channels are channels which will not be displayed in the InChannelCheckFailure error message. """ + hidden_channels = hidden_channels or [] + bypass_roles = bypass_roles or [] + def predicate(ctx: Context) -> bool: """In-channel checker predicate.""" if ctx.channel.id in channels or ctx.channel.id in hidden_channels: -- cgit v1.2.3 From dbd7220caed5a6ba759d0cf9efaa0c0c0e57f391 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sun, 8 Dec 2019 23:40:17 -0800 Subject: Use the AsyncResolver for APIClient and discord.py sessions too Active thread counts are observed to be lower with it in use. --- bot/bot.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 4b3b991a3..8f808272f 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -14,10 +14,18 @@ class Bot(commands.Bot): """A subclass of `discord.ext.commands.Bot` with an aiohttp session and an API client.""" def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + # Use asyncio for DNS resolution instead of threads so threads aren't spammed. + # Use AF_INET as its socket family to prevent HTTPS related problems both locally + # and in production. + self.connector = aiohttp.TCPConnector( + resolver=aiohttp.AsyncResolver(), + family=socket.AF_INET, + ) + + super().__init__(*args, connector=self.connector, **kwargs) self.http_session: Optional[aiohttp.ClientSession] = None - self.api_client = api.APIClient(loop=self.loop) + self.api_client = api.APIClient(loop=self.loop, connector=self.connector) log.addHandler(api.APILoggingHandler(self.api_client)) @@ -40,14 +48,6 @@ class Bot(commands.Bot): async def start(self, *args, **kwargs) -> None: """Open an aiohttp session before logging in and connecting to Discord.""" - # Global aiohttp session for all cogs - # - Uses asyncio for DNS resolution instead of threads, so we don't spam threads - # - Uses AF_INET as its socket family to prevent https related problems both locally and in prod. - self.http_session = aiohttp.ClientSession( - connector=aiohttp.TCPConnector( - resolver=aiohttp.AsyncResolver(), - family=socket.AF_INET, - ) - ) + self.http_session = aiohttp.ClientSession(connector=self.connector) await super().start(*args, **kwargs) -- cgit v1.2.3 From 1b938af27cb9901acdb86579029dc4a7cbae0b7d Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 9 Dec 2019 23:18:32 -0800 Subject: Moderation: show HTTP status code in the log for deactivation failures --- bot/cogs/moderation/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 3e0968121..703b09802 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -329,7 +329,7 @@ class InfractionScheduler(Scheduler): log_content = mod_role.mention except discord.HTTPException as e: log.exception(f"Failed to deactivate infraction #{id_} ({type_})") - log_text["Failure"] = f"HTTPException with code {e.code}." + log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}." log_content = mod_role.mention # Check if the user is currently being watched by Big Brother. -- cgit v1.2.3 From d0e14dca855179bd71c46747ecf63d4038045881 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 9 Dec 2019 23:34:30 -0800 Subject: Moderation: catch HTTPException when applying an infraction Only a warning is logged if it's a Forbidden error. Otherwise, the whole exception is logged. --- bot/cogs/moderation/scheduler.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 703b09802..8e5b4691f 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -146,14 +146,18 @@ class InfractionScheduler(Scheduler): if expiry: # Schedule the expiration of the infraction. self.schedule_task(ctx.bot.loop, infraction["id"], infraction) - except discord.Forbidden: + except discord.HTTPException as e: # Accordingly display that applying the infraction failed. confirm_msg = f":x: failed to apply" expiry_msg = "" log_content = ctx.author.mention log_title = "failed to apply" - log.warning(f"Failed to apply {infr_type} infraction #{id_} to {user}.") + log_msg = f"Failed to apply {infr_type} infraction #{id_} to {user}" + if isinstance(e, discord.Forbidden): + log.warning(f"{log_msg}: bot lacks permissions.") + else: + log.exception(log_msg) # Send a confirmation message to the invoking context. log.trace(f"Sending infraction #{id_} confirmation message.") @@ -324,7 +328,7 @@ class InfractionScheduler(Scheduler): f"Attempted to deactivate an unsupported infraction #{id_} ({type_})!" ) except discord.Forbidden: - log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions") + log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions.") log_text["Failure"] = f"The bot lacks permissions to do this (role hierarchy?)" log_content = mod_role.mention except discord.HTTPException as e: -- cgit v1.2.3 From f0e993a3514c1ef7256c4b7593d4db94a6d34569 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 9 Dec 2019 23:41:27 -0800 Subject: Infractions: kick user from voice after muting (#644) --- bot/cogs/moderation/infractions.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 2713a1b68..fe5150652 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -208,8 +208,13 @@ class Infractions(InfractionScheduler, commands.Cog): self.mod_log.ignore(Event.member_update, user.id) - action = user.add_roles(self._muted_role, reason=reason) - await self.apply_infraction(ctx, infraction, user, action) + async def action() -> None: + await user.add_roles(self._muted_role, reason=reason) + + log.trace(f"Attempting to kick {user} from voice because they've been muted.") + await user.move_to(None, reason=reason) + + await self.apply_infraction(ctx, infraction, user, action()) @respect_role_hierarchy() async def apply_kick(self, ctx: Context, user: Member, reason: str, **kwargs) -> None: -- cgit v1.2.3 From 9a3e83116e145b720fc47b0686b357fa6ae9e488 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 02:05:18 -0800 Subject: ErrorHandler: fix #650 tag fallback not respecting checks --- bot/cogs/error_handler.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 49411814c..5fba9633b 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -75,6 +75,16 @@ class ErrorHandler(Cog): tags_get_command = self.bot.get_command("tags get") ctx.invoked_from_error_handler = True + log_msg = "Cancelling attempt to fall back to a tag due to failed checks." + try: + if not await tags_get_command.can_run(ctx): + log.debug(log_msg) + return + except CommandError as tag_error: + log.debug(log_msg) + await self.on_command_error(ctx, tag_error) + return + # Return to not raise the exception with contextlib.suppress(ResponseCodeError): await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with) -- cgit v1.2.3 From 9d551cc69c1935165389f26f52753895604dd3f5 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 20:26:26 -0800 Subject: Add a generic converter for only allowing certain string values --- bot/cogs/moderation/management.py | 13 ++----------- bot/converters.py | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index abfe5c2b3..50bce3981 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -9,7 +9,7 @@ from discord.ext import commands from discord.ext.commands import Context from bot import constants -from bot.converters import InfractionSearchQuery +from bot.converters import InfractionSearchQuery, string from bot.pagination import LinePaginator from bot.utils import time from bot.utils.checks import in_channel_check, with_role_check @@ -22,15 +22,6 @@ log = logging.getLogger(__name__) UserConverter = t.Union[discord.User, utils.proxy_user] -def permanent_duration(expires_at: str) -> str: - """Only allow an expiration to be 'permanent' if it is a string.""" - expires_at = expires_at.lower() - if expires_at != "permanent": - raise commands.BadArgument - else: - return expires_at - - class ModManagement(commands.Cog): """Management of infractions.""" @@ -61,7 +52,7 @@ class ModManagement(commands.Cog): self, ctx: Context, infraction_id: int, - duration: t.Union[utils.Expiry, permanent_duration, None], + duration: t.Union[utils.Expiry, string("permanent"), None], *, reason: str = None ) -> None: diff --git a/bot/converters.py b/bot/converters.py index cf0496541..2cfc42903 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -1,8 +1,8 @@ import logging import re +import typing as t from datetime import datetime from ssl import CertificateError -from typing import Union import dateutil.parser import dateutil.tz @@ -15,6 +15,25 @@ from discord.ext.commands import BadArgument, Context, Converter log = logging.getLogger(__name__) +def string(*values, preserve_case: bool = False) -> t.Callable[[str], str]: + """ + Return a converter which only allows arguments equal to one of the given values. + + Unless preserve_case is True, the argument is converter to lowercase. All values are then + expected to have already been given in lowercase too. + """ + def converter(arg: str) -> str: + if not preserve_case: + arg = arg.lower() + + if arg not in values: + raise BadArgument(f"Only the following values are allowed:\n```{', '.join(values)}```") + else: + return arg + + return converter + + class ValidPythonIdentifier(Converter): """ A converter that checks whether the given string is a valid Python identifier. @@ -70,7 +89,7 @@ class InfractionSearchQuery(Converter): """A converter that checks if the argument is a Discord user, and if not, falls back to a string.""" @staticmethod - async def convert(ctx: Context, arg: str) -> Union[discord.Member, str]: + async def convert(ctx: Context, arg: str) -> t.Union[discord.Member, str]: """Check if the argument is a Discord user, and if not, falls back to a string.""" try: maybe_snowflake = arg.strip("<@!>") -- cgit v1.2.3 From 729ac3d83a3bd4620d1e9b24769466e219d45de6 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 21:00:47 -0800 Subject: ModManagement: allow "recent" as ID to edit infraction (#624) It will attempt to find the most recent infraction authored by the invoker of the edit command. --- bot/cogs/moderation/management.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 50bce3981..35832ded5 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -51,7 +51,7 @@ class ModManagement(commands.Cog): async def infraction_edit( self, ctx: Context, - infraction_id: int, + infraction_id: t.Union[int, string("recent")], duration: t.Union[utils.Expiry, string("permanent"), None], *, reason: str = None @@ -69,6 +69,9 @@ class ModManagement(commands.Cog): \u2003`M` - minutes∗ \u2003`s` - seconds + Use "recent" as the infraction ID to specify that the ost recent infraction authored by the + command invoker should be edited. + Use "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601 timestamp can be provided for the duration. """ @@ -77,7 +80,23 @@ class ModManagement(commands.Cog): raise commands.BadArgument("Neither a new expiry nor a new reason was specified.") # Retrieve the previous infraction for its information. - old_infraction = await self.bot.api_client.get(f'bot/infractions/{infraction_id}') + if infraction_id == "recent": + params = { + "actor__id": ctx.author.id, + "ordering": "-inserted_at" + } + infractions = await self.bot.api_client.get(f"bot/infractions", params=params) + + if infractions: + old_infraction = infractions[0] + infraction_id = old_infraction["id"] + else: + await ctx.send( + f":x: Couldn't find most recent infraction; you have never given an infraction." + ) + return + else: + old_infraction = await self.bot.api_client.get(f"bot/infractions/{infraction_id}") request_data = {} confirm_messages = [] -- cgit v1.2.3 From c1bf0a48692d87c5cbe9ee310cd0120bda339a96 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 21:01:16 -0800 Subject: ModManagement: display ID of edited infraction in confirmation message --- bot/cogs/moderation/management.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 35832ded5..904611e13 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -139,7 +139,8 @@ class ModManagement(commands.Cog): New expiry: {new_infraction['expires_at'] or "Permanent"} """.rstrip() - await ctx.send(f":ok_hand: Updated infraction: {' & '.join(confirm_messages)}") + changes = ' & '.join(confirm_messages) + await ctx.send(f":ok_hand: Updated infraction #{infraction_id}: {changes}") # Get information about the infraction's user user_id = new_infraction['user'] -- cgit v1.2.3 From 56833bbe99ce3cd93af87e9d33cec47a059b61f3 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 21:52:57 -0800 Subject: ModManagement: add more aliases for "special" params of infraction edit --- bot/cogs/moderation/management.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 904611e13..37bdb1934 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -51,8 +51,8 @@ class ModManagement(commands.Cog): async def infraction_edit( self, ctx: Context, - infraction_id: t.Union[int, string("recent")], - duration: t.Union[utils.Expiry, string("permanent"), None], + infraction_id: t.Union[int, string("l", "last", "recent")], + duration: t.Union[utils.Expiry, string("p", "permanent"), None], *, reason: str = None ) -> None: @@ -69,18 +69,18 @@ class ModManagement(commands.Cog): \u2003`M` - minutes∗ \u2003`s` - seconds - Use "recent" as the infraction ID to specify that the ost recent infraction authored by the - command invoker should be edited. + Use "l", "last", or "recent" as the infraction ID to specify that the most recent infraction + authored by the command invoker should be edited. - Use "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601 timestamp - can be provided for the duration. + Use "p" or "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601 + timestamp can be provided for the duration. """ if duration is None and reason is None: # Unlike UserInputError, the error handler will show a specified message for BadArgument raise commands.BadArgument("Neither a new expiry nor a new reason was specified.") # Retrieve the previous infraction for its information. - if infraction_id == "recent": + if isinstance(infraction_id, str): params = { "actor__id": ctx.author.id, "ordering": "-inserted_at" @@ -102,7 +102,7 @@ class ModManagement(commands.Cog): confirm_messages = [] log_text = "" - if duration == "permanent": + if isinstance(duration, str): request_data['expires_at'] = None confirm_messages.append("marked as permanent") elif duration is not None: -- cgit v1.2.3 From eb53a4594dff20372574058ec90062995362b098 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 11 Dec 2019 22:03:20 -0800 Subject: Converters: rename string to allowed_strings --- bot/cogs/moderation/management.py | 6 +++--- bot/converters.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/cogs/moderation/management.py b/bot/cogs/moderation/management.py index 37bdb1934..20ff25ba1 100644 --- a/bot/cogs/moderation/management.py +++ b/bot/cogs/moderation/management.py @@ -9,7 +9,7 @@ from discord.ext import commands from discord.ext.commands import Context from bot import constants -from bot.converters import InfractionSearchQuery, string +from bot.converters import InfractionSearchQuery, allowed_strings from bot.pagination import LinePaginator from bot.utils import time from bot.utils.checks import in_channel_check, with_role_check @@ -51,8 +51,8 @@ class ModManagement(commands.Cog): async def infraction_edit( self, ctx: Context, - infraction_id: t.Union[int, string("l", "last", "recent")], - duration: t.Union[utils.Expiry, string("p", "permanent"), None], + infraction_id: t.Union[int, allowed_strings("l", "last", "recent")], + duration: t.Union[utils.Expiry, allowed_strings("p", "permanent"), None], *, reason: str = None ) -> None: diff --git a/bot/converters.py b/bot/converters.py index 2cfc42903..8d2ab7eb8 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -15,11 +15,11 @@ from discord.ext.commands import BadArgument, Context, Converter log = logging.getLogger(__name__) -def string(*values, preserve_case: bool = False) -> t.Callable[[str], str]: +def allowed_strings(*values, preserve_case: bool = False) -> t.Callable[[str], str]: """ Return a converter which only allows arguments equal to one of the given values. - Unless preserve_case is True, the argument is converter to lowercase. All values are then + Unless preserve_case is True, the argument is converted to lowercase. All values are then expected to have already been given in lowercase too. """ def converter(arg: str) -> str: -- cgit v1.2.3