Files
ridebus-admin/docs/telegram-cancellation-import.md
Zakhar Timoshenko 666a0b268f feat: implement stop alias management for Telegram import
- Added TelegramStopAliasService for handling stop aliases in the database.
- Created API endpoints for managing stop aliases (list, create, update, delete).
- Introduced TelegramStopAliases component in the frontend for user interaction with stop aliases.
- Updated TelegramImportService to utilize stop aliases for message processing.
- Enhanced telegramCancellationParser to resolve stop names using aliases.
- Added migration script to create telegram_stop_aliases table and seed initial data.
- Updated documentation to include details about stop alias functionality.
2026-10-03 01:31:11 +03:00

17 KiB
Raw Blame History

Отмены рейсов из @avp2Polotsk

Первый этап читает публичный канал через отдельный существующий Telegram-аккаунт и MTProto-клиент teleproto. Права управления каналом и Bot API не нужны. Получение выполняется раз в минуту; просматриваются публикации за текущий и два предыдущих дня, включая правки. Вложения не скачиваются. Расписания рейсов не изменяются: импорт создаёт предупреждения существующего типа cancellation.

Распознавание и срок действия

Формат «Не будут выполняться рейсы: ПО МАРШРУТУ № 27 С ОСТАНОВКИ АВ/ВОКЗАЛ НА 15.43,16.43. С ОСТАНОВКИ ЗОДЧЕГО НА 15.58,16.58.» разбирается в четыре конкретных отправления. АВ/ВОКЗАЛ означает Автовокзал, точки в часах заменяются двоеточием. Отмена всего маршрута из такого объявления не выводится.

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

Явная дата, «сегодня» или «завтра» определяют день отмены. Если дата отсутствует, используется день оригинальной публикации по Europe/Minsk, а не день импорта или правки. Дата без года выбирается ближайшей к публикации. Конец действия — 00:00 следующего дня по Минску, граница исключительная. Например, для 02.10.2026 это 2026-10-02T21:00:00Z. Будущие отмены активируются в начале указанного дня и получают push тогда же. Просроченные объявления сохраняются как завершённые без push.

Новые однозначные отмены публикуются автоматически. TELEGRAM_AUTO_PUBLISH=false переводит все распознанные отмены на проверку. В админке на странице «Уведомления» → «Отмены из Telegram» доступны состояние подключения, ручная проверка канала, исходные сообщения, ссылки на оригинал и история. При ручной публикации оператор проверяет маршрут, текст и день отмены; срок до полуночи назначается сервером.

Словарь остановок и текст уведомления

В блоке «Отмены из Telegram» → «Словарь названий остановок» можно добавлять, изменять и удалять соответствия: название из канала → конкретная остановка из каталога выбранного города. Например, ЗОДЧЕГО → Зодчего Иоанна. Для разных сокращений одной остановки создайте несколько записей. Регистр, пробелы, знаки препинания и различие ё/е не учитываются; два эквивалентных сокращения в одном городе запрещены. Если есть несколько платформ с одним названием, выберите нужную остановку по адресу/ID. Отключённая остановка требует исправления соответствия и ручной проверки.

Миграция 014 добавляет словарь и начальные соответствия АВ/ВОКЗАЛ → Автовокзал, ЗОДЧЕГО → Зодчего Иоанна только для Полоцка и только при единственной действующей остановке с таким названием. При неоднозначности соответствие задаёт администратор. Уже сохранённые соответствия при повторном запуске миграции не перезаписываются.

Изменения словаря учитываются при следующем чтении канала, включая сообщения на проверке с неизменившимся исходным текстом в трёхдневном окне. Уже опубликованные или отклонённые сообщения повторно из-за изменения словаря не публикуются. Для распознанных отмен backend формирует лаконичный текст без приветствия и подписи перевозчика. Например:

Отмена отдельных рейсов №27

02.10.2026: не выполняются рейсы:
Автовокзал — 15:43, 16:43
Зодчего Иоанна — 15:58, 16:58
Источник: https://t.me/avp2Polotsk/1166

При нескольких направлениях формируются отдельные уведомления с соответствующими отправлениями. Исходный текст остаётся в истории импорта. В ручной проверке распознанный текст также подставляется в удобочитаемом виде; выбор направления подставляет его отправления, пока оператор не изменил текст самостоятельно. Если сообщение не удалось разобрать, текст для ручной правки остаётся исходным.

Установка

Для существующей базы применить миграцию 013 один раз, до запуска новой версии backend. Она также переводит UTC-значения времени существующих алертов из timestamp в timestamptz. Это соответствует принятому формату хранения UTC в админке. При наличии вручную внесённых локальных времен сначала проверить их и сделать резервную копию базы.

docker compose exec -T postgres psql -U transit_admin -d polotsk_transit \
  -f /docker-entrypoint-initdb.d/013_telegram_alert_import.sql

docker compose exec -T postgres \
  psql -v ON_ERROR_STOP=1 --single-transaction -U transit_admin -d polotsk_transit \
  < backend/migrations/014_telegram_stop_aliases.sql

Если миграция 013 уже установлена, для обновления словаря достаточно миграции 014 и пересборки backend/frontend. На чистой базе Docker применяет обе миграции автоматически. Не запускайте npm run migrate на существующей базе: старые init-миграции проекта не поддерживают повторный запуск.

Авторизация аккаунта

  1. Создать API-приложение на my.telegram.org и получить api_id и api_hash.

  2. На компьютере/сервере оператора создать закрытый каталог вне checkout. Пример для локального запуска:

    mkdir -p "$HOME/.config/polotsk-transit-telegram"
    chmod 700 "$HOME/.config/polotsk-transit-telegram"
    
  3. В backend/.env указать:

    TELEGRAM_IMPORT_ENABLED=false
    TELEGRAM_API_ID=your_api_id
    TELEGRAM_API_HASH=your_api_hash
    TELEGRAM_SESSION_FILE=/absolute/private/directory/account.session
    TELEGRAM_CHANNEL=avp2Polotsk
    TELEGRAM_CITY_SLUG=polotsk
    TELEGRAM_AUTO_PUBLISH=true
    TELEGRAM_POLL_INTERVAL_MS=60000
    
  4. Из каталога backend выполнить npm ci, затем npm run telegram:login. Номер, код и пароль вводятся оператором локально. Код и пароль скрыты; session string не печатается. Утилита сохраняет файл с правами 600 и работает только с существующим пользовательским аккаунтом. Если Telegram требует регистрацию/дополнительный неподдерживаемый шаг, завершить его в официальном клиенте и повторить авторизацию.

  5. Включить TELEGRAM_IMPORT_ENABLED=true и перезапустить backend. Для push также должны быть настроены Firebase и FCM_ENABLED=true.

Файл сессии даёт доступ к аккаунту. Его нельзя коммитить, передавать frontend или присылать в чат. Backend не выполняет интерактивную авторизацию; отсутствующая/отозванная сессия отображается как ошибка подключения. В статус и логи не попадают API hash, сессия, телефон, коды или сырые ошибки клиента.

Docker / Proxmox

Авторизацию выполнить на доверенном хосте. Поместить созданный account.session в закрытый каталог на Docker-хосте, например /opt/polotsk-transit/telegram-secrets, с правами 600. В корневом .env настроить:

TELEGRAM_SECRETS_DIR=/opt/polotsk-transit/telegram-secrets
TELEGRAM_SESSION_FILE=/run/telegram-secrets/account.session
TELEGRAM_API_ID=your_api_id
TELEGRAM_API_HASH=your_api_hash
TELEGRAM_IMPORT_ENABLED=true
TELEGRAM_AUTO_PUBLISH=true

Compose монтирует каталог только для чтения, отдельно от Firebase. После миграции выполнить docker compose up -d --build backend frontend.

Повторы, правки и доставка

Идентичность публикации: город + числовой ID канала + ID сообщения. Снимок текста, результат распознавания и связи с алертами сохраняются одной транзакцией; повторный импорт и одновременные читатели не создают второй алерт. Более старая правка не заменяет свежую. Администратор публикует только проверенную версию по content_hash; изменившийся текст даёт 409 и требует нового просмотра.

Правка публикации обновляет связанные алерты. Если новая версия неоднозначна или больше не распознаётся как отмена, прежние алерты выключаются и сообщение возвращается на проверку. Отклонение сохраняется при последующих правках. Удалённый администратором алерт не воссоздаётся при правке исходного сообщения. Автоудаление сообщения в Telegram не отменяет предупреждение раньше полуночи; удаление не трактуется как восстановление движения.

Импорт помечает алерты как notification_pending в той же транзакции. Существующий scheduler отправляет их после начала действия, использует блокировки и до пяти попыток; Firebase отключён — ожидание сохраняется. Уже доставленный push при правке того же алерта не отправляется повторно. Остаётся обычное окно между приёмом FCM и записью результата в БД: при аварии в этот момент возможен повтор доставки. alertId позволяет клиенту показывать один экземпляр уведомления.

FCM TTL ограничен остатком срока действия алерта; истёкший алерт не отправляется. Backend передаёт абсолютный endTime. Удаление уже показанного уведомления из системной шторки требует поддержки Android-клиента: проверить endTime при получении, не показывать просроченный push, поставить setTimeoutAfter(endTime-now) и исключать истёкшие алерты из локального списка. Изменения Android-клиента не входят в этот этап backend/админки. Правки и отключение алерта не отзывают уже показанный push.

При FloodWait worker выдерживает паузу, не запускает параллельные циклы и отображает время следующей попытки. За один цикл лимит составляет 1000 публикаций; превышение отображается как ошибка, а не как успешное чтение всей истории. Сообщения, исчезнувшие из Telegram во время длительного простоя сервера, восстановить нельзя.

API и проверка

Все endpoint требуют JWT администратора и учитывают X-City-Id:

  • GET /api/v1/telegram-import — безопасный статус и до 100 сообщений, сначала требующие проверки.
  • POST /api/v1/telegram-import/sync — чтение настроенного канала; запрещено в другом городе.
  • POST /api/v1/telegram-import/:id/review — content_hash, action: publish|dismiss; для публикации также service_date, route_id, title, message.
  • GET /api/v1/telegram-import/stop-aliases — словарь выбранного города.
  • POST /api/v1/telegram-import/stop-aliases — alias, stop_id; добавить соответствие.
  • PUT /api/v1/telegram-import/stop-aliases/:id — alias, stop_id; изменить соответствие.
  • DELETE /api/v1/telegram-import/stop-aliases/:id — удалить соответствие.

Обычные unit-тесты: cd backend && npm test. SQL-тесты используют временный PostgreSQL runtime PGlite и не обращаются к рабочей базе:

npm install --prefix /tmp/polotsk-telegram-test-runtime @electric-sql/pglite --ignore-scripts
TELEGRAM_TEST_PGLITE_MODULE=/tmp/polotsk-telegram-test-runtime/node_modules/@electric-sql/pglite \
  node --test test/telegramImport.integration.test.js

Реальное чтение Telegram и доставка на устройство проверяются после локальной авторизации аккаунта, применения миграции и включения Firebase.