diff options
author | 2019-10-20 21:11:08 +0200 | |
---|---|---|
committer | 2019-10-20 21:11:08 +0200 | |
commit | 93789fe66e82760dc90d28d29301c312bdc52373 (patch) | |
tree | c4376bd37862d868fe100cababc731911d48fabd | |
parent | Merge branch 'master' into unittest-migration (diff) | |
parent | Merge pull request #528 from bendiller/antimalware-cog (diff) |
Merge branch 'master' into unittest-migration
-rw-r--r-- | bot/__main__.py | 1 | ||||
-rw-r--r-- | bot/cogs/alias.py | 6 | ||||
-rw-r--r-- | bot/cogs/antimalware.py | 56 | ||||
-rw-r--r-- | bot/cogs/antispam.py | 2 | ||||
-rw-r--r-- | bot/cogs/defcon.py | 5 | ||||
-rw-r--r-- | bot/cogs/doc.py | 3 | ||||
-rw-r--r-- | bot/cogs/filtering.py | 2 | ||||
-rw-r--r-- | bot/cogs/moderation/infractions.py | 12 | ||||
-rw-r--r-- | bot/cogs/moderation/modlog.py | 30 | ||||
-rw-r--r-- | bot/cogs/moderation/superstarify.py | 4 | ||||
-rw-r--r-- | bot/cogs/off_topic_names.py | 48 | ||||
-rw-r--r-- | bot/cogs/site.py | 6 | ||||
-rw-r--r-- | bot/cogs/snekbox.py | 12 | ||||
-rw-r--r-- | bot/constants.py | 7 | ||||
-rw-r--r-- | config-default.yml | 22 |
15 files changed, 163 insertions, 53 deletions
diff --git a/bot/__main__.py b/bot/__main__.py index 19a7e5ec6..f352cd60e 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -39,6 +39,7 @@ bot.load_extension("bot.cogs.logging") bot.load_extension("bot.cogs.security") # Commands, etc +bot.load_extension("bot.cogs.antimalware") bot.load_extension("bot.cogs.antispam") bot.load_extension("bot.cogs.bot") bot.load_extension("bot.cogs.clean") diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 6648805e9..5190c559b 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -79,10 +79,10 @@ class Alias (Cog): """Alias for invoking <prefix>site faq.""" await self.invoke(ctx, "site faq") - @command(name="rules", hidden=True) - async def site_rules_alias(self, ctx: Context) -> None: + @command(name="rules", aliases=("rule",), hidden=True) + async def site_rules_alias(self, ctx: Context, *rules: int) -> None: """Alias for invoking <prefix>site rules.""" - await self.invoke(ctx, "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: diff --git a/bot/cogs/antimalware.py b/bot/cogs/antimalware.py new file mode 100644 index 000000000..ababd6f18 --- /dev/null +++ b/bot/cogs/antimalware.py @@ -0,0 +1,56 @@ +import logging + +from discord import Message, NotFound +from discord.ext.commands import Bot, Cog + +from bot.constants import AntiMalware as AntiMalwareConfig, Channels + +log = logging.getLogger(__name__) + + +class AntiMalware(Cog): + """Delete messages which contain attachments with non-whitelisted file extensions.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @Cog.listener() + async def on_message(self, message: Message) -> None: + """Identify messages with prohibited attachments.""" + rejected_attachments = False + detected_pyfile = False + for attachment in message.attachments: + if attachment.filename.lower().endswith('.py'): + detected_pyfile = True + break # Other detections irrelevant because we prioritize the .py message. + if not attachment.filename.lower().endswith(tuple(AntiMalwareConfig.whitelist)): + rejected_attachments = True + + if detected_pyfile or rejected_attachments: + # Send a message to the user indicating the problem (with special treatment for .py) + author = message.author + if detected_pyfile: + msg = ( + f"{author.mention}, it looks like you tried to attach a Python file - please " + f"use a code-pasting service such as https://paste.pythondiscord.com/ instead." + ) + else: + meta_channel = self.bot.get_channel(Channels.meta) + msg = ( + f"{author.mention}, it looks like you tried to attach a file type we don't " + f"allow. Feel free to ask in {meta_channel.mention} if you think this is a mistake." + ) + + await message.channel.send(msg) + + # Delete the offending message: + try: + await message.delete() + except NotFound: + log.info(f"Tried to delete message `{message.id}`, but message could not be found.") + + +def setup(bot: Bot) -> None: + """Antimalware cog load.""" + bot.add_cog(AntiMalware(bot)) + log.info("Cog loaded: AntiMalware") diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 1b394048a..1340eb608 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -59,7 +59,7 @@ class DeletionContext: async def upload_messages(self, actor_id: int, modlog: ModLog) -> None: """Method that takes care of uploading the queue and posting modlog alert.""" - triggered_by_users = ", ".join(f"{m.display_name}#{m.discriminator} (`{m.id}`)" for m in self.members.values()) + triggered_by_users = ", ".join(f"{m} (`{m.id}`)" for m in self.members.values()) mod_alert_message = ( f"**Triggered by:** {triggered_by_users}\n" diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index 70e101baa..38a0915e5 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -90,8 +90,7 @@ class Defcon(Cog): await member.kick(reason="DEFCON active, user is too new") message = ( - f"{member.name}#{member.discriminator} (`{member.id}`) " - f"was denied entry because their account is too new." + f"{member} (`{member.id}`) was denied entry because their account is too new." ) if not message_sent: @@ -254,7 +253,7 @@ class Defcon(Cog): `change` string may be one of the following: ('enabled', 'disabled', 'updated') """ - log_msg = f"**Staffer:** {actor.name}#{actor.discriminator} (`{actor.id}`)\n" + log_msg = f"**Staffer:** {actor} (`{actor.id}`)\n" if change.lower() == "enabled": icon = Icons.defcon_enabled diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index a13464bff..65cabe46f 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -336,8 +336,7 @@ class Doc(commands.Cog): await self.bot.api_client.post('bot/documentation-links', json=body) log.info( - f"User @{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.id}) " - "added a new documentation package:\n" + f"User @{ctx.author} ({ctx.author.id}) added a new documentation package:\n" f"Package name: {package_name}\n" f"Base url: {base_url}\n" f"Inventory URL: {inventory_url}" diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 265ae5160..1d1d74e74 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -186,7 +186,7 @@ class Filtering(Cog): message = ( f"The {filter_name} {_filter['type']} was triggered " - f"by **{msg.author.name}#{msg.author.discriminator}** " + f"by **{msg.author}** " f"(`{msg.author.id}`) {channel_str} with [the " f"following message]({msg.jump_url}):\n\n" f"{msg.content}" diff --git a/bot/cogs/moderation/infractions.py b/bot/cogs/moderation/infractions.py index 592ead60f..f2ae7b95d 100644 --- a/bot/cogs/moderation/infractions.py +++ b/bot/cogs/moderation/infractions.py @@ -2,6 +2,7 @@ import logging import textwrap import typing as t from datetime import datetime +from gettext import ngettext import dateutil.parser import discord @@ -436,7 +437,13 @@ class Infractions(Scheduler, commands.Cog): # Default values for the confirmation message and mod log. confirm_msg = f":ok_hand: applied" - expiry_msg = f" until {expiry}" if expiry else " permanently" + + # Specifying an expiry for a note or warning makes no sense. + if infr_type in ("note", "warning"): + expiry_msg = "" + else: + expiry_msg = f" until {expiry}" if expiry else " permanently" + dm_result = "" dm_log_text = "" expiry_log_text = f"Expires: {expiry}" if expiry else "" @@ -463,7 +470,8 @@ class Infractions(Scheduler, commands.Cog): "bot/infractions", params={"user__id": str(user.id)} ) - end_msg = f" ({len(infractions)} infractions total)" + total = len(infractions) + end_msg = f" ({total} infraction{ngettext('', 's', total)} total)" # Execute the necessary actions to apply the infraction on Discord. if action_coro: diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 118503517..88f2b6c67 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -363,7 +363,7 @@ class ModLog(Cog, name="ModLog"): await self.send_log_message( Icons.user_ban, Colours.soft_red, - "User banned", f"{member.name}#{member.discriminator} (`{member.id}`)", + "User banned", f"{member} (`{member.id}`)", thumbnail=member.avatar_url_as(static_format="png"), channel_id=Channels.userlog ) @@ -374,7 +374,7 @@ class ModLog(Cog, name="ModLog"): if member.guild.id != GuildConstant.id: return - message = f"{member.name}#{member.discriminator} (`{member.id}`)" + message = f"{member} (`{member.id}`)" now = datetime.utcnow() difference = abs(relativedelta(now, member.created_at)) @@ -402,7 +402,7 @@ class ModLog(Cog, name="ModLog"): await self.send_log_message( Icons.sign_out, Colours.soft_red, - "User left", f"{member.name}#{member.discriminator} (`{member.id}`)", + "User left", f"{member} (`{member.id}`)", thumbnail=member.avatar_url_as(static_format="png"), channel_id=Channels.userlog ) @@ -419,7 +419,7 @@ class ModLog(Cog, name="ModLog"): await self.send_log_message( Icons.user_unban, Colour.blurple(), - "User unbanned", f"{member.name}#{member.discriminator} (`{member.id}`)", + "User unbanned", f"{member} (`{member.id}`)", thumbnail=member.avatar_url_as(static_format="png"), channel_id=Channels.modlog ) @@ -511,7 +511,7 @@ class ModLog(Cog, name="ModLog"): for item in sorted(changes): message += f"{Emojis.bullet} {item}\n" - message = f"**{after.name}#{after.discriminator}** (`{after.id}`)\n{message}" + message = f"**{after}** (`{after.id}`)\n{message}" await self.send_log_message( Icons.user_update, Colour.blurple(), @@ -540,14 +540,14 @@ class ModLog(Cog, name="ModLog"): if channel.category: response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" ) else: response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" @@ -638,7 +638,7 @@ class ModLog(Cog, name="ModLog"): if channel.category: before_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{before.id}`\n" "\n" @@ -646,7 +646,7 @@ class ModLog(Cog, name="ModLog"): ) after_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{before.id}`\n" "\n" @@ -654,7 +654,7 @@ class ModLog(Cog, name="ModLog"): ) else: before_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{before.id}`\n" "\n" @@ -662,7 +662,7 @@ class ModLog(Cog, name="ModLog"): ) after_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{before.id}`\n" "\n" @@ -721,7 +721,7 @@ class ModLog(Cog, name="ModLog"): if channel.category: before_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" @@ -729,7 +729,7 @@ class ModLog(Cog, name="ModLog"): ) after_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" @@ -737,7 +737,7 @@ class ModLog(Cog, name="ModLog"): ) else: before_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" @@ -745,7 +745,7 @@ class ModLog(Cog, name="ModLog"): ) after_response = ( - f"**Author:** {author.name}#{author.discriminator} (`{author.id}`)\n" + f"**Author:** {author} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n" diff --git a/bot/cogs/moderation/superstarify.py b/bot/cogs/moderation/superstarify.py index ccc6395d9..82f8621fc 100644 --- a/bot/cogs/moderation/superstarify.py +++ b/bot/cogs/moderation/superstarify.py @@ -129,7 +129,7 @@ class Superstarify(Cog): # Log to the mod_log channel log.trace("Logging to the #mod-log channel. This could fail because of channel permissions.") mod_log_message = ( - f"**{member.name}#{member.discriminator}** (`{member.id}`)\n\n" + f"**{member}** (`{member.id}`)\n\n" f"Superstarified member potentially tried to escape the prison.\n" f"Restored enforced nickname: `{forced_nick}`\n" f"Superstardom ends: **{end_timestamp_human}**" @@ -183,7 +183,7 @@ class Superstarify(Cog): # Log to the mod_log channel log.trace("Logging to the #mod-log channel. This could fail because of channel permissions.") mod_log_message = ( - f"**{member.name}#{member.discriminator}** (`{member.id}`)\n\n" + f"**{member}** (`{member.id}`)\n\n" f"Superstarified by **{ctx.author.name}**\n" f"Old nickname: `{member.display_name}`\n" f"New nickname: `{forced_nick}`\n" diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py index 2977e4ebb..1f9fb0b4f 100644 --- a/bot/cogs/off_topic_names.py +++ b/bot/cogs/off_topic_names.py @@ -98,15 +98,42 @@ class OffTopicNames(Cog): @otname_group.command(name='add', aliases=('a',)) @with_role(*MODERATION_ROLES) async def add_command(self, ctx: Context, *names: OffTopicName) -> None: - """Adds a new off-topic name to the rotation.""" + """ + Adds a new off-topic name to the rotation. + + The name is not added if it is too similar to an existing name. + """ # Chain multiple words to a single one name = "-".join(names) - await self.bot.api_client.post(f'bot/off-topic-channel-names', params={'name': name}) - log.info( - f"{ctx.author.name}#{ctx.author.discriminator}" - f" added the off-topic channel name '{name}" - ) + existing_names = await self.bot.api_client.get('bot/off-topic-channel-names') + close_match = difflib.get_close_matches(name, existing_names, n=1, cutoff=0.8) + + if close_match: + match = close_match[0] + log.info( + f"{ctx.author} tried to add channel name '{name}' but it was too similar to '{match}'" + ) + await ctx.send( + f":x: The channel name `{name}` is too similar to `{match}`, and thus was not added. " + "Use `!otn forceadd` to override this check." + ) + else: + await self._add_name(ctx, name) + + @otname_group.command(name='forceadd', aliases=('fa',)) + @with_role(*MODERATION_ROLES) + async def force_add_command(self, ctx: Context, *names: OffTopicName) -> None: + """Forcefully adds a new off-topic name to the rotation.""" + # Chain multiple words to a single one + name = "-".join(names) + await self._add_name(ctx, name) + + async def _add_name(self, ctx: Context, name: str) -> None: + """Adds an off-topic channel name to the site storage.""" + await self.bot.api_client.post('bot/off-topic-channel-names', params={'name': name}) + + log.info(f"{ctx.author} added the off-topic channel name '{name}'") await ctx.send(f":ok_hand: Added `{name}` to the names list.") @otname_group.command(name='delete', aliases=('remove', 'rm', 'del', 'd')) @@ -115,12 +142,9 @@ class OffTopicNames(Cog): """Removes a off-topic name from the rotation.""" # Chain multiple words to a single one name = "-".join(names) - await self.bot.api_client.delete(f'bot/off-topic-channel-names/{name}') - log.info( - f"{ctx.author.name}#{ctx.author.discriminator}" - f" deleted the off-topic channel name '{name}" - ) + + log.info(f"{ctx.author} deleted the off-topic channel name '{name}'") await ctx.send(f":ok_hand: Removed `{name}` from the names list.") @otname_group.command(name='list', aliases=('l',)) @@ -152,7 +176,7 @@ class OffTopicNames(Cog): close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70) lines = sorted(f"• {name}" for name in in_matches.union(close_matches)) embed = Embed( - title=f"Query results", + title="Query results", colour=Colour.blue() ) diff --git a/bot/cogs/site.py b/bot/cogs/site.py index c3bdf85e4..d95359159 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -126,15 +126,15 @@ class Site(Cog): invalid_indices = tuple( pick for pick in rules - if pick < 0 or pick >= len(full_rules) + if pick < 1 or pick > len(full_rules) ) if invalid_indices: indices = ', '.join(map(str, invalid_indices)) - await ctx.send(f":x: Invalid rule indices {indices}") + await ctx.send(f":x: Invalid rule indices: {indices}") return - final_rules = tuple(f"**{pick}.** {full_rules[pick]}" for pick in rules) + final_rules = tuple(f"**{pick}.** {full_rules[pick - 1]}" for pick in rules) await LinePaginator.paginate(final_rules, ctx, rules_embed, max_lines=3) diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py index 81185cf3e..c0390cb1e 100644 --- a/bot/cogs/snekbox.py +++ b/bot/cogs/snekbox.py @@ -178,7 +178,7 @@ class Snekbox(Cog): if ctx.author.id in self.jobs: await ctx.send( f"{ctx.author.mention} You've already got a job running - " - f"please wait for it to finish!" + "please wait for it to finish!" ) return @@ -186,10 +186,7 @@ class Snekbox(Cog): await ctx.invoke(self.bot.get_command("help"), "eval") return - log.info( - f"Received code from {ctx.author.name}#{ctx.author.discriminator} " - f"for evaluation:\n{code}" - ) + log.info(f"Received code from {ctx.author} for evaluation:\n{code}") self.jobs[ctx.author.id] = datetime.datetime.now() code = self.prepare_input(code) @@ -213,10 +210,7 @@ class Snekbox(Cog): wait_for_deletion(response, user_ids=(ctx.author.id,), client=ctx.bot) ) - log.info( - f"{ctx.author.name}#{ctx.author.discriminator}'s job had a return code of " - f"{results['returncode']}" - ) + log.info(f"{ctx.author}'s job had a return code of {results['returncode']}") finally: del self.jobs[ctx.author.id] diff --git a/bot/constants.py b/bot/constants.py index f4f45eb2c..4beae84e9 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -345,6 +345,7 @@ class Channels(metaclass=YAMLGetter): help_7: int helpers: int message_log: int + meta: int mod_alerts: int modlog: int off_topic_0: int @@ -460,6 +461,12 @@ class AntiSpam(metaclass=YAMLGetter): rules: Dict[str, Dict[str, int]] +class AntiMalware(metaclass=YAMLGetter): + section = "anti_malware" + + whitelist: list + + class BigBrother(metaclass=YAMLGetter): section = 'big_brother' diff --git a/config-default.yml b/config-default.yml index ca405337e..197743296 100644 --- a/config-default.yml +++ b/config-default.yml @@ -107,6 +107,7 @@ guild: help_7: 587375768556797982 helpers: 385474242440986624 message_log: &MESSAGE_LOG 467752170159079424 + meta: 429409067623251969 mod_alerts: 473092532147060736 modlog: &MODLOG 282638479504965634 off_topic_0: 291284109232308226 @@ -322,6 +323,27 @@ anti_spam: max: 3 +anti_malware: + whitelist: + - '.3gp' + - '.3g2' + - '.avi' + - '.bmp' + - '.gif' + - '.h264' + - '.jpg' + - '.jpeg' + - '.m4v' + - '.mkv' + - '.mov' + - '.mp4' + - '.mpeg' + - '.mpg' + - '.png' + - '.tiff' + - '.wmv' + + reddit: request_delay: 60 subreddits: |