TgrEzLi 0.1.2__tar.gz → 0.4.0__tar.gz

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.

Potentially problematic release.


This version of TgrEzLi might be problematic. Click here for more details.

tgrezli-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.3
2
+ Name: TgrEzLi
3
+ Version: 0.4.0
4
+ Summary: Telegram bot library with sync-style API, handlers for messages/commands/callbacks, and optional embedded API server.
5
+ Author: eaannist
6
+ Author-email: eaannist <eaannist@gmail.com>
7
+ Requires-Dist: ezlog-py>=1.0.5
8
+ Requires-Dist: python-telegram-bot>=21.0
9
+ Requires-Dist: pycypherlib
10
+ Requires-Dist: requests>=2.28.0
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+
14
+ # TgrEzLi
15
+
16
+ Telegram bot library with a **sync-style API**, handlers for messages, commands, and callbacks, and an optional **embedded HTTP server** for custom POST endpoints. Built on [python-telegram-bot](https://pypi.org/project/python-telegram-bot/) and [ezlog](https://pypi.org/project/ezlog-py/) for logging.
17
+
18
+ - **Sync-style usage**: register handlers with decorators, use `TgMsg` / `TgCmd` / `TgCb` / `TgArgs` in handlers (thread-local context).
19
+ - **Multiple chats**: connect with a `{chat_name: chat_id}` dict; restrict handlers per chat.
20
+ - **Optional API server**: expose custom POST routes; call them with the `TReq` client.
21
+ - **Credential encryption**: store token and chats in an encrypted file with **PyCypherLib** (included by default).
22
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install TgrEzLi
29
+ ```
30
+
31
+ Dependencies: `ezlog-py`, `python-telegram-bot`, **PyCypherLib**, `requests`.
32
+
33
+ ---
34
+
35
+ ## Quick start
36
+
37
+ **After `connect()`** the bot starts polling in a **background thread**, so **`send_msg()` and handlers work immediately** (no need to call `run()`). Use **`run()`** only to **block the main thread** until you press Ctrl+C or call `stop()` (e.g. in a script so the process does not exit).
38
+
39
+ ```python
40
+ from TgrEzLi import TEL, TgMsg, TgCmd, TgCb, TgArgs, TReq
41
+
42
+ bot = TEL()
43
+
44
+ # 1) Connect: starts polling in background. send_msg() works right away.
45
+ TOKEN = "123456789:ABCDEFGHIJKLMNO"
46
+ CHAT_DICT = {"chat1": "123456789", "chat2": "789456123"}
47
+ bot.connect(TOKEN, CHAT_DICT)
48
+
49
+ # You can send without calling run() (e.g. from REPL)
50
+ bot.send_msg("Ciao a tutti")
51
+
52
+ # 2) Register handlers (before or after connect)
53
+ @bot.on_message()
54
+ def on_message():
55
+ bot.send_msg(f"Hi {TgMsg.userName}! You wrote: {TgMsg.text}")
56
+
57
+ @bot.on_command("start")
58
+ def on_start():
59
+ bot.send_msg(f"Welcome {TgCmd.userName}!")
60
+ if TgCmd.args:
61
+ bot.send_msg(f"Args: {TgCmd.args}")
62
+
63
+ # 3) Optional: block main thread until Ctrl+C (so the script does not exit)
64
+ bot.run()
65
+ ```
66
+
67
+ From the **REPL**: after `connect()` you can call `bot.send_msg("...")` and it is sent; handlers are active. If you **redefine** a handler (same function name), the new one **replaces** the previous one (no duplicate execution).
68
+
69
+ ---
70
+
71
+ ## Configuration
72
+
73
+ Create the bot with optional config:
74
+
75
+ ```python
76
+ from TgrEzLi import TEL
77
+ from TgrEzLi.types import TgrezliConfig
78
+
79
+ config = TgrezliConfig(
80
+ save_log=True,
81
+ log_file="TgrEzLi.log",
82
+ api_host="localhost",
83
+ api_port=9999,
84
+ )
85
+ bot = TEL(config)
86
+ ```
87
+
88
+ Or mutate after creation:
89
+
90
+ - **`bot.set_save_log(flag)`** – enable/disable writing log lines to the file (console always uses ezlog).
91
+ - **`bot.set_host(host)`** – API server host (used when the first `on_api_req` is registered).
92
+ - **`bot.set_port(port)`** – API server port.
93
+
94
+ Defaults: `save_log=True`, `log_file="TgrEzLi.log"`, `api_host="localhost"`, `api_port=9999`.
95
+
96
+ ---
97
+
98
+ ## Connecting and running
99
+
100
+ - **`connect(token, chat_dict)`** – Prepares the bot and **starts polling in a background thread**. `send_msg()` and handlers work immediately (e.g. from REPL you can send without calling `run()`).
101
+ - **`run()`** – **Optional.** Blocks the main thread until `stop()` (e.g. Ctrl+C). Use in scripts so the process does not exit. If API routes are registered, starts the API server when you call `run()`.
102
+ - **`stop()`** – Stops polling and API server; if `run()` was waiting, it returns. Safe to call from a signal handler (Ctrl+C) or from another thread.
103
+
104
+ **Direct:**
105
+
106
+ ```python
107
+ bot.connect(TOKEN, {"chat1": "123456789", "chat2": "789456123"})
108
+ bot.send_msg("Hello!") # works immediately
109
+ # ... register handlers ...
110
+ bot.run() # optional: block until Ctrl+C
111
+ ```
112
+
113
+ **Encrypted storage (PyCypherLib):**
114
+
115
+ ```python
116
+ # First time: save token and chats with a password (then connects)
117
+ bot.signup(TOKEN, CHAT_DICT, "your_password")
118
+ bot.run()
119
+
120
+ # Later: load and connect
121
+ bot.login("your_password")
122
+ bot.send_msg("Hi from login")
123
+ bot.run()
124
+ ```
125
+
126
+ Data is stored in `tgrdata.cy` by default (PyCypherLib).
127
+
128
+ ---
129
+
130
+ ## Sending content
131
+
132
+ All send methods accept an optional **`chat`**: `None` (default/first chat), a **string** (one chat name), or a **list/set** of names.
133
+
134
+ | Method | Description |
135
+ |--------|--------------|
136
+ | **`send_msg(text, chat=None)`** | Text message |
137
+ | **`reply_to_msg(text, msg_id, chat=None)`** | Reply to a message |
138
+ | **`send_img(photo_path, caption=None, chat=None)`** | Photo |
139
+ | **`send_file(file_path, caption=None, chat=None)`** | Document |
140
+ | **`send_position(latitude, longitude, chat=None)`** | Location |
141
+ | **`send_buttons(text, buttons, chat=None)`** | Message with inline keyboard (see below) |
142
+ | **`send_log(limit=None, chat=None)`** | Last `limit` lines of log file (or full) |
143
+ | **`send_info(chat=None)`** | Bot info (chats, handlers, API server) |
144
+ | **`send_registered_handlers(chat=None)`** | List of registered handlers (debug) |
145
+
146
+ **Inline buttons:** `buttons` is a list of rows; each row is a list of `{"text": "...", "value": "..."}` (value = callback_data).
147
+
148
+ ```python
149
+ bot.send_buttons("Choose:", [
150
+ [{"text": "Red", "value": "red"}, {"text": "Blue", "value": "blue"}],
151
+ [{"text": "Cancel", "value": "cancel"}],
152
+ ])
153
+ ```
154
+
155
+ CamelCase aliases exist: `sendMsg`, `replyToMsg`, `sendImg`, `sendFile`, `sendPosition`, `sendButtons`, `sendLog`, `sendInfo`, `sendRegisteredHandlers`.
156
+
157
+ ---
158
+
159
+ ## Handlers
160
+
161
+ **Replace-by-name:** If you register a handler whose function has the same **name** as an existing one (same type: message, command, or callback), the new handler **replaces** the old one. So in the REPL you can redefine `handle_message` and only the latest version will run.
162
+
163
+ Handlers run in a **background thread**; inside them use the global proxies **`TgMsg`**, **`TgCmd`**, **`TgCb`**, **`TgArgs`** to access the current payload. **`chat`** filters which chats trigger the handler: `None` = default chat, string = one chat, list/set = multiple chats.
164
+
165
+ ### Message handler
166
+
167
+ ```python
168
+ @bot.on_message() # default chat
169
+ @bot.on_message("chat1") # only chat1
170
+ @bot.on_message(["chat1", "chat2"])
171
+
172
+ def on_message():
173
+ # TgMsg: .text, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
174
+ bot.send_msg(f"Echo: {TgMsg.text}")
175
+ ```
176
+
177
+ ### Command handler
178
+
179
+ ```python
180
+ @bot.on_command("start")
181
+ @bot.on_command("help", "chat1")
182
+
183
+ def on_start():
184
+ # TgCmd: .command, .args, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
185
+ bot.send_msg(f"Welcome {TgCmd.userName}!")
186
+ if TgCmd.args:
187
+ bot.send_msg(f"You said: {TgCmd.args}")
188
+ ```
189
+
190
+ ### Callback handler (inline buttons)
191
+
192
+ ```python
193
+ @bot.on_callback("chat1")
194
+
195
+ def on_callback():
196
+ # TgCb: .text, .value, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
197
+ if TgCb.value == "red":
198
+ bot.send_msg(f"You pressed {TgCb.text} -> {TgCb.value}")
199
+ ```
200
+
201
+ ### API request handler
202
+
203
+ Register a POST route; the embedded server starts automatically when the first route is registered.
204
+
205
+ ```python
206
+ @bot.on_api_req("/action", args=["chat", "msg"])
207
+ def action():
208
+ # TgArgs.get(key, default)
209
+ chat_id = TgArgs.get("chat")
210
+ msg = TgArgs.get("msg")
211
+ bot.send_msg(msg, chat=chat_id)
212
+ ```
213
+
214
+ **TReq** – client to call these endpoints:
215
+
216
+ ```python
217
+ from TgrEzLi import TReq
218
+
219
+ TReq("/action").host("127.0.0.1").port(9999).arg("chat", "chat1").arg("msg", "Hello!").send()
220
+ # or
221
+ TReq("/action").body({"chat": "chat1", "msg": "Hello!"}).send()
222
+ ```
223
+
224
+ `.host()`, `.port()`, `.timeout(seconds)` are optional (defaults: localhost, 9999, 30s). `.send()` returns a `requests.Response`; on failure raises **`TgrezliRequestError`**.
225
+
226
+ CamelCase decorator aliases: `onMessage`, `onCommand`, `onCallback`, `onApiReq`.
227
+
228
+ ---
229
+
230
+ ## Data interfaces (handler context)
231
+
232
+ Use only **inside** the corresponding handler; otherwise `TgMsg` / `TgCmd` / `TgCb` / `TgArgs` raise if accessed.
233
+
234
+ | Proxy | Handler | Main fields |
235
+ |-------|---------|-------------|
236
+ | **TgMsg** | `on_message` | `text`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
237
+ | **TgCmd** | `on_command` | `command`, `args`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
238
+ | **TgCb** | `on_callback` | `text`, `value`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
239
+ | **TgArgs** | `on_api_req` | `TgArgs.get(key, default)` for POST body |
240
+
241
+ All IDs/names are available as both **snake_case** (e.g. `msg_id`) and **camelCase** (e.g. `msgId`) on the data objects.
242
+
243
+ ---
244
+
245
+ ## Stopping
246
+
247
+ **`stop()`** stops polling and the API server; if **`run()`** was blocking, it returns. Call **`stop()`** via **Ctrl+C** (SIGINT is handled) or from another thread. After that, to use the bot again you must call **`connect()`** (or **`login()`**) again.
248
+
249
+ ---
250
+
251
+ ## Project layout
252
+
253
+ - **`TgrEzLi`** – main package
254
+ - **`TEL`** – bot class (core)
255
+ - **`TgMsg`, `TgCmd`, `TgCb`, `TgArgs`** – handler context proxies (from `TgrEzLi.models`)
256
+ - **`TReq`, `TgrezliRequestError`** – HTTP client for the embedded API
257
+ - **`TgrezliConfig`** – config dataclass (from `TgrEzLi.types`)
258
+ - **`TgrEzLi.crypto`** – **`CredentialManager`** (uses PyCypherLib): `signup(token, chat_dict, password)`, `login(password)`.
259
+
260
+ Logging is done with **ezlog** on the console; if `save_log` is true, the same messages are appended to the configured log file.
261
+
262
+ ---
263
+
264
+ ## Security and robustness
265
+
266
+ - **API server**: POST body size limited (default 1 MiB); invalid JSON returns 400; unknown route 404. No built-in authentication – protect the server (e.g. firewall, reverse proxy, or add your own auth in routes).
267
+ - **Credentials**: Stored in an encrypted file when using `signup`/`login` (PyCypherLib); decryption only with the correct password. Keep the file and password secure.
268
+ - **Handlers**: User code runs in daemon threads; exceptions are logged and do not crash the bot.
269
+
270
+ ---
271
+
272
+ ## License
273
+
274
+ MIT License. Copyright (c) eaannist.
@@ -0,0 +1,261 @@
1
+ # TgrEzLi
2
+
3
+ Telegram bot library with a **sync-style API**, handlers for messages, commands, and callbacks, and an optional **embedded HTTP server** for custom POST endpoints. Built on [python-telegram-bot](https://pypi.org/project/python-telegram-bot/) and [ezlog](https://pypi.org/project/ezlog-py/) for logging.
4
+
5
+ - **Sync-style usage**: register handlers with decorators, use `TgMsg` / `TgCmd` / `TgCb` / `TgArgs` in handlers (thread-local context).
6
+ - **Multiple chats**: connect with a `{chat_name: chat_id}` dict; restrict handlers per chat.
7
+ - **Optional API server**: expose custom POST routes; call them with the `TReq` client.
8
+ - **Credential encryption**: store token and chats in an encrypted file with **PyCypherLib** (included by default).
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install TgrEzLi
16
+ ```
17
+
18
+ Dependencies: `ezlog-py`, `python-telegram-bot`, **PyCypherLib**, `requests`.
19
+
20
+ ---
21
+
22
+ ## Quick start
23
+
24
+ **After `connect()`** the bot starts polling in a **background thread**, so **`send_msg()` and handlers work immediately** (no need to call `run()`). Use **`run()`** only to **block the main thread** until you press Ctrl+C or call `stop()` (e.g. in a script so the process does not exit).
25
+
26
+ ```python
27
+ from TgrEzLi import TEL, TgMsg, TgCmd, TgCb, TgArgs, TReq
28
+
29
+ bot = TEL()
30
+
31
+ # 1) Connect: starts polling in background. send_msg() works right away.
32
+ TOKEN = "123456789:ABCDEFGHIJKLMNO"
33
+ CHAT_DICT = {"chat1": "123456789", "chat2": "789456123"}
34
+ bot.connect(TOKEN, CHAT_DICT)
35
+
36
+ # You can send without calling run() (e.g. from REPL)
37
+ bot.send_msg("Ciao a tutti")
38
+
39
+ # 2) Register handlers (before or after connect)
40
+ @bot.on_message()
41
+ def on_message():
42
+ bot.send_msg(f"Hi {TgMsg.userName}! You wrote: {TgMsg.text}")
43
+
44
+ @bot.on_command("start")
45
+ def on_start():
46
+ bot.send_msg(f"Welcome {TgCmd.userName}!")
47
+ if TgCmd.args:
48
+ bot.send_msg(f"Args: {TgCmd.args}")
49
+
50
+ # 3) Optional: block main thread until Ctrl+C (so the script does not exit)
51
+ bot.run()
52
+ ```
53
+
54
+ From the **REPL**: after `connect()` you can call `bot.send_msg("...")` and it is sent; handlers are active. If you **redefine** a handler (same function name), the new one **replaces** the previous one (no duplicate execution).
55
+
56
+ ---
57
+
58
+ ## Configuration
59
+
60
+ Create the bot with optional config:
61
+
62
+ ```python
63
+ from TgrEzLi import TEL
64
+ from TgrEzLi.types import TgrezliConfig
65
+
66
+ config = TgrezliConfig(
67
+ save_log=True,
68
+ log_file="TgrEzLi.log",
69
+ api_host="localhost",
70
+ api_port=9999,
71
+ )
72
+ bot = TEL(config)
73
+ ```
74
+
75
+ Or mutate after creation:
76
+
77
+ - **`bot.set_save_log(flag)`** – enable/disable writing log lines to the file (console always uses ezlog).
78
+ - **`bot.set_host(host)`** – API server host (used when the first `on_api_req` is registered).
79
+ - **`bot.set_port(port)`** – API server port.
80
+
81
+ Defaults: `save_log=True`, `log_file="TgrEzLi.log"`, `api_host="localhost"`, `api_port=9999`.
82
+
83
+ ---
84
+
85
+ ## Connecting and running
86
+
87
+ - **`connect(token, chat_dict)`** – Prepares the bot and **starts polling in a background thread**. `send_msg()` and handlers work immediately (e.g. from REPL you can send without calling `run()`).
88
+ - **`run()`** – **Optional.** Blocks the main thread until `stop()` (e.g. Ctrl+C). Use in scripts so the process does not exit. If API routes are registered, starts the API server when you call `run()`.
89
+ - **`stop()`** – Stops polling and API server; if `run()` was waiting, it returns. Safe to call from a signal handler (Ctrl+C) or from another thread.
90
+
91
+ **Direct:**
92
+
93
+ ```python
94
+ bot.connect(TOKEN, {"chat1": "123456789", "chat2": "789456123"})
95
+ bot.send_msg("Hello!") # works immediately
96
+ # ... register handlers ...
97
+ bot.run() # optional: block until Ctrl+C
98
+ ```
99
+
100
+ **Encrypted storage (PyCypherLib):**
101
+
102
+ ```python
103
+ # First time: save token and chats with a password (then connects)
104
+ bot.signup(TOKEN, CHAT_DICT, "your_password")
105
+ bot.run()
106
+
107
+ # Later: load and connect
108
+ bot.login("your_password")
109
+ bot.send_msg("Hi from login")
110
+ bot.run()
111
+ ```
112
+
113
+ Data is stored in `tgrdata.cy` by default (PyCypherLib).
114
+
115
+ ---
116
+
117
+ ## Sending content
118
+
119
+ All send methods accept an optional **`chat`**: `None` (default/first chat), a **string** (one chat name), or a **list/set** of names.
120
+
121
+ | Method | Description |
122
+ |--------|--------------|
123
+ | **`send_msg(text, chat=None)`** | Text message |
124
+ | **`reply_to_msg(text, msg_id, chat=None)`** | Reply to a message |
125
+ | **`send_img(photo_path, caption=None, chat=None)`** | Photo |
126
+ | **`send_file(file_path, caption=None, chat=None)`** | Document |
127
+ | **`send_position(latitude, longitude, chat=None)`** | Location |
128
+ | **`send_buttons(text, buttons, chat=None)`** | Message with inline keyboard (see below) |
129
+ | **`send_log(limit=None, chat=None)`** | Last `limit` lines of log file (or full) |
130
+ | **`send_info(chat=None)`** | Bot info (chats, handlers, API server) |
131
+ | **`send_registered_handlers(chat=None)`** | List of registered handlers (debug) |
132
+
133
+ **Inline buttons:** `buttons` is a list of rows; each row is a list of `{"text": "...", "value": "..."}` (value = callback_data).
134
+
135
+ ```python
136
+ bot.send_buttons("Choose:", [
137
+ [{"text": "Red", "value": "red"}, {"text": "Blue", "value": "blue"}],
138
+ [{"text": "Cancel", "value": "cancel"}],
139
+ ])
140
+ ```
141
+
142
+ CamelCase aliases exist: `sendMsg`, `replyToMsg`, `sendImg`, `sendFile`, `sendPosition`, `sendButtons`, `sendLog`, `sendInfo`, `sendRegisteredHandlers`.
143
+
144
+ ---
145
+
146
+ ## Handlers
147
+
148
+ **Replace-by-name:** If you register a handler whose function has the same **name** as an existing one (same type: message, command, or callback), the new handler **replaces** the old one. So in the REPL you can redefine `handle_message` and only the latest version will run.
149
+
150
+ Handlers run in a **background thread**; inside them use the global proxies **`TgMsg`**, **`TgCmd`**, **`TgCb`**, **`TgArgs`** to access the current payload. **`chat`** filters which chats trigger the handler: `None` = default chat, string = one chat, list/set = multiple chats.
151
+
152
+ ### Message handler
153
+
154
+ ```python
155
+ @bot.on_message() # default chat
156
+ @bot.on_message("chat1") # only chat1
157
+ @bot.on_message(["chat1", "chat2"])
158
+
159
+ def on_message():
160
+ # TgMsg: .text, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
161
+ bot.send_msg(f"Echo: {TgMsg.text}")
162
+ ```
163
+
164
+ ### Command handler
165
+
166
+ ```python
167
+ @bot.on_command("start")
168
+ @bot.on_command("help", "chat1")
169
+
170
+ def on_start():
171
+ # TgCmd: .command, .args, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
172
+ bot.send_msg(f"Welcome {TgCmd.userName}!")
173
+ if TgCmd.args:
174
+ bot.send_msg(f"You said: {TgCmd.args}")
175
+ ```
176
+
177
+ ### Callback handler (inline buttons)
178
+
179
+ ```python
180
+ @bot.on_callback("chat1")
181
+
182
+ def on_callback():
183
+ # TgCb: .text, .value, .msgId, .chatId, .userId, .userName, .timestamp, .raw_update
184
+ if TgCb.value == "red":
185
+ bot.send_msg(f"You pressed {TgCb.text} -> {TgCb.value}")
186
+ ```
187
+
188
+ ### API request handler
189
+
190
+ Register a POST route; the embedded server starts automatically when the first route is registered.
191
+
192
+ ```python
193
+ @bot.on_api_req("/action", args=["chat", "msg"])
194
+ def action():
195
+ # TgArgs.get(key, default)
196
+ chat_id = TgArgs.get("chat")
197
+ msg = TgArgs.get("msg")
198
+ bot.send_msg(msg, chat=chat_id)
199
+ ```
200
+
201
+ **TReq** – client to call these endpoints:
202
+
203
+ ```python
204
+ from TgrEzLi import TReq
205
+
206
+ TReq("/action").host("127.0.0.1").port(9999).arg("chat", "chat1").arg("msg", "Hello!").send()
207
+ # or
208
+ TReq("/action").body({"chat": "chat1", "msg": "Hello!"}).send()
209
+ ```
210
+
211
+ `.host()`, `.port()`, `.timeout(seconds)` are optional (defaults: localhost, 9999, 30s). `.send()` returns a `requests.Response`; on failure raises **`TgrezliRequestError`**.
212
+
213
+ CamelCase decorator aliases: `onMessage`, `onCommand`, `onCallback`, `onApiReq`.
214
+
215
+ ---
216
+
217
+ ## Data interfaces (handler context)
218
+
219
+ Use only **inside** the corresponding handler; otherwise `TgMsg` / `TgCmd` / `TgCb` / `TgArgs` raise if accessed.
220
+
221
+ | Proxy | Handler | Main fields |
222
+ |-------|---------|-------------|
223
+ | **TgMsg** | `on_message` | `text`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
224
+ | **TgCmd** | `on_command` | `command`, `args`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
225
+ | **TgCb** | `on_callback` | `text`, `value`, `msgId`, `chatId`, `userId`, `userName`, `timestamp`, `raw_update` |
226
+ | **TgArgs** | `on_api_req` | `TgArgs.get(key, default)` for POST body |
227
+
228
+ All IDs/names are available as both **snake_case** (e.g. `msg_id`) and **camelCase** (e.g. `msgId`) on the data objects.
229
+
230
+ ---
231
+
232
+ ## Stopping
233
+
234
+ **`stop()`** stops polling and the API server; if **`run()`** was blocking, it returns. Call **`stop()`** via **Ctrl+C** (SIGINT is handled) or from another thread. After that, to use the bot again you must call **`connect()`** (or **`login()`**) again.
235
+
236
+ ---
237
+
238
+ ## Project layout
239
+
240
+ - **`TgrEzLi`** – main package
241
+ - **`TEL`** – bot class (core)
242
+ - **`TgMsg`, `TgCmd`, `TgCb`, `TgArgs`** – handler context proxies (from `TgrEzLi.models`)
243
+ - **`TReq`, `TgrezliRequestError`** – HTTP client for the embedded API
244
+ - **`TgrezliConfig`** – config dataclass (from `TgrEzLi.types`)
245
+ - **`TgrEzLi.crypto`** – **`CredentialManager`** (uses PyCypherLib): `signup(token, chat_dict, password)`, `login(password)`.
246
+
247
+ Logging is done with **ezlog** on the console; if `save_log` is true, the same messages are appended to the configured log file.
248
+
249
+ ---
250
+
251
+ ## Security and robustness
252
+
253
+ - **API server**: POST body size limited (default 1 MiB); invalid JSON returns 400; unknown route 404. No built-in authentication – protect the server (e.g. firewall, reverse proxy, or add your own auth in routes).
254
+ - **Credentials**: Stored in an encrypted file when using `signup`/`login` (PyCypherLib); decryption only with the correct password. Keep the file and password secure.
255
+ - **Handlers**: User code runs in daemon threads; exceptions are logged and do not crash the bot.
256
+
257
+ ---
258
+
259
+ ## License
260
+
261
+ MIT License. Copyright (c) eaannist.
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "TgrEzLi"
3
+ version = "0.4.0"
4
+ description = "Telegram bot library with sync-style API, handlers for messages/commands/callbacks, and optional embedded API server."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "eaannist", email = "eaannist@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "ezlog-py>=1.0.5",
12
+ "python-telegram-bot>=21.0",
13
+ "PyCypherLib",
14
+ "requests>=2.28.0",
15
+ ]
16
+
17
+ [project.scripts]
18
+ TgrEzLi = "TgrEzLi:main"
19
+
20
+ [build-system]
21
+ requires = ["uv_build>=0.9.27,<0.10.0"]
22
+ build-backend = "uv_build"
23
+
24
+ [tool.uv.build-backend]
25
+ module-root = "src"
26
+ module-name = "TgrEzLi"
@@ -0,0 +1,26 @@
1
+ """TgrEzLi: Telegram bot library with sync-style API and optional embedded API server."""
2
+ from __future__ import annotations
3
+
4
+ from TgrEzLi.core import TEL, __version__
5
+ from TgrEzLi.models import TgArgs, TgCb, TgCmd, TgMsg
6
+ from TgrEzLi.requests import TReq, TgrezliRequestError
7
+ from TgrEzLi.types import TgrezliConfig
8
+
9
+ __all__ = [
10
+ "TEL",
11
+ "TgMsg",
12
+ "TgCmd",
13
+ "TgCb",
14
+ "TgArgs",
15
+ "TReq",
16
+ "TgrezliConfig",
17
+ "TgrezliRequestError",
18
+ "__version__",
19
+ ]
20
+
21
+
22
+ def main() -> None:
23
+ """CLI entry point: print version and short usage."""
24
+ print(f"TgrEzLi {__version__}")
25
+ print("Usage: from TgrEzLi import TEL, TgMsg, TgCmd, TgCb, TgArgs, TReq")
26
+ print(" bot = TEL(); bot.connect(token, chat_dict); ...")