maxapi-python 2.4.0__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 (101) hide show
  1. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/METADATA +22 -12
  2. maxapi_python-2.5.0.dist-info/RECORD +195 -0
  3. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/WHEEL +1 -1
  4. pymax/__init__.py +8 -2
  5. pymax/_data/apk_fingerprints.json +374 -17
  6. pymax/_data/rootca_ssl_rsa2022.crt +33 -0
  7. pymax/api/auth/service.py +15 -2
  8. pymax/api/auth/types.py +3 -0
  9. pymax/api/bots/payloads.py +8 -0
  10. pymax/api/bots/service.py +13 -1
  11. pymax/api/chats/enums.py +1 -0
  12. pymax/api/chats/payloads.py +21 -1
  13. pymax/api/chats/service.py +41 -0
  14. pymax/api/facade.py +2 -0
  15. pymax/api/messages/__init__.py +1 -1
  16. pymax/api/messages/enums.py +1 -0
  17. pymax/api/messages/payloads.py +56 -4
  18. pymax/api/messages/service.py +405 -27
  19. pymax/api/response.py +1 -1
  20. pymax/api/stories/__init__.py +2 -0
  21. pymax/api/stories/enums.py +19 -0
  22. pymax/api/stories/payloads.py +64 -0
  23. pymax/api/stories/service.py +198 -0
  24. pymax/api/uploads/payloads.py +42 -1
  25. pymax/api/uploads/service.py +171 -69
  26. pymax/app.py +39 -8
  27. pymax/auth/__init__.py +2 -0
  28. pymax/auth/exceptions.py +9 -0
  29. pymax/auth/providers.py +6 -2
  30. pymax/auth/qr.py +11 -1
  31. pymax/auth/sms.py +11 -1
  32. pymax/base.py +146 -27
  33. pymax/client.py +39 -11
  34. pymax/client_web.py +15 -12
  35. pymax/config.py +101 -53
  36. pymax/di/__init__.py +2 -0
  37. pymax/di/context.py +18 -0
  38. pymax/di/resolver.py +38 -0
  39. pymax/dispatch/__init__.py +7 -0
  40. pymax/dispatch/dispatcher.py +143 -40
  41. pymax/dispatch/enums.py +1 -0
  42. pymax/dispatch/mapping.py +23 -3
  43. pymax/dispatch/middleware.py +32 -0
  44. pymax/dispatch/resolvers.py +4 -0
  45. pymax/dispatch/router.py +86 -34
  46. pymax/files/base.py +58 -1
  47. pymax/files/video.py +32 -40
  48. pymax/files/voice.py +18 -4
  49. pymax/filters/__init__.py +15 -0
  50. pymax/filters/base.py +218 -0
  51. pymax/filters/containers/__init__.py +2 -0
  52. pymax/filters/containers/chat.py +240 -0
  53. pymax/filters/containers/message.py +147 -0
  54. pymax/filters/filters.py +211 -0
  55. pymax/filters/magic.py +13 -0
  56. pymax/filters/presets.py +88 -0
  57. pymax/filters/refs.py +56 -0
  58. pymax/fingerprint/fingerprint.py +5 -22
  59. pymax/fingerprint/models.py +1 -0
  60. pymax/formatting/markdown.py +55 -1
  61. pymax/fsm/__init__.py +3 -0
  62. pymax/fsm/context.py +30 -0
  63. pymax/fsm/protocol.py +14 -0
  64. pymax/fsm/state.py +70 -0
  65. pymax/fsm/storage.py +151 -0
  66. pymax/infra/base.py +2 -0
  67. pymax/infra/bots.py +20 -0
  68. pymax/infra/chat.py +37 -5
  69. pymax/infra/message.py +204 -9
  70. pymax/infra/protocol.py +5 -1
  71. pymax/infra/stories.py +205 -0
  72. pymax/integrations/__init__.py +0 -0
  73. pymax/integrations/dishka.py +109 -0
  74. pymax/protocol/enums.py +2 -0
  75. pymax/protocol/tcp/payload.py +0 -2
  76. pymax/session/__init__.py +1 -1
  77. pymax/session/models.py +30 -0
  78. pymax/session/protocol.py +33 -7
  79. pymax/session/store.py +73 -5
  80. pymax/transport/tcp.py +11 -3
  81. pymax/types/domain/__init__.py +23 -1
  82. pymax/types/domain/attachments/__init__.py +1 -1
  83. pymax/types/domain/attachments/call.py +7 -1
  84. pymax/types/domain/attachments/contact.py +3 -3
  85. pymax/types/domain/attachments/control.py +3 -0
  86. pymax/types/domain/attachments/enums.py +16 -0
  87. pymax/types/domain/attachments/keyboards/__init__.py +1 -1
  88. pymax/types/domain/attachments/keyboards/enums.py +12 -0
  89. pymax/types/domain/attachments/keyboards/inline.py +74 -3
  90. pymax/types/domain/attachments/video.py +3 -3
  91. pymax/types/domain/callback.py +19 -0
  92. pymax/types/domain/comment.py +24 -0
  93. pymax/types/domain/enums.py +7 -0
  94. pymax/types/domain/message.py +158 -9
  95. pymax/types/domain/story.py +126 -0
  96. pymax/types/events/reaction.py +28 -4
  97. pymax/versions/__init__.py +2 -0
  98. pymax/versions/catalog.py +104 -0
  99. pymax/versions/exceptions.py +10 -0
  100. maxapi_python-2.4.0.dist-info/RECORD +0 -161
  101. {maxapi_python-2.4.0.dist-info → maxapi_python-2.5.0.dist-info}/licenses/LICENSE +0 -0
pymax/filters/base.py ADDED
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from inspect import isawaitable
5
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Awaitable
9
+
10
+ from pymax.base import BaseClient
11
+
12
+
13
+ MISSING = object()
14
+
15
+ EventT = TypeVar("EventT")
16
+ ClientT = TypeVar("ClientT", bound="BaseClient")
17
+
18
+
19
+ class Filter(ABC, Generic[EventT, ClientT]):
20
+ """Базовый класс фильтра событий.
21
+
22
+ Реализуйте ``__call__(event, client)`` с результатом ``bool`` или
23
+ ``Awaitable[bool]`` и передайте экземпляр в декоратор роутера. Фильтры
24
+ объединяются через ``&``, ``|`` и ``~``; составной фильтр при ручном вызове
25
+ нужно ожидать через ``await``.
26
+ """
27
+
28
+ @abstractmethod
29
+ def __call__(
30
+ self,
31
+ event: EventT,
32
+ client: ClientT,
33
+ /,
34
+ ) -> bool | Awaitable[bool]: ...
35
+ def __and__(self, other: Filter[EventT, ClientT]) -> AndFilter:
36
+ return AndFilter(self, other)
37
+
38
+ def __or__(self, value: Filter[EventT, ClientT]) -> OrFilter:
39
+ return OrFilter(self, value)
40
+
41
+ def __invert__(self) -> NotFilter:
42
+ return NotFilter(self)
43
+
44
+ async def resolve_filter_result(
45
+ self,
46
+ result: bool | Awaitable[bool],
47
+ ) -> bool:
48
+ if isawaitable(result):
49
+ return await result
50
+
51
+ return result
52
+
53
+
54
+ class PathResolver:
55
+ def __init__(self, path: list[str]) -> None:
56
+ self.path = path
57
+
58
+ def resolve_path(self, event: Any) -> Any | None:
59
+ value = event
60
+
61
+ for attr in self.path:
62
+ value = getattr(value, attr, MISSING)
63
+
64
+ if value is MISSING:
65
+ return MISSING
66
+
67
+ return value
68
+
69
+
70
+ class IsNoneFilter(Filter[EventT, ClientT], PathResolver):
71
+ def __init__(
72
+ self,
73
+ path: list[str],
74
+ expected_type: type[EventT],
75
+ ) -> None:
76
+ super().__init__(path)
77
+ self.expected_type = expected_type
78
+ self.path = path
79
+
80
+ def __call__(self, event: EventT, _: ClientT) -> bool:
81
+ if not isinstance(event, self.expected_type):
82
+ return False
83
+
84
+ value = self.resolve_path(event)
85
+
86
+ if value is MISSING:
87
+ return False
88
+
89
+ return value is None
90
+
91
+
92
+ class IsEmptyFilter(Filter[EventT, ClientT], PathResolver):
93
+ def __init__(
94
+ self,
95
+ path: list[str],
96
+ expected_type: type[EventT],
97
+ ) -> None:
98
+ super().__init__(path)
99
+ self.expected_type = expected_type
100
+ self.path = path
101
+
102
+ def __call__(self, event: EventT, _: ClientT) -> bool:
103
+ if not isinstance(event, self.expected_type):
104
+ return False
105
+
106
+ value = self.resolve_path(event)
107
+
108
+ return value == []
109
+
110
+
111
+ class FieldRef(Filter[EventT, ClientT], PathResolver):
112
+ def __init__(self, path: list[str], expected_type: type[EventT]) -> None:
113
+ super().__init__(path)
114
+ self.expected_type: type[EventT] = expected_type
115
+ self.path = path
116
+
117
+ def __eq__(self, value: object) -> EqFilter: # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
118
+ return EqFilter(self.path, self.expected_type, value)
119
+
120
+ def __ne__(self, value: object) -> NeFilter: # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
121
+ return NeFilter(self.path, self.expected_type, value)
122
+
123
+ def is_none(self) -> IsNoneFilter:
124
+ return IsNoneFilter(self.path, expected_type=self.expected_type)
125
+
126
+ def is_empty(self) -> IsEmptyFilter:
127
+ return IsEmptyFilter(self.path, expected_type=self.expected_type)
128
+
129
+ def __call__(self, event: EventT, _: ClientT, /) -> bool:
130
+ if not isinstance(event, self.expected_type):
131
+ return False
132
+
133
+ value = self.resolve_path(event)
134
+
135
+ if value is MISSING:
136
+ return False
137
+
138
+ return bool(value)
139
+
140
+
141
+ class BinaryFilter(Filter[EventT, ClientT]):
142
+ def __init__(self, left: Filter[EventT, ClientT], right: Filter[EventT, ClientT]) -> None:
143
+ self.left = left
144
+ self.right = right
145
+
146
+
147
+ class NotFilter(Filter[EventT, ClientT]):
148
+ def __init__(self, filter_instance: Filter[EventT, ClientT]) -> None:
149
+ self.filter = filter_instance
150
+
151
+ async def __call__(self, event: EventT, client: ClientT) -> bool:
152
+ return not await self.resolve_filter_result(self.filter(event, client))
153
+
154
+
155
+ class NeFilter(Filter[EventT, ClientT], PathResolver):
156
+ def __init__(
157
+ self,
158
+ path: list[str],
159
+ expected_type: type[EventT],
160
+ expected: object,
161
+ ) -> None:
162
+ self.path: list[str] = path
163
+ self.expected_type = expected_type
164
+ self.expected: object = expected
165
+
166
+ def __call__(self, event: EventT, _: ClientT) -> bool:
167
+ if not isinstance(event, self.expected_type):
168
+ return False
169
+
170
+ value = self.resolve_path(event)
171
+
172
+ if value is MISSING:
173
+ return False
174
+
175
+ return value != self.expected
176
+
177
+
178
+ class AndFilter(BinaryFilter[EventT, ClientT]):
179
+ async def __call__(self, event: EventT, client: ClientT) -> bool:
180
+ left = await self.resolve_filter_result(self.left(event, client))
181
+
182
+ if not left:
183
+ return False
184
+
185
+ return await self.resolve_filter_result(self.right(event, client))
186
+
187
+
188
+ class OrFilter(BinaryFilter[EventT, ClientT]):
189
+ async def __call__(self, event: EventT, client: ClientT) -> bool:
190
+ left = await self.resolve_filter_result(self.left(event, client))
191
+
192
+ if left:
193
+ return True
194
+
195
+ return await self.resolve_filter_result(self.right(event, client))
196
+
197
+
198
+ class EqFilter(Filter[EventT, ClientT], PathResolver):
199
+ def __init__(
200
+ self,
201
+ path: list[str],
202
+ expected_type: type[EventT],
203
+ expected: object,
204
+ ) -> None:
205
+ self.path: list[str] = path
206
+ self.expected_type = expected_type
207
+ self.expected: object = expected
208
+
209
+ def __call__(self, event: EventT, _: ClientT) -> bool:
210
+ if not isinstance(event, self.expected_type):
211
+ return False
212
+
213
+ value = self.resolve_path(event)
214
+
215
+ if value is MISSING:
216
+ return False
217
+
218
+ return value == self.expected
@@ -0,0 +1,2 @@
1
+ from .chat import ChatContainer
2
+ from .message import MessageContainer
@@ -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))