feat(tradein/tgbot): Telegram support-мост @MERAsupport_bot #2526
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2526
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-tg-support-bridge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что это
Клиент пишет
@MERAsupport_botв личку → воркер зеркалит сообщение в топик супергруппы-форума → оператор отвечает реплаем на зеркало → бот доставляет ответ клиенту. Текст и медиа (фото/файлы) в обе стороны. Полный лог переписки в Postgres.Целевой топик:
t.me/c/3742528982/4414(группа «Идейные и бедные», форум). Бот в группе уже состоит.Решения
Отдельный контейнер на long-polling, а не webhook в
tradein-backend. Webhook потребовал бы дырку в auth-middleware (_PUBLIC_PATHS, тот самый, что закрывали в #2213) и маршрут в Caddy. Так — нулевая внешняя поверхность, падение бота не задевает API. Паттерн тот же, что уscheduler_main.py/scraper.Без
aiogram—httpxуже в зависимостях, из Bot API нужны толькоgetUpdates/copyMessage/sendMessage.Модуль в этой репе, не отдельная. Бот живёт в той же БД и едет тем же деплой-трактом; отдельная репа дала бы распределённую связность без выигрыша. Изоляция достигается контейнером.
Маршрутизация — по
topic_message_id.message_idв Telegram уникален в пределах чата сквозь все топики, а все зеркала лежат в одном support-чате → спутать адресата нельзя (подпёрто частичным UNIQUE-индексом). Реплай на шапку или на ответ другого оператора не резолвится → тихий игнор, обычная болтовня в топике бота не трогает.Найдено ревью и починено (воспроизведено эмпирически, не по догадке)
sanitize_urlего не режет. Уезжал через locals стек-фреймов (include_local_variablesпо умолчаниюTrue) и через span dataHttpxIntegrationinclude_local_variables=False+ regex-редактор вbefore_send(обе формы:/bot<id>:<secret>и голая<id>:<secret>), поверх существующего PII-scrubdocker logsкаждые 30с. httpx-логгер печатает полный URL на INFO, воркер поллит бесконечноexit(0)при пустом токене +restart: unless-stopped(рестартует независимо от кода)unless-stoppedоставлен сознательно: только он даёт автозапуск после ребута VPS, а уon-failureкод выхода — гонка с long-pollstop_grace_periodпотеряли при копировании паттерна уscraper→ дефолтные 10сstop_grace_period: 120sSQLAlchemyErrorловится отдельно,rollback()перед сдвигом offset152-ФЗ
Переписка — ПДн.
ON DELETE CASCADEпоchat_id: удаление клиента = одинDELETE. Таблицы прокомментированы с пометкой. Политика ретенции — follow-up, на MVP не блокирует.Тесты
51 passed— маршрутизация обоих направлений, дедуп поupdate_id,403 → is_blocked, throttle-окно шапки, redaction токена во всех формах event (frame locals, span data, breadcrumb, голая форма) + отсутствие ложных срабатываний наchat_id:12345и подобном. Сеть не трогается (MockTransport).Деплой — требуется ручной шаг
Бот не включится сам:
TELEGRAM_BOT_TOKEN/TELEGRAM_SUPPORT_CHAT_ID/TELEGRAM_SUPPORT_TOPIC_IDзадаются в runtime-env на VPS (в git не хранится). Без них воркер штатно висит в idle — это норма, не сбой. Порядок описан вDEPLOY.md.⚠️ Токен для этого бота светился в переписке — на VPS класть перевыпущенный через @BotFather.
Follow-ups (не в этом PR)
copyMessageи commit (сейчас логируется WARNING)scheduler_main.py— тот же паттернinclude_local_variables=Trueс секретами в URL (proxy creds, cian login)