maxapi-python 2.4.1__py3-none-any.whl → 2.5.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.
Files changed (74) hide show
  1. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +17 -9
  2. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/RECORD +74 -45
  3. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +5 -1
  5. pymax/_data/apk_fingerprints.json +272 -0
  6. pymax/api/auth/service.py +1 -0
  7. pymax/api/auth/types.py +3 -0
  8. pymax/api/bots/payloads.py +8 -0
  9. pymax/api/bots/service.py +13 -1
  10. pymax/api/chats/enums.py +1 -0
  11. pymax/api/chats/payloads.py +19 -0
  12. pymax/api/chats/service.py +34 -0
  13. pymax/api/facade.py +2 -0
  14. pymax/api/messages/enums.py +1 -0
  15. pymax/api/messages/payloads.py +47 -1
  16. pymax/api/messages/service.py +308 -24
  17. pymax/api/response.py +1 -1
  18. pymax/api/stories/__init__.py +2 -0
  19. pymax/api/stories/enums.py +19 -0
  20. pymax/api/stories/payloads.py +64 -0
  21. pymax/api/stories/service.py +198 -0
  22. pymax/api/uploads/payloads.py +10 -1
  23. pymax/api/uploads/service.py +94 -7
  24. pymax/app.py +16 -4
  25. pymax/auth/qr.py +11 -1
  26. pymax/base.py +70 -25
  27. pymax/client.py +0 -1
  28. pymax/config.py +38 -32
  29. pymax/di/__init__.py +2 -0
  30. pymax/di/context.py +18 -0
  31. pymax/di/resolver.py +38 -0
  32. pymax/dispatch/__init__.py +7 -0
  33. pymax/dispatch/dispatcher.py +143 -40
  34. pymax/dispatch/enums.py +1 -0
  35. pymax/dispatch/mapping.py +23 -3
  36. pymax/dispatch/middleware.py +32 -0
  37. pymax/dispatch/resolvers.py +4 -0
  38. pymax/dispatch/router.py +86 -34
  39. pymax/filters/__init__.py +15 -0
  40. pymax/filters/base.py +218 -0
  41. pymax/filters/containers/__init__.py +2 -0
  42. pymax/filters/containers/chat.py +240 -0
  43. pymax/filters/containers/message.py +147 -0
  44. pymax/filters/filters.py +211 -0
  45. pymax/filters/magic.py +13 -0
  46. pymax/filters/presets.py +88 -0
  47. pymax/filters/refs.py +56 -0
  48. pymax/formatting/markdown.py +55 -1
  49. pymax/fsm/__init__.py +3 -0
  50. pymax/fsm/context.py +30 -0
  51. pymax/fsm/protocol.py +14 -0
  52. pymax/fsm/state.py +70 -0
  53. pymax/fsm/storage.py +151 -0
  54. pymax/infra/base.py +2 -0
  55. pymax/infra/bots.py +20 -0
  56. pymax/infra/chat.py +28 -0
  57. pymax/infra/message.py +195 -6
  58. pymax/infra/stories.py +205 -0
  59. pymax/integrations/__init__.py +0 -0
  60. pymax/integrations/dishka.py +109 -0
  61. pymax/protocol/enums.py +2 -0
  62. pymax/protocol/tcp/payload.py +0 -2
  63. pymax/types/domain/__init__.py +23 -1
  64. pymax/types/domain/attachments/__init__.py +1 -1
  65. pymax/types/domain/attachments/keyboards/__init__.py +1 -1
  66. pymax/types/domain/attachments/keyboards/enums.py +12 -0
  67. pymax/types/domain/attachments/keyboards/inline.py +74 -3
  68. pymax/types/domain/callback.py +19 -0
  69. pymax/types/domain/comment.py +24 -0
  70. pymax/types/domain/message.py +91 -22
  71. pymax/types/domain/story.py +126 -0
  72. pymax/types/events/reaction.py +28 -4
  73. pymax/versions/catalog.py +1 -1
  74. {maxapi_python-2.4.1.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,240 @@
1
+ from __future__ import annotations
2
+
3
+ import builtins
4
+ from typing import TYPE_CHECKING, Any, TypeVar
5
+
6
+ from pymax.filters.base import ClientT, Filter, PathResolver
7
+ from pymax.filters.refs import FieldRef, NumberFieldRef, TextFieldRef
8
+
9
+ from .message import MessageContainer
10
+
11
+ if TYPE_CHECKING:
12
+ pass
13
+
14
+ EventT = TypeVar("EventT")
15
+
16
+
17
+ class ChatContainer(Filter[EventT, ClientT], PathResolver):
18
+ def __init__(
19
+ self,
20
+ expected_type: builtins.type[EventT],
21
+ path: list[str] | None = None,
22
+ ) -> None:
23
+ self.expected_type: type[EventT] = expected_type
24
+ self.path = path or []
25
+
26
+ @property
27
+ def id(self) -> NumberFieldRef:
28
+ return NumberFieldRef(
29
+ path=[*self.path, "id"],
30
+ expected_type=self.expected_type,
31
+ )
32
+
33
+ @property
34
+ def type(self) -> TextFieldRef:
35
+ return TextFieldRef(
36
+ path=[*self.path, "type"],
37
+ expected_type=self.expected_type,
38
+ )
39
+
40
+ @property
41
+ def status(self) -> TextFieldRef:
42
+ return TextFieldRef(
43
+ path=[*self.path, "status"],
44
+ expected_type=self.expected_type,
45
+ )
46
+
47
+ @property
48
+ def owner(self) -> NumberFieldRef:
49
+ return NumberFieldRef(
50
+ path=[*self.path, "owner"],
51
+ expected_type=self.expected_type,
52
+ )
53
+
54
+ @property
55
+ def participants(self) -> FieldRef:
56
+ return FieldRef(
57
+ path=[*self.path, "participants"],
58
+ expected_type=self.expected_type,
59
+ )
60
+
61
+ @property
62
+ def title(self) -> TextFieldRef:
63
+ return TextFieldRef(
64
+ path=[*self.path, "title"],
65
+ expected_type=self.expected_type,
66
+ )
67
+
68
+ @property
69
+ def base_raw_icon_url(self) -> TextFieldRef:
70
+ return TextFieldRef(
71
+ path=[*self.path, "base_raw_icon_url"],
72
+ expected_type=self.expected_type,
73
+ )
74
+
75
+ @property
76
+ def base_icon_url(self) -> TextFieldRef:
77
+ return TextFieldRef(
78
+ path=[*self.path, "base_icon_url"],
79
+ expected_type=self.expected_type,
80
+ )
81
+
82
+ @property
83
+ def last_message(self) -> MessageContainer:
84
+ return MessageContainer(
85
+ path=[*self.path, "last_message"],
86
+ expected_type=self.expected_type,
87
+ )
88
+
89
+ @property
90
+ def last_event_time(self) -> NumberFieldRef:
91
+ return NumberFieldRef(
92
+ path=[*self.path, "last_event_time"],
93
+ expected_type=self.expected_type,
94
+ )
95
+
96
+ @property
97
+ def last_delayed_update_time(self) -> NumberFieldRef:
98
+ return NumberFieldRef(
99
+ path=[*self.path, "last_delayed_update_time"],
100
+ expected_type=self.expected_type,
101
+ )
102
+
103
+ @property
104
+ def last_fire_delayed_error_time(self) -> NumberFieldRef:
105
+ return NumberFieldRef(
106
+ path=[*self.path, "last_fire_delayed_error_time"],
107
+ expected_type=self.expected_type,
108
+ )
109
+
110
+ @property
111
+ def created(self) -> NumberFieldRef:
112
+ return NumberFieldRef(
113
+ path=[*self.path, "created"],
114
+ expected_type=self.expected_type,
115
+ )
116
+
117
+ @property
118
+ def new_messages(self) -> NumberFieldRef:
119
+ return NumberFieldRef(
120
+ path=[*self.path, "new_messages"],
121
+ expected_type=self.expected_type,
122
+ )
123
+
124
+ @property
125
+ def link(self) -> TextFieldRef:
126
+ return TextFieldRef(
127
+ path=[*self.path, "link"],
128
+ expected_type=self.expected_type,
129
+ )
130
+
131
+ @property
132
+ def access(self) -> TextFieldRef:
133
+ return TextFieldRef(
134
+ path=[*self.path, "access"],
135
+ expected_type=self.expected_type,
136
+ )
137
+
138
+ @property
139
+ def restrictions(self) -> NumberFieldRef:
140
+ return NumberFieldRef(
141
+ path=[*self.path, "restrictions"],
142
+ expected_type=self.expected_type,
143
+ )
144
+
145
+ @property
146
+ def pinned_message(self) -> MessageContainer:
147
+ return MessageContainer(
148
+ path=[*self.path, "pinned_message"],
149
+ expected_type=self.expected_type,
150
+ )
151
+
152
+ @property
153
+ def participants_count(self) -> NumberFieldRef:
154
+ return NumberFieldRef(
155
+ path=[*self.path, "participants_count"],
156
+ expected_type=self.expected_type,
157
+ )
158
+
159
+ @property
160
+ def description(self) -> TextFieldRef:
161
+ return TextFieldRef(
162
+ path=[*self.path, "description"],
163
+ expected_type=self.expected_type,
164
+ )
165
+
166
+ @property
167
+ def options(self) -> FieldRef:
168
+ return FieldRef(
169
+ path=[*self.path, "options"],
170
+ expected_type=self.expected_type,
171
+ )
172
+
173
+ @property
174
+ def join_time(self) -> NumberFieldRef:
175
+ return NumberFieldRef(
176
+ path=[*self.path, "join_time"],
177
+ expected_type=self.expected_type,
178
+ )
179
+
180
+ @property
181
+ def invited_by(self) -> NumberFieldRef:
182
+ return NumberFieldRef(
183
+ path=[*self.path, "invited_by"],
184
+ expected_type=self.expected_type,
185
+ )
186
+
187
+ @property
188
+ def modified(self) -> NumberFieldRef:
189
+ return NumberFieldRef(
190
+ path=[*self.path, "modified"],
191
+ expected_type=self.expected_type,
192
+ )
193
+
194
+ @property
195
+ def messages_count(self) -> NumberFieldRef:
196
+ return NumberFieldRef(
197
+ path=[*self.path, "messages_count"],
198
+ expected_type=self.expected_type,
199
+ )
200
+
201
+ @property
202
+ def has_bots(self) -> FieldRef:
203
+ return FieldRef(
204
+ path=[*self.path, "has_bots"],
205
+ expected_type=self.expected_type,
206
+ )
207
+
208
+ @property
209
+ def prev_message_id(self) -> NumberFieldRef:
210
+ return NumberFieldRef(
211
+ path=[*self.path, "prev_message_id"],
212
+ expected_type=self.expected_type,
213
+ )
214
+
215
+ @property
216
+ def admin_participants(self) -> FieldRef:
217
+ return FieldRef(
218
+ path=[*self.path, "admin_participants"],
219
+ expected_type=self.expected_type,
220
+ )
221
+
222
+ @property
223
+ def admins(self) -> FieldRef:
224
+ return FieldRef(
225
+ path=[*self.path, "admins"],
226
+ expected_type=self.expected_type,
227
+ )
228
+
229
+ @property
230
+ def cid(self) -> NumberFieldRef:
231
+ return NumberFieldRef(
232
+ path=[*self.path, "cid"],
233
+ expected_type=self.expected_type,
234
+ )
235
+
236
+ def __call__(self, event: Any, client: ClientT) -> bool:
237
+ if not isinstance(event, self.expected_type):
238
+ return False
239
+
240
+ return bool(self.resolve_path(event))
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import builtins
4
+ from typing import TYPE_CHECKING, Any, TypeVar
5
+
6
+ from pymax.filters.base import ClientT, Filter, PathResolver
7
+ from pymax.filters.refs import FieldRef, NumberFieldRef, TextFieldRef
8
+
9
+ if TYPE_CHECKING:
10
+ pass
11
+
12
+ EventT = TypeVar("EventT")
13
+
14
+
15
+ class MessageContainer(Filter[EventT, ClientT], PathResolver):
16
+ def __init__(
17
+ self,
18
+ expected_type: builtins.type[EventT],
19
+ path: list[str] | None = None,
20
+ ) -> None:
21
+ self.expected_type: type[EventT] = expected_type
22
+ self.path = path or []
23
+
24
+ @property
25
+ def text(self) -> TextFieldRef:
26
+ return TextFieldRef(
27
+ path=[*self.path, "text"],
28
+ expected_type=self.expected_type,
29
+ )
30
+
31
+ @property
32
+ def id(self) -> NumberFieldRef:
33
+ return NumberFieldRef(
34
+ path=[*self.path, "id"],
35
+ expected_type=self.expected_type,
36
+ )
37
+
38
+ @property
39
+ def chat_id(self) -> NumberFieldRef:
40
+ return NumberFieldRef(
41
+ path=[*self.path, "chat_id"],
42
+ expected_type=self.expected_type,
43
+ )
44
+
45
+ @property
46
+ def sender(self) -> NumberFieldRef:
47
+ return NumberFieldRef(
48
+ path=[*self.path, "sender"],
49
+ expected_type=self.expected_type,
50
+ )
51
+
52
+ @property
53
+ def time(self) -> NumberFieldRef:
54
+ return NumberFieldRef(
55
+ path=[*self.path, "time"],
56
+ expected_type=self.expected_type,
57
+ )
58
+
59
+ @property
60
+ def type(self) -> TextFieldRef:
61
+ return TextFieldRef(
62
+ path=[*self.path, "type"],
63
+ expected_type=self.expected_type,
64
+ )
65
+
66
+ @property
67
+ def cid(self) -> NumberFieldRef:
68
+ return NumberFieldRef(
69
+ path=[*self.path, "cid"],
70
+ expected_type=self.expected_type,
71
+ )
72
+
73
+ @property
74
+ def attaches(self) -> FieldRef:
75
+ return FieldRef(
76
+ path=[*self.path, "attaches"],
77
+ expected_type=self.expected_type,
78
+ )
79
+
80
+ @property
81
+ def stats(self) -> FieldRef:
82
+ return FieldRef(
83
+ path=[*self.path, "stats"],
84
+ expected_type=self.expected_type,
85
+ )
86
+
87
+ @property
88
+ def status(self) -> TextFieldRef:
89
+ return TextFieldRef(
90
+ path=[*self.path, "status"],
91
+ expected_type=self.expected_type,
92
+ )
93
+
94
+ @property
95
+ def reaction_info(self) -> FieldRef:
96
+ return FieldRef(
97
+ path=[*self.path, "reaction_info"],
98
+ expected_type=self.expected_type,
99
+ )
100
+
101
+ @property
102
+ def options(self) -> FieldRef:
103
+ return FieldRef(
104
+ path=[*self.path, "options"],
105
+ expected_type=self.expected_type,
106
+ )
107
+
108
+ @property
109
+ def prev_message_id(self) -> NumberFieldRef:
110
+ return NumberFieldRef(
111
+ path=[*self.path, "prev_message_id"],
112
+ expected_type=self.expected_type,
113
+ )
114
+
115
+ @property
116
+ def ttl(self) -> FieldRef:
117
+ return FieldRef(
118
+ path=[*self.path, "ttl"],
119
+ expected_type=self.expected_type,
120
+ )
121
+
122
+ @property
123
+ def unread(self) -> NumberFieldRef:
124
+ return NumberFieldRef(
125
+ path=[*self.path, "unread"],
126
+ expected_type=self.expected_type,
127
+ )
128
+
129
+ @property
130
+ def mark(self) -> NumberFieldRef:
131
+ return NumberFieldRef(
132
+ path=[*self.path, "mark"],
133
+ expected_type=self.expected_type,
134
+ )
135
+
136
+ @property
137
+ def elements(self) -> FieldRef:
138
+ return FieldRef(
139
+ path=[*self.path, "elements"],
140
+ expected_type=self.expected_type,
141
+ )
142
+
143
+ def __call__(self, event: Any, client: ClientT) -> bool:
144
+ if not isinstance(event, self.expected_type):
145
+ return False
146
+
147
+ return bool(self.resolve_path(event))
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, TypeVar
4
+
5
+ from .base import ClientT, Filter, PathResolver
6
+
7
+ if TYPE_CHECKING:
8
+ pass
9
+
10
+ EventT = TypeVar("EventT")
11
+
12
+
13
+ class ContainsFilter(Filter[EventT, ClientT], PathResolver):
14
+ def __init__(
15
+ self,
16
+ path: list[str],
17
+ expected_type: type[EventT],
18
+ text: str,
19
+ ignore_case: bool,
20
+ ) -> None:
21
+ self.path = path
22
+ self.expected_type = expected_type
23
+ self.text = text
24
+ self.ignore_case = ignore_case
25
+
26
+ def __call__(self, event: EventT, _: ClientT) -> bool:
27
+ if not isinstance(event, self.expected_type):
28
+ return False
29
+
30
+ value = self.resolve_path(event)
31
+
32
+ if not isinstance(value, str):
33
+ return False
34
+
35
+ if self.ignore_case:
36
+ return self.text.casefold() in value.casefold()
37
+
38
+ return self.text in value
39
+
40
+
41
+ class StartsWithFilter(Filter[EventT, ClientT], PathResolver):
42
+ def __init__(
43
+ self,
44
+ path: list[str],
45
+ expected_type: type[EventT],
46
+ text: str,
47
+ ignore_case: bool,
48
+ ) -> None:
49
+ self.path = path
50
+ self.expected_type = expected_type
51
+ self.text = text
52
+ self.ignore_case = ignore_case
53
+
54
+ def __call__(self, event: EventT, _: ClientT) -> bool:
55
+ if not isinstance(event, self.expected_type):
56
+ return False
57
+
58
+ value = self.resolve_path(event)
59
+
60
+ if not isinstance(value, str):
61
+ return False
62
+
63
+ if self.ignore_case:
64
+ return value.casefold().startswith(self.text.casefold())
65
+
66
+ return value.startswith(self.text)
67
+
68
+
69
+ class EndsWithFilter(Filter[EventT, ClientT], PathResolver):
70
+ def __init__(
71
+ self,
72
+ path: list[str],
73
+ expected_type: type[EventT],
74
+ text: str,
75
+ ignore_case: bool,
76
+ ) -> None:
77
+ self.path = path
78
+ self.expected_type = expected_type
79
+ self.text = text
80
+ self.ignore_case = ignore_case
81
+
82
+ def __call__(self, event: EventT, _: ClientT) -> bool:
83
+ if not isinstance(event, self.expected_type):
84
+ return False
85
+
86
+ value = self.resolve_path(event)
87
+
88
+ if not isinstance(value, str):
89
+ return False
90
+
91
+ if self.ignore_case:
92
+ return value.casefold().endswith(self.text.casefold())
93
+
94
+ return value.endswith(self.text)
95
+
96
+
97
+ class LessThanFilter(Filter[EventT, ClientT], PathResolver):
98
+ def __init__(
99
+ self,
100
+ path: list[str],
101
+ expected_type: type[EventT],
102
+ expected: float,
103
+ ) -> None:
104
+ self.path = path
105
+ self.expected_type = expected_type
106
+ self.expected = expected
107
+
108
+ def __call__(self, event: EventT, _: ClientT) -> bool:
109
+ if not isinstance(event, self.expected_type):
110
+ return False
111
+
112
+ value = self.resolve_path(event)
113
+
114
+ if not isinstance(value, int | float) or isinstance(value, bool):
115
+ return False
116
+
117
+ return value < self.expected
118
+
119
+
120
+ class LessThanOrEqualFilter(Filter[EventT, ClientT], PathResolver):
121
+ def __init__(
122
+ self,
123
+ path: list[str],
124
+ expected_type: type[EventT],
125
+ expected: float,
126
+ ) -> None:
127
+ self.path = path
128
+ self.expected_type = expected_type
129
+ self.expected = expected
130
+
131
+ def __call__(self, event: EventT, _: ClientT) -> bool:
132
+ if not isinstance(event, self.expected_type):
133
+ return False
134
+
135
+ value = self.resolve_path(event)
136
+
137
+ if not isinstance(value, int | float) or isinstance(value, bool):
138
+ return False
139
+
140
+ return value <= self.expected
141
+
142
+
143
+ class GreaterThanFilter(Filter[EventT, ClientT], PathResolver):
144
+ def __init__(
145
+ self,
146
+ path: list[str],
147
+ expected_type: type[EventT],
148
+ expected: float,
149
+ ) -> None:
150
+ self.path = path
151
+ self.expected_type = expected_type
152
+ self.expected = expected
153
+
154
+ def __call__(self, event: EventT, _: ClientT) -> bool:
155
+ if not isinstance(event, self.expected_type):
156
+ return False
157
+
158
+ value = self.resolve_path(event)
159
+
160
+ if not isinstance(value, int | float) or isinstance(value, bool):
161
+ return False
162
+
163
+ return value > self.expected
164
+
165
+
166
+ class GreaterThanOrEqualFilter(Filter[EventT, ClientT], PathResolver):
167
+ def __init__(
168
+ self,
169
+ path: list[str],
170
+ expected_type: type[EventT],
171
+ expected: float,
172
+ ) -> None:
173
+ self.path = path
174
+ self.expected_type = expected_type
175
+ self.expected = expected
176
+
177
+ def __call__(self, event: EventT, _: ClientT) -> bool:
178
+ if not isinstance(event, self.expected_type):
179
+ return False
180
+
181
+ value = self.resolve_path(event)
182
+
183
+ if not isinstance(value, int | float) or isinstance(value, bool):
184
+ return False
185
+
186
+ return value >= self.expected
187
+
188
+
189
+ class BetweenFilter(Filter[EventT, ClientT], PathResolver):
190
+ def __init__(
191
+ self,
192
+ path: list[str],
193
+ expected_type: type[EventT],
194
+ minimum: float,
195
+ maximum: float,
196
+ ) -> None:
197
+ self.path = path
198
+ self.expected_type = expected_type
199
+ self.minimum = minimum
200
+ self.maximum = maximum
201
+
202
+ def __call__(self, event: EventT, _: ClientT) -> bool:
203
+ if not isinstance(event, self.expected_type):
204
+ return False
205
+
206
+ value = self.resolve_path(event)
207
+
208
+ if not isinstance(value, int | float) or isinstance(value, bool):
209
+ return False
210
+
211
+ return self.minimum <= value <= self.maximum
pymax/filters/magic.py ADDED
@@ -0,0 +1,13 @@
1
+ from pymax.types import Chat, Message
2
+
3
+ from .containers import ChatContainer, MessageContainer
4
+
5
+
6
+ class MagicFilter:
7
+ @property
8
+ def message(self) -> MessageContainer:
9
+ return MessageContainer(Message)
10
+
11
+ @property
12
+ def chat(self) -> ChatContainer:
13
+ return ChatContainer(Chat)
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from pymax.types import LinkType, Message
6
+
7
+ from .base import Filter
8
+
9
+ if TYPE_CHECKING:
10
+ from pymax.base import BaseClient
11
+ from pymax.types.domain.message import Attachment
12
+
13
+
14
+ class Command(Filter[Message, "BaseClient"]):
15
+ """Проверяет команду в первом слове текста сообщения.
16
+
17
+ Args:
18
+ command: Имя команды, например ``"start"`` для ``/start``.
19
+ prefix: Префикс команды или кортеж допустимых префиксов.
20
+
21
+ Аргументы после команды не влияют на совпадение.
22
+ """
23
+
24
+ def __init__(self, command: str, prefix: str | tuple[str, ...] = "/") -> None:
25
+ self.prefixes = (prefix,) if isinstance(prefix, str) else prefix
26
+
27
+ for prefix_ in self.prefixes:
28
+ self.command = command.removeprefix(prefix_)
29
+
30
+ def __call__(self, message: Message, _: BaseClient) -> bool:
31
+ if not message.text:
32
+ return False
33
+
34
+ parts = message.text.split(maxsplit=1)
35
+ if not parts:
36
+ return False
37
+
38
+ dirty_command = parts[0]
39
+
40
+ for prefix in self.prefixes:
41
+ if message.text.startswith(prefix) and dirty_command[len(prefix) :] == self.command:
42
+ return True
43
+
44
+ return False
45
+
46
+
47
+ class Reply(Filter[Message, "BaseClient"]):
48
+ """Проверяет, является ли сообщение ответом на другое сообщение."""
49
+
50
+ def __call__(self, message: Message, _: BaseClient) -> bool:
51
+ if not message.link:
52
+ return False
53
+
54
+ return message.link.type == LinkType.REPLY
55
+
56
+
57
+ class Forwarded(Filter[Message, "BaseClient"]):
58
+ """Проверяет, является ли сообщение пересланным."""
59
+
60
+ def __call__(self, message: Message, _: BaseClient) -> bool:
61
+ if not message.link:
62
+ return False
63
+
64
+ return message.link.type == LinkType.FORWARD
65
+
66
+
67
+ class FromMe(Filter[Message, "BaseClient"]):
68
+ """Проверяет, отправлено ли сообщение текущим пользователем клиента."""
69
+
70
+ def __call__(self, message: Message, client: BaseClient) -> bool:
71
+ if not message.sender or not client.me:
72
+ return False
73
+
74
+ return message.sender == client.me.contact.id
75
+
76
+
77
+ class HasAttachment(Filter[Message, "BaseClient"]):
78
+ """Проверяет наличие хотя бы одного вложения указанных типов.
79
+
80
+ Передавайте классы входящих вложений из ``pymax.types``, например
81
+ ``PhotoAttachment``, а не классы файлов для отправки.
82
+ """
83
+
84
+ def __init__(self, *attachment_types: type[Attachment]) -> None:
85
+ self.expected_types = attachment_types
86
+
87
+ def __call__(self, message: Message, _: BaseClient) -> bool:
88
+ return any(isinstance(attach, self.expected_types) for attach in message.attaches)