Quellcode durchsuchen

Migrating to new CogSettings. Cleaning up CogSetting code. Brainstorming pattern matching expression language.

tags/1.0.1
Rocketsoup vor 4 Jahren
Ursprung
Commit
8b2cfd2bcc
5 geänderte Dateien mit 168 neuen und 227 gelöschten Zeilen
  1. 65
    49
      cogs/basecog.py
  2. 38
    168
      cogs/crosspostcog.py
  3. 7
    4
      cogs/generalcog.py
  4. 52
    0
      cogs/patterncog.py
  5. 6
    6
      config.py.sample

+ 65
- 49
cogs/basecog.py Datei anzeigen

366
 
366
 
367
 	@commands.Cog.listener()
367
 	@commands.Cog.listener()
368
 	async def on_ready(self):
368
 	async def on_ready(self):
369
+		self.__set_up_setting_commands()
370
+
371
+	def __set_up_setting_commands(self):
372
+		"""
373
+		Sets up getter and setter commands for all registered cog settings.
374
+		Only runs once.
375
+		"""
369
 		if self.are_settings_setup:
376
 		if self.are_settings_setup:
370
 			return
377
 			return
378
+		self.are_settings_setup = True
379
+
380
+		# See if the cog has a command group. Currently only supporting one max.
371
 		group: commands.core.Group = None
381
 		group: commands.core.Group = None
372
 		for member_name in dir(self):
382
 		for member_name in dir(self):
373
 			member = getattr(self, member_name)
383
 			member = getattr(self, member_name)
374
 			if isinstance(member, commands.core.Group):
384
 			if isinstance(member, commands.core.Group):
375
 				group = member
385
 				group = member
376
 				break
386
 				break
377
-		lookup = {}
387
+
378
 		for setting in self.settings:
388
 		for setting in self.settings:
379
-			# Manually constructing equivalent of:
380
-			# 	@commands.command(
381
-			# 		brief='Posts a test warning in the configured warning channel.'
382
-			# 	)
383
-			# 	@commands.has_permissions(ban_members=True)
384
-			# 	@commands.guild_only()
385
-			# 	async def get/setvar(self, context, ...):
386
-			async def _set_setting(self, context, new_value):
387
-				s = lookup[context.command.name]
388
-				await self.__set_setting(context, new_value, s)
389
-			async def _get_setting(self, context):
390
-				s = lookup[context.command.name]
391
-				await self.__get_setting(context, s)
392
-
393
-			set_command = commands.Command(
394
-				_set_setting,
395
-				name=f'set{setting.name}',
396
-				brief=f'Sets {setting.brief}',
397
-				description=setting.description,
398
-				usage=setting.usage,
399
-				checks=[
400
-					commands.has_permissions(ban_members=True),
401
-					commands.guild_only(),
402
-				])
403
-			# XXX: Passing `cog` in init gets ignored and set to `None`.
404
-			set_command.cog = self
405
-			get_command = commands.Command(
406
-				_get_setting,
407
-				name=f'get{setting.name}',
408
-				brief=f'Shows {setting.brief}',
409
-				description=setting.description,
410
-				checks=[
411
-					commands.has_permissions(ban_members=True),
412
-					commands.guild_only(),
413
-				])
414
-			get_command.cog = self
415
-
416
-			if group:
417
-				group.add_command(get_command)
418
-				group.add_command(set_command)
419
-			else:
420
-				self.bot.add_command(get_command)
421
-				self.bot.add_command(set_command)
389
+			self.__make_getter_setter_commands(setting, group)
422
 
390
 
423
-			lookup[set_command.name] = setting
424
-			lookup[get_command.name] = setting
425
-		self.are_settings_setup = True
391
+	def __make_getter_setter_commands(self,
392
+			setting: CogSetting,
393
+			group: commands.core.Group) -> None:
394
+		"""
395
+		Creates a "get..." and "set..." command for the given setting and
396
+		either registers them as subcommands under the given command group or
397
+		under the bot if `None`.
398
+		"""
399
+		# Manually constructing equivalent of:
400
+		# 	@commands.command(
401
+		# 		brief='Posts a test warning in the configured warning channel.'
402
+		# 	)
403
+		# 	@commands.has_permissions(ban_members=True)
404
+		# 	@commands.guild_only()
405
+		# 	async def getvar(self, context):
406
+		async def getter(self, context):
407
+			await self.__get_setting_command(context, setting)
408
+		async def setter(self, context, new_value):
409
+			await self.__set_setting_command(context, new_value, setting)
410
+
411
+		get_command = commands.Command(
412
+			getter,
413
+			name=f'get{setting.name}',
414
+			brief=f'Shows {setting.brief}',
415
+			description=setting.description,
416
+			checks=[
417
+				commands.has_permissions(ban_members=True),
418
+				commands.guild_only(),
419
+			])
420
+		set_command = commands.Command(
421
+			setter,
422
+			name=f'set{setting.name}',
423
+			brief=f'Sets {setting.brief}',
424
+			description=setting.description,
425
+			usage=setting.usage,
426
+			checks=[
427
+				commands.has_permissions(ban_members=True),
428
+				commands.guild_only(),
429
+			])
430
+
431
+		# XXX: Passing `cog` in init gets ignored and set to `None` so set after.
432
+		# This ensures the callback is passed `self`.
433
+		get_command.cog = self
434
+		set_command.cog = self
435
+
436
+		if group:
437
+			group.add_command(get_command)
438
+			group.add_command(set_command)
439
+		else:
440
+			self.bot.add_command(get_command)
441
+			self.bot.add_command(set_command)
426
 
442
 
427
-	async def __set_setting(self, context, new_value, setting) -> None:
443
+	async def __set_setting_command(self, context, new_value, setting) -> None:
428
 		setting_name = setting.name
444
 		setting_name = setting.name
429
 		if context.command.parent:
445
 		if context.command.parent:
430
 			setting_name = f'{context.command.parent.name}.{setting_name}'
446
 			setting_name = f'{context.command.parent.name}.{setting_name}'
450
 			f'{CONFIG["success_emoji"]} `{setting_name}` is now set to `{new_value}`',
466
 			f'{CONFIG["success_emoji"]} `{setting_name}` is now set to `{new_value}`',
451
 			mention_author=False)
467
 			mention_author=False)
452
 
468
 
453
-	async def __get_setting(self, context, setting) -> None:
469
+	async def __get_setting_command(self, context, setting) -> None:
454
 		setting_name = setting.name
470
 		setting_name = setting.name
455
 		if context.command.parent:
471
 		if context.command.parent:
456
 			setting_name = f'{context.command.parent.name}.{setting_name}'
472
 			setting_name = f'{context.command.parent.name}.{setting_name}'

+ 38
- 168
cogs/crosspostcog.py Datei anzeigen

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, BotMessage, BotMessageReaction
8
+from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
9
 from storage import Storage
9
 from storage import Storage
10
 
10
 
11
 class SpamContext:
11
 class SpamContext:
21
 		self.deleted_messages = set()  # of Message
21
 		self.deleted_messages = set()  # of Message
22
 
22
 
23
 class CrossPostCog(BaseCog):
23
 class CrossPostCog(BaseCog):
24
+	SETTING_WARN_COUNT = CogSetting('warncount',
25
+		brief='number of messages to trigger a warning',
26
+		description='The number of identical messages to trigger a mod warning.',
27
+		usage='<count:int>',
28
+		min_value=2)
29
+	SETTING_BAN_COUNT = CogSetting('bancount',
30
+		brief='number of messages to trigger a ban',
31
+		description='The number of identical messages to trigger an ' + \
32
+			'automatic ban. Set to a large value to effectively disable, e.g. 9999.',
33
+		usage='<count:int>',
34
+		min_value=2)
35
+	SETTING_MIN_LENGTH = CogSetting('minlength',
36
+		brief='minimum message length',
37
+		description='The minimum number of characters in a message to be ' + \
38
+			'checked for duplicates. This can help ignore common short ' + \
39
+			'messages like "lol" or a single emoji. Set to 0 to count all ' + \
40
+			'message lengths.',
41
+		usage='<character_count:int>',
42
+		min_value=0)
43
+	SETTING_TIMESPAN = CogSetting('timespan',
44
+		brief='time window to look for dupe messages',
45
+		description='The number of seconds of message history to look at ' + \
46
+			'when looking for duplicates. Shorter values are preferred, ' + \
47
+			'both to detect bots and avoid excessive memory usage.',
48
+		usage='<seconds:int>',
49
+		min_value=1)
50
+
24
 	STATE_KEY_RECENT_MESSAGES = "crosspost_recent_messages"
51
 	STATE_KEY_RECENT_MESSAGES = "crosspost_recent_messages"
25
 	STATE_KEY_SPAM_CONTEXT = "crosspost_spam_context"
52
 	STATE_KEY_SPAM_CONTEXT = "crosspost_spam_context"
26
 
53
 
29
 	CONFIG_KEY_MIN_MESSAGE_LENGTH = "crosspost_min_message_length"
56
 	CONFIG_KEY_MIN_MESSAGE_LENGTH = "crosspost_min_message_length"
30
 	CONFIG_KEY_MESSAGE_AGE = "crosspost_message_age"
57
 	CONFIG_KEY_MESSAGE_AGE = "crosspost_message_age"
31
 
58
 
32
-	MIN_WARN_COUNT = 2
33
-	MIN_BAN_COUNT = 2
34
-	MIN_MESSAGE_LENGTH = 0
35
-	MIN_TIME_SPAN = 1
36
-
37
 	def __init__(self, bot):
59
 	def __init__(self, bot):
38
 		super().__init__(bot)
60
 		super().__init__(bot)
61
+		self.add_setting(CrossPostCog.SETTING_WARN_COUNT)
62
+		self.add_setting(CrossPostCog.SETTING_BAN_COUNT)
63
+		self.add_setting(CrossPostCog.SETTING_MIN_LENGTH)
64
+		self.add_setting(CrossPostCog.SETTING_TIMESPAN)
39
 		self.max_spam_contexts = 12
65
 		self.max_spam_contexts = 12
40
 
66
 
41
 	# Config
67
 	# Config
42
 
68
 
43
-	def __warn_count(self, guild: Guild) -> int:
44
-		return Storage.get_config_value(guild, self.CONFIG_KEY_WARN_COUNT) or \
45
-			self.get_cog_default('warn_message_count')
46
-
47
-	def __ban_count(self, guild: Guild) -> int:
48
-		return Storage.get_config_value(guild, self.CONFIG_KEY_BAN_COUNT) or \
49
-			self.get_cog_default('ban_message_count')
50
-
51
-	def __min_message_length(self, guild: Guild) -> int:
52
-		return Storage.get_config_value(guild, self.CONFIG_KEY_MIN_MESSAGE_LENGTH) or \
53
-			self.get_cog_default('min_message_length')
54
-
55
-	def __message_age_seconds(self, guild: Guild) -> int:
56
-		return Storage.get_config_value(guild, self.CONFIG_KEY_MESSAGE_AGE) or \
57
-			self.get_cog_default('time_window_seconds')
58
-
59
 	async def __record_message(self, message: Message) -> None:
69
 	async def __record_message(self, message: Message) -> None:
60
 		if message.author.permissions_in(message.channel).ban_members:
70
 		if message.author.permissions_in(message.channel).ban_members:
61
 			# User exempt from spam detection
71
 			# User exempt from spam detection
62
 			return
72
 			return
63
-		if len(message.content) < self.__min_message_length(message.guild):
73
+		if len(message.content) < self.get_guild_setting(message.guild, self.SETTING_MIN_LENGTH):
64
 			# Message too short to count towards spam total
74
 			# Message too short to count towards spam total
65
 			return
75
 			return
66
-		max_age = timedelta(seconds=self.__message_age_seconds(message.guild))
76
+		max_age = timedelta(seconds=self.get_guild_setting(message.guild, self.SETTING_TIMESPAN))
67
 		recent_messages = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES) \
77
 		recent_messages = Storage.get_state_value(message.guild, self.STATE_KEY_RECENT_MESSAGES) \
68
 			or AgeBoundList(max_age, lambda index, message : message.created_at)
78
 			or AgeBoundList(max_age, lambda index, message : message.created_at)
69
 		recent_messages.max_age = max_age
79
 		recent_messages.max_age = max_age
72
 
82
 
73
 		# Get all recent messages by user
83
 		# Get all recent messages by user
74
 		member_messages = [m for m in recent_messages if m.author.id == message.author.id]
84
 		member_messages = [m for m in recent_messages if m.author.id == message.author.id]
75
-		if len(member_messages) < self.__warn_count(message.guild):
85
+		if len(member_messages) < self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT):
76
 			return
86
 			return
77
 
87
 
78
 		# Look for repeats
88
 		# Look for repeats
83
 			count = (hash_to_count.get(key) or 0) + 1
93
 			count = (hash_to_count.get(key) or 0) + 1
84
 			hash_to_count[key] = count
94
 			hash_to_count[key] = count
85
 			max_count = max(max_count, count)
95
 			max_count = max(max_count, count)
86
-		if max_count < self.__warn_count(message.guild):
96
+		if max_count < self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT):
87
 			return
97
 			return
88
 
98
 
89
 		# Handle the spam
99
 		# Handle the spam
91
 			or SizeBoundDict(self.max_spam_contexts, lambda key, context : context.age)
101
 			or SizeBoundDict(self.max_spam_contexts, lambda key, context : context.age)
92
 		Storage.set_state_value(message.guild, self.STATE_KEY_SPAM_CONTEXT, spam_lookup)
102
 		Storage.set_state_value(message.guild, self.STATE_KEY_SPAM_CONTEXT, spam_lookup)
93
 		for message_hash, count in hash_to_count.items():
103
 		for message_hash, count in hash_to_count.items():
94
-			if count < self.__warn_count(message.guild):
104
+			if count < self.get_guild_setting(message.guild, self.SETTING_WARN_COUNT):
95
 				continue
105
 				continue
96
 			key = f'{message.author.id}|{message_hash}'
106
 			key = f'{message.author.id}|{message_hash}'
97
 			context = spam_lookup.get(key)
107
 			context = spam_lookup.get(key)
106
 			await self.__update_from_context(context)
116
 			await self.__update_from_context(context)
107
 
117
 
108
 	async def __update_from_context(self, context: SpamContext):
118
 	async def __update_from_context(self, context: SpamContext):
109
-		if len(context.spam_messages) >= self.__ban_count(context.member.guild):
119
+		if len(context.spam_messages) >= self.get_guild_setting(context.member.guild, self.SETTING_BAN_COUNT):
110
 			if not context.is_banned:
120
 			if not context.is_banned:
111
 				count = len(context.spam_messages)
121
 				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)
122
 				await context.member.ban(reason=f'Autobanned by Rocketbot for posting same message {count} times', delete_message_days=1)
205
 		'Command group'
215
 		'Command group'
206
 		if context.invoked_subcommand is None:
216
 		if context.invoked_subcommand is None:
207
 			await context.send_help()
217
 			await context.send_help()
208
-
209
-	@crosspost.command(
210
-		name='setwarncount',
211
-		brief='Sets the number of duplicate messages to trigger a mod warning',
212
-		description='If the same user posts the exact same message ' +
213
-			'content this many times a warning will be posted to the mods,' +
214
-			'even if the messages are posted in different channels.',
215
-		usage='<warn_count:int>',
216
-	)
217
-	async def joinraid_setwarncount(self, context: commands.Context,
218
-			warn_count: int):
219
-		if not await self.validate_param(context, 'warn_count', warn_count,
220
-			allowed_types=(int, ), min_value=self.MIN_WARN_COUNT):
221
-			return
222
-		Storage.set_config_value(context.guild, self.CONFIG_KEY_WARN_COUNT, warn_count)
223
-		await context.message.reply(
224
-			CONFIG['success_emoji'] + ' ' +
225
-			'Mods will be warned if a user posts the exact same message ' +
226
-			f'{warn_count} or more times within ' +
227
-			f'{self.__message_age_seconds(context.guild)} seconds.',
228
-			mention_author=False)
229
-
230
-	@crosspost.command(
231
-		name='getwarncount',
232
-		brief='Returns the number of duplicate messages to trigger a mod warning',
233
-	)
234
-	async def joinraid_getwarncount(self, context: commands.Context):
235
-		await context.message.reply(f'ℹ️ Mods will be warned if a user posts ' +
236
-			f'the exact same message {self.__warn_count(context.guild)} or more ' +
237
-			f'times within {self.__message_age_seconds(context.guild)} seconds.',
238
-			mention_author=False)
239
-
240
-	@crosspost.command(
241
-		name='setbancount',
242
-		brief='Sets the number of duplicate messages to trigger an automatic ban',
243
-		description='If the same user posts the exact same message ' +
244
-			'content this many times they will be automatically banned and the ' +
245
-			'mods will be alerted.',
246
-		usage='<ban_count:int>',
247
-	)
248
-	async def joinraid_setbancount(self, context: commands.Context,
249
-			ban_count: int):
250
-		if not await self.validate_param(context, 'ban_count', ban_count,
251
-			allowed_types=(int, ), min_value=self.MIN_BAN_COUNT):
252
-			return
253
-		Storage.set_config_value(context.guild, self.CONFIG_KEY_BAN_COUNT, ban_count)
254
-		await context.message.reply(
255
-			CONFIG['success_emoji'] + ' ' +
256
-			'Users will be banned if they post the exact same message ' +
257
-			f'{ban_count} or more times within ' +
258
-			f'{self.__message_age_seconds(context.guild)} seconds.',
259
-			mention_author=False)
260
-
261
-	@crosspost.command(
262
-		name='getbancount',
263
-		brief='Returns the number of duplicate messages to trigger an automatic ban',
264
-	)
265
-	async def joinraid_getbancount(self, context: commands.Context):
266
-		await context.message.reply(
267
-			CONFIG['info_emoji'] + ' ' +
268
-			'Users will be banned if they post the exact same message ' +
269
-			f'{self.__ban_count(context.guild)} or more times within ' +
270
-			f'{self.__message_age_seconds(context.guild)} seconds.',
271
-			mention_author=False)
272
-
273
-	@crosspost.command(
274
-		name='setminlength',
275
-		brief='Sets the minimum number of characters for a message to count toward spamming',
276
-		description='Messages shorter than this number of characters will not ' +
277
-			'count toward spam counts. This helps prevent flagging common, ' +
278
-			'frequent, short responses like "lol". A value of 0 counts all messages.',
279
-		usage='<min_length:int>',
280
-	)
281
-	async def joinraid_setminlength(self, context: commands.Context,
282
-			min_length: int):
283
-		if not await self.validate_param(context, 'min_length', min_length,
284
-			allowed_types=(int, ), min_value=self.MIN_MESSAGE_LENGTH):
285
-			return
286
-		Storage.set_config_value(context.guild, self.CONFIG_KEY_MIN_MESSAGE_LENGTH, min_length)
287
-		if min_length == 0:
288
-			await context.message.reply(
289
-				CONFIG['success_emoji'] + ' ' +
290
-				f'All messages will count against spam counts, regardless ' +
291
-				'of length.', mention_author=False)
292
-		else:
293
-			await context.message.reply(
294
-				CONFIG['success_emoji'] + ' ' +
295
-				f'Only messages {min_length} characters or longer will ' +
296
-				'count against spam counts.',
297
-				mention_author=False)
298
-
299
-	@crosspost.command(
300
-		name='getminlength',
301
-		brief='Returns the number of duplicate messages to trigger an automatic ban',
302
-	)
303
-	async def joinraid_getminlength(self, context: commands.Context):
304
-		min_length = self.__min_message_length(context.guild)
305
-		if min_length == 0:
306
-			await context.message.reply(
307
-				CONFIG['info_emoji'] + ' ' +
308
-				f'All messages will count against spam counts, regardless ' +
309
-				'of length.', mention_author=False)
310
-		else:
311
-			await context.message.reply(
312
-				CONFIG['info_emoji'] + ' ' +
313
-				f'Only messages {min_length} characters or longer will ' +
314
-				'count against spam counts.',
315
-				mention_author=False)
316
-
317
-	@crosspost.command(
318
-		name='settimewindow',
319
-		brief='Sets the length of time recent messages are checked for duplicates',
320
-		description='Repeated messages are only checked against recent ' +
321
-			'messages. This sets the length of that window, in seconds. Lower ' +
322
-			'values save memory and prevent false positives.',
323
-		usage='<seconds:int>',
324
-	)
325
-	async def joinraid_settimewindow(self, context: commands.Context,
326
-			seconds: int):
327
-		if not await self.validate_param(context, 'seconds', seconds,
328
-			allowed_types=(int, ), min_value=self.MIN_TIME_SPAN):
329
-			return
330
-		Storage.set_config_value(context.guild, self.CONFIG_KEY_MESSAGE_AGE, seconds)
331
-		await context.message.reply(
332
-			CONFIG['success_emoji'] + ' ' +
333
-			f'Only messages in the past {seconds} seconds will be checked ' +
334
-			'for duplicates.',
335
-			mention_author=False)
336
-
337
-	@crosspost.command(
338
-		name='gettimewindow',
339
-		brief='Returns the length of time recent messages are checked for duplicates',
340
-	)
341
-	async def joinraid_gettimewindow(self, context: commands.Context):
342
-		seconds = self.__message_age_seconds(context.guild)
343
-		await context.message.reply(
344
-			CONFIG['info_emoji'] + ' ' +
345
-			f'Only messages in the past {seconds} seconds will be checked ' +
346
-			'for duplicates.',
347
-			mention_author=False)

+ 7
- 4
cogs/generalcog.py Datei anzeigen

1
 from discord.ext import commands
1
 from discord.ext import commands
2
-from cogs.basecog import BaseCog
2
+from cogs.basecog import BaseCog, BotMessage
3
 from storage import ConfigKey, Storage
3
 from storage import ConfigKey, Storage
4
 
4
 
5
 class GeneralCog(BaseCog):
5
 class GeneralCog(BaseCog):
26
 	async def testwarn(self, context):
26
 	async def testwarn(self, context):
27
 		if Storage.get_config_value(context.guild, ConfigKey.WARNING_CHANNEL_ID) is None:
27
 		if Storage.get_config_value(context.guild, ConfigKey.WARNING_CHANNEL_ID) is None:
28
 			await context.message.reply(
28
 			await context.message.reply(
29
-				'No warning channel set!',
29
+				f'{CONFIG["warning_emoji"]} No warning channel set!',
30
 				mention_author=False)
30
 				mention_author=False)
31
 		else:
31
 		else:
32
-			await self.warn(context.guild,
33
-				f'Test warning message (requested by {context.author.name})')
32
+			bm = BotMessage(
33
+				context.guild,
34
+				f'Test warning message (requested by {context.author.name})',
35
+				type=BotMessage.TYPE_MOD_WARNING)
36
+			await self.post_message(bm)
34
 
37
 
35
 	@commands.command(
38
 	@commands.command(
36
 		brief='Simple test reply',
39
 		brief='Simple test reply',

+ 52
- 0
cogs/patterncog.py Datei anzeigen

110
 					m.quote = message.content
110
 					m.quote = message.content
111
 					await self.post_message(m)
111
 					await self.post_message(m)
112
 				break
112
 				break
113
+
114
+	"""
115
+	Expression language samples:
116
+
117
+		content contains "poop"
118
+		content contains "poop" and content contains "tinkle"
119
+		joinage < 600s
120
+		(content contains "this" and content contains "that") or content contains "whatever"
121
+
122
+		<field> <op> <value>
123
+
124
+		Fields:	
125
+			content
126
+			author.id
127
+			author.name
128
+			author.joinage
129
+
130
+		Ops:
131
+			==
132
+			!=
133
+			<
134
+			>
135
+			<=
136
+			>=
137
+			contains, !contains  -- plain strings
138
+			matches, !matches  -- regexes
139
+
140
+		Value types:
141
+			timedelta  (600, 600s, 10m, 5m30s)
142
+			number
143
+			string
144
+			regex
145
+			mention
146
+	"""
147
+
148
+	@commands.group(
149
+		brief='Manages message pattern matching',
150
+	)
151
+	@commands.has_permissions(ban_members=True)
152
+	@commands.guild_only()
153
+	async def patterns(self, context: commands.Context):
154
+		'Message pattern matching'
155
+		if context.invoked_subcommand is None:
156
+			await context.send_help()
157
+
158
+	@patterns.command()
159
+	async def addpattern(self, context: commands.Context, name: str, expression: str, *args):
160
+		print(f'Pattern name: {name}')
161
+		tokens = []
162
+		tokens.append(expression)
163
+		tokens += args
164
+		print('Expression: ' + (' '.join(tokens)))

+ 6
- 6
config.py.sample Datei anzeigen

12
 	'info_emoji': 'ℹ️',
12
 	'info_emoji': 'ℹ️',
13
 	'config_path': 'config/',
13
 	'config_path': 'config/',
14
 	'cog_defaults': {
14
 	'cog_defaults': {
15
+		'CrossPostCog': {
16
+			'warncount': 3,
17
+			'bancount': 9999,
18
+			'minlength': 0,
19
+			'timespan': 60,
20
+		},
15
 		'JoinRaidCog': {
21
 		'JoinRaidCog': {
16
 			'enabled': False,
22
 			'enabled': False,
17
 			'warning_count': 5,
23
 			'warning_count': 5,
18
 			'warning_seconds': 5,
24
 			'warning_seconds': 5,
19
 		},
25
 		},
20
-		'CrossPostCog': {
21
-			'warn_message_count': 3,
22
-			'ban_message_count': 9999,
23
-			'time_window_seconds': 120,
24
-			'min_message_length': 0,
25
-		},
26
 		'URLSpamCog': {
26
 		'URLSpamCog': {
27
 			'joinage': 900,  # Should be > 600 due to Discord-imposed waiting period
27
 			'joinage': 900,  # Should be > 600 due to Discord-imposed waiting period
28
 			'action': 'nothing',  # "nothing" | "modwarn" | "delete" | "kick" | "ban"
28
 			'action': 'nothing',  # "nothing" | "modwarn" | "delete" | "kick" | "ban"

Laden…
Abbrechen
Speichern