aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Leon Sandøy <[email protected]>2020-07-19 19:39:20 +0200
committerGravatar Leon Sandøy <[email protected]>2020-07-19 19:39:20 +0200
commitf99d27074e8088f7ea8abe4957321490875aa249 (patch)
tree9714e3a5c8c4d8348ccc62998af14f378210092f
parentSupport the new AllowDenyList field, 'comment'. (diff)
Validation of guild invites.
We will now validate and convert any standard discord server invite to a guild ID, and automatically add the name of the server as a comment. This will ensure that the list of whitelisted guild IDs will be readable and nice. This also makes minor changes to list output aesthetics.
-rw-r--r--bot/cogs/allow_deny_lists.py29
-rw-r--r--bot/cogs/filtering.py14
-rw-r--r--bot/converters.py40
-rw-r--r--bot/utils/regex.py12
4 files changed, 78 insertions, 17 deletions
diff --git a/bot/cogs/allow_deny_lists.py b/bot/cogs/allow_deny_lists.py
index 8b3c892f5..d82d175cf 100644
--- a/bot/cogs/allow_deny_lists.py
+++ b/bot/cogs/allow_deny_lists.py
@@ -7,7 +7,7 @@ from discord.ext.commands import BadArgument, Cog, Context, group
from bot import constants
from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.converters import ValidAllowDenyListType
+from bot.converters import ValidAllowDenyListType, ValidDiscordServerInvite
from bot.pagination import LinePaginator
from bot.utils.checks import with_role_check
@@ -31,6 +31,24 @@ class AllowDenyLists(Cog):
"""Add an item to an allow or denylist."""
allow_type = "whitelist" if allowed else "blacklist"
+ # If this is a server invite, we gotta validate it.
+ if list_type == "GUILD_INVITE":
+ log.trace(f"{content} is a guild invite, attempting to validate.")
+ validator = ValidDiscordServerInvite()
+ guild_data = await validator.convert(ctx, content)
+
+ # If we make it this far without raising a BadArgument, the invite is
+ # valid. Let's convert the content to an ID.
+ log.trace(f"{content} validated as server invite. Converting to ID.")
+ content = guild_data.get("id")
+
+ # Unless the user has specified another comment, let's
+ # use the server name as the comment so that the list
+ # of guild IDs will be more easily readable when we
+ # display it.
+ if not comment:
+ comment = guild_data.get("name")
+
# Try to add the item to the database
log.trace(f"Trying to add the {content} item to the {list_type} {allow_type}")
payload = {
@@ -100,17 +118,18 @@ class AllowDenyLists(Cog):
# Build a list of lines we want to show in the paginator
lines = []
for item in result:
- line = f"• {item.get('content')}"
+ line = f"• `{item.get('content')}`"
if item.get("comment"):
- line += f" ({item.get('comment')})"
+ line += f" - {item.get('comment')}"
lines.append(line)
lines = sorted(lines)
# Build the embed
+ list_type_plural = list_type.lower().replace("_", " ").title() + "s"
embed = Embed(
- title=f"{allow_type.title()}ed {list_type.lower()} items ({len(result)} total)",
+ title=f"{allow_type.title()}ed {list_type_plural} ({len(result)} total)",
colour=Colour.blue()
)
log.trace(f"Trying to list {len(result)} items from the {list_type.lower()} {allow_type}")
@@ -139,6 +158,7 @@ class AllowDenyLists(Cog):
ctx: Context,
list_type: ValidAllowDenyListType,
content: str,
+ *,
comment: Optional[str] = None,
) -> None:
"""Add an item to the specified allowlist."""
@@ -150,6 +170,7 @@ class AllowDenyLists(Cog):
ctx: Context,
list_type: ValidAllowDenyListType,
content: str,
+ *,
comment: Optional[str] = None,
) -> None:
"""Add an item to the specified denylist."""
diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py
index 4d51bba2e..3ebb47a0f 100644
--- a/bot/cogs/filtering.py
+++ b/bot/cogs/filtering.py
@@ -18,22 +18,12 @@ from bot.constants import (
Filter, Icons, URLs
)
from bot.utils.redis_cache import RedisCache
+from bot.utils.regex import INVITE_RE
from bot.utils.scheduling import Scheduler
log = logging.getLogger(__name__)
# Regular expressions
-INVITE_RE = re.compile(
- r"(?:discord(?:[\.,]|dot)gg|" # Could be discord.gg/
- r"discord(?:[\.,]|dot)com(?:\/|slash)invite|" # or discord.com/invite/
- r"discordapp(?:[\.,]|dot)com(?:\/|slash)invite|" # or discordapp.com/invite/
- r"discord(?:[\.,]|dot)me|" # or discord.me
- r"discord(?:[\.,]|dot)io" # or discord.io.
- r")(?:[\/]|slash)" # / or 'slash'
- r"([a-zA-Z0-9]+)", # the invite code itself
- flags=re.IGNORECASE
-)
-
SPOILER_RE = re.compile(r"(\|\|.+?\|\|)", re.DOTALL)
URL_RE = re.compile(r"(https?://[^\s]+)", flags=re.IGNORECASE)
ZALGO_RE = re.compile(r"[\u0300-\u036F\u0489]")
@@ -478,7 +468,7 @@ class Filtering(Cog):
return True
guild_id = guild.get("id")
- guild_invite_whitelist = self._get_allowlist_items(True, "guild_invite_id")
+ guild_invite_whitelist = self._get_allowlist_items(True, "guild_invite")
if guild_id not in guild_invite_whitelist:
guild_icon_hash = guild["icon"]
diff --git a/bot/converters.py b/bot/converters.py
index edac67be2..7e21c1542 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -9,8 +9,10 @@ import dateutil.tz
import discord
from aiohttp import ClientConnectorError, ContentTypeError
from dateutil.relativedelta import relativedelta
-from discord.ext.commands import BadArgument, Context, Converter, UserConverter
+from discord.ext.commands import BadArgument, Context, Converter, IDConverter, UserConverter
+from bot.constants import URLs
+from bot.utils.regex import INVITE_RE
log = logging.getLogger(__name__)
@@ -34,6 +36,42 @@ def allowed_strings(*values, preserve_case: bool = False) -> t.Callable[[str], s
return converter
+class ValidDiscordServerInvite(Converter):
+ """
+ A converter that validates whether a given string is a valid Discord server invite.
+
+ Raises 'BadArgument' if:
+ - The string is not a valid Discord server invite.
+ - The string is valid, but is an invite for a group DM.
+ - The string is valid, but is expired.
+
+ Returns a (partial) guild object if:
+ - The string is a valid vanity
+ - The string is a full invite URI
+ - The string contains the invite code (the stuff after discord.gg/)
+
+ See the Discord API docs for documentation on the guild object:
+ https://discord.com/developers/docs/resources/guild#guild-object
+ """
+
+ async def convert(self, ctx: Context, server_invite: str) -> dict:
+ """Check whether the string is a valid Discord server invite."""
+ invite_code = INVITE_RE.match(server_invite)
+ if invite_code:
+ response = await ctx.bot.http_session.get(
+ f"{URLs.discord_invite_api}/{invite_code[1]}"
+ )
+ if response.status != 404:
+ invite_data = await response.json()
+ return invite_data.get("guild")
+
+ id_converter = IDConverter()
+ if id_converter._get_id_match(server_invite):
+ raise BadArgument("Guild IDs are not supported, only invites.")
+
+ raise BadArgument("This does not appear to be a valid Discord server invite.")
+
+
class ValidAllowDenyListType(Converter):
"""
A converter that checks whether the given string is a valid AllowDenyList type.
diff --git a/bot/utils/regex.py b/bot/utils/regex.py
new file mode 100644
index 000000000..d194f93cb
--- /dev/null
+++ b/bot/utils/regex.py
@@ -0,0 +1,12 @@
+import re
+
+INVITE_RE = re.compile(
+ r"(?:discord(?:[\.,]|dot)gg|" # Could be discord.gg/
+ r"discord(?:[\.,]|dot)com(?:\/|slash)invite|" # or discord.com/invite/
+ r"discordapp(?:[\.,]|dot)com(?:\/|slash)invite|" # or discordapp.com/invite/
+ r"discord(?:[\.,]|dot)me|" # or discord.me
+ r"discord(?:[\.,]|dot)io" # or discord.io.
+ r")(?:[\/]|slash)" # / or 'slash'
+ r"([a-zA-Z0-9]+)", # the invite code itself
+ flags=re.IGNORECASE
+)