telegrinder 0.1.dev19__py3-none-any.whl → 0.1.dev158__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 +129 -22
- telegrinder/api/__init__.py +11 -2
- telegrinder/api/abc.py +25 -9
- telegrinder/api/api.py +29 -24
- telegrinder/api/error.py +14 -4
- telegrinder/api/response.py +11 -7
- telegrinder/bot/__init__.py +68 -7
- telegrinder/bot/bot.py +30 -24
- telegrinder/bot/cute_types/__init__.py +11 -1
- telegrinder/bot/cute_types/base.py +47 -0
- telegrinder/bot/cute_types/callback_query.py +64 -14
- telegrinder/bot/cute_types/inline_query.py +22 -16
- telegrinder/bot/cute_types/message.py +163 -43
- telegrinder/bot/cute_types/update.py +23 -0
- telegrinder/bot/dispatch/__init__.py +56 -3
- telegrinder/bot/dispatch/abc.py +9 -7
- telegrinder/bot/dispatch/composition.py +74 -0
- telegrinder/bot/dispatch/context.py +71 -0
- telegrinder/bot/dispatch/dispatch.py +86 -49
- telegrinder/bot/dispatch/handler/__init__.py +3 -0
- telegrinder/bot/dispatch/handler/abc.py +11 -5
- telegrinder/bot/dispatch/handler/func.py +41 -32
- telegrinder/bot/dispatch/handler/message_reply.py +46 -0
- telegrinder/bot/dispatch/middleware/__init__.py +2 -0
- telegrinder/bot/dispatch/middleware/abc.py +10 -4
- telegrinder/bot/dispatch/process.py +53 -49
- telegrinder/bot/dispatch/return_manager/__init__.py +19 -0
- telegrinder/bot/dispatch/return_manager/abc.py +95 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +25 -0
- telegrinder/bot/dispatch/view/__init__.py +14 -2
- telegrinder/bot/dispatch/view/abc.py +121 -2
- telegrinder/bot/dispatch/view/box.py +38 -0
- telegrinder/bot/dispatch/view/callback_query.py +13 -38
- telegrinder/bot/dispatch/view/inline_query.py +11 -38
- telegrinder/bot/dispatch/view/message.py +11 -46
- telegrinder/bot/dispatch/waiter_machine/__init__.py +9 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +116 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +76 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +37 -0
- telegrinder/bot/polling/__init__.py +2 -0
- telegrinder/bot/polling/abc.py +11 -4
- telegrinder/bot/polling/polling.py +89 -40
- telegrinder/bot/rules/__init__.py +92 -5
- telegrinder/bot/rules/abc.py +81 -63
- telegrinder/bot/rules/adapter/__init__.py +11 -0
- telegrinder/bot/rules/adapter/abc.py +21 -0
- telegrinder/bot/rules/adapter/errors.py +5 -0
- telegrinder/bot/rules/adapter/event.py +43 -0
- telegrinder/bot/rules/adapter/raw_update.py +24 -0
- telegrinder/bot/rules/callback_data.py +159 -38
- telegrinder/bot/rules/command.py +116 -0
- telegrinder/bot/rules/enum_text.py +28 -0
- telegrinder/bot/rules/func.py +17 -17
- telegrinder/bot/rules/fuzzy.py +13 -10
- telegrinder/bot/rules/inline.py +61 -0
- telegrinder/bot/rules/integer.py +12 -7
- telegrinder/bot/rules/is_from.py +148 -7
- telegrinder/bot/rules/markup.py +21 -18
- telegrinder/bot/rules/mention.py +17 -0
- telegrinder/bot/rules/message_entities.py +33 -0
- telegrinder/bot/rules/regex.py +27 -19
- telegrinder/bot/rules/rule_enum.py +74 -0
- telegrinder/bot/rules/start.py +42 -0
- telegrinder/bot/rules/text.py +23 -14
- telegrinder/bot/scenario/__init__.py +2 -0
- telegrinder/bot/scenario/abc.py +12 -5
- telegrinder/bot/scenario/checkbox.py +48 -30
- telegrinder/bot/scenario/choice.py +16 -10
- telegrinder/client/__init__.py +2 -0
- telegrinder/client/abc.py +8 -21
- telegrinder/client/aiohttp.py +30 -21
- telegrinder/model.py +68 -37
- telegrinder/modules.py +189 -21
- telegrinder/msgspec_json.py +14 -0
- telegrinder/msgspec_utils.py +207 -0
- telegrinder/node/__init__.py +31 -0
- telegrinder/node/attachment.py +71 -0
- telegrinder/node/base.py +93 -0
- telegrinder/node/composer.py +71 -0
- telegrinder/node/container.py +22 -0
- telegrinder/node/message.py +18 -0
- telegrinder/node/rule.py +56 -0
- telegrinder/node/source.py +31 -0
- telegrinder/node/text.py +13 -0
- telegrinder/node/tools/__init__.py +3 -0
- telegrinder/node/tools/generator.py +40 -0
- telegrinder/node/update.py +12 -0
- telegrinder/rules.py +1 -1
- telegrinder/tools/__init__.py +165 -4
- telegrinder/tools/buttons.py +75 -51
- telegrinder/tools/error_handler/__init__.py +8 -0
- telegrinder/tools/error_handler/abc.py +30 -0
- telegrinder/tools/error_handler/error_handler.py +156 -0
- telegrinder/tools/formatting/__init__.py +81 -3
- telegrinder/tools/formatting/html.py +283 -37
- telegrinder/tools/formatting/links.py +32 -0
- telegrinder/tools/formatting/spec_html_formats.py +121 -0
- telegrinder/tools/global_context/__init__.py +12 -0
- telegrinder/tools/global_context/abc.py +66 -0
- telegrinder/tools/global_context/global_context.py +451 -0
- telegrinder/tools/global_context/telegrinder_ctx.py +25 -0
- telegrinder/tools/i18n/__init__.py +12 -0
- telegrinder/tools/i18n/base.py +31 -0
- telegrinder/tools/i18n/middleware/__init__.py +3 -0
- telegrinder/tools/i18n/middleware/base.py +26 -0
- telegrinder/tools/i18n/simple.py +48 -0
- telegrinder/tools/inline_query.py +684 -0
- telegrinder/tools/kb_set/__init__.py +2 -0
- telegrinder/tools/kb_set/base.py +3 -0
- telegrinder/tools/kb_set/yaml.py +28 -17
- telegrinder/tools/keyboard.py +84 -62
- telegrinder/tools/loop_wrapper/__init__.py +4 -0
- telegrinder/tools/loop_wrapper/abc.py +18 -0
- telegrinder/tools/loop_wrapper/loop_wrapper.py +132 -0
- telegrinder/tools/magic.py +48 -23
- telegrinder/tools/parse_mode.py +1 -2
- telegrinder/types/__init__.py +1 -0
- telegrinder/types/enums.py +651 -0
- telegrinder/types/methods.py +3933 -1128
- telegrinder/types/objects.py +4755 -1633
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/LICENSE +2 -1
- telegrinder-0.1.dev158.dist-info/METADATA +108 -0
- telegrinder-0.1.dev158.dist-info/RECORD +126 -0
- {telegrinder-0.1.dev19.dist-info → telegrinder-0.1.dev158.dist-info}/WHEEL +1 -1
- telegrinder/bot/dispatch/waiter.py +0 -37
- telegrinder/result.py +0 -38
- telegrinder/tools/formatting/abc.py +0 -52
- telegrinder/tools/formatting/markdown.py +0 -57
- telegrinder/typegen/__init__.py +0 -1
- telegrinder/typegen/__main__.py +0 -3
- telegrinder/typegen/nicification.py +0 -20
- telegrinder/typegen/schema_generator.py +0 -259
- telegrinder-0.1.dev19.dist-info/METADATA +0 -22
- telegrinder-0.1.dev19.dist-info/RECORD +0 -74
|
@@ -1,71 +1,120 @@
|
|
|
1
|
-
import traceback
|
|
2
1
|
import asyncio
|
|
2
|
+
import typing
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import aiohttp
|
|
5
|
+
import msgspec
|
|
6
|
+
from fntypes.result import Error, Ok
|
|
5
7
|
|
|
6
|
-
from .abc import ABCPolling
|
|
7
8
|
from telegrinder.api.abc import ABCAPI
|
|
8
|
-
import
|
|
9
|
+
from telegrinder.api.error import InvalidTokenError
|
|
10
|
+
from telegrinder.bot.polling.abc import ABCPolling
|
|
9
11
|
from telegrinder.modules import logger
|
|
10
|
-
from telegrinder.
|
|
11
|
-
from telegrinder.types import Update
|
|
12
|
-
|
|
13
|
-
ALLOWED_UPDATES = [
|
|
14
|
-
"update_id",
|
|
15
|
-
"message",
|
|
16
|
-
"edited_message",
|
|
17
|
-
"channel_post",
|
|
18
|
-
"edited_channel_post",
|
|
19
|
-
"inline_query",
|
|
20
|
-
"chosen_inline_result",
|
|
21
|
-
"callback_query",
|
|
22
|
-
"shipping_query",
|
|
23
|
-
"pre_checkout_query",
|
|
24
|
-
"poll",
|
|
25
|
-
"poll_answer",
|
|
26
|
-
"my_chat_member",
|
|
27
|
-
"chat_member",
|
|
28
|
-
]
|
|
12
|
+
from telegrinder.msgspec_utils import decoder
|
|
13
|
+
from telegrinder.types import Update, UpdateType
|
|
29
14
|
|
|
30
15
|
|
|
31
16
|
class Polling(ABCPolling):
|
|
32
17
|
def __init__(
|
|
33
18
|
self,
|
|
34
|
-
api:
|
|
35
|
-
|
|
19
|
+
api: ABCAPI,
|
|
20
|
+
*,
|
|
21
|
+
offset: int = 0,
|
|
22
|
+
reconnection_timeout: float = 5,
|
|
23
|
+
max_reconnetions: int = 10,
|
|
24
|
+
include_updates: set[str | UpdateType] | None = None,
|
|
25
|
+
exclude_updates: set[str | UpdateType] | None = None,
|
|
36
26
|
):
|
|
37
27
|
self.api = api
|
|
38
|
-
self.
|
|
28
|
+
self.allowed_updates = self.get_allowed_updates(
|
|
29
|
+
include_updates=include_updates,
|
|
30
|
+
exclude_updates=exclude_updates,
|
|
31
|
+
)
|
|
32
|
+
self.reconnection_timeout = 5 if reconnection_timeout < 0 else reconnection_timeout
|
|
33
|
+
self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
|
|
34
|
+
self.offset = offset
|
|
39
35
|
self._stop = False
|
|
40
|
-
self.allowed_updates = ALLOWED_UPDATES
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
def get_allowed_updates(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
include_updates: set[str | UpdateType] | None = None,
|
|
41
|
+
exclude_updates: set[str | UpdateType] | None = None,
|
|
42
|
+
) -> list[str]:
|
|
43
|
+
allowed_updates: list[str] = list(x.value for x in UpdateType)
|
|
44
|
+
if not include_updates and not exclude_updates:
|
|
45
|
+
return allowed_updates
|
|
46
|
+
|
|
47
|
+
if include_updates and exclude_updates:
|
|
48
|
+
allowed_updates = [
|
|
49
|
+
x
|
|
50
|
+
for x in allowed_updates
|
|
51
|
+
if x in include_updates and x not in exclude_updates
|
|
52
|
+
]
|
|
53
|
+
elif exclude_updates:
|
|
54
|
+
allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
|
|
55
|
+
elif include_updates:
|
|
56
|
+
allowed_updates = [x for x in allowed_updates if x in include_updates]
|
|
57
|
+
|
|
58
|
+
return [x.value if isinstance(x, UpdateType) else x for x in allowed_updates]
|
|
59
|
+
|
|
60
|
+
async def get_updates(self) -> msgspec.Raw | None:
|
|
43
61
|
raw_updates = await self.api.request_raw(
|
|
44
62
|
"getUpdates",
|
|
45
63
|
{"offset": self.offset, "allowed_updates": self.allowed_updates},
|
|
46
64
|
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
match raw_updates:
|
|
66
|
+
case Ok(value):
|
|
67
|
+
return value
|
|
68
|
+
case Error(err) if err.code in (401, 404):
|
|
69
|
+
raise InvalidTokenError("Token seems to be invalid")
|
|
51
70
|
|
|
52
|
-
async def listen(self) -> typing.
|
|
53
|
-
logger.debug("
|
|
71
|
+
async def listen(self) -> typing.AsyncGenerator[list[Update], None]:
|
|
72
|
+
logger.debug("Listening polling")
|
|
73
|
+
reconn_counter = 0
|
|
74
|
+
|
|
54
75
|
while not self._stop:
|
|
55
76
|
try:
|
|
56
77
|
updates = await self.get_updates()
|
|
57
|
-
|
|
58
|
-
|
|
78
|
+
reconn_counter = 0
|
|
79
|
+
if not updates:
|
|
80
|
+
continue
|
|
81
|
+
updates_list: list[Update] = decoder.decode(
|
|
82
|
+
updates, type=list[Update]
|
|
59
83
|
)
|
|
60
84
|
if updates_list:
|
|
61
85
|
yield updates_list
|
|
62
86
|
self.offset = updates_list[-1].update_id + 1
|
|
87
|
+
except InvalidTokenError as e:
|
|
88
|
+
logger.error(e)
|
|
89
|
+
self.stop()
|
|
90
|
+
exit(6)
|
|
63
91
|
except asyncio.CancelledError:
|
|
64
|
-
logger.info("
|
|
65
|
-
self.
|
|
92
|
+
logger.info("Caught cancel, polling stopping...")
|
|
93
|
+
self.stop()
|
|
94
|
+
except (aiohttp.client.ServerConnectionError, TimeoutError):
|
|
95
|
+
if reconn_counter > self.max_reconnetions:
|
|
96
|
+
logger.error(
|
|
97
|
+
"Failed to reconnect to the server after {} attempts, polling stopping.",
|
|
98
|
+
self.max_reconnetions,
|
|
99
|
+
)
|
|
100
|
+
self.stop()
|
|
101
|
+
exit(9)
|
|
102
|
+
else:
|
|
103
|
+
logger.warning("Server disconnected, waiting 5 seconds to reconnetion...")
|
|
104
|
+
reconn_counter += 1
|
|
105
|
+
await asyncio.sleep(self.reconnection_timeout)
|
|
106
|
+
except aiohttp.ClientConnectorError:
|
|
107
|
+
logger.error(
|
|
108
|
+
"Client connection failed, polling stopping! "
|
|
109
|
+
"Please, check your internet connection."
|
|
110
|
+
)
|
|
111
|
+
self.stop()
|
|
112
|
+
exit(3)
|
|
66
113
|
except BaseException as e:
|
|
67
|
-
|
|
68
|
-
logger.error(e)
|
|
114
|
+
logger.exception(e)
|
|
69
115
|
|
|
70
116
|
def stop(self) -> None:
|
|
71
117
|
self._stop = True
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = ("Polling",)
|
|
@@ -1,14 +1,101 @@
|
|
|
1
|
-
from .abc import ABCRule,
|
|
1
|
+
from .abc import ABCRule, AndRule, MessageRule, OrRule
|
|
2
2
|
from .callback_data import (
|
|
3
3
|
CallbackDataEq,
|
|
4
4
|
CallbackDataJsonEq,
|
|
5
5
|
CallbackDataJsonModel,
|
|
6
|
+
CallbackDataMap,
|
|
6
7
|
CallbackDataMarkup,
|
|
8
|
+
CallbackQueryDataRule,
|
|
9
|
+
CallbackQueryRule,
|
|
10
|
+
HasData,
|
|
7
11
|
)
|
|
12
|
+
from .command import Argument, Command
|
|
13
|
+
from .enum_text import EnumTextRule
|
|
8
14
|
from .func import FuncRule
|
|
9
|
-
from .is_from import IsPrivate, IsChat
|
|
10
|
-
from .markup import Markup
|
|
11
|
-
from .regex import Regex
|
|
12
|
-
from .text import Text, HasText, ABCTextMessageRule
|
|
13
15
|
from .fuzzy import FuzzyText
|
|
16
|
+
from .inline import (
|
|
17
|
+
HasLocation,
|
|
18
|
+
InlineQueryChatType,
|
|
19
|
+
InlineQueryMarkup,
|
|
20
|
+
InlineQueryRule,
|
|
21
|
+
InlineQueryText,
|
|
22
|
+
)
|
|
14
23
|
from .integer import Integer, IntegerInRange
|
|
24
|
+
from .is_from import (
|
|
25
|
+
IsBasketballDice,
|
|
26
|
+
IsBot,
|
|
27
|
+
IsBowlingDice,
|
|
28
|
+
IsChat,
|
|
29
|
+
IsChatId,
|
|
30
|
+
IsDartDice,
|
|
31
|
+
IsDice,
|
|
32
|
+
IsForum,
|
|
33
|
+
IsGroup,
|
|
34
|
+
IsLanguageCode,
|
|
35
|
+
IsPremium,
|
|
36
|
+
IsPrivate,
|
|
37
|
+
IsReply,
|
|
38
|
+
IsSuperGroup,
|
|
39
|
+
IsUser,
|
|
40
|
+
IsUserId,
|
|
41
|
+
)
|
|
42
|
+
from .markup import Markup
|
|
43
|
+
from .mention import HasMention
|
|
44
|
+
from .message_entities import HasEntities, MessageEntities
|
|
45
|
+
from .regex import Regex
|
|
46
|
+
from .rule_enum import RuleEnum
|
|
47
|
+
from .start import StartCommand
|
|
48
|
+
from .text import HasText, Text, TextMessageRule
|
|
49
|
+
|
|
50
|
+
__all__ = (
|
|
51
|
+
"ABCRule",
|
|
52
|
+
"AndRule",
|
|
53
|
+
"Argument",
|
|
54
|
+
"CallbackDataEq",
|
|
55
|
+
"CallbackDataJsonEq",
|
|
56
|
+
"CallbackDataJsonModel",
|
|
57
|
+
"CallbackDataMap",
|
|
58
|
+
"CallbackDataMarkup",
|
|
59
|
+
"CallbackQueryDataRule",
|
|
60
|
+
"CallbackQueryRule",
|
|
61
|
+
"Command",
|
|
62
|
+
"EnumTextRule",
|
|
63
|
+
"FuncRule",
|
|
64
|
+
"FuzzyText",
|
|
65
|
+
"HasData",
|
|
66
|
+
"HasEntities",
|
|
67
|
+
"HasMention",
|
|
68
|
+
"HasText",
|
|
69
|
+
"InlineQueryRule",
|
|
70
|
+
"InlineQueryText",
|
|
71
|
+
"Integer",
|
|
72
|
+
"IntegerInRange",
|
|
73
|
+
"IsBasketballDice",
|
|
74
|
+
"IsBot",
|
|
75
|
+
"IsBowlingDice",
|
|
76
|
+
"IsChat",
|
|
77
|
+
"IsChatId",
|
|
78
|
+
"IsDartDice",
|
|
79
|
+
"IsDice",
|
|
80
|
+
"IsForum",
|
|
81
|
+
"IsGroup",
|
|
82
|
+
"IsLanguageCode",
|
|
83
|
+
"IsPremium",
|
|
84
|
+
"IsPrivate",
|
|
85
|
+
"IsReply",
|
|
86
|
+
"IsSuperGroup",
|
|
87
|
+
"IsUser",
|
|
88
|
+
"IsUserId",
|
|
89
|
+
"HasLocation",
|
|
90
|
+
"InlineQueryChatType",
|
|
91
|
+
"InlineQueryMarkup",
|
|
92
|
+
"Markup",
|
|
93
|
+
"MessageEntities",
|
|
94
|
+
"MessageRule",
|
|
95
|
+
"OrRule",
|
|
96
|
+
"Regex",
|
|
97
|
+
"RuleEnum",
|
|
98
|
+
"StartCommand",
|
|
99
|
+
"Text",
|
|
100
|
+
"TextMessageRule",
|
|
101
|
+
)
|
telegrinder/bot/rules/abc.py
CHANGED
|
@@ -1,102 +1,120 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from telegrinder.bot.cute_types import MessageCute
|
|
3
|
-
from telegrinder.types import Update
|
|
4
|
-
import typing
|
|
5
|
-
import collections
|
|
6
1
|
import inspect
|
|
7
|
-
import
|
|
2
|
+
import typing
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
from telegrinder.bot.cute_types import BaseCute, MessageCute, UpdateCute
|
|
6
|
+
from telegrinder.bot.dispatch.context import Context
|
|
7
|
+
from telegrinder.bot.dispatch.process import check_rule
|
|
8
|
+
from telegrinder.bot.rules.adapter import ABCAdapter, EventAdapter, RawUpdateAdapter
|
|
9
|
+
from telegrinder.tools.i18n.base import ABCTranslator
|
|
10
|
+
from telegrinder.tools.magic import cache_translation, get_cached_translation
|
|
11
|
+
from telegrinder.types.objects import Update as UpdateObject
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
EventScheme = collections.namedtuple("EventScheme", ["name", "dataclass"])
|
|
13
|
+
T = typing.TypeVar("T", bound=BaseCute)
|
|
14
14
|
|
|
15
|
+
Message: typing.TypeAlias = MessageCute
|
|
16
|
+
Update: typing.TypeAlias = UpdateCute
|
|
15
17
|
|
|
16
|
-
class ABCRule(ABC, typing.Generic[T]):
|
|
17
|
-
__event__: typing.Optional[EventScheme] = None
|
|
18
|
-
require: typing.List["ABCRule[T]"] = []
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
def with_caching_translations(func):
|
|
20
|
+
"""Should be used as decorator for .translate method. Caches rule translations."""
|
|
21
|
+
|
|
22
|
+
async def wrapper(self: "ABCRule", translator: ABCTranslator):
|
|
23
|
+
if translation := get_cached_translation(self, translator.locale):
|
|
24
|
+
return translation
|
|
25
|
+
translation = await func(self, translator)
|
|
26
|
+
cache_translation(self, translator.locale, translation)
|
|
27
|
+
return translation
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ABCRule(ABC, typing.Generic[T]):
|
|
33
|
+
adapter: ABCAdapter[UpdateObject, T] = RawUpdateAdapter() # type: ignore
|
|
34
|
+
requires: list["ABCRule[T]"] = []
|
|
27
35
|
|
|
28
36
|
@abstractmethod
|
|
29
|
-
async def check(self, event: T, ctx:
|
|
37
|
+
async def check(self, event: T, ctx: Context) -> bool:
|
|
30
38
|
pass
|
|
31
39
|
|
|
32
|
-
def __init_subclass__(cls,
|
|
33
|
-
"""Merges requirements from inherited classes and rule-specific requirements"""
|
|
40
|
+
def __init_subclass__(cls, requires: list["ABCRule[T]"] | None = None):
|
|
41
|
+
"""Merges requirements from inherited classes and rule-specific requirements."""
|
|
42
|
+
|
|
34
43
|
requirements = []
|
|
35
44
|
for base in inspect.getmro(cls):
|
|
36
45
|
if issubclass(base, ABCRule) and base != cls:
|
|
37
|
-
requirements.extend(base.
|
|
46
|
+
requirements.extend(base.requires or ()) # type: ignore
|
|
38
47
|
|
|
39
|
-
requirements.extend(
|
|
40
|
-
cls.
|
|
48
|
+
requirements.extend(requires or ())
|
|
49
|
+
cls.requires = list(dict.fromkeys(requirements))
|
|
41
50
|
|
|
42
|
-
def __and__(self, other: "ABCRule"):
|
|
51
|
+
def __and__(self, other: "ABCRule[T]"):
|
|
43
52
|
return AndRule(self, other)
|
|
44
53
|
|
|
45
|
-
def __or__(self, other: "ABCRule"):
|
|
54
|
+
def __or__(self, other: "ABCRule[T]"):
|
|
46
55
|
return OrRule(self, other)
|
|
47
56
|
|
|
57
|
+
def __neg__(self) -> "ABCRule[T]":
|
|
58
|
+
return NotRule(self)
|
|
59
|
+
|
|
48
60
|
def __repr__(self) -> str:
|
|
49
|
-
return
|
|
61
|
+
return "<rule: {!r}, adapter: {!r}>".format(
|
|
62
|
+
self.__class__.__name__,
|
|
63
|
+
self.adapter,
|
|
64
|
+
)
|
|
50
65
|
|
|
66
|
+
async def translate(self, translator: ABCTranslator) -> typing.Self:
|
|
67
|
+
return self
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
|
|
70
|
+
class AndRule(ABCRule[T]):
|
|
71
|
+
def __init__(self, *rules: ABCRule[T]):
|
|
54
72
|
self.rules = rules
|
|
55
73
|
|
|
56
|
-
async def check(self, event: Update, ctx:
|
|
74
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
57
75
|
ctx_copy = ctx.copy()
|
|
58
76
|
for rule in self.rules:
|
|
59
|
-
|
|
60
|
-
if rule.__event__:
|
|
61
|
-
event_dict = event.to_dict()
|
|
62
|
-
if rule.__event__.name not in event:
|
|
63
|
-
return False
|
|
64
|
-
e = rule.__event__.dataclass(
|
|
65
|
-
**event_dict[rule.__event__.name].to_dict()
|
|
66
|
-
)
|
|
67
|
-
if not await rule.run_check(e, ctx_copy):
|
|
77
|
+
if not await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
68
78
|
return False
|
|
69
|
-
ctx
|
|
70
|
-
ctx.update(ctx_copy)
|
|
79
|
+
ctx |= ctx_copy
|
|
71
80
|
return True
|
|
72
81
|
|
|
73
82
|
|
|
74
|
-
class OrRule(ABCRule):
|
|
75
|
-
def __init__(self, *rules: ABCRule):
|
|
83
|
+
class OrRule(ABCRule[T]):
|
|
84
|
+
def __init__(self, *rules: ABCRule[T]):
|
|
76
85
|
self.rules = rules
|
|
77
86
|
|
|
78
|
-
async def check(self, event:
|
|
79
|
-
ctx_copy = ctx.copy()
|
|
80
|
-
found = False
|
|
81
|
-
|
|
87
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
82
88
|
for rule in self.rules:
|
|
83
|
-
|
|
84
|
-
if rule
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if await rule.run_check(e, ctx_copy):
|
|
89
|
-
found = True
|
|
90
|
-
break
|
|
89
|
+
ctx_copy = ctx.copy()
|
|
90
|
+
if await check_rule(event.ctx_api, rule, event, ctx_copy):
|
|
91
|
+
ctx |= ctx_copy
|
|
92
|
+
return True
|
|
93
|
+
return False
|
|
91
94
|
|
|
92
|
-
ctx.clear()
|
|
93
|
-
ctx.update(ctx_copy)
|
|
94
|
-
return found
|
|
95
95
|
|
|
96
|
+
class NotRule(ABCRule[T]):
|
|
97
|
+
def __init__(self, rule: ABCRule[T]):
|
|
98
|
+
self.rule = rule
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
async def check(self, event: Update, ctx: Context) -> bool:
|
|
101
|
+
ctx_copy = ctx.copy()
|
|
102
|
+
return not await check_rule(event.ctx_api, self.rule, event, ctx_copy)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MessageRule(ABCRule[Message], ABC, requires=[]):
|
|
106
|
+
adapter = EventAdapter("message", Message)
|
|
99
107
|
|
|
100
108
|
@abstractmethod
|
|
101
|
-
async def check(self, message: Message, ctx:
|
|
109
|
+
async def check(self, message: Message, ctx: Context) -> bool:
|
|
102
110
|
...
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
__all__ = (
|
|
114
|
+
"ABCRule",
|
|
115
|
+
"AndRule",
|
|
116
|
+
"MessageRule",
|
|
117
|
+
"NotRule",
|
|
118
|
+
"OrRule",
|
|
119
|
+
"with_caching_translations",
|
|
120
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from fntypes.result import Result
|
|
5
|
+
|
|
6
|
+
from telegrinder.api.abc import ABCAPI
|
|
7
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
8
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
|
+
from telegrinder.model import Model
|
|
10
|
+
|
|
11
|
+
UpdateT = typing.TypeVar("UpdateT", bound=Model)
|
|
12
|
+
CuteT = typing.TypeVar("CuteT", bound=BaseCute)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ABCAdapter(abc.ABC, typing.Generic[UpdateT, CuteT]):
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
async def adapt(self, api: ABCAPI, update: UpdateT) -> Result[CuteT, AdapterError]:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ("ABCAdapter",)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from fntypes.result import Error, Ok, Result
|
|
4
|
+
|
|
5
|
+
from telegrinder.api.abc import ABCAPI
|
|
6
|
+
from telegrinder.bot.cute_types import BaseCute
|
|
7
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
8
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
9
|
+
from telegrinder.msgspec_utils import Nothing
|
|
10
|
+
from telegrinder.types.objects import Model, Update
|
|
11
|
+
|
|
12
|
+
EventT = typing.TypeVar("EventT", bound=Model)
|
|
13
|
+
CuteT = typing.TypeVar("CuteT", bound=BaseCute)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EventAdapter(ABCAdapter[Update, CuteT]):
|
|
17
|
+
def __init__(self, event_name: str, model: type[CuteT]):
|
|
18
|
+
self.event_name = event_name
|
|
19
|
+
self.model = model
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return "<{}: adapt Update.{} -> {}>".format(
|
|
23
|
+
self.__class__.__name__,
|
|
24
|
+
self.event_name,
|
|
25
|
+
self.model.__name__,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def adapt(self, api: ABCAPI, update: Update) -> Result[CuteT, AdapterError]:
|
|
29
|
+
update_dct = update.to_dict()
|
|
30
|
+
if self.event_name not in update_dct:
|
|
31
|
+
return Error(
|
|
32
|
+
AdapterError(f"Update is not of event type {self.event_name!r}.")
|
|
33
|
+
)
|
|
34
|
+
if update_dct[self.event_name] is Nothing:
|
|
35
|
+
return Error(
|
|
36
|
+
AdapterError(f"Update is not an {self.event_name!r}.")
|
|
37
|
+
)
|
|
38
|
+
return Ok(
|
|
39
|
+
self.model.from_update(update_dct[self.event_name].unwrap(), bound_api=api)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ("EventAdapter",)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from fntypes.result import Ok, Result
|
|
2
|
+
|
|
3
|
+
from telegrinder.api.abc import ABCAPI
|
|
4
|
+
from telegrinder.bot.cute_types.update import UpdateCute
|
|
5
|
+
from telegrinder.bot.rules.adapter.abc import ABCAdapter
|
|
6
|
+
from telegrinder.bot.rules.adapter.errors import AdapterError
|
|
7
|
+
from telegrinder.types.objects import Update
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
|
|
11
|
+
def __repr__(self) -> str:
|
|
12
|
+
return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
|
|
13
|
+
|
|
14
|
+
async def adapt(
|
|
15
|
+
self,
|
|
16
|
+
api: ABCAPI,
|
|
17
|
+
update: Update,
|
|
18
|
+
) -> Result[UpdateCute, AdapterError]:
|
|
19
|
+
if not isinstance(update, UpdateCute):
|
|
20
|
+
return Ok(UpdateCute.from_update(update, api))
|
|
21
|
+
return Ok(update)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ("RawUpdateAdapter",)
|