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 CHANGED
@@ -1,10 +1,26 @@
1
- from .core import TEL, TgMsg, TgCmd, TgCb, TgArgs, TReq
2
-
3
- __all__ = [
4
- "TEL",
5
- "TgMsg",
6
- "TgCmd",
7
- "TgCb",
8
- "TgArgs",
9
- "TReq"
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"]
TgrEzLi/config.py ADDED
@@ -0,0 +1,5 @@
1
+ """Configuration for TgrEzLi. Uses TgrezliConfig from types."""
2
+
3
+ from TgrEzLi.types import TgrezliConfig
4
+
5
+ __all__ = ["TgrezliConfig"]