Ver código fonte

Adding lastmatched so patterns can have cooldowns

master
Rocketsoup 4 anos atrás
pai
commit
b4f5ebcc42
3 arquivos alterados com 49 adições e 23 exclusões
  1. 7
    6
      patterns.md
  2. 21
    1
      rocketbot/cogs/patterncog.py
  3. 21
    16
      rocketbot/pattern.py

+ 7
- 6
patterns.md Ver arquivo

@@ -51,12 +51,13 @@ The available operators and type of value depends on the field being accessed.
51 51
 
52 52
 #### Fields
53 53
 
54
-* `content.plain` - The plain text of the message. All markdown formatting is removed, and mentions look like the `@Username` text name that gets displayed.
55
-* `content.markdown` - The raw markdown of the message. This contains all markdown characters, and mentions are of the `<@!0000000>` form. Available operators: `==`, `!=`, `contains`, `!contains`, `matches`, `!matches`. Comparison value must be a quoted string.
56 54
 * `author` - Who sent the message. Available operators: `==`, `!=`. Comparison value must be a user mention (an @ that Discord will tab-complete for you).
57 55
 * `author.id` - The numeric ID of the user who sent the message. Available operators: `==`, `!=`. Comparison value must be a numeric user ID.
58
-* `author.name` - The username of the author. Available operators: `==`, `!=`, `contains`, `!contains`, `containsword`, `!containsword`, `matches`, `!matches`. Comparison value must be a quoted string.
59 56
 * `author.joinage` - How much time has elapsed from when the author joined and when the message was sent. If the user has joined and left multiple times this is the most recent join time. Available operators: `==`, `!=`, `<`, `>`, `<=`, `>=`. Comparison value must be a timespan (see below)
57
+* `author.name` - The username of the author. Available operators: `==`, `!=`, `contains`, `!contains`, `containsword`, `!containsword`, `matches`, `!matches`. Comparison value must be a quoted string.
58
+* `content.plain` - The plain text of the message. All markdown formatting is removed, and mentions look like the `@Username` text name that gets displayed.
59
+* `content.markdown` - The raw markdown of the message. This contains all markdown characters, and mentions are of the `<@!0000000>` form. Available operators: `==`, `!=`, `contains`, `!contains`, `matches`, `!matches`. Comparison value must be a quoted string.
60
+* `lastmatched` - How long ago this pattern matched a message. Used for imposing a cooldown, especially for autoresponses. If the pattern has never matched it will be treated as if it last ran 100 years ago for comparison purposes. Available operators: `==`, `!=`, `<`, `>`, `<=`, `>=`. Comparison value must be a timespan (see below).
60 61
 
61 62
 #### Operators
62 63
 
@@ -139,7 +140,7 @@ $rb_pattern add "lunch" reply "Lunch is at noon." if content.plain == "When is l
139 140
 
140 141
 `<actions>` ::= `<action>` | `<action>` " " `<actions>`
141 142
 
142
-`<action>` ::= "ban" | "delete" | "kick" | "modwarn" | "reply " `<quoted_string>`
143
+`<action>` ::= "ban" | "delete" | "kick" | "modinfo" | "modwarn" | "reply " `<quoted_string>`
143 144
 
144 145
 `<quoted_string>` ::= '"' `<any>` '"'
145 146
 
@@ -157,9 +158,9 @@ $rb_pattern add "lunch" reply "Lunch is at noon." if content.plain == "When is l
157 158
 
158 159
 `<or_expr>` ::= `<expression>` " or " `<expression>`
159 160
 
160
-`<field_name>` ::= "content.plain" | "content.markdown" | "author" | "author.id" | "author.name" | "author.joinage"
161
+`<field_name>` ::= "author" | "author.id" | "author.joinage" | "author.name" | "content.markdown" | "content.plain" | "lastmatched"
161 162
 
162
-`<op>` ::= "==" | "!=" | "<" | ">" | "<=" | ">=" | "contains" | "!contains" | "matches" | "!matches"
163
+`<op>` ::= "==" | "!=" | "<" | ">" | "<=" | ">=" | "contains" | "!contains" | "containsword" | "!containsword" | "matches" | "!matches"
163 164
 
164 165
 `<value>` ::= `<int>` | `<float>` | `<quoted_string>` | `<timespan>` | `<mention>`
165 166
 

+ 21
- 1
rocketbot/cogs/patterncog.py Ver arquivo

@@ -2,6 +2,7 @@
2 2
 Cog for matching messages against guild-configurable criteria and taking
3 3
 automated actions on them.
4 4
 """
5
+from datetime import datetime
5 6
 from discord import Guild, Member, Message, utils as discordutils
6 7
 from discord.ext import commands
7 8
 
@@ -62,6 +63,21 @@ class PatternCog(BaseCog, name='Pattern Matching'):
62 63
 		to_save: list[dict] = list(map(PatternStatement.to_json, patterns.values()))
63 64
 		cls.set_guild_setting(guild, cls.SETTING_PATTERNS, to_save)
64 65
 
66
+	@classmethod
67
+	def __get_last_matched(cls, guild: Guild, name: str) -> datetime:
68
+		last_matched: dict[name, datetime] = Storage.get_state_value(guild, 'PatternCog.last_matched')
69
+		if last_matched:
70
+			return last_matched.get(name)
71
+		return None
72
+
73
+	@classmethod
74
+	def __set_last_matched(cls, guild: Guild, name: str, time: datetime) -> None:
75
+		last_matched: dict[name, datetime] = Storage.get_state_value(guild, 'PatternCog.last_matched')
76
+		if last_matched is None:
77
+			last_matched = {}
78
+			Storage.set_state_value(guild, 'PatternCog.last_matched', last_matched)
79
+		last_matched[name] = time
80
+
65 81
 	@commands.Cog.listener()
66 82
 	async def on_message(self, message: Message) -> None:
67 83
 		'Event listener'
@@ -78,7 +94,11 @@ class PatternCog(BaseCog, name='Pattern Matching'):
78 94
 
79 95
 		patterns = self.__get_patterns(message.guild)
80 96
 		for statement in sorted(patterns.values(), key=lambda s : s.priority, reverse=True):
81
-			if statement.expression.matches(message):
97
+			other_fields = {
98
+				'last_matched': self.__get_last_matched(message.guild, statement.name),
99
+			}
100
+			if statement.expression.matches(message, other_fields):
101
+				self.__set_last_matched(message.guild, statement.name, message.created_at)
82 102
 				await self.__trigger_actions(message, statement)
83 103
 				break
84 104
 

+ 21
- 16
rocketbot/pattern.py Ver arquivo

@@ -4,6 +4,7 @@ to take on them.
4 4
 """
5 5
 import re
6 6
 from abc import ABCMeta, abstractmethod
7
+from datetime import datetime
7 8
 from typing import Any
8 9
 
9 10
 from discord import Message, utils as discordutils
@@ -42,9 +43,10 @@ class PatternExpression(metaclass=ABCMeta):
42 43
 		pass
43 44
 
44 45
 	@abstractmethod
45
-	def matches(self, message: Message) -> bool:
46
+	def matches(self, message: Message, other_fields: dict[str: Any]) -> bool:
46 47
 		"""
47
-		Whether a message matches this expression.
48
+		Whether a message matches this expression. other_fields are additional
49
+		fields that can be queried not contained in the message itself.
48 50
 		"""
49 51
 		return False
50 52
 
@@ -59,7 +61,7 @@ class PatternSimpleExpression(PatternExpression):
59 61
 		self.operator = operator
60 62
 		self.value = value
61 63
 
62
-	def __field_value(self, message: Message) -> Any:
64
+	def __field_value(self, message: Message, other_fields: dict[str: Any]) -> Any:
63 65
 		if self.field in ('content.markdown', 'content'):
64 66
 			return message.content
65 67
 		if self.field == 'content.plain':
@@ -72,11 +74,14 @@ class PatternSimpleExpression(PatternExpression):
72 74
 			return message.created_at - message.author.joined_at
73 75
 		if self.field == 'author.name':
74 76
 			return message.author.name
75
-		else:
76
-			raise ValueError(f'Bad field name {self.field}')
77
-
78
-	def matches(self, message: Message) -> bool:
79
-		field_value = self.__field_value(message)
77
+		if self.field == 'lastmatched':
78
+			long_ago = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0)
79
+			last_matched = other_fields.get('last_matched') or long_ago
80
+			return message.created_at - last_matched
81
+		raise ValueError(f'Bad field name {self.field}')
82
+
83
+	def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:
84
+		field_value = self.__field_value(message, other_fields)
80 85
 		if self.operator == '==':
81 86
 			if isinstance(field_value, str) and isinstance(self.value, str):
82 87
 				return field_value.lower() == self.value.lower()
@@ -116,17 +121,17 @@ class PatternCompoundExpression(PatternExpression):
116 121
 		self.operator = operator
117 122
 		self.operands = list(operands)
118 123
 
119
-	def matches(self, message: Message) -> bool:
124
+	def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:
120 125
 		if self.operator == '!':
121
-			return not self.operands[0].matches(message)
126
+			return not self.operands[0].matches(message, other_fields)
122 127
 		if self.operator == 'and':
123 128
 			for op in self.operands:
124
-				if not op.matches(message):
129
+				if not op.matches(message, other_fields):
125 130
 					return False
126 131
 			return True
127 132
 		if self.operator == 'or':
128 133
 			for op in self.operands:
129
-				if op.matches(message):
134
+				if op.matches(message, other_fields):
130 135
 					return True
131 136
 			return False
132 137
 		raise ValueError(f'Bad operator "{self.operator}"')
@@ -208,14 +213,14 @@ class PatternCompiler:
208 213
 	TYPE_TIMESPAN = 'timespan'
209 214
 
210 215
 	FIELD_TO_TYPE: dict[str, str] = {
211
-		'content.plain': TYPE_TEXT,
212
-		'content.markdown': TYPE_TEXT,
213 216
 		'author': TYPE_MEMBER,
214 217
 		'author.id': TYPE_ID,
215
-		'author.name': TYPE_TEXT,
216 218
 		'author.joinage': TYPE_TIMESPAN,
217
-
219
+		'author.name': TYPE_TEXT,
218 220
 		'content': TYPE_TEXT, # deprecated, use content.markdown or content.plain
221
+		'content.markdown': TYPE_TEXT,
222
+		'content.plain': TYPE_TEXT,
223
+		'lastmatched': TYPE_TIMESPAN,
219 224
 	}
220 225
 	DEPRECATED_FIELDS: set[str] = set([ 'content' ])
221 226
 

Carregando…
Cancelar
Salvar