SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/ Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue, контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер), Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог). Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0, тесты 1340/130/52/38/9 зелёные.
18 KiB
Поиск и подключение каналов (Discovery) — дизайн
Исторический документ (дизайн Discovery прототипа LeadRadar, 2026-09-04). Актуальное состояние —
docs/superpowers/STATUS.mdиdocs/technical/Техническая-документация-Дейл.md.
Дата: 2026-09-04 Статус: согласован с пользователем (правки от 2026-09-04 учтены)
1. Цель
Пользователь даёт системе «задание»: найти Telegram-каналы и группы, в которых мы ещё не состоим, по описанию цели (например, «вакансии и фриланс для разработки») и подключить их к мониторингу. Система сама ищет кандидатов, оценивает их (по метаданным, языку и содержанию сообщений) и показывает человеку список «на рассмотрение»; человек решает — вступить и мониторить или отклонить. Возможен режим авто-вступления в рамках суточных квот и с паузами против бана.
Ключевое правило: источники, в которых мы уже состоим (вступили/мониторим), исключаются сразу и безусловно — независимо от запроса, ключей и настроек задачи. Это глобальное правило системы: действует на всех этапах (поиск → оценка → вступление) и для всех задач.
2. Ограничения Telegram API (факты, на которых строится дизайн)
- Глобального «поиска по критериям» в API нет.
contacts.search(q)возвращает публичные каналы/группы/боты по имени/username/запросу — без фильтров по участникам, языку и содержимому. Всю дальнейшую фильтрацию делаем сами. - Число участников/описание — через
channels.getFullChannel. Для публичных каналов доступно без вступления; для групп часто доступно только членам. - Чтение истории без вступления: публичные каналы — обычно можно; публичные группы — только если история открыта; иначе — только членам.
- Массовый поиск/чтение/вступления с юзер-аккаунта ограничены эмпирически — нужны
квоты, паузы и обработка
FloodWaitError.
3. Понятия
- Задача (task) — конфиг поиска: описание цели, ключи, фильтры, план, режим авто-вступления, статус/счётчики. Задач может быть несколько.
- Кандидат (candidate) — найденный источник (канал/группа, для форумов — оценка по
темам). Проходит стадии:
new → evaluated → review → joined | rejected. - Метки кандидата — человекочитаемые пометки: «закрытая группа/канал», «форум», «не прочитано», «участники не подтверждены», «язык не подтверждён», «есть проходные темы».
- Чёрный список — источники, отклонённые пользователем; поиск их больше не возвращает (снимается вручную).
- BanGuard — единый менеджер квот и пауз для всех действий discovery (search/read/join/leave), общий для задач.
4. Задача: конфигурация и правила создания
Поля задачи:
| Поле | Назначение | По умолчанию |
|---|---|---|
name |
название задачи | — |
description |
описание цели (что ищем) | — |
keywords |
поисковые ключи (генерирует ИИ, редактируются перед стартом) | [] |
minSubscribers |
минимум участников (0 = не важно) | 0 |
lang |
язык источников (ru / any) |
ru |
threshold |
доля подходящих сообщений, % | 40 |
sampleSize |
сколько сообщений смотреть при оценке | 10 |
planJoins |
план вступлений N | 1..50 |
autoJoin |
авто-вступление подходящих | false |
status |
`draft → running → paused → done | failed` |
Правила создания:
- Бюджет планов: сумма
planJoinsвсех задач в статусе неdone/failed+planJoinsновой ≤ суточного лимита вступлений (по умолчанию 50). Задача с планом 50 не даёт создать другую; план 25 оставляет максимум 25. - Запуск возможен только после генерации/подтверждения ключей.
- При редактировании активной задачи план нельзя увеличить сверх свободного бюджета.
5. Пайплайн поиска (каскад фильтров)
Выполняется фоновым воркером задачи строго через BanGuard (по одному действию, с паузами).
Для каждого кандидата фильтры идут по нарастающей стоимости; при первом «нет» источник пропускается и берётся следующий:
- Поиск —
contacts.searchпо каждому ключу (с паузами). Кандидаты дедуплицируются поdialog_id/username. - «Мы не состоим» — глобальный фильтр, применяется сразу и безусловно: как только
источник найден (независимо от запроса/ключей), он отбрасывается, если уже есть
в
dialogs(вступили/мониторим), в чёрном списке или уже обрабатывается/вступил/ждёт рассмотрения в другой задаче (глобальная дедупликация кандидатов). Остальные фильтры (участники/язык/контент) применяются уже после этого. Проверка повторяется непосредственно перед вступлением (между оценкой и join'ом кандидат мог быть добавлен вручную). - Число участников — если
minSubscribersзадано:- значение получено и меньше минимума → пропуск;
- значение получить не удалось → не пропускаем, ставим метку «участники не подтверждены».
- Язык — если
lang=ru: по выборке сообщений эвристикой кириллицы (без ИИ); не удалось прочитать → метка «язык не подтверждён» (не пропуск). - Содержимое — оценка выборки сообщений (см. §6).
Пометки «не подтверждено» — не ошибка, а сигнал человеку на экране рассмотрения.
6. Оценка содержимого (по темам, для форумов)
- Что считается «подходящим сообщением»: сообщение проходит те же правила, что в основной системе (этап 1 → ML → ИИ), но профиль оценки = профиль задачи (описание + ключи задачи), а не глобальные настройки дашборда. Оценка ничего не создаёт: ни карточек, ни очереди, ни обучения ML.
- Если ИИ выключен — оценка локальным разбором/ML.
- Каналы: читаем до
sampleSizeпоследних сообщений; доля подходящих ≥threshold→ в «на рассмотрение». - Открытые группы: то же; чтение не удалось → «на рассмотрение» с меткой «открытая группа, не прочитана».
- Закрытые группы (нашлись по ключам, история скрыта): сразу «на рассмотрение» с меткой «закрытая группа/канал» (+ метки неподтверждённых фильтров). Пользователь вступает сам.
- Форумы (группы с темами): группа раскладывается по темам (
reply_to_top_id): читаем выборку по активным темам, оценка считается по темам («тема: подходит X из N»). Группа подходящая, если есть ≥1 проходная тема. В превью — список тем с пометками проходная/нет. Имена тем, если API не отдаёт без членства, подставляем сниппетом первого сообщения темы. - Порог «40%» применяется к сообщениям темы/канала; если в выборке меньше 3 содержательных сообщений — кандидат идёт «на рассмотрение» с меткой «мало сообщений».
7. «На рассмотрение» и действия человека
Экран по задаче содержит списки: В обработке / На рассмотрении / Вступили / Отклонены, плюс история.
Кандидат на рассмотрении показывает: тип (канал/группа/форум), число участников, метки, долю «подходит X из N» и почему подошло (перечень подходящих сообщений/тем с причинами — как блок «попала по фильтру» в карточках), превью сообщений (для форумов — по темам).
Действия:
- «Вступить и мониторить» —
channels.joinChannel(по username), добавление вdialogsсmonitor=1, backfill последних ~10 сообщений. Ручной клик — вне квот. После вступления источник автоматически попадает под правило «мы состоим» и из поиска исключается. - «Отклонить» — источник в чёрный список (исключается из поиска во всех задачах).
Если для оценки пришлось вступать — выходим (
channels.leaveChannel) в рамках квот. Чёрный список редактируется вручную (можно снять). - Закрытые группы: вместо авто-вступления — кнопка-ссылка
t.me/<username>; система замечает вступление при синхронизации диалогов и предлагает добавить источник в мониторинг (метка «вступили, добавить в мониторинг?»).
8. Авто-вступление, квоты и анти-бан (BanGuard)
- Суточный лимит вступлений — 50 (настройка), общий для всех задач, считаются только автоматические вступления. Ручные — без ограничений.
- Авто-вступление включается на задачу (
autoJoin). Подходящие кандидаты вступают сами. - Интервалы между автоматическими вступлениями: случайно 50–70 секунд; по одному действию, без параллелей. Поиск и чтение — мягкие паузы (единицы секунд + джиттер, переиспользуем значения анти-бана из telegram.py).
- Задача «выполнена» при достижении плана вступлений. Если за сутки упёрлись в общий бюджет — авто-режим продолжает на следующий день (новый суточный бюджет).
FloodWaitError→ пауза по секундам из ответа + запас; авто-вступления останавливаются до следующего дня при флуде. Общий «стоп-кран» — пауза всего discovery.- Все квоты/интервалы — настройки в UI.
9. Хранилище
| Таблица | Назначение / ключевые поля |
|---|---|
disc_tasks |
задачи: name, description, keywords(JSON), min_subscribers, lang, threshold, sample_size, plan_joins, auto_join, status, counters (found/evaluated/joined/rejected), created/updated |
disc_candidates |
dialog_id/username/name/kind(channel |
disc_blacklist |
dialog_id, name, reason, created_at |
disc_log |
история задачи: task_id, event(search/evaluate/join/leave/flood/error/review/blacklist), text, created_at |
Дубли кандидатов не создаются; источник, попавший в другую задачу или dialogs,
из поиска исключается (правило «мы состоим» — глобальное).
Реализация: у кандидата нет транзитного статуса evaluated (счётчик оценённых — на
задаче); join_failures — неудачные авто-вступления подряд, после 3 кандидат удаляется.
10. API
GET/POST/PATCH/DELETE /api/discovery/tasks(создание с валидацией бюджета планов),POST /api/discovery/tasks/{id}/start|pausePOST /api/discovery/tasks/{id}/generate-keywords— ИИ генерирует ключи по описаниюGET /api/discovery/tasks/{id}/candidates?status=review|joined|rejectedPOST /api/discovery/candidates/{id}/join(вступить и мониторить),.../rejectGET /api/discovery/blacklist,DELETE /api/discovery/blacklist/{dialog_id}GET /api/discovery/tasks/{id}/log
11. UI
Подвкладка «Поиск» на экране «Каналы»:
- список задач (статус, прогресс, план/вступили, авто-режим) + «Новая задача»;
- мастер задачи: описание → «Сгенерировать ключи ИИ» → редактирование ключей → фильтры/план/авто-режим → запуск;
- по задаче: статус-лента (поиск → оценка → вступление), вкладки «В обработке / На рассмотрении / Вступили / Отклонены», история;
- кандидат на рассмотрении раскрывается с превью и действиями; форум — по темам;
- настройки квот (лимит/интервалы) — в том же экране или «Настройки → Telegram».
12. Интеграция с существующим кодом
- Фоновый воркер discovery — отдельный цикл в
main.py(как_pipeline_loop), сервисapp/services/discovery.py, Telegram-действия — методыTelegramManager(поиск/join/leave/чтение) с общим pacing. - Переиспользуем:
rules/stage1/ML/AIдля оценки сообщений (новый лёгкий вызов с профилем задачи, без записи карточек), списокdialogsдля фильтра «мы состоим», синхронизацию диалогов для авто-добавления закрытых групп. - К основному пайплайну карточек, ML и ТЗ-логике не прикасаемся.
13. Вне рамок (сейчас)
- Агрегаторы-каталоги как источник кандидатов.
- «Похожие каналы» (
getChannelRecommendations) от наших подписок — отдельная опция позже. - Авто-вступление в закрытые группы по инвайт-ссылкам (глобальным поиском они не находятся).
14. Значения по умолчанию (настраиваются в UI)
суточный лимит вступлений = 50; интервал авто-вступлений = 50–70 с; выборка = 10 сообщений; порог = 40%; мин. содержательных сообщений для оценки = 3; язык = ru.