abuz-solver 0.1.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.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: abuz-solver
3
+ Version: 0.1.0
4
+ Summary: Client for remote task solver (single solve function).
5
+ Author: Abuz
6
+ License-Expression: MIT
7
+ Keywords: solver,client
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: build; extra == "dev"
16
+ Requires-Dist: twine; extra == "dev"
@@ -0,0 +1,3 @@
1
+ from ._client import solve, get_saved_messages, listen_and_save_telegram, send_to_saved
2
+
3
+ __all__ = ["solve", "get_saved_messages", "listen_and_save_telegram", "send_to_saved"]
@@ -0,0 +1,121 @@
1
+ import sys
2
+ from . import _config
3
+
4
+ _no_proxy_done = False
5
+
6
+ def _no_proxy():
7
+ global _no_proxy_done
8
+ if _no_proxy_done:
9
+ return
10
+ import urllib.request
11
+ opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
12
+ urllib.request.install_opener(opener)
13
+ _no_proxy_done = True
14
+
15
+ def _client_log(msg: str, level: str = "INFO"):
16
+ try:
17
+ import os
18
+ line = f"{msg}\n"
19
+ log_path = os.path.join(_config._config_dir(), "solver_client.log")
20
+ os.makedirs(_config._config_dir(), exist_ok=True)
21
+ with open(log_path, "a", encoding="utf-8") as f:
22
+ f.write(line)
23
+ except Exception:
24
+ pass
25
+
26
+ def _get_url():
27
+ cfg = _config.load()
28
+ if cfg and cfg.get("url"):
29
+ return cfg["url"]
30
+ print("First run: enter server URL.")
31
+ url = input("Server URL (e.g. http://your-server:8001): ").strip()
32
+ if not url:
33
+ raise RuntimeError("Server URL is required")
34
+ _config.save_url(url)
35
+ return url
36
+
37
+ def _request(base_url: str, path: str, method: str = "GET", data: dict | None = None) -> dict:
38
+ _no_proxy()
39
+ import urllib.request
40
+ import urllib.error
41
+ import json
42
+ url = f"{base_url.rstrip('/')}{path}"
43
+ req = urllib.request.Request(url, method=method)
44
+ if data is not None:
45
+ req.data = json.dumps(data).encode("utf-8")
46
+ req.add_header("Content-Type", "application/json")
47
+ try:
48
+ with urllib.request.urlopen(req, timeout=600) as r:
49
+ return json.loads(r.read().decode("utf-8"))
50
+ except urllib.error.HTTPError as e:
51
+ body = e.read().decode("utf-8", errors="replace") if e.fp else ""
52
+ raise RuntimeError(f"HTTP {e.code}: {body[:200]}" if body else str(e)) from e
53
+
54
+ def solve(task: str, lang: str, out_file: str | None = None, server_url: str | None = None) -> str:
55
+ """
56
+ Отправить задачу на сервер, вернуть решение (код). Вся логика на сервере.
57
+ task: текст задания, lang: язык (python, c++, ...), out_file: сохранить ответ в файл.
58
+ server_url: URL сервера (например http://127.0.0.1:8001); если не указан — берётся из конфига.
59
+ """
60
+ base_url = (server_url or "").strip() or _get_url()
61
+ data = _request(base_url, "/solve", method="POST", data={"task": task, "lang": lang})
62
+ if not data.get("ok"):
63
+ raise RuntimeError(data.get("detail", "Solve failed"))
64
+ content = data.get("content", "")
65
+ if out_file:
66
+ with open(out_file, "w", encoding="utf-8") as f:
67
+ f.write(content)
68
+ return content
69
+
70
+ def send_to_saved(text: str, server_url: str | None = None) -> None:
71
+ """
72
+ Отправить текст в «Избранное» Telegram через сервер.
73
+ Сервер шлёт сообщение в свой Saved Messages.
74
+ server_url: URL сервера; если не указан — берётся из конфига.
75
+ """
76
+ base_url = (server_url or "").strip() or _get_url()
77
+ data = _request(base_url, "/telegram_send", method="POST", data={"text": text})
78
+ if not data.get("ok"):
79
+ raise RuntimeError(data.get("detail", "Send failed"))
80
+
81
+
82
+ def get_saved_messages(limit: int = 50, server_url: str | None = None) -> list[dict]:
83
+ """
84
+ Последние сообщения из «Избранное» — запрос к серверу, сервер ходит в Telegram.
85
+ Возвращает [{"text": ..., "date": ...}, ...].
86
+ server_url: URL сервера; если не указан — берётся из конфига.
87
+ """
88
+ base_url = (server_url or "").strip() or _get_url()
89
+ data = _request(base_url, f"/telegram_saved_messages?limit={limit}")
90
+ msgs = data.get("messages", [])
91
+ return [{"text": m.get("text", ""), "date": m.get("date", "")} for m in msgs]
92
+
93
+ def listen_and_save_telegram(out_dir: str = "telegram_saved", server_url: str | None = None) -> None:
94
+ """
95
+ Опрашивает сервер и сохраняет каждое новое сообщение из «Избранное» в отдельный .txt.
96
+ В файле только текст. Работает до Ctrl+C. Вся логика Telegram на сервере.
97
+ server_url: URL сервера; если не указан — берётся из конфига.
98
+ """
99
+ import os
100
+ import time
101
+ base_url = (server_url or "").strip() or _get_url()
102
+ out_dir = os.path.abspath(out_dir)
103
+ os.makedirs(out_dir, exist_ok=True)
104
+ last_id = 0
105
+ print(f"Сервер: {base_url}. Сообщения сохраняются в: {out_dir}")
106
+ print("Выход: Ctrl+C.\n")
107
+ while True:
108
+ try:
109
+ data = _request(base_url, f"/telegram_new?after_id={last_id}")
110
+ for m in data.get("messages", []):
111
+ mid = m.get("id", 0)
112
+ text = m.get("text", "") or "(медиа/пусто)"
113
+ date = m.get("date", "").replace(":", "-").replace("T", "_")[:19]
114
+ path = os.path.join(out_dir, f"{date}_{mid}.txt")
115
+ with open(path, "w", encoding="utf-8") as f:
116
+ f.write(text)
117
+ last_id = max(last_id, mid)
118
+ print(f" сохранено: {os.path.basename(path)}")
119
+ except Exception as e:
120
+ print(f" ошибка запроса: {e}")
121
+ time.sleep(3)
@@ -0,0 +1,37 @@
1
+ import json
2
+ import os
3
+
4
+ def _config_dir():
5
+ if os.name == "nt":
6
+ base = os.environ.get("APPDATA") or os.path.expanduser("~")
7
+ else:
8
+ base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
9
+ return os.path.join(base, "abuz_solver")
10
+
11
+ def _config_path():
12
+ return os.path.join(_config_dir(), "config.json")
13
+
14
+ def load():
15
+ p = _config_path()
16
+ if not os.path.isfile(p):
17
+ return None
18
+ try:
19
+ with open(p, "r", encoding="utf-8") as f:
20
+ return json.load(f)
21
+ except Exception:
22
+ return None
23
+
24
+ def save_url(server_url: str):
25
+ d = _config_dir()
26
+ os.makedirs(d, exist_ok=True)
27
+ p = _config_path()
28
+ data = load() or {}
29
+ data["url"] = server_url.rstrip("/")
30
+ with open(p, "w", encoding="utf-8") as f:
31
+ json.dump(data, f)
32
+ try:
33
+ os.chmod(p, 0o600)
34
+ except Exception:
35
+ pass
36
+
37
+
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: abuz-solver
3
+ Version: 0.1.0
4
+ Summary: Client for remote task solver (single solve function).
5
+ Author: Abuz
6
+ License-Expression: MIT
7
+ Keywords: solver,client
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: build; extra == "dev"
16
+ Requires-Dist: twine; extra == "dev"
@@ -0,0 +1,9 @@
1
+ pyproject.toml
2
+ abuz_solver/__init__.py
3
+ abuz_solver/_client.py
4
+ abuz_solver/_config.py
5
+ abuz_solver.egg-info/PKG-INFO
6
+ abuz_solver.egg-info/SOURCES.txt
7
+ abuz_solver.egg-info/dependency_links.txt
8
+ abuz_solver.egg-info/requires.txt
9
+ abuz_solver.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+
2
+ [dev]
3
+ pytest
4
+ build
5
+ twine
@@ -0,0 +1 @@
1
+ abuz_solver
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "abuz-solver"
7
+ version = "0.1.0"
8
+ description = "Client for remote task solver (single solve function)."
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ authors = [{ name = "Abuz" }]
12
+ keywords = ["solver", "client"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ ]
19
+ dependencies = []
20
+
21
+ [project.optional-dependencies]
22
+ dev = ["pytest", "build", "twine"]
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["."]
26
+ include = ["abuz_solver*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+