Фаза 1 — ядро прокси:
- src/telegram/proxy.ts: агент из TELEGRAM_PROXY (socks5/http/https), редакция
кред в логах, override в ./data (перекрывает env), singleton на старте.
- app.ts: агент в Telegraf; upload.ts: то же для скачивания файлов с
api.telegram.org/file (единственная точка мимо Telegraf). MAX всегда напрямую.
- setup.sh: вопрос про прокси до проверки связи; проверка Telegram, getUpdates и
setChatPhoto идут через прокси; TELEGRAM_PROXY пишется в .env.
Фаза 2 — веб-панель:
- Эндпоинты GET/POST /api/proxy, /api/proxy/test, /api/system/restart.
- Карточка «Telegram-прокси» в Configuration: тест вживую + сохранить и
перезапустить (persist в ./data, применение self-restart'ом).
Фаза 3 — ошибки моста в ТГ, без спама:
- src/bridge/errorReporter.ts: троттлинг (10 мин на ключ + 6/час потолок).
- Хуки: потеря/восстановление связи с MAX (дебаунс 60с), сбой доставки в
Telegram, unhandled/uncaught (общий текст, без утечки токенов).
MAX отвергал реакцию ❤ (error.message.like.unknown.like), а 👍 принимал:
Telegram Bot API отдаёт часть реакций в text-форме без U+FE0F (❤ = U+2764),
а MAX хранит emoji-форму (❤️ = U+2764 U+FE0F). Это не разные наборы, а разное
представление одного эмодзи — чинится симметричной нормализацией selector'а,
а не таблицей соответствия.
Новый модуль max/reactions.ts: toMaxReaction (TG→MAX, добавляет FE0F нужным
базовым символам, включая внутри ZWJ-последовательностей вроде ❤🔥/🤷♂️) и
toTelegramReaction (MAX→TG, убирает FE0F — setMessageReaction хочет bare-форму).
Применено в addReaction, в релее реакций MAX→TG и в re-affirm зонда. Список
проблемных code points — из набора реакций Telegram (Emoji_Presentation=No).
Покрыто тестами (round-trip TG→MAX→TG).
Живой тест на боевом вскрыл: удалённое сообщение ПОЛЬЗОВАТЕЛЯ Telegram
отвечает на пустой setMessageReaction кодом MESSAGE_ID_INVALID, а не
"message to react not found" (та строка — от удалённого сообщения бота,
на которой строился первый эксперимент). Классификатор её не знал →
возвращал unknown → зонд не удалял. Живое сообщение по-прежнему даёт
REACTION_EMPTY, так что механика верна, не хватало только этого кода.
Безопасно как gone: пингуем только свой сохранённый id, живой даёт
REACTION_EMPTY, rate-limit даёт 429.
Bot API не сообщает боту об удалении сообщения, поэтому свайп-делит в теме
Telegram раньше не доходил до MAX. Два новых пути (в дополнение к /delete):
- 👎 на СВОём сообщении = удалить с обеих сторон (forAll). 🗑/❌ Telegram как
реакции не принимает (REACTION_INVALID, проверено живьём), из тройки реально
ставится только 👎. На чужом входящем 👎 остаётся обычной реакцией.
- Зонд: периодически «прощупывает» наши сообщения невидимым setMessageReaction
и по тексту ошибки отличает живое (REACTION_EMPTY) от удалённого (message to
react not found). Пропавшее зеркалим в MAX forAll. Лесенка затухания
(probeIntervalMs) кладёт усилие туда, где ~90% удалений — сразу после
отправки: 15с первые 2 мин → минута → 5 мин → 30 мин → стоп через 6ч.
Только свои (outgoing) сообщения: MAX→TG удаления и так приходят нативным
REMOVED-пушем, а удалять чужое в MAX для всех не наше дело. Флаг направления
добавлен в MessageLink.
Зонд не конфликтует с пробросом реакций: вместо слепой пустышки он
переподтверждает текущую релеированную реакцию из lastRelayedReaction
(идемпотентно) — иначе пустой setMessageReaction стёр бы MAX-реакцию,
поставленную на наше сообщение.
Классификатор ошибки зонда возвращает "удалено" ТОЛЬКО по точной строке
not-found — 429/сеть/прочее → "не знаю, позже", т.к. ложное срабатывание =
необратимое удаление в MAX. Покрыто тестами (probe.test.ts).
- redactSecrets: маскируются password/trackId/phone и любые строки от 512
символов — пароль MAX больше не попадает в лог веб-панели открытым текстом
- Веб-панель по умолчанию работает по HTTPS с самоподписанным сертификатом
(генерируется при первом старте в .data/tls); старые http-ссылки получают
301-редирект на том же порту (polyglot по первому байту; resume строго в
process.nextTick — иначе TLS-хендшейк зависает); /apikey даёт https-ссылку;
PANEL_TLS=off для установки за своим reverse proxy
- Сертификаты Минцифры больше не доверяются всему контейнеру через
NODE_EXTRA_CA_CERTS — src/max/ca.ts скоупит их только на соединения с MAX
(ca в tls.connect + undici-агент для CDN-загрузок); Telegram и GitHub
проверяются только по стандартным корням Mozilla
- Запросы одного опкода к MAX сериализуются (request() в client.ts) — два
конкурентных MSG_SEND больше не перепутают messageId-связки редактирования
и удаления
- Починена гонка первого чтения в ChatMapStore: конкурентные обращения
форкали кэш, и часть upsert'ов молча терялась на диске
- Graceful shutdown по SIGTERM/SIGINT; в панели — реконнект WebSocket и
периодическое обновление метрик; сравнение id в patchCachedChatLastMessage
переведено на String() (BigInt-чаты не совпадали)
- Удалён мёртвый код: эндпоинты /api/debug/*, getPollVoters,
createTelegramBot, цепочка historySynced, getChats(marker)
- React/Tailwind/lucide перенесены в devDependencies — рантайм-образ легче;
vitest 4, npm audit: 0 уязвимостей; chmod 600 для .env в setup.sh/update.sh
- Новые тесты: redactSecrets, сторы (эвикшн/нормализация/конкурентность),
сериализация запросов — всего 41
Two-way message/media bridge between MAX and Telegram forum topics, with
group management, polls, calls, forwarding, and admin commands (/help,
/reboot, /kill, /donate) from Telegram. Includes one-command install
(install.sh) and interactive first-run setup (setup.sh) for a bare Ubuntu
server.