vault ключей + мост abuse-radar + китайские источники: всё в одном реестре
- omni/vault.py: пул ключей парсеров (env-first, data/vault.json, ротация, cooldown 401/402/403=1ч, 429=10м), только маски в выводе - tools/build_vault.py: сборка пула из MASTER_KEYS.csv и локальных дампов (значения не в git) - tools/vault_check.py: живая проверка с маскированием - searchers: firecrawl/tavily/exa/serper/jina/apify берут ключ из vault, если нет env - omni radar: домены+каналы из radar_server.db -> реестр, --import-posts заливает посты каналов - radar-источники помечены opt_in: общий обход не тормозят (869 против 3337 всего) - omni/sources_cn.py + tools/wave_cn.py: 9 живых китайских источников (52pojie, nodeseek, kanxue, anquanke, 4hou, xz.aliyun, ithome, cnbeta, v2ex) - net.get(proxy=...): принудительный прокси для CF-площадок - /api/keys + строка ключей в панели - tests/test_vault_radar.py: 7 тестов, всего 29 passed
This commit is contained in:
129
omni/cli.py
129
omni/cli.py
@@ -298,6 +298,122 @@ def cmd_pump(a):
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_keys(a):
|
||||
"""Сводка vault: сколько ключей на сервис, сколько готово, что остывает."""
|
||||
from . import vault
|
||||
st = vault.stats()
|
||||
if a.service:
|
||||
st = [x for x in st if x["service"] == a.service]
|
||||
if not st:
|
||||
print("vault пуст — собери: python tools/build_vault.py")
|
||||
return 1
|
||||
w = max(len(x["service"]) for x in st)
|
||||
print("%-*s %8s %8s %9s %s" % (w, "сервис", "ключей", "готово", "остывают", "env"))
|
||||
for x in st:
|
||||
print("%-*s %8d %8d %9d %s" % (w, x["service"], x["keys"], x["ready"],
|
||||
x["cooling"], "*" if x["env"] else ""))
|
||||
if a.verbose:
|
||||
print()
|
||||
for x in st:
|
||||
for r in (vault._load()[0].get(x["service"]) or []):
|
||||
print(" %-*s %s %-7s %s" % (w, x["service"], vault.mask(r["key"]),
|
||||
r.get("status") or "?", (r.get("note") or "")[:30]))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_radar(a):
|
||||
"""Мост из abuse-radar: домены и Telegram-каналы радара -> реестр omni."""
|
||||
import json as _json
|
||||
import sqlite3 as _sq
|
||||
from pathlib import Path as _P
|
||||
from . import sources as _src
|
||||
|
||||
cand = [a.db] if a.db else [
|
||||
str(_P("C:/Users/User/tmp/abuse_radar/radar_server.db")),
|
||||
str(_P("C:/Users/User/tmp/abuse_radar/radar.db")),
|
||||
"data/radar.db",
|
||||
]
|
||||
db = next((c for c in cand if _P(c).exists()), "")
|
||||
if not db:
|
||||
print("radar.db не найден, укажи --db")
|
||||
return 2
|
||||
|
||||
c = _sq.connect(db)
|
||||
doms = c.execute("SELECT domain, score, info FROM domains ORDER BY score DESC").fetchall()
|
||||
chans = c.execute("SELECT name, ok, fails FROM channels WHERE ok=1").fetchall()
|
||||
posts = c.execute("SELECT chan, mid, date, text FROM messages").fetchall() if a.import_posts else []
|
||||
c.close()
|
||||
|
||||
if a.import_posts:
|
||||
def head_line(txt):
|
||||
for ln in (txt or "").splitlines():
|
||||
ln = ln.strip()
|
||||
if len(ln) >= 20 and sum(ch.isalpha() for ch in ln) >= 10:
|
||||
return ln[:150]
|
||||
return ((txt or "").strip().splitlines() or [""])[0][:150]
|
||||
|
||||
items, seen_u = [], set()
|
||||
for chan, mid, date, text in posts:
|
||||
text = (text or "").strip()
|
||||
if len(text) < 30:
|
||||
continue
|
||||
url_u = "https://t.me/%s/%s" % (chan, mid)
|
||||
if url_u in seen_u:
|
||||
continue
|
||||
seen_u.add(url_u)
|
||||
items.append(Item("rad_" + chan, "post", url=url_u, title=head_line(text), text=text,
|
||||
author="@" + chan, published=(date or "")[:19],
|
||||
source="rad_" + chan, extra={"via": "radar"}))
|
||||
if a.limit:
|
||||
items = items[:a.limit]
|
||||
n_new = n_dup = 0
|
||||
for i in range(0, len(items), 500):
|
||||
n1, n2 = store.add(items[i:i + 500])
|
||||
n_new += n1
|
||||
n_dup += n2
|
||||
print("постов радара: %d | новых %d, дублей %d" % (len(items), n_new, n_dup))
|
||||
|
||||
have = {s.url.rstrip('/').lower() for s in _src.all_sources()}
|
||||
have_names = {s.name for s in _src.all_sources()}
|
||||
|
||||
new_dom, new_chan = [], []
|
||||
for dom, score, info in doms:
|
||||
if a.min_score and (score or 0) < a.min_score:
|
||||
continue
|
||||
url = "https://" + dom
|
||||
if url.lower() in have or ("rad_" + dom.replace('.', '_').replace('-', '_')) in have_names:
|
||||
continue
|
||||
title = ""
|
||||
try:
|
||||
title = (_json.loads(info or "{}") or {}).get("title") or ""
|
||||
except Exception:
|
||||
pass
|
||||
new_dom.append((dom, score, title))
|
||||
for name, ok, fails in chans:
|
||||
if ("tg_" + name) in have_names:
|
||||
continue
|
||||
new_chan.append(name)
|
||||
|
||||
if a.limit:
|
||||
new_dom, new_chan = new_dom[:a.limit], new_chan[:a.limit]
|
||||
print("радар: %s" % db)
|
||||
print("доменов всего %d, новых для omni %d | каналов живых %d, новых %d"
|
||||
% (len(doms), len(new_dom), len(chans), len(new_chan)))
|
||||
for dom, score, title in new_dom[:5]:
|
||||
print(" %-28s score %-4s %s" % (dom, score, (title or "")[:44]))
|
||||
if new_chan[:5]:
|
||||
print(" каналы:", ", ".join(new_chan[:8]))
|
||||
if not a.apply:
|
||||
print("dry-run: добавь --apply, чтобы записать в data/radar_sources.json")
|
||||
return 0
|
||||
|
||||
out = _P("data") / "radar_sources.json"
|
||||
out.write_text(_json.dumps({"domains": new_dom, "channels": new_chan},
|
||||
ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print("записано: %s (доменов %d, каналов %d)" % (out, len(new_dom), len(new_chan)))
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
@@ -405,6 +521,19 @@ def main(argv=None):
|
||||
sp.add_argument("--apply", action="store_true")
|
||||
sp.set_defaults(fn=cmd_pump)
|
||||
|
||||
sp = sub.add_parser("keys", help="ключи парсеров из vault: сервисы, счётчики, остывание")
|
||||
sp.add_argument("--service", default="", help="только один сервис")
|
||||
sp.add_argument("-v", "--verbose", action="store_true", help="по ключам, масками")
|
||||
sp.set_defaults(fn=cmd_keys)
|
||||
|
||||
sp = sub.add_parser("radar", help="забрать источники из abuse-radar (домены + каналы)")
|
||||
sp.add_argument("--db", default="", help="путь к radar.db (по умолчанию ищет рядом)")
|
||||
sp.add_argument("--apply", action="store_true", help="записать в реестр omni")
|
||||
sp.add_argument("--min-score", type=int, default=20, help="минимальный score домена")
|
||||
sp.add_argument("--import-posts", action="store_true", help="залить посты каналов радара в базу")
|
||||
sp.add_argument("--limit", type=int, default=0)
|
||||
sp.set_defaults(fn=cmd_radar)
|
||||
|
||||
args = p.parse_args(argv)
|
||||
return args.fn(args)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user