Переглянути джерело

Crosspost cog looks at attachments as well

tags/2.0.0
Rocketsoup 2 місяці тому
джерело
коміт
461c1520ea

+ 2
- 2
rocketbot/cogs/autokickcog.py Переглянути файл

107
 
107
 
108
 	async def __kick_or_ban_if_needed(self, member: Member):
108
 	async def __kick_or_ban_if_needed(self, member: Member):
109
 		guild: Guild = member.guild
109
 		guild: Guild = member.guild
110
-		recent_kicks: AgeBoundDict = Storage.get_state_value(guild, AutoKickCog.STATE_KEY_RECENT_KICKS)
110
+		recent_kicks: AgeBoundDict[int, AutoKickContext, datetime, timedelta] = Storage.get_state_value(guild, AutoKickCog.STATE_KEY_RECENT_KICKS)
111
 		if recent_kicks is None:
111
 		if recent_kicks is None:
112
 			recent_kicks = AgeBoundDict(timedelta(seconds=3600), lambda i, context : context.last_kick)
112
 			recent_kicks = AgeBoundDict(timedelta(seconds=3600), lambda i, context : context.last_kick)
113
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_KICKS, recent_kicks)
113
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_KICKS, recent_kicks)
146
 	@staticmethod
146
 	@staticmethod
147
 	def ordinal(val: int):
147
 	def ordinal(val: int):
148
 		"""Formats an integer with an ordinal suffix (English only)"""
148
 		"""Formats an integer with an ordinal suffix (English only)"""
149
-		if val % 100 < 10 or val % 100 > 20:
149
+		if (val // 10) % 10 != 1:
150
 			if val % 10 == 1:
150
 			if val % 10 == 1:
151
 				return f'{val}st'
151
 				return f'{val}st'
152
 			if val % 10 == 2:
152
 			if val % 10 == 2:

+ 4
- 4
rocketbot/cogs/basecog.py Переглянути файл

118
 		issue warnings regardless. Call record_warning or record_warnings after
118
 		issue warnings regardless. Call record_warning or record_warnings after
119
 		triggering a mod warning.
119
 		triggering a mod warning.
120
 		"""
120
 		"""
121
-		recent_warns: AgeBoundDict = Storage.get_state_value(member.guild,
121
+		recent_warns: AgeBoundDict[int, WarningContext, datetime, timedelta] = Storage.get_state_value(member.guild,
122
 			BaseCog.STATE_KEY_RECENT_WARNINGS)
122
 			BaseCog.STATE_KEY_RECENT_WARNINGS)
123
 		if recent_warns is None:
123
 		if recent_warns is None:
124
 			return False
124
 			return False
133
 		Records that mods have been warned about a member and do not need to be
133
 		Records that mods have been warned about a member and do not need to be
134
 		warned about them again for a short while.
134
 		warned about them again for a short while.
135
 		"""
135
 		"""
136
-		recent_warns: AgeBoundDict = Storage.get_state_value(member.guild,
136
+		recent_warns: AgeBoundDict[int, WarningContext, datetime, timedelta] = Storage.get_state_value(member.guild,
137
 			BaseCog.STATE_KEY_RECENT_WARNINGS)
137
 			BaseCog.STATE_KEY_RECENT_WARNINGS)
138
 		if recent_warns is None:
138
 		if recent_warns is None:
139
 			recent_warns = AgeBoundDict(timedelta(seconds=CONFIG['squelch_warning_seconds']),
139
 			recent_warns = AgeBoundDict(timedelta(seconds=CONFIG['squelch_warning_seconds']),
157
 	# Bot message handling
157
 	# Bot message handling
158
 
158
 
159
 	@classmethod
159
 	@classmethod
160
-	def __bot_messages(cls, guild: Guild) -> AgeBoundDict[int, BotMessage]:
161
-		bm = Storage.get_state_value(guild, 'bot_messages')
160
+	def __bot_messages(cls, guild: Guild) -> AgeBoundDict[int, BotMessage, datetime, timedelta]:
161
+		bm: AgeBoundDict[int, BotMessage, datetime, timedelta] = Storage.get_state_value(guild, 'bot_messages')
162
 		if bm is None:
162
 		if bm is None:
163
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
163
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
164
 			bm = AgeBoundDict(timedelta(seconds=600),
164
 			bm = AgeBoundDict(timedelta(seconds=600),

+ 32
- 24
rocketbot/cogs/crosspostcog.py Переглянути файл

2
 Cog for detecting spam messages posted in multiple channels.
2
 Cog for detecting spam messages posted in multiple channels.
3
 """
3
 """
4
 from datetime import datetime, timedelta
4
 from datetime import datetime, timedelta
5
-from discord import Member, Message, utils as discordutils
5
+from typing import Optional
6
+
7
+from discord import Member, Message, utils as discordutils, TextChannel
6
 from discord.ext import commands
8
 from discord.ext import commands
7
 
9
 
8
 from config import CONFIG
10
 from config import CONFIG
14
 	"""
16
 	"""
15
 	Data about a set of duplicate messages from a user.
17
 	Data about a set of duplicate messages from a user.
16
 	"""
18
 	"""
17
-	def __init__(self, member, message_hash):
18
-		self.member = member
19
-		self.message_hash = message_hash
20
-		self.age = datetime.now()
21
-		self.bot_message = None  # BotMessage
22
-		self.is_kicked = False
23
-		self.is_banned = False
24
-		self.is_autobanned = False
25
-		self.spam_messages = set()  # of Message
26
-		self.deleted_messages = set()  # of Message
27
-		self.unique_channels = set()  # of TextChannel
19
+	def __init__(self, member: Member, message_hash: int) -> None:
20
+		self.member: Member = member
21
+		self.message_hash: int = message_hash
22
+		self.age: datetime = datetime.now()
23
+		self.bot_message: Optional[BotMessage] = None
24
+		self.is_kicked: bool = False
25
+		self.is_banned: bool = False
26
+		self.is_autobanned: bool = False
27
+		self.spam_messages: set[Message] = set()
28
+		self.deleted_messages: set[Message] = set()
29
+		self.unique_channels: set[TextChannel] = set()
28
 
30
 
29
 class CrossPostCog(BaseCog, name='Crosspost Detection'):
31
 class CrossPostCog(BaseCog, name='Crosspost Detection'):
30
 	"""
32
 	"""
82
 		if message.channel.permissions_for(message.author).ban_members:
84
 		if message.channel.permissions_for(message.author).ban_members:
83
 			# User exempt from spam detection
85
 			# User exempt from spam detection
84
 			return
86
 			return
85
-		if len(message.content) < self.get_guild_setting(message.guild, self.SETTING_MIN_LENGTH):
87
+		def compute_message_hash(m: Message) -> int:
88
+			to_hash = m.content
89
+			for attachment in m.attachments:
90
+				to_hash += f'\n[[ATT: ct={attachment.content_type} s={attachment.size} w={attachment.width} h={attachment.height}]]'
91
+			h = hash(to_hash)
92
+			return h
93
+		compute_message_hash(message)
94
+		if len(message.attachments) == 0 and len(message.content) < self.get_guild_setting(message.guild, self.SETTING_MIN_LENGTH):
86
 			# Message too short to count towards spam total
95
 			# Message too short to count towards spam total
87
 			return
96
 			return
88
 		max_age = timedelta(seconds=self.get_guild_setting(message.guild, self.SETTING_TIMESPAN))
97
 		max_age = timedelta(seconds=self.get_guild_setting(message.guild, self.SETTING_TIMESPAN))
89
-		warn_count = self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT)
90
-		recent_messages = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES)
98
+		warn_count: int = self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT)
99
+		recent_messages: AgeBoundList[Message, datetime, timedelta] = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES)
91
 		if recent_messages is None:
100
 		if recent_messages is None:
92
 			recent_messages = AgeBoundList(max_age, lambda index, message : message.created_at)
101
 			recent_messages = AgeBoundList(max_age, lambda index, message : message.created_at)
93
 			Storage.set_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES, recent_messages)
102
 			Storage.set_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES, recent_messages)
100
 			return
109
 			return
101
 
110
 
102
 		# Look for repeats
111
 		# Look for repeats
103
-		hash_to_channels = {}  # int --> set(TextChannel)
112
+		hash_to_channels: dict[int, set[TextChannel]] = {}
104
 		max_count = 0
113
 		max_count = 0
105
 		for m in member_messages:
114
 		for m in member_messages:
106
-			key = hash(m.content)
107
-			channels = hash_to_channels.get(key)
115
+			message_hash = compute_message_hash(m)
116
+			channels: set[TextChannel] = hash_to_channels.get(message_hash)
108
 			if channels is None:
117
 			if channels is None:
109
 				channels = set()
118
 				channels = set()
110
-				hash_to_channels[key] = channels
119
+				hash_to_channels[message_hash] = channels
111
 			channels.add(m.channel)
120
 			channels.add(m.channel)
112
 			max_count = max(max_count, len(channels))
121
 			max_count = max(max_count, len(channels))
113
 		if max_count < warn_count:
122
 		if max_count < warn_count:
114
 			return
123
 			return
115
 
124
 
116
 		# Handle the spam
125
 		# Handle the spam
117
-		spam_lookup = Storage.get_state_value(message.guild, self.STATE_KEY_SPAM_CONTEXT)
126
+		spam_lookup: SizeBoundDict[str, SpamContext, datetime] = Storage.get_state_value(message.guild, self.STATE_KEY_SPAM_CONTEXT)
118
 		if spam_lookup is None:
127
 		if spam_lookup is None:
119
 			spam_lookup = SizeBoundDict(
128
 			spam_lookup = SizeBoundDict(
120
 				self.max_spam_contexts,
129
 				self.max_spam_contexts,
134
 					f'\u0007{message.author.name} ({message.author.id}) ' + \
143
 					f'\u0007{message.author.name} ({message.author.id}) ' + \
135
 					f'posted the same message in {channel_count} or more channels.')
144
 					f'posted the same message in {channel_count} or more channels.')
136
 			for m in member_messages:
145
 			for m in member_messages:
137
-				if hash(m.content) == message_hash:
146
+				if compute_message_hash(m) == message_hash:
138
 					context.spam_messages.add(m)
147
 					context.spam_messages.add(m)
139
 					context.unique_channels.add(m.channel)
148
 					context.unique_channels.add(m.channel)
140
 			await self.__update_from_context(context)
149
 			await self.__update_from_context(context)
172
 			message_type: int = BotMessage.TYPE_INFO if self.was_warned_recently(context.member) \
181
 			message_type: int = BotMessage.TYPE_INFO if self.was_warned_recently(context.member) \
173
 				else BotMessage.TYPE_MOD_WARNING
182
 				else BotMessage.TYPE_MOD_WARNING
174
 			message = BotMessage(context.member.guild, '', message_type, context)
183
 			message = BotMessage(context.member.guild, '', message_type, context)
175
-			message.quote = discordutils.remove_markdown(first_spam_message.clean_content())
184
+			message.quote = discordutils.remove_markdown(first_spam_message.clean_content)
176
 			self.record_warning(context.member)
185
 			self.record_warning(context.member)
177
 		if context.is_autobanned:
186
 		if context.is_autobanned:
178
 			text = f'User {context.member.mention} auto banned for ' + \
187
 			text = f'User {context.member.mention} auto banned for ' + \
238
 				message.author.bot or \
247
 				message.author.bot or \
239
 				message.channel is None or \
248
 				message.channel is None or \
240
 				message.guild is None or \
249
 				message.guild is None or \
241
-				message.content is None or \
242
-				message.content == '':
250
+				message.content is None:
243
 			return
251
 			return
244
 		if not self.get_guild_setting(message.guild, self.SETTING_ENABLED):
252
 		if not self.get_guild_setting(message.guild, self.SETTING_ENABLED):
245
 			return
253
 			return

+ 2
- 2
rocketbot/cogs/joinagecog.py Переглянути файл

60
 	async def search(self, context: commands.Context, timespan: str):
60
 	async def search(self, context: commands.Context, timespan: str):
61
 		"""Command handler"""
61
 		"""Command handler"""
62
 		guild: Guild = context.guild
62
 		guild: Guild = context.guild
63
-		recent_joins: AgeBoundList = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
63
+		recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
64
 		if recent_joins is None:
64
 		if recent_joins is None:
65
 			max_age: timedelta = timedelta(seconds=self.get_guild_setting(guild, self.SETTING_JOIN_TIME))
65
 			max_age: timedelta = timedelta(seconds=self.get_guild_setting(guild, self.SETTING_JOIN_TIME))
66
 			recent_joins = AgeBoundList(max_age, lambda i, member0 : member0.joined_at)
66
 			recent_joins = AgeBoundList(max_age, lambda i, member0 : member0.joined_at)
110
 		guild: Guild = member.guild
110
 		guild: Guild = member.guild
111
 		if not self.get_guild_setting(guild, self.SETTING_ENABLED):
111
 		if not self.get_guild_setting(guild, self.SETTING_ENABLED):
112
 			return
112
 			return
113
-		recent_joins: AgeBoundList = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
113
+		recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
114
 		if recent_joins is None:
114
 		if recent_joins is None:
115
 			max_age: timedelta = timedelta(seconds=self.get_guild_setting(guild, self.SETTING_JOIN_TIME))
115
 			max_age: timedelta = timedelta(seconds=self.get_guild_setting(guild, self.SETTING_JOIN_TIME))
116
 			recent_joins = AgeBoundList(max_age, lambda i, member : member.joined_at)
116
 			recent_joins = AgeBoundList(max_age, lambda i, member : member.joined_at)

+ 2
- 2
rocketbot/cogs/joinraidcog.py Переглянути файл

101
 		timespan: timedelta = timedelta(seconds=seconds)
101
 		timespan: timedelta = timedelta(seconds=seconds)
102
 
102
 
103
 		last_raid: JoinRaidContext = Storage.get_state_value(guild, self.STATE_KEY_LAST_RAID)
103
 		last_raid: JoinRaidContext = Storage.get_state_value(guild, self.STATE_KEY_LAST_RAID)
104
-		recent_joins: AgeBoundList = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
104
+		recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild, self.STATE_KEY_RECENT_JOINS)
105
 		if recent_joins is None:
105
 		if recent_joins is None:
106
 			recent_joins = AgeBoundList(timespan, lambda i, member : member.joined_at)
106
 			recent_joins = AgeBoundList(timespan, lambda i, member : member.joined_at)
107
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_JOINS, recent_joins)
107
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_JOINS, recent_joins)
147
 		if setting is self.SETTING_JOIN_TIME:
147
 		if setting is self.SETTING_JOIN_TIME:
148
 			seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
148
 			seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
149
 			timespan: timedelta = timedelta(seconds=seconds)
149
 			timespan: timedelta = timedelta(seconds=seconds)
150
-			recent_joins: AgeBoundList = Storage.get_state_value(guild,
150
+			recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild,
151
 				self.STATE_KEY_RECENT_JOINS)
151
 				self.STATE_KEY_RECENT_JOINS)
152
 			if recent_joins:
152
 			if recent_joins:
153
 				recent_joins.max_age = timespan
153
 				recent_joins.max_age = timespan

+ 1
- 1
rocketbot/cogs/patterncog.py Переглянути файл

158
 				type=message_type,
158
 				type=message_type,
159
 				context=context)
159
 				context=context)
160
 			self.record_warning(message.author)
160
 			self.record_warning(message.author)
161
-			bm.quote = discordutils.remove_markdown(message.clean_content())
161
+			bm.quote = discordutils.remove_markdown(message.clean_content)
162
 			await bm.set_reactions(BotMessageReaction.standard_set(
162
 			await bm.set_reactions(BotMessageReaction.standard_set(
163
 				did_delete=context.is_deleted,
163
 				did_delete=context.is_deleted,
164
 				did_kick=context.is_kicked,
164
 				did_kick=context.is_kicked,

+ 1
- 1
rocketbot/cogs/urlspamcog.py Переглянути файл

132
 					f'{join_age_str} after joining.',
132
 					f'{join_age_str} after joining.',
133
 					type = BotMessage.TYPE_MOD_WARNING if needs_attention else BotMessage.TYPE_INFO,
133
 					type = BotMessage.TYPE_MOD_WARNING if needs_attention else BotMessage.TYPE_INFO,
134
 					context = context)
134
 					context = context)
135
-			bm.quote = discordutils.remove_markdown(message.clean_content())
135
+			bm.quote = discordutils.remove_markdown(message.clean_content)
136
 			await bm.set_reactions(BotMessageReaction.standard_set(
136
 			await bm.set_reactions(BotMessageReaction.standard_set(
137
 				did_delete=context.is_deleted,
137
 				did_delete=context.is_deleted,
138
 				did_kick=context.is_kicked,
138
 				did_kick=context.is_kicked,

+ 24
- 18
rocketbot/collections.py Переглянути файл

3
 """
3
 """
4
 
4
 
5
 from abc import ABCMeta, abstractmethod
5
 from abc import ABCMeta, abstractmethod
6
-from typing import Generic, TypeVar
6
+from typing import Callable, Generic, TypeVar, Optional
7
 
7
 
8
 # Abstract collections
8
 # Abstract collections
9
 
9
 
10
+# Dictionary key
10
 K = TypeVar('K')
11
 K = TypeVar('K')
12
+# Collection value
11
 V = TypeVar('V')
13
 V = TypeVar('V')
14
+# Element age
15
+A = TypeVar('A')
16
+# Age delta
17
+D = TypeVar('D')
12
 
18
 
13
 class AbstractMutableList(list[V], Generic[V], metaclass=ABCMeta):
19
 class AbstractMutableList(list[V], Generic[V], metaclass=ABCMeta):
14
 	"""
20
 	"""
232
 
238
 
233
 # Collections with limited number of elements
239
 # Collections with limited number of elements
234
 
240
 
235
-class SizeBoundList(AbstractMutableList[V], Generic[V]):
241
+class SizeBoundList(AbstractMutableList[V], Generic[V, A]):
236
 	"""
242
 	"""
237
 	Subclass of `list` that enforces a maximum number of elements.
243
 	Subclass of `list` that enforces a maximum number of elements.
238
 
244
 
253
 	"""
259
 	"""
254
 	def __init__(self,
260
 	def __init__(self,
255
 			max_element_count: int,
261
 			max_element_count: int,
256
-			element_age,
262
+			element_age: Callable[[int, V], A],
257
 			*args, **kwargs):
263
 			*args, **kwargs):
258
 		super().__init__(*args, **kwargs)
264
 		super().__init__(*args, **kwargs)
259
 		self.element_age = element_age
265
 		self.element_age = element_age
290
 	def copy(self):
296
 	def copy(self):
291
 		return SizeBoundList(self.max_element_count, self.element_age, super())
297
 		return SizeBoundList(self.max_element_count, self.element_age, super())
292
 
298
 
293
-class SizeBoundSet(AbstractMutableSet[V], Generic[V]):
299
+class SizeBoundSet(AbstractMutableSet[V], Generic[V, A]):
294
 	"""
300
 	"""
295
 	Subclass of `set` that enforces a maximum number of elements.
301
 	Subclass of `set` that enforces a maximum number of elements.
296
 
302
 
311
 	"""
317
 	"""
312
 	def __init__(self,
318
 	def __init__(self,
313
 			max_element_count: int,
319
 			max_element_count: int,
314
-			element_age,
320
+			element_age: Callable[[int, V], A],
315
 			*args, **kwargs):
321
 			*args, **kwargs):
316
 		super().__init__(*args, **kwargs)
322
 		super().__init__(*args, **kwargs)
317
 		self.element_age = element_age
323
 		self.element_age = element_age
347
 	def copy(self):
353
 	def copy(self):
348
 		return SizeBoundSet(self.max_element_count, self.element_age, super())
354
 		return SizeBoundSet(self.max_element_count, self.element_age, super())
349
 
355
 
350
-class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
356
+class SizeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A]):
351
 	"""
357
 	"""
352
 	Subclass of `dict` that enforces a maximum number of elements.
358
 	Subclass of `dict` that enforces a maximum number of elements.
353
 
359
 
368
 	"""
374
 	"""
369
 	def __init__(self,
375
 	def __init__(self,
370
 			max_element_count: int,
376
 			max_element_count: int,
371
-			element_age,
377
+			element_age: Callable[[K, V], A],
372
 			*args, **kwargs):
378
 			*args, **kwargs):
373
 		super().__init__(*args, **kwargs)
379
 		super().__init__(*args, **kwargs)
374
 		self.element_age = element_age
380
 		self.element_age = element_age
406
 
412
 
407
 # Collections with limited age of elements
413
 # Collections with limited age of elements
408
 
414
 
409
-class AgeBoundList(AbstractMutableList[V], Generic[V]):
415
+class AgeBoundList(AbstractMutableList[V], Generic[V, A, D]):
410
 	"""
416
 	"""
411
 	Subclass of `list` that enforces a maximum "age" of elements.
417
 	Subclass of `list` that enforces a maximum "age" of elements.
412
 
418
 
425
 	however elements will only be discarded following the next mutating
431
 	however elements will only be discarded following the next mutating
426
 	operation. Call `self.purge_old_elements()` to force resizing.
432
 	operation. Call `self.purge_old_elements()` to force resizing.
427
 	"""
433
 	"""
428
-	def __init__(self, max_age, element_age, *args, **kwargs):
434
+	def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
429
 		super().__init__(*args, **kwargs)
435
 		super().__init__(*args, **kwargs)
430
 		self.max_age = max_age
436
 		self.max_age = max_age
431
 		self.element_age = element_age
437
 		self.element_age = element_age
445
 		if self.is_culling or len(self) <= 1:
451
 		if self.is_culling or len(self) <= 1:
446
 			return
452
 			return
447
 		self.is_culling = True
453
 		self.is_culling = True
448
-		min_age = None
449
-		max_age = None
450
-		ages = {}
454
+		min_age: Optional[A] = None
455
+		max_age: Optional[A] = None
456
+		ages: dict[int, A] = {}
451
 		for i, elem in enumerate(self):
457
 		for i, elem in enumerate(self):
452
-			age = self.element_age(i, elem)
458
+			age: A = self.element_age(i, elem)
453
 			ages[i] = age
459
 			ages[i] = age
454
 			if min_age is None or age < min_age:
460
 			if min_age is None or age < min_age:
455
 				min_age = age
461
 				min_age = age
456
 			if max_age is None or age > max_age:
462
 			if max_age is None or age > max_age:
457
 				max_age = age
463
 				max_age = age
458
-		cutoff = max_age - self.max_age
464
+		cutoff: A = max_age - self.max_age
459
 		if min_age >= cutoff:
465
 		if min_age >= cutoff:
460
 			self.is_culling = False
466
 			self.is_culling = False
461
 			return
467
 			return
467
 	def copy(self):
473
 	def copy(self):
468
 		return AgeBoundList(self.max_age, self.element_age, super())
474
 		return AgeBoundList(self.max_age, self.element_age, super())
469
 
475
 
470
-class AgeBoundSet(AbstractMutableSet[V], Generic[V]):
476
+class AgeBoundSet(AbstractMutableSet[V], Generic[V, A, D]):
471
 	"""
477
 	"""
472
 	Subclass of `set` that enforces a maximum "age" of elements.
478
 	Subclass of `set` that enforces a maximum "age" of elements.
473
 
479
 
486
 	however elements will only be discarded following the next mutating
492
 	however elements will only be discarded following the next mutating
487
 	operation. Call `self.purge_old_elements()` to force resizing.
493
 	operation. Call `self.purge_old_elements()` to force resizing.
488
 	"""
494
 	"""
489
-	def __init__(self, max_age, element_age, *args, **kwargs):
495
+	def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
490
 		super().__init__(*args, **kwargs)
496
 		super().__init__(*args, **kwargs)
491
 		self.max_age = max_age
497
 		self.max_age = max_age
492
 		self.element_age = element_age
498
 		self.element_age = element_age
528
 	def copy(self):
534
 	def copy(self):
529
 		return AgeBoundSet(self.max_age, self.element_age, super())
535
 		return AgeBoundSet(self.max_age, self.element_age, super())
530
 
536
 
531
-class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V]):
537
+class AgeBoundDict(AbstractMutableDict[K, V], Generic[K, V, A, D]):
532
 	"""
538
 	"""
533
 	Subclass of `dict` that enforces a maximum "age" of elements.
539
 	Subclass of `dict` that enforces a maximum "age" of elements.
534
 
540
 
547
 	however elements will only be discarded following the next mutating
553
 	however elements will only be discarded following the next mutating
548
 	operation. Call `self.purge_old_elements()` to force resizing.
554
 	operation. Call `self.purge_old_elements()` to force resizing.
549
 	"""
555
 	"""
550
-	def __init__(self, max_age, element_age, *args, **kwargs):
556
+	def __init__(self, max_age: D, element_age: Callable[[int, V], A], *args, **kwargs):
551
 		super().__init__(*args, **kwargs)
557
 		super().__init__(*args, **kwargs)
552
 		self.max_age = max_age
558
 		self.max_age = max_age
553
 		self.element_age = element_age
559
 		self.element_age = element_age

+ 1
- 1
rocketbot/pattern.py Переглянути файл

65
 		if self.field in ('content.markdown', 'content'):
65
 		if self.field in ('content.markdown', 'content'):
66
 			return message.content
66
 			return message.content
67
 		if self.field == 'content.plain':
67
 		if self.field == 'content.plain':
68
-			return discordutils.remove_markdown(message.clean_content())
68
+			return discordutils.remove_markdown(message.clean_content)
69
 		if self.field == 'author':
69
 		if self.field == 'author':
70
 			return str(message.author.id)
70
 			return str(message.author.id)
71
 		if self.field == 'author.id':
71
 		if self.field == 'author.id':

+ 1
- 1
rocketbot/storage.py Переглянути файл

136
 		cls.__write_guild_config(guild, config)
136
 		cls.__write_guild_config(guild, config)
137
 
137
 
138
 	@classmethod
138
 	@classmethod
139
-	def get_bot_messages(cls, guild: Guild) -> AgeBoundDict:
139
+	def get_bot_messages(cls, guild: Guild) -> AgeBoundDict[int, Any, datetime, timedelta]:
140
 		"""Returns all the bot messages for a guild."""
140
 		"""Returns all the bot messages for a guild."""
141
 		bm = cls.get_state_value(guild, 'bot_messages')
141
 		bm = cls.get_state_value(guild, 'bot_messages')
142
 		if bm is None:
142
 		if bm is None:

Завантаження…
Відмінити
Зберегти