|
Some checks failed
Build / build (push) Has been cancelled
Album items reach the media-group buffer with the sender attribution already applied by telegram.go, so item.caption is non-empty even for parts that carry no caption. formatTgMediaGroupCaption selected the caption by that field, which had two consequences: - a caption attached to a later photo was ignored (the first part always won); - the attribution was applied a second time, so MAX rendered "Name: Name:" with the actual caption gone. Take the caption from the raw Telegram message instead: the entity offsets refer to it, not to the attributed string. Crossposts keep using item.caption, which is prepared HTML without author attribution. The existing test passed the raw caption in item.caption, a shape production never produces, which is why the bug went unnoticed; the fixture now builds items the way telegram.go does. |
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| migrations | ||
| scripts | ||
| .env.example | ||
| .gitignore | ||
| addon.go | ||
| addon_entitlements_test.go | ||
| addon_errs_test.go | ||
| addon_local_billing_test.go | ||
| addon_stub.go | ||
| addressbook.go | ||
| addressbook_test.go | ||
| admin.go | ||
| admin_test.go | ||
| billing_entitlements_addon.go | ||
| botchats.go | ||
| breaker_pause_test.go | ||
| bridge.go | ||
| bridge_direction.go | ||
| crosspost_access.go | ||
| crosspost_access_test.go | ||
| crosspost_funnel_test.go | ||
| crosspost_status.go | ||
| crosspost_status_test.go | ||
| discussion_relay_test.go | ||
| docker-compose.yml | ||
| Dockerfile | ||
| doctor.go | ||
| doctor_test.go | ||
| dualbot.go | ||
| format.go | ||
| format_test.go | ||
| funnel_test.go | ||
| go.mod | ||
| go.sum | ||
| help.go | ||
| helpmenu.go | ||
| helpmenu_test.go | ||
| LICENSE | ||
| main.go | ||
| main_test.go | ||
| Makefile | ||
| markup.go | ||
| markup_test.go | ||
| max.go | ||
| max_callback_test.go | ||
| max_caption_test.go | ||
| max_comment_probe.go | ||
| max_comment_probe_test.go | ||
| max_delete_suppress_test.go | ||
| max_format_send_test.go | ||
| max_share_test.go | ||
| max_video_queue_test.go | ||
| maxban.go | ||
| mediagroup.go | ||
| mediagroup_caption_test.go | ||
| mediagroup_document_test.go | ||
| mediagroup_manual_test.go | ||
| message_origin_test.go | ||
| migrate.go | ||
| pair_rank_test.go | ||
| postgres.go | ||
| queue.go | ||
| queue_fairness_test.go | ||
| README.md | ||
| replacements.go | ||
| replacements_entities_test.go | ||
| repository.go | ||
| sqlite.go | ||
| telegram.go | ||
| telegram_crosspost_edit_test.go | ||
| tg_document.go | ||
| tg_document_test.go | ||
| tg_media_edit.go | ||
| tgsender.go | ||
| tgsender_backpressure_test.go | ||
| tgsender_impl.go | ||
| tgsender_impl_test.go | ||
| tgsender_rich.go | ||
| tgsender_welcome_ephemeral.go | ||
| upload.go | ||
MaxTelegramBridgeBot
Мост между Telegram и MAX мессенджером. Пересылает сообщения, медиа, файлы и редактирования между связанными чатами.
Сайт: maxtelegrambridge.ru
Возможности
- Пересылка текстовых сообщений в обе стороны
- Пересылка медиа: фото, видео, GIF, стикеры, документы, голосовые, аудио, кружки
- Поддержка ответов (reply) — сохраняется контекст
- Отслеживание редактирования сообщений. Если при редактировании добавлено медиа — отправляется как новое сообщение (MAX API не поддерживает редактирование вложений)
- Удаление сообщений (MAX→TG). TG→MAX удаление невозможно — Telegram Bot API не отправляет событие удаления
- Retry-очередь — при недоступности API сообщения сохраняются в БД и доставляются позже
- Поддержка локального Telegram Bot API сервера (
TG_API_URL) - Поддержка форумов (топиков) в TG-группах — сообщения из MAX приходят в нужный топик
- Команда
/thread— выбрать топик по умолчанию для сообщений из MAX - Команда
/thread_bridge— связать отдельный TG-тред с отдельным MAX-чатом (форум ↔ несколько MAX-групп) - Reply из MAX автоматически попадает в тред исходного сообщения (а не в дефолтный)
- Автосброс топика при отключении форума в группе
- Настраиваемый префикс
[TG]/[MAX] - Кросспостинг каналов с выбором направления (
tg>max,max>tg,both) - Сохранение форматирования при кросспостинге (жирный, курсив, код, ссылки, зачёркнутый, подчёркнутый)
- Управление кросспостингом через inline-кнопки
- SQLite или PostgreSQL для хранения связок и маппинга сообщений
Форматирование при кросспостинге
| Формат | TG → MAX | MAX → TG |
|---|---|---|
| Жирный | ✅ | ✅ |
| Курсив | ✅ | ✅ |
| Моноширинный | ✅ | ✅ |
| ✅ | ✅ | |
| Подчёркнутый | ✅ | ✅ |
| Ссылки | ✅ | ✅ |
| Цитата | ❌ | ❌ |
| Спойлер | ❌ | — |
Цитаты и спойлеры не поддерживаются MAX Bot API.
Установка
Из бинаря
Скачайте бинарь со страницы релизов и запустите:
chmod +x max-telegram-bridge-bot
./max-telegram-bridge-bot
Docker
docker run -e TG_TOKEN=your_token -e MAX_TOKEN=your_token ghcr.io/bearlogin/max-telegram-bridge-bot:latest
Docker Compose (с PostgreSQL)
cp .env.example .env
# Заполните TG_TOKEN и MAX_TOKEN в .env
docker compose up -d
PostgreSQL настраивается через .env:
POSTGRES_USER=bridge
POSTGRES_PASSWORD=bridge
POSTGRES_DB=bridge
Из исходников
git clone https://github.com/BEARlogin/max-telegram-bridge-bot.git
cd max-telegram-bridge-bot
go build -o max-telegram-bridge-bot .
./max-telegram-bridge-bot
Быстрый старт
1. Создайте ботов
- Telegram: через @BotFather, отключите Privacy Mode (Bot Settings → Group Privacy → Turn off)
- MAX: через business.max.ru
2. Настройте и запустите
Передайте токены через переменные окружения:
TG_TOKEN=your_token MAX_TOKEN=your_token ./max-telegram-bridge-bot
Или через export:
export TG_TOKEN=your_token
export MAX_TOKEN=your_token
./max-telegram-bridge-bot
3. Свяжите чаты
- Добавьте бота в Telegram-группу и MAX-группу
- В MAX сделайте бота админом группы
- В Telegram сделайте бота админом, если группа — супергруппа с темами (форум). В обычных группах админство не требуется (нужно только отключить Bot Privacy Mode через @BotFather, либо сделать бота админом)
- В одном из чатов отправьте
/bridge - Бот выдаст ключ — отправьте
/bridge <ключ>в другом чате (в группе, не в ЛС бота)
4. Кросспостинг каналов
Настройка через личные сообщения с ботами (ничего не публикуется в каналах):
- Добавьте бота как админа в TG-канал и MAX-канал
- Перешлите любой пост из TG-канала в личку TG-бота → бот покажет ID канала
- В личке MAX-бота напишите
/crosspost <TG_ID> - Перешлите любой пост из MAX-канала в личку MAX-бота → кросспостинг настроен!
По умолчанию посты идут в обе стороны. Управление:
/crosspost(в личке любого бота) — список всех связок с кнопками- Перешлите пост из связанного канала в личку бота → появятся кнопки управления (направление, удаление)
Команды
Группы (bridge)
| Команда | Описание |
|---|---|
/start, /help |
Инструкция |
/bridge |
Создать ключ для связки |
/bridge <ключ> |
Связать чат по ключу |
/bridge prefix on/off |
Включить/выключить префикс [TG]/[MAX] |
/unbridge |
Удалить связку |
/thread |
Направить сообщения из MAX в текущий топик (форум-группы) |
Форум-тред ↔ отдельная MAX-группа (thread-bridge)
В MAX нет тредов, но можно связать отдельные MAX-группы с конкретными тредами TG-форума — каждый тред получает своё зеркало.
| Команда | Где | Описание |
|---|---|---|
/thread_bridge |
TG, внутри треда | Создать ключ для связки этого треда (нужны права админа, не работает в General) |
/thread_bridge <ключ> |
MAX, отдельная группа | Связать MAX-чат с TG-тредом по ключу |
/thread_unbridge |
TG (в треде) или MAX | Удалить связку треда |
Один MAX-чат может быть зеркалом максимум одного треда и не может одновременно участвовать в /bridge. В одной форум-группе может быть несколько /thread_bridge (по одной на тред) параллельно с обычным /bridge для General.
Каналы (crosspost) — через личку бота
| Команда | Где | Описание |
|---|---|---|
/crosspost |
TG или MAX личка | Список всех связок с кнопками управления |
/crosspost <TG_ID> |
MAX личка | Начать настройку (затем переслать пост из MAX-канала) |
| Переслать пост из канала | TG или MAX личка | Показать ID (если не связан) или кнопки управления |
Кнопки управления позволяют менять направление (TG→MAX, MAX→TG, оба) и удалять связку.
5. Автозамены в кросспостинге
Автоматическая замена текста при пересылке постов. Удобно для UTM-меток, ссылок и любых строк.
/crosspost→ нажмите 🔄 Замены на нужной связке- Выберите направление: + TG→MAX или + MAX→TG
- Выберите тип: Весь текст или Только ссылки
- Отправьте правило:
from | to
Для регулярных выражений: /regex/ | replacement
Пример: utm_source=tg | utm_source=max — при пересылке из TG в MAX все вхождения utm_source=tg заменятся на utm_source=max.
Переменные окружения
| Переменная | Описание | По умолчанию |
|---|---|---|
TG_TOKEN |
Токен Telegram бота | — (обязательно) |
MAX_TOKEN |
Токен MAX бота | — (обязательно) |
DB_PATH |
Путь к SQLite базе | bridge.db |
DATABASE_URL |
DSN для PostgreSQL (если задана — SQLite игнорируется) | — |
TG_BOT_URL |
Ссылка на TG-бота (показывается в /help) |
https://t.me/MaxTelegramBridgeBot |
MAX_BOT_URL |
Ссылка на основного MAX-бота (показывается в /help) |
https://max.ru/id710708943262_bot |
MAX_BOT_URL_RESERVE |
Ссылка на запасного MAX-бота (failover; пусто — скрыть подсказку) | https://max.ru/id710708943262_4_bot |
MAX_WEBHOOK_URL |
Базовый URL для webhook, например https://bridge.example.com (если не задан — long polling). Эндпоинт: /max-webhook |
— |
TG_WEBHOOK_URL |
Базовый URL для webhook, например https://bridge.example.com (если не задан — long polling). Эндпоинты: /tg-webhook |
— |
MAX_WEBHOOK_PORT |
Порт для webhook сервера MAX. Если порт совпадает с Telegram, значит будет общий сервер | 8443 |
TG_WEBHOOK_PORT |
Порт для webhook сервера Telegram . Если порт совпадает с Max, значит будет общий сервер | 8443 |
LOG_LEVEL |
Уровень логирования: debug, info, warn, error |
info |
TG_API_URL |
URL локального Telegram Bot API сервера, например http://localhost:8081. Снимает лимиты на размер файлов |
— |
ALLOWED_USERS |
Белый список Telegram user ID через запятую. Если не задан — доступ открыт для всех | — |
TG_MAX_FILE_SIZE_MB |
Дополнительный лимит файла из Telegram в MAX. Если не задан: документы — до 1 ГиБ, остальные медиа — до 512 МиБ | — |
MAX_MAX_FILE_SIZE_MB |
Максимальный размер файла из Max в Telegram. Рекомендуется 20 МБ (если не используется локальный сервер API), если не задано - без ограничений | — |
MAX_ALLOWED_EXTENSIONS |
Список расширений файлов через запятую, которые разрешены к отправке. Если не задано - без ограничений | — |
MESSAGE_FORMAT |
Формат сообщений. inline (текущий Имя: текст) и newline (Имя:\nтекст) | inline |
Расширения (аддоны)
Бридж умеет подключать опциональные расширения — отдельные модули, которые добавляют свои команды и обработчики, не меняя ядро. В публичной сборке аддоны отсутствуют (всё работает как обычный мост); реализация подключается отдельной сборкой через build-тег.
Как это устроено
-
addon.go— интерфейсAddon, который должно реализовать расширение:type Addon interface { // Запуск фоновой работы (воркеры, регистрация вебхуков и т.п.). Start(ctx context.Context) error // Личное сообщение боту: вернуть true, если расширение его обработало. HandleDMCommand(ctx context.Context, userID, chatID int64, text string) (handled bool) // Нажатие inline-кнопки. msgID — id сообщения с кнопкой (можно удалить). HandleCallback(ctx context.Context, userID, chatID int64, callbackID, data string, msgID int) (handled bool) // Пересланный в личку пост из канала (sourceMsgID — id оригинала в канале). HandleDMForward(ctx context.Context, userID, dmChatID, sourceChatID int64, sourceTitle string, sourceMsgID int) (handled bool) }Аддон получает личные сообщения, форварды и callback'и первым: если он вернул
true, ядро дальше их не обрабатывает. -
addon_stub.go(//go:build !addon) — публичная сборка, расширения выключены:func loadAddon(b *Bridge) Addon { return nil } -
addon_local.go(//go:build addon) — склейка с конкретным расширением. Этот файл не входит в публичный репозиторий (в.gitignore); его пишет тот, кто подключает своё расширение. Здесь:- создаётся экземпляр расширения и пробрасываются нужные операции бриджа (отправка, пересылка, удаление сообщений, резолв связок и т.п.) через узкие колбэки — ядро не знает внутренней логики расширения;
- команды меню расширения добавляются обобщённо:
b.extraCommands = append(b.extraCommands, BotCommand{...})— ядро лишь покажет их вsetMyCommands, не зная их семантики.
Сборка
# Без аддонов (публичная сборка)
go build -o bridge .
# С подключённым расширением
go build -tags addon -o bridge .
Для локальной разработки приватного расширения как отдельного Go-модуля удобно использовать go.work (тоже в .gitignore):
go work init . ../path-to-addon-module
go build -tags addon -o bridge .
Как написать своё расширение
- Реализуйте интерфейс
Addonв своём пакете (отдельный модуль или подпапка). - Создайте
addon_local.goс тегом//go:build addonи функциейloadAddon(b *Bridge) Addon, которая собирает ваш аддон, прокидывая ему нужные операции бриджа. - Соберите с
-tags addon.
Ядро остаётся независимым: убрав тег (или собрав публично), вы получаете чистый мост без следов расширения.
Лицензия
CC BY-NC 4.0 — свободное использование и модификация, но коммерческое использование только с письменного разрешения автора.
Разработчик
bearlogin.dev — разработка ботов, сайтов, лендингов и digital-продуктов под ключ. Консультации по автоматизации и AI.