ENI f9719b9149
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
ci: воркфлоу в .gitea/workflows (GitHub-токен без scope workflow отклоняет .github/workflows), копия для GitHub в ci/
2026-10-26 20:16:57 +03:00

Telegram-бот: кнопки, канал, формат

set OMNI_BOT_TOKEN=<токен BotFather>      # .bot_token, в git не попадает
set OMNI_BOT_OWNERS=<твой numeric id>     # кому отвечает, можно несколько через запятую
python -m omni.bot

Экран бота — инлайн-кнопки: Лента, Абузы, Источники, Цифры, теги Промо / Триалы / Сливы, Подписка: вкл/выкл, Формат: кратко/полно, Пост в канал.

Команды: /new [N], /abuse [тег], /find <слово>, /src <слово>, /run <категория>, /add <url>, /auto [N], /stats, /sub [@канал], /unsub [@канал], /subs, /post [@канал].

Постинг в канал. Добавь бота админом канала с правом публикации и отправь /sub @твойканал — бот каждые 5 минут проверяет новые абузы и публикует только те, что появились после подписки. /post @твойканал — опубликовать подборку сразу, /unsub @твойканал — остановить. Дальше можно подключить свой бот: код один, токен свой, база общая.

Формат текста. кратко — заголовок, источник, тег. полно — плюс ссылка на запись. Переключается кнопкой и хранится на чат/канал: канал можно держать в одном формате, личку — в другом.

Про ключи не публикуем. Тег keys отфильтрован на уровне бота (HIDE_TAGS в omni/bot.py): записи, у которых после фильтра не осталось тегов, не попадают ни в /abuse, ни в автопостинг.

omni-parse

Один парсер под любую тему: Reddit, форумы, поисковики, X/Twitter, карточки товаров и платные search-API — в одну SQLite-базу с FTS5, дедупом и рейтингом.

Ставится без ключей. Платные провайдеры включаются сами, если в окружении есть ключ.

Состояние на сейчас

  • источников: 853 в 11 категориях (662 базовых + 191 из реестра абузов, дубли вырезаны), записей в базе: 5596 (224 с ценой, 121 прогон)
  • типы записей: 14 — каждая запись размечена по смыслу: новость 3693 · софт 516 · уязвимость 402 · релиз 361 · модель 331 · вопрос 265 · цена 205 · гайд 194 · датасет 126 · слив 90 · триал 41 · промо 34 · вакансия 21 · перепродажа 10
  • X/Twitter: 30 аккаунтов читаются без API-ключей через api.fxtwitter.com (313 твитов в базе)
  • вкладки панели: Лента · Источники · Прогоны · Абузы; полоса чипов «тип», фильтр «только с ценой», сортировка score/fresh/price, экспорт CSV
  • health-аудит: живой/мёртвый/непроверенный по каждому источнику, причина в примечании
  • Telegram-бот на той же базе: /new, /abuse, /find, /src, /run, /add, /auto, /sum, /tr, /stats
  • тесты: tests/test_regress.py — 16 проверок на те же баги, что ловило ревью; CI: .github/workflows/ci.yml (py3.10/3.11/3.12)
  • автоаудит: tools/selfcheck.py обновляет REPORT.md и гоняет тесты, tools/autocommit.sh коммитит и пушит в Gitea + GitHub
категория источников
company 135
community 131
security 99
papers 72
forum 57
models 40
abuse 40
company_cn 34
release 30
benchmark 16
company_kr 11

Панель

python -m omni serve --port 8077
# http://127.0.0.1:8077/?view=abuse

?view=feed|src|runs|abuse, горячие клавиши 1..4, палитра Ctrl K, категории в URL (?cat=), кнопка «авто-добавление» ищет новые фиды на уже подключённых хостах и сохраняет только те, что реально отдали записи.

Что умеет

Провайдер Что тянет Ключ
reddit поиск по постам и комментариям через api.pullpush.io (архив Reddit) + публичный json как запас нет
forums Discourse (9 инстансов), Hacker News (Algolia), Lobsters, XenForo, веб-версии Telegram-каналов нет
ddg html-выдача DuckDuckGo нет
bing html-выдача Bing, ссылки раскодируются из ck/a?...u=a1<base64url> нет
mojeek html Mojeek (свой индекс, без капчи) нет
x без платного API: oembed по URL твита, syndication-таймлайн профиля, поиск твитов через ddg/bing/mojeek нет
tavily Tavily search TAVILY_API_KEY
exa Exa search + текст страниц EXA_API_KEY
you You.com search YOU_API_KEY
firecrawl Firecrawl search FIRECRAWL_API_KEY
cards карточки товаров: JSON-LD Product/Offer, OpenGraph, microdata, цена/валюта/наличие нет
treg treg-поиск TREG_TOKEN
feed обход реестра из 450 лент/API без запроса (см. ниже) нет

python -m omni providers печатает, что реально поднялось в этом окружении.

Что было сломано и что стало (по ревью)

Замеры сняты на этой машине, до и после правок. Скепсис к ревью тоже был: два пункта не подтвердились, они отмечены.

пункт было стало
/run в боте new += store.add(items) → TypeError, команда падала всегда n_new, _dup = store.add(items); прогон отдаёт готово: собрано 1, новых 1
параметры API ?limit=-1 → вся база (SQLite LIMIT -1), ?limit=abc → трейс и обрыв limit=-1 → 1 строка, abc → 40, 99999 → 200 (HTTP 200 во всех трёх)
соединения SQLite with conn() коммитит без close(): 200 вызовов → +76 хендлов with db() (commit/rollback/close): 200 вызовов → +0
троттлинг 24 вызова на хост при 0.3с проходили за 0.9с вместо 6.9с слот резервируется до сна, тест держит паузу и в 6 потоков
владелец бота пустой OMNI_BOT_OWNERS → владельцем становился первый написавший без env бот в read-only: /new /abuse /find /src /stats, мутации отбиваются
мёртвый abuse_sources.py 237 строк, 0 импортов подключён: +191 источник категории abuse (31 дубль отсеян по url)
дубли реестра дубли по url all_sources() дедуплицирует: 853 источника = 853 уникальных url
JOBS в панели рос без границ, 500 запусков → 500 записей eviction до 300 при переходе через 400
push_new уходили самые старые записи окна (fresh[-per_chat:]) уходят самые свежие (fresh[:per_chat])
ai.py sleep(1.5 + attempt) и после последней попытки спит только перед повтором

Что из ревью не подтвердилось: rank.py не мёртвый — его зовёт cli.py в трёх местах; дублей в реестре был один, а не 38 (на старой базе ревью их было больше). except Exception осталось 59 на ~5000 строк: часть глушит сетевые парсеры осознанно, но в панели ни один запрос больше не отдаёт трейс — параметры валидируются до вызова.

Установка

pip install -r requirements.txt
cp .env.example .env   # ключи по желанию, ни один не обязателен

Быстрый старт

# что доступно
python -m omni providers

# поиск по одной теме в четырёх бесплатных источниках
python -m omni search "scraping twitter without api" --providers reddit,forums,ddg,bing --limit 12 --show 10

# реестр источников: сколько и каких
python -m omni sources
python -m omni sources --category papers,security --list

# обход реестра БЕЗ запроса: 589 источников -> база
python -m omni feed --category community --limit 8
python -m omni feed --limit 15            # всё подряд
python -m omni feed --only arxiv_cs_ai,hn_top,reddit_localllama

# прогон по списку тем из themes.txt (по теме на строку)
python -m omni fanout --providers reddit,forums,ddg,bing --limit 10

# топ базы, статистика, поиск по своей базе
python -m omni top --limit 20
python -m omni stats
python -m omni find "proxy"

# карточки товаров по URL
python -m omni cards https://example.com/product

# выгрузка
python -m omni export --format jsonl --out data/dump.jsonl

Реестр источников

omni/sources.py + omni/sources_ext.py — 589 источников в 9 категориях, у каждого свой адаптер и флаг вкл/выкл:

  • papers 49 — arxiv по 25+ разделам (cs.AI/LG/CL/CV/RO/IR/CR/SE/NE/MA/DC/DB/HC/SD/CY, stat.ML, eess.AS/SP/IV, math.OC, q-bio.NC, cs.GR, cs.AR, cs.PF), HF daily papers, OpenAlex, Semantic Scholar, Papers with Code, Connected Papers
  • company 95 — ленты OpenAI, Google AI/Research/DeepMind, Microsoft Research, NVIDIA, Apple ML, Amazon Science, HF, Meta AI, Mistral, Cohere, Together, Perplexity, Groq, Cerebras, Stability, Databricks, Anyscale, Modal, W&B, LangChain, LlamaIndex, vLLM, Replicate, Fireworks, SambaNova, Scale, xAI, Salesforce, AWS ML, Azure AI, Anthropic, AI2, EleutherAI, LAION, Runway, ElevenLabs, Deepgram, AssemblyAI, Pinecone, Weaviate, Qdrant, Chroma, Unsloth, Axolotl
  • company_cn 31 — Qwen, DeepSeek changelog, Moonshot, Zhipu, Baichuan, MiniMax, StepFun, 01.AI, InternLM, ChatGLM + GitHub-орги Tencent-Hunyuan, PaddlePaddle, bytedance, mindspore-ai, QwenLM, deepseek-ai, MoonshotAI, THUDM, OpenBMB, Shanghai-AI-Lab
  • company_kr 10 — Kakao tech, Naver Cloud/D2, Samsung Research, LG AI Research, Upstage, Rebellions, Twelve Labs, FriendliAI
  • community 49 — HN top/new/ask/show, Lobsters (4 тега), GeekNews, Reddit: LocalLLaMA, OpenAI, singularity, MachineLearning, deeplearning, netsec, programming, rust, golang, Python, webdev, selfhosted, devops, datascience, androiddev, javascript, linux, experienced_devs
  • security 45 — THN, BleepingComputer, Krebs, SANS ISC, Packet Storm, CISA advisories + CISA KEV (JSON), NVD CVE (JSON), Exploit-DB, Project Zero, PortSwigger, GitHub Security, Rapid7, Qualys, Tenable, CrowdStrike, SentinelOne, Unit42, Talos, Securelist, WeLiveSecurity, Malwarebytes, Schneier, Graham Cluley, HackerOne hacktivity, Bugcrowd, Intigriti, YesWeHack, CTFtime, OWASP, SANS diaries, Risky Business, tl;dr sec, Detection Engineering
  • forum 31 — 10 Discourse-инстансов (discuss.python, community.openai, forum.cursor, discuss.huggingface, forum.obsidian, community.cloudflare, discuss.elastic, forum.gitlab, community.home-assistant, discuss.grafana), XenForo BHW, веб-версии Telegram-каналов (durov, telegram, AI-каналы), Stack Overflow tags, Reddit-миграции
  • models 18 — HF trending/new (модели, спейсы, датасеты), HF по 6 пайплайн-тегам, Ollama library + search, LM Studio catalog, OpenRouter models + pricing (JSON), Civitai, Replicate explore
  • benchmark 10 — Open LLM Leaderboard, LMArena, Artificial Analysis, Epoch AI, SWE-bench, LiveBench, HELM, MTEB, BigCodeBench, Chatbot Arena

Адаптеры: rss, generic_json, or_models, cisa_kev, nvd, json (HF, HN, OpenAlex, S2, Discourse), html (список + детальная), github_org. Медленные хосты (reddit, github, HF, arxiv, openrouter) обходятся последовательно с паузой, остальные — в 8 потоков. Для arxiv RSS, который часто отдаёт пустое, есть fallback на официальный API (export.arxiv.org/api/query).

Панель и команды

python -m omni serve --port 8077      # веб-панель + JSON API (без сборки, без npm)
python -m omni feed --limit 10        # обход всего реестра
python -m omni doctor --fix           # проверить источники и подобрать рабочие фиды
python -m omni digest --out data/digest.md

Панель — две вкладки:

  1. лента — счётчики (записей, с ценой, прогонов, источников, включённых), поиск по базе, фильтр по категориям, кнопка «собрать ленту» с прогрессом и разбивкой fetched/new/dup/empty.
  2. источники — менеджер реестра:
    • таблица всех 589 источников: переключатель вкл/выкл, категория, тип, url, статус (жив / мёртв / не проверен), число записей;
    • фильтр по имени/url и по категории;
    • подключить источник — форма (имя, url, категория): тип и адаптер определяются по url автоматически (/rss → rss, .json//api/ → generic_json, github.com/org → github_org, t.me/s/ → tg_web), запись живёт в SQLite и сразу участвует в обходах;
    • проверить всё / проверить категорию — фоновой джоб: реальный фетч каждого источника, запись status/items/note/checked, прогресс done/total, живых/мёртвых;
    • вкл все / выкл все — массово по текущему фильтру;
    • удаление своих источников (реестровые только выключаются).

Выключенный источник не дёргается ни панелью, ни omni feed, ни omni doctor — фильтр применяется в sources.pick().

API: GET /api/stats, GET /api/items?category=&q=&limit=&offset=, GET /api/sources?category=&q=, POST /api/sources/toggle {name,enabled}, POST /api/sources/add {name,category,url}, POST /api/sources/remove {name}, POST /api/sources/check {name|names|category|all}, POST /api/feed {category,limit}, GET /api/job/<id>.

Состояние источников — таблицы source_flags и source_custom в той же SQLite (data/omni.sqlite3).

Типы записей (не путать с категорией источника)

Категория отвечает «откуда», тип — «что именно». Одна запись получает до 3 типов, они лежат в items.types и отдаются в API (/api/types, ?type=leak), видны бейджами в панели, попадают в CSV.

слив · триал · софт · промо · перепродажа · уязвимость · релиз · гайд · датасет · модель · вакансия · цена · вопрос · новость

Правила лежат в omni/taxonomy.py: взвешенные RU/EN-паттерны, порог по весу, чтобы «free» в рекламе не превращалось в «триал».

X / Twitter без ключей

  • omni/providers/x.py → profile(handle) тянет ленту через api.fxtwitter.com/2/profile/<handle>/statuses: полный текст, метрики (лайки, репосты, просмотры), дата, язык.
  • адаптер x_user в omni/feeds.py, источники — omni/sources_x.py (30 живых хендлов: abuse/security/models/community).
  • путь 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": "ник"}}.

Автоаудит и непрерывные коммиты

  • python tools/selfcheck.py — компиляция пакета, счётчики базы, распределение типов, топ источников, поиск дублей и пустых источников; пишет блок в REPORT.md и историю в data/selfcheck.jsonl.
  • bash tools/autocommit.sh [--feed] — самоаудит (+ подкачка, если --feed), коммит только при реальных изменениях, пуш в оба ремоута.
  • cron: каждые 6 часов аудит-коммит, каждые 12 часов аудит + подкачка источников.
  • omni dedupe [--apply] — схлопывает дубли по url (или source+title при пустом url), вставка тоже дедуплицирует по url.

Как устроено

  • omni/net.py — лестница транспорта: httpx → curl_cffi (подмена TLS-фингерпринта) → прокси из env. Троттлинг по хосту, лимит байт, пометка блокировок.
  • omni/model.py — единая схема записи: провайдер, тип, url, заголовок, текст, автор, дата, теги, цена, счёт, simhash.
  • omni/filter.py — релевантность: запрос должен быть покрыт текстом (≥60% терминов) и хотя бы один термин — в заголовке. Для русского сравнивает по 5-символьному префиксу, иначе морфология убивает совпадения.
  • omni/store.py — SQLite WAL + FTS5, таблицы items, bands, runs. Дедуп: simhash по шинглам, сравнение по Хэммингу ≤3 по бакетам.
  • omni/rank.py — вес провайдера, свежесть, наличие цены, вовлечение.
  • omni/providers/ — по файлу на семейство источников, реестр в __init__.py.

Реальные прогоны

python -m omni search "scraping twitter without api" --providers reddit,forums,ddg,bing
  bing 10 ok | ddg 10 ok | forums 10 ok | reddit 8 ok
  собрано 38, новых 31, дублей 7

python -m omni fanout   # 8 тем из themes.txt
  итого новых 200, дублей 5

python -m omni stats
  записей 266 | прогонов 56 (ok 37)
  ddg=93 bing=85 forums=56 reddit=32

Ограничения

  • Reddit напрямую с серверных IP отдаёт 403 — используется архив pullpush; свежие посты (последние часы) в архиве могут отсутствовать.
  • X/Twitter без токена: доступны только публичные встраивания и таймлайн профиля, полноценного полнотекстового поиска нет.
  • mojeek отдаёт пустую страницу при агрессивном троттлинге — провайдер помечается, остальные работают.
  • cards не обходит защиту от парсинга: если страница рендерится JS, разметки в html не будет.

Лицензия

MIT.

Абузы, авто-добавление и Telegram-бот

  • абузы: категория abuse — 31 живая площадка (XenForo/Discourse/Telegram/RSS), включая свои каналы, классификатор omni/abuse.py раскладывает записи по тегам ключи / промо / триалы / сливы / софт / продажа / новости. В панели — отдельная вкладка (?view=abuse), в боте — /abuse [тег].
  • авто-добавление: omni/discover.py ищет фиды на уже подключённых хостах и каталогах, проверяет их теми же адаптерами и сохраняет только те, что реально отдали записи. В панели — кнопка «авто-добавление», в API — POST /api/discover?hosts=40&add=25.
  • новости: любые RSS/Atom/JSON-ленты (редакции, релизы, сообщества) подключаются как обычный источник или через /add <url> в боте — отдельной схемы не требуется.

Telegram-бот

set OMNI_BOT_TOKEN=<токен BotFather>
set OMNI_BOT_ALLOW=<твой numeric id>
python -m omni.bot

Команды: /new [N], /abuse [тег], /find <слово>, /src <слово>, /run <категория>, /add <url>, /stats. Бот работает на той же базе и том же реестре, что панель; отвечает только id из OMNI_BOT_ALLOW.

ИИ: перевод и саммари

Абузные площадки в основном не на русском: nodeloc (китайский), afflift и BHW (английский). Лента переводится на лету, подборка сворачивается в саммари — 3-5 пунктов, что раздают, платно или халява, где.

Подключение. Любой OpenAI-совместимый эндпоинт:

set OMNI_AI_KEY=<ключ>                       # обязателен только для ИИ-функций
set OMNI_AI_BASE=https://api.reformboss.com/v1
set OMNI_AI_MODEL=gpt-6.1-sol                # по умолчанию он же

Если OMNI_AI_KEY не задан, omni/ai.py ищет ключ в конфиге Hermes (custom_providers с reformboss в base_url). Без ключа парсер работает как раньше: ИИ-функции молча выключаются, кнопки пишут «ИИ не подключён».

В боте: кнопки ИИ-саммари, Перевод: вкл/выкл, Саммари в подписке: вкл/выкл; команды /sum [тег], /tr <id|слово>. Перевод включается на чат/канал: в канале можно держать русский формат, в личке — оригинал.

Автопостинг. Если для канала включено «саммари в подписке», бот перед списком новых абузов пишет ИИ-сводку, дальше 5 находок с переводом заголовков.

В панели: вкладка Абузы → кнопка ИИ-саммари (GET /api/summary?tag=promo&limit=14).

Как это сделано: omni/ai.py, только stdlib (urllib), ретраи с падением на glm-5.3 и qwen3.8-flash, если основная модель отдала 503/429. Переводы и саммари кэшируются в таблице ai_cache по хешу текста — повторные прогоны не тратят токены. Ключи не пишутся в репозиторий и не логируются.

Что нужно подключить

  1. Python 3.11+ и зависимости: pip install -r requirements.txt (httpx, curl_cffi, bs4, lxml). Больше ничего не обязательно — база создаётся сама в data/omni.sqlite3.
  2. Ни одного ключа не нужно. Reddit (архив), форумы (Discourse/XenForo/веб-Telegram), HN, arXiv, GitHub, HF, поисковики работают без регистрации. Платные search-API (TAVILY_API_KEY, EXA_API_KEY, YOU_API_KEY, FIRECRAWL_API_KEY) подхватываются из env, если они есть: с ними больше покрытие, без них проект полностью рабочий.
  3. Свои источники: python -m omni add <url> --category forum --tags мои, кнопка «авто-добавление» в панели, POST /api/discover или /auto в боте. Адрес проверяется живым запросом и сохраняется только если реально отдал записи (проверено: omni add https://lobste.rs/rss → добавлен manual_lobste_rs · rss · 6 записей).
  4. Свои ключи, прокси и куки — всё через env или config.json рядом с проектом:
{"proxy_mode": "list",
 "proxies": ["http://user:pass@host:port"],
 "cookies": {"site.com": "session=abc; csrf=xyz"}}
  • ключи: TAVILY_API_KEY, EXA_API_KEY, YOU_API_KEY, FIRECRAWL_API_KEY, JINA_API_KEY, SERPER_API_KEY, BRAVE_API_KEY, X_BEARER, REDDIT_CLIENT_ID + REDDIT_SECRET, TREG_TOKEN, GITHUB_TOKEN — подхватываются из .env/env, omni providers показывает, что подключилось;
  • прокси: proxy_mode = off | list | env (OMNI_PROXIES=http://p1,http://p2) — используется третьим шагом лестницы, когда прямой запрос упёрся в блок (проверено: прямой egress 178.150.68.140 → через прокси 150.241.69.145);
  • куки: cookies в config.json или OMNI_COOKIES='{"site.com":"a=b"}' — подставляются по хосту с матчем по суффиксу (проверено на echo-эндпоинте: обе куки дошли);
  • свой сервер: python -m omni serve --port 8077 поднимает панель и JSON API, за nginx/Caddy это уже удалённый инстанс на своей машине, база едет в OMNI_DB.
  1. Свой бот: токен BotFather в OMNI_BOT_TOKEN, свой numeric id в OMNI_BOT_OWNERS, бота админом канала и /sub @канал.

Проект обновляется

Проект живой: реестр источников, адаптеры и классификатор абузов правятся по мере того, как площадки ломают разметку. Обновление:

git pull
pip install -r requirements.txt
python -m omni serve --port 8077     # панель
python -m omni.bot                    # бот, если нужен

База в data/ не перезаписывается — источники, здоровье и подписки остаются на месте. Ближайшие шаги: отдельная новостная лента с группировкой по сюжетам, скоринг абузов по свежести и частоте площадки, экспорт подборки в канал по расписанию.

Description
multi-source topic parser: reddit, forums, search engines, x, cards, sqlite+fts5
Readme MIT 5.6 MiB
Languages
Python 86.2%
HTML 13.6%
Shell 0.2%