From e0438b2f78ffbc22a9d4d391db524563ec9baa18 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 20 Aug 2020 11:16:18 -0700 Subject: Watchchannels: censor message content if it has a leaked token Fixes #1094 --- bot/cogs/watchchannels/watchchannel.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bot/cogs/watchchannels/watchchannel.py b/bot/cogs/watchchannels/watchchannel.py index 044077350..a58b604c0 100644 --- a/bot/cogs/watchchannels/watchchannel.py +++ b/bot/cogs/watchchannels/watchchannel.py @@ -15,6 +15,8 @@ from discord.ext.commands import Cog, Context from bot.api import ResponseCodeError from bot.bot import Bot from bot.cogs.moderation import ModLog +from bot.cogs.token_remover import TokenRemover +from bot.cogs.webhook_remover import WEBHOOK_URL_RE from bot.constants import BigBrother as BigBrotherConfig, Guild as GuildConfig, Icons from bot.pagination import LinePaginator from bot.utils import CogABCMeta, messages @@ -226,14 +228,16 @@ class WatchChannel(metaclass=CogABCMeta): await self.send_header(msg) - cleaned_content = msg.clean_content - - if cleaned_content: + if TokenRemover.find_token_in_message(msg) or WEBHOOK_URL_RE.search(msg.content): + cleaned_content = "Content is censored because it contains a bot or webhook token." + elif cleaned_content := msg.clean_content: # Put all non-media URLs in a code block to prevent embeds media_urls = {embed.url for embed in msg.embeds if embed.type in ("image", "video")} for url in URL_RE.findall(cleaned_content): if url not in media_urls: cleaned_content = cleaned_content.replace(url, f"`{url}`") + + if cleaned_content: await self.webhook_send( cleaned_content, username=msg.author.display_name, -- cgit v1.2.3 From c0afea19897ec0b47642bb62e4a426f4ca0c3cc8 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 20 Aug 2020 11:18:02 -0700 Subject: Don't send code block help if message has a webhook token --- bot/cogs/bot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 79510739c..93f2eae7c 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -9,6 +9,7 @@ from discord.ext.commands import Cog, Context, command, group from bot.bot import Bot from bot.cogs.token_remover import TokenRemover +from bot.cogs.webhook_remover import WEBHOOK_URL_RE from bot.constants import Categories, Channels, DEBUG_MODE, Guild, MODERATION_ROLES, Roles, URLs from bot.decorators import with_role from bot.utils.messages import wait_for_deletion @@ -240,6 +241,7 @@ class BotCog(Cog, name="Bot"): and not msg.author.bot and len(msg.content.splitlines()) > 3 and not TokenRemover.find_token_in_message(msg) + and not WEBHOOK_URL_RE.search(msg.content) ) if parse_codeblock: # no token in the msg -- cgit v1.2.3 From 59a58db3ca6ba14539b028f3e02ccc4d89ec16a0 Mon Sep 17 00:00:00 2001 From: AtieP Date: Sat, 22 Aug 2020 18:38:05 +0200 Subject: Use wait_for_deletion from bot/utils/messages.py rather than help_cleanup --- bot/cogs/help.py | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 3d1d6fd10..76aaf655c 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -1,11 +1,10 @@ import itertools import logging -from asyncio import TimeoutError from collections import namedtuple from contextlib import suppress from typing import List, Union -from discord import Colour, Embed, Member, Message, NotFound, Reaction, User +from discord import Colour, Embed from discord.ext.commands import Bot, Cog, Command, Context, Group, HelpCommand from fuzzywuzzy import fuzz, process from fuzzywuzzy.utils import full_process @@ -14,6 +13,7 @@ from bot import constants from bot.constants import Channels, Emojis, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import LinePaginator +from bot.utils.messages import wait_for_deletion log = logging.getLogger(__name__) @@ -24,27 +24,6 @@ PREFIX = constants.Bot.prefix Category = namedtuple("Category", ["name", "description", "cogs"]) -async def help_cleanup(bot: Bot, author: Member, message: Message) -> None: - """ - Runs the cleanup for the help command. - - Adds the :trashcan: reaction that, when clicked, will delete the help message. - After a 300 second timeout, the reaction will be removed. - """ - def check(reaction: Reaction, user: User) -> bool: - """Checks the reaction is :trashcan:, the author is original author and messages are the same.""" - return str(reaction) == DELETE_EMOJI and user.id == author.id and reaction.message.id == message.id - - await message.add_reaction(DELETE_EMOJI) - - with suppress(NotFound): - try: - await bot.wait_for("reaction_add", check=check, timeout=300) - await message.delete() - except TimeoutError: - await message.remove_reaction(DELETE_EMOJI, bot.user) - - class HelpQueryNotFound(ValueError): """ Raised when a HelpSession Query doesn't match a command or cog. @@ -206,7 +185,7 @@ class CustomHelpCommand(HelpCommand): """Send help for a single command.""" embed = await self.command_formatting(command) message = await self.context.send(embed=embed) - await help_cleanup(self.context.bot, self.context.author, message) + await wait_for_deletion(message, (self.context.author.id,), self.context.bot) @staticmethod def get_commands_brief_details(commands_: List[Command], return_as_list: bool = False) -> Union[List[str], str]: @@ -245,7 +224,7 @@ class CustomHelpCommand(HelpCommand): embed.description += f"\n**Subcommands:**\n{command_details}" message = await self.context.send(embed=embed) - await help_cleanup(self.context.bot, self.context.author, message) + await wait_for_deletion(message, (self.context.author.id,), self.context.bot) async def send_cog_help(self, cog: Cog) -> None: """Send help for a cog.""" @@ -261,7 +240,7 @@ class CustomHelpCommand(HelpCommand): embed.description += f"\n\n**Commands:**\n{command_details}" message = await self.context.send(embed=embed) - await help_cleanup(self.context.bot, self.context.author, message) + await wait_for_deletion(message, (self.context.author.id,), self.context.bot) @staticmethod def _category_key(command: Command) -> str: -- cgit v1.2.3 From ee4efbb91300890424d1f8ecb1273166e9f0f53a Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 13:05:49 -0700 Subject: Define a Command subclass with root alias support A subclass is used because cogs make copies of Command objects. They do this to allow multiple instances of a cog to be used. If the Command class doesn't inherently support the `root_aliases` kwarg, it won't end up being copied when a command gets copied. `Command.__original_kwargs__` could be updated to include the new kwarg. However, updating it and adding the attribute to the command wouldn't be as elegant as passing a `Command` subclass as a `cls` attribute to the `commands.command` decorator. This is because the former requires copying the entire code of the decorator to add the two lines into the nested function (it's a decorator with args, hence the nested function). --- bot/command.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 bot/command.py diff --git a/bot/command.py b/bot/command.py new file mode 100644 index 000000000..92e61d97e --- /dev/null +++ b/bot/command.py @@ -0,0 +1,15 @@ +from discord.ext import commands + + +class Command(commands.Command): + """ + A `discord.ext.commands.Command` subclass which supports root aliases. + + A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as + top-level commands rather than being aliases of the command's group. It's stored as an attribute + also named `root_aliases`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) -- cgit v1.2.3 From f455a7908a9b07747db6ab89f9c5c53bd5ea2450 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 19:13:21 -0700 Subject: Bot: add root alias support Override `Bot.add_command` and `Bot.remove_command` to add/remove root aliases for a command (and recursively for any subcommands). This has to happen in `Bot` because there's no reliable way to get the `Bot` instance otherwise. Therefore, overriding the methods in `GroupMixin` unfortunately doesn't work. Otherwise, it'd be possible to avoid recursion by processing each subcommand as it got added. --- bot/bot.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/bot/bot.py b/bot/bot.py index 756449293..34254d8e8 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -130,6 +130,26 @@ class Bot(commands.Bot): super().add_cog(cog) log.info(f"Cog loaded: {cog.qualified_name}") + def add_command(self, command: commands.Command) -> None: + """Add `command` as normal and then add its root aliases to the bot.""" + super().add_command(command) + self._add_root_aliases(command) + + def remove_command(self, name: str) -> Optional[commands.Command]: + """ + Remove a command/alias as normal and then remove its root aliases from the bot. + + Individual root aliases cannot be removed by this function. + To remove them, either remove the entire command or manually edit `bot.all_commands`. + """ + command = super().remove_command(name) + if command is None: + # Even if it's a root alias, there's no way to get the Bot instance to remove the alias. + return + + self._remove_root_aliases(command) + return command + def clear(self) -> None: """ Clears the internal state of the bot and recreates the connector and sessions. @@ -235,3 +255,24 @@ class Bot(commands.Bot): scope.set_extra("kwargs", kwargs) log.exception(f"Unhandled exception in {event}.") + + def _add_root_aliases(self, command: commands.Command) -> None: + """Recursively add root aliases for `command` and any of its subcommands.""" + if isinstance(command, commands.Group): + for subcommand in command.commands: + self._add_root_aliases(subcommand) + + for alias in command.root_aliases: + if alias in self.all_commands: + raise commands.CommandRegistrationError(alias, alias_conflict=True) + + self.all_commands[alias] = command + + def _remove_root_aliases(self, command: commands.Command) -> None: + """Recursively remove root aliases for `command` and any of its subcommands.""" + if isinstance(command, commands.Group): + for subcommand in command.commands: + self._remove_root_aliases(subcommand) + + for alias in command.root_aliases: + self.all_commands.pop(alias, None) -- cgit v1.2.3 From 36ec4b31730ac7243fc76fe5140a0ed2e922940f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 19:20:54 -0700 Subject: Patch d.py decorators to support root aliases To avoid explicitly specifying `cls` everywhere, patch the decorators to set the default value of `cls` to the `Command` subclass which supports root aliases. --- bot/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot/__init__.py b/bot/__init__.py index d63086fe2..3ee70c4e9 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -2,10 +2,14 @@ import asyncio import logging import os import sys +from functools import partial, partialmethod from logging import Logger, handlers from pathlib import Path import coloredlogs +from discord.ext import commands + +from bot.command import Command TRACE_LEVEL = logging.TRACE = 5 logging.addLevelName(TRACE_LEVEL, "TRACE") @@ -66,3 +70,9 @@ logging.getLogger(__name__) # On Windows, the selector event loop is required for aiodns. if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + +# Monkey-patch discord.py decorators to use the Command subclass which supports root aliases. +# Must be patched before any cogs are added. +commands.command = partial(commands.command, cls=Command) +commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command) -- cgit v1.2.3 From 027ce8c5525187296a9f7bd26b89af9c66200835 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 19:43:15 -0700 Subject: Bot: fix AttributeError for commands which lack root_aliases Even if the `command` decorators are patched, there are still some other internal things that need to be patched. For example, the default help command subclasses the original `Command` type. It's more maintainable to exclude root alias support for these objects than to try to patch everything. --- bot/bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 34254d8e8..d25074fd9 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -262,7 +262,7 @@ class Bot(commands.Bot): for subcommand in command.commands: self._add_root_aliases(subcommand) - for alias in command.root_aliases: + for alias in getattr(command, "root_aliases", ()): if alias in self.all_commands: raise commands.CommandRegistrationError(alias, alias_conflict=True) @@ -274,5 +274,5 @@ class Bot(commands.Bot): for subcommand in command.commands: self._remove_root_aliases(subcommand) - for alias in command.root_aliases: + for alias in getattr(command, "root_aliases", ()): self.all_commands.pop(alias, None) -- cgit v1.2.3 From c6a20ef3b7b7afe3013a17042f8f2ca84566d998 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 20:36:27 -0700 Subject: Replace alias command definitions with root_aliases The fruits of my labour. --- bot/cogs/alias.py | 70 ++---------------------------------- bot/cogs/defcon.py | 4 +-- bot/cogs/extensions.py | 2 +- bot/cogs/site.py | 10 +++--- bot/cogs/watchchannels/bigbrother.py | 4 +-- bot/cogs/watchchannels/talentpool.py | 6 ++-- 6 files changed, 15 insertions(+), 81 deletions(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 55c7efe65..c6ba8d6f3 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -3,13 +3,12 @@ import logging from discord import Colour, Embed from discord.ext.commands import ( - Cog, Command, Context, Greedy, + Cog, Command, Context, clean_content, command, group, ) from bot.bot import Bot -from bot.cogs.extensions import Extension -from bot.converters import FetchedMember, TagNameConverter +from bot.converters import TagNameConverter from bot.pagination import LinePaginator log = logging.getLogger(__name__) @@ -51,56 +50,6 @@ class Alias (Cog): ctx, embed, empty=False, max_lines=20 ) - @command(name="resources", aliases=("resource",), hidden=True) - async def site_resources_alias(self, ctx: Context) -> None: - """Alias for invoking site resources.""" - await self.invoke(ctx, "site resources") - - @command(name="tools", hidden=True) - async def site_tools_alias(self, ctx: Context) -> None: - """Alias for invoking site tools.""" - await self.invoke(ctx, "site tools") - - @command(name="watch", hidden=True) - async def bigbrother_watch_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: - """Alias for invoking bigbrother watch [user] [reason].""" - await self.invoke(ctx, "bigbrother watch", user, reason=reason) - - @command(name="unwatch", hidden=True) - async def bigbrother_unwatch_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: - """Alias for invoking bigbrother unwatch [user] [reason].""" - await self.invoke(ctx, "bigbrother unwatch", user, reason=reason) - - @command(name="home", hidden=True) - async def site_home_alias(self, ctx: Context) -> None: - """Alias for invoking site home.""" - await self.invoke(ctx, "site home") - - @command(name="faq", hidden=True) - async def site_faq_alias(self, ctx: Context) -> None: - """Alias for invoking site faq.""" - await self.invoke(ctx, "site faq") - - @command(name="rules", aliases=("rule",), hidden=True) - async def site_rules_alias(self, ctx: Context, rules: Greedy[int], *_: str) -> None: - """Alias for invoking site rules.""" - await self.invoke(ctx, "site rules", *rules) - - @command(name="reload", hidden=True) - async def extensions_reload_alias(self, ctx: Context, *extensions: Extension) -> None: - """Alias for invoking extensions reload [extensions...].""" - await self.invoke(ctx, "extensions reload", *extensions) - - @command(name="defon", hidden=True) - async def defcon_enable_alias(self, ctx: Context) -> None: - """Alias for invoking defcon enable.""" - await self.invoke(ctx, "defcon enable") - - @command(name="defoff", hidden=True) - async def defcon_disable_alias(self, ctx: Context) -> None: - """Alias for invoking defcon disable.""" - await self.invoke(ctx, "defcon disable") - @command(name="exception", hidden=True) async def tags_get_traceback_alias(self, ctx: Context) -> None: """Alias for invoking tags get traceback.""" @@ -132,21 +81,6 @@ class Alias (Cog): """Alias for invoking docs get [symbol].""" await self.invoke(ctx, "docs get", symbol) - @command(name="nominate", hidden=True) - async def nomination_add_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: - """Alias for invoking talentpool add [user] [reason].""" - await self.invoke(ctx, "talentpool add", user, reason=reason) - - @command(name="unnominate", hidden=True) - async def nomination_end_alias(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: - """Alias for invoking nomination end [user] [reason].""" - await self.invoke(ctx, "nomination end", user, reason=reason) - - @command(name="nominees", hidden=True) - async def nominees_alias(self, ctx: Context) -> None: - """Alias for invoking tp watched.""" - await self.invoke(ctx, "talentpool watched") - def setup(bot: Bot) -> None: """Load the Alias cog.""" diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index 4c0ad5914..de0f4545e 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -162,7 +162,7 @@ class Defcon(Cog): self.bot.stats.gauge("defcon.threshold", days) - @defcon_group.command(name='enable', aliases=('on', 'e')) + @defcon_group.command(name='enable', aliases=('on', 'e'), root_aliases=("defon",)) @with_role(Roles.admins, Roles.owners) async def enable_command(self, ctx: Context) -> None: """ @@ -175,7 +175,7 @@ class Defcon(Cog): await self._defcon_action(ctx, days=0, action=Action.ENABLED) await self.update_channel_topic() - @defcon_group.command(name='disable', aliases=('off', 'd')) + @defcon_group.command(name='disable', aliases=('off', 'd'), root_aliases=("defoff",)) @with_role(Roles.admins, Roles.owners) async def disable_command(self, ctx: Context) -> None: """Disable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!""" diff --git a/bot/cogs/extensions.py b/bot/cogs/extensions.py index 365f198ff..396e406b0 100644 --- a/bot/cogs/extensions.py +++ b/bot/cogs/extensions.py @@ -107,7 +107,7 @@ class Extensions(commands.Cog): await ctx.send(msg) - @extensions_group.command(name="reload", aliases=("r",)) + @extensions_group.command(name="reload", aliases=("r",), root_aliases=("reload",)) async def reload_command(self, ctx: Context, *extensions: Extension) -> None: r""" Reload extensions given their fully qualified or unqualified names. diff --git a/bot/cogs/site.py b/bot/cogs/site.py index ac29daa1d..2d3a3d9f3 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -23,7 +23,7 @@ class Site(Cog): """Commands for getting info about our website.""" await ctx.send_help(ctx.command) - @site_group.command(name="home", aliases=("about",)) + @site_group.command(name="home", aliases=("about",), root_aliases=("home",)) async def site_main(self, ctx: Context) -> None: """Info about the website itself.""" url = f"{URLs.site_schema}{URLs.site}/" @@ -40,7 +40,7 @@ class Site(Cog): await ctx.send(embed=embed) - @site_group.command(name="resources") + @site_group.command(name="resources", root_aliases=("resources", "resource")) async def site_resources(self, ctx: Context) -> None: """Info about the site's Resources page.""" learning_url = f"{PAGES_URL}/resources" @@ -56,7 +56,7 @@ class Site(Cog): await ctx.send(embed=embed) - @site_group.command(name="tools") + @site_group.command(name="tools", root_aliases=("tools",)) async def site_tools(self, ctx: Context) -> None: """Info about the site's Tools page.""" tools_url = f"{PAGES_URL}/resources/tools" @@ -87,7 +87,7 @@ class Site(Cog): await ctx.send(embed=embed) - @site_group.command(name="faq") + @site_group.command(name="faq", root_aliases=("faq",)) async def site_faq(self, ctx: Context) -> None: """Info about the site's FAQ page.""" url = f"{PAGES_URL}/frequently-asked-questions" @@ -104,7 +104,7 @@ class Site(Cog): await ctx.send(embed=embed) - @site_group.command(aliases=['r', 'rule'], name='rules') + @site_group.command(name="rules", aliases=("r", "rule"), root_aliases=("rules", "rule")) async def site_rules(self, ctx: Context, *rules: int) -> None: """Provides a link to all rules or, if specified, displays specific rule(s).""" rules_embed = Embed(title='Rules', color=Colour.blurple()) diff --git a/bot/cogs/watchchannels/bigbrother.py b/bot/cogs/watchchannels/bigbrother.py index 7aa9cec58..11ab8917a 100644 --- a/bot/cogs/watchchannels/bigbrother.py +++ b/bot/cogs/watchchannels/bigbrother.py @@ -59,7 +59,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): """ await ctx.invoke(self.watched_command, oldest_first=True, update_cache=update_cache) - @bigbrother_group.command(name='watch', aliases=('w',)) + @bigbrother_group.command(name='watch', aliases=('w',), root_aliases=('watch',)) @with_role(*MODERATION_ROLES) async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """ @@ -70,7 +70,7 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"): """ await self.apply_watch(ctx, user, reason) - @bigbrother_group.command(name='unwatch', aliases=('uw',)) + @bigbrother_group.command(name='unwatch', aliases=('uw',), root_aliases=('unwatch',)) @with_role(*MODERATION_ROLES) async def unwatch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """Stop relaying messages by the given `user`.""" diff --git a/bot/cogs/watchchannels/talentpool.py b/bot/cogs/watchchannels/talentpool.py index a6df84c23..76d6fe9bd 100644 --- a/bot/cogs/watchchannels/talentpool.py +++ b/bot/cogs/watchchannels/talentpool.py @@ -37,7 +37,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): """Highlights the activity of helper nominees by relaying their messages to the talent pool channel.""" await ctx.send_help(ctx.command) - @nomination_group.command(name='watched', aliases=('all', 'list')) + @nomination_group.command(name='watched', aliases=('all', 'list'), root_aliases=("nominees",)) @with_role(*MODERATION_ROLES) async def watched_command( self, ctx: Context, oldest_first: bool = False, update_cache: bool = True @@ -63,7 +63,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): """ await ctx.invoke(self.watched_command, oldest_first=True, update_cache=update_cache) - @nomination_group.command(name='watch', aliases=('w', 'add', 'a')) + @nomination_group.command(name='watch', aliases=('w', 'add', 'a'), root_aliases=("nominate",)) @with_role(*STAFF_ROLES) async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """ @@ -157,7 +157,7 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"): max_size=1000 ) - @nomination_group.command(name='unwatch', aliases=('end', )) + @nomination_group.command(name='unwatch', aliases=('end', ), root_aliases=("unnominate",)) @with_role(*MODERATION_ROLES) async def unwatch_command(self, ctx: Context, user: FetchedMember, *, reason: str) -> None: """ -- cgit v1.2.3 From 520ac0f9871bf6775d76eea753ed2a940704e92d Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 20:44:48 -0700 Subject: Include root aliases in the command name conflict test --- tests/bot/cogs/test_cogs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bot/cogs/test_cogs.py b/tests/bot/cogs/test_cogs.py index fdda59a8f..30a04422a 100644 --- a/tests/bot/cogs/test_cogs.py +++ b/tests/bot/cogs/test_cogs.py @@ -53,6 +53,7 @@ class CommandNameTests(unittest.TestCase): """Return a list of all qualified names, including aliases, for the `command`.""" names = [f"{command.full_parent_name} {alias}".strip() for alias in command.aliases] names.append(command.qualified_name) + names += getattr(command, "root_aliases", []) return names -- cgit v1.2.3 From fa92df15a4644d01256edeb440242ae92dc8adf0 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 22 Aug 2020 20:52:21 -0700 Subject: Help: include root aliases in output --- bot/cogs/help.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 3d1d6fd10..25ce4ae0f 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -189,7 +189,9 @@ class CustomHelpCommand(HelpCommand): command_details = f"**```{PREFIX}{name} {command.signature}```**\n" # show command aliases - aliases = ", ".join(f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in command.aliases) + aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in command.aliases] + aliases += [f"`{alias}`" for alias in getattr(command, "root_aliases", ())] + aliases = ", ".join(sorted(aliases)) if aliases: command_details += f"**Can also use:** {aliases}\n\n" -- cgit v1.2.3 From fa28bf6de4dd5c5412b68d4ad448e0b4cb15cfac Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 24 Aug 2020 10:10:38 -0700 Subject: Type check root aliases Just like normal aliases, they should only be tuples or lists. This is likely done by discord.py to prevent accidentally passing a string when only a single alias is desired. --- bot/command.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/command.py b/bot/command.py index 92e61d97e..0fb900f7b 100644 --- a/bot/command.py +++ b/bot/command.py @@ -13,3 +13,6 @@ class Command(commands.Command): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a command must be a list or a tuple of strings.") -- cgit v1.2.3 From 474d78704d852eec106df8d6f64783d0216f4b7f Mon Sep 17 00:00:00 2001 From: Boris Muratov Date: Wed, 26 Aug 2020 02:42:20 +0300 Subject: Bold link to asking guide in embeds --- bot/cogs/help_channels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py index 57094751e..541c6f336 100644 --- a/bot/cogs/help_channels.py +++ b/bot/cogs/help_channels.py @@ -36,7 +36,7 @@ the **Help: Dormant** category. Try to write the best question you can by providing a detailed description and telling us what \ you've tried already. For more information on asking a good question, \ -check out our guide on [asking good questions]({ASKING_GUIDE_URL}). +check out our guide on [**asking good questions**]({ASKING_GUIDE_URL}). """ DORMANT_MSG = f""" @@ -47,7 +47,7 @@ channel until it becomes available again. If your question wasn't answered yet, you can claim a new help channel from the \ **Help: Available** category by simply asking your question again. Consider rephrasing the \ question to maximize your chance of getting a good answer. If you're not sure how, have a look \ -through our guide for [asking a good question]({ASKING_GUIDE_URL}). +through our guide for [**asking a good question**]({ASKING_GUIDE_URL}). """ CoroutineFunc = t.Callable[..., t.Coroutine] -- cgit v1.2.3 From 30cbde7a7c48e59a19b5a7f1934d0e7674473d62 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 27 Aug 2020 12:26:27 -0700 Subject: AntiSpam: ignore custom emojis in code blocks In code blocks, custom emojis render as text rather than as images. Therefore, they probably aren't being spammed and should be ignored. Fix #1130 --- bot/rules/discord_emojis.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/rules/discord_emojis.py b/bot/rules/discord_emojis.py index 5bab514f2..6e47f0197 100644 --- a/bot/rules/discord_emojis.py +++ b/bot/rules/discord_emojis.py @@ -5,6 +5,7 @@ from discord import Member, Message DISCORD_EMOJI_RE = re.compile(r"<:\w+:\d+>") +CODE_BLOCK_RE = re.compile(r"```.*?```", flags=re.DOTALL) async def apply( @@ -17,8 +18,9 @@ async def apply( if msg.author == last_message.author ) + # Get rid of code blocks in the message before searching for emojis. total_emojis = sum( - len(DISCORD_EMOJI_RE.findall(msg.content)) + len(DISCORD_EMOJI_RE.findall(CODE_BLOCK_RE.sub("", msg.content))) for msg in relevant_messages ) -- cgit v1.2.3 From 7016124192f3228145195765b1c94535700e54aa Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 27 Aug 2020 23:14:38 +0100 Subject: Update Discord Partner badge --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 8c0092e76..b4dc34e85 100644 --- a/config-default.yml +++ b/config-default.yml @@ -39,7 +39,7 @@ style: status_offline: "<:status_offline:470326266537705472>" badge_staff: "<:discord_staff:743882896498098226>" - badge_partner: "<:partner:743882897131569323>" + badge_partner: "<:partner:748666453242413136>" badge_hypesquad: "<:hypesquad_events:743882896892362873>" badge_bug_hunter: "<:bug_hunter_lvl1:743882896372269137>" badge_hypesquad_bravery: "<:hypesquad_bravery:743882896745693335>" -- cgit v1.2.3 From 2fcf07fd041fa58beca52cfa33540343b54e85fd Mon Sep 17 00:00:00 2001 From: AtieP Date: Sat, 29 Aug 2020 09:10:45 +0200 Subject: Remove unused variables and imports --- bot/cogs/help.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index 76aaf655c..6caa211a6 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -10,7 +10,7 @@ from fuzzywuzzy import fuzz, process from fuzzywuzzy.utils import full_process from bot import constants -from bot.constants import Channels, Emojis, STAFF_ROLES +from bot.constants import Channels, STAFF_ROLES from bot.decorators import redirect_output from bot.pagination import LinePaginator from bot.utils.messages import wait_for_deletion @@ -18,7 +18,6 @@ from bot.utils.messages import wait_for_deletion log = logging.getLogger(__name__) COMMANDS_PER_PAGE = 8 -DELETE_EMOJI = Emojis.trashcan PREFIX = constants.Bot.prefix Category = namedtuple("Category", ["name", "description", "cogs"]) -- cgit v1.2.3 From 8b10533851ca3fe3b44dd6662f634ae89550ad16 Mon Sep 17 00:00:00 2001 From: Xithrius Date: Sat, 29 Aug 2020 01:38:04 -0700 Subject: Completely gutted the wolfram command. Moved to seasonalbot/bot/exts/evergreen/wolfram.py --- bot/cogs/wolfram.py | 280 ---------------------------------------------------- bot/constants.py | 8 -- config-default.yml | 7 -- 3 files changed, 295 deletions(-) delete mode 100644 bot/cogs/wolfram.py diff --git a/bot/cogs/wolfram.py b/bot/cogs/wolfram.py deleted file mode 100644 index e6cae3bb8..000000000 --- a/bot/cogs/wolfram.py +++ /dev/null @@ -1,280 +0,0 @@ -import logging -from io import BytesIO -from typing import Callable, List, Optional, Tuple -from urllib import parse - -import discord -from dateutil.relativedelta import relativedelta -from discord import Embed -from discord.ext import commands -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 - -log = logging.getLogger(__name__) - -APPID = Wolfram.key -DEFAULT_OUTPUT_FORMAT = "JSON" -QUERY = "http://api.wolframalpha.com/v2/{request}?{data}" -WOLF_IMAGE = "https://www.symbols.com/gi.php?type=1&id=2886&i=1" - -MAX_PODS = 20 - -# Allows for 10 wolfram calls pr user pr day -usercd = commands.CooldownMapping.from_cooldown(Wolfram.user_limit_day, 60*60*24, BucketType.user) - -# Allows for max api requests / days in month per day for the entire guild (Temporary) -guildcd = commands.CooldownMapping.from_cooldown(Wolfram.guild_limit_day, 60*60*24, BucketType.guild) - - -async def send_embed( - ctx: Context, - message_txt: str, - colour: int = Colours.soft_red, - footer: str = None, - img_url: str = None, - f: discord.File = None -) -> None: - """Generate & send a response embed with Wolfram as the author.""" - embed = Embed(colour=colour) - embed.description = message_txt - embed.set_author(name="Wolfram Alpha", - icon_url=WOLF_IMAGE, - url="https://www.wolframalpha.com/") - if footer: - embed.set_footer(text=footer) - - if img_url: - embed.set_image(url=img_url) - - await ctx.send(embed=embed, file=f) - - -def custom_cooldown(*ignore: List[int]) -> Callable: - """ - Implement per-user and per-guild cooldowns for requests to the Wolfram API. - - A list of roles may be provided to ignore the per-user cooldown - """ - async def predicate(ctx: Context) -> bool: - if ctx.invoked_with == 'help': - # if the invoked command is help we don't want to increase the ratelimits since it's not actually - # invoking the command/making a request, so instead just check if the user/guild are on cooldown. - guild_cooldown = not guildcd.get_bucket(ctx.message).get_tokens() == 0 # if guild is on cooldown - if not any(r.id in ignore for r in ctx.author.roles): # check user bucket if user is not ignored - return guild_cooldown and not usercd.get_bucket(ctx.message).get_tokens() == 0 - return guild_cooldown - - user_bucket = usercd.get_bucket(ctx.message) - - if all(role.id not in ignore for role in ctx.author.roles): - user_rate = user_bucket.update_rate_limit() - - if user_rate: - # Can't use api; cause: member limit - delta = relativedelta(seconds=int(user_rate)) - cooldown = humanize_delta(delta) - message = ( - "You've used up your limit for Wolfram|Alpha requests.\n" - f"Cooldown: {cooldown}" - ) - await send_embed(ctx, message) - return False - - guild_bucket = guildcd.get_bucket(ctx.message) - guild_rate = guild_bucket.update_rate_limit() - - # Repr has a token attribute to read requests left - log.debug(guild_bucket) - - if guild_rate: - # Can't use api; cause: guild limit - message = ( - "The max limit of requests for the server has been reached for today.\n" - f"Cooldown: {int(guild_rate)}" - ) - await send_embed(ctx, message) - return False - - return True - return check(predicate) - - -async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tuple]]: - """Get the Wolfram API pod pages for the provided query.""" - async with ctx.channel.typing(): - url_str = parse.urlencode({ - "input": query, - "appid": APPID, - "output": DEFAULT_OUTPUT_FORMAT, - "format": "image,plaintext" - }) - request_url = QUERY.format(request="query", data=url_str) - - async with bot.http_session.get(request_url) as response: - json = await response.json(content_type='text/plain') - - result = json["queryresult"] - - if result["error"]: - # API key not set up correctly - if result["error"]["msg"] == "Invalid appid": - message = "Wolfram API key is invalid or missing." - log.warning( - "API key seems to be missing, or invalid when " - f"processing a wolfram request: {url_str}, Response: {json}" - ) - await send_embed(ctx, message) - return - - message = "Something went wrong internally with your request, please notify staff!" - log.warning(f"Something went wrong getting a response from wolfram: {url_str}, Response: {json}") - await send_embed(ctx, message) - return - - if not result["success"]: - message = f"I couldn't find anything for {query}." - await send_embed(ctx, message) - return - - if not result["numpods"]: - message = "Could not find any results." - await send_embed(ctx, message) - return - - pods = result["pods"] - pages = [] - for pod in pods[:MAX_PODS]: - subs = pod.get("subpods") - - for sub in subs: - title = sub.get("title") or sub.get("plaintext") or sub.get("id", "") - img = sub["img"]["src"] - pages.append((title, img)) - return pages - - -class Wolfram(Cog): - """Commands for interacting with the Wolfram|Alpha API.""" - - def __init__(self, bot: Bot): - self.bot = bot - - @group(name="wolfram", aliases=("wolf", "wa"), invoke_without_command=True) - @custom_cooldown(*STAFF_ROLES) - async def wolfram_command(self, ctx: Context, *, query: str) -> None: - """Requests all answers on a single image, sends an image of all related pods.""" - url_str = parse.urlencode({ - "i": query, - "appid": APPID, - }) - query = QUERY.format(request="simple", data=url_str) - - # Give feedback that the bot is working. - async with ctx.channel.typing(): - async with self.bot.http_session.get(query) as response: - status = response.status - image_bytes = await response.read() - - f = discord.File(BytesIO(image_bytes), filename="image.png") - image_url = "attachment://image.png" - - if status == 501: - message = "Failed to get response" - footer = "" - color = Colours.soft_red - elif status == 400: - message = "No input found" - footer = "" - color = Colours.soft_red - elif status == 403: - message = "Wolfram API key is invalid or missing." - footer = "" - color = Colours.soft_red - else: - message = "" - footer = "View original for a bigger picture." - color = Colours.soft_orange - - # Sends a "blank" embed if no request is received, unsure how to fix - await send_embed(ctx, message, color, footer=footer, img_url=image_url, f=f) - - @wolfram_command.command(name="page", aliases=("pa", "p")) - @custom_cooldown(*STAFF_ROLES) - async def wolfram_page_command(self, ctx: Context, *, query: str) -> None: - """ - Requests a drawn image of given query. - - Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc. - """ - pages = await get_pod_pages(ctx, self.bot, query) - - if not pages: - return - - embed = Embed() - embed.set_author(name="Wolfram Alpha", - icon_url=WOLF_IMAGE, - url="https://www.wolframalpha.com/") - embed.colour = Colours.soft_orange - - await ImagePaginator.paginate(pages, ctx, embed) - - @wolfram_command.command(name="cut", aliases=("c",)) - @custom_cooldown(*STAFF_ROLES) - async def wolfram_cut_command(self, ctx: Context, *, query: str) -> None: - """ - Requests a drawn image of given query. - - Keywords worth noting are, "like curve", "curve", "graph", "pokemon", etc. - """ - pages = await get_pod_pages(ctx, self.bot, query) - - if not pages: - return - - if len(pages) >= 2: - page = pages[1] - else: - page = pages[0] - - await send_embed(ctx, page[0], colour=Colours.soft_orange, img_url=page[1]) - - @wolfram_command.command(name="short", aliases=("sh", "s")) - @custom_cooldown(*STAFF_ROLES) - async def wolfram_short_command(self, ctx: Context, *, query: str) -> None: - """Requests an answer to a simple question.""" - url_str = parse.urlencode({ - "i": query, - "appid": APPID, - }) - query = QUERY.format(request="result", data=url_str) - - # Give feedback that the bot is working. - async with ctx.channel.typing(): - async with self.bot.http_session.get(query) as response: - status = response.status - response_text = await response.text() - - if status == 501: - message = "Failed to get response" - color = Colours.soft_red - elif status == 400: - message = "No input found" - color = Colours.soft_red - elif response_text == "Error 1: Invalid appid": - message = "Wolfram API key is invalid or missing." - color = Colours.soft_red - else: - message = response_text - color = Colours.soft_orange - - await send_embed(ctx, message, color) - - -def setup(bot: Bot) -> None: - """Load the Wolfram cog.""" - bot.add_cog(Wolfram(bot)) diff --git a/bot/constants.py b/bot/constants.py index f3db80279..17fe34e95 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -514,14 +514,6 @@ class Reddit(metaclass=YAMLGetter): secret: Optional[str] -class Wolfram(metaclass=YAMLGetter): - section = "wolfram" - - user_limit_day: int - guild_limit_day: int - key: Optional[str] - - class AntiSpam(metaclass=YAMLGetter): section = 'anti_spam' diff --git a/config-default.yml b/config-default.yml index b4dc34e85..a0f601728 100644 --- a/config-default.yml +++ b/config-default.yml @@ -393,13 +393,6 @@ reddit: secret: !ENV "REDDIT_SECRET" -wolfram: - # Max requests per day. - user_limit_day: 10 - guild_limit_day: 67 - key: !ENV "WOLFRAM_API_KEY" - - big_brother: log_delay: 15 header_message_limit: 15 -- cgit v1.2.3 From 40ad0def564109884c607c78f95c67518d7a70a5 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Fri, 28 Aug 2020 11:53:08 -0500 Subject: Everyone Ping: Add rules to default config file --- config-default.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config-default.yml b/config-default.yml index 8c0092e76..3a5918983 100644 --- a/config-default.yml +++ b/config-default.yml @@ -385,6 +385,9 @@ anti_spam: interval: 10 max: 3 + everyone_ping: + enabled: true + reddit: subreddits: -- cgit v1.2.3 From df4ef2e520cd672f0bb46b9d5d09a04647ca2ccf Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Fri, 28 Aug 2020 19:06:19 -0500 Subject: Everyone Ping: Added rule Added the filter rule to the bot/rules folder. --- bot/rules/everyone_ping.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 bot/rules/everyone_ping.py diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py new file mode 100644 index 000000000..29a734478 --- /dev/null +++ b/bot/rules/everyone_ping.py @@ -0,0 +1,31 @@ +from typing import Dict, Iterable, List, Optional, Tuple + +from discord import Member, Message + + +async def apply( + last_message: Message, + recent_messages: List[Message], + config: Dict[str, int], +) -> Optional[Tuple[str, Iterable[Member], Iterable[Message]]]: + """Detects if a user has sent an '@everyone' ping.""" + relevant_messages = tuple( + msg for msg in recent_messages if msg.author == last_message.author + ) + + ev_msgs_ct = 0 + if config["enabled"]: + for msg in relevant_messages: + ev_role = msg.guild.default_role + msg_roles = msg.role_mentions + + if ev_role in msg_roles: + ev_msgs_ct += 1 + + if ev_msgs_ct > 0: + return ( + f"pinged the everyone role {ev_msgs_ct} times", + (last_message.author), + relevant_messages, + ) + return None -- cgit v1.2.3 From 99aa7d55a72fdbf4265820e9d6f70d95132faa8f Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Fri, 28 Aug 2020 19:14:29 -0500 Subject: Everyone Ping: Added rule to recognized rules Added mapping to anti-spam cog, then also edited __init__ in the rules folder to expose the apply function. --- bot/cogs/antispam.py | 3 ++- bot/rules/__init__.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index bc31cbd95..d003f962b 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -34,7 +34,8 @@ RULE_FUNCTION_MAPPING = { 'links': rules.apply_links, 'mentions': rules.apply_mentions, 'newlines': rules.apply_newlines, - 'role_mentions': rules.apply_role_mentions + 'role_mentions': rules.apply_role_mentions, + 'everyone_ping': rules.apply_everyone_ping, } diff --git a/bot/rules/__init__.py b/bot/rules/__init__.py index a01ceae73..8a69cadee 100644 --- a/bot/rules/__init__.py +++ b/bot/rules/__init__.py @@ -10,3 +10,4 @@ from .links import apply as apply_links from .mentions import apply as apply_mentions from .newlines import apply as apply_newlines from .role_mentions import apply as apply_role_mentions +from .everyone_ping import apply as apply_everyone_ping -- cgit v1.2.3 From f873e685e34f1af62f2bc49bc3e37265c327b3ea Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Fri, 28 Aug 2020 19:30:34 -0500 Subject: Everyone Ping: Added required values to config The `max` and `interval` values were required, so they were added to the config file and the rule was modified to accept these new values. --- bot/rules/everyone_ping.py | 2 +- config-default.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 29a734478..342727093 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -14,7 +14,7 @@ async def apply( ) ev_msgs_ct = 0 - if config["enabled"]: + if config["max"]: for msg in relevant_messages: ev_role = msg.guild.default_role msg_roles = msg.role_mentions diff --git a/config-default.yml b/config-default.yml index 3a5918983..18d7f4b0e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -386,7 +386,8 @@ anti_spam: max: 3 everyone_ping: - enabled: true + interval: 1 + max: 1 reddit: -- cgit v1.2.3 From c55b7e3749166d06f66193692a7ded5d1317a154 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Fri, 28 Aug 2020 20:21:01 -0500 Subject: Everyone Ping: Fixed rule, edited config Changed the method of checking for an everyone ping. Also changed the config to act as `min pings` instead of `ping enabled/disabled`. --- bot/rules/everyone_ping.py | 16 ++++++---------- config-default.yml | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 342727093..bfc400831 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -14,18 +14,14 @@ async def apply( ) ev_msgs_ct = 0 - if config["max"]: - for msg in relevant_messages: - ev_role = msg.guild.default_role - msg_roles = msg.role_mentions + for msg in relevant_messages: + if '@everyone' in msg.content: + ev_msgs_ct += 1 - if ev_role in msg_roles: - ev_msgs_ct += 1 - - if ev_msgs_ct > 0: + if ev_msgs_ct >= config['max']: return ( - f"pinged the everyone role {ev_msgs_ct} times", - (last_message.author), + f"pinged the everyone role {ev_msgs_ct} times in {config['interval']}s", + (last_message.author,), relevant_messages, ) return None diff --git a/config-default.yml b/config-default.yml index 18d7f4b0e..8546b5310 100644 --- a/config-default.yml +++ b/config-default.yml @@ -386,8 +386,8 @@ anti_spam: max: 3 everyone_ping: - interval: 1 - max: 1 + interval: 10 + max: 0 reddit: -- cgit v1.2.3 From 24002b6b585962bf9218ad643727b30d4ed018dd Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sat, 29 Aug 2020 16:06:19 -0500 Subject: Everyone ping: Send embed on ping, fixed check When a user pings the everyone role, they now get an embed explaining why what they did was wrong. The ping detection was also fixed to not thing that every message was a ping (changed form `>=` to `>`). --- bot/rules/everyone_ping.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index bfc400831..65ee1062c 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -1,6 +1,14 @@ +import logging +import textwrap from typing import Dict, Iterable, List, Optional, Tuple -from discord import Member, Message +from discord import Embed, Member, Message + +from bot.cogs.moderation.utils import send_private_embed +from bot.constants import Colours + +# For embed sender +log = logging.getLogger(__name__) async def apply( @@ -15,10 +23,28 @@ async def apply( ev_msgs_ct = 0 for msg in relevant_messages: - if '@everyone' in msg.content: + if "@everyone" in msg.content: ev_msgs_ct += 1 - if ev_msgs_ct >= config['max']: + if ev_msgs_ct > config["max"]: + # Send the user an embed giving them more info: + member_count = "{:,}".format(last_message.guild.member_count).split( + "," + )[0] + embed_text = textwrap.dedent( + f""" + Hello {last_message.author.display_name}, please don't try to ping {member_count}k people. + **It will not have good results.** + If you want to know what it would be like, imagine pinging Greenland. Please don't ping Greenland. + """ + ) + print(embed_text) + embed = Embed( + title="Everyone Ping Mute Info", + colour=Colours.soft_red, + description=embed_text, + ) + await send_private_embed(last_message.author, embed) return ( f"pinged the everyone role {ev_msgs_ct} times in {config['interval']}s", (last_message.author,), -- cgit v1.2.3 From 218e50ce41dea40ec04614db1888cd44db7843b5 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:09:24 -0500 Subject: Everyone Ping: Removed debug `print`, spelling Removed a debug `print` statement, fixed a spelling mistake. Also added a comment for the DM string. --- bot/rules/everyone_ping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 65ee1062c..b99e75059 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -31,14 +31,14 @@ async def apply( member_count = "{:,}".format(last_message.guild.member_count).split( "," )[0] + # Change the `K` to an `M` once the server reaches over 1 million people. embed_text = textwrap.dedent( f""" - Hello {last_message.author.display_name}, please don't try to ping {member_count}k people. + Hello {last_message.author.display_name}, please don't try to ping {member_count}K people. **It will not have good results.** If you want to know what it would be like, imagine pinging Greenland. Please don't ping Greenland. """ ) - print(embed_text) embed = Embed( title="Everyone Ping Mute Info", colour=Colours.soft_red, -- cgit v1.2.3 From e42db79c2fd7be4b0c82a5ba4e3f1ca4349745a2 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sat, 29 Aug 2020 18:27:07 -0500 Subject: Everyone Ping: Changed embed text and location The you can view the embed text in the `everyone_ping.py` file. The embed also now sends in the server instead of a DM. --- bot/rules/everyone_ping.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index b99e75059..47931caae 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -4,7 +4,6 @@ from typing import Dict, Iterable, List, Optional, Tuple from discord import Embed, Member, Message -from bot.cogs.moderation.utils import send_private_embed from bot.constants import Colours # For embed sender @@ -17,9 +16,7 @@ async def apply( config: Dict[str, int], ) -> Optional[Tuple[str, Iterable[Member], Iterable[Message]]]: """Detects if a user has sent an '@everyone' ping.""" - relevant_messages = tuple( - msg for msg in recent_messages if msg.author == last_message.author - ) + relevant_messages = tuple(msg for msg in recent_messages if msg.author == last_message.author) ev_msgs_ct = 0 for msg in relevant_messages: @@ -28,23 +25,16 @@ async def apply( if ev_msgs_ct > config["max"]: # Send the user an embed giving them more info: - member_count = "{:,}".format(last_message.guild.member_count).split( - "," - )[0] + member_count = "{:,}".format(last_message.guild.member_count).split(",")[0] # Change the `K` to an `M` once the server reaches over 1 million people. embed_text = textwrap.dedent( f""" - Hello {last_message.author.display_name}, please don't try to ping {member_count}K people. + Please don't try to ping {member_count}K people. **It will not have good results.** - If you want to know what it would be like, imagine pinging Greenland. Please don't ping Greenland. """ ) - embed = Embed( - title="Everyone Ping Mute Info", - colour=Colours.soft_red, - description=embed_text, - ) - await send_private_embed(last_message.author, embed) + embed = Embed(description=embed_text, colour=Colours.soft_red) + await last_message.channel.send(f"Hey {last_message.author.mention}!", embed=embed) return ( f"pinged the everyone role {ev_msgs_ct} times in {config['interval']}s", (last_message.author,), -- cgit v1.2.3 From dbb05d95420cdd6ff08231ce7b9c67cc46bf3675 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sat, 29 Aug 2020 18:49:02 -0500 Subject: Everyone Ping: Fixed linting error Switched from string.format to f-string for server member count. --- bot/rules/everyone_ping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 47931caae..8c1b43628 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -25,7 +25,7 @@ async def apply( if ev_msgs_ct > config["max"]: # Send the user an embed giving them more info: - member_count = "{:,}".format(last_message.guild.member_count).split(",")[0] + member_count = f'{last_message.guild.member_count}'.split(",")[0] # Change the `K` to an `M` once the server reaches over 1 million people. embed_text = textwrap.dedent( f""" -- cgit v1.2.3 From 20f0dfd57f3ed711ef46169b9dcf0e8ee57bcfd1 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sun, 30 Aug 2020 07:32:44 -0500 Subject: Everyone ping: Changed message, cleaned file Changed the message to say the raw member count, not just thousands. Also cleaned up some unused variables and imports in the file. --- bot/rules/everyone_ping.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 8c1b43628..037d7254e 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -1,4 +1,3 @@ -import logging import textwrap from typing import Dict, Iterable, List, Optional, Tuple @@ -6,9 +5,6 @@ from discord import Embed, Member, Message from bot.constants import Colours -# For embed sender -log = logging.getLogger(__name__) - async def apply( last_message: Message, @@ -25,11 +21,9 @@ async def apply( if ev_msgs_ct > config["max"]: # Send the user an embed giving them more info: - member_count = f'{last_message.guild.member_count}'.split(",")[0] - # Change the `K` to an `M` once the server reaches over 1 million people. embed_text = textwrap.dedent( f""" - Please don't try to ping {member_count}K people. + Please don't try to ping {last_message.guild.member_count} people. **It will not have good results.** """ ) -- cgit v1.2.3 From e7862878bd4233cc9340c00bfb77079c318a0b22 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sun, 30 Aug 2020 07:37:57 -0500 Subject: Everyone ping: added formatting to member count Seperated the member count by commas every three digits. --- bot/rules/everyone_ping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 037d7254e..44e9aade4 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -23,7 +23,7 @@ async def apply( # Send the user an embed giving them more info: embed_text = textwrap.dedent( f""" - Please don't try to ping {last_message.guild.member_count} people. + Please don't try to ping {last_message.guild.member_count:,} people. **It will not have good results.** """ ) -- cgit v1.2.3 From 702ff7e80d859dbc8189e55d1dcf9e3bd5959c7a Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sun, 30 Aug 2020 08:18:52 -0500 Subject: Everyone Ping: PR Review Changed cryptic variable name. Changed ping response to use `bot.constants.NEGATIVE_REPLIES`. Changed ping repsonse to only ping user once. --- bot/rules/everyone_ping.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 44e9aade4..f3790ba2c 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -1,9 +1,10 @@ +import random import textwrap from typing import Dict, Iterable, List, Optional, Tuple from discord import Embed, Member, Message -from bot.constants import Colours +from bot.constants import Colours, NEGATIVE_REPLIES async def apply( @@ -14,23 +15,27 @@ async def apply( """Detects if a user has sent an '@everyone' ping.""" relevant_messages = tuple(msg for msg in recent_messages if msg.author == last_message.author) - ev_msgs_ct = 0 + everyone_messages_count = 0 for msg in relevant_messages: if "@everyone" in msg.content: - ev_msgs_ct += 1 + everyone_messages_count += 1 - if ev_msgs_ct > config["max"]: + if everyone_messages_count > config["max"]: # Send the user an embed giving them more info: embed_text = textwrap.dedent( f""" + **{random.choice(NEGATIVE_REPLIES)}** Please don't try to ping {last_message.guild.member_count:,} people. - **It will not have good results.** - """ + """ ) + + # Make embed: embed = Embed(description=embed_text, colour=Colours.soft_red) - await last_message.channel.send(f"Hey {last_message.author.mention}!", embed=embed) + + # Send embed: + await last_message.channel.send(embed=embed) return ( - f"pinged the everyone role {ev_msgs_ct} times in {config['interval']}s", + f"pinged the everyone role {everyone_messages_count} times in {config['interval']}s", (last_message.author,), relevant_messages, ) -- cgit v1.2.3 From 94b89a867942d98138f43ec8d2e6bf8f6607c240 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sun, 30 Aug 2020 09:52:17 -0500 Subject: Everyone Ping: NEGATIVE_REPLIES in title The NEGATIVE_REPLIES header is now the title of the embed. --- bot/rules/everyone_ping.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index f3790ba2c..08415b1e0 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -1,5 +1,4 @@ import random -import textwrap from typing import Dict, Iterable, List, Optional, Tuple from discord import Embed, Member, Message @@ -22,15 +21,10 @@ async def apply( if everyone_messages_count > config["max"]: # Send the user an embed giving them more info: - embed_text = textwrap.dedent( - f""" - **{random.choice(NEGATIVE_REPLIES)}** - Please don't try to ping {last_message.guild.member_count:,} people. - """ - ) + embed_text = f"Please don't try to ping {last_message.guild.member_count:,} people." # Make embed: - embed = Embed(description=embed_text, colour=Colours.soft_red) + embed = Embed(title=random.choice(NEGATIVE_REPLIES), description=embed_text, colour=Colours.soft_red) # Send embed: await last_message.channel.send(embed=embed) -- cgit v1.2.3 From a61d0321c4b7a6e137ccb59d8a3af0428838778c Mon Sep 17 00:00:00 2001 From: Dennis Pham Date: Sun, 30 Aug 2020 17:35:10 -0400 Subject: Allow moderators to use defcon --- bot/cogs/defcon.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index de0f4545e..9087ac454 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -10,7 +10,7 @@ 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.constants import Channels, Colours, Emojis, Event, Icons, MODERATION_ROLES, Roles from bot.decorators import with_role log = logging.getLogger(__name__) @@ -119,7 +119,7 @@ class Defcon(Cog): ) @group(name='defcon', aliases=('dc',), invoke_without_command=True) - @with_role(Roles.admins, Roles.owners) + @with_role(*MODERATION_ROLES) async def defcon_group(self, ctx: Context) -> None: """Check the DEFCON status or run a subcommand.""" await ctx.send_help(ctx.command) @@ -163,7 +163,7 @@ class Defcon(Cog): self.bot.stats.gauge("defcon.threshold", days) @defcon_group.command(name='enable', aliases=('on', 'e'), root_aliases=("defon",)) - @with_role(Roles.admins, Roles.owners) + @with_role(*MODERATION_ROLES) async def enable_command(self, ctx: Context) -> None: """ Enable DEFCON mode. Useful in a pinch, but be sure you know what you're doing! @@ -176,7 +176,7 @@ class Defcon(Cog): await self.update_channel_topic() @defcon_group.command(name='disable', aliases=('off', 'd'), root_aliases=("defoff",)) - @with_role(Roles.admins, Roles.owners) + @with_role(*MODERATION_ROLES) async def disable_command(self, ctx: Context) -> None: """Disable DEFCON mode. Useful in a pinch, but be sure you know what you're doing!""" self.enabled = False @@ -184,7 +184,7 @@ class Defcon(Cog): await self.update_channel_topic() @defcon_group.command(name='status', aliases=('s',)) - @with_role(Roles.admins, Roles.owners) + @with_role(*MODERATION_ROLES) async def status_command(self, ctx: Context) -> None: """Check the current status of DEFCON mode.""" embed = Embed( @@ -196,7 +196,7 @@ class Defcon(Cog): await ctx.send(embed=embed) @defcon_group.command(name='days') - @with_role(Roles.admins, Roles.owners) + @with_role(*MODERATION_ROLES) async def days_command(self, ctx: Context, days: int) -> None: """Set how old an account must be to join the server, in days, with DEFCON mode enabled.""" self.days = timedelta(days=days) -- cgit v1.2.3 From 94dcfa584599301f0cfb9e47d4ef9f7f40bdc23c Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Sun, 30 Aug 2020 21:55:39 -0500 Subject: Everyone Ping: PR Review 2 Removed redundant comments. Switched to regex to avoid punishing users for putting `@everyone` in codeblocks. Changed log message since this isn't a anti-spam rule based off of frequency. Added check for `<@&{guild_id}>` ping, also checks for codeblocks. --- bot/rules/everyone_ping.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 08415b1e0..3a8174e44 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -1,9 +1,15 @@ import random +import re from typing import Dict, Iterable, List, Optional, Tuple from discord import Embed, Member, Message -from bot.constants import Colours, NEGATIVE_REPLIES +from bot.constants import Colours, Guild, NEGATIVE_REPLIES + +# Generate regex for checking for pings: +guild_id = Guild.id +EVERYONE_RE_INLINE_CODE = re.compile(rf"(?!`)@everyone(?!`)|(?!`)<@&{guild_id}>(?!`)") +EVERYONE_RE_MULTILINE_CODE = re.compile(rf"(?!```\n.*)@everyone(?!\n.*```)|(?!```\n.*)<@&{guild_id}>(?!\n.*```)") async def apply( @@ -16,20 +22,19 @@ async def apply( everyone_messages_count = 0 for msg in relevant_messages: - if "@everyone" in msg.content: + num_everyone_pings_inline = len(re.findall(EVERYONE_RE_INLINE_CODE, msg.content)) + num_everyone_pings_multiline = len(re.findall(EVERYONE_RE_MULTILINE_CODE, msg.content)) + if num_everyone_pings_inline and num_everyone_pings_multiline: everyone_messages_count += 1 if everyone_messages_count > config["max"]: - # Send the user an embed giving them more info: + # Send the channel an embed giving the user more info: embed_text = f"Please don't try to ping {last_message.guild.member_count:,} people." - - # Make embed: embed = Embed(title=random.choice(NEGATIVE_REPLIES), description=embed_text, colour=Colours.soft_red) - - # Send embed: await last_message.channel.send(embed=embed) + return ( - f"pinged the everyone role {everyone_messages_count} times in {config['interval']}s", + "pinged the everyone role", (last_message.author,), relevant_messages, ) -- cgit v1.2.3 From 9c52a99a03777cdfd728f354cdb305398791eac1 Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Mon, 31 Aug 2020 08:17:57 -0500 Subject: Everyone Ping: Regex Fix Changed the regex to not punish users who have text other than `@everyone` in their codeblocks. Multiline codeblocks can now have `@everyone` in them. --- bot/rules/everyone_ping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 3a8174e44..560a9ec14 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -8,8 +8,8 @@ from bot.constants import Colours, Guild, NEGATIVE_REPLIES # Generate regex for checking for pings: guild_id = Guild.id -EVERYONE_RE_INLINE_CODE = re.compile(rf"(?!`)@everyone(?!`)|(?!`)<@&{guild_id}>(?!`)") -EVERYONE_RE_MULTILINE_CODE = re.compile(rf"(?!```\n.*)@everyone(?!\n.*```)|(?!```\n.*)<@&{guild_id}>(?!\n.*```)") +EVERYONE_RE_INLINE_CODE = re.compile(rf"^(?!`)@everyone(?!`)$|^(?!`)<@&{guild_id}>(?!`)$") +EVERYONE_RE_MULTILINE_CODE = re.compile(rf"^(?!```)@everyone(?!```)$|^(?!```)<@&{guild_id}>(?!```)$") async def apply( -- cgit v1.2.3 From 4906406cedaa476b8eb1665bc0e20616c91d7f6b Mon Sep 17 00:00:00 2001 From: MrAwesomeRocks <42477863+MrAwesomeRocks@users.noreply.github.com> Date: Mon, 31 Aug 2020 19:19:57 -0500 Subject: Everyone Ping: Fixed regex to catch *all* pings --- bot/rules/everyone_ping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/rules/everyone_ping.py b/bot/rules/everyone_ping.py index 560a9ec14..89d9fe570 100644 --- a/bot/rules/everyone_ping.py +++ b/bot/rules/everyone_ping.py @@ -8,8 +8,8 @@ from bot.constants import Colours, Guild, NEGATIVE_REPLIES # Generate regex for checking for pings: guild_id = Guild.id -EVERYONE_RE_INLINE_CODE = re.compile(rf"^(?!`)@everyone(?!`)$|^(?!`)<@&{guild_id}>(?!`)$") -EVERYONE_RE_MULTILINE_CODE = re.compile(rf"^(?!```)@everyone(?!```)$|^(?!```)<@&{guild_id}>(?!```)$") +EVERYONE_RE_INLINE_CODE = re.compile(rf"^(?!`).*@everyone.*(?!`)$|^(?!`).*<@&{guild_id}>.*(?!`)$") +EVERYONE_RE_MULTILINE_CODE = re.compile(rf"^(?!```).*@everyone.*(?!```)$|^(?!```).*<@&{guild_id}>.*(?!```)$") async def apply( -- cgit v1.2.3 From 9bf8e5e394b5f9a8735f235cafe0fd2526be6ab2 Mon Sep 17 00:00:00 2001 From: Xithrius Date: Mon, 31 Aug 2020 19:49:38 -0700 Subject: Removed image pagination utility. --- bot/pagination.py | 164 ------------------------------------------------------ 1 file changed, 164 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index bab98cacf..182b2fa76 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -374,167 +374,3 @@ class LinePaginator(Paginator): log.debug("Ending pagination and clearing reactions.") with suppress(discord.NotFound): await message.clear_reactions() - - -class ImagePaginator(Paginator): - """ - Helper class that paginates images for embeds in messages. - - Close resemblance to LinePaginator, except focuses on images over text. - - Refer to ImagePaginator.paginate for documentation on how to use. - """ - - def __init__(self, prefix: str = "", suffix: str = ""): - super().__init__(prefix, suffix) - self._current_page = [prefix] - self.images = [] - self._pages = [] - self._count = 0 - - def add_line(self, line: str = '', *, empty: bool = False) -> None: - """Adds a line to each page.""" - if line: - self._count = len(line) - else: - self._count = 0 - self._current_page.append(line) - self.close_page() - - def add_image(self, image: str = None) -> None: - """Adds an image to a page.""" - self.images.append(image) - - @classmethod - async def paginate( - cls, - pages: t.List[t.Tuple[str, str]], - ctx: Context, embed: discord.Embed, - prefix: str = "", - suffix: str = "", - timeout: int = 300, - exception_on_empty_embed: bool = False - ) -> t.Optional[discord.Message]: - """ - Use a paginator and set of reactions to provide pagination over a set of title/image pairs. - - The reactions are used to switch page, or to finish with pagination. - - When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may - be used to change page, or to remove pagination from the message. - - Note: Pagination will be removed automatically if no reaction is added for five minutes (300 seconds). - - Example: - >>> embed = discord.Embed() - >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) - >>> await ImagePaginator.paginate(pages, ctx, embed) - """ - def check_event(reaction_: discord.Reaction, member: discord.Member) -> bool: - """Checks each reaction added, if it matches our conditions pass the wait_for.""" - return all(( - # Reaction is on the same message sent - reaction_.message.id == message.id, - # The reaction is part of the navigation menu - str(reaction_.emoji) in PAGINATION_EMOJI, - # The reactor is not a bot - not member.bot - )) - - paginator = cls(prefix=prefix, suffix=suffix) - current_page = 0 - - if not pages: - if exception_on_empty_embed: - log.exception("Pagination asked for empty image list") - raise EmptyPaginatorEmbed("No images to paginate") - - log.debug("No images to add to paginator, adding '(no images to display)' message") - pages.append(("(no images to display)", "")) - - for text, image_url in pages: - paginator.add_line(text) - paginator.add_image(image_url) - - embed.description = paginator.pages[current_page] - image = paginator.images[current_page] - - if image: - embed.set_image(url=image) - - if len(paginator.pages) <= 1: - return await ctx.send(embed=embed) - - embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") - message = await ctx.send(embed=embed) - - for emoji in PAGINATION_EMOJI: - await message.add_reaction(emoji) - - while True: - # Start waiting for reactions - try: - reaction, user = await ctx.bot.wait_for("reaction_add", timeout=timeout, check=check_event) - except asyncio.TimeoutError: - log.debug("Timed out waiting for a reaction") - break # We're done, no reactions for the last 5 minutes - - # Deletes the users reaction - await message.remove_reaction(reaction.emoji, user) - - # 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: - log.debug("Got first page reaction, but we're on the first page - ignoring") - continue - - current_page = 0 - reaction_type = "first" - - # Last reaction press - [:track_next:] - if reaction.emoji == LAST_EMOJI: - if current_page >= len(paginator.pages) - 1: - log.debug("Got last page reaction, but we're on the last page - ignoring") - continue - - current_page = len(paginator.pages) - 1 - reaction_type = "last" - - # Previous reaction press - [:arrow_left: ] - if reaction.emoji == LEFT_EMOJI: - if current_page <= 0: - log.debug("Got previous page reaction, but we're on the first page - ignoring") - continue - - current_page -= 1 - reaction_type = "previous" - - # Next reaction press - [:arrow_right:] - if reaction.emoji == RIGHT_EMOJI: - if current_page >= len(paginator.pages) - 1: - log.debug("Got next page reaction, but we're on the last page - ignoring") - continue - - current_page += 1 - reaction_type = "next" - - # Magic happens here, after page and reaction_type is set - embed.description = paginator.pages[current_page] - - image = paginator.images[current_page] - if image: - embed.set_image(url=image) - - embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") - log.debug(f"Got {reaction_type} page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") - - await message.edit(embed=embed) - - log.debug("Ending pagination and clearing reactions.") - with suppress(discord.NotFound): - await message.clear_reactions() -- cgit v1.2.3 From b7644aa822def549e2591b53c69af3cf44355ac9 Mon Sep 17 00:00:00 2001 From: Xithrius Date: Mon, 31 Aug 2020 19:56:24 -0700 Subject: Removed ImagePaginator testing. --- tests/bot/test_pagination.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/bot/test_pagination.py b/tests/bot/test_pagination.py index ce880d457..630f2516d 100644 --- a/tests/bot/test_pagination.py +++ b/tests/bot/test_pagination.py @@ -44,18 +44,3 @@ class LinePaginatorTests(TestCase): self.paginator.add_line('x' * (self.paginator.scale_to_size + 1)) # Note: item at index 1 is the truncated line, index 0 is prefix self.assertEqual(self.paginator._current_page[1], 'x' * self.paginator.scale_to_size) - - -class ImagePaginatorTests(TestCase): - """Tests functionality of the `ImagePaginator`.""" - - def setUp(self): - """Create a paginator for the test method.""" - self.paginator = pagination.ImagePaginator() - - def test_add_image_appends_image(self): - """`add_image` appends the image to the image list.""" - image = 'lemon' - self.paginator.add_image(image) - - assert self.paginator.images == [image] -- cgit v1.2.3 From 10181ab4dc8711c561caca0a2fbc40ff4c4ecf6c Mon Sep 17 00:00:00 2001 From: Xithrius Date: Mon, 31 Aug 2020 20:40:45 -0700 Subject: Removed loading of the Wolfram cog. --- bot/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/__main__.py b/bot/__main__.py index f698b5662..fe2cf90e6 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -74,7 +74,6 @@ bot.load_extension("bot.cogs.token_remover") bot.load_extension("bot.cogs.utils") bot.load_extension("bot.cogs.watchchannels") bot.load_extension("bot.cogs.webhook_remover") -bot.load_extension("bot.cogs.wolfram") if constants.HelpChannels.enable: bot.load_extension("bot.cogs.help_channels") -- cgit v1.2.3 From 03ab17b9383a57591b2f82a0526188efd902f61b Mon Sep 17 00:00:00 2001 From: wookie184 Date: Tue, 1 Sep 2020 11:27:29 +0100 Subject: Added checks to ignore webhook and bot messages --- bot/cogs/antimalware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bot/cogs/antimalware.py b/bot/cogs/antimalware.py index c76bd2c60..7894ec48f 100644 --- a/bot/cogs/antimalware.py +++ b/bot/cogs/antimalware.py @@ -55,6 +55,10 @@ class AntiMalware(Cog): if not message.attachments or not message.guild: return + # Ignore webhook and bot messages + if message.webhook_id or message.author.bot: + return + # Check if user is staff, if is, return # Since we only care that roles exist to iterate over, check for the attr rather than a User/Member instance if hasattr(message.author, "roles") and any(role.id in STAFF_ROLES for role in message.author.roles): -- cgit v1.2.3 From 1a47f5d80f2f91c3da5a9626e9a6694381d49cd0 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Tue, 1 Sep 2020 12:22:43 +0100 Subject: Fixed old tests and added 2 new ones --- tests/bot/cogs/test_antimalware.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/bot/cogs/test_antimalware.py b/tests/bot/cogs/test_antimalware.py index ecb7abf00..f50c0492d 100644 --- a/tests/bot/cogs/test_antimalware.py +++ b/tests/bot/cogs/test_antimalware.py @@ -23,6 +23,8 @@ class AntiMalwareCogTests(unittest.IsolatedAsyncioTestCase): } self.cog = antimalware.AntiMalware(self.bot) self.message = MockMessage() + self.message.webhook_id = None + self.message.author.bot = None self.whitelist = [".first", ".second", ".third"] async def test_message_with_allowed_attachment(self): @@ -48,6 +50,26 @@ class AntiMalwareCogTests(unittest.IsolatedAsyncioTestCase): self.message.delete.assert_not_called() + async def test_webhook_message_with_illegal_extension(self): + """A webhook message containing an illegal extension should be ignored.""" + attachment = MockAttachment(filename="python.disallowed") + self.message.webhook_id = 697140105563078727 + self.message.attachments = [attachment] + + await self.cog.on_message(self.message) + + self.message.delete.assert_not_called() + + async def test_bot_message_with_illegal_extension(self): + """A bot message containing an illegal extension should be ignored.""" + attachment = MockAttachment(filename="python.disallowed") + self.message.author.bot = 409107086526644234 + self.message.attachments = [attachment] + + await self.cog.on_message(self.message) + + self.message.delete.assert_not_called() + async def test_message_with_illegal_extension_gets_deleted(self): """A message containing an illegal extension should send an embed.""" attachment = MockAttachment(filename="python.disallowed") -- cgit v1.2.3 From 1512dcc994dfacd0995a93320efc001550f15212 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff Date: Fri, 4 Sep 2020 20:05:03 +0200 Subject: Disable burst_shared filter of the AntiSpam cog Our AntiSpam cog suffers from a race condition that causes it to try and infract the same user multiple times. As that happens frequently with the burst_shared filter, it means that our bot joins in and starts spamming the channel with error messages. Another issue is that burst_shared may cause our bot to send a lot of DMs to a lot of different members. This caused our bot to get a DM ban from Discord after a recent `everyone` ping incident. I've decided to disable the `burst_shared` filter by commenting out the relevant lines but leave the code in place otherwise. This means we still have the implementation handy in case we want to re-enable it on short notice. Signed-off-by: Sebastiaan Zeeff --- bot/cogs/antispam.py | 3 ++- config-default.yml | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index d003f962b..b8939113f 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -27,7 +27,8 @@ log = logging.getLogger(__name__) RULE_FUNCTION_MAPPING = { 'attachments': rules.apply_attachments, 'burst': rules.apply_burst, - 'burst_shared': rules.apply_burst_shared, + # burst shared is temporarily disabled due to a bug + # 'burst_shared': rules.apply_burst_shared, 'chars': rules.apply_chars, 'discord_emojis': rules.apply_discord_emojis, 'duplicates': rules.apply_duplicates, diff --git a/config-default.yml b/config-default.yml index 766f7050c..e9324c62f 100644 --- a/config-default.yml +++ b/config-default.yml @@ -352,9 +352,13 @@ anti_spam: interval: 10 max: 7 - burst_shared: - interval: 10 - max: 20 + # Burst shared it (temporarily) disabled to prevent + # the bug that triggers multiple infractions/DMs per + # user. It also tends to catch a lot of innocent users + # now that we're so big. + # burst_shared: + # interval: 10 + # max: 20 chars: interval: 5 -- cgit v1.2.3 From d2e7dd3763d24a2224fe0eefd78852e2a2389850 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Fri, 4 Sep 2020 20:25:26 +0200 Subject: Move bolding markdown outside of text link. On some devices the markdown gets rendered improperly, leaving the asterisks in the message without bolding. --- bot/cogs/help_channels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py index 541c6f336..0f9cac89e 100644 --- a/bot/cogs/help_channels.py +++ b/bot/cogs/help_channels.py @@ -36,7 +36,7 @@ the **Help: Dormant** category. Try to write the best question you can by providing a detailed description and telling us what \ you've tried already. For more information on asking a good question, \ -check out our guide on [**asking good questions**]({ASKING_GUIDE_URL}). +check out our guide on **[asking good questions]({ASKING_GUIDE_URL})**. """ DORMANT_MSG = f""" @@ -47,7 +47,7 @@ channel until it becomes available again. If your question wasn't answered yet, you can claim a new help channel from the \ **Help: Available** category by simply asking your question again. Consider rephrasing the \ question to maximize your chance of getting a good answer. If you're not sure how, have a look \ -through our guide for [**asking a good question**]({ASKING_GUIDE_URL}). +through our guide for **[asking a good question]({ASKING_GUIDE_URL})**. """ CoroutineFunc = t.Callable[..., t.Coroutine] -- cgit v1.2.3 From 53cb77bb2d541d0be61bc3c25e37b54601963b7c Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff Date: Sat, 5 Sep 2020 11:07:58 +0200 Subject: Disable everyone_ping filter in AntiSpam cog As there are a few bugs in the implementation, I've temporarily disabled the at-everyone ping filter in the AntiSpam cog. We can disable it after we've fixed the bugs. Signed-off-by: Sebastiaan Zeeff --- bot/cogs/antispam.py | 4 +++- config-default.yml | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index b8939113f..3ad487d8c 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -36,7 +36,9 @@ RULE_FUNCTION_MAPPING = { 'mentions': rules.apply_mentions, 'newlines': rules.apply_newlines, 'role_mentions': rules.apply_role_mentions, - 'everyone_ping': rules.apply_everyone_ping, + # the everyone filter is temporarily disabled until + # it has been improved. + # 'everyone_ping': rules.apply_everyone_ping, } diff --git a/config-default.yml b/config-default.yml index e9324c62f..6e7cff92d 100644 --- a/config-default.yml +++ b/config-default.yml @@ -389,9 +389,11 @@ anti_spam: interval: 10 max: 3 - everyone_ping: - interval: 10 - max: 0 + # The everyone ping filter is temporarily disabled + # until we've fixed a couple of bugs. + # everyone_ping: + # interval: 10 + # max: 0 reddit: -- cgit v1.2.3