telegrinder 0.1.dev158__py3-none-any.whl → 0.1.dev159__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/abc.py +4 -4
- telegrinder/api/api.py +25 -11
- telegrinder/bot/cute_types/base.py +100 -9
- telegrinder/bot/cute_types/callback_query.py +421 -28
- telegrinder/bot/cute_types/inline_query.py +13 -13
- telegrinder/bot/cute_types/message.py +2914 -102
- telegrinder/bot/cute_types/update.py +14 -7
- telegrinder/bot/cute_types/utils.py +794 -0
- telegrinder/bot/dispatch/view/abc.py +22 -15
- telegrinder/bot/rules/adapter/event.py +12 -6
- telegrinder/client/__init__.py +2 -2
- telegrinder/client/abc.py +35 -12
- telegrinder/client/aiohttp.py +35 -22
- telegrinder/model.py +70 -22
- telegrinder/modules.py +2 -2
- telegrinder/msgspec_utils.py +28 -8
- telegrinder/tools/__init__.py +0 -27
- telegrinder/tools/buttons.py +19 -5
- telegrinder/types/enums.py +11 -9
- telegrinder/types/methods.py +311 -152
- telegrinder/types/objects.py +109 -67
- {telegrinder-0.1.dev158.dist-info → telegrinder-0.1.dev159.dist-info}/METADATA +3 -2
- {telegrinder-0.1.dev158.dist-info → telegrinder-0.1.dev159.dist-info}/RECORD +25 -25
- {telegrinder-0.1.dev158.dist-info → telegrinder-0.1.dev159.dist-info}/WHEEL +1 -1
- telegrinder/tools/inline_query.py +0 -684
- {telegrinder-0.1.dev158.dist-info → telegrinder-0.1.dev159.dist-info}/LICENSE +0 -0
|
@@ -16,6 +16,7 @@ from telegrinder.msgspec_utils import Option
|
|
|
16
16
|
from telegrinder.types.objects import Update
|
|
17
17
|
|
|
18
18
|
EventType = typing.TypeVar("EventType", bound=BaseCute)
|
|
19
|
+
MiddlewareT = typing.TypeVar("MiddlewareT", bound=ABCMiddleware)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ABCView(ABC):
|
|
@@ -60,16 +61,12 @@ class BaseView(ABCView, typing.Generic[EventType]):
|
|
|
60
61
|
return Nothing()
|
|
61
62
|
|
|
62
63
|
@classmethod
|
|
63
|
-
def
|
|
64
|
-
match
|
|
65
|
-
case Some(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
event_raw = event_raw.unwrap_or_none()
|
|
70
|
-
if event_raw is not None and issubclass(event_type, event_raw.__class__):
|
|
71
|
-
return Some(event_raw)
|
|
72
|
-
return Nothing()
|
|
64
|
+
def get_raw_event(cls, update: Update) -> Option[Model]:
|
|
65
|
+
match update.update_type:
|
|
66
|
+
case Some(update_type):
|
|
67
|
+
return getattr(update, update_type.value)
|
|
68
|
+
case _:
|
|
69
|
+
return Nothing()
|
|
73
70
|
|
|
74
71
|
def __call__(
|
|
75
72
|
self,
|
|
@@ -96,20 +93,30 @@ class BaseView(ABCView, typing.Generic[EventType]):
|
|
|
96
93
|
return wrapper
|
|
97
94
|
|
|
98
95
|
def register_middleware(self, *args: typing.Any, **kwargs: typing.Any):
|
|
99
|
-
def wrapper(cls: type[
|
|
96
|
+
def wrapper(cls: type[MiddlewareT]):
|
|
100
97
|
self.middlewares.append(cls(*args, **kwargs))
|
|
101
98
|
return cls
|
|
102
99
|
|
|
103
100
|
return wrapper
|
|
104
101
|
|
|
105
102
|
async def check(self, event: Update) -> bool:
|
|
106
|
-
|
|
103
|
+
match self.get_raw_event(event):
|
|
104
|
+
case Some(e) if issubclass(
|
|
105
|
+
self.get_event_type().expect(
|
|
106
|
+
"{!r} has no event type in generic.".format(self.__class__.__name__),
|
|
107
|
+
),
|
|
108
|
+
e.__class__,
|
|
109
|
+
):
|
|
110
|
+
return True
|
|
111
|
+
case _:
|
|
112
|
+
return False
|
|
107
113
|
|
|
108
114
|
async def process(self, event: Update, api: ABCAPI) -> bool:
|
|
109
|
-
event_raw = self.get_event_raw(event).unwrap()
|
|
110
|
-
event_type = self.get_event_type().unwrap()
|
|
111
115
|
return await process_inner(
|
|
112
|
-
|
|
116
|
+
self.get_event_type().unwrap().from_update(
|
|
117
|
+
update=self.get_raw_event(event).unwrap(),
|
|
118
|
+
bound_api=api,
|
|
119
|
+
),
|
|
113
120
|
event,
|
|
114
121
|
self.middlewares,
|
|
115
122
|
self.handlers,
|
|
@@ -14,14 +14,20 @@ CuteT = typing.TypeVar("CuteT", bound=BaseCute)
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
17
|
-
def __init__(self, event_name: str, model: type[CuteT]):
|
|
17
|
+
def __init__(self, event_name: str, model: type[CuteT]) -> None:
|
|
18
18
|
self.event_name = event_name
|
|
19
19
|
self.model = model
|
|
20
20
|
|
|
21
21
|
def __repr__(self) -> str:
|
|
22
|
-
|
|
22
|
+
raw_update_type = Update.__annotations__.get(self.event_name, "Unknown")
|
|
23
|
+
raw_update_type = (
|
|
24
|
+
typing.get_args(raw_update_type)[0].__forward_arg__
|
|
25
|
+
if typing.get_args(raw_update_type)
|
|
26
|
+
else raw_update_type
|
|
27
|
+
)
|
|
28
|
+
return "<{}: adapt {} -> {}>".format(
|
|
23
29
|
self.__class__.__name__,
|
|
24
|
-
|
|
30
|
+
raw_update_type,
|
|
25
31
|
self.model.__name__,
|
|
26
32
|
)
|
|
27
33
|
|
|
@@ -29,14 +35,14 @@ class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
|
29
35
|
update_dct = update.to_dict()
|
|
30
36
|
if self.event_name not in update_dct:
|
|
31
37
|
return Error(
|
|
32
|
-
AdapterError(f"Update is not of event type {self.event_name!r}.")
|
|
38
|
+
AdapterError(f"Update is not of event type {self.event_name!r}."),
|
|
33
39
|
)
|
|
34
40
|
if update_dct[self.event_name] is Nothing:
|
|
35
41
|
return Error(
|
|
36
|
-
AdapterError(f"Update is not an {self.event_name!r}.")
|
|
42
|
+
AdapterError(f"Update is not an {self.event_name!r}."),
|
|
37
43
|
)
|
|
38
44
|
return Ok(
|
|
39
|
-
self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api)
|
|
45
|
+
self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api),
|
|
40
46
|
)
|
|
41
47
|
|
|
42
48
|
|
telegrinder/client/__init__.py
CHANGED
telegrinder/client/abc.py
CHANGED
|
@@ -1,35 +1,49 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
|
|
4
|
-
ClientData = typing.Any
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
class ABCClient(ABC):
|
|
8
6
|
@abstractmethod
|
|
9
|
-
def __init__(self, *args, **kwargs):
|
|
7
|
+
def __init__(self, *args: typing.Any, **kwargs: typing.Any):
|
|
10
8
|
pass
|
|
11
9
|
|
|
12
10
|
@abstractmethod
|
|
13
11
|
async def request_text(
|
|
14
|
-
self,
|
|
12
|
+
self,
|
|
13
|
+
url: str,
|
|
14
|
+
method: str = "GET",
|
|
15
|
+
data: dict[str, typing.Any] | None = None,
|
|
16
|
+
**kwargs: typing.Any,
|
|
15
17
|
) -> str:
|
|
16
18
|
pass
|
|
17
19
|
|
|
18
20
|
@abstractmethod
|
|
19
21
|
async def request_json(
|
|
20
|
-
self,
|
|
21
|
-
|
|
22
|
+
self,
|
|
23
|
+
url: str,
|
|
24
|
+
method: str = "GET",
|
|
25
|
+
data: dict[str, typing.Any] | None = None,
|
|
26
|
+
**kwargs: typing.Any,
|
|
27
|
+
) -> dict[str, typing.Any]:
|
|
22
28
|
pass
|
|
23
29
|
|
|
24
30
|
@abstractmethod
|
|
25
31
|
async def request_content(
|
|
26
|
-
self,
|
|
32
|
+
self,
|
|
33
|
+
url: str,
|
|
34
|
+
method: str = "GET",
|
|
35
|
+
data: dict[str, typing.Any] | None = None,
|
|
36
|
+
**kwargs: typing.Any,
|
|
27
37
|
) -> bytes:
|
|
28
38
|
pass
|
|
29
39
|
|
|
30
40
|
@abstractmethod
|
|
31
41
|
async def request_bytes(
|
|
32
|
-
self,
|
|
42
|
+
self,
|
|
43
|
+
url: str,
|
|
44
|
+
method: str = "GET",
|
|
45
|
+
data: dict[str, typing.Any] | None = None,
|
|
46
|
+
**kwargs: typing.Any,
|
|
33
47
|
) -> bytes:
|
|
34
48
|
pass
|
|
35
49
|
|
|
@@ -39,14 +53,23 @@ class ABCClient(ABC):
|
|
|
39
53
|
|
|
40
54
|
@classmethod
|
|
41
55
|
@abstractmethod
|
|
42
|
-
def get_form(
|
|
56
|
+
def get_form(
|
|
57
|
+
cls,
|
|
58
|
+
data: dict[str, typing.Any],
|
|
59
|
+
files: dict[str, tuple[str, bytes]] | None = None,
|
|
60
|
+
) -> typing.Any:
|
|
43
61
|
pass
|
|
44
62
|
|
|
45
|
-
async def __aenter__(self) ->
|
|
63
|
+
async def __aenter__(self) -> typing.Self:
|
|
46
64
|
return self
|
|
47
65
|
|
|
48
|
-
async def __aexit__(
|
|
66
|
+
async def __aexit__(
|
|
67
|
+
self,
|
|
68
|
+
exc_type: type[BaseException],
|
|
69
|
+
exc_val: typing.Any,
|
|
70
|
+
exc_tb: typing.Any,
|
|
71
|
+
) -> None:
|
|
49
72
|
await self.close()
|
|
50
73
|
|
|
51
74
|
|
|
52
|
-
__all__ = ("ABCClient",
|
|
75
|
+
__all__ = ("ABCClient",)
|
telegrinder/client/aiohttp.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import secrets
|
|
1
2
|
import ssl
|
|
2
3
|
import typing
|
|
3
4
|
|
|
@@ -18,19 +19,27 @@ class AiohttpClient(ABCClient):
|
|
|
18
19
|
session: ClientSession | None = None,
|
|
19
20
|
json_processing_module: JSONModule | None = None,
|
|
20
21
|
timeout: aiohttp.ClientTimeout | None = None,
|
|
21
|
-
**session_params,
|
|
22
|
-
):
|
|
22
|
+
**session_params: typing.Any,
|
|
23
|
+
) -> None:
|
|
23
24
|
self.session = session
|
|
24
25
|
self.json_processing_module = json_processing_module or json
|
|
25
26
|
self.session_params = session_params
|
|
26
27
|
self.timeout = timeout or aiohttp.ClientTimeout(total=0)
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return "<{}: session={!r}, timeout={}, closed={}>".format(
|
|
31
|
+
self.__class__.__name__,
|
|
32
|
+
self.session,
|
|
33
|
+
self.timeout,
|
|
34
|
+
False if self.session is None else self.session.closed,
|
|
35
|
+
)
|
|
36
|
+
|
|
28
37
|
async def request_raw(
|
|
29
38
|
self,
|
|
30
39
|
url: str,
|
|
31
40
|
method: str = "GET",
|
|
32
|
-
data: dict | None = None,
|
|
33
|
-
**kwargs,
|
|
41
|
+
data: dict[str, typing.Any] | None = None,
|
|
42
|
+
**kwargs: typing.Any,
|
|
34
43
|
) -> "ClientResponse":
|
|
35
44
|
if not self.session:
|
|
36
45
|
self.session = ClientSession(
|
|
@@ -54,9 +63,9 @@ class AiohttpClient(ABCClient):
|
|
|
54
63
|
self,
|
|
55
64
|
url: str,
|
|
56
65
|
method: str = "GET",
|
|
57
|
-
data: dict | None = None,
|
|
58
|
-
**kwargs,
|
|
59
|
-
) -> dict:
|
|
66
|
+
data: dict[str, typing.Any] | None = None,
|
|
67
|
+
**kwargs: typing.Any,
|
|
68
|
+
) -> dict[str, typing.Any]:
|
|
60
69
|
response = await self.request_raw(url, method, data, **kwargs)
|
|
61
70
|
return await response.json(
|
|
62
71
|
encoding="utf-8",
|
|
@@ -68,8 +77,8 @@ class AiohttpClient(ABCClient):
|
|
|
68
77
|
self,
|
|
69
78
|
url: str,
|
|
70
79
|
method: str = "GET",
|
|
71
|
-
data: dict | aiohttp.FormData | None = None,
|
|
72
|
-
**kwargs,
|
|
80
|
+
data: dict[str, typing.Any] | aiohttp.FormData | None = None,
|
|
81
|
+
**kwargs: typing.Any,
|
|
73
82
|
) -> str:
|
|
74
83
|
response = await self.request_raw(url, method, data, **kwargs) # type: ignore
|
|
75
84
|
return await response.text(encoding="utf-8")
|
|
@@ -78,8 +87,8 @@ class AiohttpClient(ABCClient):
|
|
|
78
87
|
self,
|
|
79
88
|
url: str,
|
|
80
89
|
method: str = "GET",
|
|
81
|
-
data: dict | aiohttp.FormData | None = None,
|
|
82
|
-
**kwargs,
|
|
90
|
+
data: dict[str, typing.Any] | aiohttp.FormData | None = None,
|
|
91
|
+
**kwargs: typing.Any,
|
|
83
92
|
) -> bytes:
|
|
84
93
|
response = await self.request_raw(url, method, data, **kwargs) # type: ignore
|
|
85
94
|
if response._body is None:
|
|
@@ -90,8 +99,8 @@ class AiohttpClient(ABCClient):
|
|
|
90
99
|
self,
|
|
91
100
|
url: str,
|
|
92
101
|
method: str = "GET",
|
|
93
|
-
data: dict | None = None,
|
|
94
|
-
**kwargs,
|
|
102
|
+
data: dict[str, typing.Any] | None = None,
|
|
103
|
+
**kwargs: typing.Any,
|
|
95
104
|
) -> bytes:
|
|
96
105
|
response = await self.request_raw(url, method, data, **kwargs)
|
|
97
106
|
return response._body
|
|
@@ -101,18 +110,22 @@ class AiohttpClient(ABCClient):
|
|
|
101
110
|
await self.session.close()
|
|
102
111
|
|
|
103
112
|
@classmethod
|
|
104
|
-
def get_form(
|
|
113
|
+
def get_form(
|
|
114
|
+
cls,
|
|
115
|
+
data: dict[str, typing.Any],
|
|
116
|
+
files: dict[str, tuple[str, bytes]] | None = None,
|
|
117
|
+
) -> aiohttp.formdata.FormData:
|
|
118
|
+
files = files or {}
|
|
105
119
|
form = aiohttp.formdata.FormData(quote_fields=False)
|
|
106
120
|
for k, v in data.items():
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
form.add_field(k, v, **params)
|
|
121
|
+
form.add_field(k, str(v))
|
|
122
|
+
|
|
123
|
+
for n, f in files.items():
|
|
124
|
+
form.add_field(n, f[1], filename=f[0])
|
|
125
|
+
|
|
113
126
|
return form
|
|
114
127
|
|
|
115
|
-
def __del__(self):
|
|
128
|
+
def __del__(self) -> None:
|
|
116
129
|
if self.session and not self.session.closed:
|
|
117
130
|
if self.session._connector is not None and self.session._connector_owner:
|
|
118
131
|
self.session._connector.close()
|
telegrinder/model.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import enum
|
|
3
|
+
import secrets
|
|
1
4
|
import typing
|
|
5
|
+
from datetime import datetime
|
|
2
6
|
from types import NoneType
|
|
3
7
|
|
|
4
8
|
import msgspec
|
|
5
9
|
from fntypes.co import Nothing, Result, Some
|
|
6
10
|
|
|
7
|
-
from .msgspec_utils import decoder, encoder
|
|
11
|
+
from .msgspec_utils import decoder, encoder, get_origin
|
|
8
12
|
|
|
9
13
|
T = typing.TypeVar("T")
|
|
10
14
|
|
|
@@ -41,31 +45,12 @@ def full_result(
|
|
|
41
45
|
return result.map(lambda v: decoder.decode(v, type=full_t)) # type: ignore
|
|
42
46
|
|
|
43
47
|
|
|
44
|
-
def convert(d: typing.Any, serialize: bool = True) -> typing.Any:
|
|
45
|
-
if isinstance(d, Model):
|
|
46
|
-
converted_dct = convert(d.to_dict(), serialize=False)
|
|
47
|
-
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
48
|
-
|
|
49
|
-
if isinstance(d, dict):
|
|
50
|
-
return {
|
|
51
|
-
k: convert(v, serialize=serialize)
|
|
52
|
-
for k, v in d.items()
|
|
53
|
-
if type(v) not in (NoneType, Nothing)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if isinstance(d, list):
|
|
57
|
-
converted_lst = [convert(x, serialize=False) for x in d]
|
|
58
|
-
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
59
|
-
|
|
60
|
-
return d
|
|
61
|
-
|
|
62
|
-
|
|
63
48
|
def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
64
49
|
return {
|
|
65
50
|
k: v.unwrap() if v and isinstance(v, Some) else v
|
|
66
51
|
for k, v in (
|
|
67
|
-
*params.items(),
|
|
68
52
|
*params.pop("other", {}).items(),
|
|
53
|
+
*params.items(),
|
|
69
54
|
)
|
|
70
55
|
if k != "self" and type(v) not in (NoneType, Nothing)
|
|
71
56
|
}
|
|
@@ -87,9 +72,72 @@ class Model(msgspec.Struct, **MODEL_CONFIG):
|
|
|
87
72
|
}
|
|
88
73
|
|
|
89
74
|
|
|
75
|
+
@dataclasses.dataclass(kw_only=True)
|
|
76
|
+
class DataConverter:
|
|
77
|
+
files: dict[str, tuple[str, bytes]] = dataclasses.field(default_factory=lambda: {})
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def converters(self) -> dict[
|
|
81
|
+
type[typing.Any], typing.Callable[[typing.Self, typing.Any, bool], typing.Any]
|
|
82
|
+
]:
|
|
83
|
+
return {
|
|
84
|
+
get_origin(value.__annotations__["d"]): value
|
|
85
|
+
for key, value in vars(self.__class__).items()
|
|
86
|
+
if key.startswith("convert_")
|
|
87
|
+
and callable(value)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def get_converter(
|
|
91
|
+
self, t: type[typing.Any]
|
|
92
|
+
) -> typing.Callable[[typing.Self, typing.Any, bool], typing.Any] | None:
|
|
93
|
+
for type, converter in self.converters.items():
|
|
94
|
+
if issubclass(t, type):
|
|
95
|
+
return converter
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def convert_model(self, d: Model, serialize: bool = True) -> str | dict[str, typing.Any]:
|
|
99
|
+
converted_dct = self.convert(d.to_dict(), serialize=False)
|
|
100
|
+
return encoder.encode(converted_dct) if serialize is True else converted_dct
|
|
101
|
+
|
|
102
|
+
def convert_dct(self, d: dict[str, typing.Any], serialize: bool = True) -> dict[str, typing.Any]:
|
|
103
|
+
return {
|
|
104
|
+
k: self.convert(v, serialize=serialize)
|
|
105
|
+
for k, v in d.items()
|
|
106
|
+
if type(v) not in (NoneType, Nothing)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def convert_lst(self, d: list[typing.Any], serialize: bool = True) -> str | list[typing.Any]:
|
|
110
|
+
converted_lst = [self.convert(x, serialize=False) for x in d]
|
|
111
|
+
return encoder.encode(converted_lst) if serialize is True else converted_lst
|
|
112
|
+
|
|
113
|
+
def convert_tpl(self, d: tuple[typing.Any, ...], serialize: bool = True) -> str | tuple[typing.Any, ...]:
|
|
114
|
+
if (
|
|
115
|
+
isinstance(d, tuple)
|
|
116
|
+
and len(d) == 2
|
|
117
|
+
and isinstance(d[0], str)
|
|
118
|
+
and isinstance(d[1], bytes)
|
|
119
|
+
):
|
|
120
|
+
attach_name = secrets.token_urlsafe(16)
|
|
121
|
+
self.files[attach_name] = d
|
|
122
|
+
return "attach://{}".format(attach_name)
|
|
123
|
+
return d
|
|
124
|
+
|
|
125
|
+
def convert_enum(self, d: enum.Enum, serialize: bool = True) -> str:
|
|
126
|
+
return d.value
|
|
127
|
+
|
|
128
|
+
def convert_datetime(self, d: datetime, serialize: bool = True) -> int:
|
|
129
|
+
return int(d.timestamp())
|
|
130
|
+
|
|
131
|
+
def convert(self, data: typing.Any, *, serialize: bool = True) -> typing.Any:
|
|
132
|
+
converter = self.get_converter(get_origin(type(data)))
|
|
133
|
+
if converter is not None:
|
|
134
|
+
return converter(self, data, serialize)
|
|
135
|
+
return data
|
|
136
|
+
|
|
137
|
+
|
|
90
138
|
__all__ = (
|
|
139
|
+
"DataConverter",
|
|
91
140
|
"Model",
|
|
92
|
-
"convert",
|
|
93
141
|
"full_result",
|
|
94
142
|
"get_params",
|
|
95
143
|
"MODEL_CONFIG",
|
telegrinder/modules.py
CHANGED
|
@@ -5,10 +5,10 @@ from choicelib import choice_in_order
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class JSONModule(typing.Protocol):
|
|
8
|
-
def loads(self, s: str) -> dict | list:
|
|
8
|
+
def loads(self, s: str) -> dict[str, typing.Any] | list[typing.Any]:
|
|
9
9
|
...
|
|
10
10
|
|
|
11
|
-
def dumps(self, o: dict | list) -> str:
|
|
11
|
+
def dumps(self, o: dict[str, typing.Any] | list[typing.Any]) -> str:
|
|
12
12
|
...
|
|
13
13
|
|
|
14
14
|
|
telegrinder/msgspec_utils.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import types # noqa: TCH003
|
|
2
1
|
import typing
|
|
3
2
|
|
|
4
3
|
import fntypes.option
|
|
@@ -9,11 +8,17 @@ T = typing.TypeVar("T")
|
|
|
9
8
|
Ts = typing.TypeVarTuple("Ts")
|
|
10
9
|
|
|
11
10
|
if typing.TYPE_CHECKING:
|
|
11
|
+
import types
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
12
14
|
from fntypes.option import Option
|
|
13
15
|
else:
|
|
16
|
+
from datetime import datetime as dt
|
|
14
17
|
|
|
15
18
|
Value = typing.TypeVar("Value")
|
|
16
19
|
|
|
20
|
+
datetime = type("datetime", (dt,), {})
|
|
21
|
+
|
|
17
22
|
class OptionMeta(type):
|
|
18
23
|
def __instancecheck__(cls, __instance: typing.Any) -> bool:
|
|
19
24
|
return isinstance(__instance, fntypes.option.Some | fntypes.option.Nothing)
|
|
@@ -22,8 +27,8 @@ else:
|
|
|
22
27
|
class Option(typing.Generic[Value], metaclass=OptionMeta):
|
|
23
28
|
pass
|
|
24
29
|
|
|
25
|
-
DecHook: typing.TypeAlias = typing.Callable[[type[T],
|
|
26
|
-
EncHook: typing.TypeAlias = typing.Callable[[T],
|
|
30
|
+
DecHook: typing.TypeAlias = typing.Callable[[type[T], typing.Any], object]
|
|
31
|
+
EncHook: typing.TypeAlias = typing.Callable[[T], typing.Any]
|
|
27
32
|
|
|
28
33
|
Nothing: typing.Final[fntypes.option.Nothing] = fntypes.option.Nothing()
|
|
29
34
|
|
|
@@ -43,7 +48,11 @@ def msgspec_convert(obj: typing.Any, t: type[T]) -> Result[T, msgspec.Validation
|
|
|
43
48
|
return Error(exc)
|
|
44
49
|
|
|
45
50
|
|
|
46
|
-
def
|
|
51
|
+
def datetime_dec_hook(tp: type[datetime], obj: int | float) -> datetime:
|
|
52
|
+
return tp.fromtimestamp(obj)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
|
|
47
56
|
if obj is None:
|
|
48
57
|
return Nothing
|
|
49
58
|
generic_args = typing.get_args(tp)
|
|
@@ -85,19 +94,26 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
|
|
|
85
94
|
)
|
|
86
95
|
|
|
87
96
|
|
|
88
|
-
def
|
|
97
|
+
def datetime_enc_hook(obj: datetime) -> int:
|
|
98
|
+
return int(obj.timestamp())
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def option_enc_hook(obj: Option[typing.Any]) -> typing.Any | None:
|
|
89
102
|
return obj.value if isinstance(obj, fntypes.option.Some) else None
|
|
90
103
|
|
|
91
104
|
|
|
92
|
-
def variative_enc_hook(obj: Variative) -> typing.Any:
|
|
93
|
-
return
|
|
105
|
+
def variative_enc_hook(obj: Variative[typing.Any]) -> typing.Any:
|
|
106
|
+
return obj.v
|
|
94
107
|
|
|
95
108
|
|
|
96
109
|
class Decoder:
|
|
97
110
|
def __init__(self) -> None:
|
|
98
|
-
self.dec_hooks: dict[
|
|
111
|
+
self.dec_hooks: dict[
|
|
112
|
+
typing.Union[type[typing.Any], "types.UnionType"], DecHook[typing.Any]
|
|
113
|
+
] = {
|
|
99
114
|
Option: option_dec_hook,
|
|
100
115
|
Variative: variative_dec_hook,
|
|
116
|
+
datetime: datetime_dec_hook,
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
def add_dec_hook(self, tp: type[T]):
|
|
@@ -156,6 +172,7 @@ class Encoder:
|
|
|
156
172
|
fntypes.option.Some: option_enc_hook,
|
|
157
173
|
fntypes.option.Nothing: option_enc_hook,
|
|
158
174
|
Variative: variative_enc_hook,
|
|
175
|
+
datetime: datetime_enc_hook,
|
|
159
176
|
}
|
|
160
177
|
|
|
161
178
|
def add_dec_hook(self, tp: type[T]):
|
|
@@ -198,10 +215,13 @@ __all__ = (
|
|
|
198
215
|
"get_origin",
|
|
199
216
|
"repr_type",
|
|
200
217
|
"msgspec_convert",
|
|
218
|
+
"datetime_dec_hook",
|
|
219
|
+
"datetime_enc_hook",
|
|
201
220
|
"option_dec_hook",
|
|
202
221
|
"option_enc_hook",
|
|
203
222
|
"variative_dec_hook",
|
|
204
223
|
"variative_enc_hook",
|
|
224
|
+
"datetime",
|
|
205
225
|
"decoder",
|
|
206
226
|
"encoder",
|
|
207
227
|
)
|
telegrinder/tools/__init__.py
CHANGED
|
@@ -54,33 +54,6 @@ from .i18n import (
|
|
|
54
54
|
SimpleI18n,
|
|
55
55
|
SimpleTranslator,
|
|
56
56
|
)
|
|
57
|
-
from .inline_query import (
|
|
58
|
-
inline_query_article,
|
|
59
|
-
inline_query_audio,
|
|
60
|
-
inline_query_cached_audio,
|
|
61
|
-
inline_query_cached_document,
|
|
62
|
-
inline_query_cached_gif,
|
|
63
|
-
inline_query_cached_mpeg4_gif,
|
|
64
|
-
inline_query_cached_photo,
|
|
65
|
-
inline_query_cached_sticker,
|
|
66
|
-
inline_query_cached_video,
|
|
67
|
-
inline_query_cached_voice,
|
|
68
|
-
inline_query_contact,
|
|
69
|
-
inline_query_document,
|
|
70
|
-
inline_query_game,
|
|
71
|
-
inline_query_gif,
|
|
72
|
-
inline_query_location,
|
|
73
|
-
inline_query_mpeg4_gif,
|
|
74
|
-
inline_query_photo,
|
|
75
|
-
inline_query_venue,
|
|
76
|
-
inline_query_video,
|
|
77
|
-
inline_query_voice,
|
|
78
|
-
input_contact_message_content,
|
|
79
|
-
input_invoice_message_content,
|
|
80
|
-
input_location_message_content,
|
|
81
|
-
input_text_message_content,
|
|
82
|
-
input_venue_message_content,
|
|
83
|
-
)
|
|
84
57
|
from .kb_set import KeyboardSetBase, KeyboardSetYAML
|
|
85
58
|
from .keyboard import (
|
|
86
59
|
AnyMarkup,
|
telegrinder/tools/buttons.py
CHANGED
|
@@ -4,6 +4,15 @@ import typing
|
|
|
4
4
|
import msgspec
|
|
5
5
|
|
|
6
6
|
from telegrinder.model import encoder
|
|
7
|
+
from telegrinder.types import (
|
|
8
|
+
CallbackGame,
|
|
9
|
+
KeyboardButtonPollType,
|
|
10
|
+
KeyboardButtonRequestChat,
|
|
11
|
+
KeyboardButtonRequestUsers,
|
|
12
|
+
SwitchInlineQueryChosenChat,
|
|
13
|
+
WebAppInfo,
|
|
14
|
+
)
|
|
15
|
+
from telegrinder.types.objects import LoginUrl
|
|
7
16
|
|
|
8
17
|
ButtonT = typing.TypeVar("ButtonT", bound="BaseButton")
|
|
9
18
|
|
|
@@ -43,8 +52,10 @@ class Button(BaseButton):
|
|
|
43
52
|
_: dataclasses.KW_ONLY
|
|
44
53
|
request_contact: bool = False
|
|
45
54
|
request_location: bool = False
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
request_chat: dict[str, typing.Any] | KeyboardButtonRequestChat | None = None
|
|
56
|
+
request_user: dict[str, typing.Any] | KeyboardButtonRequestUsers | None = None
|
|
57
|
+
request_poll: dict[str, typing.Any] | KeyboardButtonPollType | None = None
|
|
58
|
+
web_app: dict[str, typing.Any] | WebAppInfo | None = None
|
|
48
59
|
|
|
49
60
|
|
|
50
61
|
@dataclasses.dataclass
|
|
@@ -52,7 +63,7 @@ class InlineButton(BaseButton):
|
|
|
52
63
|
text: str
|
|
53
64
|
_: dataclasses.KW_ONLY
|
|
54
65
|
url: str | None = None
|
|
55
|
-
login_url: dict | None = None
|
|
66
|
+
login_url: dict[str, typing.Any] | LoginUrl | None = None
|
|
56
67
|
pay: bool | None = None
|
|
57
68
|
callback_data: typing.Union[
|
|
58
69
|
str,
|
|
@@ -60,10 +71,13 @@ class InlineButton(BaseButton):
|
|
|
60
71
|
DataclassInstance,
|
|
61
72
|
msgspec.Struct,
|
|
62
73
|
] | None = None
|
|
63
|
-
callback_game: dict | None = None
|
|
74
|
+
callback_game: dict[str, typing.Any] | CallbackGame | None = None
|
|
64
75
|
switch_inline_query: str | None = None
|
|
65
76
|
switch_inline_query_current_chat: str | None = None
|
|
66
|
-
|
|
77
|
+
switch_inline_query_chosen_chat: dict[
|
|
78
|
+
str, typing.Any
|
|
79
|
+
] | SwitchInlineQueryChosenChat | None = None
|
|
80
|
+
web_app: dict[str, typing.Any] | WebAppInfo | None = None
|
|
67
81
|
|
|
68
82
|
|
|
69
83
|
__all__ = (
|
telegrinder/types/enums.py
CHANGED
|
@@ -152,17 +152,19 @@ class ReactionEmoji(str, enum.Enum):
|
|
|
152
152
|
ENRAGED_FACE = "😡"
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
class
|
|
156
|
-
"""Type of
|
|
155
|
+
class DefaultAccentColor(int, enum.Enum):
|
|
156
|
+
"""Type of DefaultAccentColor.
|
|
157
157
|
|
|
158
158
|
One of 7 possible user colors:
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
159
|
+
- Red
|
|
160
|
+
- Orange
|
|
161
|
+
- Purple
|
|
162
|
+
- Green
|
|
163
|
+
- Cyan
|
|
164
|
+
- Blue
|
|
165
|
+
- Pink
|
|
166
|
+
|
|
167
|
+
Docs: https://core.telegram.org/bots/api#accent-colors
|
|
166
168
|
"""
|
|
167
169
|
|
|
168
170
|
RED = 0
|