Kaynağa Gözat

Reorganizing files

master
Rocketsoup 4 yıl önce
ebeveyn
işleme
4c0899dde1

+ 1
- 1
README.md Dosyayı Görüntüle

@@ -23,7 +23,7 @@ config.py's "client_token" attribute.
23 23
 
24 24
 Create a "config" subdirectory under your source folder. This is where guild-specific configuration is written as JSON files.
25 25
 
26
-To start, run `python3 rocketbot.py`. Then visit
26
+To start, run `python3 bot.py`. Then visit
27 27
 https://discord.com/oauth2/authorize?client_id=[application_id]&scope=bot&permissions=395204357318,
28 28
 where [application_id] is the "application id" value on your app configuration
29 29
 "general information" page. Once invited, test if the bot is working by typing

rocketbot.py → bot.py Dosyayı Görüntüle

@@ -10,12 +10,12 @@ from discord import Intents
10 10
 from discord.ext import commands
11 11
 
12 12
 from config import CONFIG
13
-from cogs.configcog import ConfigCog
14
-from cogs.crosspostcog import CrossPostCog
15
-from cogs.generalcog import GeneralCog
16
-from cogs.joinraidcog import JoinRaidCog
17
-from cogs.patterncog import PatternCog
18
-from cogs.urlspamcog import URLSpamCog
13
+from rocketbot.cogs.configcog import ConfigCog
14
+from rocketbot.cogs.crosspostcog import CrossPostCog
15
+from rocketbot.cogs.generalcog import GeneralCog
16
+from rocketbot.cogs.joinraidcog import JoinRaidCog
17
+from rocketbot.cogs.patterncog import PatternCog
18
+from rocketbot.cogs.urlspamcog import URLSpamCog
19 19
 
20 20
 CURRENT_CONFIG_VERSION = 3
21 21
 if (CONFIG.get('__config_version') or 0) < CURRENT_CONFIG_VERSION:

cogs/__init__.py → rocketbot/__init__.py Dosyayı Görüntüle


+ 0
- 0
rocketbot/cogs/__init__.py Dosyayı Görüntüle


cogs/basecog.py → rocketbot/cogs/basecog.py Dosyayı Görüntüle

@@ -1,11 +1,14 @@
1
+"""
2
+Base cog class and helper classes.
3
+"""
1 4
 from datetime import datetime, timedelta
2 5
 from discord import Guild, Member, Message, PartialEmoji, RawReactionActionEvent, TextChannel
3 6
 from discord.abc import GuildChannel
4 7
 from discord.ext import commands
5 8
 
6 9
 from config import CONFIG
7
-from rscollections import AgeBoundDict
8
-from storage import ConfigKey, Storage
10
+from rocketbot.collections import AgeBoundDict
11
+from rocketbot.storage import ConfigKey, Storage
9 12
 
10 13
 class BotMessageReaction:
11 14
 	"""
@@ -111,7 +114,7 @@ class BotMessage:
111 114
 	def __init__(self,
112 115
 			guild: Guild,
113 116
 			text: str,
114
-			type: int = 0, # TYPE_DEFAULT
117
+			type: int = TYPE_DEFAULT,
115 118
 			context = None,
116 119
 			reply_to: Message = None):
117 120
 		self.guild = guild
@@ -143,6 +146,7 @@ class BotMessage:
143 146
 		return self.__message.created_at if self.__message else None
144 147
 
145 148
 	def has_reactions(self) -> bool:
149
+		'Whether this message has any reactions defined.'
146 150
 		return len(self.__reactions) > 0
147 151
 
148 152
 	async def set_text(self, new_text: str) -> None:
@@ -329,7 +333,7 @@ class CogSetting:
329 333
 		self.name = name
330 334
 		self.datatype = datatype
331 335
 		self.brief = brief
332
-		self.description = description or ''  # XXX: Can't be None
336
+		self.description = description or ''  # Can't be None
333 337
 		self.usage = usage
334 338
 		self.min_value = min_value
335 339
 		self.max_value = max_value
@@ -508,7 +512,7 @@ class BaseCog(commands.Cog):
508 512
 				commands.guild_only(),
509 513
 			])
510 514
 
511
-		# XXX: Passing `cog` in init gets ignored and set to `None` so set after.
515
+		# Passing `cog` in init gets ignored and set to `None` so set after.
512 516
 		# This ensures the callback is passed `self`.
513 517
 		get_command.cog = self
514 518
 		set_command.cog = self

cogs/configcog.py → rocketbot/cogs/configcog.py Dosyayı Görüntüle

@@ -1,9 +1,12 @@
1
+"""
2
+Cog handling general configuration for a guild.
3
+"""
1 4
 from discord import Guild, TextChannel
2 5
 from discord.ext import commands
3 6
 
4 7
 from config import CONFIG
5
-from storage import ConfigKey, Storage
6
-from cogs.basecog import BaseCog
8
+from rocketbot.storage import ConfigKey, Storage
9
+from rocketbot.cogs.basecog import BaseCog
7 10
 
8 11
 class ConfigCog(BaseCog, name='Configuration'):
9 12
 	"""

cogs/crosspostcog.py → rocketbot/cogs/crosspostcog.py Dosyayı Görüntüle

@@ -1,11 +1,14 @@
1
+"""
2
+Cog for detecting spam messages posted in multiple channels.
3
+"""
1 4
 from datetime import datetime, timedelta
2 5
 from discord import Member, Message
3 6
 from discord.ext import commands
4 7
 
5
-from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
6 8
 from config import CONFIG
7
-from rscollections import AgeBoundList, SizeBoundDict
8
-from storage import Storage
9
+from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
10
+from rocketbot.collections import AgeBoundList, SizeBoundDict
11
+from rocketbot.storage import Storage
9 12
 
10 13
 class SpamContext:
11 14
 	"""

cogs/generalcog.py → rocketbot/cogs/generalcog.py Dosyayı Görüntüle

@@ -1,12 +1,16 @@
1
+"""
2
+Cog for handling most ungrouped commands and basic behaviors.
3
+"""
1 4
 import re
2 5
 from datetime import datetime, timedelta
3 6
 from discord import Message
7
+from discord.errors import DiscordException
4 8
 from discord.ext import commands
5 9
 
6
-from cogs.basecog import BaseCog, BotMessage
7 10
 from config import CONFIG
8
-from rbutils import parse_timedelta, describe_timedelta
9
-from storage import ConfigKey, Storage
11
+from rocketbot.cogs.basecog import BaseCog, BotMessage
12
+from rocketbot.utils import parse_timedelta, describe_timedelta
13
+from rocketbot.storage import ConfigKey, Storage
10 14
 
11 15
 class GeneralCog(BaseCog, name='General'):
12 16
 	"""
@@ -106,7 +110,7 @@ class GeneralCog(BaseCog, name='General'):
106 110
 		for channel in context.guild.text_channels:
107 111
 			try:
108 112
 				deleted_messages += await channel.purge(limit=100, check=predicate)
109
-			except:
113
+			except DiscordException:
110 114
 				# XXX: Sloppily glossing over access errors instead of checking access
111 115
 				pass
112 116
 		await context.message.reply(

cogs/joinraidcog.py → rocketbot/cogs/joinraidcog.py Dosyayı Görüntüle

@@ -1,12 +1,15 @@
1
+"""
2
+Cog for detecting large numbers of guild joins in a short period of time.
3
+"""
1 4
 import weakref
2 5
 from datetime import datetime, timedelta
3 6
 from discord import Guild, Member
4 7
 from discord.ext import commands
5 8
 
6
-from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
7 9
 from config import CONFIG
8
-from rscollections import AgeBoundList
9
-from storage import Storage
10
+from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
11
+from rocketbot.collections import AgeBoundList
12
+from rocketbot.storage import Storage
10 13
 
11 14
 class JoinRaidContext:
12 15
 	"""

cogs/patterncog.py → rocketbot/cogs/patterncog.py Dosyayı Görüntüle

@@ -1,12 +1,16 @@
1
+"""
2
+Cog for matching messages against guild-configurable criteria and taking
3
+automated actions on them.
4
+"""
1 5
 import re
2
-from abc import ABC, abstractmethod
6
+from abc import ABCMeta, abstractmethod
3 7
 from discord import Guild, Member, Message
4 8
 from discord.ext import commands
5 9
 
6
-from cogs.basecog import BaseCog, BotMessage, BotMessageReaction
7 10
 from config import CONFIG
8
-from rbutils import parse_timedelta
9
-from storage import Storage
11
+from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction
12
+from rocketbot.storage import Storage
13
+from rocketbot.utils import parse_timedelta
10 14
 
11 15
 class PatternAction:
12 16
 	"""
@@ -20,7 +24,7 @@ class PatternAction:
20 24
 		arg_str = ', '.join(self.arguments)
21 25
 		return f'{self.action}({arg_str})'
22 26
 
23
-class PatternExpression(ABC):
27
+class PatternExpression(metaclass=ABCMeta):
24 28
 	"""
25 29
 	Abstract message matching expression.
26 30
 	"""
@@ -165,8 +169,9 @@ class PatternCog(BaseCog, name='Pattern Matching'):
165 169
 					try:
166 170
 						ps = PatternCompiler.parse_statement(name, statement)
167 171
 						patterns[name] = ps
168
-					except Exception as e:
169
-						self.log(guild, f'Error parsing saved statement "{name}". Skipping: {statement}. Error: {e}')
172
+					except PatternError as e:
173
+						self.log(guild, 'Error parsing saved statement ' + \
174
+							f'"{name}": "{e}" Statement: {statement}')
170 175
 			Storage.set_state_value(guild, 'PatternCog.patterns', patterns)
171 176
 		return patterns
172 177
 
@@ -302,7 +307,7 @@ class PatternCog(BaseCog, name='Pattern Matching'):
302 307
 			await context.message.reply(
303 308
 				f'{CONFIG["success_emoji"]} Pattern `{name}` added.',
304 309
 				mention_author=False)
305
-		except Exception as e:
310
+		except PatternError as e:
306 311
 			await context.message.reply(
307 312
 				f'{CONFIG["failure_emoji"]} Error parsing statement. {e}',
308 313
 				mention_author=False)
@@ -339,6 +344,11 @@ class PatternCog(BaseCog, name='Pattern Matching'):
339 344
 			msg += f'Pattern `{name}`:\n```\n{statement.original}\n```\n'
340 345
 		await context.message.reply(msg, mention_author=False)
341 346
 
347
+class PatternError(RuntimeError):
348
+	"""
349
+	Error thrown when parsing a pattern statement.
350
+	"""
351
+
342 352
 class PatternCompiler:
343 353
 	"""
344 354
 	Parses a user-provided message filter statement into a PatternStatement.
@@ -457,7 +467,7 @@ class PatternCompiler:
457 467
 					in_quote = ch
458 468
 					current_token = ch
459 469
 				elif ch == '\\':
460
-					raise RuntimeError("Unexpected \\")
470
+					raise PatternError("Unexpected \\ outside quoted string")
461 471
 				elif ch in cls.WHITESPACE_CHARS:
462 472
 					if len(current_token) > 0:
463 473
 						tokens.append(current_token)
@@ -528,7 +538,7 @@ class PatternCompiler:
528 538
 				return (actions, token_index)
529 539
 			elif token == ',':
530 540
 				if len(current_action_tokens) < 1:
531
-					raise RuntimeError('Unexpected ,')
541
+					raise PatternError('Unexpected ,')
532 542
 				a = PatternAction(current_action_tokens[0], current_action_tokens[1:])
533 543
 				cls.__validate_action(a)
534 544
 				actions.append(a)
@@ -536,19 +546,19 @@ class PatternCompiler:
536 546
 			else:
537 547
 				current_action_tokens.append(token)
538 548
 			token_index += 1
539
-		raise RuntimeError('Unexpected end of line')
549
+		raise PatternError('Unexpected end of line in action list')
540 550
 
541 551
 	@classmethod
542 552
 	def __validate_action(cls, action: PatternAction) -> None:
543 553
 		args = cls.ACTION_TO_ARGS.get(action.action)
544 554
 		if args is None:
545
-			raise RuntimeError(f'Unknown action "{action.action}"')
555
+			raise PatternError(f'Unknown action "{action.action}"')
546 556
 		if len(action.arguments) != len(args):
547 557
 			if len(args) == 0:
548
-				raise RuntimeError(f'Action "{action.action}" expects no arguments, ' + \
558
+				raise PatternError(f'Action "{action.action}" expects no arguments, ' + \
549 559
 					f'got {len(action.arguments)}.')
550 560
 			else:
551
-				raise RuntimeError(f'Action "{action.action}" expects {len(args)} ' + \
561
+				raise PatternError(f'Action "{action.action}" expects {len(args)} ' + \
552 562
 					f'arguments, got {len(action.arguments)}.')
553 563
 		for i, datatype in enumerate(args):
554 564
 			action.arguments[i] = cls.parse_value(action.arguments[i], datatype)
@@ -573,11 +583,11 @@ class PatternCompiler:
573 583
 				if len(subexpressions) == 1:
574 584
 					return (subexpressions[0], token_index)
575 585
 				if len(subexpressions) > 1:
576
-					raise RuntimeError('Too many subexpressions')
586
+					raise PatternError('Too many subexpressions')
577 587
 			compound_operator = None
578 588
 			if tokens[token_index] == ')':
579 589
 				if len(subexpressions) == 0:
580
-					raise RuntimeError('No subexpressions')
590
+					raise PatternError('No subexpressions')
581 591
 				if len(subexpressions) == 1:
582 592
 					return (subexpressions[0], token_index)
583 593
 				return (PatternCompoundExpression(last_compound_operator, subexpressions), token_index)
@@ -597,7 +607,7 @@ class PatternCompiler:
597 607
 			elif tokens[token_index] == '(':
598 608
 				(exp, next_index) = cls.read_expression(tokens, token_index + 1, depth + 1)
599 609
 				if tokens[next_index] != ')':
600
-					raise RuntimeError('Expected )')
610
+					raise PatternError('Expected )')
601 611
 				subexpressions.append(exp)
602 612
 				token_index = next_index + 1
603 613
 			else:
@@ -605,7 +615,7 @@ class PatternCompiler:
605 615
 				subexpressions.append(simple)
606 616
 				token_index = next_index
607 617
 		if len(subexpressions) == 0:
608
-			raise RuntimeError('No subexpressions')
618
+			raise PatternError('No subexpressions')
609 619
 		elif len(subexpressions) == 1:
610 620
 			return (subexpressions[0], token_index)
611 621
 		else:
@@ -619,39 +629,41 @@ class PatternCompiler:
619 629
 		the token index it left off at.
620 630
 		"""
621 631
 		if depth > 8:
622
-			raise RuntimeError('Expression nests too deeply')
632
+			raise PatternError('Expression nests too deeply')
623 633
 		if token_index >= len(tokens):
624
-			raise RuntimeError('Expected field name, found EOL')
634
+			raise PatternError('Expected field name, found EOL')
625 635
 		field = tokens[token_index]
626 636
 		token_index += 1
627 637
 
628 638
 		datatype = cls.FIELD_TO_TYPE.get(field)
629 639
 		if datatype is None:
630
-			raise RuntimeError(f'No such field "{field}"')
640
+			raise PatternError(f'No such field "{field}"')
631 641
 
632 642
 		if token_index >= len(tokens):
633
-			raise RuntimeError('Expected operator, found EOL')
643
+			raise PatternError('Expected operator, found EOL')
634 644
 		op = tokens[token_index]
635 645
 		token_index += 1
636 646
 
637 647
 		if op == '!':
638 648
 			if token_index >= len(tokens):
639
-				raise RuntimeError('Expected operator, found EOL')
649
+				raise PatternError('Expected operator, found EOL')
640 650
 			op = '!' + tokens[token_index]
641 651
 			token_index += 1
642 652
 
643 653
 		allowed_ops = cls.TYPE_TO_OPERATORS[datatype]
644 654
 		if op not in allowed_ops:
645 655
 			if op in cls.OPERATORS_ALL:
646
-				raise RuntimeError(f'Operator {op} cannot be used with field "{field}"')
647
-			else:
648
-				raise RuntimeError(f'Unrecognized operator "{op}" - allowed: {list(allowed_ops)}')
656
+				raise PatternError(f'Operator {op} cannot be used with field "{field}"')
657
+			raise PatternError(f'Unrecognized operator "{op}" - allowed: {list(allowed_ops)}')
649 658
 
650 659
 		if token_index >= len(tokens):
651
-			raise RuntimeError('Expected value, found EOL')
660
+			raise PatternError('Expected value, found EOL')
652 661
 		value = tokens[token_index]
653 662
 
654
-		value = cls.parse_value(value, datatype)
663
+		try:
664
+			value = cls.parse_value(value, datatype)
665
+		except ValueError as cause:
666
+			raise PatternError(f'Bad value {value}') from cause
655 667
 
656 668
 		token_index += 1
657 669
 		exp = PatternSimpleExpression(field, op, value)
@@ -660,7 +672,7 @@ class PatternCompiler:
660 672
 	@classmethod
661 673
 	def parse_value(cls, value: str, datatype: str):
662 674
 		"""
663
-		Converts a value token to its Python value.
675
+		Converts a value token to its Python value. Raises ValueError on failure.
664 676
 		"""
665 677
 		if datatype == cls.TYPE_ID:
666 678
 			p = re.compile('^[0-9]+$')

cogs/urlspamcog.py → rocketbot/cogs/urlspamcog.py Dosyayı Görüntüle

@@ -1,11 +1,14 @@
1
+"""
2
+Cog for detecting URLs posted by new users.
3
+"""
1 4
 import re
2 5
 from datetime import timedelta
3 6
 from discord import Member, Message
4 7
 from discord.ext import commands
5 8
 
6
-from cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
7 9
 from config import CONFIG
8
-from rbutils import describe_timedelta
10
+from rocketbot.cogs.basecog import BaseCog, BotMessage, BotMessageReaction, CogSetting
11
+from rocketbot.utils import describe_timedelta
9 12
 
10 13
 class URLSpamContext:
11 14
 	"""

rscollections.py → rocketbot/collections.py Dosyayı Görüntüle


storage.py → rocketbot/storage.py Dosyayı Görüntüle


rbutils.py → rocketbot/utils.py Dosyayı Görüntüle

@@ -1,3 +1,6 @@
1
+"""
2
+General utility functions.
3
+"""
1 4
 import re
2 5
 from datetime import timedelta
3 6
 

Loading…
İptal
Kaydet