Преглед изворни кода

New bot message system. Squelching joinraid and storage log noise. Join raids beep in console.

tags/1.0.1
Rocketsoup пре 4 година
родитељ
комит
a98521aac1
6 измењених фајлова са 334 додато и 103 уклоњено
  1. 248
    25
      cogs/basecog.py
  2. 71
    59
      cogs/crosspostcog.py
  3. 7
    13
      cogs/joinraidcog.py
  4. 5
    5
      cogs/urlspamcog.py
  5. 1
    0
      config.py.sample
  6. 2
    1
      storage.py

+ 248
- 25
cogs/basecog.py Прегледај датотеку

1
-from discord import Guild, Message, PartialEmoji, RawReactionActionEvent, TextChannel
1
+from discord import Guild, Member, Message, PartialEmoji, RawReactionActionEvent, TextChannel
2
 from discord.ext import commands
2
 from discord.ext import commands
3
-from datetime import timedelta
3
+from datetime import datetime, timedelta
4
 
4
 
5
 from config import CONFIG
5
 from config import CONFIG
6
 from rscollections import AgeBoundDict
6
 from rscollections import AgeBoundDict
7
 from storage import ConfigKey, Storage
7
 from storage import ConfigKey, Storage
8
 import json
8
 import json
9
 
9
 
10
+class BotMessageReaction:
11
+	"""
12
+	A possible reaction to a bot message that will trigger an action. The list
13
+	of available reactions will be listed at the end of a BotMessage. When a
14
+	mod reacts to the message with the emote, something can happen.
15
+
16
+	If the reaction is disabled, reactions will not register. The description
17
+	will still show up in the message, but no emoji is shown. This can be used
18
+	to explain why an action is no longer available.
19
+	"""
20
+	def __init__(self, emoji: str, is_enabled: bool, description: str):
21
+		self.emoji = emoji
22
+		self.is_enabled = is_enabled
23
+		self.description = description
24
+
25
+	def __eq__(self, other):
26
+		return other is not None and \
27
+			other.emoji == self.emoji and \
28
+			other.is_enabled == self.is_enabled and \
29
+			other.description == self.description
30
+
31
+class BotMessage:
32
+	"""
33
+	Holds state for a bot-generated message. A message is composed, sent via
34
+	`BaseCog.post_message()`, and can later be updated.
35
+
36
+	A message consists of a type (e.g. info, warning), text, optional quoted
37
+	text (such as the content of a flagged message), and an optional list of
38
+	actions that can be taken via a mod reacting to the message.
39
+	"""
40
+
41
+	TYPE_DEFAULT = 0
42
+	TYPE_INFO = 1
43
+	TYPE_MOD_WARNING = 2
44
+	TYPE_SUCCESS = 3
45
+	TYPE_FAILURE = 4
46
+
47
+	def __init__(self,
48
+			guild: Guild,
49
+			text: str,
50
+			type: int = 0, # TYPE_DEFAULT
51
+			context = None,
52
+			reply_to: Message = None):
53
+		self.guild = guild
54
+		self.text = text
55
+		self.type = type
56
+		self.context = context
57
+		self.quote = None
58
+		self.__posted_text = None  # last text posted, to test for changes
59
+		self.__posted_emoji = set()
60
+		self.__message = None  # Message
61
+		self.__reply_to = reply_to
62
+		self.__reactions = []  # BotMessageReaction[]
63
+
64
+	def is_sent(self) -> bool:
65
+		"""
66
+		Returns whether this message has been sent to the guild. This may
67
+		continue returning False even after calling BaseCog.post_message if
68
+		the guild has no configured warning channel.
69
+		"""
70
+		return self.__message is not None
71
+
72
+	def message_id(self):
73
+		return self.__message.id if self.__message else None
74
+
75
+	def message_sent_at(self):
76
+		return self.__message.created_at if self.__message else None
77
+
78
+	async def set_text(self, new_text: str) -> None:
79
+		"""
80
+		Replaces the text of this message. If the message has been sent, it will
81
+		be updated.
82
+		"""
83
+		self.text = new_text
84
+		await self.__update_if_sent()
85
+
86
+	async def set_reactions(self, reactions: list) -> None:
87
+		"""
88
+		Replaces all BotMessageReactions with a new list. If the message has
89
+		been sent, it will be updated.
90
+		"""
91
+		if reactions == self.__reactions:
92
+			# No change
93
+			return
94
+		self.__reactions = reactions.copy() if reactions is not None else []
95
+		await self.__update_if_sent()
96
+
97
+	async def add_reaction(self, reaction: BotMessageReaction) -> None:
98
+		"""
99
+		Adds one BotMessageReaction to this message. If a reaction already
100
+		exists for the given emoji it is replaced with the new one. If the
101
+		message has been sent, it will be updated.
102
+		"""
103
+		# Alias for update. Makes for clearer intent.
104
+		await self.update_reaction(reaction)
105
+
106
+	async def update_reaction(self, reaction: BotMessageReaction) -> None:
107
+		"""
108
+		Updates or adds a BotMessageReaction. If the message has been sent, it
109
+		will be updated.
110
+		"""
111
+		found = False
112
+		for i in range(len(self.__reactions)):
113
+			existing = self.__reactions[i]
114
+			if existing.emoji == reaction.emoji:
115
+				if reaction == self.__reactions[i]:
116
+					# No change
117
+					return
118
+				self.__reactions[i] = reaction
119
+				found = True
120
+				break
121
+		if not found:
122
+			self.__reactions.append(reaction)
123
+		await self.__update_if_sent()
124
+
125
+	async def remove_reaction(self, reaction_or_emoji) -> None:
126
+		"""
127
+		Removes a reaction. Can pass either a BotMessageReaction or just the
128
+		emoji string. If the message has been sent, it will be updated.
129
+		"""
130
+		for i in range(len(self.__reactions)):
131
+			existing = self.__reactions[i]
132
+			if (isinstance(reaction_or_emoji, str) and existing.emoji == reaction_or_emoji) or \
133
+				(isinstance(reaction_or_emoji, BotMessageReaction) and existing.emoji == reaction_or_emoji.emoji):
134
+				self.__reactions.pop(i)
135
+				await self.__update_if_sent()
136
+				return
137
+
138
+	def reaction_for_emoji(self, emoji) -> BotMessageReaction:
139
+		for reaction in self.__reactions:
140
+			if isinstance(emoji, PartialEmoji) and reaction.emoji == emoji.name:
141
+				return reaction
142
+			elif isinstance(emoji, str) and reaction.emoji == emoji:
143
+				return reaction
144
+		return None
145
+
146
+	async def __update_if_sent(self) -> None:
147
+		if self.__message:
148
+			await self._update()
149
+
150
+	async def _update(self) -> None:
151
+		content: str = self.__formatted_message()
152
+		if self.__message:
153
+			if content != self.__posted_text:
154
+				await self.__message.edit(content=content)
155
+				self.__posted_text = content
156
+		else:
157
+			if self.__reply_to:
158
+				self.__message = await self.__reply_to.reply(content=content, mention_author=False)
159
+				self.__posted_text = content
160
+			else:
161
+				channel_id = Storage.get_config_value(self.guild, ConfigKey.WARNING_CHANNEL_ID)
162
+				if channel_id is None:
163
+					BaseCog.guild_trace(self.guild, 'No warning channel set! No warning issued.')
164
+					return
165
+				channel: TextChannel = self.guild.get_channel(channel_id)
166
+				if channel is None:
167
+					BaseCog.guild_trace(self.guild, 'Configured warning channel does not exist!')
168
+					return
169
+				self.__message = await channel.send(content=content)
170
+				self.__posted_text = content
171
+		emoji_to_remove = self.__posted_emoji.copy()
172
+		for reaction in self.__reactions:
173
+			if reaction.is_enabled:
174
+				if reaction.emoji not in self.__posted_emoji:
175
+					await self.__message.add_reaction(reaction.emoji)
176
+					self.__posted_emoji.add(reaction.emoji)
177
+				if reaction.emoji in emoji_to_remove:
178
+					emoji_to_remove.remove(reaction.emoji)
179
+		for emoji in emoji_to_remove:
180
+			await self.__message.clear_reaction(emoji)
181
+			if emoji in self.__posted_emoji:
182
+				self.__posted_emoji.remove(emoji)
183
+
184
+	def __formatted_message(self) -> str:
185
+		s: str = ''
186
+
187
+		if self.type == self.TYPE_INFO:
188
+			s += CONFIG['info_emoji'] + ' '
189
+		elif self.type == self.TYPE_MOD_WARNING:
190
+			mention: str = Storage.get_config_value(self.guild, ConfigKey.WARNING_MENTION)
191
+			if mention:
192
+				s += mention + ' '
193
+			s += CONFIG['warning_emoji'] + ' '
194
+		elif self.type == self.TYPE_SUCCESS:
195
+			s += CONFIG['success_emoji'] + ' '
196
+		elif self.type == self.TYPE_FAILURE:
197
+			s += CONFIG['failure_emoji'] + ' '
198
+
199
+		s += self.text
200
+
201
+		if self.quote:
202
+			s += f'\n\n> {self.quote}'
203
+
204
+		if len(self.__reactions) > 0:
205
+			s += '\n\nAvailable actions:'
206
+			for reaction in self.__reactions:
207
+				if reaction.is_enabled:
208
+					s += f'\n   {reaction.emoji} {reaction.description}'
209
+				else:
210
+					s += f'\n   {reaction.description}'
211
+
212
+		return s
213
+
10
 class BaseCog(commands.Cog):
214
 class BaseCog(commands.Cog):
11
 	def __init__(self, bot):
215
 	def __init__(self, bot):
12
 		self.bot = bot
216
 		self.bot = bot
13
-		self.listened_mod_react_message_ids = AgeBoundDict(timedelta(minutes=5), lambda message_id, tpl : tpl[0])
217
+
218
+	# Config
14
 
219
 
15
 	@classmethod
220
 	@classmethod
16
 	def get_cog_default(cls, key: str):
221
 	def get_cog_default(cls, key: str):
24
 			return None
229
 			return None
25
 		return cog.get(key)
230
 		return cog.get(key)
26
 
231
 
27
-	def listen_for_reactions_to(self, message: Message, context = None) -> None:
28
-		"""
29
-		Registers a warning message as something a mod may react to to enact
30
-		some action. `context` will be passed back in `on_mod_react` and can be
31
-		any value that helps give the cog context about the action being
32
-		performed.
33
-		"""
34
-		self.listened_mod_react_message_ids[message.id] = (message.created_at, context)
232
+	# Bot message handling
233
+
234
+	@classmethod
235
+	def __bot_messages(cls, guild: Guild) -> AgeBoundDict:
236
+		bm = Storage.get_state_value(guild, 'bot_messages')
237
+		if bm is None:
238
+			far_future = datetime.utcnow() + timedelta(days=1000)
239
+			bm = AgeBoundDict(timedelta(seconds=600),
240
+				lambda k, v : v.message_sent_at() or far_future)
241
+			Storage.set_state_value(guild, 'bot_messages', bm)
242
+		return bm
243
+
244
+	@classmethod
245
+	async def post_message(cls, message: BotMessage) -> bool:
246
+		await message._update()
247
+		guild_messages = cls.__bot_messages(message.guild)
248
+		if message.is_sent():
249
+			guild_messages[message.message_id()] = message
250
+			return True
251
+		return False
35
 
252
 
36
 	@commands.Cog.listener()
253
 	@commands.Cog.listener()
37
 	async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
254
 	async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
57
 		if message.author.id != self.bot.user.id:
274
 		if message.author.id != self.bot.user.id:
58
 			# Bot didn't author this
275
 			# Bot didn't author this
59
 			return
276
 			return
60
-		if not member.permissions_in(channel).ban_members:
61
-			# Not a mod
277
+		guild_messages = self.__bot_messages(guild)
278
+		bot_message = guild_messages.get(message.id)
279
+		if bot_message is None:
280
+			# Unknown message (expired or was never tracked)
62
 			return
281
 			return
63
-		tpl = self.listened_mod_react_message_ids.get(message.id)
64
-		if tpl is None:
65
-			# Not a message we're listening for
282
+		reaction = bot_message.reaction_for_emoji(payload.emoji)
283
+		if reaction is None or not reaction.is_enabled:
284
+			# Can't use this reaction with this message
66
 			return
285
 			return
67
-		context = tpl[1]
68
-		await self.on_mod_react(message, payload.emoji, context)
286
+		if not member.permissions_in(channel).ban_members:
287
+			# Not a mod (could make permissions configurable per BotMessageReaction some day)
288
+			return
289
+		await self.on_mod_react(bot_message, reaction, member)
69
 
290
 
70
-	async def on_mod_react(self, message: Message, emoji: PartialEmoji, context) -> None:
291
+	async def on_mod_react(self,
292
+			bot_message: BotMessage,
293
+			reaction: BotMessageReaction,
294
+			reacted_by: Member) -> None:
71
 		"""
295
 		"""
72
-		Override point for getting a mod's emote on a bot message. Used to take
73
-		action on a warning, such as banning an offending user. This event is
74
-		only triggered for registered bot messages and reactions by members
75
-		with the proper permissions. The given `context` value is whatever was
76
-		passed in `listen_to_reactions_to()`.
296
+		Subclass override point for receiving mod reactions to bot messages sent
297
+		via `post_message()`.
77
 		"""
298
 		"""
78
 		pass
299
 		pass
79
 
300
 
80
-	async def validate_param(self, context: commands.Context, param_name: str, value,
301
+	@classmethod
302
+	async def validate_param(cls, context: commands.Context, param_name: str, value,
81
 		allowed_types: tuple = None,
303
 		allowed_types: tuple = None,
82
 		min_value = None,
304
 		min_value = None,
83
 		max_value = None) -> bool:
305
 		max_value = None) -> bool:
87
 		to the original message and a False will be returned. If all checks
309
 		to the original message and a False will be returned. If all checks
88
 		succeed, True will be returned.
310
 		succeed, True will be returned.
89
 		"""
311
 		"""
312
+		# TODO: Rework this to use BotMessage
90
 		if allowed_types is not None and not isinstance(value, allowed_types):
313
 		if allowed_types is not None and not isinstance(value, allowed_types):
91
 			if len(allowed_types) == 1:
314
 			if len(allowed_types) == 1:
92
 				await context.message.reply(f'⚠️ `{param_name}` must be of type ' +
315
 				await context.message.reply(f'⚠️ `{param_name}` must be of type ' +

+ 71
- 59
cogs/crosspostcog.py Прегледај датотеку

1
-from discord import Guild, Message, PartialEmoji
1
+from discord import Guild, Member, Message, PartialEmoji
2
 from discord.ext import commands
2
 from discord.ext import commands
3
 from datetime import datetime, timedelta
3
 from datetime import datetime, timedelta
4
 import math
4
 import math
5
 
5
 
6
 from config import CONFIG
6
 from config import CONFIG
7
 from rscollections import AgeBoundList, SizeBoundDict
7
 from rscollections import AgeBoundList, SizeBoundDict
8
-from cogs.basecog import BaseCog
8
+from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
9
 from storage import Storage
9
 from storage import Storage
10
 
10
 
11
 class SpamContext:
11
 class SpamContext:
13
 		self.member = member
13
 		self.member = member
14
 		self.message_hash = message_hash
14
 		self.message_hash = message_hash
15
 		self.age = datetime.now()
15
 		self.age = datetime.now()
16
-		self.warning_message = None
16
+		self.bot_message = None  # BotMessage
17
 		self.is_kicked = False
17
 		self.is_kicked = False
18
 		self.is_banned = False
18
 		self.is_banned = False
19
-		self.messages = set()
20
-		self.deleted_messages = set()
19
+		self.is_autobanned = False
20
+		self.spam_messages = set()  # of Message
21
+		self.deleted_messages = set()  # of Message
21
 
22
 
22
 class CrossPostCog(BaseCog):
23
 class CrossPostCog(BaseCog):
23
 	STATE_KEY_RECENT_MESSAGES = "crosspost_recent_messages"
24
 	STATE_KEY_RECENT_MESSAGES = "crosspost_recent_messages"
101
 				context.age = message.created_at
102
 				context.age = message.created_at
102
 			for m in member_messages:
103
 			for m in member_messages:
103
 				if hash(m.content) == message_hash:
104
 				if hash(m.content) == message_hash:
104
-					context.messages.add(m)
105
+					context.spam_messages.add(m)
105
 			await self.__update_from_context(context)
106
 			await self.__update_from_context(context)
106
 
107
 
107
 	async def __update_from_context(self, context: SpamContext):
108
 	async def __update_from_context(self, context: SpamContext):
108
-		content = next(iter(context.messages)).content
109
-		if len(context.messages) >= self.__ban_count(context.member.guild):
109
+		if len(context.spam_messages) >= self.__ban_count(context.member.guild):
110
 			if not context.is_banned:
110
 			if not context.is_banned:
111
-				await context.member.ban(reason='Posting same message repeatedly', delete_message_days=1)
112
-				msg = f'User {context.member.mention} auto banned for ' + \
113
-					'crosspost spamming.\n' + \
114
-					f'> {content}'
115
-				if context.warning_message:
116
-					await self.update_warn(context.warning_message, msg)
117
-					await context.warning_message.clear_reaction(CONFIG['trash_emoji'])
118
-					await context.warning_message.clear_reaction(CONFIG['kick_emoji'])
119
-					await context.warning_message.clear_reaction(CONFIG['ban_emoji'])
120
-				else:
121
-					context.warning_message = await self.warn(context.member.guild, msg)
122
-		elif len(context.messages) >= self.__warn_count(context.member.guild):
123
-			content = next(iter(context.messages)).content
124
-			msg = f'User {context.member.mention} has posted the exact same ' + \
125
-				f'message {len(context.messages)} times.\n' + \
126
-				f'> {content}' + \
127
-				'\n'
128
-			can_delete = len(context.messages) > len(context.deleted_messages)
129
-			if can_delete:
130
-				msg += f'\n{CONFIG["trash_emoji"]} to delete messages'
111
+				count = len(context.spam_messages)
112
+				await context.member.ban(reason=f'Autobanned by Rocketbot for posting same message {count} times', delete_message_days=1)
113
+				context.is_kicked = True
114
+				context.is_banned = True
115
+				context.is_autobanned = True
116
+				context.deleted_messages |= context.spam_messages
131
 			else:
117
 			else:
132
-				msg += '\nAll messages deleted'
133
-			if not context.is_kicked:
134
-				msg += f'\n{CONFIG["kick_emoji"]} to kick user'
135
-			elif not context.is_banned:
136
-				msg += '\nUser kicked'
137
-			if context.is_banned:
138
-				msg += '\nUser banned'
139
-			else:
140
-				msg += '\n{CONFIG["ban_emoji"]} to ban user'
141
-			if context.warning_message:
142
-				await self.update_warn(context.warning_message, msg)
143
-			else:
144
-				context.warning_message = await self.warn(context.member.guild, msg)
145
-				self.listen_for_reactions_to(context.warning_message, context)
146
-			if can_delete:
147
-				await context.warning_message.add_reaction(CONFIG['trash_emoji'])
148
-			else:
149
-				await context.warning_message.clear_reaction(CONFIG['trash_emoji'])
150
-			if not context.is_kicked:
151
-				await context.warning_message.add_reaction(CONFIG['kick_emoji'])
152
-			else:
153
-				await context.warning_message.clear_reaction(CONFIG['kick_emoji'])
154
-			if not context.is_banned:
155
-				await context.warning_message.add_reaction(CONFIG['ban_emoji'])
118
+				# Already banned. Nothing to update in the message.
119
+				return
120
+		await self.__update_message_from_context(context)
121
+
122
+	async def __update_message_from_context(self, context: SpamContext) -> None:
123
+		first_spam_message = next(iter(context.spam_messages))
124
+		spam_count = len(context.spam_messages)
125
+		deleted_count = len(context.deleted_messages)
126
+		message = context.bot_message
127
+		if message is None:
128
+			message = BotMessage(context.member.guild, '',
129
+				BotMessage.TYPE_MOD_WARNING, context)
130
+			message.quote = first_spam_message.content
131
+		if context.is_autobanned:
132
+			text = f'User {context.member.mention} auto banned for ' + \
133
+				f'posting the same message {deleted_count} ' + \
134
+				'times. Messages from past 24 hours deleted.'
135
+			await message.set_reactions([])
136
+			await message.set_text(text)
137
+		else:
138
+			await message.set_text(f'User {context.member.mention} posted ' +
139
+				f'the same message {spam_count} times.')
140
+			can_delete = spam_count > deleted_count
141
+			can_kick = not context.is_kicked
142
+			can_ban = not context.is_banned
143
+			await message.add_reaction(
144
+				BotMessageReaction(
145
+					CONFIG['trash_emoji'],
146
+					can_delete,
147
+					'Delete messages' if can_delete else f'Deleted {deleted_count} messages'))
148
+			if can_ban:
149
+				# Only show kick info if they can also be banned. Otherwise we say
150
+				# dumb stuff like "user was kicked, user was banned".
151
+				await message.add_reaction(
152
+					BotMessageReaction(
153
+						CONFIG['kick_emoji'],
154
+						can_kick,
155
+						'Kick user' if can_kick else 'User kicked'))
156
 			else:
156
 			else:
157
-				await context.warning_message.clear_reaction(CONFIG['ban_emoji'])
157
+				await message.remove_reaction(CONFIG['kick_emoji'])
158
+			await message.add_reaction(
159
+				BotMessageReaction(
160
+					CONFIG['ban_emoji'], 
161
+					can_ban,
162
+					'Ban user' if can_ban else 'User banned'))
163
+		if context.bot_message is None:
164
+			await self.post_message(message)
165
+			context.bot_message = message
158
 
166
 
159
 	async def __delete_messages(self, context: SpamContext) -> None:
167
 	async def __delete_messages(self, context: SpamContext) -> None:
160
-		for message in context.messages - context.deleted_messages:
168
+		for message in context.spam_messages - context.deleted_messages:
161
 			await message.delete()
169
 			await message.delete()
162
 			context.deleted_messages.add(message)
170
 			context.deleted_messages.add(message)
163
 		await self.__update_from_context(context)
171
 		await self.__update_from_context(context)
169
 
177
 
170
 	async def __ban(self, context: SpamContext) -> None:
178
 	async def __ban(self, context: SpamContext) -> None:
171
 		await context.member.ban(reason='Posting same message repeatedly', delete_message_days=1)
179
 		await context.member.ban(reason='Posting same message repeatedly', delete_message_days=1)
172
-		context.deleted_messages |= context.messages
180
+		context.deleted_messages |= context.spam_messages
173
 		context.is_kicked = True
181
 		context.is_kicked = True
174
 		context.is_banned = True
182
 		context.is_banned = True
175
 		await self.__update_from_context(context)
183
 		await self.__update_from_context(context)
176
 
184
 
177
-	async def on_mod_react(self, message: Message, emoji: PartialEmoji, context: SpamContext) -> None:
185
+	async def on_mod_react(self,
186
+			bot_message: BotMessage,
187
+			reaction: BotMessageReaction,
188
+			reacted_by: Member) -> None:
189
+		context: SpamContext = bot_message.context
178
 		if context is None:
190
 		if context is None:
179
 			return
191
 			return
180
 
192
 
181
-		if emoji.name == CONFIG['trash_emoji']:
193
+		if reaction.emoji == CONFIG['trash_emoji']:
182
 			await self.__delete_messages(context)
194
 			await self.__delete_messages(context)
183
-		elif emoji.name == CONFIG['kick_emoji']:
195
+		elif reaction.emoji == CONFIG['kick_emoji']:
184
 			await self.__kick(context)
196
 			await self.__kick(context)
185
-		elif emoji.name == CONFIG['ban_emoji']:
197
+		elif reaction.emoji == CONFIG['ban_emoji']:
186
 			await self.__ban(context)
198
 			await self.__ban(context)
187
 
199
 
188
 	@commands.Cog.listener()
200
 	@commands.Cog.listener()

+ 7
- 13
cogs/joinraidcog.py Прегледај датотеку

57
 		self.phase and self.raid_start_time properties.
57
 		self.phase and self.raid_start_time properties.
58
 		"""
58
 		"""
59
 		# Check for existing record for this user
59
 		# Check for existing record for this user
60
-		print(f'handle_join({member.name}) start')
61
 		join: JoinRecord = None
60
 		join: JoinRecord = None
62
 		i: int = 0
61
 		i: int = 0
63
 		while i < len(self.joins):
62
 		while i < len(self.joins):
64
 			elem = self.joins[i]
63
 			elem = self.joins[i]
65
 			if elem.member.id == member.id:
64
 			if elem.member.id == member.id:
66
-				print(f'Member {member.name} already in join list at index {i}. Removing.')
67
 				join = self.joins.pop(i)
65
 				join = self.joins.pop(i)
68
 				join.join_time = now
66
 				join.join_time = now
69
 				break
67
 				break
72
 		self.joins.append(join or JoinRecord(member))
70
 		self.joins.append(join or JoinRecord(member))
73
 		# Check raid status and do upkeep
71
 		# Check raid status and do upkeep
74
 		self.__process_joins(now, max_age_seconds, max_join_count)
72
 		self.__process_joins(now, max_age_seconds, max_join_count)
75
-		print(f'handle_join({member.name}) end')
76
 
73
 
77
 	def __process_joins(self,
74
 	def __process_joins(self,
78
 			now: datetime,
75
 			now: datetime,
82
 		Processes self.joins after each addition, detects raids, updates
79
 		Processes self.joins after each addition, detects raids, updates
83
 		self.phase, and throws out unneeded records.
80
 		self.phase, and throws out unneeded records.
84
 		"""
81
 		"""
85
-		print('__process_joins {')
86
 		i: int = 0
82
 		i: int = 0
87
 		recent_count: int = 0
83
 		recent_count: int = 0
88
 		should_cull: bool = self.phase == RaidPhase.NONE
84
 		should_cull: bool = self.phase == RaidPhase.NONE
92
 			is_old: bool = age > max_age_seconds
88
 			is_old: bool = age > max_age_seconds
93
 			if not is_old:
89
 			if not is_old:
94
 				recent_count += 1
90
 				recent_count += 1
95
-				print(f'- {i}. {join.member.name} is {age}s old - recent_count={recent_count}')
96
 			if is_old and should_cull:
91
 			if is_old and should_cull:
97
 				self.joins.pop(i)
92
 				self.joins.pop(i)
98
-				print(f'- {i}. {join.member.name} is {age}s old - too old, removing')
99
 			else:
93
 			else:
100
-				print(f'- {i}. {join.member.name} is {age}s old - moving on to next')
101
 				i += 1
94
 				i += 1
102
 		is_raid = recent_count > max_join_count
95
 		is_raid = recent_count > max_join_count
103
-		print(f'- is_raid {is_raid}')
104
 		if is_raid:
96
 		if is_raid:
105
 			if self.phase == RaidPhase.NONE:
97
 			if self.phase == RaidPhase.NONE:
106
 				self.phase = RaidPhase.JUST_STARTED
98
 				self.phase = RaidPhase.JUST_STARTED
107
 				self.raid_start_time = now
99
 				self.raid_start_time = now
108
-				print('- Phase moved to JUST_STARTED. Recording raid start time.')
100
+				print('\u0007Join raid started!')
109
 			elif self.phase == RaidPhase.JUST_STARTED:
101
 			elif self.phase == RaidPhase.JUST_STARTED:
110
 				self.phase = RaidPhase.CONTINUING
102
 				self.phase = RaidPhase.CONTINUING
111
-				print('- Phase moved to CONTINUING.')
103
+				print(f'\u0007Join raid up to {recent_count} people')
112
 		elif self.phase == self.phase in (RaidPhase.JUST_STARTED, RaidPhase.CONTINUING):
104
 		elif self.phase == self.phase in (RaidPhase.JUST_STARTED, RaidPhase.CONTINUING):
113
 			self.phase = RaidPhase.ENDED
105
 			self.phase = RaidPhase.ENDED
114
-			print('- Phase moved to ENDED.')
106
+			print('Previous join raid ended')
115
 
107
 
116
 		# Undo join add if the raid is over
108
 		# Undo join add if the raid is over
117
 		if self.phase == RaidPhase.ENDED and len(self.joins) > 0:
109
 		if self.phase == RaidPhase.ENDED and len(self.joins) > 0:
118
 			last = self.joins.pop(-1)
110
 			last = self.joins.pop(-1)
119
-			print(f'- Popping last join for {last.member.name}')
120
-		print('} __process_joins')
121
 
111
 
122
 	async def kick_all(self,
112
 	async def kick_all(self,
123
 			reason: str = "Part of join raid") -> list[Member]:
113
 			reason: str = "Part of join raid") -> list[Member]:
134
 			join.is_kicked = True
124
 			join.is_kicked = True
135
 			kicks.append(join.member)
125
 			kicks.append(join.member)
136
 		self.phase = RaidPhase.ENDED
126
 		self.phase = RaidPhase.ENDED
127
+		if len(kicks) > 0:
128
+			print(f'Kicked {len(kicks)} people')
137
 		return kicks
129
 		return kicks
138
 
130
 
139
 	async def ban_all(self,
131
 	async def ban_all(self,
154
 			join.is_banned = True
146
 			join.is_banned = True
155
 			bans.append(join.member)
147
 			bans.append(join.member)
156
 		self.phase = RaidPhase.ENDED
148
 		self.phase = RaidPhase.ENDED
149
+		if len(bans) > 0:
150
+			print(f'Banned {len(bans)} people')
157
 		return bans
151
 		return bans
158
 
152
 
159
 class GuildContext:
153
 class GuildContext:

+ 5
- 5
cogs/urlspamcog.py Прегледај датотеку

53
 				# TODO: Info to mods
53
 				# TODO: Info to mods
54
 
54
 
55
 	def __contains_url(self, text: str) -> bool:
55
 	def __contains_url(self, text: str) -> bool:
56
-		p = re.compile(r'http[^\s]*')
56
+		p = re.compile(r'http(?:s)?://[^\s]+')
57
 		return p.search(text) is not None
57
 		return p.search(text) is not None
58
 
58
 
59
 	def __format_timedelta(self, timespan: timedelta) -> str:
59
 	def __format_timedelta(self, timespan: timedelta) -> str:
63
 		m = (timespan.seconds // 60) % 60
63
 		m = (timespan.seconds // 60) % 60
64
 		s = timespan.seconds % 60
64
 		s = timespan.seconds % 60
65
 		if d > 0:
65
 		if d > 0:
66
-			parts.append(f'{d}d')
66
+			parts.append(f'{d} days')
67
 		if d > 0 or h > 0:
67
 		if d > 0 or h > 0:
68
-			parts.append(f'{h}h')
68
+			parts.append(f'{h} hours')
69
 		if d > 0 or h > 0 or m > 0:
69
 		if d > 0 or h > 0 or m > 0:
70
-			parts.append(f'{m}m')
71
-		parts.append(f'{s}s')
70
+			parts.append(f'{m} minutes')
71
+		parts.append(f'{s} seconds')
72
 		# Limit the precision to the two most significant elements
72
 		# Limit the precision to the two most significant elements
73
 		while len(parts) > 2:
73
 		while len(parts) > 2:
74
 			parts.pop(-1)
74
 			parts.pop(-1)

+ 1
- 0
config.py.sample Прегледај датотеку

7
 	'ban_emoji': '🚫',
7
 	'ban_emoji': '🚫',
8
 	'trash_emoji': '🗑',
8
 	'trash_emoji': '🗑',
9
 	'success_emoji': '✅',
9
 	'success_emoji': '✅',
10
+	'failure_emoji': '❌',
10
 	'warning_emoji': '⚠️',
11
 	'warning_emoji': '⚠️',
11
 	'info_emoji': 'ℹ️',
12
 	'info_emoji': 'ℹ️',
12
 	'config_path': 'config/',
13
 	'config_path': 'config/',

+ 2
- 1
storage.py Прегледај датотеку

167
 
167
 
168
 	@classmethod
168
 	@classmethod
169
 	def __trace(cls, message: str) -> None:
169
 	def __trace(cls, message: str) -> None:
170
-		print(f'{cls.__name__}: {message}')
170
+		# print(f'{cls.__name__}: {message}')
171
+		pass

Loading…
Откажи
Сачувај