|
|
@@ -5,7 +5,7 @@ to take on them.
|
|
5
|
5
|
import re
|
|
6
|
6
|
from abc import ABCMeta, abstractmethod
|
|
7
|
7
|
from datetime import datetime, timezone
|
|
8
|
|
-from typing import Any, Union
|
|
|
8
|
+from typing import Any, Union, Literal
|
|
9
|
9
|
|
|
10
|
10
|
from discord import Message, utils as discordutils
|
|
11
|
11
|
from discord.ext.commands import Context
|
|
|
@@ -13,6 +13,11 @@ from discord.ext.commands import Context
|
|
13
|
13
|
from rocketbot.utils import is_user_id, str_from_quoted_str, timedelta_from_str, \
|
|
14
|
14
|
user_id_from_mention
|
|
15
|
15
|
|
|
|
16
|
+PatternField = Literal['content.markdown', 'content', 'content.plain', 'author', 'author.id', 'author.joinage', 'author.name', 'lastmatched']
|
|
|
17
|
+PatternComparisonOperator = Literal['==', '!=', '<', '>', '<=', '>=', 'contains', '!contains', 'matches', '!matches', 'containsword', '!containsword']
|
|
|
18
|
+PatternBooleanOperator = Literal['!', 'and', 'or']
|
|
|
19
|
+PatternActionType = Literal['ban', 'delete', 'kick', 'modinfo', 'modwarn', 'reply']
|
|
|
20
|
+
|
|
16
|
21
|
class PatternError(RuntimeError):
|
|
17
|
22
|
"""
|
|
18
|
23
|
Error thrown when parsing a pattern statement.
|
|
|
@@ -27,6 +32,14 @@ class PatternAction:
|
|
27
|
32
|
"""
|
|
28
|
33
|
Describes one action to take on a matched message or its author.
|
|
29
|
34
|
"""
|
|
|
35
|
+
|
|
|
36
|
+ TYPE_BAN: PatternActionType = 'ban'
|
|
|
37
|
+ TYPE_DELETE: PatternActionType = 'delete'
|
|
|
38
|
+ TYPE_KICK: PatternActionType = 'kick'
|
|
|
39
|
+ TYPE_INFORM_MODS: PatternActionType = 'modinfo'
|
|
|
40
|
+ TYPE_WARN_MODS: PatternActionType = 'modwarn'
|
|
|
41
|
+ TYPE_REPLY: PatternActionType = 'reply'
|
|
|
42
|
+
|
|
30
|
43
|
def __init__(self, action: str, args: list[Any]):
|
|
31
|
44
|
self.action = action
|
|
32
|
45
|
self.arguments = list(args)
|
|
|
@@ -55,56 +68,81 @@ class PatternSimpleExpression(PatternExpression):
|
|
55
|
68
|
Message matching expression with a simple "<field> <operator> <value>"
|
|
56
|
69
|
structure.
|
|
57
|
70
|
"""
|
|
58
|
|
- def __init__(self, field: str, operator: str, value: Any):
|
|
|
71
|
+
|
|
|
72
|
+ FIELD_CONTENT_MARKDOWN: PatternField = 'content.markdown'
|
|
|
73
|
+ FIELD_CONTENT_PLAIN: PatternField = 'content.plain'
|
|
|
74
|
+ FIELD_AUTHOR_ID: PatternField = 'author.id'
|
|
|
75
|
+ FIELD_AUTHOR_JOINAGE: PatternField = 'author.joinage'
|
|
|
76
|
+ FIELD_AUTHOR_NAME: PatternField = 'author.name'
|
|
|
77
|
+ FIELD_LAST_MATCHED: PatternField = 'lastmatched'
|
|
|
78
|
+
|
|
|
79
|
+ # Less preferred but recognized field aliases
|
|
|
80
|
+ ALIAS_FIELD_CONTENT_MARKDOWN: PatternField = 'content'
|
|
|
81
|
+ ALIAS_FIELD_AUTHOR_ID: PatternField = 'author'
|
|
|
82
|
+
|
|
|
83
|
+ OP_EQUALS: PatternComparisonOperator = '=='
|
|
|
84
|
+ OP_NOT_EQUALS: PatternComparisonOperator = '!='
|
|
|
85
|
+ OP_LESS_THAN: PatternComparisonOperator = '<'
|
|
|
86
|
+ OP_GREATER_THAN: PatternComparisonOperator = '>'
|
|
|
87
|
+ OP_LESS_THAN_OR_EQUALS: PatternComparisonOperator = '<='
|
|
|
88
|
+ OP_GREATER_THAN_OR_EQUALS: PatternComparisonOperator = '>='
|
|
|
89
|
+ OP_CONTAINS: PatternComparisonOperator = 'contains'
|
|
|
90
|
+ OP_NOT_CONTAINS: PatternComparisonOperator = '!contains'
|
|
|
91
|
+ OP_MATCHES: PatternComparisonOperator = 'matches'
|
|
|
92
|
+ OP_NOT_MATCHES: PatternComparisonOperator = '!matches'
|
|
|
93
|
+ OP_CONTAINS_WORD: PatternComparisonOperator = 'containsword'
|
|
|
94
|
+ OP_NOT_CONTAINS_WORD: PatternComparisonOperator = '!containsword'
|
|
|
95
|
+
|
|
|
96
|
+ def __init__(self, field: PatternField, operator: PatternComparisonOperator, value: Any):
|
|
59
|
97
|
super().__init__()
|
|
60
|
|
- self.field: str = field
|
|
61
|
|
- self.operator: str = operator
|
|
|
98
|
+ self.field: PatternField = field
|
|
|
99
|
+ self.operator: PatternComparisonOperator = operator
|
|
62
|
100
|
self.value: Any = value
|
|
63
|
101
|
|
|
64
|
102
|
def __field_value(self, message: Message, other_fields: dict[str, Any]) -> Any:
|
|
65
|
|
- if self.field in ('content.markdown', 'content'):
|
|
|
103
|
+ cls = PatternSimpleExpression
|
|
|
104
|
+ if self.field in (cls.FIELD_CONTENT_MARKDOWN, cls.ALIAS_FIELD_CONTENT_MARKDOWN):
|
|
66
|
105
|
return message.content
|
|
67
|
|
- if self.field == 'content.plain':
|
|
|
106
|
+ if self.field == cls.FIELD_CONTENT_PLAIN:
|
|
68
|
107
|
return discordutils.remove_markdown(message.clean_content)
|
|
69
|
|
- if self.field == 'author':
|
|
|
108
|
+ if self.field in (cls.FIELD_AUTHOR_ID, cls.ALIAS_FIELD_AUTHOR_ID):
|
|
70
|
109
|
return str(message.author.id)
|
|
71
|
|
- if self.field == 'author.id':
|
|
72
|
|
- return str(message.author.id)
|
|
73
|
|
- if self.field == 'author.joinage':
|
|
|
110
|
+ if self.field == cls.FIELD_AUTHOR_JOINAGE:
|
|
74
|
111
|
return message.created_at - message.author.joined_at
|
|
75
|
|
- if self.field == 'author.name':
|
|
|
112
|
+ if self.field == cls.FIELD_AUTHOR_NAME:
|
|
76
|
113
|
return message.author.name
|
|
77
|
|
- if self.field == 'lastmatched':
|
|
|
114
|
+ if self.field == cls.FIELD_LAST_MATCHED:
|
|
78
|
115
|
long_ago = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=timezone.utc)
|
|
79
|
116
|
last_matched = other_fields.get('last_matched') or long_ago
|
|
80
|
117
|
return message.created_at - last_matched
|
|
81
|
|
- raise ValueError(f'Bad field name {self.field}')
|
|
|
118
|
+ raise ValueError(f'Bad field name "{self.field}"')
|
|
82
|
119
|
|
|
83
|
120
|
def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:
|
|
|
121
|
+ cls = PatternSimpleExpression
|
|
84
|
122
|
field_value = self.__field_value(message, other_fields)
|
|
85
|
|
- if self.operator == '==':
|
|
|
123
|
+ if self.operator == cls.OP_EQUALS:
|
|
86
|
124
|
if isinstance(field_value, str) and isinstance(self.value, str):
|
|
87
|
125
|
return field_value.lower() == self.value.lower()
|
|
88
|
126
|
return field_value == self.value
|
|
89
|
|
- if self.operator == '!=':
|
|
|
127
|
+ if self.operator == cls.OP_NOT_EQUALS:
|
|
90
|
128
|
if isinstance(field_value, str) and isinstance(self.value, str):
|
|
91
|
129
|
return field_value.lower() != self.value.lower()
|
|
92
|
130
|
return field_value != self.value
|
|
93
|
|
- if self.operator == '<':
|
|
|
131
|
+ if self.operator == cls.OP_LESS_THAN:
|
|
94
|
132
|
return field_value < self.value
|
|
95
|
|
- if self.operator == '>':
|
|
|
133
|
+ if self.operator == cls.OP_GREATER_THAN:
|
|
96
|
134
|
return field_value > self.value
|
|
97
|
|
- if self.operator == '<=':
|
|
|
135
|
+ if self.operator == cls.OP_LESS_THAN_OR_EQUALS:
|
|
98
|
136
|
return field_value <= self.value
|
|
99
|
|
- if self.operator == '>=':
|
|
|
137
|
+ if self.operator == cls.OP_GREATER_THAN_OR_EQUALS:
|
|
100
|
138
|
return field_value >= self.value
|
|
101
|
|
- if self.operator == 'contains':
|
|
|
139
|
+ if self.operator == cls.OP_CONTAINS:
|
|
102
|
140
|
return self.value.lower() in field_value.lower()
|
|
103
|
|
- if self.operator == '!contains':
|
|
|
141
|
+ if self.operator == cls.OP_NOT_CONTAINS:
|
|
104
|
142
|
return self.value.lower() not in field_value.lower()
|
|
105
|
|
- if self.operator in ('matches', 'containsword'):
|
|
|
143
|
+ if self.operator in (cls.OP_MATCHES, cls.OP_CONTAINS_WORD):
|
|
106
|
144
|
return self.value.search(field_value.lower()) is not None
|
|
107
|
|
- if self.operator in ('!matches', '!containsword'):
|
|
|
145
|
+ if self.operator in (cls.OP_NOT_MATCHES, cls.OP_NOT_CONTAINS_WORD):
|
|
108
|
146
|
return self.value.search(field_value.lower()) is None
|
|
109
|
147
|
raise ValueError(f'Bad operator {self.operator}')
|
|
110
|
148
|
|
|
|
@@ -116,20 +154,24 @@ class PatternCompoundExpression(PatternExpression):
|
|
116
|
154
|
Message matching expression that combines several child expressions with
|
|
117
|
155
|
a boolean operator.
|
|
118
|
156
|
"""
|
|
119
|
|
- def __init__(self, operator: str, operands: list[PatternExpression]):
|
|
|
157
|
+ OP_NOT = '!'
|
|
|
158
|
+ OP_AND = 'and'
|
|
|
159
|
+ OP_OR = 'or'
|
|
|
160
|
+
|
|
|
161
|
+ def __init__(self, operator: PatternBooleanOperator, operands: list[PatternExpression]):
|
|
120
|
162
|
super().__init__()
|
|
121
|
|
- self.operator = operator
|
|
|
163
|
+ self.operator: PatternBooleanOperator = operator
|
|
122
|
164
|
self.operands = list(operands)
|
|
123
|
165
|
|
|
124
|
166
|
def matches(self, message: Message, other_fields: dict[str, Any]) -> bool:
|
|
125
|
|
- if self.operator == '!':
|
|
|
167
|
+ if self.operator == PatternCompoundExpression.OP_NOT:
|
|
126
|
168
|
return not self.operands[0].matches(message, other_fields)
|
|
127
|
|
- if self.operator == 'and':
|
|
|
169
|
+ if self.operator == PatternCompoundExpression.OP_AND:
|
|
128
|
170
|
for op in self.operands:
|
|
129
|
171
|
if not op.matches(message, other_fields):
|
|
130
|
172
|
return False
|
|
131
|
173
|
return True
|
|
132
|
|
- if self.operator == 'or':
|
|
|
174
|
+ if self.operator == PatternCompoundExpression.OP_OR:
|
|
133
|
175
|
for op in self.operands:
|
|
134
|
176
|
if op.matches(message, other_fields):
|
|
135
|
177
|
return True
|
|
|
@@ -137,7 +179,7 @@ class PatternCompoundExpression(PatternExpression):
|
|
137
|
179
|
raise ValueError(f'Bad operator "{self.operator}"')
|
|
138
|
180
|
|
|
139
|
181
|
def __str__(self) -> str:
|
|
140
|
|
- if self.operator == '!':
|
|
|
182
|
+ if self.operator == PatternCompoundExpression.OP_NOT:
|
|
141
|
183
|
return f'(!( {self.operands[0]} ))'
|
|
142
|
184
|
strs = map(str, self.operands)
|
|
143
|
185
|
joined = f' {self.operator} '.join(strs)
|
|
|
@@ -204,52 +246,63 @@ class PatternCompiler:
|
|
204
|
246
|
"""
|
|
205
|
247
|
Parses a user-provided message filter statement into a PatternStatement.
|
|
206
|
248
|
"""
|
|
207
|
|
- TYPE_FLOAT: str = 'float'
|
|
208
|
|
- TYPE_ID: str = 'id'
|
|
209
|
|
- TYPE_INT: str = 'int'
|
|
210
|
|
- TYPE_MEMBER: str = 'Member'
|
|
211
|
|
- TYPE_REGEX: str = 'regex'
|
|
212
|
|
- TYPE_TEXT: str = 'text'
|
|
213
|
|
- TYPE_TIMESPAN: str = 'timespan'
|
|
214
|
|
-
|
|
215
|
|
- FIELD_TO_TYPE: dict[str, str] = {
|
|
216
|
|
- 'author': TYPE_MEMBER,
|
|
217
|
|
- 'author.id': TYPE_ID,
|
|
218
|
|
- 'author.joinage': TYPE_TIMESPAN,
|
|
219
|
|
- 'author.name': TYPE_TEXT,
|
|
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,
|
|
|
249
|
+ DATATYPE_FLOAT: str = 'float'
|
|
|
250
|
+ DATATYPE_ID: str = 'id'
|
|
|
251
|
+ DATATYPE_INT: str = 'int'
|
|
|
252
|
+ DATATYPE_MEMBER: str = 'Member'
|
|
|
253
|
+ DATATYPE_REGEX: str = 'regex'
|
|
|
254
|
+ DATATYPE_TEXT: str = 'text'
|
|
|
255
|
+ DATATYPE_TIMESPAN: str = 'timespan'
|
|
|
256
|
+
|
|
|
257
|
+ FIELD_TO_DATATYPE: dict[PatternField, str] = {
|
|
|
258
|
+ PatternSimpleExpression.ALIAS_FIELD_AUTHOR_ID: DATATYPE_MEMBER,
|
|
|
259
|
+ PatternSimpleExpression.FIELD_AUTHOR_ID: DATATYPE_ID,
|
|
|
260
|
+ PatternSimpleExpression.FIELD_AUTHOR_JOINAGE: DATATYPE_TIMESPAN,
|
|
|
261
|
+ PatternSimpleExpression.FIELD_AUTHOR_NAME: DATATYPE_TEXT,
|
|
|
262
|
+ PatternSimpleExpression.ALIAS_FIELD_CONTENT_MARKDOWN: DATATYPE_TEXT, # deprecated, use content.markdown or content.plain
|
|
|
263
|
+ PatternSimpleExpression.FIELD_CONTENT_MARKDOWN: DATATYPE_TEXT,
|
|
|
264
|
+ PatternSimpleExpression.FIELD_CONTENT_PLAIN: DATATYPE_TEXT,
|
|
|
265
|
+ PatternSimpleExpression.FIELD_LAST_MATCHED: DATATYPE_TIMESPAN,
|
|
224
|
266
|
}
|
|
225
|
|
- DEPRECATED_FIELDS: set[str] = { 'content' }
|
|
226
|
|
-
|
|
227
|
|
- ACTION_TO_ARGS: dict[str, list[str]] = {
|
|
228
|
|
- 'ban': [],
|
|
229
|
|
- 'delete': [],
|
|
230
|
|
- 'kick': [],
|
|
231
|
|
- 'modinfo': [],
|
|
232
|
|
- 'modwarn': [],
|
|
233
|
|
- 'reply': [ TYPE_TEXT ],
|
|
|
267
|
+ DEPRECATED_FIELDS: set[PatternField] = { 'content' }
|
|
|
268
|
+
|
|
|
269
|
+ ACTION_TO_ARGS: dict[PatternActionType, list[str]] = {
|
|
|
270
|
+ PatternAction.TYPE_BAN: [],
|
|
|
271
|
+ PatternAction.TYPE_DELETE: [],
|
|
|
272
|
+ PatternAction.TYPE_KICK: [],
|
|
|
273
|
+ PatternAction.TYPE_INFORM_MODS: [],
|
|
|
274
|
+ PatternAction.TYPE_WARN_MODS: [],
|
|
|
275
|
+ PatternAction.TYPE_REPLY: [ DATATYPE_TEXT ],
|
|
234
|
276
|
}
|
|
235
|
277
|
|
|
236
|
|
- OPERATORS_IDENTITY: set[str] = { '==', '!=' }
|
|
237
|
|
- OPERATORS_COMPARISON: set[str] = { '<', '>', '<=', '>=' }
|
|
238
|
|
- OPERATORS_NUMERIC: set[str] = OPERATORS_IDENTITY | OPERATORS_COMPARISON
|
|
239
|
|
- OPERATORS_TEXT: set[str] = OPERATORS_IDENTITY | {
|
|
240
|
|
- 'contains', '!contains',
|
|
241
|
|
- 'containsword', '!containsword',
|
|
242
|
|
- 'matches', '!matches',
|
|
|
278
|
+ OPERATORS_IDENTITY: set[PatternComparisonOperator] = {
|
|
|
279
|
+ PatternSimpleExpression.OP_EQUALS,
|
|
|
280
|
+ PatternSimpleExpression.OP_NOT_EQUALS,
|
|
|
281
|
+ }
|
|
|
282
|
+ OPERATORS_COMPARISON: set[PatternComparisonOperator] = {
|
|
|
283
|
+ PatternSimpleExpression.OP_LESS_THAN,
|
|
|
284
|
+ PatternSimpleExpression.OP_GREATER_THAN,
|
|
|
285
|
+ PatternSimpleExpression.OP_LESS_THAN_OR_EQUALS,
|
|
|
286
|
+ PatternSimpleExpression.OP_GREATER_THAN_OR_EQUALS,
|
|
|
287
|
+ }
|
|
|
288
|
+ OPERATORS_NUMERIC: set[PatternComparisonOperator] = OPERATORS_IDENTITY | OPERATORS_COMPARISON
|
|
|
289
|
+ OPERATORS_TEXT: set[PatternComparisonOperator] = OPERATORS_IDENTITY | {
|
|
|
290
|
+ PatternSimpleExpression.OP_CONTAINS,
|
|
|
291
|
+ PatternSimpleExpression.OP_NOT_CONTAINS,
|
|
|
292
|
+ PatternSimpleExpression.OP_CONTAINS_WORD,
|
|
|
293
|
+ PatternSimpleExpression.OP_NOT_CONTAINS_WORD,
|
|
|
294
|
+ PatternSimpleExpression.OP_MATCHES,
|
|
|
295
|
+ PatternSimpleExpression.OP_NOT_MATCHES,
|
|
243
|
296
|
}
|
|
244
|
297
|
OPERATORS_ALL: set[str] = OPERATORS_IDENTITY | OPERATORS_COMPARISON | OPERATORS_TEXT
|
|
245
|
298
|
|
|
246
|
|
- TYPE_TO_OPERATORS: dict[str, set[str]] = {
|
|
247
|
|
- TYPE_ID: OPERATORS_IDENTITY,
|
|
248
|
|
- TYPE_MEMBER: OPERATORS_IDENTITY,
|
|
249
|
|
- TYPE_TEXT: OPERATORS_TEXT,
|
|
250
|
|
- TYPE_INT: OPERATORS_NUMERIC,
|
|
251
|
|
- TYPE_FLOAT: OPERATORS_NUMERIC,
|
|
252
|
|
- TYPE_TIMESPAN: OPERATORS_NUMERIC,
|
|
|
299
|
+ DATATYPE_TO_OPERATORS: dict[str, set[PatternComparisonOperator]] = {
|
|
|
300
|
+ DATATYPE_ID: OPERATORS_IDENTITY,
|
|
|
301
|
+ DATATYPE_MEMBER: OPERATORS_IDENTITY,
|
|
|
302
|
+ DATATYPE_TEXT: OPERATORS_TEXT,
|
|
|
303
|
+ DATATYPE_INT: OPERATORS_NUMERIC,
|
|
|
304
|
+ DATATYPE_FLOAT: OPERATORS_NUMERIC,
|
|
|
305
|
+ DATATYPE_TIMESPAN: OPERATORS_NUMERIC,
|
|
253
|
306
|
}
|
|
254
|
307
|
|
|
255
|
308
|
WHITESPACE_CHARS: str = ' \t\n\r'
|
|
|
@@ -458,7 +511,7 @@ class PatternCompiler:
|
|
458
|
511
|
return subexpressions[0], token_index
|
|
459
|
512
|
return (PatternCompoundExpression(last_compound_operator,
|
|
460
|
513
|
subexpressions), token_index)
|
|
461
|
|
- if tokens[token_index] in { "and", "or" }:
|
|
|
514
|
+ if tokens[token_index] in { PatternCompoundExpression.OP_AND, PatternCompoundExpression.OP_OR }:
|
|
462
|
515
|
compound_operator = tokens[token_index]
|
|
463
|
516
|
if last_compound_operator and \
|
|
464
|
517
|
compound_operator != last_compound_operator:
|
|
|
@@ -468,7 +521,7 @@ class PatternCompiler:
|
|
468
|
521
|
]
|
|
469
|
522
|
last_compound_operator = compound_operator
|
|
470
|
523
|
token_index += 1
|
|
471
|
|
- if tokens[token_index] == '!':
|
|
|
524
|
+ if tokens[token_index] == PatternCompoundExpression.OP_NOT:
|
|
472
|
525
|
(exp, next_index) = cls.__read_expression(tokens,
|
|
473
|
526
|
token_index + 1, depth + 1, one_subexpression=True)
|
|
474
|
527
|
subexpressions.append(PatternCompoundExpression('!', [exp]))
|
|
|
@@ -507,10 +560,10 @@ class PatternCompiler:
|
|
507
|
560
|
raise PatternError('Expression nests too deeply')
|
|
508
|
561
|
if token_index >= len(tokens):
|
|
509
|
562
|
raise PatternError('Expected field name, found EOL')
|
|
510
|
|
- field = tokens[token_index]
|
|
|
563
|
+ field: PatternField = tokens[token_index]
|
|
511
|
564
|
token_index += 1
|
|
512
|
565
|
|
|
513
|
|
- datatype = cls.FIELD_TO_TYPE.get(field)
|
|
|
566
|
+ datatype = cls.FIELD_TO_DATATYPE.get(field, None)
|
|
514
|
567
|
if datatype is None:
|
|
515
|
568
|
raise PatternError(f'No such field "{field}"')
|
|
516
|
569
|
|
|
|
@@ -519,13 +572,13 @@ class PatternCompiler:
|
|
519
|
572
|
op = tokens[token_index]
|
|
520
|
573
|
token_index += 1
|
|
521
|
574
|
|
|
522
|
|
- if op == '!':
|
|
|
575
|
+ if op == PatternCompoundExpression.OP_NOT:
|
|
523
|
576
|
if token_index >= len(tokens):
|
|
524
|
577
|
raise PatternError('Expected operator, found EOL')
|
|
525
|
578
|
op = '!' + tokens[token_index]
|
|
526
|
579
|
token_index += 1
|
|
527
|
580
|
|
|
528
|
|
- allowed_ops = cls.TYPE_TO_OPERATORS[datatype]
|
|
|
581
|
+ allowed_ops = cls.DATATYPE_TO_OPERATORS[datatype]
|
|
529
|
582
|
if op not in allowed_ops:
|
|
530
|
583
|
if op in cls.OPERATORS_ALL:
|
|
531
|
584
|
raise PatternError(f'Operator {op} cannot be used with ' + \
|
|
|
@@ -551,13 +604,13 @@ class PatternCompiler:
|
|
551
|
604
|
"""
|
|
552
|
605
|
Converts a value token to its Python value. Raises ValueError on failure.
|
|
553
|
606
|
"""
|
|
554
|
|
- if datatype == cls.TYPE_ID:
|
|
|
607
|
+ if datatype == cls.DATATYPE_ID:
|
|
555
|
608
|
if not is_user_id(value):
|
|
556
|
609
|
raise ValueError(f'Illegal user id value: {value}')
|
|
557
|
610
|
return value
|
|
558
|
|
- if datatype == cls.TYPE_MEMBER:
|
|
|
611
|
+ if datatype == cls.DATATYPE_MEMBER:
|
|
559
|
612
|
return user_id_from_mention(value)
|
|
560
|
|
- if datatype == cls.TYPE_TEXT:
|
|
|
613
|
+ if datatype == cls.DATATYPE_TEXT:
|
|
561
|
614
|
s = str_from_quoted_str(value)
|
|
562
|
615
|
if op in ('matches', '!matches'):
|
|
563
|
616
|
try:
|
|
|
@@ -570,10 +623,10 @@ class PatternCompiler:
|
|
570
|
623
|
except re.error as e:
|
|
571
|
624
|
raise ValueError(f'Invalid regex: {e}') from e
|
|
572
|
625
|
return s
|
|
573
|
|
- if datatype == cls.TYPE_INT:
|
|
|
626
|
+ if datatype == cls.DATATYPE_INT:
|
|
574
|
627
|
return int(value)
|
|
575
|
|
- if datatype == cls.TYPE_FLOAT:
|
|
|
628
|
+ if datatype == cls.DATATYPE_FLOAT:
|
|
576
|
629
|
return float(value)
|
|
577
|
|
- if datatype == cls.TYPE_TIMESPAN:
|
|
|
630
|
+ if datatype == cls.DATATYPE_TIMESPAN:
|
|
578
|
631
|
return timedelta_from_str(value)
|
|
579
|
632
|
raise ValueError(f'Unhandled datatype {datatype}')
|