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,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
|
|
6
|
+
from bot_framework.entities.user import User
|
|
7
|
+
from bot_framework.flow_management.entities import FlowStackEntry
|
|
8
|
+
from bot_framework.flow_management.flow_registry import FlowRegistry
|
|
9
|
+
from bot_framework.flow_management.protocols import (
|
|
10
|
+
IFlowMessageDeleter,
|
|
11
|
+
IFlowStackStorage,
|
|
12
|
+
IFlowStackValidator,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FlowStackNavigator:
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
storage: IFlowStackStorage,
|
|
22
|
+
registry: FlowRegistry,
|
|
23
|
+
validator: IFlowStackValidator,
|
|
24
|
+
message_deleter: IFlowMessageDeleter | None = None,
|
|
25
|
+
):
|
|
26
|
+
self._storage = storage
|
|
27
|
+
self._registry = registry
|
|
28
|
+
self._validator = validator
|
|
29
|
+
self._message_deleter = message_deleter
|
|
30
|
+
|
|
31
|
+
def push(self, user: User, flow_name: str) -> None:
|
|
32
|
+
self._validator.validate_push(user.id, flow_name)
|
|
33
|
+
|
|
34
|
+
stack = self._storage.get_stack(user.id)
|
|
35
|
+
entry = FlowStackEntry(flow_name=flow_name, started_at=datetime.now(UTC))
|
|
36
|
+
self._storage.push(user.id, entry)
|
|
37
|
+
|
|
38
|
+
logger.info(
|
|
39
|
+
f"Flow pushed for user {user.id}: {flow_name}. "
|
|
40
|
+
f"Stack depth: {len(stack) + 1}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def pop_and_return(self, user: User) -> None:
|
|
44
|
+
current_entry = self._storage.pop(user.id)
|
|
45
|
+
if not current_entry:
|
|
46
|
+
logger.warning(f"No flow to pop for user {user.id}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
self._clean_flow_messages(user.id, current_entry.flow_name)
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
f"Flow popped for user {user.id}: {current_entry.flow_name}. "
|
|
53
|
+
f"Duration: {datetime.now(UTC) - current_entry.started_at}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
remaining_stack = self._storage.get_stack(user.id)
|
|
57
|
+
if not remaining_stack:
|
|
58
|
+
logger.info(f"Flow stack empty for user {user.id}")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
parent_entry = remaining_stack[-1]
|
|
62
|
+
parent_flow = self._registry.get(parent_entry.flow_name)
|
|
63
|
+
if not parent_flow:
|
|
64
|
+
logger.error(
|
|
65
|
+
f"Parent flow '{parent_entry.flow_name}' not found in registry"
|
|
66
|
+
)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
logger.info(
|
|
70
|
+
f"Returning to parent flow for user {user.id}: {parent_entry.flow_name}"
|
|
71
|
+
)
|
|
72
|
+
parent_flow.start(user)
|
|
73
|
+
|
|
74
|
+
def terminate(self, user: User) -> None:
|
|
75
|
+
current_entry = self._storage.pop(user.id)
|
|
76
|
+
if not current_entry:
|
|
77
|
+
logger.warning(f"No flow to terminate for user {user.id}")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
self._clean_flow_messages(user.id, current_entry.flow_name)
|
|
81
|
+
|
|
82
|
+
logger.info(
|
|
83
|
+
f"Flow terminated for user {user.id}: {current_entry.flow_name}. "
|
|
84
|
+
f"Duration: {datetime.now(UTC) - current_entry.started_at}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def is_flow_in_stack(self, user: User, flow_name: str) -> bool:
|
|
88
|
+
stack = self._storage.get_stack(user.id)
|
|
89
|
+
return flow_name in [entry.flow_name for entry in stack]
|
|
90
|
+
|
|
91
|
+
def clear_all(self, user: User) -> None:
|
|
92
|
+
stack = self._storage.get_stack(user.id)
|
|
93
|
+
|
|
94
|
+
if self._message_deleter and stack:
|
|
95
|
+
for entry in stack:
|
|
96
|
+
self._message_deleter.delete_flow_messages(user.id, entry.flow_name)
|
|
97
|
+
|
|
98
|
+
if stack:
|
|
99
|
+
logger.info(f"Clearing flow stack for user {user.id}. Depth: {len(stack)}")
|
|
100
|
+
self._storage.clear(user.id)
|
|
101
|
+
|
|
102
|
+
def _clean_flow_messages(self, chat_id: int, flow_name: str) -> None:
|
|
103
|
+
if self._message_deleter:
|
|
104
|
+
self._message_deleter.delete_flow_messages(chat_id, flow_name)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from bot_framework.flow_management.flow_registry import FlowRegistry
|
|
6
|
+
from bot_framework.flow_management.protocols import IFlowStackStorage
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FlowStackValidator:
|
|
12
|
+
MAX_STACK_DEPTH = 5
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
storage: IFlowStackStorage,
|
|
17
|
+
registry: FlowRegistry,
|
|
18
|
+
):
|
|
19
|
+
self._storage = storage
|
|
20
|
+
self._registry = registry
|
|
21
|
+
|
|
22
|
+
def validate_push(self, user_id: int, flow_name: str) -> None:
|
|
23
|
+
stack = self._storage.get_stack(user_id)
|
|
24
|
+
|
|
25
|
+
if len(stack) >= self.MAX_STACK_DEPTH:
|
|
26
|
+
logger.error(
|
|
27
|
+
f"Flow stack depth exceeded for user {user_id}. "
|
|
28
|
+
f"Max depth: {self.MAX_STACK_DEPTH}"
|
|
29
|
+
)
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"Maximum flow stack depth ({self.MAX_STACK_DEPTH}) exceeded"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
flow_names_in_stack = [entry.flow_name for entry in stack]
|
|
35
|
+
if flow_name in flow_names_in_stack:
|
|
36
|
+
logger.error(
|
|
37
|
+
f"Circular flow dependency detected for user {user_id}: "
|
|
38
|
+
f"{flow_names_in_stack} -> {flow_name}"
|
|
39
|
+
)
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"Circular flow dependency detected: {flow_name} is already in stack"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if not self._registry.get(flow_name):
|
|
45
|
+
logger.error(f"Flow '{flow_name}' not found in registry")
|
|
46
|
+
raise ValueError(f"Flow '{flow_name}' not registered")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from bot_framework.flows.request_role_flow.factory import RequestRoleFlowFactory
|
|
2
|
+
from bot_framework.flows.request_role_flow.protocols import IRequestRoleFlowRouter
|
|
3
|
+
from bot_framework.flows.request_role_flow.request_role_flow_router import (
|
|
4
|
+
RequestRoleFlowRouter,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"IRequestRoleFlowRouter",
|
|
9
|
+
"RequestRoleFlowFactory",
|
|
10
|
+
"RequestRoleFlowRouter",
|
|
11
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from bot_framework.flows.request_role_flow.actions.role_assigner import RoleAssigner
|
|
2
|
+
from bot_framework.flows.request_role_flow.actions.role_rejection_notifier import (
|
|
3
|
+
RoleRejectionNotifier,
|
|
4
|
+
)
|
|
5
|
+
from bot_framework.flows.request_role_flow.actions.role_request_sender import (
|
|
6
|
+
RoleRequestSender,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"RoleAssigner",
|
|
11
|
+
"RoleRejectionNotifier",
|
|
12
|
+
"RoleRequestSender",
|
|
13
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
2
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
3
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RoleAssigner:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
message_sender: IMessageSender,
|
|
10
|
+
phrase_repo: IPhraseRepo,
|
|
11
|
+
role_repo: IRoleRepo,
|
|
12
|
+
) -> None:
|
|
13
|
+
self.message_sender = message_sender
|
|
14
|
+
self.phrase_repo = phrase_repo
|
|
15
|
+
self.role_repo = role_repo
|
|
16
|
+
|
|
17
|
+
def assign_and_notify(
|
|
18
|
+
self,
|
|
19
|
+
user_id: int,
|
|
20
|
+
role_id: int,
|
|
21
|
+
language_code: str,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.role_repo.assign_role(user_id=user_id, role_id=role_id)
|
|
24
|
+
|
|
25
|
+
text = self.phrase_repo.get_phrase(
|
|
26
|
+
key="request_role.approved",
|
|
27
|
+
language_code=language_code,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.message_sender.send(chat_id=user_id, text=text)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
2
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RoleRejectionNotifier:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
message_sender: IMessageSender,
|
|
9
|
+
phrase_repo: IPhraseRepo,
|
|
10
|
+
) -> None:
|
|
11
|
+
self.message_sender = message_sender
|
|
12
|
+
self.phrase_repo = phrase_repo
|
|
13
|
+
|
|
14
|
+
def notify(
|
|
15
|
+
self,
|
|
16
|
+
user_id: int,
|
|
17
|
+
language_code: str,
|
|
18
|
+
) -> None:
|
|
19
|
+
text = self.phrase_repo.get_phrase(
|
|
20
|
+
key="request_role.rejected",
|
|
21
|
+
language_code=language_code,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
self.message_sender.send(chat_id=user_id, text=text)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from bot_framework.entities.button import Button
|
|
2
|
+
from bot_framework.entities.keyboard import Keyboard
|
|
3
|
+
from bot_framework.entities.role import Role
|
|
4
|
+
from bot_framework.entities.user import User
|
|
5
|
+
from bot_framework.flows.request_role_flow.exceptions import NoSupervisorsFoundError
|
|
6
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
7
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
8
|
+
from bot_framework.role_management.repos.protocols.i_user_repo import IUserRepo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RoleRequestSender:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
message_sender: IMessageSender,
|
|
15
|
+
phrase_repo: IPhraseRepo,
|
|
16
|
+
user_repo: IUserRepo,
|
|
17
|
+
approve_handler_prefix: str,
|
|
18
|
+
reject_handler_prefix: str,
|
|
19
|
+
) -> None:
|
|
20
|
+
self.message_sender = message_sender
|
|
21
|
+
self.phrase_repo = phrase_repo
|
|
22
|
+
self.user_repo = user_repo
|
|
23
|
+
self.approve_handler_prefix = approve_handler_prefix
|
|
24
|
+
self.reject_handler_prefix = reject_handler_prefix
|
|
25
|
+
|
|
26
|
+
def send_to_supervisors(
|
|
27
|
+
self,
|
|
28
|
+
requester: User,
|
|
29
|
+
role: Role,
|
|
30
|
+
) -> None:
|
|
31
|
+
supervisors = self.user_repo.get_by_role_name(role_name="supervisors")
|
|
32
|
+
if not supervisors:
|
|
33
|
+
raise NoSupervisorsFoundError()
|
|
34
|
+
|
|
35
|
+
for supervisor in supervisors:
|
|
36
|
+
language_code = supervisor.language_code
|
|
37
|
+
|
|
38
|
+
title = self.phrase_repo.get_phrase(
|
|
39
|
+
key="request_role.supervisor.title",
|
|
40
|
+
language_code=language_code,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
requester_name = requester.username or requester.first_name or str(requester.id)
|
|
44
|
+
text = f"{title}\n\nUser: {requester_name}\nRole: {role.name}"
|
|
45
|
+
|
|
46
|
+
approve_text = self.phrase_repo.get_phrase(
|
|
47
|
+
key="request_role.button.approve",
|
|
48
|
+
language_code=language_code,
|
|
49
|
+
)
|
|
50
|
+
reject_text = self.phrase_repo.get_phrase(
|
|
51
|
+
key="request_role.button.reject",
|
|
52
|
+
language_code=language_code,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
keyboard = Keyboard(
|
|
56
|
+
rows=[
|
|
57
|
+
[
|
|
58
|
+
Button(
|
|
59
|
+
text=approve_text,
|
|
60
|
+
callback_data=f"{self.approve_handler_prefix}:{role.id}:{requester.id}",
|
|
61
|
+
),
|
|
62
|
+
Button(
|
|
63
|
+
text=reject_text,
|
|
64
|
+
callback_data=f"{self.reject_handler_prefix}:{role.id}:{requester.id}",
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.message_sender.send(
|
|
71
|
+
chat_id=supervisor.id,
|
|
72
|
+
text=text,
|
|
73
|
+
keyboard=keyboard,
|
|
74
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from bot_framework.flows.request_role_flow.actions import (
|
|
2
|
+
RoleAssigner,
|
|
3
|
+
RoleRejectionNotifier,
|
|
4
|
+
RoleRequestSender,
|
|
5
|
+
)
|
|
6
|
+
from bot_framework.flows.request_role_flow.handlers import (
|
|
7
|
+
ApproveRoleHandler,
|
|
8
|
+
RejectRoleHandler,
|
|
9
|
+
RequestRoleCommandHandler,
|
|
10
|
+
RoleSelectionHandler,
|
|
11
|
+
ShowRolesHandler,
|
|
12
|
+
)
|
|
13
|
+
from bot_framework.flows.request_role_flow.presenters import RoleListPresenter
|
|
14
|
+
from bot_framework.flows.request_role_flow.protocols import (
|
|
15
|
+
IRequestRoleFlowRouter,
|
|
16
|
+
IRequestRoleFlowStateStorage,
|
|
17
|
+
)
|
|
18
|
+
from bot_framework.flows.request_role_flow.request_role_flow_router import (
|
|
19
|
+
RequestRoleFlowRouter,
|
|
20
|
+
)
|
|
21
|
+
from bot_framework.language_management.repos.protocols.i_phrase_repo import IPhraseRepo
|
|
22
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
23
|
+
from bot_framework.protocols.i_callback_handler_registry import (
|
|
24
|
+
ICallbackHandlerRegistry,
|
|
25
|
+
)
|
|
26
|
+
from bot_framework.protocols.i_message_handler_registry import IMessageHandlerRegistry
|
|
27
|
+
from bot_framework.protocols.i_message_sender import IMessageSender
|
|
28
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
29
|
+
from bot_framework.role_management.repos.protocols.i_user_repo import IUserRepo
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RequestRoleFlowFactory:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
callback_answerer: ICallbackAnswerer,
|
|
36
|
+
message_sender: IMessageSender,
|
|
37
|
+
phrase_repo: IPhraseRepo,
|
|
38
|
+
role_repo: IRoleRepo,
|
|
39
|
+
user_repo: IUserRepo,
|
|
40
|
+
state_storage: IRequestRoleFlowStateStorage,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.callback_answerer = callback_answerer
|
|
43
|
+
self.message_sender = message_sender
|
|
44
|
+
self.phrase_repo = phrase_repo
|
|
45
|
+
self.role_repo = role_repo
|
|
46
|
+
self.user_repo = user_repo
|
|
47
|
+
self.state_storage = state_storage
|
|
48
|
+
|
|
49
|
+
self._show_roles_handler: ShowRolesHandler | None = None
|
|
50
|
+
self._role_selection_handler: RoleSelectionHandler | None = None
|
|
51
|
+
self._approve_handler: ApproveRoleHandler | None = None
|
|
52
|
+
self._reject_handler: RejectRoleHandler | None = None
|
|
53
|
+
|
|
54
|
+
def _get_approve_handler(self) -> ApproveRoleHandler:
|
|
55
|
+
if self._approve_handler is None:
|
|
56
|
+
self._approve_handler = ApproveRoleHandler(
|
|
57
|
+
callback_answerer=self.callback_answerer,
|
|
58
|
+
role_repo=self.role_repo,
|
|
59
|
+
user_repo=self.user_repo,
|
|
60
|
+
role_assigner=RoleAssigner(
|
|
61
|
+
message_sender=self.message_sender,
|
|
62
|
+
phrase_repo=self.phrase_repo,
|
|
63
|
+
role_repo=self.role_repo,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
return self._approve_handler
|
|
67
|
+
|
|
68
|
+
def _get_reject_handler(self) -> RejectRoleHandler:
|
|
69
|
+
if self._reject_handler is None:
|
|
70
|
+
self._reject_handler = RejectRoleHandler(
|
|
71
|
+
callback_answerer=self.callback_answerer,
|
|
72
|
+
role_repo=self.role_repo,
|
|
73
|
+
user_repo=self.user_repo,
|
|
74
|
+
role_rejection_notifier=RoleRejectionNotifier(
|
|
75
|
+
message_sender=self.message_sender,
|
|
76
|
+
phrase_repo=self.phrase_repo,
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
return self._reject_handler
|
|
80
|
+
|
|
81
|
+
def _get_role_selection_handler(self) -> RoleSelectionHandler:
|
|
82
|
+
if self._role_selection_handler is None:
|
|
83
|
+
approve_handler = self._get_approve_handler()
|
|
84
|
+
reject_handler = self._get_reject_handler()
|
|
85
|
+
|
|
86
|
+
self._role_selection_handler = RoleSelectionHandler(
|
|
87
|
+
callback_answerer=self.callback_answerer,
|
|
88
|
+
message_sender=self.message_sender,
|
|
89
|
+
phrase_repo=self.phrase_repo,
|
|
90
|
+
role_repo=self.role_repo,
|
|
91
|
+
user_repo=self.user_repo,
|
|
92
|
+
state_storage=self.state_storage,
|
|
93
|
+
role_request_sender=RoleRequestSender(
|
|
94
|
+
message_sender=self.message_sender,
|
|
95
|
+
phrase_repo=self.phrase_repo,
|
|
96
|
+
user_repo=self.user_repo,
|
|
97
|
+
approve_handler_prefix=approve_handler.prefix,
|
|
98
|
+
reject_handler_prefix=reject_handler.prefix,
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
return self._role_selection_handler
|
|
102
|
+
|
|
103
|
+
def _get_show_roles_handler(self) -> ShowRolesHandler:
|
|
104
|
+
if self._show_roles_handler is None:
|
|
105
|
+
role_selection_handler = self._get_role_selection_handler()
|
|
106
|
+
|
|
107
|
+
self._show_roles_handler = ShowRolesHandler(
|
|
108
|
+
callback_answerer=self.callback_answerer,
|
|
109
|
+
role_list_presenter=RoleListPresenter(
|
|
110
|
+
message_sender=self.message_sender,
|
|
111
|
+
phrase_repo=self.phrase_repo,
|
|
112
|
+
role_repo=self.role_repo,
|
|
113
|
+
role_selection_handler_prefix=role_selection_handler.prefix,
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
return self._show_roles_handler
|
|
117
|
+
|
|
118
|
+
def create_router(self) -> IRequestRoleFlowRouter:
|
|
119
|
+
role_selection_handler = self._get_role_selection_handler()
|
|
120
|
+
|
|
121
|
+
role_list_presenter = RoleListPresenter(
|
|
122
|
+
message_sender=self.message_sender,
|
|
123
|
+
phrase_repo=self.phrase_repo,
|
|
124
|
+
role_repo=self.role_repo,
|
|
125
|
+
role_selection_handler_prefix=role_selection_handler.prefix,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return RequestRoleFlowRouter(role_list_presenter=role_list_presenter)
|
|
129
|
+
|
|
130
|
+
def register_handlers(
|
|
131
|
+
self,
|
|
132
|
+
callback_registry: ICallbackHandlerRegistry,
|
|
133
|
+
message_registry: IMessageHandlerRegistry,
|
|
134
|
+
) -> None:
|
|
135
|
+
callback_registry.register(self._get_show_roles_handler())
|
|
136
|
+
callback_registry.register(self._get_role_selection_handler())
|
|
137
|
+
callback_registry.register(self._get_approve_handler())
|
|
138
|
+
callback_registry.register(self._get_reject_handler())
|
|
139
|
+
|
|
140
|
+
router = self.create_router()
|
|
141
|
+
command_handler = RequestRoleCommandHandler(request_role_flow_router=router)
|
|
142
|
+
message_registry.register(
|
|
143
|
+
handler=command_handler,
|
|
144
|
+
commands=["request_role"],
|
|
145
|
+
content_types=["text"],
|
|
146
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from bot_framework.flows.request_role_flow.handlers.approve_role_handler import (
|
|
2
|
+
ApproveRoleHandler,
|
|
3
|
+
)
|
|
4
|
+
from bot_framework.flows.request_role_flow.handlers.reject_role_handler import (
|
|
5
|
+
RejectRoleHandler,
|
|
6
|
+
)
|
|
7
|
+
from bot_framework.flows.request_role_flow.handlers.request_role_command_handler import (
|
|
8
|
+
RequestRoleCommandHandler,
|
|
9
|
+
)
|
|
10
|
+
from bot_framework.flows.request_role_flow.handlers.role_selection_handler import (
|
|
11
|
+
RoleSelectionHandler,
|
|
12
|
+
)
|
|
13
|
+
from bot_framework.flows.request_role_flow.handlers.show_roles_handler import (
|
|
14
|
+
ShowRolesHandler,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ApproveRoleHandler",
|
|
19
|
+
"RejectRoleHandler",
|
|
20
|
+
"RequestRoleCommandHandler",
|
|
21
|
+
"RoleSelectionHandler",
|
|
22
|
+
"ShowRolesHandler",
|
|
23
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
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 IRoleAssigner
|
|
5
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
6
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
7
|
+
from bot_framework.role_management.repos.protocols.i_user_repo import IUserRepo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApproveRoleHandler:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
callback_answerer: ICallbackAnswerer,
|
|
14
|
+
role_repo: IRoleRepo,
|
|
15
|
+
user_repo: IUserRepo,
|
|
16
|
+
role_assigner: IRoleAssigner,
|
|
17
|
+
) -> None:
|
|
18
|
+
self.callback_answerer = callback_answerer
|
|
19
|
+
self.role_repo = role_repo
|
|
20
|
+
self.user_repo = user_repo
|
|
21
|
+
self.role_assigner = role_assigner
|
|
22
|
+
self.prefix = uuid4().hex
|
|
23
|
+
self.allowed_roles: set[str] | None = None
|
|
24
|
+
|
|
25
|
+
def handle(self, callback: BotCallback) -> None:
|
|
26
|
+
self.callback_answerer.answer(callback_query_id=callback.id)
|
|
27
|
+
|
|
28
|
+
if not callback.data:
|
|
29
|
+
raise ValueError("callback.data is required but was None")
|
|
30
|
+
|
|
31
|
+
parts = callback.data.split(":")
|
|
32
|
+
if len(parts) < 3:
|
|
33
|
+
raise ValueError(f"Invalid callback data format: {callback.data}")
|
|
34
|
+
|
|
35
|
+
role_id = int(parts[1])
|
|
36
|
+
user_id = int(parts[2])
|
|
37
|
+
|
|
38
|
+
user = self.user_repo.find_by_id(id=user_id)
|
|
39
|
+
if not user:
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
language_code = user.language_code
|
|
43
|
+
|
|
44
|
+
self.role_assigner.assign_and_notify(
|
|
45
|
+
user_id=user_id,
|
|
46
|
+
role_id=role_id,
|
|
47
|
+
language_code=language_code,
|
|
48
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
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 IRoleRejectionNotifier
|
|
5
|
+
from bot_framework.protocols.i_callback_answerer import ICallbackAnswerer
|
|
6
|
+
from bot_framework.role_management.repos.protocols.i_role_repo import IRoleRepo
|
|
7
|
+
from bot_framework.role_management.repos.protocols.i_user_repo import IUserRepo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RejectRoleHandler:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
callback_answerer: ICallbackAnswerer,
|
|
14
|
+
role_repo: IRoleRepo,
|
|
15
|
+
user_repo: IUserRepo,
|
|
16
|
+
role_rejection_notifier: IRoleRejectionNotifier,
|
|
17
|
+
) -> None:
|
|
18
|
+
self.callback_answerer = callback_answerer
|
|
19
|
+
self.role_repo = role_repo
|
|
20
|
+
self.user_repo = user_repo
|
|
21
|
+
self.role_rejection_notifier = role_rejection_notifier
|
|
22
|
+
self.prefix = uuid4().hex
|
|
23
|
+
self.allowed_roles: set[str] | None = None
|
|
24
|
+
|
|
25
|
+
def handle(self, callback: BotCallback) -> None:
|
|
26
|
+
self.callback_answerer.answer(callback_query_id=callback.id)
|
|
27
|
+
|
|
28
|
+
if not callback.data:
|
|
29
|
+
raise ValueError("callback.data is required but was None")
|
|
30
|
+
|
|
31
|
+
parts = callback.data.split(":")
|
|
32
|
+
if len(parts) < 3:
|
|
33
|
+
raise ValueError(f"Invalid callback data format: {callback.data}")
|
|
34
|
+
|
|
35
|
+
user_id = int(parts[2])
|
|
36
|
+
|
|
37
|
+
user = self.user_repo.find_by_id(id=user_id)
|
|
38
|
+
if not user:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
language_code = user.language_code
|
|
42
|
+
|
|
43
|
+
self.role_rejection_notifier.notify(
|
|
44
|
+
user_id=user_id,
|
|
45
|
+
language_code=language_code,
|
|
46
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from bot_framework.entities.bot_message import BotMessage
|
|
2
|
+
from bot_framework.flows.request_role_flow.protocols import (
|
|
3
|
+
IRequestRoleFlowRouter,
|
|
4
|
+
)
|
|
5
|
+
from bot_framework.protocols.i_message_handler import IMessageHandler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RequestRoleCommandHandler(IMessageHandler):
|
|
9
|
+
allowed_roles: set[str] | None = None
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
request_role_flow_router: IRequestRoleFlowRouter,
|
|
14
|
+
) -> None:
|
|
15
|
+
self.request_role_flow_router = request_role_flow_router
|
|
16
|
+
|
|
17
|
+
def handle(self, message: BotMessage) -> None:
|
|
18
|
+
if not message.from_user:
|
|
19
|
+
raise ValueError("message.from_user is required but was None")
|
|
20
|
+
|
|
21
|
+
self.request_role_flow_router.start(
|
|
22
|
+
user=message.from_user,
|
|
23
|
+
chat_id=message.chat_id,
|
|
24
|
+
)
|