Przeglądaj źródła

Adding deletemessages command

tags/1.0.2
Rocketsoup 4 lat temu
rodzic
commit
a70f553157
4 zmienionych plików z 119 dodań i 21 usunięć
  1. 55
    0
      cogs/generalcog.py
  2. 4
    20
      cogs/patterncog.py
  3. 1
    1
      patterns.md
  4. 59
    0
      rbutils.py

+ 55
- 0
cogs/generalcog.py Wyświetl plik

1
+from datetime import datetime, timedelta
2
+from discord import Member, Message
1
 from discord.ext import commands
3
 from discord.ext import commands
4
+import re
5
+
2
 from cogs.basecog import BaseCog, BotMessage
6
 from cogs.basecog import BaseCog, BotMessage
7
+from config import CONFIG
8
+from rbutils import parse_timedelta, describe_timedelta
3
 from storage import ConfigKey, Storage
9
 from storage import ConfigKey, Storage
4
 
10
 
5
 class GeneralCog(BaseCog):
11
 class GeneralCog(BaseCog):
50
 	@commands.guild_only()
56
 	@commands.guild_only()
51
 	async def shutdown(self, context):
57
 	async def shutdown(self, context):
52
 		await self.bot.close()
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

+ 4
- 20
cogs/patterncog.py Wyświetl plik

6
 
6
 
7
 from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
7
 from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
8
 from config import CONFIG
8
 from config import CONFIG
9
+from rbutils import parse_timedelta
9
 from storage import Storage
10
 from storage import Storage
10
 
11
 
11
 class PatternAction:
12
 class PatternAction:
373
 		for cmd in command_chain:
374
 		for cmd in command_chain:
374
 			if pattern_str.startswith(cmd):
375
 			if pattern_str.startswith(cmd):
375
 				pattern_str = pattern_str[len(cmd):].lstrip()
376
 				pattern_str = pattern_str[len(cmd):].lstrip()
377
+			elif pattern_str.startswith(f'"{cmd}"'):
378
+				pattern_str = pattern_str[len(cmd) + 2:].lstrip()
376
 		return pattern_str
379
 		return pattern_str
377
 
380
 
378
 	@classmethod
381
 	@classmethod
632
 		if type == cls.TYPE_FLOAT:
635
 		if type == cls.TYPE_FLOAT:
633
 			return float(value)
636
 			return float(value)
634
 		if type == cls.TYPE_TIMESPAN:
637
 		if type == cls.TYPE_TIMESPAN:
635
-			p = re.compile('^(?:[0-9]+[dhms])+$')
636
-			if p.match(value) is None:
637
-				raise RuntimeError("Illegal timespan value \"{value}\". Must be like \"100d\", \"5m30s\", etc.")
638
-			p = re.compile('([0-9]+)([dhms])')
639
-			days = 0
640
-			hours = 0
641
-			minutes = 0
642
-			seconds = 0
643
-			for m in p.finditer(value):
644
-				scalar = int(m.group(1))
645
-				unit = m.group(2)
646
-				if unit == 'd':
647
-					days = scalar
648
-				elif unit == 'h':
649
-					hours = scalar
650
-				elif unit == 'm':
651
-					minutes = scalar
652
-				elif unit == 's':
653
-					seconds = scalar
654
-			return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
638
+			return parse_timedelta(value)
655
 		raise ValueError(f'Unhandled datatype {datatype}')
639
 		raise ValueError(f'Unhandled datatype {datatype}')

+ 1
- 1
patterns.md Wyświetl plik

16
 reply "You can't say that word!", delete if message contains "heck"
16
 reply "You can't say that word!", delete if message contains "heck"
17
 ```
17
 ```
18
 
18
 
19
-This statement has two actions: replying, then deleting the original message. It will match any word that contains the word "heck".
19
+This statement has two actions: replying, then deleting the original message. It will match any message that contains the word "heck".
20
 
20
 
21
 The list of actions and the matching expression are separated by the word "if", which must be present in every statement.
21
 The list of actions and the matching expression are separated by the word "if", which must be present in every statement.
22
 
22
 

+ 59
- 0
rbutils.py Wyświetl plik

1
+from datetime import timedelta
2
+import re
3
+
4
+def parse_timedelta(s: str) -> timedelta:
5
+	"""
6
+	Parses a timespan. Format examples:
7
+	"30m"
8
+	"10s"
9
+	"90d"
10
+	"1h30m"
11
+	"73d18h22m52s"
12
+	"""
13
+	p = re.compile('^(?:[0-9]+[dhms])+$')
14
+	if p.match(s) is None:
15
+		raise ValueError("Illegal timespan value '{s}'.")
16
+	p = re.compile('([0-9]+)([dhms])')
17
+	days = 0
18
+	hours = 0
19
+	minutes = 0
20
+	seconds = 0
21
+	for m in p.finditer(s):
22
+		scalar = int(m.group(1))
23
+		unit = m.group(2)
24
+		if unit == 'd':
25
+			days = scalar
26
+		elif unit == 'h':
27
+			hours = scalar
28
+		elif unit == 'm':
29
+			minutes = scalar
30
+		elif unit == 's':
31
+			seconds = scalar
32
+	return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
33
+
34
+def describe_timedelta(td: timedelta, max_components: int = 2) -> str:
35
+	values = [
36
+		td.days,
37
+		td.seconds // 3600,
38
+		(td.seconds // 60) % 60,
39
+		td.seconds % 60,
40
+	]
41
+	units = [
42
+		'day' if values[0] == 1 else 'days',
43
+		'hour' if values[1] == 1 else 'hours',
44
+		'minute' if values[2] == 1 else 'minutes',
45
+		'second' if values[3] == 1 else 'seconds',
46
+	]
47
+	while len(values) > 1 and values[0] == 0:
48
+		values.pop(0)
49
+		units.pop(0)
50
+	if len(values) > max_components:
51
+		values = values[0:max_components]
52
+		units = units[0:max_components]
53
+	while len(values) > 1 and values[-1] == 0:
54
+		values.pop(-1)
55
+		units.pop(-1)
56
+	tokens = []
57
+	for i in range(len(values)):
58
+		tokens.append(f'{values[i]} {units[i]}')
59
+	return ' '.join(tokens)

Ładowanie…
Anuluj
Zapisz