maxkit 2.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aiomax/router.py ADDED
@@ -0,0 +1,383 @@
1
+ import logging
2
+ from copy import deepcopy
3
+ from typing import Callable, Optional
4
+
5
+ from . import exceptions
6
+ from .filters import normalize_filter
7
+ from .types import CommandHandler, Handler, MessageHandler
8
+
9
+ bot_logger = logging.getLogger("aiomax.bot")
10
+
11
+
12
+ class Router:
13
+ def __init__(
14
+ self,
15
+ case_sensitive: bool = True,
16
+ ):
17
+ """
18
+ Router init
19
+ :param case_sensitive: If False the bot will respond to commands
20
+ regardless of case
21
+ """
22
+ self._handlers: dict[str, list[Handler]] = {
23
+ "message_created": [],
24
+ "on_ready": [],
25
+ "bot_started": [],
26
+ "message_callback": [],
27
+ "message_chat_created": [],
28
+ "message_edited": [],
29
+ "message_removed": [],
30
+ "chat_title_changed": [],
31
+ "bot_added": [],
32
+ "bot_removed": [],
33
+ "user_added": [],
34
+ "user_removed": [],
35
+ } # handlers in this router
36
+ self._commands: dict[
37
+ str, list[CommandHandler]
38
+ ] = {} # commands in this router
39
+ self.case_sensitive: bool = case_sensitive
40
+ self.parent: Router | None = None # Parent bot of this router
41
+ self.routers: list[Router] = []
42
+ self.filters: dict[str, list[Callable]] = {
43
+ "message_created": [],
44
+ "message_edited": [],
45
+ "message_removed": [],
46
+ "message_callback": [],
47
+ }
48
+
49
+ @staticmethod
50
+ def wrap_filters(
51
+ filters: tuple["Callable | str | None", ...], mode: str = "and"
52
+ ) -> Callable:
53
+ """
54
+ Normalize multiple filters into a single callable.
55
+
56
+ :param filters: filters to combine
57
+ :param mode: "and" (default) — all filters must pass,
58
+ "or" — at least one filter must pass.
59
+ """
60
+ normalized_filters = []
61
+
62
+ for filter_ in filters:
63
+ if filter_ is None:
64
+ continue
65
+ normalized_filters.append(normalize_filter(filter_))
66
+
67
+ if not normalized_filters:
68
+ return lambda message: True
69
+
70
+ if mode == "and":
71
+
72
+ def combined_filter(message):
73
+ return all(f(message) for f in normalized_filters)
74
+ elif mode == "or":
75
+
76
+ def combined_filter(message):
77
+ return any(f(message) for f in normalized_filters)
78
+ else:
79
+ raise ValueError(f"Unsupported mode: {mode}. Use 'and' or 'or'.")
80
+
81
+ return combined_filter
82
+
83
+ # routers
84
+
85
+ @property
86
+ def handlers(self) -> dict[str, list[Handler]]:
87
+ """
88
+ Returns all handlers in this and all the child routers.
89
+ """
90
+ out = deepcopy(self._handlers)
91
+
92
+ for router in self.routers:
93
+ for handler_type in out:
94
+ out[handler_type].extend(router.handlers[handler_type])
95
+
96
+ return out
97
+
98
+ @property
99
+ def commands(self) -> dict[str, list[CommandHandler]]:
100
+ """
101
+ Returns all commands in this and all the child routers.
102
+ """
103
+ out = deepcopy(self._commands)
104
+ for router in self.routers:
105
+ # Merge (not overwrite): a command name defined in more than one
106
+ # router must keep every handler, mirroring the handlers property.
107
+ for name, handlers in router.commands.items():
108
+ out.setdefault(name, []).extend(handlers)
109
+ return out
110
+
111
+ @property
112
+ def bot(self):
113
+ """
114
+ Returns the bot this router is attached to.
115
+ """
116
+ # Walk up to the root so nesting deeper than two levels resolves to
117
+ # the actual Bot instead of an intermediate Router.
118
+ node = self
119
+ while node.parent is not None:
120
+ node = node.parent
121
+ return node if node is not self else None
122
+
123
+ def add_router(self, router: "Router"):
124
+ if router.parent is not None:
125
+ raise ValueError("Router already has a parent")
126
+
127
+ router.parent = self
128
+ self.routers.append(router)
129
+
130
+ def remove_router(self, router: "Router"):
131
+ if router not in self.routers:
132
+ raise ValueError("Router not found")
133
+
134
+ router.parent = None
135
+ self.routers.remove(router)
136
+
137
+ # decorators
138
+
139
+ def on_message(
140
+ self,
141
+ *filters: "Callable | str | None",
142
+ mode: str = "and",
143
+ detect_commands: bool = False,
144
+ ):
145
+ """
146
+ Decorator for receiving messages.
147
+ """
148
+
149
+ def decorator(func):
150
+ new_filter = self.wrap_filters(filters, mode=mode)
151
+
152
+ self._handlers["message_created"].append(
153
+ MessageHandler(
154
+ call=func,
155
+ deco_filter=new_filter,
156
+ router_filters=self.filters["message_created"],
157
+ detect_commands=detect_commands,
158
+ )
159
+ )
160
+ return func
161
+
162
+ return decorator
163
+
164
+ def on_message_edit(
165
+ self, *filters: "Callable | str | None", mode: str = "and"
166
+ ):
167
+ """
168
+ Decorator for editing messages.
169
+ """
170
+
171
+ def decorator(func):
172
+ new_filter = self.wrap_filters(filters, mode=mode)
173
+
174
+ self._handlers["message_edited"].append(
175
+ Handler(
176
+ call=func,
177
+ deco_filter=new_filter,
178
+ router_filters=self.filters["message_edited"],
179
+ )
180
+ )
181
+ return func
182
+
183
+ return decorator
184
+
185
+ def on_message_delete(
186
+ self, *filters: "Callable | str | None", mode: str = "and"
187
+ ):
188
+ """
189
+ Decorator for deleted messages.
190
+ """
191
+
192
+ def decorator(func):
193
+ new_filter = self.wrap_filters(filters, mode=mode)
194
+
195
+ self._handlers["message_removed"].append(
196
+ Handler(
197
+ call=func,
198
+ deco_filter=new_filter,
199
+ router_filters=self.filters["message_removed"],
200
+ )
201
+ )
202
+ return func
203
+
204
+ return decorator
205
+
206
+ def on_bot_start(self):
207
+ """
208
+ Decorator for handling bot start.
209
+ """
210
+
211
+ def decorator(func):
212
+ self._handlers["bot_started"].append(func)
213
+ return func
214
+
215
+ return decorator
216
+
217
+ def on_chat_title_change(self):
218
+ """
219
+ Decorator for handling chat title changes.
220
+ """
221
+
222
+ def decorator(func):
223
+ self._handlers["chat_title_changed"].append(func)
224
+ return func
225
+
226
+ return decorator
227
+
228
+ def on_bot_add(self):
229
+ """
230
+ Decorator for handling bot invitations in groups.
231
+ """
232
+
233
+ def decorator(func):
234
+ self._handlers["bot_added"].append(func)
235
+ return func
236
+
237
+ return decorator
238
+
239
+ def on_bot_remove(self):
240
+ """
241
+ Decorator for handling bot kicks from groups.
242
+ """
243
+
244
+ def decorator(func):
245
+ self._handlers["bot_removed"].append(func)
246
+ return func
247
+
248
+ return decorator
249
+
250
+ def on_user_add(self):
251
+ """
252
+ Decorator for handling user joins.
253
+ """
254
+
255
+ def decorator(func):
256
+ self._handlers["user_added"].append(func)
257
+ return func
258
+
259
+ return decorator
260
+
261
+ def on_user_remove(self):
262
+ """
263
+ Decorator for handling user leaves.
264
+ """
265
+
266
+ def decorator(func):
267
+ self._handlers["user_removed"].append(func)
268
+ return func
269
+
270
+ return decorator
271
+
272
+ def on_ready(self):
273
+ """
274
+ Decorator for receiving messages.
275
+ """
276
+
277
+ def decorator(func):
278
+ self._handlers["on_ready"].append(func)
279
+ return func
280
+
281
+ return decorator
282
+
283
+ def on_button_callback(
284
+ self, *filters: "Callable | str | None", mode: str = "and"
285
+ ):
286
+ """
287
+ Decorator for receiving button presses.
288
+ """
289
+
290
+ def decorator(func):
291
+ new_filter = self.wrap_filters(filters, mode=mode)
292
+
293
+ self._handlers["message_callback"].append(
294
+ Handler(
295
+ call=func,
296
+ deco_filter=new_filter,
297
+ router_filters=self.filters["message_callback"],
298
+ )
299
+ )
300
+ return func
301
+
302
+ return decorator
303
+
304
+ def on_button_chat_create(self):
305
+ """
306
+ Decorator for receiving button presses.
307
+ """
308
+
309
+ def decorator(func):
310
+ self._handlers["message_chat_created"].append(func)
311
+ return func
312
+
313
+ return decorator
314
+
315
+ def on_command(
316
+ self,
317
+ name: "str | None" = None,
318
+ aliases: Optional[list[str]] = None,
319
+ as_message: bool = False,
320
+ ):
321
+ """
322
+ Decorator for receiving commands.
323
+
324
+ :param name: Command name
325
+ :param aliases: List of alternative names for this command
326
+ :param as_message: Whether to trigger on_message decorator
327
+ when this command is invoked
328
+ """
329
+
330
+ if aliases is None:
331
+ aliases = []
332
+
333
+ def decorator(func):
334
+ # command name
335
+ if name is None:
336
+ command_name = func.__name__
337
+ else:
338
+ if " " in name:
339
+ raise exceptions.AiomaxException(
340
+ f'Command name "{name}" cannot contain spaces'
341
+ )
342
+
343
+ command_name = name
344
+
345
+ check_name = (
346
+ command_name.lower()
347
+ if not self.case_sensitive
348
+ else command_name
349
+ )
350
+ if check_name not in self._commands:
351
+ self._commands[check_name] = []
352
+ self._commands[check_name].append(CommandHandler(func, as_message))
353
+
354
+ # aliases
355
+ for i in aliases:
356
+ if " " in i:
357
+ raise exceptions.AiomaxException(
358
+ f'Command alias "{i}" cannot contain spaces'
359
+ )
360
+
361
+ check_name = i.lower() if not self.case_sensitive else i
362
+ if check_name not in self._commands:
363
+ self._commands[check_name] = []
364
+ self._commands[check_name].append(
365
+ CommandHandler(func, as_message)
366
+ )
367
+ return func
368
+
369
+ return decorator
370
+
371
+ # filters
372
+
373
+ def add_message_filter(self, filter: "Callable"):
374
+ self.filters["message_created"].append(filter)
375
+
376
+ def add_message_edit_filter(self, filter: "Callable"):
377
+ self.filters["message_edited"].append(filter)
378
+
379
+ def add_message_delete_filter(self, filter: "Callable"):
380
+ self.filters["message_removed"].append(filter)
381
+
382
+ def add_button_callback_filter(self, filter: "Callable"):
383
+ self.filters["message_callback"].append(filter)
@@ -0,0 +1,33 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
3
+ PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
4
+ ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
5
+ Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
6
+ VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
7
+ YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
8
+ dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
9
+ qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
10
+ XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
11
+ zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
12
+ YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
13
+ Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
14
+ U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
15
+ 4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
16
+ G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
17
+ BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
18
+ ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
19
+ OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
20
+ BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
21
+ BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
22
+ AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
23
+ tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
24
+ W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
25
+ /3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
26
+ AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
27
+ C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
28
+ 4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
29
+ WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
30
+ D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
31
+ EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
32
+ 391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
33
+ -----END CERTIFICATE-----