telegrinder 0.1.dev161__py3-none-any.whl → 0.1.dev163__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.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/api/api.py +4 -2
- telegrinder/api/error.py +3 -3
- telegrinder/bot/bot.py +8 -12
- telegrinder/bot/cute_types/base.py +1 -4
- telegrinder/bot/cute_types/message.py +140 -0
- telegrinder/bot/dispatch/composition.py +1 -1
- telegrinder/bot/dispatch/dispatch.py +3 -4
- telegrinder/bot/dispatch/view/box.py +6 -6
- telegrinder/bot/dispatch/waiter_machine/short_state.py +1 -1
- telegrinder/bot/rules/__init__.py +4 -0
- telegrinder/bot/rules/is_from.py +18 -3
- telegrinder/bot/scenario/checkbox.py +3 -4
- telegrinder/client/aiohttp.py +1 -2
- telegrinder/modules.py +5 -3
- telegrinder/msgspec_json.py +3 -3
- telegrinder/msgspec_utils.py +80 -38
- telegrinder/node/source.py +2 -3
- telegrinder/tools/__init__.py +6 -0
- telegrinder/tools/buttons.py +8 -13
- telegrinder/tools/error_handler/error_handler.py +2 -2
- telegrinder/tools/formatting/__init__.py +6 -0
- telegrinder/tools/formatting/html.py +10 -0
- telegrinder/tools/formatting/links.py +7 -0
- telegrinder/tools/formatting/spec_html_formats.py +27 -15
- telegrinder/tools/global_context/global_context.py +7 -5
- telegrinder/tools/keyboard.py +3 -3
- telegrinder/tools/loop_wrapper/abc.py +4 -4
- telegrinder/tools/magic.py +1 -1
- telegrinder/types/enums.py +4 -0
- telegrinder/types/methods.py +175 -41
- telegrinder/types/objects.py +442 -201
- {telegrinder-0.1.dev161.dist-info → telegrinder-0.1.dev163.dist-info}/METADATA +1 -1
- {telegrinder-0.1.dev161.dist-info → telegrinder-0.1.dev163.dist-info}/RECORD +35 -35
- {telegrinder-0.1.dev161.dist-info → telegrinder-0.1.dev163.dist-info}/WHEEL +1 -1
- {telegrinder-0.1.dev161.dist-info → telegrinder-0.1.dev163.dist-info}/LICENSE +0 -0
|
@@ -30,6 +30,8 @@ from .is_from import (
|
|
|
30
30
|
IsDartDice,
|
|
31
31
|
IsDice,
|
|
32
32
|
IsForum,
|
|
33
|
+
IsForward,
|
|
34
|
+
IsForwardType,
|
|
33
35
|
IsGroup,
|
|
34
36
|
IsLanguageCode,
|
|
35
37
|
IsPremium,
|
|
@@ -78,6 +80,8 @@ __all__ = (
|
|
|
78
80
|
"IsDartDice",
|
|
79
81
|
"IsDice",
|
|
80
82
|
"IsForum",
|
|
83
|
+
"IsForward",
|
|
84
|
+
"IsForwardType",
|
|
81
85
|
"IsGroup",
|
|
82
86
|
"IsLanguageCode",
|
|
83
87
|
"IsPremium",
|
telegrinder/bot/rules/is_from.py
CHANGED
|
@@ -12,13 +12,13 @@ T = typing.TypeVar("T", bound=BaseCute)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@typing.runtime_checkable
|
|
15
|
-
class
|
|
15
|
+
class FromUserProto(typing.Protocol):
|
|
16
16
|
from_: User | Option[User]
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class HasFrom(ABCRule[T]):
|
|
20
20
|
async def check(self, event: T, ctx: Context) -> bool:
|
|
21
|
-
return isinstance(event,
|
|
21
|
+
return isinstance(event, FromUserProto) and bool(event.from_)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class HasDice(MessageRule):
|
|
@@ -26,6 +26,19 @@ class HasDice(MessageRule):
|
|
|
26
26
|
return bool(message.dice)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class IsForward(MessageRule):
|
|
30
|
+
async def check(self, message: Message, ctx: Context) -> bool:
|
|
31
|
+
return bool(message.forward_origin)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class IsForwardType(MessageRule, requires=[IsForward()]):
|
|
35
|
+
def __init__(self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /) -> None:
|
|
36
|
+
self.fwd_type = fwd_type
|
|
37
|
+
|
|
38
|
+
async def check(self, message: Message, ctx: Context) -> bool:
|
|
39
|
+
return message.forward_origin.unwrap().v.type == self.fwd_type
|
|
40
|
+
|
|
41
|
+
|
|
29
42
|
class IsReply(MessageRule):
|
|
30
43
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
31
44
|
return bool(message.reply_to_message)
|
|
@@ -58,7 +71,7 @@ class IsLanguageCode(MessageRule, requires=[HasFrom()]):
|
|
|
58
71
|
async def check(self, message: Message, ctx: Context) -> bool:
|
|
59
72
|
if not message.from_user.language_code:
|
|
60
73
|
return False
|
|
61
|
-
return message.from_user.language_code.
|
|
74
|
+
return message.from_user.language_code.unwrap() in self.lang_codes
|
|
62
75
|
|
|
63
76
|
|
|
64
77
|
class IsForum(MessageRule):
|
|
@@ -141,6 +154,8 @@ __all__ = (
|
|
|
141
154
|
"IsDartDice",
|
|
142
155
|
"IsDice",
|
|
143
156
|
"IsForum",
|
|
157
|
+
"IsForward",
|
|
158
|
+
"IsForwardType",
|
|
144
159
|
"IsGroup",
|
|
145
160
|
"IsLanguageCode",
|
|
146
161
|
"IsPremium",
|
|
@@ -43,7 +43,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
43
43
|
self.choices: list[Choice] = []
|
|
44
44
|
self.ready = ready_text
|
|
45
45
|
self.max_in_row = max_in_row
|
|
46
|
-
self.random_code = secrets.token_hex(
|
|
46
|
+
self.random_code = secrets.token_hex(8)
|
|
47
47
|
self.waiter_machine = waiter_machine
|
|
48
48
|
|
|
49
49
|
def get_markup(self) -> InlineKeyboardMarkup:
|
|
@@ -73,7 +73,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
73
73
|
is_picked: bool = False,
|
|
74
74
|
) -> typing.Self:
|
|
75
75
|
self.choices.append(
|
|
76
|
-
Choice(name, is_picked, default_text, picked_text, secrets.token_hex(
|
|
76
|
+
Choice(name, is_picked, default_text, picked_text, secrets.token_hex(8)),
|
|
77
77
|
)
|
|
78
78
|
return self
|
|
79
79
|
|
|
@@ -103,7 +103,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
103
103
|
assert len(self.choices) > 1
|
|
104
104
|
message = (
|
|
105
105
|
await api.send_message(
|
|
106
|
-
self.chat_id,
|
|
106
|
+
chat_id=self.chat_id,
|
|
107
107
|
text=self.msg,
|
|
108
108
|
parse_mode=self.PARSE_MODE,
|
|
109
109
|
reply_markup=self.get_markup(),
|
|
@@ -111,7 +111,6 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
|
|
|
111
111
|
).unwrap()
|
|
112
112
|
|
|
113
113
|
while True:
|
|
114
|
-
q: CallbackQueryCute
|
|
115
114
|
q, _ = await self.waiter_machine.wait(view, (api, message.message_id))
|
|
116
115
|
should_continue = await self.handle(q)
|
|
117
116
|
await q.answer(self.CALLBACK_ANSWER)
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import secrets
|
|
2
1
|
import ssl
|
|
3
2
|
import typing
|
|
4
3
|
|
|
@@ -31,7 +30,7 @@ class AiohttpClient(ABCClient):
|
|
|
31
30
|
self.__class__.__name__,
|
|
32
31
|
self.session,
|
|
33
32
|
self.timeout,
|
|
34
|
-
|
|
33
|
+
True if self.session is None else self.session.closed,
|
|
35
34
|
)
|
|
36
35
|
|
|
37
36
|
async def request_raw(
|
telegrinder/modules.py
CHANGED
|
@@ -4,14 +4,16 @@ import typing
|
|
|
4
4
|
from choicelib import choice_in_order
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
@typing.runtime_checkable
|
|
7
8
|
class JSONModule(typing.Protocol):
|
|
8
|
-
def loads(self, s: str | bytes) ->
|
|
9
|
+
def loads(self, s: str | bytes) -> typing.Any:
|
|
9
10
|
...
|
|
10
11
|
|
|
11
|
-
def dumps(self, o:
|
|
12
|
+
def dumps(self, o: typing.Any) -> str:
|
|
12
13
|
...
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
@typing.runtime_checkable
|
|
15
17
|
class LoggerModule(typing.Protocol):
|
|
16
18
|
def debug(self, __msg: object, *args: object, **kwargs: object) -> None:
|
|
17
19
|
...
|
|
@@ -47,7 +49,7 @@ class LoggerModule(typing.Protocol):
|
|
|
47
49
|
|
|
48
50
|
logger: LoggerModule
|
|
49
51
|
json: JSONModule = choice_in_order(
|
|
50
|
-
["
|
|
52
|
+
["orjson", "ujson", "hyperjson"], do_import=True, default="telegrinder.msgspec_json"
|
|
51
53
|
)
|
|
52
54
|
logging_module = choice_in_order(["loguru"], default="logging")
|
|
53
55
|
logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
|
telegrinder/msgspec_json.py
CHANGED
|
@@ -3,11 +3,11 @@ import typing
|
|
|
3
3
|
from .msgspec_utils import decoder, encoder
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def loads(s: str | bytes) ->
|
|
7
|
-
return decoder.decode(s
|
|
6
|
+
def loads(s: str | bytes) -> typing.Any:
|
|
7
|
+
return decoder.decode(s)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def dumps(o:
|
|
10
|
+
def dumps(o: typing.Any) -> str:
|
|
11
11
|
return encoder.encode(o)
|
|
12
12
|
|
|
13
13
|
|
telegrinder/msgspec_utils.py
CHANGED
|
@@ -4,11 +4,7 @@ import fntypes.option
|
|
|
4
4
|
import msgspec
|
|
5
5
|
from fntypes.co import Error, Ok, Result, Variative
|
|
6
6
|
|
|
7
|
-
T = typing.TypeVar("T")
|
|
8
|
-
Ts = typing.TypeVarTuple("Ts")
|
|
9
|
-
|
|
10
7
|
if typing.TYPE_CHECKING:
|
|
11
|
-
import types
|
|
12
8
|
from datetime import datetime
|
|
13
9
|
|
|
14
10
|
from fntypes.option import Option
|
|
@@ -19,6 +15,7 @@ else:
|
|
|
19
15
|
|
|
20
16
|
datetime = type("datetime", (dt,), {})
|
|
21
17
|
|
|
18
|
+
|
|
22
19
|
class OptionMeta(type):
|
|
23
20
|
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
24
21
|
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
@@ -27,6 +24,9 @@ else:
|
|
|
27
24
|
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
28
25
|
pass
|
|
29
26
|
|
|
27
|
+
T = typing.TypeVar("T")
|
|
28
|
+
Ts = typing.TypeVarTuple("Ts")
|
|
29
|
+
|
|
30
30
|
DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], object]
|
|
31
31
|
EncHook: typing.TypeAlias = typing.Callable[[T], typing.Any]
|
|
32
32
|
|
|
@@ -48,10 +48,6 @@ def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.Validation
|
|
|
48
48
|
return Error(exc)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def datetime_dec_hook(tp: type[datetime], obj: int | float) -> datetime:
|
|
52
|
-
return tp.fromtimestamp(obj)
|
|
53
|
-
|
|
54
|
-
|
|
55
51
|
def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
|
|
56
52
|
if obj is None:
|
|
57
53
|
return Nothing
|
|
@@ -88,37 +84,49 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
88
84
|
|
|
89
85
|
raise TypeError(
|
|
90
86
|
"Object of type `{}` does not belong to types `{}`".format(
|
|
91
|
-
repr_type(
|
|
87
|
+
repr_type(obj.__class__),
|
|
92
88
|
" | ".join(map(repr_type, union_types)),
|
|
93
89
|
)
|
|
94
90
|
)
|
|
95
91
|
|
|
96
92
|
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
class Decoder:
|
|
94
|
+
"""Class `Decoder` for `msgspec` module with decode hook
|
|
95
|
+
for objects with the specified type.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
import enum
|
|
99
|
+
|
|
100
|
+
from datetime import datetime as dt
|
|
99
101
|
|
|
102
|
+
class Digit(enum.IntEnum):
|
|
103
|
+
ONE = 1
|
|
104
|
+
TWO = 2
|
|
105
|
+
THREE = 3
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
decoder = Encoder()
|
|
108
|
+
decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
|
|
103
109
|
|
|
110
|
+
decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
|
|
111
|
+
decoder.dec_hook(int, "123") #> TypeError: Unknown type `int`. You can implement decode hook for this type.
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
decoder.convert("123", type=int, strict=False) #> 123
|
|
114
|
+
decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
|
|
107
115
|
|
|
116
|
+
decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
|
|
117
|
+
```
|
|
118
|
+
"""
|
|
108
119
|
|
|
109
|
-
class Decoder:
|
|
110
120
|
def __init__(self) -> None:
|
|
111
|
-
self.dec_hooks: dict[
|
|
112
|
-
typing.Union[type[typing.Any], "types.UnionType"], DecHook[typing.Any]
|
|
113
|
-
] = {
|
|
121
|
+
self.dec_hooks: dict[typing.Any, DecHook[typing.Any]] = {
|
|
114
122
|
Option: option_dec_hook,
|
|
115
123
|
Variative: variative_dec_hook,
|
|
116
|
-
datetime:
|
|
124
|
+
datetime: lambda t, obj: t.fromtimestamp(obj),
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
def add_dec_hook(self,
|
|
127
|
+
def add_dec_hook(self, t: T): # type: ignore
|
|
120
128
|
def decorator(func: DecHook[T]) -> DecHook[T]:
|
|
121
|
-
return self.dec_hooks.setdefault(get_origin(
|
|
129
|
+
return self.dec_hooks.setdefault(get_origin(t), func) # type: ignore
|
|
122
130
|
|
|
123
131
|
return decorator
|
|
124
132
|
|
|
@@ -138,7 +146,7 @@ class Decoder:
|
|
|
138
146
|
type: type[T] = dict,
|
|
139
147
|
strict: bool = True,
|
|
140
148
|
from_attributes: bool = False,
|
|
141
|
-
builtin_types: typing.Iterable[type] | None = None,
|
|
149
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
142
150
|
str_keys: bool = False,
|
|
143
151
|
) -> T:
|
|
144
152
|
return msgspec.convert(
|
|
@@ -150,12 +158,30 @@ class Decoder:
|
|
|
150
158
|
builtin_types=builtin_types,
|
|
151
159
|
str_keys=str_keys,
|
|
152
160
|
)
|
|
161
|
+
|
|
162
|
+
@typing.overload
|
|
163
|
+
def decode(self, buf: str | bytes) -> typing.Any:
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
@typing.overload
|
|
167
|
+
def decode(self, buf: str | bytes, *, strict: bool = True) -> typing.Any:
|
|
168
|
+
...
|
|
153
169
|
|
|
170
|
+
@typing.overload
|
|
154
171
|
def decode(
|
|
155
172
|
self,
|
|
156
173
|
buf: str | bytes,
|
|
157
174
|
*,
|
|
158
|
-
type: type[T]
|
|
175
|
+
type: type[T],
|
|
176
|
+
strict: bool = True,
|
|
177
|
+
) -> T:
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
def decode(
|
|
181
|
+
self,
|
|
182
|
+
buf: str | bytes,
|
|
183
|
+
*,
|
|
184
|
+
type: type[T] = typing.Any, # type: ignore
|
|
159
185
|
strict: bool = True,
|
|
160
186
|
) -> T:
|
|
161
187
|
return msgspec.json.decode(
|
|
@@ -167,35 +193,55 @@ class Decoder:
|
|
|
167
193
|
|
|
168
194
|
|
|
169
195
|
class Encoder:
|
|
196
|
+
"""Class `Encoder` for `msgspec` module with encode hooks for objects.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
from datetime import datetime as dt
|
|
200
|
+
|
|
201
|
+
encoder = Encoder()
|
|
202
|
+
encoder.enc_hooks[dt] = lambda d: int(d.timestamp())
|
|
203
|
+
|
|
204
|
+
encoder.enc_hook(dt.now()) #> 1713354732
|
|
205
|
+
encoder.enc_hook(123) #> NotImplementedError: Not implemented encode hook for object of type `int`.
|
|
206
|
+
|
|
207
|
+
encoder.encode({'digit': Digit.ONE}) #> '{"digit":1}'
|
|
208
|
+
```
|
|
209
|
+
"""
|
|
210
|
+
|
|
170
211
|
def __init__(self) -> None:
|
|
171
|
-
self.enc_hooks: dict[
|
|
172
|
-
fntypes.option.Some:
|
|
173
|
-
fntypes.option.Nothing:
|
|
174
|
-
Variative:
|
|
175
|
-
datetime:
|
|
212
|
+
self.enc_hooks: dict[typing.Any, EncHook[typing.Any]] = {
|
|
213
|
+
fntypes.option.Some: lambda opt: opt.value,
|
|
214
|
+
fntypes.option.Nothing: lambda _: None,
|
|
215
|
+
Variative: lambda variative: variative.v,
|
|
216
|
+
datetime: lambda date: int(date.timestamp()),
|
|
176
217
|
}
|
|
177
218
|
|
|
178
|
-
def add_dec_hook(self,
|
|
219
|
+
def add_dec_hook(self, t: type[T]):
|
|
179
220
|
def decorator(func: EncHook[T]) -> EncHook[T]:
|
|
180
|
-
|
|
221
|
+
encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
|
|
222
|
+
return func if encode_hook is not func else encode_hook
|
|
181
223
|
|
|
182
224
|
return decorator
|
|
183
225
|
|
|
184
226
|
def enc_hook(self, obj: object) -> object:
|
|
185
|
-
origin_type = get_origin(
|
|
227
|
+
origin_type = get_origin(obj.__class__)
|
|
186
228
|
if origin_type not in self.enc_hooks:
|
|
187
229
|
raise NotImplementedError(
|
|
188
230
|
"Not implemented encode hook for "
|
|
189
231
|
f"object of type `{repr_type(origin_type)}`."
|
|
190
232
|
)
|
|
191
233
|
return self.enc_hooks[origin_type](obj)
|
|
234
|
+
|
|
235
|
+
@typing.overload
|
|
236
|
+
def encode(self, obj: typing.Any) -> str:
|
|
237
|
+
...
|
|
192
238
|
|
|
193
239
|
@typing.overload
|
|
194
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]
|
|
240
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[True]) -> str:
|
|
195
241
|
...
|
|
196
242
|
|
|
197
243
|
@typing.overload
|
|
198
|
-
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]
|
|
244
|
+
def encode(self, obj: typing.Any, *, as_str: typing.Literal[False]) -> bytes:
|
|
199
245
|
...
|
|
200
246
|
|
|
201
247
|
def encode(self, obj: typing.Any, *, as_str: bool = True) -> str | bytes:
|
|
@@ -215,12 +261,8 @@ __all__ = (
|
|
|
215
261
|
"get_origin",
|
|
216
262
|
"repr_type",
|
|
217
263
|
"msgspec_convert",
|
|
218
|
-
"datetime_dec_hook",
|
|
219
|
-
"datetime_enc_hook",
|
|
220
264
|
"option_dec_hook",
|
|
221
|
-
"option_enc_hook",
|
|
222
265
|
"variative_dec_hook",
|
|
223
|
-
"variative_enc_hook",
|
|
224
266
|
"datetime",
|
|
225
267
|
"decoder",
|
|
226
268
|
"encoder",
|
telegrinder/node/source.py
CHANGED
|
@@ -2,7 +2,6 @@ import dataclasses
|
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
from telegrinder.api import API
|
|
5
|
-
from telegrinder.msgspec_utils import Nothing, Option
|
|
6
5
|
from telegrinder.types import Chat, Message
|
|
7
6
|
|
|
8
7
|
from .base import DataNode
|
|
@@ -13,14 +12,14 @@ from .message import MessageNode
|
|
|
13
12
|
class Source(DataNode):
|
|
14
13
|
api: API
|
|
15
14
|
chat: Chat
|
|
16
|
-
thread_id:
|
|
15
|
+
thread_id: int | None = None
|
|
17
16
|
|
|
18
17
|
@classmethod
|
|
19
18
|
async def compose(cls, message: MessageNode) -> typing.Self:
|
|
20
19
|
return cls(
|
|
21
20
|
api=message.ctx_api,
|
|
22
21
|
chat=message.chat,
|
|
23
|
-
thread_id=message.message_thread_id,
|
|
22
|
+
thread_id=message.message_thread_id.unwrap_or_none(),
|
|
24
23
|
)
|
|
25
24
|
|
|
26
25
|
async def send(self, text: str) -> Message:
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@ from .formatting import (
|
|
|
14
14
|
StartBotLink,
|
|
15
15
|
StartGroupLink,
|
|
16
16
|
TgEmoji,
|
|
17
|
+
UserOpenMessage,
|
|
17
18
|
block_quote,
|
|
18
19
|
bold,
|
|
19
20
|
channel_boost_link,
|
|
@@ -37,6 +38,8 @@ from .formatting import (
|
|
|
37
38
|
strike,
|
|
38
39
|
tg_emoji,
|
|
39
40
|
underline,
|
|
41
|
+
user_open_message,
|
|
42
|
+
user_open_message_link,
|
|
40
43
|
)
|
|
41
44
|
from .global_context import (
|
|
42
45
|
ABCGlobalContext,
|
|
@@ -109,6 +112,7 @@ __all__ = (
|
|
|
109
112
|
"StartGroupLink",
|
|
110
113
|
"TelegrinderCtx",
|
|
111
114
|
"TgEmoji",
|
|
115
|
+
"UserOpenMessage",
|
|
112
116
|
"block_quote",
|
|
113
117
|
"bold",
|
|
114
118
|
"channel_boost_link",
|
|
@@ -135,4 +139,6 @@ __all__ = (
|
|
|
135
139
|
"strike",
|
|
136
140
|
"tg_emoji",
|
|
137
141
|
"underline",
|
|
142
|
+
"user_open_message",
|
|
143
|
+
"user_open_message_link",
|
|
138
144
|
)
|
telegrinder/tools/buttons.py
CHANGED
|
@@ -26,13 +26,11 @@ class DataclassInstance(typing.Protocol):
|
|
|
26
26
|
class BaseButton:
|
|
27
27
|
def get_data(self) -> dict[str, typing.Any]:
|
|
28
28
|
return {
|
|
29
|
-
k: v
|
|
30
|
-
if k != "callback_data" or isinstance(v, str)
|
|
31
|
-
else encoder.encode(v)
|
|
29
|
+
k: v if k != "callback_data" or isinstance(v, str) else encoder.encode(v)
|
|
32
30
|
for k, v in dataclasses.asdict(self).items()
|
|
33
31
|
if v is not None
|
|
34
32
|
}
|
|
35
|
-
|
|
33
|
+
|
|
36
34
|
|
|
37
35
|
class RowButtons(typing.Generic[ButtonT]):
|
|
38
36
|
buttons: list[ButtonT]
|
|
@@ -65,18 +63,15 @@ class InlineButton(BaseButton):
|
|
|
65
63
|
url: str | None = None
|
|
66
64
|
login_url: dict[str, typing.Any] | LoginUrl | None = None
|
|
67
65
|
pay: bool | None = None
|
|
68
|
-
callback_data:
|
|
69
|
-
str,
|
|
70
|
-
|
|
71
|
-
DataclassInstance,
|
|
72
|
-
msgspec.Struct,
|
|
73
|
-
] | None = None
|
|
66
|
+
callback_data: (
|
|
67
|
+
str | dict[str, typing.Any] | DataclassInstance | msgspec.Struct | None
|
|
68
|
+
) = None
|
|
74
69
|
callback_game: dict[str, typing.Any] | CallbackGame | None = None
|
|
75
70
|
switch_inline_query: str | None = None
|
|
76
71
|
switch_inline_query_current_chat: str | None = None
|
|
77
|
-
switch_inline_query_chosen_chat:
|
|
78
|
-
str, typing.Any
|
|
79
|
-
|
|
72
|
+
switch_inline_query_chosen_chat: (
|
|
73
|
+
dict[str, typing.Any] | SwitchInlineQueryChosenChat | None
|
|
74
|
+
) = None
|
|
80
75
|
web_app: dict[str, typing.Any] | WebAppInfo | None = None
|
|
81
76
|
|
|
82
77
|
|
|
@@ -74,9 +74,9 @@ class Catcher(typing.Generic[EventT]):
|
|
|
74
74
|
|
|
75
75
|
def match_exception(self, exception: BaseException) -> bool:
|
|
76
76
|
for exc in self.exceptions:
|
|
77
|
-
if isinstance(exc, type) and type(exception)
|
|
77
|
+
if isinstance(exc, type) and type(exception) is exc:
|
|
78
78
|
return True
|
|
79
|
-
if isinstance(exc, object) and type(exception)
|
|
79
|
+
if isinstance(exc, object) and type(exception) is type(exc):
|
|
80
80
|
return True if not exc.args else exc.args == exception.args
|
|
81
81
|
return False
|
|
82
82
|
|
|
@@ -18,6 +18,7 @@ from .html import (
|
|
|
18
18
|
strike,
|
|
19
19
|
tg_emoji,
|
|
20
20
|
underline,
|
|
21
|
+
user_open_message,
|
|
21
22
|
)
|
|
22
23
|
from .links import (
|
|
23
24
|
get_channel_boost_link,
|
|
@@ -26,6 +27,7 @@ from .links import (
|
|
|
26
27
|
get_resolve_domain_link,
|
|
27
28
|
get_start_bot_link,
|
|
28
29
|
get_start_group_link,
|
|
30
|
+
user_open_message_link,
|
|
29
31
|
)
|
|
30
32
|
from .spec_html_formats import (
|
|
31
33
|
BaseSpecFormat,
|
|
@@ -39,6 +41,7 @@ from .spec_html_formats import (
|
|
|
39
41
|
StartBotLink,
|
|
40
42
|
StartGroupLink,
|
|
41
43
|
TgEmoji,
|
|
44
|
+
UserOpenMessage,
|
|
42
45
|
)
|
|
43
46
|
|
|
44
47
|
__all__ = (
|
|
@@ -55,6 +58,7 @@ __all__ = (
|
|
|
55
58
|
"StartBotLink",
|
|
56
59
|
"StartGroupLink",
|
|
57
60
|
"TgEmoji",
|
|
61
|
+
"UserOpenMessage",
|
|
58
62
|
"block_quote",
|
|
59
63
|
"bold",
|
|
60
64
|
"channel_boost_link",
|
|
@@ -78,4 +82,6 @@ __all__ = (
|
|
|
78
82
|
"strike",
|
|
79
83
|
"tg_emoji",
|
|
80
84
|
"underline",
|
|
85
|
+
"user_open_message",
|
|
86
|
+
"user_open_message_link",
|
|
81
87
|
)
|
|
@@ -13,6 +13,7 @@ from .links import (
|
|
|
13
13
|
get_resolve_domain_link,
|
|
14
14
|
get_start_bot_link,
|
|
15
15
|
get_start_group_link,
|
|
16
|
+
user_open_message_link,
|
|
16
17
|
)
|
|
17
18
|
from .spec_html_formats import SpecialFormat, is_spec_format
|
|
18
19
|
|
|
@@ -274,6 +275,14 @@ def underline(string: str) -> TagFormat:
|
|
|
274
275
|
return TagFormat(string, tag="u")
|
|
275
276
|
|
|
276
277
|
|
|
278
|
+
def user_open_message(
|
|
279
|
+
user_id: int,
|
|
280
|
+
message: str | None = None,
|
|
281
|
+
string: str | None = None,
|
|
282
|
+
) -> TagFormat:
|
|
283
|
+
return link(user_open_message_link(user_id, message), string)
|
|
284
|
+
|
|
285
|
+
|
|
277
286
|
__all__ = (
|
|
278
287
|
"FormatString",
|
|
279
288
|
"HTMLFormatter",
|
|
@@ -301,4 +310,5 @@ __all__ = (
|
|
|
301
310
|
"strike",
|
|
302
311
|
"tg_emoji",
|
|
303
312
|
"underline",
|
|
313
|
+
"user_open_message",
|
|
304
314
|
)
|
|
@@ -22,6 +22,12 @@ def get_invite_chat_link(invite_link: str) -> str:
|
|
|
22
22
|
return f"tg://join?invite={invite_link}"
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def user_open_message_link(user_id: int, message: str | None = None) -> str:
|
|
26
|
+
return f"tg://openmessage?user_id={user_id}" + (
|
|
27
|
+
"" if not message else f"&msg?text={message}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
25
31
|
__all__ = (
|
|
26
32
|
"get_channel_boost_link",
|
|
27
33
|
"get_invite_chat_link",
|
|
@@ -29,4 +35,5 @@ __all__ = (
|
|
|
29
35
|
"get_resolve_domain_link",
|
|
30
36
|
"get_start_bot_link",
|
|
31
37
|
"get_start_group_link",
|
|
38
|
+
"user_open_message_link",
|
|
32
39
|
)
|