bot-framework 0.1.3__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.
Files changed (130) hide show
  1. bot_framework/__init__.py +57 -0
  2. bot_framework/base_protocols/__init__.py +21 -0
  3. bot_framework/base_protocols/create.py +9 -0
  4. bot_framework/base_protocols/delete.py +9 -0
  5. bot_framework/base_protocols/get_all.py +9 -0
  6. bot_framework/base_protocols/get_by_key.py +9 -0
  7. bot_framework/base_protocols/get_by_name.py +9 -0
  8. bot_framework/base_protocols/read.py +11 -0
  9. bot_framework/base_protocols/read_sequence_by_user_id.py +11 -0
  10. bot_framework/base_protocols/update.py +9 -0
  11. bot_framework/entities/__init__.py +24 -0
  12. bot_framework/entities/bot_callback.py +21 -0
  13. bot_framework/entities/bot_message.py +27 -0
  14. bot_framework/entities/bot_user.py +22 -0
  15. bot_framework/entities/button.py +8 -0
  16. bot_framework/entities/keyboard.py +9 -0
  17. bot_framework/entities/language_code.py +8 -0
  18. bot_framework/entities/parse_mode.py +6 -0
  19. bot_framework/entities/role.py +16 -0
  20. bot_framework/entities/role_name.py +7 -0
  21. bot_framework/entities/user.py +23 -0
  22. bot_framework/flow_management/__init__.py +31 -0
  23. bot_framework/flow_management/entities/__init__.py +5 -0
  24. bot_framework/flow_management/entities/flow_stack_entry.py +10 -0
  25. bot_framework/flow_management/flow_registry.py +14 -0
  26. bot_framework/flow_management/protocols/__init__.py +11 -0
  27. bot_framework/flow_management/protocols/i_flow_message_deleter.py +8 -0
  28. bot_framework/flow_management/protocols/i_flow_message_storage.py +12 -0
  29. bot_framework/flow_management/protocols/i_flow_stack_storage.py +14 -0
  30. bot_framework/flow_management/protocols/i_flow_stack_validator.py +6 -0
  31. bot_framework/flow_management/repos/__init__.py +8 -0
  32. bot_framework/flow_management/repos/redis_flow_message_storage.py +35 -0
  33. bot_framework/flow_management/repos/redis_flow_stack_storage.py +53 -0
  34. bot_framework/flow_management/services/__init__.py +15 -0
  35. bot_framework/flow_management/services/flow_message_deleter.py +33 -0
  36. bot_framework/flow_management/services/flow_stack_navigator.py +104 -0
  37. bot_framework/flow_management/services/flow_stack_validator.py +46 -0
  38. bot_framework/flows/__init__.py +11 -0
  39. bot_framework/flows/request_role_flow/__init__.py +11 -0
  40. bot_framework/flows/request_role_flow/actions/__init__.py +13 -0
  41. bot_framework/flows/request_role_flow/actions/role_assigner.py +30 -0
  42. bot_framework/flows/request_role_flow/actions/role_rejection_notifier.py +24 -0
  43. bot_framework/flows/request_role_flow/actions/role_request_sender.py +74 -0
  44. bot_framework/flows/request_role_flow/entities/__init__.py +7 -0
  45. bot_framework/flows/request_role_flow/entities/request_role_flow_state.py +6 -0
  46. bot_framework/flows/request_role_flow/exceptions.py +6 -0
  47. bot_framework/flows/request_role_flow/factory.py +146 -0
  48. bot_framework/flows/request_role_flow/handlers/__init__.py +23 -0
  49. bot_framework/flows/request_role_flow/handlers/approve_role_handler.py +48 -0
  50. bot_framework/flows/request_role_flow/handlers/reject_role_handler.py +46 -0
  51. bot_framework/flows/request_role_flow/handlers/request_role_command_handler.py +24 -0
  52. bot_framework/flows/request_role_flow/handlers/role_selection_handler.py +76 -0
  53. bot_framework/flows/request_role_flow/handlers/show_roles_handler.py +30 -0
  54. bot_framework/flows/request_role_flow/presenters/__init__.py +7 -0
  55. bot_framework/flows/request_role_flow/presenters/role_list_presenter.py +53 -0
  56. bot_framework/flows/request_role_flow/protocols/__init__.py +27 -0
  57. bot_framework/flows/request_role_flow/protocols/i_request_role_flow_router.py +7 -0
  58. bot_framework/flows/request_role_flow/protocols/i_request_role_flow_state_storage.py +11 -0
  59. bot_framework/flows/request_role_flow/protocols/i_role_assigner.py +10 -0
  60. bot_framework/flows/request_role_flow/protocols/i_role_list_presenter.py +5 -0
  61. bot_framework/flows/request_role_flow/protocols/i_role_rejection_notifier.py +9 -0
  62. bot_framework/flows/request_role_flow/protocols/i_role_request_sender.py +12 -0
  63. bot_framework/flows/request_role_flow/repos/__init__.py +7 -0
  64. bot_framework/flows/request_role_flow/repos/redis_request_role_flow_state_storage.py +33 -0
  65. bot_framework/flows/request_role_flow/request_role_flow_router.py +18 -0
  66. bot_framework/language_management/__init__.py +13 -0
  67. bot_framework/language_management/entities/__init__.py +10 -0
  68. bot_framework/language_management/entities/language.py +15 -0
  69. bot_framework/language_management/entities/phrase.py +16 -0
  70. bot_framework/language_management/repos/__init__.py +7 -0
  71. bot_framework/language_management/repos/language_repo.py +67 -0
  72. bot_framework/language_management/repos/phrase_repo.py +31 -0
  73. bot_framework/language_management/repos/protocols/__init__.py +7 -0
  74. bot_framework/language_management/repos/protocols/i_language_repo.py +10 -0
  75. bot_framework/language_management/repos/protocols/i_phrase_repo.py +9 -0
  76. bot_framework/protocols/__init__.py +37 -0
  77. bot_framework/protocols/i_callback_answerer.py +10 -0
  78. bot_framework/protocols/i_callback_handler.py +16 -0
  79. bot_framework/protocols/i_callback_handler_registry.py +9 -0
  80. bot_framework/protocols/i_card_field_formatter.py +7 -0
  81. bot_framework/protocols/i_display_width_calculator.py +5 -0
  82. bot_framework/protocols/i_ensure_user_exists.py +7 -0
  83. bot_framework/protocols/i_flow_router.py +19 -0
  84. bot_framework/protocols/i_markdown_to_html_converter.py +5 -0
  85. bot_framework/protocols/i_message_deleter.py +5 -0
  86. bot_framework/protocols/i_message_handler.py +16 -0
  87. bot_framework/protocols/i_message_handler_registry.py +14 -0
  88. bot_framework/protocols/i_message_replacer.py +17 -0
  89. bot_framework/protocols/i_message_sender.py +31 -0
  90. bot_framework/protocols/i_message_service.py +16 -0
  91. bot_framework/protocols/i_next_step_handler_registrar.py +12 -0
  92. bot_framework/protocols/i_notify_replacer.py +17 -0
  93. bot_framework/protocols/i_remaining_time_formatter.py +6 -0
  94. bot_framework/role_management/__init__.py +13 -0
  95. bot_framework/role_management/entities/__init__.py +10 -0
  96. bot_framework/role_management/entities/user_role.py +13 -0
  97. bot_framework/role_management/repos/__init__.py +5 -0
  98. bot_framework/role_management/repos/protocols/__init__.py +7 -0
  99. bot_framework/role_management/repos/protocols/i_role_repo.py +30 -0
  100. bot_framework/role_management/repos/protocols/i_user_repo.py +24 -0
  101. bot_framework/role_management/repos/role_repo.py +103 -0
  102. bot_framework/role_management/services/__init__.py +1 -0
  103. bot_framework/role_management/services/protocols/__init__.py +1 -0
  104. bot_framework/services/__init__.py +9 -0
  105. bot_framework/services/card_field_formatter.py +43 -0
  106. bot_framework/services/display_width_calculator.py +45 -0
  107. bot_framework/services/remaining_time_formatter.py +31 -0
  108. bot_framework/telegram/__init__.py +56 -0
  109. bot_framework/telegram/middleware/__init__.py +4 -0
  110. bot_framework/telegram/middleware/ensure_user_middleware.py +46 -0
  111. bot_framework/telegram/middleware/i_middleware.py +7 -0
  112. bot_framework/telegram/protocols/__init__.py +36 -0
  113. bot_framework/telegram/protocols/i_markdown_escaper.py +5 -0
  114. bot_framework/telegram/services/__init__.py +29 -0
  115. bot_framework/telegram/services/callback_answerer.py +16 -0
  116. bot_framework/telegram/services/callback_handler_registry.py +43 -0
  117. bot_framework/telegram/services/close_callback_handler.py +25 -0
  118. bot_framework/telegram/services/ensure_user_exists.py +37 -0
  119. bot_framework/telegram/services/markdown_escaper.py +8 -0
  120. bot_framework/telegram/services/markdown_to_html_converter.py +16 -0
  121. bot_framework/telegram/services/message_handler_registry.py +49 -0
  122. bot_framework/telegram/services/next_step_handler_registrar.py +43 -0
  123. bot_framework/telegram/services/telegram_message_core.py +62 -0
  124. bot_framework/telegram/services/telegram_message_deleter.py +19 -0
  125. bot_framework/telegram/services/telegram_message_replacer.py +47 -0
  126. bot_framework/telegram/services/telegram_message_sender.py +73 -0
  127. bot_framework/telegram/services/telegram_notify_replacer.py +30 -0
  128. bot_framework-0.1.3.dist-info/METADATA +71 -0
  129. bot_framework-0.1.3.dist-info/RECORD +130 -0
  130. bot_framework-0.1.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,57 @@
1
+ """
2
+ Bot Framework - A reusable Python library for building Telegram bots.
3
+
4
+ This package provides core components for building Telegram bots with
5
+ Clean Architecture principles.
6
+ """
7
+
8
+ from bot_framework.entities.bot_callback import BotCallback
9
+ from bot_framework.entities.bot_message import BotMessage, BotMessageUser
10
+ from bot_framework.entities.bot_user import BotUser
11
+ from bot_framework.entities.button import Button
12
+ from bot_framework.entities.keyboard import Keyboard
13
+ from bot_framework.entities.language_code import LanguageCode
14
+ from bot_framework.entities.parse_mode import ParseMode
15
+ from bot_framework.entities.role import Role
16
+ from bot_framework.entities.role_name import RoleName
17
+ from bot_framework.entities.user import User
18
+ from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
19
+ from bot_framework.protocols.i_callback_handler import ICallbackHandler
20
+ from bot_framework.protocols.i_callback_handler_registry import (
21
+ ICallbackHandlerRegistry,
22
+ )
23
+ from bot_framework.protocols.i_flow_router import IFlowRouter
24
+ from bot_framework.protocols.i_message_deleter import IMessageDeleter
25
+ from bot_framework.protocols.i_message_handler import IMessageHandler
26
+ from bot_framework.protocols.i_message_handler_registry import IMessageHandlerRegistry
27
+ from bot_framework.protocols.i_message_replacer import IMessageReplacer
28
+ from bot_framework.protocols.i_message_sender import IMessageSender
29
+ from bot_framework.protocols.i_notify_replacer import INotifyReplacer
30
+
31
+ __version__ = "0.1.0"
32
+
33
+ __all__ = [
34
+ # Entities
35
+ "BotCallback",
36
+ "BotMessage",
37
+ "BotMessageUser",
38
+ "BotUser",
39
+ "Button",
40
+ "Keyboard",
41
+ "LanguageCode",
42
+ "ParseMode",
43
+ "Role",
44
+ "RoleName",
45
+ "User",
46
+ # Protocols
47
+ "ICallbackAnswerer",
48
+ "ICallbackHandler",
49
+ "ICallbackHandlerRegistry",
50
+ "IFlowRouter",
51
+ "IMessageDeleter",
52
+ "IMessageHandler",
53
+ "IMessageHandlerRegistry",
54
+ "IMessageReplacer",
55
+ "IMessageSender",
56
+ "INotifyReplacer",
57
+ ]
@@ -0,0 +1,21 @@
1
+ from .create import CreateProtocol
2
+ from .delete import DeleteProtocol
3
+ from .get_all import GetAllProtocol
4
+ from .get_by_key import GetByKeyProtocol
5
+ from .get_by_name import GetByNameProtocol
6
+ from .read import ReadProtocol
7
+ from .read_sequence_by_user_id import (
8
+ ReadSequenceByUserIdProtocol,
9
+ )
10
+ from .update import UpdateProtocol
11
+
12
+ __all__ = [
13
+ "CreateProtocol",
14
+ "ReadProtocol",
15
+ "UpdateProtocol",
16
+ "DeleteProtocol",
17
+ "GetAllProtocol",
18
+ "GetByKeyProtocol",
19
+ "GetByNameProtocol",
20
+ "ReadSequenceByUserIdProtocol",
21
+ ]
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class CreateProtocol(Protocol[T]):
9
+ def create(self, entity: T) -> T: ...
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T", contravariant=True)
6
+
7
+
8
+ class DeleteProtocol(Protocol[T]):
9
+ def delete(self, entity: T) -> None: ...
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class GetAllProtocol(Protocol[T]):
9
+ def get_all(self) -> list[T]: ...
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T", covariant=True)
6
+
7
+
8
+ class GetByKeyProtocol(Protocol[T]):
9
+ def get_by_key(self, key: str) -> T: ...
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class GetByNameProtocol(Protocol[T]):
9
+ def get_by_name(self, name: str) -> list[T]: ...
@@ -0,0 +1,11 @@
1
+ from typing import (
2
+ Protocol,
3
+ TypeVar,
4
+ )
5
+
6
+ T = TypeVar("T", covariant=True)
7
+
8
+
9
+ class ReadProtocol(Protocol[T]):
10
+ def get_by_id(self, id: int) -> T: ...
11
+ def find_by_id(self, id: int) -> T | None: ...
@@ -0,0 +1,11 @@
1
+ from collections.abc import Sequence
2
+ from typing import (
3
+ Protocol,
4
+ TypeVar,
5
+ )
6
+
7
+ T = TypeVar("T", covariant=True)
8
+
9
+
10
+ class ReadSequenceByUserIdProtocol(Protocol[T]):
11
+ def get_by_user_id(self, user_id: int) -> Sequence[T]: ...
@@ -0,0 +1,9 @@
1
+ from typing import (
2
+ Protocol, TypeVar,
3
+ )
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class UpdateProtocol(Protocol[T]):
9
+ def update(self, entity: T) -> T: ...
@@ -0,0 +1,24 @@
1
+ from .bot_callback import BotCallback
2
+ from .bot_message import BotMessage, BotMessageUser
3
+ from .bot_user import BotUser
4
+ from .button import Button
5
+ from .keyboard import Keyboard
6
+ from .language_code import LanguageCode
7
+ from .parse_mode import ParseMode
8
+ from .role import Role
9
+ from .role_name import RoleName
10
+ from .user import User
11
+
12
+ __all__ = [
13
+ "BotCallback",
14
+ "BotMessage",
15
+ "BotMessageUser",
16
+ "BotUser",
17
+ "Button",
18
+ "Keyboard",
19
+ "LanguageCode",
20
+ "ParseMode",
21
+ "Role",
22
+ "RoleName",
23
+ "User",
24
+ ]
@@ -0,0 +1,21 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, PrivateAttr
4
+
5
+
6
+ class BotCallback(BaseModel):
7
+ model_config = ConfigDict(from_attributes=True)
8
+
9
+ id: str
10
+ user_id: int
11
+ data: str | None
12
+ message_id: int | None
13
+ message_chat_id: int | None
14
+ user_language_code: str | None = None
15
+ _original: Any = PrivateAttr(default=None)
16
+
17
+ def get_original(self) -> Any:
18
+ return self._original
19
+
20
+ def set_original(self, original: Any) -> None:
21
+ self._original = original
@@ -0,0 +1,27 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, PrivateAttr
4
+
5
+
6
+ class BotMessageUser(BaseModel):
7
+ model_config = ConfigDict(from_attributes=True)
8
+
9
+ id: int
10
+ language_code: str | None = None
11
+
12
+
13
+ class BotMessage(BaseModel):
14
+ model_config = ConfigDict(from_attributes=True)
15
+
16
+ chat_id: int
17
+ message_id: int
18
+ user_id: int | None = None
19
+ text: str | None = None
20
+ from_user: BotMessageUser | None = None
21
+ _original: Any = PrivateAttr(default=None)
22
+
23
+ def get_original(self) -> Any:
24
+ return self._original
25
+
26
+ def set_original(self, original: Any) -> None:
27
+ self._original = original
@@ -0,0 +1,22 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, PrivateAttr
4
+
5
+
6
+ class BotUser(BaseModel):
7
+ model_config = ConfigDict(from_attributes=True)
8
+
9
+ id: int
10
+ username: str | None
11
+ first_name: str | None
12
+ last_name: str | None
13
+ language_code: str
14
+ is_bot: bool
15
+ is_premium: bool
16
+ _original: Any = PrivateAttr(default=None)
17
+
18
+ def get_original(self) -> Any:
19
+ return self._original
20
+
21
+ def set_original(self, original: Any) -> None:
22
+ self._original = original
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class Button(BaseModel):
5
+ model_config = ConfigDict(from_attributes=True)
6
+
7
+ text: str
8
+ callback_data: str
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+ from bot_framework.entities.button import Button
4
+
5
+
6
+ class Keyboard(BaseModel):
7
+ model_config = ConfigDict(from_attributes=True)
8
+
9
+ rows: list[list[Button]]
@@ -0,0 +1,8 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class LanguageCode(StrEnum):
5
+ EN = "en"
6
+ RU = "ru"
7
+ KZ = "kz"
8
+ KH = "kh"
@@ -0,0 +1,6 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class ParseMode(StrEnum):
5
+ HTML = "HTML"
6
+ MARKDOWN = "MarkdownV2"
@@ -0,0 +1,16 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import (
4
+ BaseModel, ConfigDict, Field,
5
+ )
6
+
7
+
8
+ class Role(BaseModel):
9
+ id: int = 0
10
+ name: str
11
+ description: str | None = None
12
+ is_active: bool = True
13
+ created_at: datetime = Field(default_factory=datetime.now)
14
+ updated_at: datetime = Field(default_factory=datetime.now)
15
+
16
+ model_config = ConfigDict(from_attributes=True)
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class RoleName(str, Enum):
5
+ USER = "user"
6
+ ADMIN = "admin"
7
+ MODERATOR = "moderator"
@@ -0,0 +1,23 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import (
4
+ BaseModel,
5
+ ConfigDict,
6
+ Field,
7
+ )
8
+
9
+ from bot_framework.entities.language_code import LanguageCode
10
+
11
+
12
+ class User(BaseModel):
13
+ id: int
14
+ username: str | None = None
15
+ first_name: str | None = None
16
+ last_name: str | None = None
17
+ language_code: str = LanguageCode.EN
18
+ is_bot: bool = False
19
+ is_premium: bool = False
20
+ created_at: datetime = Field(default_factory=datetime.now)
21
+ updated_at: datetime = Field(default_factory=datetime.now)
22
+
23
+ model_config = ConfigDict(from_attributes=True)
@@ -0,0 +1,31 @@
1
+ from bot_framework.flow_management.entities import FlowStackEntry
2
+ from bot_framework.flow_management.flow_registry import FlowRegistry
3
+ from bot_framework.flow_management.protocols import (
4
+ IFlowMessageDeleter,
5
+ IFlowMessageStorage,
6
+ IFlowStackStorage,
7
+ IFlowStackValidator,
8
+ )
9
+ from bot_framework.flow_management.repos import (
10
+ RedisFlowMessageStorage,
11
+ RedisFlowStackStorage,
12
+ )
13
+ from bot_framework.flow_management.services import (
14
+ FlowMessageDeleter,
15
+ FlowStackNavigator,
16
+ FlowStackValidator,
17
+ )
18
+
19
+ __all__ = [
20
+ "FlowStackEntry",
21
+ "FlowRegistry",
22
+ "RedisFlowMessageStorage",
23
+ "RedisFlowStackStorage",
24
+ "IFlowMessageStorage",
25
+ "IFlowStackStorage",
26
+ "FlowMessageDeleter",
27
+ "FlowStackNavigator",
28
+ "FlowStackValidator",
29
+ "IFlowMessageDeleter",
30
+ "IFlowStackValidator",
31
+ ]
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from bot_framework.flow_management.entities.flow_stack_entry import FlowStackEntry
4
+
5
+ __all__ = ["FlowStackEntry"]
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class FlowStackEntry(BaseModel):
9
+ flow_name: str
10
+ started_at: datetime
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from bot_framework.protocols.i_flow_router import IFlowRouter
4
+
5
+
6
+ class FlowRegistry:
7
+ def __init__(self):
8
+ self._flows: dict[str, IFlowRouter] = {}
9
+
10
+ def register(self, name: str, router: IFlowRouter) -> None:
11
+ self._flows[name] = router
12
+
13
+ def get(self, name: str) -> IFlowRouter | None:
14
+ return self._flows.get(name)
@@ -0,0 +1,11 @@
1
+ from bot_framework.flow_management.protocols.i_flow_message_deleter import IFlowMessageDeleter
2
+ from bot_framework.flow_management.protocols.i_flow_message_storage import IFlowMessageStorage
3
+ from bot_framework.flow_management.protocols.i_flow_stack_storage import IFlowStackStorage
4
+ from bot_framework.flow_management.protocols.i_flow_stack_validator import IFlowStackValidator
5
+
6
+ __all__ = [
7
+ "IFlowMessageDeleter",
8
+ "IFlowMessageStorage",
9
+ "IFlowStackStorage",
10
+ "IFlowStackValidator",
11
+ ]
@@ -0,0 +1,8 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+
4
+ @runtime_checkable
5
+ class IFlowMessageDeleter(Protocol):
6
+ def delete_flow_messages(self, chat_id: int, flow_name: str) -> None: ...
7
+
8
+ def delete_all_flow_messages(self, chat_id: int) -> None: ...
@@ -0,0 +1,12 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+
4
+ @runtime_checkable
5
+ class IFlowMessageStorage(Protocol):
6
+ def add_message(self, telegram_id: int, flow_name: str, message_id: int) -> None: ...
7
+
8
+ def get_messages(self, telegram_id: int, flow_name: str) -> list[int]: ...
9
+
10
+ def clear_messages(self, telegram_id: int, flow_name: str) -> None: ...
11
+
12
+ def clear_all_messages(self, telegram_id: int) -> None: ...
@@ -0,0 +1,14 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+ from bot_framework.flow_management.entities import FlowStackEntry
4
+
5
+
6
+ @runtime_checkable
7
+ class IFlowStackStorage(Protocol):
8
+ def push(self, telegram_id: int, entry: FlowStackEntry) -> None: ...
9
+
10
+ def pop(self, telegram_id: int) -> FlowStackEntry | None: ...
11
+
12
+ def get_stack(self, telegram_id: int) -> list[FlowStackEntry]: ...
13
+
14
+ def clear(self, telegram_id: int) -> None: ...
@@ -0,0 +1,6 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+
4
+ @runtime_checkable
5
+ class IFlowStackValidator(Protocol):
6
+ def validate_push(self, user_id: int, flow_name: str) -> None: ...
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from bot_framework.flow_management.repos.redis_flow_message_storage import (
4
+ RedisFlowMessageStorage,
5
+ )
6
+ from bot_framework.flow_management.repos.redis_flow_stack_storage import RedisFlowStackStorage
7
+
8
+ __all__ = ["RedisFlowMessageStorage", "RedisFlowStackStorage"]
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import redis
4
+
5
+
6
+ class RedisFlowMessageStorage:
7
+ def __init__(self, redis_url: str):
8
+ self.redis_client = redis.from_url(redis_url) # pyright: ignore[reportUnknownMemberType]
9
+ self.ttl = 600
10
+
11
+ def _get_key(self, telegram_id: int, flow_name: str) -> str:
12
+ return f"flow_messages:{telegram_id}:{flow_name}"
13
+
14
+ def _get_pattern(self, telegram_id: int) -> str:
15
+ return f"flow_messages:{telegram_id}:*"
16
+
17
+ def add_message(self, telegram_id: int, flow_name: str, message_id: int) -> None:
18
+ key = self._get_key(telegram_id, flow_name)
19
+ self.redis_client.rpush(key, message_id)
20
+ self.redis_client.expire(key, self.ttl)
21
+
22
+ def get_messages(self, telegram_id: int, flow_name: str) -> list[int]:
23
+ key = self._get_key(telegram_id, flow_name)
24
+ messages = self.redis_client.lrange(key, 0, -1)
25
+ return [int(m) for m in messages] # pyright: ignore[reportGeneralTypeIssues]
26
+
27
+ def clear_messages(self, telegram_id: int, flow_name: str) -> None:
28
+ key = self._get_key(telegram_id, flow_name)
29
+ self.redis_client.delete(key)
30
+
31
+ def clear_all_messages(self, telegram_id: int) -> None:
32
+ pattern = self._get_pattern(telegram_id)
33
+ keys = self.redis_client.keys(pattern)
34
+ if keys:
35
+ self.redis_client.delete(*keys) # pyright: ignore[reportGeneralTypeIssues]
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import redis
6
+
7
+ from bot_framework.flow_management.entities import FlowStackEntry
8
+
9
+
10
+ class RedisFlowStackStorage:
11
+ def __init__(self, redis_url: str):
12
+ self.redis_client = redis.from_url(redis_url)
13
+ self.ttl = 600
14
+
15
+ def _get_key(self, telegram_id: int) -> str:
16
+ return f"flow_stack:{telegram_id}"
17
+
18
+ def _get_raw_stack(self, telegram_id: int) -> list[dict]:
19
+ value = self.redis_client.get(self._get_key(telegram_id))
20
+ if value is None:
21
+ return []
22
+ if isinstance(value, bytes):
23
+ return json.loads(value.decode())
24
+ if isinstance(value, str):
25
+ return json.loads(value)
26
+ return []
27
+
28
+ def _save_raw_stack(self, telegram_id: int, stack: list[dict]) -> None:
29
+ self.redis_client.setex(
30
+ name=self._get_key(telegram_id),
31
+ time=self.ttl,
32
+ value=json.dumps(stack),
33
+ )
34
+
35
+ def push(self, telegram_id: int, entry: FlowStackEntry) -> None:
36
+ stack = self._get_raw_stack(telegram_id)
37
+ stack.append(entry.model_dump(mode="json"))
38
+ self._save_raw_stack(telegram_id, stack)
39
+
40
+ def pop(self, telegram_id: int) -> FlowStackEntry | None:
41
+ stack = self._get_raw_stack(telegram_id)
42
+ if not stack:
43
+ return None
44
+ entry_dict = stack.pop()
45
+ self._save_raw_stack(telegram_id, stack)
46
+ return FlowStackEntry.model_validate(entry_dict)
47
+
48
+ def get_stack(self, telegram_id: int) -> list[FlowStackEntry]:
49
+ raw_stack = self._get_raw_stack(telegram_id)
50
+ return [FlowStackEntry.model_validate(entry) for entry in raw_stack]
51
+
52
+ def clear(self, telegram_id: int) -> None:
53
+ self.redis_client.delete(self._get_key(telegram_id))
@@ -0,0 +1,15 @@
1
+ from bot_framework.flow_management.protocols import (
2
+ IFlowMessageDeleter,
3
+ IFlowStackValidator,
4
+ )
5
+ from bot_framework.flow_management.services.flow_message_deleter import FlowMessageDeleter
6
+ from bot_framework.flow_management.services.flow_stack_navigator import FlowStackNavigator
7
+ from bot_framework.flow_management.services.flow_stack_validator import FlowStackValidator
8
+
9
+ __all__ = [
10
+ "FlowMessageDeleter",
11
+ "FlowStackNavigator",
12
+ "FlowStackValidator",
13
+ "IFlowMessageDeleter",
14
+ "IFlowStackValidator",
15
+ ]
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ from bot_framework.flow_management.protocols import IFlowMessageStorage
6
+ from bot_framework.protocols import IMessageDeleter
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class FlowMessageDeleter:
12
+ def __init__(
13
+ self,
14
+ message_deleter: IMessageDeleter,
15
+ message_storage: IFlowMessageStorage,
16
+ ):
17
+ self._message_deleter = message_deleter
18
+ self._message_storage = message_storage
19
+
20
+ def delete_flow_messages(self, chat_id: int, flow_name: str) -> None:
21
+ message_ids = self._message_storage.get_messages(chat_id, flow_name)
22
+ if not message_ids:
23
+ return
24
+
25
+ self._delete_messages_batch(chat_id, message_ids)
26
+ self._message_storage.clear_messages(chat_id, flow_name)
27
+
28
+ def delete_all_flow_messages(self, chat_id: int) -> None:
29
+ self._message_storage.clear_all_messages(chat_id)
30
+
31
+ def _delete_messages_batch(self, chat_id: int, message_ids: list[int]) -> None:
32
+ for message_id in message_ids:
33
+ self._message_deleter.delete(chat_id, message_id)