sender — UI message delivery and synchronization
Layer for managing display and synchronization of messages in Telegram.
ViewSender
ViewSender
Stateless service for persistent UI orchestration.
The ViewSender implements a "Dual-Message" UI pattern, maintaining two distinct messages per session:
- Menu: A high-level navigation block for switching features.
- Content: A dynamic block representing the current feature's state.
The synchronization algorithm performs atomic updates to these blocks,
handling message deletion (e.g., of trigger commands) and state
persistence via an external SenderManager.
Note
alert_text in UnifiedViewDTO is not handled by this service as it
requires a CallbackQuery context. It must be answered manually in
the handler prior to calling send().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bot
|
Bot
|
Instance of the |
required |
manager
|
SenderManager
|
A |
required |
Source code in src/codex_bot/sender/view_sender.py
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 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 | |
Functions
send(view)
async
Synchronize the current UI state with Telegram.
Analyzes the UnifiedViewDTO to perform required deletions, updates,
and message deliveries. This method is thread-safe and stateless,
operating strictly on the provided DTO.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
UnifiedViewDTO
|
The structured UI definition from the orchestrator. Must contain valid routing metadata (chat_id, session_key). |
required |
Side Effects
- Deletes the trigger message if
trigger_message_idis present. - Updates persistent UI coordinates in the backend storage.
- Modifies existing messages in the target Telegram chat.
Source code in src/codex_bot/sender/view_sender.py
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 | |
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 | |
Storage Implementations
Ready-to-use implementations for storing UI coordinates.
MemorySenderStorage
MemorySenderStorage
Bases: SenderStateStorageProtocol
In-memory storage for UI coordinates.
Ideal for lightweight bots or development environments where Redis is not available. Data is stored in a local dictionary and persists only for the duration of the bot's process life.
Example
if not settings.use_redis:
storage = MemorySenderStorage()
manager = SenderManager(storage=storage)
view_sender = ViewSender(bot=bot, manager=manager)
Source code in src/codex_bot/sender/memory_storage.py
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 | |
Functions
__init__()
Initializes the local storage dictionary.
Source code in src/codex_bot/sender/memory_storage.py
34 35 36 | |
clear_sender_state(key)
async
Removes coordinates for the specified key from memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Storage key to clear. |
required |
Source code in src/codex_bot/sender/memory_storage.py
63 64 65 66 67 68 69 | |
get_sender_state(key)
async
Retrieves coordinates from memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Storage key (e.g., "sender:user:123"). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
A copy of the coordinate dictionary. Returns an empty dict if not found. |
Source code in src/codex_bot/sender/memory_storage.py
38 39 40 41 42 43 44 45 46 47 48 49 | |
save_sender_state(key, data)
async
Saves or updates coordinates in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Storage key. |
required |
data
|
dict[str, int]
|
Dictionary with updated coordinates (e.g., {"menu_msg_id": 42}). |
required |
Source code in src/codex_bot/sender/memory_storage.py
51 52 53 54 55 56 57 58 59 60 61 | |
RedisSenderStorage
RedisSenderStorage
Bases: SenderStateStorageProtocol
Redis storage implementation using HASHES.
Coordinates are stored as fields within a Redis Hash, allowing partial updates (e.g. only updating menu_msg_id) without rewriting the entire record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
redis
|
Any
|
An initialized Redis client (redis.asyncio). |
required |
ttl
|
int
|
Time-to-live for coordinate keys in seconds (default: 7 days). |
604800
|
Source code in src/codex_bot/sender/redis_storage.py
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 | |
Functions
clear_sender_state(key)
async
Removes the entire hash key from Redis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Prepared Redis key. |
required |
Source code in src/codex_bot/sender/redis_storage.py
63 64 65 66 67 68 69 | |
get_sender_state(key)
async
Retrieves coordinates using HGETALL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Prepared Redis key from SenderManager. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dictionary with coordinates. Keys and values are cast to correct types. |
Source code in src/codex_bot/sender/redis_storage.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
save_sender_state(key, data)
async
Saves or updates coordinates using HSET.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Prepared Redis key. |
required |
data
|
dict[str, int]
|
Fields to update (e.g. {"menu_msg_id": 123}). |
required |
Source code in src/codex_bot/sender/redis_storage.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
Infrastructure Utils
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 | |