дарк-форумы + cookie-проход CF + opt_in радара в обходе
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

- omni/sources_dark.py: cracked.st, nulledbb, forum.tuts4you — реально отдают темы без логина
- tools/cf_cookies.py: Camoufox снимает cookies с JS-челленджей в data/cookies.json (вне git)
- config: cookies.json подхватывается автоматически
- feeds: include_contains матчится по полному href (showthread.php?tid= не резался по path)
- sources_abuse/abuse_sources: A(extra=...) + tuts4you на html_listing (/topic/)
- sources: _gen_sources() = китайские + дарк наборы
- README: ключи/радар/китайцы/дарк
This commit is contained in:
ENI
2026-10-26 23:39:47 +03:00
parent 2320acf3c5
commit 2b9f4e1d88
8 changed files with 239 additions and 18 deletions

View File

@@ -249,6 +249,37 @@ python -m omni pump thehackernews,cvefeed --limit 200 --apply
- путь `syndication.twitter.com` (429 для всех IP, включая VPS и прокси) и nitter-зеркала (410/403/0) выпилены — не тратить время. - путь `syndication.twitter.com` (429 для всех IP, включая VPS и прокси) и nitter-зеркала (410/403/0) выпилены — не тратить время.
- свой хендл добавляется как источник: `{"name": "x_ник", "category": "abuse", "kind": "tweet", "url": "https://x.com/ник", "adapter": "x_user", "extra": {"handle": "ник"}}`. - свой хендл добавляется как источник: `{"name": "x_ник", "category": "abuse", "kind": "tweet", "url": "https://x.com/ник", "adapter": "x_user", "extra": {"handle": "ник"}}`.
## Ключи парсеров: один пул на весь комбайн
Ключи не лежат в коде и не уходят в git — только локально и в env.
- `python tools/build_vault.py` — собирает пул из `MASTER_KEYS.csv` и локальных дампов в `data/vault.json` (файл в `.gitignore`, значения никогда не печатаются).
- `python -m omni keys` / `python -m omni keys --service firecrawl -v` — сводка: сервис, сколько ключей, сколько готовых, маски.
- `python tools/vault_check.py` — живая проверка: помечает `live / dead / rate / nofund`.
- Рантайм `omni/vault.py`: сначала env (`FIRECRAWL_API_KEY`, `TAVILY_API_KEY`, …), потом пул; ротация по кругу; остывание ключа после 401/402/403 — час, после 429 — 10 минут. Один мёртвый ключ не ломает прогон.
- Провайдеры поиска/скрейпа (`firecrawl`, `tavily`, `exa`, `serper`, `jina`, `apify`) берут ключ из пула автоматически — отдельная настройка не нужна.
- Панель: `GET /api/keys` — сводка по сервисам без значений, строка «N ключей · M сервисов» в сайдбаре.
- В пуле сейчас 66 сервисов и ~9.9k записей; из парсерных: `firecrawl` 70 (живые, поиск проверен), `2captcha` 36, `anticaptcha` 15, `capsolver` 1, `telegram` 77 ботов; ключи `openai/deepseek/openrouter` идут на перевод и саммари.
- Капча для закрытых площадок: `2captcha`/`anticaptcha`/`capsolver` из того же пула.
## abuse-radar внутрь реестра
Радар (домены + каналы + посты) больше не отдельная система:
- `python -m omni radar` — dry-run: сколько доменов с каким score и сколько живых каналов.
- `python -m omni radar --apply --min-score 20` — пишет `data/radar_sources.json`: домены становятся HTML-источниками, каналы — Telegram-источниками (`tg_web`).
- `python -m omni radar --import-posts` — заливает посты каналов радара в общую базу как обычные записи (типы, score, фильтры, бот работают сразу).
- Источники радара помечены `opt_in`: в обычном обходе их нет, чтобы 2.5k новых источников не тормозили ленту. Берутся явно: `omni feed --tags radar` или `--category abuse`.
- Дефолтный путь к базе радара: `C:/Users/User/tmp/abuse_radar/radar_server.db`, переопределяется `--db`.
## Китайские и дарк-форумы
- `python tools/wave_cn.py [--apply]` — проверяет живые китайские/азиатские площадки и генерирует `omni/sources_cn.py`. Живых 9: 52pojie, nodeseek, kanxue, anquanke, 4hou, xz.aliyun, ithome, cnbeta, v2ex.
- `omni/sources_dark.py` — дарк/крэк-форумы, реально отдающие темы без логина: `cracked.st`, `nulledbb.com`, `forum.tuts4you.com`, `leak.sx` (плюс существующий набор `ab_*`).
- Для площадок под JS-челленджем: `python tools/cf_cookies.py [host …]` — открывает сайт настоящим браузером (Camoufox), ждёт прохода проверки, снимает cookies в `data/cookies.json` (вне git), после чего `omni/net.py` подставляет их сам. Проверка: `python tools/cf_cookies.py --check`.
- `net.get(..., proxy=…)` — принудительный прокси для отдельного источника (например CF-форумы через чистый DE-выход).
- Честное ограничение: `xss.is`, `exploit.in` (форум), `blackhatworld`, `hackforums` отдают контент только аккаунту — cookie-прохода мало, нужен логин.
## Автоаудит и непрерывные коммиты ## Автоаудит и непрерывные коммиты
- `python tools/selfcheck.py` — компиляция пакета, счётчики базы, распределение типов, топ источников, поиск дублей и пустых источников; пишет блок в `REPORT.md` и историю в `data/selfcheck.jsonl`. - `python tools/selfcheck.py` — компиляция пакета, счётчики базы, распределение типов, топ источников, поиск дублей и пустых источников; пишет блок в `REPORT.md` и историю в `data/selfcheck.jsonl`.

View File

@@ -5,9 +5,10 @@
""" """
def A(name, category, kind, url, adapter, tags=(), limit=20): def A(name, category, kind, url, adapter, tags=(), limit=20, extra=None):
return {"name": name, "category": category, "kind": kind, "url": url, return {"name": name, "category": category, "kind": kind, "url": url,
"adapter": adapter, "tags": tuple(tags), "limit": limit, "detail": False, "extra": {}} "adapter": adapter, "tags": tuple(tags), "limit": limit,
"detail": False, "extra": dict(extra or {})}
ABUSE = [ ABUSE = [
@@ -61,8 +62,10 @@ ABUSE = [
A("ab_carder_su", "abuse", "html", "https://carder.su/", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_carder_su", "abuse", "html", "https://carder.su/", "html_listing", ('abuse', 'abuse'), limit=20),
A("ab_procarders_com", "abuse", "html", "https://procarders.com/", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_procarders_com", "abuse", "html", "https://procarders.com/", "html_listing", ('abuse', 'abuse'), limit=20),
A("ab_darkpro_net", "abuse", "html", "https://darkpro.net/", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_darkpro_net", "abuse", "html", "https://darkpro.net/", "html_listing", ('abuse', 'abuse'), limit=20),
A("ab_tuts4you_com", "abuse", "html", "https://tuts4you.com/", "xenforo_latest", ('abuse', 'abuse'), limit=20), A("ab_tuts4you_com", "abuse", "html", "https://tuts4you.com/", "html_listing", ('abuse', 'abuse'), limit=20,
A("ab_forum_tuts4you_com", "abuse", "html", "https://forum.tuts4you.com/", "xenforo_latest", ('abuse', 'abuse'), limit=20), extra={"include_contains": ["/topic/"]}),
A("ab_forum_tuts4you_com", "abuse", "html", "https://forum.tuts4you.com/", "html_listing", ('abuse', 'abuse'), limit=20,
extra={"include_contains": ["/topic/"]}),
A("ab_forum_exetools_com", "abuse", "html", "https://forum.exetools.com/", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_forum_exetools_com", "abuse", "html", "https://forum.exetools.com/", "html_listing", ('abuse', 'abuse'), limit=20),
A("ab_tlgrm_eu", "abuse", "html", "https://tlgrm.eu/channels", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_tlgrm_eu", "abuse", "html", "https://tlgrm.eu/channels", "html_listing", ('abuse', 'abuse'), limit=20),
A("ab_telemetr_me", "abuse", "html", "https://telemetr.me/channels", "html_listing", ('abuse', 'abuse'), limit=20), A("ab_telemetr_me", "abuse", "html", "https://telemetr.me/channels", "html_listing", ('abuse', 'abuse'), limit=20),

View File

@@ -69,6 +69,15 @@ def load():
except Exception: except Exception:
pass pass
CONFIG = cfg CONFIG = cfg
# cookies, снятые браузером (tools/cf_cookies.py) — файл вне git
ck_file = DATA / "cookies.json"
if ck_file.exists():
try:
extra = json.loads(ck_file.read_text(encoding="utf-8"))
if isinstance(extra, dict):
cfg["cookies"] = {**extra, **(cfg.get("cookies") or {})}
except Exception:
pass
raw = os.environ.get("OMNI_COOKIES") or "" raw = os.environ.get("OMNI_COOKIES") or ""
if raw: if raw:
try: try:

View File

@@ -432,7 +432,7 @@ def _html_listing(src, limit):
continue continue
if inc and not any(path.startswith(p) for p in inc): if inc and not any(path.startswith(p) for p in inc):
continue continue
if inc_c and not any(c in path for c in inc_c): if inc_c and not any(c in href for c in inc_c):
continue continue
if href in seen: if href in seen:
continue continue

View File

@@ -245,14 +245,15 @@ def radar_sources():
return rows return rows
def _cn_sources(): def _gen_sources():
"""Китайские/азиатские источники из сгенерированного omni/sources_cn.py.""" """Сгенерированные наборы: китайские (sources_cn) + дарк-форумы (sources_dark)."""
try:
from .sources_cn import CN
except Exception:
return []
out = [] out = []
for d in CN: for mod, attr in (("sources_cn", "CN"), ("sources_dark", "DARK")):
try:
rows = getattr(__import__("omni." + mod, fromlist=[attr]), attr)
except Exception:
continue
for d in rows:
try: try:
out.append(Source(**d)) out.append(Source(**d))
except Exception: except Exception:
@@ -271,7 +272,7 @@ def all_sources():
("name", "category", "kind", "url", "adapter", "tags", "limit", "detail", "extra")} ("name", "category", "kind", "url", "adapter", "tags", "limit", "detail", "extra")}
extra.append(Source(**d)) extra.append(Source(**d))
seen, out = set(), [] seen, out = set(), []
for s in SOURCES + _cn_sources() + extra + radar_sources(): for s in SOURCES + _gen_sources() + extra + radar_sources():
key = (s.url or "").rstrip("/").lower() key = (s.url or "").rstrip("/").lower()
if key and key in seen: if key and key in seen:
continue continue

View File

@@ -5,9 +5,9 @@
""" """
def A(name, category, kind, url, adapter, tags=(), limit=20): def A(name, category, kind, url, adapter, tags=(), limit=20, extra=None):
return {"name": name, "category": category, "kind": kind, "url": url, return {"name": name, "category": category, "kind": kind, "url": url,
"adapter": adapter, "tags": tuple(tags), "limit": limit, "detail": False, "extra": {}} "adapter": adapter, "tags": tuple(tags), "limit": limit, "detail": False, "extra": dict(extra or {})}
ABUSE_LIVE = [ ABUSE_LIVE = [
@@ -18,8 +18,10 @@ ABUSE_LIVE = [
A("ab_blackhatworld_com", "abuse", "html", "https://www.blackhatworld.com/seo/up-to-date-list-of-specialized-software-for-telegram.1642970/", "xenforo_latest", ('abuse', 'abuse'), limit=20), A("ab_blackhatworld_com", "abuse", "html", "https://www.blackhatworld.com/seo/up-to-date-list-of-specialized-software-for-telegram.1642970/", "xenforo_latest", ('abuse', 'abuse'), limit=20),
A("ab_cracked_io", "abuse", "html", "https://cracked.io/", "xenforo_latest", ('abuse', 'abuse'), limit=20), A("ab_cracked_io", "abuse", "html", "https://cracked.io/", "xenforo_latest", ('abuse', 'abuse'), limit=20),
A("ab_t_me", "abuse", "html", "https://t.me/s/Abuz_ai_soft", "tg_web", ('abuse', 'abuse'), limit=20), A("ab_t_me", "abuse", "html", "https://t.me/s/Abuz_ai_soft", "tg_web", ('abuse', 'abuse'), limit=20),
A("ab_tuts4you_com", "abuse", "html", "https://tuts4you.com/", "xenforo_latest", ('abuse', 'abuse'), limit=20), A("ab_tuts4you_com", "abuse", "html", "https://tuts4you.com/", "html_listing", ('abuse', 'abuse'), limit=20,
A("ab_forum_tuts4you_com", "abuse", "html", "https://forum.tuts4you.com/", "xenforo_latest", ('abuse', 'abuse'), limit=20), extra={"include_contains": ["/topic/"]}),
A("ab_forum_tuts4you_com", "abuse", "html", "https://forum.tuts4you.com/", "html_listing", ('abuse', 'abuse'), limit=20,
extra={"include_contains": ["/topic/"]}),
A("ab_nodeloc_com", "abuse", "json", "https://www.nodeloc.com/latest.json", "discourse", ('abuse', 'abuse'), limit=20), A("ab_nodeloc_com", "abuse", "json", "https://www.nodeloc.com/latest.json", "discourse", ('abuse', 'abuse'), limit=20),
A("ab_v2ex_com", "abuse", "json", "https://www.v2ex.com/api/topics/hot.json", "generic_json", ('abuse', 'abuse'), limit=20), A("ab_v2ex_com", "abuse", "json", "https://www.v2ex.com/api/topics/hot.json", "generic_json", ('abuse', 'abuse'), limit=20),
A("ab_hostloc_com", "abuse", "rss", "https://hostloc.com/forum.php?mod=rss", "rss", ('abuse', 'abuse'), limit=20), A("ab_hostloc_com", "abuse", "rss", "https://hostloc.com/forum.php?mod=rss", "rss", ('abuse', 'abuse'), limit=20),

37
omni/sources_dark.py Normal file
View File

@@ -0,0 +1,37 @@
"""Дарк/крэк-форумы, реально отдающие контент без логина.
Проверено живыми запросами: cracked.st (переехал с cracked.io), nulledbb.com,
forum.tuts4you.com (реверс/крэкинг). Закрыты JS-челленджем без cookie:
exploit.in, xss.is, blackhatworld, hackforums, patched.to, darkforums.st.
Подключается в реестр omni через sources.all_sources().
"""
DARK = [
{"name": "dk_cracked_st", "category": "forum", "kind": "site",
"url": "https://cracked.st/", "adapter": "html_listing",
"tags": ("dark", "leaks", "abuse"), "limit": 25,
"extra": {"include_contains": ["showthread.php?tid="]}},
{"name": "dk_nulledbb", "category": "forum", "kind": "site",
"url": "https://nulledbb.com/", "adapter": "html_listing",
"tags": ("dark", "leaks", "abuse"), "limit": 25,
"extra": {"include_contains": ["showthread.php?tid="]}},
{"name": "dk_tuts4you", "category": "forum", "kind": "site",
"url": "https://forum.tuts4you.com/", "adapter": "html_listing",
"tags": ("dark", "reverse", "abuse"), "limit": 25,
"extra": {"include_contains": ["/topic/"]}},
{"name": "dk_leaksx", "category": "forum", "kind": "site",
"url": "https://leak.sx/", "adapter": "html_listing",
"tags": ("dark", "leaks", "abuse"), "limit": 20,
"extra": {}},
]
# Домены, которые открываются только с живой cookie (cf_clearance / сессия).
# Их можно поднять: python tools/cf_cookies.py <host> -> data/cookies.json
NEED_COOKIE = [
"exploit.in", "xss.is", "www.blackhatworld.com", "hackforums.net",
"patched.to", "darkforums.st",
]

138
tools/cf_cookies.py Normal file
View File

@@ -0,0 +1,138 @@
"""Снять cookies с CF-защищённых площадок через реальный браузер (Camoufox).
Домены вроде exploit.in / xss.is / blackhatworld отдают JS-челлендж в ответ на
любой HTTP-клиент. Скрипт открывает площадку настоящим браузером, ждёт прохода
проверки и складывает cookies в data/cookies.json — оттуда их берёт omni/net.py
(config.cookies_for), и html_listing начинает получать контент.
Запуск:
python tools/cf_cookies.py # все из omni.sources_dark.NEED_COOKIE
python tools/cf_cookies.py xss.is # конкретные хосты
python tools/cf_cookies.py --check # только проверить, что уже есть
"""
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
OUT = ROOT / "data" / "cookies.json"
WAIT = 18 # секунд на проход челленджа
def load():
try:
return json.loads(OUT.read_text(encoding="utf-8"))
except Exception:
return {}
def save(d):
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(d, ensure_ascii=False, indent=1), encoding="utf-8")
def grab(host):
"""Открыть https://host, дождаться прохода и вернуть строку cookie."""
from camoufox.sync_api import Camoufox
url = host if host.startswith("http") else "https://" + host
with Camoufox(headless=True, humanize=True, geoip=False) as br:
pg = br.new_page()
pg.goto(url, wait_until="domcontentloaded", timeout=60000)
deadline = time.time() + WAIT
last = ""
while time.time() < deadline:
try:
title = pg.title() or ""
html = pg.content() or ""
except Exception:
title, html = "", ""
last = title
low = (title + html[:3000]).lower()
if not any(k in low for k in ("just a moment", "verify humanity", "checking your browser",
"attention required", "ddos-guard")):
break
time.sleep(2.5)
ck = pg.context.cookies()
html_len = len(pg.content() or "")
br.close()
parts = []
for c in ck:
dom = (c.get("domain") or "").lstrip(".")
if dom and dom not in host and host not in dom:
continue
parts.append("%s=%s" % (c["name"], c["value"]))
return "; ".join(parts), last, html_len
def probe(host, cookie):
"""Проверить, что с cookie приходит настоящая страница."""
import urllib.request
url = host if host.startswith("http") else "https://" + host
req = urllib.request.Request(url, headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0 Safari/537.36",
"Cookie": cookie,
"Accept-Language": "ru,en;q=0.8",
})
try:
with urllib.request.urlopen(req, timeout=25) as r:
body = r.read(300_000).decode("utf-8", "ignore")
low = body[:4000].lower()
bad = any(k in low for k in ("just a moment", "verify humanity", "checking your browser"))
return r.status, len(body), not bad
except Exception as e:
return 0, 0, False
def main():
args = [a for a in sys.argv[1:] if not a.startswith("-")]
check_only = "--check" in sys.argv
if not args:
try:
from omni.sources_dark import NEED_COOKIE
args = list(NEED_COOKIE)
except Exception:
args = ["exploit.in", "xss.is", "www.blackhatworld.com"]
store = load()
print("cookies-файл:", OUT, "| записей уже:", len(store))
if check_only:
for h in args:
ck = store.get(h) or ""
if not ck:
print("%-26s нет cookie" % h)
continue
st, ln, ok = probe(h, ck)
print("%-26s %s len %-7d %s" % (h, st, ln, "OK" if ok else "закрыт"))
return 0
ok_n = 0
for h in args:
print("→", h)
try:
cookie, title, html_len = grab(h)
except Exception as e:
print(" браузер: %s: %s" % (type(e).__name__, str(e)[:90]))
continue
st, ln, ok = probe(h, cookie) if cookie else (0, 0, False)
print(" title=%r html=%d cookie=%d | проверка %s len %d %s"
% (title[:40], html_len, cookie.count("="), st, ln, "OK" if ok else "закрыт"))
if cookie and ok:
store[h] = cookie
ok_n += 1
save(store)
print("\nсохранено рабочих: %d | всего в файле: %d" % (ok_n, len(store)))
print("теперь: python -m omni feed --only <источник>")
return 0
if __name__ == "__main__":
sys.exit(main())