aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/__init__.py14
-rw-r--r--bot/__main__.py3
-rw-r--r--bot/cogs/antispam.py3
-rw-r--r--bot/cogs/filtering.py40
-rw-r--r--bot/cogs/modlog.py11
-rw-r--r--bot/cogs/snekbox.py43
-rw-r--r--bot/cogs/tags.py23
-rw-r--r--bot/constants.py12
-rw-r--r--config-default.yml18
9 files changed, 110 insertions, 57 deletions
diff --git a/bot/__init__.py b/bot/__init__.py
index 5a446d71c..54550842e 100644
--- a/bot/__init__.py
+++ b/bot/__init__.py
@@ -88,12 +88,8 @@ for key, value in logging.Logger.manager.loggerDict.items():
value.addHandler(handler)
-# Silence aio_pika.pika.{callback,channel}, discord, PIL, and and websockets
-logging.getLogger("aio_pika.pika.callback").setLevel(logging.ERROR)
-logging.getLogger("aio_pika.pika.channel").setLevel(logging.ERROR)
-logging.getLogger("discord.client").setLevel(logging.ERROR)
-logging.getLogger("discord.gateway").setLevel(logging.ERROR)
-logging.getLogger("discord.state").setLevel(logging.ERROR)
-logging.getLogger("discord.http").setLevel(logging.ERROR)
-logging.getLogger("PIL.PngImagePlugin").setLevel(logging.ERROR)
-logging.getLogger("websockets.protocol").setLevel(logging.ERROR)
+# Silence irrelevant loggers
+logging.getLogger("aio_pika").setLevel(logging.ERROR)
+logging.getLogger("discord").setLevel(logging.ERROR)
+logging.getLogger("PIL").setLevel(logging.ERROR)
+logging.getLogger("websockets").setLevel(logging.ERROR)
diff --git a/bot/__main__.py b/bot/__main__.py
index 0055e19ba..0018cba9c 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -53,15 +53,14 @@ bot.load_extension("bot.cogs.bot")
bot.load_extension("bot.cogs.clean")
bot.load_extension("bot.cogs.cogs")
-
# Only load this in production
if not DEBUG_MODE:
+ bot.load_extension("bot.cogs.doc")
bot.load_extension("bot.cogs.verification")
# Feature cogs
bot.load_extension("bot.cogs.deployment")
bot.load_extension("bot.cogs.defcon")
-bot.load_extension("bot.cogs.doc")
bot.load_extension("bot.cogs.eval")
bot.load_extension("bot.cogs.fun")
bot.load_extension("bot.cogs.hiphopify")
diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py
index 7a33ba9e8..d5b72718c 100644
--- a/bot/cogs/antispam.py
+++ b/bot/cogs/antispam.py
@@ -56,7 +56,8 @@ class AntiSpam:
async def on_message(self, message: Message):
if (
- message.guild.id != GuildConfig.id
+ not message.guild
+ or message.guild.id != GuildConfig.id
or message.author.bot
or (message.channel.id in WHITELISTED_CHANNELS and not DEBUG_MODE)
or (message.author.top_role.id in WHITELISTED_ROLES and not DEBUG_MODE)
diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py
index 586c99174..36be78a7e 100644
--- a/bot/cogs/filtering.py
+++ b/bot/cogs/filtering.py
@@ -7,7 +7,7 @@ from discord.ext.commands import Bot
from bot.cogs.modlog import ModLog
from bot.constants import (
Channels, Colours, DEBUG_MODE,
- Filter, Icons
+ Filter, Icons, URLs
)
log = logging.getLogger(__name__)
@@ -24,6 +24,9 @@ INVITE_RE = (
URL_RE = "(https?://[^\s]+)"
ZALGO_RE = r"[\u0300-\u036F\u0489]"
+RETARDED_RE = r"(re+)tar+(d+|t+)(ed)?"
+SELF_DEPRECATION_RE = fr"((i'?m)|(i am)|(it'?s)|(it is)) (.+? )?{RETARDED_RE}"
+RETARDED_QUESTIONS_RE = fr"{RETARDED_RE} questions?"
class Filtering:
@@ -111,8 +114,8 @@ class Filtering:
message = (
f"The {filter_name} {_filter['type']} was triggered "
f"by **{msg.author.name}#{msg.author.discriminator}** "
- f"(`{msg.author.id}`) in <#{msg.channel.id}> with the "
- f"following message:\n\n"
+ f"(`{msg.author.id}`) in <#{msg.channel.id}> with [the "
+ f"following message]({msg.jump_url}):\n\n"
f"{msg.content}"
)
@@ -148,6 +151,18 @@ class Filtering:
for expression in Filter.word_watchlist:
if re.search(fr"\b{expression}\b", text, re.IGNORECASE):
+
+ # Special handling for `retarded`
+ if expression == RETARDED_RE:
+
+ # stuff like "I'm just retarded"
+ if re.search(SELF_DEPRECATION_RE, text, re.IGNORECASE):
+ return False
+
+ # stuff like "sorry for all the retarded questions"
+ elif re.search(RETARDED_QUESTIONS_RE, text, re.IGNORECASE):
+ return False
+
return True
return False
@@ -165,7 +180,10 @@ class Filtering:
for expression in Filter.token_watchlist:
if re.search(fr"{expression}", text, re.IGNORECASE):
- return True
+
+ # Make sure it's not a URL
+ if not re.search(URL_RE, text, re.IGNORECASE):
+ return True
return False
@@ -197,8 +215,7 @@ class Filtering:
return bool(re.search(ZALGO_RE, text))
- @staticmethod
- async def _has_invites(text: str) -> bool:
+ async def _has_invites(self, text: str) -> bool:
"""
Returns True if the text contains an invite which
is not on the guild_invite_whitelist in config.yml.
@@ -207,7 +224,7 @@ class Filtering:
"""
# Remove spaces to prevent cases like
- # d i s c o r d . c o m / i n v i t e / p y t h o n
+ # d i s c o r d . c o m / i n v i t e / s e x y t e e n s
text = text.replace(" ", "")
# Remove backslashes to prevent escape character aroundfuckery like
@@ -217,12 +234,13 @@ class Filtering:
invites = re.findall(INVITE_RE, text, re.IGNORECASE)
for invite in invites:
- filter_invite = (
- invite not in Filter.guild_invite_whitelist
- and invite.lower() not in Filter.vanity_url_whitelist
+ response = await self.bot.http_session.get(
+ f"{URLs.discord_invite_api}/{invite}"
)
+ response = await response.json()
+ guild_id = int(response.get("guild", {}).get("id"))
- if filter_invite:
+ if guild_id not in Filter.guild_invite_whitelist:
return True
return False
diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py
index 2f72d92fc..9c81661ba 100644
--- a/bot/cogs/modlog.py
+++ b/bot/cogs/modlog.py
@@ -360,7 +360,7 @@ class ModLog:
now = datetime.datetime.utcnow()
difference = abs(relativedelta(now, member.created_at))
- message += "\n\n**Account age:** " + humanize_delta(member.created_at)
+ message += "\n\n**Account age:** " + humanize_delta(difference)
if difference.days < 1 and difference.months < 1 and difference.years < 1: # New user account!
message = f"{Emojis.new} {message}"
@@ -440,7 +440,7 @@ class ModLog:
if key in done or key in MEMBER_CHANGES_SUPPRESSED:
continue
- if key == "roles":
+ if key == "_roles":
new_roles = after.roles
old_roles = before.roles
@@ -453,10 +453,11 @@ class ModLog:
changes.append(f"**Role added:** {role.name} (`{role.id}`)")
else:
- new = value["new_value"]
- old = value["old_value"]
+ new = value.get("new_value")
+ old = value.get("old_value")
- changes.append(f"**{key.title()}:** `{old}` **->** `{new}`")
+ if new and old:
+ changes.append(f"**{key.title()}:** `{old}` **->** `{new}`")
done.append(key)
diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py
index 6f618a2c7..fb9164194 100644
--- a/bot/cogs/snekbox.py
+++ b/bot/cogs/snekbox.py
@@ -32,6 +32,23 @@ except Exception as e:
"""
ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}")
+FORMATTED_CODE_REGEX = re.compile(
+ r"^\s*" # any leading whitespace from the beginning of the string
+ r"(?P<delim>(?P<block>```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block
+ r"(?(block)(?:(?P<lang>[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline)
+ r"(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code
+ r"(?P<code>.*?)" # extract all code inside the markup
+ r"\s*" # any more whitespace before the end of the code markup
+ r"(?P=delim)" # match the exact same delimiter from the start again
+ r"\s*$", # any trailing whitespace until the end of the string
+ re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive
+)
+RAW_CODE_REGEX = re.compile(
+ r"^(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code
+ r"(?P<code>.*?)" # extract all the rest as code
+ r"\s*$", # any trailing whitespace until the end of the string
+ re.DOTALL # "." also matches newlines
+)
BYPASS_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers)
WHITELISTED_CHANNELS = (Channels.bot,)
WHITELISTED_CHANNELS_STRING = ', '.join(f"<#{channel_id}>" for channel_id in WHITELISTED_CHANNELS)
@@ -54,8 +71,6 @@ class Snekbox:
Safe evaluation using Snekbox
"""
- jobs = None # type: dict
-
def __init__(self, bot: Bot):
self.bot = bot
self.jobs = {}
@@ -85,18 +100,20 @@ class Snekbox:
log.info(f"Received code from {ctx.author.name}#{ctx.author.discriminator} for evaluation:\n{code}")
self.jobs[ctx.author.id] = datetime.datetime.now()
- while code.startswith("\n"):
- code = code[1:]
-
- if code.startswith("```") and code.endswith("```"):
- code = code[3:-3]
-
- if code.startswith("python"):
- code = code[6:]
- elif code.startswith("py"):
- code = code[2:]
+ # Strip whitespace and inline or block code markdown and extract the code and some formatting info
+ match = FORMATTED_CODE_REGEX.fullmatch(code)
+ if match:
+ code, block, lang, delim = match.group("code", "block", "lang", "delim")
+ code = textwrap.dedent(code)
+ if block:
+ info = (f"'{lang}' highlighted" if lang else "plain") + " code block"
+ else:
+ info = f"{delim}-enclosed inline code"
+ log.trace(f"Extracted {info} for evaluation:\n{code}")
+ else:
+ code = textwrap.dedent(RAW_CODE_REGEX.fullmatch(code).group("code"))
+ log.trace(f"Eval message contains not or badly formatted code, stripping whitespace only:\n{code}")
- code = textwrap.dedent(code.strip())
code = textwrap.indent(code, " ")
code = CODE_TEMPLATE.replace("{CODE}", code)
diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py
index baed44a8c..85244b835 100644
--- a/bot/cogs/tags.py
+++ b/bot/cogs/tags.py
@@ -1,6 +1,7 @@
import logging
import random
import time
+from typing import Optional
from discord import Colour, Embed
from discord.ext.commands import (
@@ -11,6 +12,7 @@ from discord.ext.commands import (
from bot.constants import (
Channels, Cooldowns, ERROR_REPLIES, Keys, Roles, URLs
)
+from bot.converters import ValidURL
from bot.decorators import with_role
from bot.pagination import LinePaginator
@@ -108,12 +110,13 @@ class Tags:
return tag_data
- async def post_tag_data(self, tag_name: str, tag_content: str) -> dict:
+ async def post_tag_data(self, tag_name: str, tag_content: str, image_url: Optional[str]) -> dict:
"""
Send some tag_data to our API to have it saved in the database.
:param tag_name: The name of the tag to create or edit.
:param tag_content: The content of the tag.
+ :param image_url: The image URL of the tag, can be `None`.
:return: json response from the API in the following format:
{
'success': bool
@@ -122,7 +125,8 @@ class Tags:
params = {
'tag_name': tag_name,
- 'tag_content': tag_content
+ 'tag_content': tag_content,
+ 'image_url': image_url
}
response = await self.bot.http_session.post(URLs.site_tags_api, headers=self.headers, json=params)
@@ -226,13 +230,20 @@ class Tags:
@tags_group.command(name='set', aliases=('add', 'edit', 's'))
@with_role(Roles.admin, Roles.owner, Roles.moderator)
- async def set_command(self, ctx: Context, tag_name: TagNameConverter, *, tag_content: TagContentConverter):
+ async def set_command(
+ self,
+ ctx: Context,
+ tag_name: TagNameConverter,
+ tag_content: TagContentConverter,
+ image_url: ValidURL = None
+ ):
"""
Create a new tag or edit an existing one.
:param ctx: discord message context
:param tag_name: The name of the tag to create or edit.
:param tag_content: The content of the tag.
+ :param image_url: An optional image for the tag.
"""
tag_name = tag_name.lower().strip()
@@ -240,12 +251,13 @@ class Tags:
embed = Embed()
embed.colour = Colour.red()
- tag_data = await self.post_tag_data(tag_name, tag_content)
+ tag_data = await self.post_tag_data(tag_name, tag_content, image_url)
if tag_data.get("success"):
log.debug(f"{ctx.author} successfully added the following tag to our database: \n"
f"tag_name: {tag_name}\n"
- f"tag_content: '{tag_content}'")
+ f"tag_content: '{tag_content}'\n"
+ f"image_url: '{image_url}'")
embed.colour = Colour.blurple()
embed.title = "Tag successfully added"
embed.description = f"**{tag_name}** added to tag database."
@@ -253,6 +265,7 @@ class Tags:
log.error("There was an unexpected database error when trying to add the following tag: \n"
f"tag_name: {tag_name}\n"
f"tag_content: '{tag_content}'\n"
+ f"image_url: '{image_url}'\n"
f"response: {tag_data}")
embed.title = "Database error"
embed.description = ("There was a problem adding the data to the tags database. "
diff --git a/bot/constants.py b/bot/constants.py
index 3ade4ac7b..68fbc2bc4 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -202,8 +202,7 @@ class Filter(metaclass=YAMLGetter):
watch_tokens: bool
ping_everyone: bool
- guild_invite_whitelist: List[str]
- vanity_url_whitelist: List[str]
+ guild_invite_whitelist: List[int]
domain_blacklist: List[str]
word_watchlist: List[str]
token_watchlist: List[str]
@@ -375,10 +374,18 @@ class RabbitMQ(metaclass=YAMLGetter):
class URLs(metaclass=YAMLGetter):
section = "urls"
+ # Discord API endpoints
+ discord_api: str
+ discord_invite_api: str
+
+ # Misc endpoints
bot_avatar: str
deploy: str
gitlab_bot_repo: str
omdb: str
+ status: str
+
+ # Site endpoints
site: str
site_api: str
site_facts_api: str
@@ -401,7 +408,6 @@ class URLs(metaclass=YAMLGetter):
site_infractions_by_id: str
site_infractions_user_type_current: str
site_infractions_user_type: str
- status: str
paste_service: str
diff --git a/config-default.yml b/config-default.yml
index b621c5b90..ce7639186 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -134,11 +134,10 @@ filter:
ping_everyone: true # Ping @everyone when we send a mod-alert?
guild_invite_whitelist:
- - kWJYurV # Functional Programming
- - XBGetGp # STEM
-
- vanity_url_whitelist:
- - python # Python Discord
+ - 280033776820813825 # Functional Programming
+ - 267624335836053506 # Python Discord
+ - 440186186024222721 # Python Discord: ModLog Emojis
+ - 273944235143593984 # STEM
domain_blacklist:
- pornhub.com
@@ -147,7 +146,6 @@ filter:
word_watchlist:
- goo+ks*
- ky+s+
- - gh?[ae]+y+s*
- ki+ke+s*
- beaner+s?
- coo+ns*
@@ -209,6 +207,7 @@ urls:
# PyDis site vars
site: &DOMAIN "pythondiscord.com"
site_api: &API !JOIN ["api.", *DOMAIN]
+ site_paste: &PASTE !JOIN ["paste.", *DOMAIN]
site_schema: &SCHEMA "https://"
site_bigbrother_api: !JOIN [*SCHEMA, *API, "/bot/bigbrother"]
@@ -231,17 +230,20 @@ urls:
site_tags_api: !JOIN [*SCHEMA, *API, "/bot/tags"]
site_user_api: !JOIN [*SCHEMA, *API, "/bot/users"]
site_user_complete_api: !JOIN [*SCHEMA, *API, "/bot/users/complete"]
+ paste_service: !JOIN [*SCHEMA, *PASTE, "/{key}"]
# Env vars
deploy: !ENV "DEPLOY_URL"
status: !ENV "STATUS_URL"
+ # Discord API URLs
+ discord_api: &DISCORD_API "https://discordapp.com/api/v7/"
+ discord_invite_api: !JOIN [*DISCORD_API, "invites"]
+
# Misc URLs
bot_avatar: "https://raw.githubusercontent.com/discord-python/branding/master/logos/logo_circle/logo_circle.png"
gitlab_bot_repo: "https://gitlab.com/python-discord/projects/bot"
omdb: "http://omdbapi.com"
- paste_service: "https://paste.pydis.com/{key}"
-
anti_spam:
# Clean messages that violate a rule.