абузы: free-панели new-api на вкладке (роут /api/panels + грид), CSS вынесен из JS, панельный классификатор даёт freeapi-вес, тесты
This commit is contained in:
@@ -115,6 +115,33 @@ def _names_in_category(cat):
|
||||
return [s.name for s in sources.all_sources() if s.category == cat]
|
||||
|
||||
|
||||
def _run_state(status):
|
||||
"""Класс статуса прогона: ok / empty / none / err.
|
||||
|
||||
"ok", "ok 0/10", "ok-304" — успех (startswith); "пусто"/"empty" — источник ответил пусто,
|
||||
это не ошибка; отсутствующий статус (None/"") — отдельный класс none; остальное — err.
|
||||
"""
|
||||
v = " ".join(str(status or "").split()).lower()
|
||||
if not v:
|
||||
return "none"
|
||||
if v.startswith("ok"):
|
||||
return "ok"
|
||||
if v in ("пусто", "empty"):
|
||||
return "empty"
|
||||
return "err"
|
||||
|
||||
|
||||
def _since_days(days):
|
||||
"""?days=N -> граница since (YYYY-MM-DD) для store.browse2; 0 — без фильтра.
|
||||
|
||||
Единая логика для /api/items и экспортов: «сутки/неделя/месяц» значат одно и то же.
|
||||
"""
|
||||
if not days:
|
||||
return None
|
||||
import datetime as _dt
|
||||
return (_dt.date.today() - _dt.timedelta(days=days)).isoformat()
|
||||
|
||||
|
||||
def _run_feed(job_id, category, limit, tags):
|
||||
picked = sources.pick(category, None, tags)
|
||||
_job(job_id, state="running", total=len(picked), done=0)
|
||||
@@ -211,6 +238,18 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return self._send(404, "no ui", "text/plain")
|
||||
return self._send(200, f.read_bytes(), "text/html; charset=utf-8")
|
||||
|
||||
if path == "/api/panels":
|
||||
import json as _json
|
||||
fp = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "panels.json")
|
||||
try:
|
||||
with open(fp, "r", encoding="utf-8") as f:
|
||||
raw = _json.load(f)
|
||||
except Exception:
|
||||
raw = []
|
||||
rows = raw if isinstance(raw, list) else (raw.get("panels") or raw.get("items") or [])
|
||||
rows = sorted(rows, key=lambda x: -(x.get("free_count") or x.get("free") or 0))
|
||||
return self._json({"ok": True, "total": len(rows), "panels": rows[:200]})
|
||||
|
||||
if path == "/api/stats":
|
||||
s = store.stats()
|
||||
per_source = store.count_by_source()
|
||||
@@ -264,6 +303,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
import time as _t
|
||||
con = store._con() if hasattr(store, "_con") else None
|
||||
rows = []
|
||||
by_status = {"ok": 0, "empty": 0, "err": 0, "none": 0}
|
||||
try:
|
||||
import sqlite3 as _s3
|
||||
from .config import DB as _DB
|
||||
@@ -274,12 +314,22 @@ class Handler(BaseHTTPRequestHandler):
|
||||
d = dict(r)
|
||||
ts = d.get("ts") or 0
|
||||
d["when"] = _t.strftime("%Y-%m-%d %H:%M", _t.localtime(ts)) if ts else ""
|
||||
d["state"] = _run_state(d.get("status"))
|
||||
d["ok"] = d["state"] == "ok"
|
||||
rows.append(d)
|
||||
total = db.execute("SELECT count(*) FROM runs").fetchone()[0]
|
||||
# статусы считаем по всей таблице: total-ok из среза limit давал ложные «ошибки»
|
||||
sql = ("SELECT COALESCE(SUM(CASE WHEN lower(trim(status)) LIKE 'ok%' THEN 1 ELSE 0 END),0),"
|
||||
" COALESCE(SUM(CASE WHEN lower(trim(status)) IN ('пусто','empty') THEN 1 ELSE 0 END),0),"
|
||||
" COALESCE(SUM(CASE WHEN status IS NULL OR trim(status)='' THEN 1 ELSE 0 END),0)"
|
||||
" FROM runs")
|
||||
n_ok, n_empty, n_none = db.execute(sql).fetchone()
|
||||
by_status = {"ok": int(n_ok or 0), "empty": int(n_empty or 0), "none": int(n_none or 0)}
|
||||
by_status["err"] = max(0, int(total) - by_status["ok"] - by_status["empty"] - by_status["none"])
|
||||
db.close()
|
||||
except Exception as e:
|
||||
return self._json({"error": "runs: %s" % e})
|
||||
return self._json({"items": rows, "total": total, "shown": len(rows)})
|
||||
return self._json({"items": rows, "total": total, "shown": len(rows), "by_status": by_status})
|
||||
|
||||
if path == "/api/abuse":
|
||||
from . import abuse as _ab
|
||||
@@ -355,13 +405,13 @@ class Handler(BaseHTTPRequestHandler):
|
||||
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()
|
||||
since = _since_days(days)
|
||||
rows, total = store.browse2(q=query, names=_names_in_category(cat), limit=limit, offset=offset,
|
||||
sort=sort, priced=priced, typ=typ, since=since, src=src)
|
||||
from . import abuse as _ab
|
||||
for _r in rows:
|
||||
# find нужен itemRow/findChips: browse2 сам признаки абуза не считает
|
||||
_r["find"] = _ab.extract(" ".join((_r.get("title") or "", _r.get("text") or "")))
|
||||
_r["title"] = _clean_txt(_r.get("title"), 180)
|
||||
_r["text"] = _clean_txt(_r.get("text"), 260)
|
||||
_r["published"] = _fmt_date(_r.get("published"))
|
||||
@@ -374,8 +424,11 @@ class Handler(BaseHTTPRequestHandler):
|
||||
priced = (q.get("priced") or ["0"])[0] in ("1", "true", "on")
|
||||
sort = (q.get("sort") or ["score"])[0]
|
||||
typ = (q.get("type") or ["all"])[0]
|
||||
rows, total = store.browse2(q=query, names=_names_in_category(cat), limit=5000, offset=0,
|
||||
sort=sort, priced=priced, typ=typ)
|
||||
since = _since_days(_intv(q, "days", 0, 0, 3650))
|
||||
limit = _intv(q, "limit", 1000, 1, 5000)
|
||||
src = (q.get("src") or [""])[0].strip() or None
|
||||
rows, total = store.browse2(q=query, names=_names_in_category(cat), limit=limit, offset=0,
|
||||
sort=sort, priced=priced, typ=typ, since=since, src=src)
|
||||
import io as _io
|
||||
buf = _io.StringIO()
|
||||
w = _csv.writer(buf, delimiter=";")
|
||||
@@ -445,12 +498,17 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if path == "/api/export.json":
|
||||
cat = (q.get("category") or [None])[0]
|
||||
priced = (q.get("priced") or ["0"])[0] in ("1", "true", "on")
|
||||
since = _since_days(_intv(q, "days", 0, 0, 3650))
|
||||
src = (q.get("src") or [""])[0].strip() or None
|
||||
rows, total = store.browse2(q=(q.get("q") or [""])[0].strip(),
|
||||
names=_names_in_category(cat),
|
||||
limit=_intv(q, "limit", 500, 1, 5000),
|
||||
limit=_intv(q, "limit", 1000, 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")
|
||||
typ=(q.get("type") or ["all"])[0],
|
||||
priced=priced, since=since, src=src)
|
||||
data = json.dumps({"total": total, "count": len(rows), "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"')
|
||||
|
||||
Reference in New Issue
Block a user