Просмотр исходного кода

Normalizing datetimes to utc. Fixing datatype conversions on guild settings

main
Rocketsoup 1 месяц назад
Родитель
Сommit
258150e578

+ 0
- 1
rocketbot/bot.py Просмотреть файл

83
 		return
83
 		return
84
 	bot_log(None, None, 'Creating bot...')
84
 	bot_log(None, None, 'Creating bot...')
85
 	intents = Intents.default()
85
 	intents = Intents.default()
86
-	intents.messages = True
87
 	intents.message_content = True
86
 	intents.message_content = True
88
 	intents.members = True
87
 	intents.members = True
89
 	intents.presences = False
88
 	intents.presences = False

+ 2
- 2
rocketbot/botmessage.py Просмотреть файл

9
 
9
 
10
 from config import CONFIG
10
 from config import CONFIG
11
 from rocketbot.storage import ConfigKey, Storage
11
 from rocketbot.storage import ConfigKey, Storage
12
-from rocketbot.utils import bot_log
12
+from rocketbot.utils import bot_log, norm_datetime
13
 
13
 
14
 
14
 
15
 class BotMessageReaction:
15
 class BotMessageReaction:
165
 
165
 
166
 	def message_sent_at(self) -> Optional[datetime]:
166
 	def message_sent_at(self) -> Optional[datetime]:
167
 		"""Returns when the message was sent or None if not sent."""
167
 		"""Returns when the message was sent or None if not sent."""
168
-		return self.__messages[0].created_at if len(self.__messages) > 0 else None
168
+		return norm_datetime(self.__messages[0].created_at) if len(self.__messages) > 0 else None
169
 
169
 
170
 	def has_reactions(self) -> bool:
170
 	def has_reactions(self) -> bool:
171
 		"""Whether this message has any reactions defined."""
171
 		"""Whether this message has any reactions defined."""

+ 5
- 5
rocketbot/cogs/autokickcog.py Просмотреть файл

1
-from datetime import datetime, timedelta
1
+from datetime import datetime, timedelta, timezone
2
 
2
 
3
 from discord import Guild, Member, Status
3
 from discord import Guild, Member, Status
4
 from discord.ext import tasks
4
 from discord.ext import tasks
27
 class StatusCheckContext:
27
 class StatusCheckContext:
28
 	def __init__(self, member: Member):
28
 	def __init__(self, member: Member):
29
 		self.member = member
29
 		self.member = member
30
-		self.joined_at = datetime.now()
30
+		self.joined_at = datetime.now(timezone.utc)
31
 
31
 
32
 class AutoKickCog(BaseCog, name='Auto Kick'):
32
 class AutoKickCog(BaseCog, name='Auto Kick'):
33
 	"""
33
 	"""
93
 		"""Checks status of new members shortly after joining to see if they go offline"""
93
 		"""Checks status of new members shortly after joining to see if they go offline"""
94
 		contexts = self.status_check_members.copy()
94
 		contexts = self.status_check_members.copy()
95
 		self.status_check_members = []
95
 		self.status_check_members = []
96
-		now = datetime.now()
96
+		now = datetime.now(timezone.utc)
97
 		# bot_log(guild=None, cog_class=None, message=f'Found {len(contexts)} members to check')
97
 		# bot_log(guild=None, cog_class=None, message=f'Found {len(contexts)} members to check')
98
 		for c in contexts:
98
 		for c in contexts:
99
 			context: StatusCheckContext = c
99
 			context: StatusCheckContext = c
118
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_KICKS, recent_kicks)
118
 			Storage.set_state_value(guild, self.STATE_KEY_RECENT_KICKS, recent_kicks)
119
 		context: AutoKickContext = recent_kicks.get(member.id)
119
 		context: AutoKickContext = recent_kicks.get(member.id)
120
 		if context is None:
120
 		if context is None:
121
-			context = AutoKickContext(member, datetime.now())
121
+			context = AutoKickContext(member, datetime.now(timezone.utc))
122
 			recent_kicks[member.id] = context
122
 			recent_kicks[member.id] = context
123
 		else:
123
 		else:
124
-			context.record_kick(datetime.now())
124
+			context.record_kick(datetime.now(timezone.utc))
125
 		max_kick_count: int = self.get_guild_setting(guild, self.SETTING_BAN_COUNT)
125
 		max_kick_count: int = self.get_guild_setting(guild, self.SETTING_BAN_COUNT)
126
 		disable_help = 'To disable this feature: `/disable autokick`.'
126
 		disable_help = 'To disable this feature: `/disable autokick`.'
127
 		ban_help = 'To configure ban threshold: `/set autokick_bancount #` (0 to disable)'
127
 		ban_help = 'To configure ban threshold: `/set autokick_bancount #` (0 to disable)'

+ 10
- 9
rocketbot/cogs/basecog.py Просмотреть файл

24
 from rocketbot.cogsetting import CogSetting
24
 from rocketbot.cogsetting import CogSetting
25
 from rocketbot.collections import AgeBoundDict
25
 from rocketbot.collections import AgeBoundDict
26
 from rocketbot.storage import Storage
26
 from rocketbot.storage import Storage
27
-from rocketbot.utils import bot_log, dump_stacktrace
27
+from rocketbot.utils import bot_log, dump_stacktrace, norm_datetime
28
 
28
 
29
 
29
 
30
 class WarningContext:
30
 class WarningContext:
129
 		"""
129
 		"""
130
 		if guild:
130
 		if guild:
131
 			key = f'{cls.__name__}.{setting.name}'
131
 			key = f'{cls.__name__}.{setting.name}'
132
-			value = Storage.get_config_value(guild, key)
133
-			if value is not None:
134
-				return value
132
+			stored_value = Storage.get_config_value(guild, key)
133
+			if stored_value is not None:
134
+				return setting.to_native_value(stored_value)
135
 		if use_cog_default_if_not_set:
135
 		if use_cog_default_if_not_set:
136
 			return setting.default_value
136
 			return setting.default_value
137
 		return None
137
 		return None
150
 		"""
150
 		"""
151
 		setting.validate_value(new_value)
151
 		setting.validate_value(new_value)
152
 		key = f'{cls.__name__}.{setting.name}'
152
 		key = f'{cls.__name__}.{setting.name}'
153
-		Storage.set_config_value(guild, key, new_value)
153
+		stored_value = setting.to_stored_value(new_value)
154
+		Storage.set_config_value(guild, key, stored_value)
154
 
155
 
155
 	# @commands.Cog.listener()
156
 	# @commands.Cog.listener()
156
 	async def __on_ready(self):
157
 	async def __on_ready(self):
183
 		if context is None:
184
 		if context is None:
184
 			return False
185
 			return False
185
 		squelch_warning_seconds: int = CONFIG['squelch_warning_seconds']
186
 		squelch_warning_seconds: int = CONFIG['squelch_warning_seconds']
186
-		return datetime.now() - context.last_warned < timedelta(seconds=squelch_warning_seconds)
187
+		return datetime.now(timezone.utc) - context.last_warned < timedelta(seconds=squelch_warning_seconds)
187
 
188
 
188
 	def record_warning(self, member: Member):
189
 	def record_warning(self, member: Member):
189
 		"""
190
 		"""
198
 			Storage.set_state_value(member.guild, BaseCog.STATE_KEY_RECENT_WARNINGS, recent_warns)
199
 			Storage.set_state_value(member.guild, BaseCog.STATE_KEY_RECENT_WARNINGS, recent_warns)
199
 		context: WarningContext = recent_warns.get(member.id)
200
 		context: WarningContext = recent_warns.get(member.id)
200
 		if context is None:
201
 		if context is None:
201
-			context = WarningContext(member, datetime.now())
202
+			context = WarningContext(member, datetime.now(timezone.utc))
202
 			recent_warns[member.id] = context
203
 			recent_warns[member.id] = context
203
 		else:
204
 		else:
204
-			context.last_warned = datetime.now()
205
+			context.last_warned = datetime.now(timezone.utc)
205
 
206
 
206
 	def record_warnings(self, members: list[Member]):
207
 	def record_warnings(self, members: list[Member]):
207
 		"""
208
 		"""
219
 		if bm is None:
220
 		if bm is None:
220
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
221
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
221
 			bm = AgeBoundDict(timedelta(seconds=600),
222
 			bm = AgeBoundDict(timedelta(seconds=600),
222
-				lambda k, v : v.message_sent_at() or far_future)
223
+				lambda k, v : norm_datetime(v.message_sent_at()) or far_future)
223
 			Storage.set_state_value(guild, 'bot_messages', bm)
224
 			Storage.set_state_value(guild, 'bot_messages', bm)
224
 		return bm
225
 		return bm
225
 
226
 

+ 14
- 14
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
 import re
4
 import re
5
-from datetime import datetime, timedelta
5
+from datetime import datetime, timedelta, timezone
6
 from typing import Optional
6
 from typing import Optional
7
 
7
 
8
 from discord import Member, Message, TextChannel
8
 from discord import Member, Message, TextChannel
13
 from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
13
 from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
14
 from rocketbot.collections import AgeBoundDict, AgeBoundList
14
 from rocketbot.collections import AgeBoundDict, AgeBoundList
15
 from rocketbot.storage import Storage
15
 from rocketbot.storage import Storage
16
-from rocketbot.utils import str_from_timedelta
16
+from rocketbot.utils import norm_datetime, str_from_timedelta
17
 
17
 
18
 
18
 
19
 class SpamContext:
19
 class SpamContext:
22
 	"""
22
 	"""
23
 	def __init__(self, member: Member) -> None:
23
 	def __init__(self, member: Member) -> None:
24
 		self.member: Member = member
24
 		self.member: Member = member
25
-		self.age: datetime = datetime.now()
25
+		self.age: datetime = datetime.now(timezone.utc)
26
 		self.bot_message: Optional[BotMessage] = None
26
 		self.bot_message: Optional[BotMessage] = None
27
 		self.is_kicked: bool = False
27
 		self.is_kicked: bool = False
28
 		self.is_banned: bool = False
28
 		self.is_banned: bool = False
152
 			self.__trace(f"Hash for message #{m.id} by {m.author.name} is {h}\n\thash content: \"{to_hash}\"")
152
 			self.__trace(f"Hash for message #{m.id} by {m.author.name} is {h}\n\thash content: \"{to_hash}\"")
153
 			return h
153
 			return h
154
 
154
 
155
-		min_length = self.get_guild_setting(message.guild, self.SETTING_MIN_LENGTH)
155
+		min_length: int = self.get_guild_setting(message.guild, self.SETTING_MIN_LENGTH)
156
 		if len(message.attachments) == 0 and len(message.content) < min_length:
156
 		if len(message.attachments) == 0 and len(message.content) < min_length:
157
 			# Message too short to count towards spam total
157
 			# Message too short to count towards spam total
158
 			self.__trace(f"Message len {len(message.content)} < {min_length} configured min")
158
 			self.__trace(f"Message len {len(message.content)} < {min_length} configured min")
159
 			return
159
 			return
160
 
160
 
161
 		# Get config
161
 		# Get config
162
-		max_age = timedelta(seconds=self.get_guild_setting(message.guild, self.SETTING_TIMESPAN))
162
+		max_age: timedelta = self.get_guild_setting(message.guild, self.SETTING_TIMESPAN)
163
 		warn_count: int = self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT)
163
 		warn_count: int = self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT)
164
 		dupe_warn_count: int = self.get_guild_setting(message.guild, self.SETTING_DUPE_WARN_COUNT)
164
 		dupe_warn_count: int = self.get_guild_setting(message.guild, self.SETTING_DUPE_WARN_COUNT)
165
 
165
 
166
 		# Record message
166
 		# Record message
167
 		recent_messages: AgeBoundList[Message, datetime, timedelta] = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES)
167
 		recent_messages: AgeBoundList[Message, datetime, timedelta] = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES)
168
 		if recent_messages is None:
168
 		if recent_messages is None:
169
-			recent_messages = AgeBoundList(max_age, lambda index, message : message.created_at)
169
+			recent_messages = AgeBoundList(max_age, lambda index, message : norm_datetime(message.created_at))
170
 			Storage.set_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES, recent_messages)
170
 			Storage.set_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES, recent_messages)
171
 		recent_messages.max_age = max_age
171
 		recent_messages.max_age = max_age
172
 		recent_messages.append(message)
172
 		recent_messages.append(message)
219
 			self.log(message.guild,
219
 			self.log(message.guild,
220
 				f'\u0007{message.author.name} ({message.author.id}) ' + \
220
 				f'\u0007{message.author.name} ({message.author.id}) ' + \
221
 				f'posted messages in {channel_count} channels.')
221
 				f'posted messages in {channel_count} channels.')
222
-		context.age = message.created_at
222
+		context.age = norm_datetime(message.created_at)
223
 		context.duplicate_count = max_duplicate_count
223
 		context.duplicate_count = max_duplicate_count
224
 		context.spam_messages.update(member_messages)
224
 		context.spam_messages.update(member_messages)
225
 		context.unique_channels.update(unique_channels)
225
 		context.unique_channels.update(unique_channels)
226
 		await self.__update_from_context(context)
226
 		await self.__update_from_context(context)
227
 
227
 
228
 	async def __update_from_context(self, context: SpamContext):
228
 	async def __update_from_context(self, context: SpamContext):
229
-		ban_count = self.get_guild_setting(context.member.guild, self.SETTING_BAN_COUNT)
230
-		dupe_ban_count = self.get_guild_setting(context.member.guild, self.SETTING_DUPE_BAN_COUNT)
229
+		ban_count: int = self.get_guild_setting(context.member.guild, self.SETTING_BAN_COUNT)
230
+		dupe_ban_count: int = self.get_guild_setting(context.member.guild, self.SETTING_DUPE_BAN_COUNT)
231
 		channel_count = len(context.unique_channels)
231
 		channel_count = len(context.unique_channels)
232
 		if channel_count >= ban_count or context.duplicate_count >= dupe_ban_count:
232
 		if channel_count >= ban_count or context.duplicate_count >= dupe_ban_count:
233
 			if not context.is_banned:
233
 			if not context.is_banned:
234
-				max_age = timedelta(seconds=self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN))
234
+				max_age: timedelta = self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN)
235
 				max_age_str = str_from_timedelta(max_age)
235
 				max_age_str = str_from_timedelta(max_age)
236
 				await context.member.ban(
236
 				await context.member.ban(
237
 					reason=f'Rocketbot: Posted in {channel_count} channels within {max_age_str} ' + \
237
 					reason=f'Rocketbot: Posted in {channel_count} channels within {max_age_str} ' + \
253
 		channel_count = len(context.unique_channels)
253
 		channel_count = len(context.unique_channels)
254
 		deleted_count = len(context.deleted_messages)
254
 		deleted_count = len(context.deleted_messages)
255
 		duplicate_count = context.duplicate_count
255
 		duplicate_count = context.duplicate_count
256
-		max_age = timedelta(seconds=self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN))
256
+		max_age: timedelta = self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN)
257
 		max_age_str = str_from_timedelta(max_age)
257
 		max_age_str = str_from_timedelta(max_age)
258
 		message = context.bot_message
258
 		message = context.bot_message
259
 		if message is None:
259
 		if message is None:
328
 			self.__log_ban(context, reacted_by.name)
328
 			self.__log_ban(context, reacted_by.name)
329
 
329
 
330
 	def __log_deletion(self, context: SpamContext, by_who: str) -> None:
330
 	def __log_deletion(self, context: SpamContext, by_who: str) -> None:
331
-		max_age = timedelta(seconds=self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN))
331
+		max_age: timedelta = self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN)
332
 		max_age_str = str_from_timedelta(max_age)
332
 		max_age_str = str_from_timedelta(max_age)
333
 		channel_count = len(context.unique_channels)
333
 		channel_count = len(context.unique_channels)
334
 		duplicate_count = context.duplicate_count
334
 		duplicate_count = context.duplicate_count
338
 				 f'({duplicate_count} identical). Deleted by {by_who}.')
338
 				 f'({duplicate_count} identical). Deleted by {by_who}.')
339
 
339
 
340
 	def __log_kick(self, context: SpamContext, by_who: str) -> None:
340
 	def __log_kick(self, context: SpamContext, by_who: str) -> None:
341
-		max_age = timedelta(seconds=self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN))
341
+		max_age: timedelta = self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN)
342
 		max_age_str = str_from_timedelta(max_age)
342
 		max_age_str = str_from_timedelta(max_age)
343
 		channel_count = len(context.unique_channels)
343
 		channel_count = len(context.unique_channels)
344
 		duplicate_count = context.duplicate_count
344
 		duplicate_count = context.duplicate_count
348
 				 f'({duplicate_count} identical). Kicked by {by_who}.')
348
 				 f'({duplicate_count} identical). Kicked by {by_who}.')
349
 
349
 
350
 	def __log_ban(self, context: SpamContext, by_who: str) -> None:
350
 	def __log_ban(self, context: SpamContext, by_who: str) -> None:
351
-		max_age = timedelta(seconds=self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN))
351
+		max_age: timedelta = self.get_guild_setting(context.member.guild, self.SETTING_TIMESPAN)
352
 		max_age_str = str_from_timedelta(max_age)
352
 		max_age_str = str_from_timedelta(max_age)
353
 		channel_count = len(context.unique_channels)
353
 		channel_count = len(context.unique_channels)
354
 		duplicate_count = context.duplicate_count
354
 		duplicate_count = context.duplicate_count

+ 3
- 5
rocketbot/cogs/joinraidcog.py Просмотреть файл

106
 		guild: Guild = member.guild
106
 		guild: Guild = member.guild
107
 		if not self.get_guild_setting(guild, self.SETTING_ENABLED):
107
 		if not self.get_guild_setting(guild, self.SETTING_ENABLED):
108
 			return
108
 			return
109
-		min_count = self.get_guild_setting(guild, self.SETTING_JOIN_COUNT)
110
-		seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
111
-		timespan: timedelta = timedelta(seconds=seconds)
109
+		min_count: int = self.get_guild_setting(guild, self.SETTING_JOIN_COUNT)
110
+		timespan: timedelta = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
112
 
111
 
113
 		last_raid: JoinRaidContext = Storage.get_state_value(guild, self.STATE_KEY_LAST_RAID)
112
 		last_raid: JoinRaidContext = Storage.get_state_value(guild, self.STATE_KEY_LAST_RAID)
114
 		recent_joins: AgeBoundList[Member, datetime, timedelta] = 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)
155
 
154
 
156
 	async def on_setting_updated(self, guild: Guild, setting: CogSetting) -> None:
155
 	async def on_setting_updated(self, guild: Guild, setting: CogSetting) -> None:
157
 		if setting is self.SETTING_JOIN_TIME:
156
 		if setting is self.SETTING_JOIN_TIME:
158
-			seconds = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
159
-			timespan: timedelta = timedelta(seconds=seconds)
157
+			timespan: timedelta = self.get_guild_setting(guild, self.SETTING_JOIN_TIME)
160
 			recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild,
158
 			recent_joins: AgeBoundList[Member, datetime, timedelta] = Storage.get_state_value(guild,
161
 				self.STATE_KEY_RECENT_JOINS)
159
 				self.STATE_KEY_RECENT_JOINS)
162
 			if recent_joins:
160
 			if recent_joins:

+ 12
- 5
rocketbot/cogs/logcog.py Просмотреть файл

30
 
30
 
31
 from rocketbot.cogs.basecog import BaseCog, BotMessage, CogSetting
31
 from rocketbot.cogs.basecog import BaseCog, BotMessage, CogSetting
32
 from rocketbot.storage import Storage
32
 from rocketbot.storage import Storage
33
-from rocketbot.utils import dump_stacktrace
33
+from rocketbot.utils import dump_stacktrace, norm_datetime
34
 
34
 
35
 
35
 
36
 class BufferedMessageEditEvent:
36
 class BufferedMessageEditEvent:
336
 			pass  # not that interesting and probably noisy
336
 			pass  # not that interesting and probably noisy
337
 		if after.timed_out_until != before.timed_out_until:
337
 		if after.timed_out_until != before.timed_out_until:
338
 			if after.timed_out_until:
338
 			if after.timed_out_until:
339
-				delta = after.timed_out_until - datetime.now()
339
+				delta = after.timed_out_until - datetime.now(timezone.utc)
340
 				changes.append(f'Timed out for `{delta}`')
340
 				changes.append(f'Timed out for `{delta}`')
341
 			elif before.timed_out_until:
341
 			elif before.timed_out_until:
342
 				changes.append('Timeout cleared')
342
 				changes.append('Timeout cleared')
426
 			guild = user.guild
426
 			guild = user.guild
427
 		else:
427
 		else:
428
 			return None
428
 			return None
429
-		now = datetime.now()
429
+		now = datetime.now(timezone.utc)
430
 		async for entry in guild.audit_logs():
430
 		async for entry in guild.audit_logs():
431
 			age_seconds = now.timestamp() - entry.created_at.timestamp()
431
 			age_seconds = now.timestamp() - entry.created_at.timestamp()
432
 			if entry.action == action and entry.target == user and age_seconds <= max_age:
432
 			if entry.action == action and entry.target == user and age_seconds <= max_age:
493
 
493
 
494
 		https://discordpy.readthedocs.io/en/stable/api.html#discord.on_message
494
 		https://discordpy.readthedocs.io/en/stable/api.html#discord.on_message
495
 		"""
495
 		"""
496
-		# print(f"Saw message {message.id} \"{message.content}\"")
496
+		# print(f"on_message:"
497
+		# 	f"\n\tid: {message.id}"
498
+		# 	f"\n\tauthor: {message.author.name}"
499
+		# 	f"\n\tchannel: {message.channel.name}"
500
+		# 	f"\n\tcontent: \"{message.content}\""
501
+		# 	f"\n\tattachments: x{len(message.attachments)}"
502
+		# 	f"\n\tembeds: x{len(message.embeds)}"
503
+		# 	f"\n\tcomponents: x{len(message.components)}")
497
 
504
 
498
 	@Cog.listener()
505
 	@Cog.listener()
499
 	async def on_message_edit(self, before: Message, after: Message) -> None:
506
 	async def on_message_edit(self, before: Message, after: Message) -> None:
570
 		old_cutoff = timedelta(days=1)
577
 		old_cutoff = timedelta(days=1)
571
 		now = datetime.now(timezone.utc)
578
 		now = datetime.now(timezone.utc)
572
 		for event in events:
579
 		for event in events:
573
-			if event.before is not None and (now - event.after.created_at) < old_cutoff:
580
+			if event.before is not None and (now - norm_datetime(event.after.created_at)) < old_cutoff:
574
 				simple_edits.append(event)
581
 				simple_edits.append(event)
575
 			else:
582
 			else:
576
 				complex_edits.append(event)
583
 				complex_edits.append(event)

+ 2
- 2
rocketbot/cogs/patterncog.py Просмотреть файл

22
 	PatternStatement,
22
 	PatternStatement,
23
 )
23
 )
24
 from rocketbot.storage import Storage
24
 from rocketbot.storage import Storage
25
-from rocketbot.utils import MOD_PERMISSIONS, dump_stacktrace
25
+from rocketbot.utils import MOD_PERMISSIONS, dump_stacktrace, norm_datetime
26
 
26
 
27
 
27
 
28
 class PatternContext:
28
 class PatternContext:
179
 		if last_matched is None:
179
 		if last_matched is None:
180
 			last_matched = {}
180
 			last_matched = {}
181
 			Storage.set_state_value(guild, 'PatternCog.last_matched', last_matched)
181
 			Storage.set_state_value(guild, 'PatternCog.last_matched', last_matched)
182
-		last_matched[name] = time
182
+		last_matched[name] = norm_datetime(time)
183
 
183
 
184
 	@Cog.listener()
184
 	@Cog.listener()
185
 	async def on_message(self, message: Message) -> None:
185
 	async def on_message(self, message: Message) -> None:

+ 4
- 5
rocketbot/cogs/urlspamcog.py Просмотреть файл

12
 
12
 
13
 from config import CONFIG
13
 from config import CONFIG
14
 from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
14
 from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
15
-from rocketbot.utils import describe_timedelta
15
+from rocketbot.utils import describe_timedelta, norm_datetime
16
 
16
 
17
 
17
 
18
 class URLSpamContext:
18
 class URLSpamContext:
140
 
140
 
141
 	async def check_message_recency(self, message: Message):
141
 	async def check_message_recency(self, message: Message):
142
 		"""Checks if the message was sent too recently by a new user"""
142
 		"""Checks if the message was sent too recently by a new user"""
143
-		action = self.get_guild_setting(message.guild, self.SETTING_ACTION)
144
-		join_seconds = self.get_guild_setting(message.guild, self.SETTING_JOIN_AGE)
145
-		min_join_age = timedelta(seconds=join_seconds)
143
+		action: str = self.get_guild_setting(message.guild, self.SETTING_ACTION)
144
+		min_join_age: timedelta = self.get_guild_setting(message.guild, self.SETTING_JOIN_AGE)
146
 		if action == 'nothing':
145
 		if action == 'nothing':
147
 			self.__trace("Configured action is nothing")
146
 			self.__trace("Configured action is nothing")
148
 			return
147
 			return
149
-		join_age = message.created_at - message.author.joined_at
148
+		join_age = norm_datetime(message.created_at) - norm_datetime(message.author.joined_at)
150
 		join_age_str = describe_timedelta(join_age)
149
 		join_age_str = describe_timedelta(join_age)
151
 		if join_age > min_join_age:
150
 		if join_age > min_join_age:
152
 			self.__trace("User has been member long enough")
151
 			self.__trace("User has been member long enough")

+ 2
- 1
rocketbot/cogs/videopreviewcog.py Просмотреть файл

151
 
151
 
152
 	async def _wait_for_preview(self, message: Message, links: list[MessageLink]):
152
 	async def _wait_for_preview(self, message: Message, links: list[MessageLink]):
153
 		Self = VideoPreviewCog
153
 		Self = VideoPreviewCog
154
-		await asyncio.sleep(self.get_guild_setting(message.guild, Self.SETTING_DELAY))
154
+		delay: timedelta = self.get_guild_setting(message.guild, Self.SETTING_DELAY)
155
+		await asyncio.sleep(delay.total_seconds())
155
 		# Look for embeds already showing the video
156
 		# Look for embeds already showing the video
156
 		self.log(message.guild, "Checking message for embeds")
157
 		self.log(message.guild, "Checking message for embeds")
157
 		for embed in message.embeds:
158
 		for embed in message.embeds:

+ 4
- 0
rocketbot/cogsetting.py Просмотреть файл

129
 
129
 
130
 	def to_stored_value(self, native_value: Any) -> Any:
130
 	def to_stored_value(self, native_value: Any) -> Any:
131
 		"""Converts a configuration value to a JSON-compatible datatype."""
131
 		"""Converts a configuration value to a JSON-compatible datatype."""
132
+		if native_value is None:
133
+			return None
132
 		if self.datatype is timedelta:
134
 		if self.datatype is timedelta:
133
 			return native_value.total_seconds()
135
 			return native_value.total_seconds()
134
 		return native_value
136
 		return native_value
135
 
137
 
136
 	def to_native_value(self, stored_value: Any) -> Any:
138
 	def to_native_value(self, stored_value: Any) -> Any:
137
 		"""Converts the stored JSON-compatible datatype to its actual value."""
139
 		"""Converts the stored JSON-compatible datatype to its actual value."""
140
+		if stored_value is None:
141
+			return None
138
 		if self.datatype is timedelta and isinstance(stored_value, (int, float)):
142
 		if self.datatype is timedelta and isinstance(stored_value, (int, float)):
139
 			return timedelta(seconds=stored_value)
143
 			return timedelta(seconds=stored_value)
140
 		return stored_value
144
 		return stored_value

+ 3
- 2
rocketbot/pattern.py Просмотреть файл

13
 
13
 
14
 from rocketbot.utils import (
14
 from rocketbot.utils import (
15
 	is_user_id,
15
 	is_user_id,
16
+	norm_datetime,
16
 	str_from_quoted_str,
17
 	str_from_quoted_str,
17
 	timedelta_from_str,
18
 	timedelta_from_str,
18
 	user_id_from_mention,
19
 	user_id_from_mention,
113
 		if self.field in (cls.FIELD_AUTHOR_ID, cls.ALIAS_FIELD_AUTHOR_ID):
114
 		if self.field in (cls.FIELD_AUTHOR_ID, cls.ALIAS_FIELD_AUTHOR_ID):
114
 			return str(message.author.id)
115
 			return str(message.author.id)
115
 		if self.field == cls.FIELD_AUTHOR_JOINAGE:
116
 		if self.field == cls.FIELD_AUTHOR_JOINAGE:
116
-			return message.created_at - message.author.joined_at
117
+			return norm_datetime(message.created_at) - norm_datetime(message.author.joined_at)
117
 		if self.field == cls.FIELD_AUTHOR_NAME:
118
 		if self.field == cls.FIELD_AUTHOR_NAME:
118
 			return message.author.name
119
 			return message.author.name
119
 		if self.field == cls.FIELD_LAST_MATCHED:
120
 		if self.field == cls.FIELD_LAST_MATCHED:
120
 			long_ago = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=timezone.utc)
121
 			long_ago = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=timezone.utc)
121
 			last_matched = other_fields.get('last_matched') or long_ago
122
 			last_matched = other_fields.get('last_matched') or long_ago
122
-			return message.created_at - last_matched
123
+			return norm_datetime(message.created_at) - norm_datetime(last_matched)
123
 		raise ValueError(f'Bad field name "{self.field}"')
124
 		raise ValueError(f'Bad field name "{self.field}"')
124
 
125
 
125
 	def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:
126
 	def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:

+ 2
- 1
rocketbot/storage.py Просмотреть файл

10
 
10
 
11
 from config import CONFIG
11
 from config import CONFIG
12
 from rocketbot.collections import AgeBoundDict
12
 from rocketbot.collections import AgeBoundDict
13
+from rocketbot.utils import norm_datetime
13
 
14
 
14
 
15
 
15
 class ConfigKey:
16
 class ConfigKey:
143
 		if bm is None:
144
 		if bm is None:
144
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
145
 			far_future = datetime.now(timezone.utc) + timedelta(days=1000)
145
 			bm = AgeBoundDict(timedelta(seconds=600),
146
 			bm = AgeBoundDict(timedelta(seconds=600),
146
-				lambda k, v : v.message_sent_at() or far_future)
147
+				lambda k, v : norm_datetime(v.message_sent_at()) or far_future)
147
 			Storage.set_state_value(guild, 'bot_messages', bm)
148
 			Storage.set_state_value(guild, 'bot_messages', bm)
148
 		return bm
149
 		return bm
149
 
150
 

+ 6
- 1
rocketbot/utils.py Просмотреть файл

4
 import re
4
 import re
5
 import sys
5
 import sys
6
 import traceback
6
 import traceback
7
-from datetime import datetime, timedelta
7
+from datetime import datetime, timedelta, timezone
8
 from typing import Any, Optional, Union
8
 from typing import Any, Optional, Union
9
 
9
 
10
 import discord
10
 import discord
206
 		return f"{size/gib:,.1f} GiB"
206
 		return f"{size/gib:,.1f} GiB"
207
 	return f"{size/gib:,.0f} GiB"
207
 	return f"{size/gib:,.0f} GiB"
208
 
208
 
209
+def norm_datetime(dt: datetime) -> datetime:
210
+	"""Converts a datetime to UTC for consistent comparison."""
211
+	# "Naive" datetimes (without a time zone) are assumed as system local time zone
212
+	return datetime.fromtimestamp(dt.timestamp(), timezone.utc)
213
+
209
 MOD_PERMISSIONS: Permissions = Permissions(Permissions.manage_messages.flag)
214
 MOD_PERMISSIONS: Permissions = Permissions(Permissions.manage_messages.flag)
210
 
215
 
211
 class TimeDeltaTransformer(Transformer):
216
 class TimeDeltaTransformer(Transformer):

Загрузка…
Отмена
Сохранить