abuse: полный пересчёт индекса только по явному вызову (env не тормозит scan)
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled

This commit is contained in:
Vlad Gothbreach
2026-10-27 01:21:01 +03:00
parent d0b2187319
commit 42a864419b
12 changed files with 1844 additions and 983 deletions

View File

@@ -12,7 +12,7 @@ import urllib.parse
from concurrent.futures import ThreadPoolExecutor
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from . import feeds, net, sources, source_state, store
from . import feeds, net, panel_api, sources, source_state, store
WEB = pathlib.Path(__file__).resolve().parent.parent / "web"
JOBS = {}
@@ -353,8 +353,14 @@ class Handler(BaseHTTPRequestHandler):
sort = (q.get("sort") or ["score"])[0]
priced = (q.get("priced") or ["0"])[0] in ("1", "true", "on")
typ = (q.get("type") or ["all"])[0]
src = (q.get("src") or [""])[0].strip() or None
days = _intv(q, "days", 0, 0, 3650)
since = None
if days:
import datetime as _dt
since = (_dt.date.today() - _dt.timedelta(days=days)).isoformat()
rows, total = store.browse2(q=query, names=_names_in_category(cat), limit=limit, offset=offset,
sort=sort, priced=priced, typ=typ)
sort=sort, priced=priced, typ=typ, since=since, src=src)
for _r in rows:
_r["title"] = _clean_txt(_r.get("title"), 180)
_r["text"] = _clean_txt(_r.get("text"), 260)
@@ -387,6 +393,71 @@ class Handler(BaseHTTPRequestHandler):
self.wfile.write(data)
return
if path.startswith("/api/item/"):
try:
iid = int(path.rsplit("/", 1)[-1])
except ValueError:
return self._json({"error": "bad id"}, 400)
return self._json(panel_api.item_detail(iid))
if path == "/api/timeline":
days = _intv(q, "days", 30, 1, 365)
cat = (q.get("category") or [None])[0]
return self._json(panel_api.timeline(days=days, cat=cat))
if path == "/api/suggest":
return self._json(panel_api.suggest((q.get("q") or [""])[0], _intv(q, "limit", 8, 1, 20)))
if path == "/api/health":
return self._json(panel_api.health())
if path == "/api/tg/status":
return self._json(panel_api.tg_status())
if path == "/api/tg/search":
term = (q.get("q") or [""])[0].strip()
if len(term) < 2:
return self._json({"ok": False, "error": "нужно минимум 2 символа", "channels": []})
return self._json(panel_api.tg_search(term, limit=_intv(q, "limit", 40, 1, 100),
min_members=_intv(q, "min", 0, 0, 10_000_000)))
if path == "/api/source":
name = (q.get("name") or [""])[0].strip()
return self._json(panel_api.source_detail(name) if name else {"error": "name required"})
if path == "/api/watch":
return self._json(panel_api.watch_list(hits=_intv(q, "hits", 3, 1, 10)))
if path == "/api/digest":
days = _intv(q, "days", 1, 1, 30)
res = panel_api.digest(days=days, limit=_intv(q, "limit", 30, 5, 100))
if (q.get("download") or ["0"])[0] in ("1", "true"):
data = res["md"].encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/markdown; charset=utf-8")
self.send_header("Content-Disposition", 'attachment; filename="omni-digest.md"')
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
return self._json(res)
if path == "/api/export.json":
cat = (q.get("category") or [None])[0]
rows, total = store.browse2(q=(q.get("q") or [""])[0].strip(),
names=_names_in_category(cat),
limit=_intv(q, "limit", 500, 1, 5000),
sort=(q.get("sort") or ["score"])[0],
typ=(q.get("type") or ["all"])[0])
data = json.dumps({"total": total, "items": rows}, ensure_ascii=False, indent=1).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Disposition", 'attachment; filename="omni.json"')
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
if path.startswith("/api/job/"):
jid = path.rsplit("/", 1)[-1]
return self._json(JOBS.get(jid) or {"state": "unknown"})
@@ -414,6 +485,25 @@ class Handler(BaseHTTPRequestHandler):
res = _d.discover(limit_hosts=hosts, workers=8, max_add=add, dry=dry)
return self._json(res)
if u.path == "/api/tg/pull":
handles = [h for h in (body.get("handles") or []) if isinstance(h, str)][:20]
if not handles:
return self._json({"error": "handles required"}, 400)
jid = _jid("tg")
_job(jid, state="running")
threading.Thread(target=lambda: _job(jid, **panel_api.tg_pull(handles, int(body.get("limit") or 100))),
daemon=True).start()
return self._json({"job": jid})
if u.path == "/api/watch/add":
return self._json(panel_api.watch_add(body.get("q"), body.get("type") or "all"))
if u.path == "/api/watch/remove":
rid = body.get("id")
if not rid:
return self._json({"error": "id required"}, 400)
return self._json(panel_api.watch_remove(str(rid)))
if u.path == "/api/feed":
jid = _jid("feed")
_job(jid, state="running")