|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+from datetime import datetime, timedelta
|
|
|
2
|
+from discord import Member, Message
|
|
1
|
3
|
from discord.ext import commands
|
|
|
4
|
+import re
|
|
|
5
|
+
|
|
2
|
6
|
from cogs.basecog import BaseCog, BotMessage
|
|
|
7
|
+from config import CONFIG
|
|
|
8
|
+from rbutils import parse_timedelta, describe_timedelta
|
|
3
|
9
|
from storage import ConfigKey, Storage
|
|
4
|
10
|
|
|
5
|
11
|
class GeneralCog(BaseCog):
|
|
|
@@ -50,3 +56,52 @@ class GeneralCog(BaseCog):
|
|
50
|
56
|
@commands.guild_only()
|
|
51
|
57
|
async def shutdown(self, context):
|
|
52
|
58
|
await self.bot.close()
|
|
|
59
|
+
|
|
|
60
|
+ @commands.command(
|
|
|
61
|
+ brief='Mass deletes messages',
|
|
|
62
|
+ description='Deletes recent messages by the given user. The age is ' +
|
|
|
63
|
+ 'a duration, such as "30s", "5m", "1h30m". Messages far back in ' +
|
|
|
64
|
+ 'the scrollback might not be deleted by this command.',
|
|
|
65
|
+ usage='<user> <age>'
|
|
|
66
|
+ )
|
|
|
67
|
+ @commands.has_permissions(manage_messages=True)
|
|
|
68
|
+ @commands.guild_only()
|
|
|
69
|
+ async def deletemessages(self, context, user: str, age: str) -> None:
|
|
|
70
|
+ member_id = self.__parse_member_id(user)
|
|
|
71
|
+ if member_id is None:
|
|
|
72
|
+ await context.message.reply(
|
|
|
73
|
+ f'{CONFIG["failure_emoji"]} user must be a mention or numeric user id',
|
|
|
74
|
+ mention_author=False)
|
|
|
75
|
+ return
|
|
|
76
|
+ try:
|
|
|
77
|
+ age_delta: timedelta = parse_timedelta(age)
|
|
|
78
|
+ except ValueError:
|
|
|
79
|
+ await context.message.reply(
|
|
|
80
|
+ f'{CONFIG["failure_emoji"]} age must be a timespan, like "30s", "10m", "1h30m"',
|
|
|
81
|
+ mention_author=False)
|
|
|
82
|
+ return
|
|
|
83
|
+ cutoff: datetime = datetime.utcnow() - age_delta
|
|
|
84
|
+ def predicate(message: Message) -> bool:
|
|
|
85
|
+ return str(message.author.id) == member_id and message.created_at >= cutoff
|
|
|
86
|
+ deleted_messages = []
|
|
|
87
|
+ for channel in context.guild.text_channels:
|
|
|
88
|
+ try:
|
|
|
89
|
+ deleted_messages += await channel.purge(limit=100, check=predicate)
|
|
|
90
|
+ except:
|
|
|
91
|
+ # XXX: Sloppily glossing over access errors instead of checking access
|
|
|
92
|
+ pass
|
|
|
93
|
+ await context.message.reply(
|
|
|
94
|
+ f'{CONFIG["success_emoji"]} Deleted {len(deleted_messages)} ' + \
|
|
|
95
|
+ f'messages by <@!{member_id}> from the past {describe_timedelta(age_delta)}.',
|
|
|
96
|
+ mention_author=False)
|
|
|
97
|
+
|
|
|
98
|
+ def __parse_member_id(self, arg: str) -> str:
|
|
|
99
|
+ p = re.compile('^<@!?([0-9]+)>$')
|
|
|
100
|
+ m = p.match(arg)
|
|
|
101
|
+ if m:
|
|
|
102
|
+ return m.group(1)
|
|
|
103
|
+ p = re.compile('^([0-9]+)$')
|
|
|
104
|
+ m = p.match(arg)
|
|
|
105
|
+ if m:
|
|
|
106
|
+ return m.group(1)
|
|
|
107
|
+ return None
|