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.
- bot_framework/__init__.py +57 -0
- bot_framework/base_protocols/__init__.py +21 -0
- bot_framework/base_protocols/create.py +9 -0
- bot_framework/base_protocols/delete.py +9 -0
- bot_framework/base_protocols/get_all.py +9 -0
- bot_framework/base_protocols/get_by_key.py +9 -0
- bot_framework/base_protocols/get_by_name.py +9 -0
- bot_framework/base_protocols/read.py +11 -0
- bot_framework/base_protocols/read_sequence_by_user_id.py +11 -0
- bot_framework/base_protocols/update.py +9 -0
- bot_framework/entities/__init__.py +24 -0
- bot_framework/entities/bot_callback.py +21 -0
- bot_framework/entities/bot_message.py +27 -0
- bot_framework/entities/bot_user.py +22 -0
- bot_framework/entities/button.py +8 -0
- bot_framework/entities/keyboard.py +9 -0
- bot_framework/entities/language_code.py +8 -0
- bot_framework/entities/parse_mode.py +6 -0
- bot_framework/entities/role.py +16 -0
- bot_framework/entities/role_name.py +7 -0
- bot_framework/entities/user.py +23 -0
- bot_framework/flow_management/__init__.py +31 -0
- bot_framework/flow_management/entities/__init__.py +5 -0
- bot_framework/flow_management/entities/flow_stack_entry.py +10 -0
- bot_framework/flow_management/flow_registry.py +14 -0
- bot_framework/flow_management/protocols/__init__.py +11 -0
- bot_framework/flow_management/protocols/i_flow_message_deleter.py +8 -0
- bot_framework/flow_management/protocols/i_flow_message_storage.py +12 -0
- bot_framework/flow_management/protocols/i_flow_stack_storage.py +14 -0
- bot_framework/flow_management/protocols/i_flow_stack_validator.py +6 -0
- bot_framework/flow_management/repos/__init__.py +8 -0
- bot_framework/flow_management/repos/redis_flow_message_storage.py +35 -0
- bot_framework/flow_management/repos/redis_flow_stack_storage.py +53 -0
- bot_framework/flow_management/services/__init__.py +15 -0
- bot_framework/flow_management/services/flow_message_deleter.py +33 -0
- bot_framework/flow_management/services/flow_stack_navigator.py +104 -0
- bot_framework/flow_management/services/flow_stack_validator.py +46 -0
- bot_framework/flows/__init__.py +11 -0
- bot_framework/flows/request_role_flow/__init__.py +11 -0
- bot_framework/flows/request_role_flow/actions/__init__.py +13 -0
- bot_framework/flows/request_role_flow/actions/role_assigner.py +30 -0
- bot_framework/flows/request_role_flow/actions/role_rejection_notifier.py +24 -0
- bot_framework/flows/request_role_flow/actions/role_request_sender.py +74 -0
- bot_framework/flows/request_role_flow/entities/__init__.py +7 -0
- bot_framework/flows/request_role_flow/entities/request_role_flow_state.py +6 -0
- bot_framework/flows/request_role_flow/exceptions.py +6 -0
- bot_framework/flows/request_role_flow/factory.py +146 -0
- bot_framework/flows/request_role_flow/handlers/__init__.py +23 -0
- bot_framework/flows/request_role_flow/handlers/approve_role_handler.py +48 -0
- bot_framework/flows/request_role_flow/handlers/reject_role_handler.py +46 -0
- bot_framework/flows/request_role_flow/handlers/request_role_command_handler.py +24 -0
- bot_framework/flows/request_role_flow/handlers/role_selection_handler.py +76 -0
- bot_framework/flows/request_role_flow/handlers/show_roles_handler.py +30 -0
- bot_framework/flows/request_role_flow/presenters/__init__.py +7 -0
- bot_framework/flows/request_role_flow/presenters/role_list_presenter.py +53 -0
- bot_framework/flows/request_role_flow/protocols/__init__.py +27 -0
- bot_framework/flows/request_role_flow/protocols/i_request_role_flow_router.py +7 -0
- bot_framework/flows/request_role_flow/protocols/i_request_role_flow_state_storage.py +11 -0
- bot_framework/flows/request_role_flow/protocols/i_role_assigner.py +10 -0
- bot_framework/flows/request_role_flow/protocols/i_role_list_presenter.py +5 -0
- bot_framework/flows/request_role_flow/protocols/i_role_rejection_notifier.py +9 -0
- bot_framework/flows/request_role_flow/protocols/i_role_request_sender.py +12 -0
- bot_framework/flows/request_role_flow/repos/__init__.py +7 -0
- bot_framework/flows/request_role_flow/repos/redis_request_role_flow_state_storage.py +33 -0
- bot_framework/flows/request_role_flow/request_role_flow_router.py +18 -0
- bot_framework/language_management/__init__.py +13 -0
- bot_framework/language_management/entities/__init__.py +10 -0
- bot_framework/language_management/entities/language.py +15 -0
- bot_framework/language_management/entities/phrase.py +16 -0
- bot_framework/language_management/repos/__init__.py +7 -0
- bot_framework/language_management/repos/language_repo.py +67 -0
- bot_framework/language_management/repos/phrase_repo.py +31 -0
- bot_framework/language_management/repos/protocols/__init__.py +7 -0
- bot_framework/language_management/repos/protocols/i_language_repo.py +10 -0
- bot_framework/language_management/repos/protocols/i_phrase_repo.py +9 -0
- bot_framework/protocols/__init__.py +37 -0
- bot_framework/protocols/i_callback_answerer.py +10 -0
- bot_framework/protocols/i_callback_handler.py +16 -0
- bot_framework/protocols/i_callback_handler_registry.py +9 -0
- bot_framework/protocols/i_card_field_formatter.py +7 -0
- bot_framework/protocols/i_display_width_calculator.py +5 -0
- bot_framework/protocols/i_ensure_user_exists.py +7 -0
- bot_framework/protocols/i_flow_router.py +19 -0
- bot_framework/protocols/i_markdown_to_html_converter.py +5 -0
- bot_framework/protocols/i_message_deleter.py +5 -0
- bot_framework/protocols/i_message_handler.py +16 -0
- bot_framework/protocols/i_message_handler_registry.py +14 -0
- bot_framework/protocols/i_message_replacer.py +17 -0
- bot_framework/protocols/i_message_sender.py +31 -0
- bot_framework/protocols/i_message_service.py +16 -0
- bot_framework/protocols/i_next_step_handler_registrar.py +12 -0
- bot_framework/protocols/i_notify_replacer.py +17 -0
- bot_framework/protocols/i_remaining_time_formatter.py +6 -0
- bot_framework/role_management/__init__.py +13 -0
- bot_framework/role_management/entities/__init__.py +10 -0
- bot_framework/role_management/entities/user_role.py +13 -0
- bot_framework/role_management/repos/__init__.py +5 -0
- bot_framework/role_management/repos/protocols/__init__.py +7 -0
- bot_framework/role_management/repos/protocols/i_role_repo.py +30 -0
- bot_framework/role_management/repos/protocols/i_user_repo.py +24 -0
- bot_framework/role_management/repos/role_repo.py +103 -0
- bot_framework/role_management/services/__init__.py +1 -0
- bot_framework/role_management/services/protocols/__init__.py +1 -0
- bot_framework/services/__init__.py +9 -0
- bot_framework/services/card_field_formatter.py +43 -0
- bot_framework/services/display_width_calculator.py +45 -0
- bot_framework/services/remaining_time_formatter.py +31 -0
- bot_framework/telegram/__init__.py +56 -0
- bot_framework/telegram/middleware/__init__.py +4 -0
- bot_framework/telegram/middleware/ensure_user_middleware.py +46 -0
- bot_framework/telegram/middleware/i_middleware.py +7 -0
- bot_framework/telegram/protocols/__init__.py +36 -0
- bot_framework/telegram/protocols/i_markdown_escaper.py +5 -0
- bot_framework/telegram/services/__init__.py +29 -0
- bot_framework/telegram/services/callback_answerer.py +16 -0
- bot_framework/telegram/services/callback_handler_registry.py +43 -0
- bot_framework/telegram/services/close_callback_handler.py +25 -0
- bot_framework/telegram/services/ensure_user_exists.py +37 -0
- bot_framework/telegram/services/markdown_escaper.py +8 -0
- bot_framework/telegram/services/markdown_to_html_converter.py +16 -0
- bot_framework/telegram/services/message_handler_registry.py +49 -0
- bot_framework/telegram/services/next_step_handler_registrar.py +43 -0
- bot_framework/telegram/services/telegram_message_core.py +62 -0
- bot_framework/telegram/services/telegram_message_deleter.py +19 -0
- bot_framework/telegram/services/telegram_message_replacer.py +47 -0
- bot_framework/telegram/services/telegram_message_sender.py +73 -0
- bot_framework/telegram/services/telegram_notify_replacer.py +30 -0
- bot_framework-0.1.3.dist-info/METADATA +71 -0
- bot_framework-0.1.3.dist-info/RECORD +130 -0
- bot_framework-0.1.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from uuid import uuid4
|
|
2
|
+
|
|
3
|
+
from bot_framework.entities.bot_callback import BotCallback
|
|
4
|
+
from bot_framework.flows.request_role_flow.exceptions import NoSupervisorsFoundError
|
|
5
|
+
from bot_framework.flows.request_role_flow.protocols import (
|
|
6
|
+
IRequestRoleFlowStateStorage,
|
|
7
|
+
IRoleRequestSender,
|
|
8
|
+
)
|
|
9
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
10
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
11
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
12
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
13
|
+
from bot_framework.role_management.repos.protocols.i_user_repo import IUserRepo
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RoleSelectionHandler:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
callback_answerer: ICallbackAnswerer,
|
|
20
|
+
message_sender: IMessageSender,
|
|
21
|
+
phrase_repo: IPhraseRepo,
|
|
22
|
+
role_repo: IRoleRepo,
|
|
23
|
+
user_repo: IUserRepo,
|
|
24
|
+
state_storage: IRequestRoleFlowStateStorage,
|
|
25
|
+
role_request_sender: IRoleRequestSender,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.callback_answerer = callback_answerer
|
|
28
|
+
self.message_sender = message_sender
|
|
29
|
+
self.phrase_repo = phrase_repo
|
|
30
|
+
self.role_repo = role_repo
|
|
31
|
+
self.user_repo = user_repo
|
|
32
|
+
self.state_storage = state_storage
|
|
33
|
+
self.role_request_sender = role_request_sender
|
|
34
|
+
self.prefix = uuid4().hex
|
|
35
|
+
self.allowed_roles: set[str] | None = None
|
|
36
|
+
|
|
37
|
+
def handle(self, callback: BotCallback) -> None:
|
|
38
|
+
self.callback_answerer.answer(callback_query_id=callback.id)
|
|
39
|
+
|
|
40
|
+
if not callback.user_id:
|
|
41
|
+
raise ValueError("callback.user_id is required but was None")
|
|
42
|
+
if not callback.message_chat_id:
|
|
43
|
+
raise ValueError("callback.message_chat_id is required but was None")
|
|
44
|
+
if not callback.data:
|
|
45
|
+
raise ValueError("callback.data is required but was None")
|
|
46
|
+
|
|
47
|
+
parts = callback.data.split(":")
|
|
48
|
+
if len(parts) < 2:
|
|
49
|
+
raise ValueError(f"Invalid callback data format: {callback.data}")
|
|
50
|
+
|
|
51
|
+
role_id = int(parts[1])
|
|
52
|
+
telegram_id = callback.user_id
|
|
53
|
+
language_code = callback.user_language_code or "en"
|
|
54
|
+
|
|
55
|
+
self.state_storage.save_selected_role(telegram_id=telegram_id, role_id=role_id)
|
|
56
|
+
|
|
57
|
+
role = self.role_repo.get_by_id(id=role_id)
|
|
58
|
+
requester = self.user_repo.get_by_id(id=telegram_id)
|
|
59
|
+
|
|
60
|
+
user_roles = self.role_repo.get_user_roles(user_id=telegram_id)
|
|
61
|
+
if any(r.id == role_id for r in user_roles):
|
|
62
|
+
phrase_key = "request_role.already_has_role"
|
|
63
|
+
else:
|
|
64
|
+
try:
|
|
65
|
+
self.role_request_sender.send_to_supervisors(requester=requester, role=role)
|
|
66
|
+
phrase_key = "request_role.sent"
|
|
67
|
+
except NoSupervisorsFoundError:
|
|
68
|
+
phrase_key = "request_role.no_supervisors"
|
|
69
|
+
|
|
70
|
+
text = self.phrase_repo.get_phrase(
|
|
71
|
+
key=phrase_key,
|
|
72
|
+
language_code=language_code,
|
|
73
|
+
)
|
|
74
|
+
self.message_sender.send(chat_id=callback.message_chat_id, text=text)
|
|
75
|
+
|
|
76
|
+
self.state_storage.clear_state(telegram_id=telegram_id)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from uuid import uuid4
|
|
2
|
+
|
|
3
|
+
from bot_framework.entities.bot_callback import BotCallback
|
|
4
|
+
from bot_framework.flows.request_role_flow.protocols import IRoleListPresenter
|
|
5
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ShowRolesHandler:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
callback_answerer: ICallbackAnswerer,
|
|
12
|
+
role_list_presenter: IRoleListPresenter,
|
|
13
|
+
) -> None:
|
|
14
|
+
self.callback_answerer = callback_answerer
|
|
15
|
+
self.role_list_presenter = role_list_presenter
|
|
16
|
+
self.prefix = uuid4().hex
|
|
17
|
+
self.allowed_roles: set[str] | None = None
|
|
18
|
+
|
|
19
|
+
def handle(self, callback: BotCallback) -> None:
|
|
20
|
+
self.callback_answerer.answer(callback_query_id=callback.id)
|
|
21
|
+
|
|
22
|
+
if not callback.message_chat_id:
|
|
23
|
+
raise ValueError("callback.message_chat_id is required but was None")
|
|
24
|
+
|
|
25
|
+
language_code = callback.user_language_code or "en"
|
|
26
|
+
self.role_list_presenter.present(
|
|
27
|
+
chat_id=callback.message_chat_id,
|
|
28
|
+
user_id=callback.user_id,
|
|
29
|
+
language_code=language_code,
|
|
30
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from bot_framework.entities.button import Button
|
|
2
|
+
from bot_framework.entities.keyboard import Keyboard
|
|
3
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
4
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
5
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RoleListPresenter:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message_sender: IMessageSender,
|
|
12
|
+
phrase_repo: IPhraseRepo,
|
|
13
|
+
role_repo: IRoleRepo,
|
|
14
|
+
role_selection_handler_prefix: str,
|
|
15
|
+
) -> None:
|
|
16
|
+
self.message_sender = message_sender
|
|
17
|
+
self.phrase_repo = phrase_repo
|
|
18
|
+
self.role_repo = role_repo
|
|
19
|
+
self.role_selection_handler_prefix = role_selection_handler_prefix
|
|
20
|
+
|
|
21
|
+
def present(self, chat_id: int, user_id: int, language_code: str) -> None:
|
|
22
|
+
roles = self.role_repo.get_all()
|
|
23
|
+
|
|
24
|
+
if not roles:
|
|
25
|
+
text = self.phrase_repo.get_phrase(
|
|
26
|
+
key="request_role.no_roles",
|
|
27
|
+
language_code=language_code,
|
|
28
|
+
)
|
|
29
|
+
self.message_sender.send(chat_id=chat_id, text=text)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
text = self.phrase_repo.get_phrase(
|
|
33
|
+
key="request_role.select_title",
|
|
34
|
+
language_code=language_code,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
user_roles = self.role_repo.get_user_roles(user_id)
|
|
38
|
+
user_role_ids = {role.id for role in user_roles}
|
|
39
|
+
|
|
40
|
+
buttons = []
|
|
41
|
+
for role in roles:
|
|
42
|
+
role_text = f"✓ {role.name}" if role.id in user_role_ids else role.name
|
|
43
|
+
buttons.append(
|
|
44
|
+
[
|
|
45
|
+
Button(
|
|
46
|
+
text=role_text,
|
|
47
|
+
callback_data=f"{self.role_selection_handler_prefix}:{role.id}",
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
keyboard = Keyboard(rows=buttons)
|
|
53
|
+
self.message_sender.send(chat_id=chat_id, text=text, keyboard=keyboard)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from bot_framework.flows.request_role_flow.protocols.i_request_role_flow_router import (
|
|
2
|
+
IRequestRoleFlowRouter,
|
|
3
|
+
)
|
|
4
|
+
from bot_framework.flows.request_role_flow.protocols.i_request_role_flow_state_storage import (
|
|
5
|
+
IRequestRoleFlowStateStorage,
|
|
6
|
+
)
|
|
7
|
+
from bot_framework.flows.request_role_flow.protocols.i_role_assigner import (
|
|
8
|
+
IRoleAssigner,
|
|
9
|
+
)
|
|
10
|
+
from bot_framework.flows.request_role_flow.protocols.i_role_list_presenter import (
|
|
11
|
+
IRoleListPresenter,
|
|
12
|
+
)
|
|
13
|
+
from bot_framework.flows.request_role_flow.protocols.i_role_rejection_notifier import (
|
|
14
|
+
IRoleRejectionNotifier,
|
|
15
|
+
)
|
|
16
|
+
from bot_framework.flows.request_role_flow.protocols.i_role_request_sender import (
|
|
17
|
+
IRoleRequestSender,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"IRequestRoleFlowRouter",
|
|
22
|
+
"IRequestRoleFlowStateStorage",
|
|
23
|
+
"IRoleAssigner",
|
|
24
|
+
"IRoleListPresenter",
|
|
25
|
+
"IRoleRejectionNotifier",
|
|
26
|
+
"IRoleRequestSender",
|
|
27
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from bot_framework.flows.request_role_flow.entities import RequestRoleFlowState
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IRequestRoleFlowStateStorage(Protocol):
|
|
7
|
+
def save_selected_role(self, telegram_id: int, role_id: int) -> None: ...
|
|
8
|
+
|
|
9
|
+
def get_state(self, telegram_id: int) -> RequestRoleFlowState | None: ...
|
|
10
|
+
|
|
11
|
+
def clear_state(self, telegram_id: int) -> None: ...
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import redis
|
|
4
|
+
|
|
5
|
+
from bot_framework.flows.request_role_flow.entities import RequestRoleFlowState
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RedisRequestRoleFlowStateStorage:
|
|
9
|
+
def __init__(self, redis_url: str):
|
|
10
|
+
self.redis_client = redis.from_url(redis_url) # pyright: ignore[reportUnknownMemberType]
|
|
11
|
+
self.ttl = 600
|
|
12
|
+
|
|
13
|
+
def _get_key(self, telegram_id: int) -> str:
|
|
14
|
+
return f"request_role_flow:{telegram_id}"
|
|
15
|
+
|
|
16
|
+
def save_selected_role(self, telegram_id: int, role_id: int) -> None:
|
|
17
|
+
key = self._get_key(telegram_id)
|
|
18
|
+
state = RequestRoleFlowState(
|
|
19
|
+
requester_user_id=telegram_id,
|
|
20
|
+
selected_role_id=role_id,
|
|
21
|
+
)
|
|
22
|
+
self.redis_client.set(key, state.model_dump_json(), ex=self.ttl)
|
|
23
|
+
|
|
24
|
+
def get_state(self, telegram_id: int) -> RequestRoleFlowState | None:
|
|
25
|
+
key = self._get_key(telegram_id)
|
|
26
|
+
data = self.redis_client.get(key)
|
|
27
|
+
if not data:
|
|
28
|
+
return None
|
|
29
|
+
return RequestRoleFlowState.model_validate(json.loads(data)) # pyright: ignore[reportArgumentType]
|
|
30
|
+
|
|
31
|
+
def clear_state(self, telegram_id: int) -> None:
|
|
32
|
+
key = self._get_key(telegram_id)
|
|
33
|
+
self.redis_client.delete(key)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from bot_framework.entities.bot_message import BotMessageUser
|
|
2
|
+
from bot_framework.flows.request_role_flow.protocols import IRoleListPresenter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RequestRoleFlowRouter:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
role_list_presenter: IRoleListPresenter,
|
|
9
|
+
) -> None:
|
|
10
|
+
self.role_list_presenter = role_list_presenter
|
|
11
|
+
|
|
12
|
+
def start(self, user: BotMessageUser, chat_id: int) -> None:
|
|
13
|
+
language_code = user.language_code or "en"
|
|
14
|
+
self.role_list_presenter.present(
|
|
15
|
+
chat_id=chat_id,
|
|
16
|
+
user_id=user.id,
|
|
17
|
+
language_code=language_code,
|
|
18
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from bot_framework.language_management.entities import Language, LanguageCode, Phrase
|
|
2
|
+
from bot_framework.language_management.repos import LanguageRepo, PhraseRepo
|
|
3
|
+
from bot_framework.language_management.repos.protocols import ILanguageRepo, IPhraseRepo
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Language",
|
|
7
|
+
"LanguageCode",
|
|
8
|
+
"Phrase",
|
|
9
|
+
"LanguageRepo",
|
|
10
|
+
"PhraseRepo",
|
|
11
|
+
"ILanguageRepo",
|
|
12
|
+
"IPhraseRepo",
|
|
13
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import (
|
|
4
|
+
BaseModel, ConfigDict, Field,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Language(BaseModel):
|
|
9
|
+
code: str
|
|
10
|
+
name: str
|
|
11
|
+
native_name: str
|
|
12
|
+
is_active: bool = True
|
|
13
|
+
created_at: datetime = Field(default_factory=datetime.now)
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(from_attributes=True)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import (
|
|
4
|
+
BaseModel, ConfigDict, Field,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Phrase(BaseModel):
|
|
9
|
+
id: int = 0
|
|
10
|
+
key: str
|
|
11
|
+
language_code: str
|
|
12
|
+
text: str
|
|
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,67 @@
|
|
|
1
|
+
import psycopg
|
|
2
|
+
from psycopg.rows import class_row
|
|
3
|
+
|
|
4
|
+
from bot_framework.language_management.entities.language import Language
|
|
5
|
+
from bot_framework.language_management.repos.protocols.i_language_repo import ILanguageRepo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LanguageRepo(ILanguageRepo):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
database_url: str,
|
|
12
|
+
):
|
|
13
|
+
self.database_url = database_url
|
|
14
|
+
|
|
15
|
+
def get_all(self) -> list[Language]:
|
|
16
|
+
with psycopg.connect(self.database_url) as conn:
|
|
17
|
+
with conn.cursor(row_factory=class_row(Language)) as cur:
|
|
18
|
+
cur.execute(
|
|
19
|
+
"SELECT * FROM languages WHERE is_active = TRUE ORDER BY code",
|
|
20
|
+
)
|
|
21
|
+
return cur.fetchall()
|
|
22
|
+
|
|
23
|
+
def get_by_key(
|
|
24
|
+
self,
|
|
25
|
+
key: str,
|
|
26
|
+
) -> Language:
|
|
27
|
+
language = self.find_by_key(key)
|
|
28
|
+
if not language:
|
|
29
|
+
raise ValueError(f"Language with code {key} not found")
|
|
30
|
+
return language
|
|
31
|
+
|
|
32
|
+
def find_by_key(
|
|
33
|
+
self,
|
|
34
|
+
key: str,
|
|
35
|
+
) -> Language | None:
|
|
36
|
+
with psycopg.connect(self.database_url) as conn:
|
|
37
|
+
with conn.cursor(row_factory=class_row(Language)) as cur:
|
|
38
|
+
cur.execute(
|
|
39
|
+
"SELECT * FROM languages WHERE code = %(key)s",
|
|
40
|
+
{
|
|
41
|
+
"key": key,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
return cur.fetchone()
|
|
45
|
+
|
|
46
|
+
def get_by_id(
|
|
47
|
+
self,
|
|
48
|
+
id: int,
|
|
49
|
+
) -> Language:
|
|
50
|
+
language = self.find_by_id(id)
|
|
51
|
+
if not language:
|
|
52
|
+
raise ValueError(f"Language with id {id} not found")
|
|
53
|
+
return language
|
|
54
|
+
|
|
55
|
+
def find_by_id(
|
|
56
|
+
self,
|
|
57
|
+
id: int,
|
|
58
|
+
) -> Language | None:
|
|
59
|
+
with psycopg.connect(self.database_url) as conn:
|
|
60
|
+
with conn.cursor(row_factory=class_row(Language)) as cur:
|
|
61
|
+
cur.execute(
|
|
62
|
+
"SELECT * FROM languages WHERE code = %(id)s",
|
|
63
|
+
{
|
|
64
|
+
"id": id,
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
return cur.fetchone()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import psycopg
|
|
2
|
+
|
|
3
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PhraseRepo(IPhraseRepo):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
database_url: str,
|
|
10
|
+
):
|
|
11
|
+
self.database_url = database_url
|
|
12
|
+
|
|
13
|
+
def get_phrase(
|
|
14
|
+
self,
|
|
15
|
+
key: str,
|
|
16
|
+
language_code: str,
|
|
17
|
+
) -> str:
|
|
18
|
+
with psycopg.connect(self.database_url) as conn:
|
|
19
|
+
with conn.cursor() as cur:
|
|
20
|
+
cur.execute(
|
|
21
|
+
"SELECT text FROM phrases WHERE key = %(key)s"
|
|
22
|
+
" AND language_code = %(language_code)s",
|
|
23
|
+
{
|
|
24
|
+
"key": key,
|
|
25
|
+
"language_code": language_code,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
row = cur.fetchone()
|
|
29
|
+
return (
|
|
30
|
+
str(row[0]) if row else f"[Missing phrase: {key}-{language_code}]"
|
|
31
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .i_callback_answerer import ICallbackAnswerer
|
|
2
|
+
from .i_callback_handler import ICallbackHandler
|
|
3
|
+
from .i_callback_handler_registry import ICallbackHandlerRegistry
|
|
4
|
+
from .i_card_field_formatter import ICardFieldFormatter
|
|
5
|
+
from .i_display_width_calculator import IDisplayWidthCalculator
|
|
6
|
+
from .i_ensure_user_exists import IEnsureUserExists
|
|
7
|
+
from .i_flow_router import IFlowRouter
|
|
8
|
+
from .i_markdown_to_html_converter import IMarkdownToHtmlConverter
|
|
9
|
+
from .i_message_deleter import IMessageDeleter
|
|
10
|
+
from .i_message_handler import IMessageHandler
|
|
11
|
+
from .i_message_handler_registry import IMessageHandlerRegistry
|
|
12
|
+
from .i_message_replacer import IMessageReplacer
|
|
13
|
+
from .i_message_sender import IMessageSender
|
|
14
|
+
from .i_message_service import IMessageService
|
|
15
|
+
from .i_next_step_handler_registrar import INextStepHandlerRegistrar
|
|
16
|
+
from .i_notify_replacer import INotifyReplacer
|
|
17
|
+
from .i_remaining_time_formatter import IRemainingTimeFormatter
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ICallbackAnswerer",
|
|
21
|
+
"ICallbackHandler",
|
|
22
|
+
"ICallbackHandlerRegistry",
|
|
23
|
+
"ICardFieldFormatter",
|
|
24
|
+
"IDisplayWidthCalculator",
|
|
25
|
+
"IEnsureUserExists",
|
|
26
|
+
"IFlowRouter",
|
|
27
|
+
"IMarkdownToHtmlConverter",
|
|
28
|
+
"IMessageDeleter",
|
|
29
|
+
"IMessageHandler",
|
|
30
|
+
"IMessageHandlerRegistry",
|
|
31
|
+
"IMessageReplacer",
|
|
32
|
+
"IMessageSender",
|
|
33
|
+
"IMessageService",
|
|
34
|
+
"INextStepHandlerRegistrar",
|
|
35
|
+
"INotifyReplacer",
|
|
36
|
+
"IRemainingTimeFormatter",
|
|
37
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Protocol,
|
|
3
|
+
runtime_checkable,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from bot_framework.entities.bot_callback import BotCallback
|
|
7
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class ICallbackHandler(Protocol):
|
|
12
|
+
callback_answerer: ICallbackAnswerer
|
|
13
|
+
prefix: str
|
|
14
|
+
allowed_roles: set[str] | None
|
|
15
|
+
|
|
16
|
+
def handle(self, callback: BotCallback) -> None: ...
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Protocol,
|
|
5
|
+
runtime_checkable,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from bot_framework.entities.user import User
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class IFlowRouter(Protocol):
|
|
13
|
+
def get_name(self) -> str: ...
|
|
14
|
+
|
|
15
|
+
def start(
|
|
16
|
+
self,
|
|
17
|
+
user: User,
|
|
18
|
+
return_to: IFlowRouter | None = None,
|
|
19
|
+
) -> None: ...
|