import re from datetime import datetime, timedelta from discord import Message from discord.ext import commands from cogs.basecog import BaseCog, BotMessage from config import CONFIG from rbutils import parse_timedelta, describe_timedelta from storage import ConfigKey, Storage class GeneralCog(BaseCog, name='General'): """ Cog for handling high-level bot functionality and commands. Should be the first cog added to the bot. """ def __init__(self, bot: commands.Bot): super().__init__(bot) self.is_connected = False self.is_ready = False @commands.Cog.listener() async def on_connect(self): 'Event handler' print('on_connect') self.is_connected = True @commands.Cog.listener() async def on_ready(self): 'Event handler' print('on_ready') self.is_ready = True @commands.command( brief='Posts a test warning', description='Tests whether a warning channel is configured for this ' + \ 'guild by posting a test warning. If a mod mention is ' + \ 'configured, that user/role will be tagged in the test warning.', ) @commands.has_permissions(ban_members=True) @commands.guild_only() async def testwarn(self, context): 'Command handler' if Storage.get_config_value(context.guild, ConfigKey.WARNING_CHANNEL_ID) is None: await context.message.reply( f'{CONFIG["warning_emoji"]} No warning channel set!', mention_author=False) else: bm = BotMessage( context.guild, f'Test warning message (requested by {context.author.name})', type=BotMessage.TYPE_MOD_WARNING) await self.post_message(bm) @commands.command( brief='Simple test reply', description='Replies to the command message. Useful to ensure the ' + \ 'bot is working properly.', ) async def hello(self, context): 'Command handler' await context.message.reply( f'Hey, {context.author.name}!', mention_author=False) @commands.command( brief='Shuts down the bot', description='Causes the bot script to terminate. Only usable by a ' + \ 'user with server admin permissions.', ) @commands.has_permissions(administrator=True) @commands.guild_only() async def shutdown(self, context: commands.Context): 'Command handler' await context.message.add_reaction('👋') await self.bot.close() @commands.command( brief='Mass deletes messages', description='Deletes recent messages by the given user. The user ' + 'can be either an @ mention or a numeric user ID. The age is ' + 'a duration, such as "30s", "5m", "1h30m". Only the most ' + 'recent 100 messages in each channel are searched.', usage=' ' ) @commands.has_permissions(manage_messages=True) @commands.guild_only() async def deletemessages(self, context, user: str, age: str) -> None: 'Command handler' member_id = self.__parse_member_id(user) if member_id is None: await context.message.reply( f'{CONFIG["failure_emoji"]} user must be a mention or numeric user id', mention_author=False) return try: age_delta: timedelta = parse_timedelta(age) except ValueError: await context.message.reply( f'{CONFIG["failure_emoji"]} age must be a timespan, like "30s", "10m", "1h30m"', mention_author=False) return cutoff: datetime = datetime.utcnow() - age_delta def predicate(message: Message) -> bool: return str(message.author.id) == member_id and message.created_at >= cutoff deleted_messages = [] for channel in context.guild.text_channels: try: deleted_messages += await channel.purge(limit=100, check=predicate) except: # XXX: Sloppily glossing over access errors instead of checking access pass await context.message.reply( f'{CONFIG["success_emoji"]} Deleted {len(deleted_messages)} ' + \ f'messages by <@!{member_id}> from the past {describe_timedelta(age_delta)}.', mention_author=False) def __parse_member_id(self, arg: str) -> str: p = re.compile('^<@!?([0-9]+)>$') m = p.match(arg) if m: return m.group(1) p = re.compile('^([0-9]+)$') m = p.match(arg) if m: return m.group(1) return None