From 4196d66b4e82de573a06a0d51448714b8c5bb7a8 Mon Sep 17 00:00:00 2001 From: ENI Date: Mon, 26 Oct 2026 17:33:03 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20cookie-jar=20=D0=BF=D0=BE=20=D1=85?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=83,=20CLI=20`omni=20add`=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=81=D0=B2=D0=BE=D0=B8=D1=85=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2,=20README=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B8/=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=D1=81=D0=B8/=D0=BA=D1=83=D0=BA=D0=B8/=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++++++-- omni/cli.py | 25 +++++++++++++++++++++++++ omni/config.py | 24 ++++++++++++++++++++++++ omni/net.py | 3 +++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61442b2..c14a2e1 100644 --- a/README.md +++ b/README.md @@ -236,8 +236,18 @@ set OMNI_AI_MODEL=gpt-6.1-sol # по умолчанию он же 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. **Свои источники**: кнопка «авто-добавление» в панели, `POST /api/discover`, `/auto` в боте или `python -m omni add ` — адрес проверяется реальным запросом и сохраняется только если отдал записи. -4. **Свой бот**: токен BotFather в `OMNI_BOT_TOKEN`, свой numeric id в `OMNI_BOT_OWNERS`, бота админом канала и `/sub @канал`. +3. **Свои источники**: `python -m omni add --category forum --tags мои`, кнопка «авто-добавление» в панели, `POST /api/discover` или `/auto` в боте. Адрес проверяется живым запросом и сохраняется только если реально отдал записи (проверено: `omni add https://lobste.rs/rss` → `добавлен manual_lobste_rs · rss · 6 записей`). +4. **Свои ключи, прокси и куки** — всё через env или `config.json` рядом с проектом: +```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`. +5. **Свой бот**: токен BotFather в `OMNI_BOT_TOKEN`, свой numeric id в `OMNI_BOT_OWNERS`, бота админом канала и `/sub @канал`. ## Проект обновляется diff --git a/omni/cli.py b/omni/cli.py index 8eae735..1bc4463 100644 --- a/omni/cli.py +++ b/omni/cli.py @@ -241,6 +241,25 @@ def cmd_export(args): return 0 +def cmd_add_src(a): + """Свой источник: пробуем url живым запросом, сохраняем только если отдал записи.""" + import re as _re + import urllib.parse as _up + from . import discover as _disc, source_state as _st + url = (a.url or "").strip() + if not url.startswith("http"): + print("нужен url фида"); return 1 + ok, n, ad = _disc.probe(url, a.category, limit=6) + if not ok: + print("фид пустой или недоступен: %s" % ad); return 1 + name = "manual_" + _re.sub(r"[^a-z0-9]+", "_", _up.urlsplit(url).netloc.lower())[:32] + kind = "rss" if ad == "rss" else "html" + tags = [t.strip() for t in (a.tags or "").split(",") if t.strip()] or ["manual"] + _st.add_custom(name, a.category, url, kind=kind, adapter=ad, tags=tags, limit=15) + print("добавлен %s · %s · %d записей" % (name, ad, n)) + return 0 + + def main(argv=None): try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") @@ -332,6 +351,12 @@ def main(argv=None): sp.add_argument("--limit", type=int, default=1000) sp.set_defaults(fn=cmd_export) + sp = sub.add_parser("add", help="добавить свой источник по url фида") + sp.add_argument("url") + sp.add_argument("--category", default="community") + sp.add_argument("--tags", default="manual") + sp.set_defaults(fn=cmd_add_src) + args = p.parse_args(argv) return args.fn(args) diff --git a/omni/config.py b/omni/config.py index 427af14..03f5ae0 100644 --- a/omni/config.py +++ b/omni/config.py @@ -19,12 +19,28 @@ DEFAULTS = { "(KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"), "proxy_mode": "off", # off | list | env "proxies": [], + "cookies": {}, # host -> "a=b; c=d" (свои сессии к закрытым площадкам) } KEYS = {} # имя -> значение CONFIG = {} # прочие настройки +def cookies_for(host): + """Cookie-строка под хост: точное совпадение или суффикс (example.com -> www.example.com).""" + ck = CONFIG.get("cookies") or {} + if not ck: + return "" + host = (host or "").lower().split(":")[0] + if host in ck: + return ck[host] + for k, v in ck.items(): + k = k.lower().lstrip(".") + if host == k or host.endswith("." + k): + return v + return "" + + def _load_env_file(path): p = pathlib.Path(path) if not p.exists(): @@ -53,6 +69,14 @@ def load(): except Exception: pass CONFIG = cfg + raw = os.environ.get("OMNI_COOKIES") or "" + if raw: + try: + extra = json.loads(raw) + if isinstance(extra, dict): + cfg["cookies"] = {**(cfg.get("cookies") or {}), **extra} + except Exception: + pass for name in ("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"): diff --git a/omni/net.py b/omni/net.py index f7e8a22..c1f9145 100644 --- a/omni/net.py +++ b/omni/net.py @@ -126,6 +126,9 @@ def get(url, headers=None, timeout=None, json_mode=False): h["Accept"] = "application/json" h["Content-Type"] = "application/json" h.update(headers or {}) + ck = config.cookies_for(host) + if ck: + h["Cookie"] = ck first = _try_httpx(url, h, timeout, None) if first.ok and not first.blocked: