TgrEzLi 0.1.2__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of TgrEzLi might be problematic. Click here for more details.
- TgrEzLi/__init__.py +26 -10
- TgrEzLi/api_server.py +120 -0
- TgrEzLi/config.py +5 -0
- TgrEzLi/core.py +482 -373
- TgrEzLi/crypto.py +39 -0
- TgrEzLi/defaults.py +16 -0
- TgrEzLi/handlers.py +70 -0
- TgrEzLi/logger.py +65 -0
- TgrEzLi/models.py +82 -0
- TgrEzLi/requests.py +49 -0
- TgrEzLi/types.py +119 -0
- tgrezli-0.4.0.dist-info/METADATA +274 -0
- tgrezli-0.4.0.dist-info/RECORD +15 -0
- {tgrezli-0.1.2.dist-info → tgrezli-0.4.0.dist-info}/WHEEL +1 -2
- tgrezli-0.4.0.dist-info/entry_points.txt +3 -0
- tgrezli-0.1.2.dist-info/METADATA +0 -238
- tgrezli-0.1.2.dist-info/RECORD +0 -6
- tgrezli-0.1.2.dist-info/top_level.txt +0 -1
TgrEzLi/__init__.py
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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); ...")
|
TgrEzLi/api_server.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Embedded HTTP server for custom POST API routes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from TgrEzLi.defaults import MAX_REQUEST_BODY_BYTES
|
|
10
|
+
from TgrEzLi.handlers import call_user_function
|
|
11
|
+
from TgrEzLi.types import TgArgsData
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApiServer:
|
|
15
|
+
"""Runs a background HTTP server that dispatches POST requests to registered routes."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
logger: Any,
|
|
20
|
+
registry: Any,
|
|
21
|
+
host: str = "localhost",
|
|
22
|
+
port: int = 9999,
|
|
23
|
+
max_body_bytes: int = MAX_REQUEST_BODY_BYTES,
|
|
24
|
+
) -> None:
|
|
25
|
+
self.logger = logger
|
|
26
|
+
self.registry = registry
|
|
27
|
+
self.host = host
|
|
28
|
+
self.port = port
|
|
29
|
+
self.max_body_bytes = max_body_bytes
|
|
30
|
+
self._server_thread: threading.Thread | None = None
|
|
31
|
+
self._running = False
|
|
32
|
+
self._httpd: HTTPServer | None = None
|
|
33
|
+
|
|
34
|
+
def start(self) -> None:
|
|
35
|
+
if self._running:
|
|
36
|
+
self.logger.info("API server is already running.")
|
|
37
|
+
return
|
|
38
|
+
self._running = True
|
|
39
|
+
self._server_thread = threading.Thread(target=self._serve_forever, daemon=True)
|
|
40
|
+
self._server_thread.start()
|
|
41
|
+
|
|
42
|
+
def stop(self) -> None:
|
|
43
|
+
if not self._running:
|
|
44
|
+
self.logger.info("API server is not running.")
|
|
45
|
+
return
|
|
46
|
+
self._running = False
|
|
47
|
+
if self._httpd:
|
|
48
|
+
self.logger.info("Shutting down API server...")
|
|
49
|
+
self._httpd.shutdown()
|
|
50
|
+
self._httpd.server_close()
|
|
51
|
+
if self._server_thread and self._server_thread.is_alive():
|
|
52
|
+
self._server_thread.join(timeout=5)
|
|
53
|
+
self.logger.info("API server has been stopped.")
|
|
54
|
+
|
|
55
|
+
def _serve_forever(self) -> None:
|
|
56
|
+
server_self = self
|
|
57
|
+
max_body = self.max_body_bytes
|
|
58
|
+
|
|
59
|
+
class ReusableHTTPServer(HTTPServer):
|
|
60
|
+
allow_reuse_address = True
|
|
61
|
+
|
|
62
|
+
class ApiRequestHandler(BaseHTTPRequestHandler):
|
|
63
|
+
def do_POST(handler_self: BaseHTTPRequestHandler) -> None:
|
|
64
|
+
path = handler_self.path
|
|
65
|
+
try:
|
|
66
|
+
length = int(handler_self.headers.get("Content-Length", 0))
|
|
67
|
+
except ValueError:
|
|
68
|
+
length = 0
|
|
69
|
+
if length < 0 or length > max_body:
|
|
70
|
+
handler_self.send_response(413)
|
|
71
|
+
handler_self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
72
|
+
handler_self.end_headers()
|
|
73
|
+
handler_self.wfile.write(
|
|
74
|
+
json.dumps({"status": "error", "message": "Invalid or too large body"}).encode("utf-8")
|
|
75
|
+
)
|
|
76
|
+
return
|
|
77
|
+
raw_body = handler_self.rfile.read(length) if length > 0 else b""
|
|
78
|
+
try:
|
|
79
|
+
data = json.loads(raw_body.decode("utf-8")) if raw_body else {}
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
handler_self.send_response(400)
|
|
82
|
+
handler_self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
83
|
+
handler_self.end_headers()
|
|
84
|
+
handler_self.wfile.write(
|
|
85
|
+
json.dumps({"status": "error", "message": "Invalid JSON body"}).encode("utf-8")
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
route_info = server_self.registry.api_routes.get(path)
|
|
90
|
+
if route_info:
|
|
91
|
+
func = route_info["func"]
|
|
92
|
+
tg_args = TgArgsData(data)
|
|
93
|
+
call_user_function(server_self.logger, func, TgArgs=tg_args)
|
|
94
|
+
handler_self.send_response(200)
|
|
95
|
+
handler_self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
96
|
+
handler_self.end_headers()
|
|
97
|
+
resp = {"status": "ok", "path": path}
|
|
98
|
+
handler_self.wfile.write(json.dumps(resp).encode("utf-8"))
|
|
99
|
+
else:
|
|
100
|
+
handler_self.send_response(404)
|
|
101
|
+
handler_self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
102
|
+
handler_self.end_headers()
|
|
103
|
+
resp = {"status": "error", "message": "Route not found"}
|
|
104
|
+
handler_self.wfile.write(json.dumps(resp).encode("utf-8"))
|
|
105
|
+
|
|
106
|
+
def log_message(self, format: str, *args: object) -> None:
|
|
107
|
+
server_self.logger.debug("%s - %s", self.address_string(), format % args)
|
|
108
|
+
|
|
109
|
+
with ReusableHTTPServer((self.host, self.port), ApiRequestHandler) as http_server:
|
|
110
|
+
self.logger.info("API server listening on http://%s:%s", self.host, self.port)
|
|
111
|
+
self._httpd = http_server
|
|
112
|
+
try:
|
|
113
|
+
http_server.serve_forever()
|
|
114
|
+
except Exception as e:
|
|
115
|
+
self.logger.error("API server error: %s", e)
|
|
116
|
+
finally:
|
|
117
|
+
self.logger.info("API server serve_forever has exited.")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = ["ApiServer"]
|