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.

@@ -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 get_event_raw(cls, update: Update) -> Option[Model]:
64
- match cls.get_event_type():
65
- case Some(event_type):
66
- for field in update.__struct_fields__:
67
- event_raw = getattr(update, field)
68
- if isinstance(event_raw, Some | Nothing):
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[ABCMiddleware[EventType]]):
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
- return bool(self.get_event_raw(event))
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
- event_type(**event_raw.to_dict(), api=api),
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
- return "<{}: adapt Update.{} -> {}>".format(
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
- self.event_name,
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
 
@@ -1,4 +1,4 @@
1
- from .abc import ABCClient, ClientData
1
+ from .abc import ABCClient
2
2
  from .aiohttp import AiohttpClient
3
3
 
4
- __all__ = ("ABCClient", "AiohttpClient", "ClientData")
4
+ __all__ = ("ABCClient", "AiohttpClient")
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, url: str, method: str = "GET", data: dict | None = None, **kwargs
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, url: str, method: str = "GET", data: dict | None = None, **kwargs
21
- ) -> dict:
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, url: str, method: str = "GET", data: dict | None = None, **kwargs
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, url: str, method: str = "GET", data: dict | None = None, **kwargs
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(cls, data: dict) -> typing.Any:
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) -> "ABCClient":
63
+ async def __aenter__(self) -> typing.Self:
46
64
  return self
47
65
 
48
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
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", "ClientData")
75
+ __all__ = ("ABCClient",)
@@ -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(cls, data: dict) -> aiohttp.formdata.FormData:
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
- params = {}
108
- if isinstance(v, tuple):
109
- params["filename"], v = v[0], v[1]
110
- else:
111
- v = str(v)
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
 
@@ -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], object], object]
26
- EncHook: typing.TypeAlias = typing.Callable[[T], object]
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 option_dec_hook(tp: type["Option[typing.Any]"], obj: typing.Any) -> typing.Any:
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 option_enc_hook(obj: "Option[typing.Any]") -> typing.Any | None:
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 typing.cast(typing.Any, obj.v)
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[type | types.UnionType, DecHook[typing.Any]] = {
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
  )
@@ -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,
@@ -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
- request_poll: dict | None = None
47
- web_app: dict | None = None
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
- web_app: dict | None = None
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__ = (
@@ -152,17 +152,19 @@ class ReactionEmoji(str, enum.Enum):
152
152
  ENRAGED_FACE = "😡"
153
153
 
154
154
 
155
- class DefaultUserColor(int, enum.Enum):
156
- """Type of DefaultUserColor.
155
+ class DefaultAccentColor(int, enum.Enum):
156
+ """Type of DefaultAccentColor.
157
157
 
158
158
  One of 7 possible user colors:
159
- - red
160
- - orange
161
- - purple
162
- - green
163
- - cyan
164
- - blue
165
- - pink
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