aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Daniel Brown <[email protected]>2020-09-03 12:00:26 -0500
committerGravatar GitHub <[email protected]>2020-09-03 12:00:26 -0500
commitd2f277646039b9f25759a0fcacc347e18c053f61 (patch)
treee85ae9a56d1f6ade3923483bc47e87a4faf2b0cc
parentEveryone Ping: Fixed regex to catch *all* pings (diff)
parentMerge pull request #1134 from python-discord/remove-wolfram (diff)
Merge branch 'master' into feat/filter/1131/everyone-ping-responder
-rw-r--r--bot/__main__.py1
-rw-r--r--bot/cogs/antimalware.py4
-rw-r--r--bot/cogs/bot.py2
-rw-r--r--bot/cogs/defcon.py12
-rw-r--r--bot/cogs/help.py34
-rw-r--r--bot/cogs/watchchannels/watchchannel.py10
-rw-r--r--bot/cogs/wolfram.py280
-rw-r--r--bot/constants.py8
-rw-r--r--bot/pagination.py164
-rw-r--r--config-default.yml9
-rw-r--r--tests/bot/cogs/test_antimalware.py22
-rw-r--r--tests/bot/test_pagination.py15
12 files changed, 48 insertions, 513 deletions
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")
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):
diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py
index 70ef407d7..ddd1cef8d 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
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)
diff --git a/bot/cogs/help.py b/bot/cogs/help.py
index 25ce4ae0f..99d503f5c 100644
--- a/bot/cogs/help.py
+++ b/bot/cogs/help.py
@@ -1,50 +1,28 @@
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
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
log = logging.getLogger(__name__)
COMMANDS_PER_PAGE = 8
-DELETE_EMOJI = Emojis.trashcan
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.
@@ -208,7 +186,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]:
@@ -247,7 +225,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."""
@@ -263,7 +241,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:
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,
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/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()
diff --git a/config-default.yml b/config-default.yml
index 8546b5310..766f7050c 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>"
@@ -397,13 +397,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
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")
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]