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 +274 -0
- tgrezli-0.4.0/README.md +261 -0
- tgrezli-0.4.0/pyproject.toml +26 -0
- tgrezli-0.4.0/src/TgrEzLi/__init__.py +26 -0
- tgrezli-0.4.0/src/TgrEzLi/api_server.py +120 -0
- tgrezli-0.4.0/src/TgrEzLi/config.py +5 -0
- tgrezli-0.4.0/src/TgrEzLi/core.py +558 -0
- tgrezli-0.4.0/src/TgrEzLi/crypto.py +39 -0
- tgrezli-0.4.0/src/TgrEzLi/defaults.py +16 -0
- tgrezli-0.4.0/src/TgrEzLi/handlers.py +70 -0
- tgrezli-0.4.0/src/TgrEzLi/logger.py +65 -0
- tgrezli-0.4.0/src/TgrEzLi/models.py +82 -0
- tgrezli-0.4.0/src/TgrEzLi/requests.py +49 -0
- tgrezli-0.4.0/src/TgrEzLi/types.py +119 -0
- tgrezli-0.1.2/PKG-INFO +0 -238
- tgrezli-0.1.2/README.md +0 -214
- tgrezli-0.1.2/TgrEzLi/__init__.py +0 -10
- tgrezli-0.1.2/TgrEzLi/core.py +0 -449
- tgrezli-0.1.2/TgrEzLi.egg-info/PKG-INFO +0 -238
- tgrezli-0.1.2/TgrEzLi.egg-info/SOURCES.txt +0 -9
- tgrezli-0.1.2/TgrEzLi.egg-info/dependency_links.txt +0 -1
- tgrezli-0.1.2/TgrEzLi.egg-info/requires.txt +0 -3
- tgrezli-0.1.2/TgrEzLi.egg-info/top_level.txt +0 -1
- tgrezli-0.1.2/setup.cfg +0 -4
- tgrezli-0.1.2/setup.py +0 -19
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.
|
tgrezli-0.4.0/README.md
ADDED
|
@@ -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); ...")
|