sender — UI message delivery and synchronization
ViewSender
ViewSender
STATELESS service for sending and updating UI messages.
Works with two persistent bot messages in the chat:
- Menu — navigation block (section buttons).
- Content — information block (current feature data).
The send() algorithm:
- Deletes the
trigger_message(e.g., the/startcommand). - If
clean_history=True— deletes old Menu and Content. - Edits existing messages (or creates new ones).
- Saves current
message_idvia SenderManager.
.. note::
alert_text from UnifiedViewDTO is intentionally not processed by ViewSender —
alert requires access to CallbackQuery.answer(), which ViewSender does not have.
Call await call.answer(view.alert_text) in the handler before await sender.send(view).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bot
|
Bot
|
Aiogram Bot instance. |
required |
manager
|
SenderManager
|
SenderManager for storing UI coordinates. |
required |
Example
sender = ViewSender(bot=bot, manager=sender_manager)
# In a handler:
if view.alert_text:
await call.answer(view.alert_text, show_alert=True)
await sender.send(view)
Source code in src/codex_bot/sender/view_sender.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
Functions
send(view)
async
Main UI synchronization method.
All context variables are local, without writing to self. Safe for concurrent calls from different users.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
UnifiedViewDTO
|
|
required |
Source code in src/codex_bot/sender/view_sender.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
SenderManager
SenderManager
Manager for UI coordinate storage.
Abstracts work with coordinates (menu_msg_id, content_msg_id) on top of SenderStateStorageProtocol. ViewSender works only through it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
storage
|
SenderStateStorageProtocol
|
Implementation of SenderStateStorageProtocol (Redis, in-memory, etc.). |
required |
Example
manager = SenderManager(storage=redis_sender_storage)
coords = await manager.get_coords(session_key=123456)
# {"menu_msg_id": 10, "content_msg_id": 11}
await manager.update_coords(123456, {"menu_msg_id": 20})
await manager.clear_coords(123456)
Source code in src/codex_bot/sender/sender_manager.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
Functions
clear_coords(session_key, is_channel=False)
async
Deletes all UI coordinates for a session (state reset).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_key
|
int | str
|
user_id or session_id. |
required |
is_channel
|
bool
|
Key type. |
False
|
Source code in src/codex_bot/sender/sender_manager.py
70 71 72 73 74 75 76 77 78 79 | |
get_coords(session_key, is_channel=False)
async
Returns saved UI coordinates for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_key
|
int | str
|
user_id (int) or session_id (str) for a channel. |
required |
is_channel
|
bool
|
True — use channel key, False — use user key. |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dictionary {"menu_msg_id": int, "content_msg_id": int}. |
dict[str, int]
|
Empty dictionary if no data exists. |
Source code in src/codex_bot/sender/sender_manager.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 | |
update_coords(session_key, coords, is_channel=False)
async
Partially updates UI coordinates for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_key
|
int | str
|
user_id or session_id. |
required |
coords
|
dict[str, int]
|
Fields to update (partial update). |
required |
is_channel
|
bool
|
Key type. |
False
|
Source code in src/codex_bot/sender/sender_manager.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
SenderKeys
SenderKeys
Key factory for SenderManager.
Standardizes key naming in the UI coordinate storage. Use in your implementation of SenderStateStorageProtocol.
Example
key = SenderKeys.user(user_id=123456789)
# "sender:user:123456789"
key = SenderKeys.channel(session_id="booking_feed_1")
# "sender:channel:booking_feed_1"
Source code in src/codex_bot/sender/sender_keys.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
Functions
channel(session_id)
staticmethod
Key for storing UI coordinates of a channel or group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str
|
Unique identifier of the channel session. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Key string like |
Source code in src/codex_bot/sender/sender_keys.py
39 40 41 42 43 44 45 46 47 48 49 50 | |
user(user_id)
staticmethod
Key for storing UI coordinates of private correspondence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
int | str
|
Telegram ID of the user. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Key string like |
Source code in src/codex_bot/sender/sender_keys.py
26 27 28 29 30 31 32 33 34 35 36 37 | |
Protocols
SenderStateStorageProtocol
Bases: Protocol
Contract for UI coordinate storage (Menu and Content message IDs).
Implement this protocol in the project via Redis, PostgreSQL, or in-memory — ViewSender and SenderManager do not know about the specific storage.
Example
class RedisSenderStorage:
def __init__(self, redis: Redis): ...
async def get_sender_state(self, key: str) -> dict[str, int]:
data = await redis.hgetall(f"sender:{key}")
return {k: int(v) for k, v in data.items()}
async def save_sender_state(self, key: str, data: dict[str, int]) -> None:
await redis.hset(f"sender:{key}", mapping={k: str(v) for k, v in data.items()})
async def clear_sender_state(self, key: str) -> None:
await redis.delete(f"sender:{key}")
Source code in src/codex_bot/sender/protocols.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
Functions
clear_sender_state(key)
async
Deletes all UI coordinates for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Session key. |
required |
Source code in src/codex_bot/sender/protocols.py
60 61 62 63 64 65 66 67 | |
get_sender_state(key)
async
Returns UI coordinates (message IDs) for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Session key (e.g., user_id string or channel session_id). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dictionary like {"menu_msg_id": 123, "content_msg_id": 124}. |
dict[str, int]
|
Empty dictionary if no data exists. |
Source code in src/codex_bot/sender/protocols.py
37 38 39 40 41 42 43 44 45 46 47 48 | |
save_sender_state(key, data)
async
Saves (partially updates) UI coordinates for a session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Session key. |
required |
data
|
dict[str, int]
|
Fields to update, e.g., {"menu_msg_id": 123}. |
required |
Source code in src/codex_bot/sender/protocols.py
50 51 52 53 54 55 56 57 58 | |