diff --git a/README.md b/README.md index 4ca0ab6..5452751 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ python -m omni serve --port 8077 ## Скриншоты панели -Снято с живого инстанса (`python -m omni serve --port 8077`), база на момент съёмки: **46 423 записи · 3 815 источников · 6 746 находок**. Пересъёмка одной командой: `python tools/shots_ui.py` (нужен playwright, ждёт исчезновения скелетонов и только потом стреляет). +Снято с живого инстанса (`python -m omni serve --port 8077`), база на момент съёмки: **46 423 записи · 3 815 источников · 6 746 находок**. Пересъёмка одной командой: `python tools/shots.py` (нужен playwright, ждёт исчезновения скелетонов и только потом стреляет). ### Лента @@ -68,10 +68,16 @@ python -m omni serve --port 8077 ### Находки -Промо, триалы, сливы и панели с бесплатными API: 6 746 находок, 1 368 уникальных, фильтр по тегам (`ключи / промо / триалы / сливы / кредиты / инвайты / lifetime deal`). +Промо, триалы, сливы и панели с бесплатными API: 6 746 находок, 5 182 уникальных — перепечатки одного поста из разных каналов схлопнуты по подписи, в карточке видно сколько копий склеено. Фильтр по тегам (`ключи / промо / триалы / сливы / кредиты / инвайты / lifetime deal`). ![Находки — промо, триалы и бесплатные API-панели](docs/screenshots/abuse.png) +### Бесплатные API-панели + +Отдельная вкладка в «Находках»: new-api шлюзы, у которых открыт бесплатный доступ к моделям. 175 панелей в реестре, 131 с бесплатными моделями, проверка идёт через публичный `/api/pricing` — учётные данные не запрашиваются. + +![Бесплатные API-панели — 131 из 175 панелей отдают модели без оплаты](docs/screenshots/panels.png) + ### Источники Реестр на 3 815 источников: тумблер вкл/выкл, результат проверки, число записей в базе, дата последней проверки, добавление своего фида и health-аудит по категории. diff --git a/docs/screenshots/abuse.png b/docs/screenshots/abuse.png index ba83ea6..ceb13a5 100644 Binary files a/docs/screenshots/abuse.png and b/docs/screenshots/abuse.png differ diff --git a/docs/screenshots/feed.png b/docs/screenshots/feed.png index 2dbe86d..1534dbf 100644 Binary files a/docs/screenshots/feed.png and b/docs/screenshots/feed.png differ diff --git a/docs/screenshots/panels.png b/docs/screenshots/panels.png new file mode 100644 index 0000000..2fa0907 Binary files /dev/null and b/docs/screenshots/panels.png differ diff --git a/docs/screenshots/runs.png b/docs/screenshots/runs.png index 9be0aeb..4f7c402 100644 Binary files a/docs/screenshots/runs.png and b/docs/screenshots/runs.png differ diff --git a/docs/screenshots/sources.png b/docs/screenshots/sources.png index 8eecb6f..fc97e4e 100644 Binary files a/docs/screenshots/sources.png and b/docs/screenshots/sources.png differ diff --git a/tools/shots.py b/tools/shots.py new file mode 100644 index 0000000..d54f9d2 --- /dev/null +++ b/tools/shots.py @@ -0,0 +1,56 @@ +"""Пересъёмка скриншотов дашборда для README. + +Запуск: python tools/shots.py +Пишет docs/screenshots/*.png с живого http://127.0.0.1:8078. +Перед сохранением проверяет кадр на токеноподобные строки — +скриншот с секретом в реестр не попадёт. +""" +import re +import sys +from camoufox.sync_api import Camoufox + +BASE = "http://127.0.0.1:8078" +SHOTS = [ + ("/#/", "overview.png"), + ("/#/feed", "feed.png"), + ("/#/abuse", "abuse.png"), + ("/#/abuse?tab=panels", "panels.png"), + ("/#/sources", "sources.png"), + ("/#/tg", "tg.png"), + ("/#/watch", "watch.png"), + ("/#/digest", "digest.png"), + ("/#/runs", "runs.png"), +] +SECRET = re.compile(r"\b(sk-|pk-|ek_live_|ghp_|xoxb-)[A-Za-z0-9_\-]{12,}") + + +def main(): + bad = [] + with Camoufox(headless=True, geoip=False, humanize=True) as b: + for route, name in SHOTS: + p = b.new_page(viewport={"width": 1500, "height": 1350}) + p.goto(BASE + route, wait_until="domcontentloaded", timeout=90000) + n = 0 + for _ in range(12): + p.wait_for_timeout(2500) + n = p.evaluate("document.querySelectorAll('.item,tbody tr').length") + head = p.evaluate("document.body.innerText.slice(0,400)") + if n > 3 and "Загрузка" not in head: + break + txt = p.evaluate("document.body.innerText") + if SECRET.search(txt): + bad.append(name) + print("SKIP (секрет в кадре):", name) + p.close() + continue + p.screenshot(path="docs/screenshots/" + name) + print("%-12s %-22s items/rows %s" % (name, route, n)) + p.close() + if bad: + print("не сняты из-за секретов:", bad) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main())