абузы: free-панели new-api на вкладке (роут /api/panels + грид), CSS вынесен из JS, панельный классификатор даёт freeapi-вес, тесты
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 02:06:32 +03:00
parent b09cb5f873
commit 16512bf4a1
21 changed files with 1386 additions and 130 deletions

View File

@@ -7,8 +7,19 @@
Критерий живого: feeds.fetch(src, limit) вернул >= 1 запись.
Мёртвым добираем диагностику прямым net.get (код/блок/размер), чтобы причина
в отчёте была фактической, а не «0 items».
Публичные хелперы (для тестов/внешних вызовов):
candidate_source(c) -> (Source, marker) — dict кандидата -> Source, url
динамического источника пересчитан ДО сети; маркер лежит в extra["_dynamic"].
_registry_sources() -> [Source] — all_sources(include_fresh=False)
_registry_keys() -> (set url, set name) — нормализованные ключи реестра без
текущей sources_fresh (иначе перегенерация считает свою волну дублем).
_registry_urls() -> set url — совместимость.
emit(rows, dead, path, dedupe=True) — только известный dynamic-маркер;
неизвестный маркер = ValueError, а не замороженный протухший url.
"""
import argparse
import datetime as _dt
import json
import sys
import time
@@ -60,34 +71,130 @@ def probe_one(src, limit):
def _norm(u):
u = u.strip().lower().rstrip("/")
u = str(u or "").strip().lower().rstrip("/")
for pre in ("https://", "http://"):
if u.startswith(pre):
u = u[len(pre):]
return u[4:] if u.startswith("www.") else u
def _registry_urls():
def _norm_name(n):
"""Имя источника — первичный ключ source_flags/runs: сравниваем без регистра,
пробелов и разделителей, иначе nvd-recent и nvd_recent разъезжаются."""
return "".join(ch for ch in str(n or "").strip().lower() if ch.isalnum())
def _registry_sources():
"""Реестр без текущего sources_fresh: перегенерация не должна считать
собственную прошлую волну «уже существующей» и стирать свои же находки."""
from omni.sources import all_sources
return {_norm(s.url) for s in all_sources()}
try:
return all_sources(include_fresh=False)
except TypeError: # реестр без флага — совместимость
return all_sources()
def _registry_keys():
"""(нормализованные url, нормализованные имена) реестра без sources_fresh."""
rows = _registry_sources()
return ({_norm(s.url) for s in rows if s.url},
{_norm_name(s.name) for s in rows if s.name})
def _registry_urls():
"""Только url-ключи реестра (совместимость с внешними вызовами)."""
return _registry_keys()[0]
NVD_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0"
KNOWN_DYNAMIC = ("NVD_LAST_WEEK",)
def _nvd_week_url():
"""Фактический url NVD на момент прогона: окно lastMod <=120 дней, база
без окна отдаёт самые старые записи страницы и как «свежая» бесполезна."""
now = _dt.datetime.utcnow()
since = (now - _dt.timedelta(days=7)).strftime("%Y-%m-%dT00:00:00.000")
return ("%s?resultsPerPage=30&lastModStartDate=%s&lastModEndDate=%s"
% (NVD_BASE, since, now.strftime("%Y-%m-%dT%H:%M:%S.000")))
def candidate_source(c):
"""dict кандидата -> (Source, dynamic-маркер), url пересчитан до сети.
Маркер сохраняется в extra["_dynamic"], чтобы отчёт/emit знали, что url
нельзя замораживать. Неизвестный маркер — ValueError: молча подставить
статический url значит опубликовать заведомо протухший источник.
"""
row = dict(c)
marker = str(row.pop("dynamic", "") or "")
extra = dict(row.get("extra") or {})
if marker:
if marker not in KNOWN_DYNAMIC:
raise ValueError("unknown dynamic marker %r" % marker)
if marker == "NVD_LAST_WEEK":
row["url"] = _nvd_week_url()
extra["_dynamic"] = marker
row["extra"] = extra
return Source(**row), marker
# контент-дубли уже имеющихся в реестре фидов (url отличается, лента та же)
CONTENT_DUP = {"elastic_security_labs", "securityweek_alt", "reddit_netsec"}
def _extra_lit(extra):
"""extra-литерал без служебных ключей: _dynamic — метаданные прогона, не источника."""
clean = {k: v for k, v in sorted((extra or {}).items()) if k != "_dynamic"}
return "" if not clean else ", extra=%r" % (clean,)
def _frozen_window(rows):
"""Строки, где в url уже стоит вычисленное окно (lastMod), но маркера нет:
такой отчёт старее фикса dynamic — при emit окно застынет на дате прогона."""
return [str(r.get("name")) for r in rows
if not r.get("dynamic") and "lastmodstartdate=" in str(r.get("url") or "").lower()]
def emit(rows, dead, path, dedupe=True):
"""omni/sources_fresh.py: только живые, без импортов из omni.sources."""
def q(s):
return '"%s"' % str(s).replace('"', '\\"')
"""omni/sources_fresh.py: только живые, без импортов из omni.sources.
Литералы пишем через repr: json.dumps даёт true/false/null — это не Python.
Дедуп сверяет и url, и имя (нормализованные) и добавляет принятые строки в
виденное, поэтому партия уникальна и сама по себе. Динамический url
поддерживается только для известного маркера: неизвестный — явная ошибка,
а не замороженный url, который молча протухнет.
"""
rows = list(rows)
unknown = sorted({str(r.get("dynamic")) for r in rows
if r.get("dynamic") and r.get("dynamic") not in KNOWN_DYNAMIC})
if unknown:
raise ValueError("emit: неизвестный dynamic-маркер %s — замораживать url нельзя"
% ", ".join(map(repr, unknown)))
frozen = _frozen_window(rows)
if frozen:
print("emit: ВНИМАНИЕ, окно в url без dynamic-маркера (%s): отчёт устарел, "
"перепрогони probe_fresh" % ", ".join(frozen))
if dedupe:
have = _registry_urls()
kept, skipped = [], []
for r in [x for x in rows if x["name"] not in CONTENT_DUP]:
n = _norm(r["url"])
(skipped if n in have else kept).append(r)
have_url, have_name = _registry_keys()
seen_url, seen_name = set(have_url), set(have_name)
kept, skipped, dupes = [], [], []
for r in rows:
n, nm = _norm(r.get("url")), _norm_name(r.get("name"))
in_registry = (bool(n) and n in have_url) or (bool(nm) and nm in have_name)
if r.get("name") in CONTENT_DUP:
in_registry = True
if in_registry or (bool(n) and n in seen_url) or (bool(nm) and nm in seen_name):
(skipped if in_registry else dupes).append(r)
continue
if n:
seen_url.add(n)
if nm:
seen_name.add(nm)
kept.append(r)
print("emit: уже есть в реестре -> %d: %s" % (len(skipped), ", ".join(r["name"] for r in skipped)))
print("emit: дубли внутри партии -> %d: %s" % (len(dupes), ", ".join(r["name"] for r in dupes)))
rows = kept
lines = [
@@ -114,23 +221,23 @@ def emit(rows, dead, path, dedupe=True):
" return [",
]
for r in rows:
extra = ""
if r.get("extra"):
extra = ", extra=%s" % json.dumps(r["extra"], ensure_ascii=False, sort_keys=True)
limit = r.get("limit") or 15
lim = "" if limit == 15 else ", limit=%d" % limit
marker = str(r.get("dynamic") or "")
tags = repr(tuple(r.get("tags") or ()))
limit = r.get("limit")
lim = ", limit=%d" % limit if isinstance(limit, int) and limit != 20 else ""
det = ", detail=True" if r.get("detail") else ""
tags = "(%s)" % ", ".join(q(t) for t in r.get("tags") or ())
if r.get("dynamic") == "NVD_LAST_WEEK" or r.get("name") == "nvd_recent":
lines.append(' S(%s, %s, %s, _nvd_week(), %s, %s%s%s),' % (
q(r["name"]), q(r["category"]), q(r["kind"]), q(r["adapter"]), tags, lim, det))
if marker == "NVD_LAST_WEEK":
lines.append(" S(%r, %r, %r, _nvd_week(), %r, %s%s%s%s)," % (
r["name"], r.get("category", ""), r.get("kind", ""), r.get("adapter", "rss"),
tags, lim, det, _extra_lit(r.get("extra"))))
continue
lines.append(' S(%s, %s, %s, %s, %s, %s%s%s%s),' % (
q(r["name"]), q(r["category"]), q(r["kind"]), q(r["url"]), q(r["adapter"]),
tags, lim, det, extra))
lines.append(" S(%r, %r, %r, %r, %r, %s%s%s%s)," % (
r["name"], r.get("category", ""), r.get("kind", ""), r.get("url", ""), r.get("adapter", "rss"),
tags, lim, det, _extra_lit(r.get("extra"))))
lines += [" ]", "", "", "# --- отклонено прогоном (не отдают записи без ключа/логина) ---"]
for r in dead:
lines.append("# %-26s %-44s %s" % (r["name"], r["url"][:44], (r["err"] or "0 items")[:90]))
lines.append("# %-26s %-44s %s" % (r.get("name", "?"), str(r.get("url") or "")[:44],
(r.get("err") or "0 items")[:90]))
text = "\n".join(lines) + "\n"
path.write_text(text, encoding="utf-8")
return len(rows)
@@ -153,6 +260,12 @@ def main():
rows = rep["results"]
alive = [r for r in rows if r["ok"]]
dead = [r for r in rows if not r["ok"]]
frozen = _frozen_window(alive)
if frozen:
print("emit-only: отчёт без dynamic-маркера для %s — окно в url застынет на дате прогона; "
"перепрогони probe_fresh (сеть) или правь источники вручную"
% ", ".join(frozen))
return 2
n = emit(alive, dead, ROOT / "omni" / "sources_fresh.py", dedupe=not a.no_dedupe)
print("omni/sources_fresh.py: %d источников" % n)
return 0
@@ -161,11 +274,11 @@ def main():
srcs = []
for c in cands:
try:
cc = dict(c)
cc.pop("dynamic", None)
srcs.append(Source(**cc))
src, _marker = candidate_source(c)
except Exception as e: # noqa: BLE001
print("skip %s: %s" % (c.get("name"), e))
continue
srcs.append(src)
t0 = time.time()
with ThreadPoolExecutor(max_workers=a.workers) as ex: