telegrinder 0.3.4.post1__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +30 -31
- telegrinder/api/__init__.py +2 -1
- telegrinder/api/api.py +28 -20
- telegrinder/api/error.py +8 -4
- telegrinder/api/response.py +2 -2
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +6 -0
- telegrinder/bot/bot.py +38 -31
- telegrinder/bot/cute_types/__init__.py +2 -0
- telegrinder/bot/cute_types/base.py +54 -128
- telegrinder/bot/cute_types/callback_query.py +76 -61
- telegrinder/bot/cute_types/chat_join_request.py +4 -3
- telegrinder/bot/cute_types/chat_member_updated.py +28 -31
- telegrinder/bot/cute_types/inline_query.py +5 -4
- telegrinder/bot/cute_types/message.py +555 -602
- telegrinder/bot/cute_types/pre_checkout_query.py +42 -0
- telegrinder/bot/cute_types/update.py +20 -12
- telegrinder/bot/cute_types/utils.py +3 -36
- telegrinder/bot/dispatch/__init__.py +4 -0
- telegrinder/bot/dispatch/abc.py +8 -9
- telegrinder/bot/dispatch/context.py +5 -7
- telegrinder/bot/dispatch/dispatch.py +85 -33
- telegrinder/bot/dispatch/handler/abc.py +5 -6
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -2
- telegrinder/bot/dispatch/handler/base.py +3 -3
- telegrinder/bot/dispatch/handler/document_reply.py +2 -2
- telegrinder/bot/dispatch/handler/func.py +36 -42
- telegrinder/bot/dispatch/handler/media_group_reply.py +5 -4
- telegrinder/bot/dispatch/handler/message_reply.py +2 -2
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -2
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -2
- telegrinder/bot/dispatch/handler/video_reply.py +2 -2
- telegrinder/bot/dispatch/middleware/abc.py +83 -8
- telegrinder/bot/dispatch/middleware/global_middleware.py +70 -0
- telegrinder/bot/dispatch/process.py +44 -50
- telegrinder/bot/dispatch/return_manager/__init__.py +2 -0
- telegrinder/bot/dispatch/return_manager/abc.py +6 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +20 -0
- telegrinder/bot/dispatch/view/__init__.py +2 -0
- telegrinder/bot/dispatch/view/abc.py +10 -6
- telegrinder/bot/dispatch/view/base.py +81 -50
- telegrinder/bot/dispatch/view/box.py +20 -9
- telegrinder/bot/dispatch/view/callback_query.py +3 -4
- telegrinder/bot/dispatch/view/chat_join_request.py +2 -7
- telegrinder/bot/dispatch/view/chat_member.py +3 -5
- telegrinder/bot/dispatch/view/inline_query.py +3 -4
- telegrinder/bot/dispatch/view/message.py +3 -4
- telegrinder/bot/dispatch/view/pre_checkout_query.py +16 -0
- telegrinder/bot/dispatch/view/raw.py +42 -40
- telegrinder/bot/dispatch/waiter_machine/actions.py +5 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +9 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +3 -2
- telegrinder/bot/dispatch/waiter_machine/machine.py +113 -34
- telegrinder/bot/dispatch/waiter_machine/middleware.py +15 -10
- telegrinder/bot/dispatch/waiter_machine/short_state.py +7 -18
- telegrinder/bot/polling/polling.py +62 -54
- telegrinder/bot/rules/__init__.py +24 -1
- telegrinder/bot/rules/abc.py +17 -10
- telegrinder/bot/rules/callback_data.py +20 -61
- telegrinder/bot/rules/chat_join.py +6 -4
- telegrinder/bot/rules/command.py +4 -4
- telegrinder/bot/rules/enum_text.py +1 -4
- telegrinder/bot/rules/func.py +5 -3
- telegrinder/bot/rules/fuzzy.py +1 -1
- telegrinder/bot/rules/id.py +24 -0
- telegrinder/bot/rules/inline.py +6 -4
- telegrinder/bot/rules/integer.py +2 -1
- telegrinder/bot/rules/logic.py +18 -0
- telegrinder/bot/rules/markup.py +5 -6
- telegrinder/bot/rules/message.py +2 -4
- telegrinder/bot/rules/message_entities.py +1 -3
- telegrinder/bot/rules/node.py +15 -9
- telegrinder/bot/rules/payload.py +81 -0
- telegrinder/bot/rules/payment_invoice.py +29 -0
- telegrinder/bot/rules/regex.py +5 -6
- telegrinder/bot/rules/state.py +1 -3
- telegrinder/bot/rules/text.py +10 -5
- telegrinder/bot/rules/update.py +0 -0
- telegrinder/bot/scenario/abc.py +2 -4
- telegrinder/bot/scenario/checkbox.py +12 -14
- telegrinder/bot/scenario/choice.py +6 -9
- telegrinder/client/__init__.py +9 -1
- telegrinder/client/abc.py +35 -10
- telegrinder/client/aiohttp.py +28 -24
- telegrinder/client/form_data.py +31 -0
- telegrinder/client/sonic.py +212 -0
- telegrinder/model.py +38 -145
- telegrinder/modules.py +3 -1
- telegrinder/msgspec_utils.py +136 -68
- telegrinder/node/__init__.py +74 -13
- telegrinder/node/attachment.py +92 -16
- telegrinder/node/base.py +196 -68
- telegrinder/node/callback_query.py +17 -16
- telegrinder/node/command.py +3 -2
- telegrinder/node/composer.py +40 -75
- telegrinder/node/container.py +13 -7
- telegrinder/node/either.py +82 -0
- telegrinder/node/event.py +20 -31
- telegrinder/node/file.py +51 -0
- telegrinder/node/me.py +4 -5
- telegrinder/node/payload.py +78 -0
- telegrinder/node/polymorphic.py +27 -8
- telegrinder/node/rule.py +2 -6
- telegrinder/node/scope.py +4 -6
- telegrinder/node/source.py +37 -21
- telegrinder/node/text.py +20 -8
- telegrinder/node/tools/generator.py +7 -11
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -61
- telegrinder/tools/__init__.py +97 -38
- telegrinder/tools/adapter/__init__.py +19 -0
- telegrinder/tools/adapter/abc.py +49 -0
- telegrinder/tools/adapter/dataclass.py +56 -0
- telegrinder/{bot/rules → tools}/adapter/event.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/node.py +8 -10
- telegrinder/{bot/rules → tools}/adapter/raw_event.py +2 -2
- telegrinder/{bot/rules → tools}/adapter/raw_update.py +2 -2
- telegrinder/tools/buttons.py +52 -26
- telegrinder/tools/callback_data_serilization/__init__.py +5 -0
- telegrinder/tools/callback_data_serilization/abc.py +51 -0
- telegrinder/tools/callback_data_serilization/json_ser.py +60 -0
- telegrinder/tools/callback_data_serilization/msgpack_ser.py +172 -0
- telegrinder/tools/error_handler/abc.py +4 -7
- telegrinder/tools/error_handler/error.py +0 -0
- telegrinder/tools/error_handler/error_handler.py +34 -48
- telegrinder/tools/formatting/__init__.py +57 -37
- telegrinder/tools/formatting/deep_links.py +541 -0
- telegrinder/tools/formatting/{html.py → html_formatter.py} +51 -79
- telegrinder/tools/formatting/spec_html_formats.py +14 -60
- telegrinder/tools/functional.py +1 -5
- telegrinder/tools/global_context/global_context.py +26 -51
- telegrinder/tools/global_context/telegrinder_ctx.py +3 -3
- telegrinder/tools/i18n/abc.py +0 -0
- telegrinder/tools/i18n/middleware/abc.py +3 -6
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard.py +9 -9
- telegrinder/tools/lifespan.py +105 -0
- telegrinder/tools/limited_dict.py +5 -10
- telegrinder/tools/loop_wrapper/abc.py +7 -2
- telegrinder/tools/loop_wrapper/loop_wrapper.py +40 -95
- telegrinder/tools/magic.py +184 -34
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +5 -9
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +13 -0
- telegrinder/types/__init__.py +8 -0
- telegrinder/types/enums.py +31 -21
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +531 -109
- telegrinder/types/objects.py +934 -826
- telegrinder/verification_utils.py +0 -2
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/LICENSE +2 -2
- telegrinder-0.4.0.dist-info/METADATA +144 -0
- telegrinder-0.4.0.dist-info/RECORD +182 -0
- {telegrinder-0.3.4.post1.dist-info → telegrinder-0.4.0.dist-info}/WHEEL +1 -1
- telegrinder/bot/rules/adapter/__init__.py +0 -17
- telegrinder/bot/rules/adapter/abc.py +0 -31
- telegrinder/node/message.py +0 -14
- telegrinder/node/update.py +0 -15
- telegrinder/tools/formatting/links.py +0 -38
- telegrinder/tools/kb_set/__init__.py +0 -4
- telegrinder/tools/kb_set/base.py +0 -15
- telegrinder/tools/kb_set/yaml.py +0 -63
- telegrinder-0.3.4.post1.dist-info/METADATA +0 -110
- telegrinder-0.3.4.post1.dist-info/RECORD +0 -165
- /telegrinder/{bot/rules → tools}/adapter/errors.py +0 -0
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import sys
|
|
2
3
|
import typing
|
|
4
|
+
from http import HTTPStatus
|
|
3
5
|
|
|
4
|
-
import aiohttp
|
|
5
6
|
import msgspec
|
|
6
7
|
from fntypes.result import Error, Ok
|
|
7
8
|
|
|
8
|
-
from telegrinder.api.api import API
|
|
9
|
-
from telegrinder.api.error import InvalidTokenError
|
|
9
|
+
from telegrinder.api.api import API, HTTPClient
|
|
10
|
+
from telegrinder.api.error import APIServerError, InvalidTokenError
|
|
10
11
|
from telegrinder.bot.polling.abc import ABCPolling
|
|
11
12
|
from telegrinder.modules import logger
|
|
12
13
|
from telegrinder.msgspec_utils import decoder
|
|
13
14
|
from telegrinder.types.objects import Update, UpdateType
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
class Polling(ABCPolling):
|
|
17
|
+
class Polling(ABCPolling, typing.Generic[HTTPClient]):
|
|
17
18
|
def __init__(
|
|
18
19
|
self,
|
|
19
|
-
api: API,
|
|
20
|
+
api: API[HTTPClient],
|
|
20
21
|
*,
|
|
21
22
|
offset: int = 0,
|
|
22
|
-
reconnection_timeout: float = 5,
|
|
23
|
-
max_reconnetions: int =
|
|
23
|
+
reconnection_timeout: float = 5.0,
|
|
24
|
+
max_reconnetions: int = 15,
|
|
24
25
|
include_updates: set[str | UpdateType] | None = None,
|
|
25
26
|
exclude_updates: set[str | UpdateType] | None = None,
|
|
26
27
|
) -> None:
|
|
@@ -30,9 +31,9 @@ class Polling(ABCPolling):
|
|
|
30
31
|
exclude_updates=exclude_updates,
|
|
31
32
|
)
|
|
32
33
|
self.reconnection_timeout = 5.0 if reconnection_timeout < 0 else reconnection_timeout
|
|
33
|
-
self.max_reconnetions =
|
|
34
|
+
self.max_reconnetions = 15 if max_reconnetions < 0 else max_reconnetions
|
|
34
35
|
self.offset = offset
|
|
35
|
-
self._stop =
|
|
36
|
+
self._stop = True
|
|
36
37
|
|
|
37
38
|
def __repr__(self) -> str:
|
|
38
39
|
return (
|
|
@@ -59,9 +60,7 @@ class Polling(ABCPolling):
|
|
|
59
60
|
return allowed_updates
|
|
60
61
|
|
|
61
62
|
if include_updates and exclude_updates:
|
|
62
|
-
allowed_updates = [
|
|
63
|
-
x for x in allowed_updates if x in include_updates and x not in exclude_updates
|
|
64
|
-
]
|
|
63
|
+
allowed_updates = [x for x in allowed_updates if x in include_updates and x not in exclude_updates]
|
|
65
64
|
elif exclude_updates:
|
|
66
65
|
allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
|
|
67
66
|
elif include_updates:
|
|
@@ -69,60 +68,69 @@ class Polling(ABCPolling):
|
|
|
69
68
|
|
|
70
69
|
return [x.value if isinstance(x, UpdateType) else x for x in allowed_updates]
|
|
71
70
|
|
|
72
|
-
async def get_updates(self) -> msgspec.Raw
|
|
71
|
+
async def get_updates(self) -> msgspec.Raw:
|
|
73
72
|
raw_updates = await self.api.request_raw(
|
|
74
|
-
"getUpdates",
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
method="getUpdates",
|
|
74
|
+
data=dict(
|
|
75
|
+
offset=self.offset,
|
|
76
|
+
allowed_updates=self.allowed_updates,
|
|
77
|
+
),
|
|
79
78
|
)
|
|
79
|
+
|
|
80
80
|
match raw_updates:
|
|
81
81
|
case Ok(value):
|
|
82
82
|
return value
|
|
83
|
-
case Error(err)
|
|
84
|
-
|
|
83
|
+
case Error(err):
|
|
84
|
+
if err.code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.NOT_FOUND):
|
|
85
|
+
raise InvalidTokenError("Token seems to be invalid")
|
|
86
|
+
if err.code in (HTTPStatus.BAD_GATEWAY, HTTPStatus.GATEWAY_TIMEOUT):
|
|
87
|
+
raise APIServerError("Unavilability of the API Telegram server")
|
|
88
|
+
raise err from None
|
|
85
89
|
|
|
86
90
|
async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
|
|
87
91
|
logger.debug("Listening polling")
|
|
88
92
|
reconn_counter = 0
|
|
93
|
+
self._stop = False
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if reconn_counter > self.max_reconnetions:
|
|
109
|
-
logger.error(
|
|
110
|
-
"Failed to reconnect to the server after {} attempts, polling stopping.",
|
|
111
|
-
self.max_reconnetions,
|
|
112
|
-
)
|
|
95
|
+
with decoder(list[Update]) as dec: # For improve performance
|
|
96
|
+
while not self._stop:
|
|
97
|
+
try:
|
|
98
|
+
updates = await self.get_updates()
|
|
99
|
+
reconn_counter = 0
|
|
100
|
+
updates_list = dec.decode(updates)
|
|
101
|
+
if updates_list:
|
|
102
|
+
yield updates_list
|
|
103
|
+
self.offset = updates_list[-1].update_id + 1
|
|
104
|
+
except InvalidTokenError as e:
|
|
105
|
+
logger.error(e)
|
|
106
|
+
self.stop()
|
|
107
|
+
sys.exit(3)
|
|
108
|
+
except APIServerError as e:
|
|
109
|
+
logger.error(f"{e}, waiting {self.reconnection_timeout} seconds to the next request...")
|
|
110
|
+
await asyncio.sleep(self.reconnection_timeout)
|
|
111
|
+
except asyncio.CancelledError:
|
|
112
|
+
logger.info("Caught cancel, polling stopping...")
|
|
113
113
|
self.stop()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
except self.api.http.CONNECTION_TIMEOUT_ERRORS:
|
|
115
|
+
if reconn_counter > self.max_reconnetions:
|
|
116
|
+
logger.error(
|
|
117
|
+
"Failed to reconnect to the server after {} attempts, polling stopping.",
|
|
118
|
+
self.max_reconnetions,
|
|
119
|
+
)
|
|
120
|
+
self.stop()
|
|
121
|
+
sys.exit(6)
|
|
122
|
+
else:
|
|
123
|
+
logger.warning(
|
|
124
|
+
"Server disconnected, waiting {} seconds to reconnect...",
|
|
125
|
+
self.reconnection_timeout,
|
|
126
|
+
)
|
|
127
|
+
reconn_counter += 1
|
|
128
|
+
await asyncio.sleep(self.reconnection_timeout)
|
|
129
|
+
except self.api.http.CLIENT_CONNECTION_ERRORS:
|
|
130
|
+
logger.error("Client connection failed, attempted to reconnect...")
|
|
120
131
|
await asyncio.sleep(self.reconnection_timeout)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
await asyncio.sleep(self.reconnection_timeout)
|
|
124
|
-
except BaseException as e:
|
|
125
|
-
logger.exception(e)
|
|
132
|
+
except BaseException as e:
|
|
133
|
+
logger.exception("Traceback message below:")
|
|
126
134
|
|
|
127
135
|
def stop(self) -> None:
|
|
128
136
|
self._stop = True
|
|
@@ -19,6 +19,7 @@ from telegrinder.bot.rules.command import Argument, Command
|
|
|
19
19
|
from telegrinder.bot.rules.enum_text import EnumTextRule
|
|
20
20
|
from telegrinder.bot.rules.func import FuncRule
|
|
21
21
|
from telegrinder.bot.rules.fuzzy import FuzzyText
|
|
22
|
+
from telegrinder.bot.rules.id import IdRule
|
|
22
23
|
from telegrinder.bot.rules.inline import (
|
|
23
24
|
HasLocation,
|
|
24
25
|
InlineQueryChatType,
|
|
@@ -45,16 +46,28 @@ from telegrinder.bot.rules.is_from import (
|
|
|
45
46
|
IsUser,
|
|
46
47
|
IsUserId,
|
|
47
48
|
)
|
|
49
|
+
from telegrinder.bot.rules.logic import If
|
|
48
50
|
from telegrinder.bot.rules.markup import Markup
|
|
49
51
|
from telegrinder.bot.rules.mention import HasMention
|
|
50
52
|
from telegrinder.bot.rules.message import MessageRule
|
|
51
53
|
from telegrinder.bot.rules.message_entities import HasEntities, MessageEntities
|
|
52
54
|
from telegrinder.bot.rules.node import NodeRule
|
|
55
|
+
from telegrinder.bot.rules.payload import (
|
|
56
|
+
PayloadEqRule,
|
|
57
|
+
PayloadJsonEqRule,
|
|
58
|
+
PayloadMarkupRule,
|
|
59
|
+
PayloadModelRule,
|
|
60
|
+
PayloadRule,
|
|
61
|
+
)
|
|
62
|
+
from telegrinder.bot.rules.payment_invoice import (
|
|
63
|
+
PaymentInvoiceCurrency,
|
|
64
|
+
PaymentInvoiceRule,
|
|
65
|
+
)
|
|
53
66
|
from telegrinder.bot.rules.regex import Regex
|
|
54
67
|
from telegrinder.bot.rules.rule_enum import RuleEnum
|
|
55
68
|
from telegrinder.bot.rules.start import StartCommand
|
|
56
69
|
from telegrinder.bot.rules.state import State, StateMeta
|
|
57
|
-
from telegrinder.bot.rules.text import HasText, Text
|
|
70
|
+
from telegrinder.bot.rules.text import HasCaption, HasText, Text
|
|
58
71
|
from telegrinder.bot.rules.update import IsUpdateType
|
|
59
72
|
|
|
60
73
|
__all__ = (
|
|
@@ -73,12 +86,15 @@ __all__ = (
|
|
|
73
86
|
"EnumTextRule",
|
|
74
87
|
"FuncRule",
|
|
75
88
|
"FuzzyText",
|
|
89
|
+
"HasCaption",
|
|
76
90
|
"HasData",
|
|
77
91
|
"HasEntities",
|
|
78
92
|
"HasInviteLink",
|
|
79
93
|
"HasLocation",
|
|
80
94
|
"HasMention",
|
|
81
95
|
"HasText",
|
|
96
|
+
"IdRule",
|
|
97
|
+
"If",
|
|
82
98
|
"InlineQueryChatType",
|
|
83
99
|
"InlineQueryMarkup",
|
|
84
100
|
"InlineQueryRule",
|
|
@@ -110,6 +126,13 @@ __all__ = (
|
|
|
110
126
|
"NodeRule",
|
|
111
127
|
"NotRule",
|
|
112
128
|
"OrRule",
|
|
129
|
+
"PayloadEqRule",
|
|
130
|
+
"PayloadJsonEqRule",
|
|
131
|
+
"PayloadMarkupRule",
|
|
132
|
+
"PayloadModelRule",
|
|
133
|
+
"PayloadRule",
|
|
134
|
+
"PaymentInvoiceCurrency",
|
|
135
|
+
"PaymentInvoiceRule",
|
|
113
136
|
"Regex",
|
|
114
137
|
"RuleEnum",
|
|
115
138
|
"StartCommand",
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -7,9 +7,10 @@ import typing_extensions as typing
|
|
|
7
7
|
from telegrinder.bot.cute_types import MessageCute, UpdateCute
|
|
8
8
|
from telegrinder.bot.dispatch.context import Context
|
|
9
9
|
from telegrinder.bot.dispatch.process import check_rule
|
|
10
|
-
from telegrinder.
|
|
11
|
-
from telegrinder.
|
|
12
|
-
from telegrinder.node
|
|
10
|
+
from telegrinder.node.base import NodeType, get_nodes, is_node
|
|
11
|
+
from telegrinder.tools.adapter import ABCAdapter
|
|
12
|
+
from telegrinder.tools.adapter.node import Event
|
|
13
|
+
from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
|
|
13
14
|
from telegrinder.tools.i18n.abc import ABCTranslator
|
|
14
15
|
from telegrinder.tools.magic import (
|
|
15
16
|
cache_translation,
|
|
@@ -24,7 +25,8 @@ if typing.TYPE_CHECKING:
|
|
|
24
25
|
|
|
25
26
|
AdaptTo = typing.TypeVar("AdaptTo", default=typing.Any, contravariant=True)
|
|
26
27
|
|
|
27
|
-
CheckResult
|
|
28
|
+
type CheckResult = bool | typing.Awaitable[bool]
|
|
29
|
+
|
|
28
30
|
Message: typing.TypeAlias = MessageCute
|
|
29
31
|
Update: typing.TypeAlias = UpdateCute
|
|
30
32
|
|
|
@@ -58,8 +60,15 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
58
60
|
def check(self, *args, **kwargs):
|
|
59
61
|
pass
|
|
60
62
|
|
|
61
|
-
def __init_subclass__(
|
|
63
|
+
def __init_subclass__(
|
|
64
|
+
cls,
|
|
65
|
+
*,
|
|
66
|
+
requires: list["ABCRule"] | None = None,
|
|
67
|
+
adapter: ABCAdapter[UpdateObject, AdaptTo] | None = None,
|
|
68
|
+
) -> None:
|
|
62
69
|
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
70
|
+
if adapter is not None:
|
|
71
|
+
cls.adapter = adapter
|
|
63
72
|
|
|
64
73
|
requirements = []
|
|
65
74
|
for base in inspect.getmro(cls):
|
|
@@ -77,7 +86,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
77
86
|
rule #> AndRule(HasText(), HasCaption()) -> True if all rules in an AndRule are True, otherwise False.
|
|
78
87
|
```
|
|
79
88
|
"""
|
|
80
|
-
|
|
81
89
|
return AndRule(self, other)
|
|
82
90
|
|
|
83
91
|
def __or__(self, other: "ABCRule") -> "OrRule":
|
|
@@ -88,7 +96,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
88
96
|
rule #> OrRule(HasText(), HasCaption()) -> True if any rule in an OrRule are True, otherwise False.
|
|
89
97
|
```
|
|
90
98
|
"""
|
|
91
|
-
|
|
92
99
|
return OrRule(self, other)
|
|
93
100
|
|
|
94
101
|
def __invert__(self) -> "NotRule":
|
|
@@ -99,7 +106,6 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
99
106
|
rule # NotRule(HasText()) -> True if rule returned False, otherwise False.
|
|
100
107
|
```
|
|
101
108
|
"""
|
|
102
|
-
|
|
103
109
|
return NotRule(self)
|
|
104
110
|
|
|
105
111
|
def __repr__(self) -> str:
|
|
@@ -109,7 +115,7 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
@cached_property
|
|
112
|
-
def required_nodes(self) -> dict[str, type[
|
|
118
|
+
def required_nodes(self) -> dict[str, type[NodeType]]:
|
|
113
119
|
return get_nodes(self.check)
|
|
114
120
|
|
|
115
121
|
def as_optional(self) -> "ABCRule":
|
|
@@ -120,8 +126,9 @@ class ABCRule(ABC, typing.Generic[AdaptTo]):
|
|
|
120
126
|
|
|
121
127
|
async def bounding_check(
|
|
122
128
|
self,
|
|
123
|
-
adapted_value: AdaptTo,
|
|
124
129
|
ctx: Context,
|
|
130
|
+
*,
|
|
131
|
+
adapted_value: AdaptTo,
|
|
125
132
|
node_col: "NodeCollection | None" = None,
|
|
126
133
|
) -> bool:
|
|
127
134
|
bound_check_rule = self.check
|
|
@@ -3,35 +3,41 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
|
|
6
|
-
import msgspec
|
|
7
|
-
|
|
8
6
|
from telegrinder.bot.cute_types import CallbackQueryCute
|
|
9
7
|
from telegrinder.bot.dispatch.context import Context
|
|
10
|
-
from telegrinder.bot.rules.
|
|
11
|
-
from telegrinder.
|
|
12
|
-
|
|
8
|
+
from telegrinder.bot.rules.abc import ABCRule, CheckResult
|
|
9
|
+
from telegrinder.bot.rules.payload import (
|
|
10
|
+
PayloadEqRule,
|
|
11
|
+
PayloadJsonEqRule,
|
|
12
|
+
PayloadMarkupRule,
|
|
13
|
+
PayloadModelRule,
|
|
14
|
+
)
|
|
15
|
+
from telegrinder.tools.adapter.event import EventAdapter
|
|
13
16
|
from telegrinder.types.enums import UpdateType
|
|
14
17
|
|
|
15
|
-
from .abc import ABCRule, CheckResult
|
|
16
|
-
from .markup import Markup, PatternLike, check_string
|
|
17
|
-
|
|
18
18
|
CallbackQuery: typing.TypeAlias = CallbackQueryCute
|
|
19
19
|
Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
|
|
20
20
|
MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
|
|
21
21
|
CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
|
|
22
22
|
CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
|
|
23
|
+
CallbackDataEq: typing.TypeAlias = PayloadEqRule
|
|
24
|
+
CallbackDataJsonEq: typing.TypeAlias = PayloadJsonEqRule
|
|
25
|
+
CallbackDataMarkup: typing.TypeAlias = PayloadMarkupRule
|
|
26
|
+
CallbackDataJsonModel: typing.TypeAlias = PayloadModelRule
|
|
23
27
|
|
|
24
28
|
|
|
25
|
-
class CallbackQueryRule(
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
class CallbackQueryRule(
|
|
30
|
+
ABCRule[CallbackQuery],
|
|
31
|
+
abc.ABC,
|
|
32
|
+
adapter=EventAdapter(UpdateType.CALLBACK_QUERY, CallbackQuery),
|
|
33
|
+
):
|
|
28
34
|
@abc.abstractmethod
|
|
29
35
|
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
30
36
|
|
|
31
37
|
|
|
32
38
|
class HasData(CallbackQueryRule):
|
|
33
39
|
def check(self, event: CallbackQuery) -> bool:
|
|
34
|
-
return bool(event.data
|
|
40
|
+
return bool(event.data)
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
|
|
@@ -47,7 +53,6 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
47
53
|
@classmethod
|
|
48
54
|
def transform_to_map(cls, mapping: MapDict) -> CallbackMap:
|
|
49
55
|
"""Transforms MapDict to CallbackMap."""
|
|
50
|
-
|
|
51
56
|
callback_map = []
|
|
52
57
|
|
|
53
58
|
for k, v in mapping.items():
|
|
@@ -60,7 +65,6 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
60
65
|
@classmethod
|
|
61
66
|
def transform_to_callbacks(cls, callback_map: CallbackMap) -> CallbackMapStrict:
|
|
62
67
|
"""Transforms `CallbackMap` to `CallbackMapStrict`."""
|
|
63
|
-
|
|
64
68
|
callback_map_result = []
|
|
65
69
|
|
|
66
70
|
for key, value in callback_map:
|
|
@@ -79,7 +83,6 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
79
83
|
@staticmethod
|
|
80
84
|
async def run_validator(value: typing.Any, validator: Validator) -> bool:
|
|
81
85
|
"""Run async or sync validator."""
|
|
82
|
-
|
|
83
86
|
with suppress(BaseException):
|
|
84
87
|
result = validator(value)
|
|
85
88
|
if inspect.isawaitable(result):
|
|
@@ -91,15 +94,12 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
91
94
|
@classmethod
|
|
92
95
|
async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
|
|
93
96
|
"""Matches callback_data with callback_map recursively."""
|
|
94
|
-
|
|
95
97
|
for key, validator in callback_map:
|
|
96
98
|
if key not in callback_data:
|
|
97
99
|
return False
|
|
98
100
|
|
|
99
101
|
if isinstance(validator, list):
|
|
100
|
-
if not (
|
|
101
|
-
isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)
|
|
102
|
-
):
|
|
102
|
+
if not (isinstance(callback_data[key], dict) and await cls.match(callback_data[key], validator)):
|
|
103
103
|
return False
|
|
104
104
|
|
|
105
105
|
elif not await cls.run_validator(callback_data[key], validator):
|
|
@@ -108,7 +108,7 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
108
108
|
return True
|
|
109
109
|
|
|
110
110
|
async def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
111
|
-
callback_data = event.
|
|
111
|
+
callback_data = event.decode_data().unwrap_or_none()
|
|
112
112
|
if callback_data is None:
|
|
113
113
|
return False
|
|
114
114
|
if await self.match(callback_data, self.mapping):
|
|
@@ -117,47 +117,6 @@ class CallbackDataMap(CallbackQueryDataRule):
|
|
|
117
117
|
return False
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
class CallbackDataEq(CallbackQueryDataRule):
|
|
121
|
-
def __init__(self, value: str, /) -> None:
|
|
122
|
-
self.value = value
|
|
123
|
-
|
|
124
|
-
def check(self, event: CallbackQuery) -> bool:
|
|
125
|
-
return event.data.unwrap() == self.value
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
class CallbackDataJsonEq(CallbackQueryDataRule):
|
|
129
|
-
def __init__(self, d: dict[str, typing.Any], /) -> None:
|
|
130
|
-
self.d = d
|
|
131
|
-
|
|
132
|
-
def check(self, event: CallbackQuery) -> bool:
|
|
133
|
-
return event.decode_callback_data().unwrap_or_none() == self.d
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class CallbackDataJsonModel(CallbackQueryDataRule):
|
|
137
|
-
def __init__(
|
|
138
|
-
self,
|
|
139
|
-
model: type[msgspec.Struct] | type[DataclassInstance],
|
|
140
|
-
*,
|
|
141
|
-
alias: str | None = None,
|
|
142
|
-
) -> None:
|
|
143
|
-
self.model = model
|
|
144
|
-
self.alias = alias or "data"
|
|
145
|
-
|
|
146
|
-
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
147
|
-
with suppress(BaseException):
|
|
148
|
-
ctx.set(self.alias, decoder.decode(event.data.unwrap().encode(), type=self.model))
|
|
149
|
-
return True
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class CallbackDataMarkup(CallbackQueryDataRule):
|
|
154
|
-
def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
|
|
155
|
-
self.patterns = Markup(patterns).patterns
|
|
156
|
-
|
|
157
|
-
def check(self, event: CallbackQuery, ctx: Context) -> bool:
|
|
158
|
-
return check_string(self.patterns, event.data.unwrap(), ctx)
|
|
159
|
-
|
|
160
|
-
|
|
161
120
|
__all__ = (
|
|
162
121
|
"CallbackDataEq",
|
|
163
122
|
"CallbackDataJsonEq",
|
|
@@ -2,7 +2,7 @@ import abc
|
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
from telegrinder.bot.cute_types import ChatJoinRequestCute
|
|
5
|
-
from telegrinder.
|
|
5
|
+
from telegrinder.tools.adapter.event import EventAdapter
|
|
6
6
|
from telegrinder.types.enums import UpdateType
|
|
7
7
|
|
|
8
8
|
from .abc import ABCRule, CheckResult
|
|
@@ -10,9 +10,11 @@ from .abc import ABCRule, CheckResult
|
|
|
10
10
|
ChatJoinRequest: typing.TypeAlias = ChatJoinRequestCute
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class ChatJoinRequestRule(
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
class ChatJoinRequestRule(
|
|
14
|
+
ABCRule[ChatJoinRequest],
|
|
15
|
+
abc.ABC,
|
|
16
|
+
adapter=EventAdapter(UpdateType.CHAT_JOIN_REQUEST, ChatJoinRequest),
|
|
17
|
+
):
|
|
16
18
|
@abc.abstractmethod
|
|
17
19
|
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
18
20
|
|
telegrinder/bot/rules/command.py
CHANGED
|
@@ -4,12 +4,12 @@ import typing
|
|
|
4
4
|
from telegrinder.bot.dispatch.context import Context
|
|
5
5
|
from telegrinder.node.command import CommandInfo, single_split
|
|
6
6
|
from telegrinder.node.me import Me
|
|
7
|
-
from telegrinder.node.source import
|
|
7
|
+
from telegrinder.node.source import ChatSource
|
|
8
8
|
from telegrinder.types.enums import ChatType
|
|
9
9
|
|
|
10
10
|
from .abc import ABCRule
|
|
11
11
|
|
|
12
|
-
Validator
|
|
12
|
+
type Validator = typing.Callable[[str], typing.Any | None]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclasses.dataclass(frozen=True, slots=True)
|
|
@@ -97,7 +97,7 @@ class Command(ABCRule):
|
|
|
97
97
|
|
|
98
98
|
return None
|
|
99
99
|
|
|
100
|
-
def check(self, command: CommandInfo, me: Me,
|
|
100
|
+
def check(self, command: CommandInfo, me: Me, chat: ChatSource, ctx: Context) -> bool:
|
|
101
101
|
name = self.remove_prefix(command.name)
|
|
102
102
|
if name is None:
|
|
103
103
|
return False
|
|
@@ -105,7 +105,7 @@ class Command(ABCRule):
|
|
|
105
105
|
if name not in self.names:
|
|
106
106
|
return False
|
|
107
107
|
|
|
108
|
-
if not command.mention and self.mention_needed_in_chat and
|
|
108
|
+
if not command.mention and self.mention_needed_in_chat and chat.type is not ChatType.PRIVATE:
|
|
109
109
|
return False
|
|
110
110
|
|
|
111
111
|
if command.mention and self.validate_mention: # noqa
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import enum
|
|
2
|
-
import typing
|
|
3
2
|
|
|
4
3
|
from telegrinder.bot.dispatch.context import Context
|
|
5
4
|
from telegrinder.node.text import Text
|
|
6
5
|
|
|
7
6
|
from .abc import ABCRule
|
|
8
7
|
|
|
9
|
-
T = typing.TypeVar("T", bound=enum.Enum)
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
class EnumTextRule(ABCRule, typing.Generic[T]):
|
|
9
|
+
class EnumTextRule[T: enum.Enum](ABCRule):
|
|
13
10
|
def __init__(self, enum_t: type[T], *, lower_case: bool = True) -> None:
|
|
14
11
|
self.enum_t = enum_t
|
|
15
12
|
self.texts = list(
|
telegrinder/bot/rules/func.py
CHANGED
|
@@ -2,9 +2,11 @@ import inspect
|
|
|
2
2
|
import typing
|
|
3
3
|
|
|
4
4
|
from telegrinder.bot.dispatch.context import Context
|
|
5
|
+
from telegrinder.tools.adapter.abc import ABCAdapter
|
|
6
|
+
from telegrinder.tools.adapter.raw_update import RawUpdateAdapter
|
|
5
7
|
from telegrinder.types.objects import Update
|
|
6
8
|
|
|
7
|
-
from .abc import
|
|
9
|
+
from .abc import ABCRule, AdaptTo
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class FuncRule(ABCRule, typing.Generic[AdaptTo]):
|
|
@@ -19,8 +21,8 @@ class FuncRule(ABCRule, typing.Generic[AdaptTo]):
|
|
|
19
21
|
async def check(self, event: AdaptTo, ctx: Context) -> bool:
|
|
20
22
|
result = self.func(event, ctx)
|
|
21
23
|
if inspect.isawaitable(result):
|
|
22
|
-
|
|
23
|
-
return result
|
|
24
|
+
result = await result
|
|
25
|
+
return result
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
__all__ = ("FuncRule",)
|
telegrinder/bot/rules/fuzzy.py
CHANGED
|
@@ -7,7 +7,7 @@ from .abc import ABCRule
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class FuzzyText(ABCRule):
|
|
10
|
-
def __init__(self, texts: str | list[str], min_ratio: float = 0.7) -> None:
|
|
10
|
+
def __init__(self, texts: str | list[str], /, min_ratio: float = 0.7) -> None:
|
|
11
11
|
if isinstance(texts, str):
|
|
12
12
|
texts = [texts]
|
|
13
13
|
self.texts = texts
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from telegrinder.types.objects import Update
|
|
4
|
+
|
|
5
|
+
from .abc import ABCRule
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from telegrinder.tools.adapter.abc import ABCAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IdRule[Identifier](ABCRule[Identifier]):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
adapter: "ABCAdapter[Update, Identifier]",
|
|
15
|
+
tracked_identifiers: set[Identifier] | None = None,
|
|
16
|
+
):
|
|
17
|
+
self.tracked_identifiers = tracked_identifiers or set()
|
|
18
|
+
self.adapter = adapter
|
|
19
|
+
|
|
20
|
+
async def check(self, event: Identifier) -> bool:
|
|
21
|
+
return event in self.tracked_identifiers
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ("IdRule",)
|
telegrinder/bot/rules/inline.py
CHANGED
|
@@ -4,7 +4,7 @@ import typing
|
|
|
4
4
|
from telegrinder.bot.cute_types import InlineQueryCute
|
|
5
5
|
from telegrinder.bot.dispatch.context import Context
|
|
6
6
|
from telegrinder.bot.rules.abc import ABCRule, CheckResult
|
|
7
|
-
from telegrinder.
|
|
7
|
+
from telegrinder.tools.adapter.event import EventAdapter
|
|
8
8
|
from telegrinder.types.enums import ChatType, UpdateType
|
|
9
9
|
|
|
10
10
|
from .markup import Markup, PatternLike, check_string
|
|
@@ -12,9 +12,11 @@ from .markup import Markup, PatternLike, check_string
|
|
|
12
12
|
InlineQuery: typing.TypeAlias = InlineQueryCute
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class InlineQueryRule(
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
class InlineQueryRule(
|
|
16
|
+
ABCRule[InlineQuery],
|
|
17
|
+
abc.ABC,
|
|
18
|
+
adapter=EventAdapter(UpdateType.INLINE_QUERY, InlineQuery),
|
|
19
|
+
):
|
|
18
20
|
@abc.abstractmethod
|
|
19
21
|
def check(self, *args: typing.Any, **kwargs: typing.Any) -> CheckResult: ...
|
|
20
22
|
|
telegrinder/bot/rules/integer.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from telegrinder.node.base import as_node
|
|
1
2
|
from telegrinder.node.text import TextInteger
|
|
2
3
|
|
|
3
4
|
from .abc import ABCRule
|
|
@@ -6,7 +7,7 @@ from .node import NodeRule
|
|
|
6
7
|
|
|
7
8
|
class IsInteger(NodeRule):
|
|
8
9
|
def __init__(self) -> None:
|
|
9
|
-
super().__init__(TextInteger)
|
|
10
|
+
super().__init__(as_node(TextInteger))
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class IntegerInRange(ABCRule):
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from .abc import ABCRule, Context, UpdateCute, check_rule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class If(ABCRule):
|
|
7
|
+
def __init__(self, condition: ABCRule) -> None:
|
|
8
|
+
self.conditions = [condition]
|
|
9
|
+
|
|
10
|
+
async def check(self, update: UpdateCute, ctx: Context) -> bool:
|
|
11
|
+
for condition in self.conditions[:-1]:
|
|
12
|
+
if not await check_rule(update.api, condition, update, ctx):
|
|
13
|
+
return True
|
|
14
|
+
return await check_rule(update.api, self.conditions[-1], update, ctx)
|
|
15
|
+
|
|
16
|
+
def then(self, condition: ABCRule) -> typing.Self:
|
|
17
|
+
self.conditions.append(condition)
|
|
18
|
+
return self
|