Testgram — форк MyTelegram, самохостируемая реализация серверной части Telegram на C#.
- API Layer:
224 - MTProto транспорты:
Abridged,Intermediate - Личные чаты
- Супергруппы
- Каналы
- Реакции на сообщения
- Star Gifts (каналы, скрыть/показать, непрочитанные упоминания)
- Вход через Passkey (WebAuthn)
- Директ канала (Monoforum)
- Поддержка ботов
- Истории
- Настройки приватности и двухфакторная аутентификация
- Голосовые и видеозвонки (1:1, WebRTC)
- Групповые звонки / голосовые и видеочаты
- Конференц-звонки (сквозное шифрование)
- Трансляции (RTMP / HLS)
- Push-уведомления (APNS / FCM / WebPush)
- Отправка email и восстановление 2FA через email
- Telegram Business
- Автоудаление сообщений
- Стикеры
- Отложенные сообщения
- Темы форума
- Темы оформления и обои
- Папки (фильтры диалогов)
- Сквозное шифрование чатов (Secret Chats)
- Полноценный вход через email
- Скачайте файлы Docker Compose:
curl -O https://raw.githubusercontent.com/glebxdlolreal/testgram/dev/docker/compose/docker-compose.yml
curl -O https://raw.githubusercontent.com/glebxdlolreal/testgram/dev/docker/compose/.env.example
cp .env.example .env-
Отредактируйте
.env:- Замените
YOUR_SERVER_IPна публичный IP вашего сервера - Установите надёжные пароли вместо
CHANGE_ME(RabbitMQ, Minio, ключи шифрования)
- Замените
-
Запустите сервер:
mkdir -p ./data/mytelegram
chmod -R a+w ./data/mytelegram
docker compose up -dОсновные параметры .env:
| Переменная | Описание |
|---|---|
App__DcOptions__0__IpAddress |
Публичный IP сервера |
RabbitMQ__Connections__Default__Password |
Пароль RabbitMQ |
App__AccessHashSecretKey |
Случайный секретный ключ |
App__EncryptionConfig__MessageKeys__0__Key |
Ключ шифрования в Base64 |
App__FixedVerifyCode |
Фиксированный SMS-код для тестирования (оставьте пустым в продакшене) |
Звонки требуют TURN/STUN сервер. В актуальной версии он уже встроен: стек
docker compose включает сервис coturn (STUN/TURN) и сервис rtmp-server
(mediamtx, используется для трансляций в групповых звонках). Устанавливать Coturn
на хост больше не нужно — достаточно указать в конфиге WebRTC IP вашего сервера.
Настройте WebRTC в .env (учётные данные должны совпадать с пользователем
встроенного coturn, по умолчанию testgram:testgram2024):
# ОБЯЗАТЕЛЬНО для работы звонков
App__WebRtcConnections__0__Ip=YOUR_SERVER_IP
App__WebRtcConnections__0__Port=3478
App__WebRtcConnections__0__Turn=True
App__WebRtcConnections__0__Stun=True
App__WebRtcConnections__0__UserName=testgram
App__WebRtcConnections__0__Password=testgram2024Групповые звонки и трансляции используют встроенный RTMP/HLS сервер:
App__RtmpStreamUrl=rtmp://YOUR_SERVER_IP:1935/live
App__RtmpHlsUrl=http://rtmp-server:8888/live
RTMP_PORT=1935
RTMP_HLS_PORT=8888Индексы MongoDB для звонков создаются автоматически при первом запуске через
контейнер call-init. Чтобы выполнить вручную:
cd scripts && ./setup_call_indexes.sh # Опционально: ручная настройкаОткройте нужные UDP/TCP порты в файрволе: 3478 (STUN/TURN), 49152-49172/udp
(TURN relay) и 1935 (RTMP). См. docs/CALLS_SETUP.md для полной
инструкции, включая использование внешнего TURN-сервера.
Если клиент не подключается с ошибкой вида:
Attempt 1 at connecting failed: ConnectionRefusedError: [WinError 1225] The remote computer refused the network connectionно при этом сам VDS/хост доступен — скорее всего, шлюз (gateway) не слушает главный
порт 20443 (DC1, первый порт, к которому подключаются клиенты — см. App__DcOptions__0__Port).
Причина: параметр App__Servers__0__Enabled не задан/закомментирован в .env.
docker-compose всё равно передаёт эту переменную в контейнер шлюза, поэтому незаданное
значение превращается в пустую строку. Из-за пустого значения .NET полностью
выбрасывает server 0 из конфигурации, шлюз не открывает слушатель на 20443, и все
подключения отклоняются.
Решение: убедитесь, что в .env есть активная строка (не закомментирована и не пустая):
App__Servers__0__Enabled=TrueЗатем пересоздайте шлюз и проверьте, что он слушает 20443:
cd docker/compose
docker compose up -d --force-recreate gateway-server
docker compose logs gateway-server | grep 20443 # ожидается: "Tcp server started at ...:20443"Если в логах file-server спам вида:
Minio.Exceptions.InvalidBucketNameException: MinIO API responded with message=Bucket name cannot be empty.а в клиентах не загружаются аватарки, стикеры или кастомные иконки верификации — значит,
Minio__BucketName не задан/закомментирован в .env. docker-compose всё равно передаёт эту
переменную в file-server, поэтому незаданное значение превращается в пустую строку, и любой
запрос файла падает с ошибкой.
Решение: убедитесь, что в .env есть активные строки (не закомментированы и не пустые):
Minio__BucketName=tg-files
Minio__CreateBucketIfNotExists=TrueЗатем пересоздайте file-server:
cd docker/compose
docker compose up -d --force-recreate file-serverЕсли логи file-server завалены ошибками вида:
[ERR] Get file failed, input: FileId: "..." Offset: ... Limit: 32768
System.NullReferenceException: Object reference not set to an instance of an object.
at Minio.MinioClient.ParseWellKnownErrorNoContent(ResponseResult response)
...
at MyTelegram.FileServer.Services.MinioStoringHelper.GetAsync(...)это регрессия в MinIO .NET SDK, встроенном в сторонний образ mytelegram-file-server
(Minio 6.0.6-local). Когда MinIO отвечает на запрос диапазона байт кодом
416 Range Not Satisfiable (без тела) — а клиенты Telegram делают такой запрос для
последнего чанка загрузки (offset на/за концом файла) — SDK не обрабатывает 416,
оставляет объект ошибки null, и throw error; превращается в NullReferenceException.
Так как file-server собирается и публикуется отдельно, пропатчить его из этого репозитория нельзя. Вместо этого file-server ходит в MinIO через сервис minio-proxy (небольшой прокси на nginx), который превращает такие ответы 416 в чистый пустой 200, понятный SDK. Весь остальной трафик проходит без изменений.
Это включено по умолчанию (Minio__FileServerEndpoint = minio-proxy:9000). Если видите
эту ошибку — убедитесь, что прокси запущен, а file-server ходит через него:
cd docker/compose
docker compose up -d minio-proxy
docker compose up -d --force-recreate file-server# Linux amd64
cd build/docker && ./build-all-amd64.sh
# Linux arm64
cd build/docker && ./build-all-arm64.sh| Платформа | Репозиторий |
|---|---|
| Android | https://github.com/glebxdlolreal/testgram-android |
| Desktop (TDesktop) | https://github.com/glebxdlolreal/testgram-tdesktop |
| iOS | https://github.com/loyldg/mytelegram-iOS |
| WebK | https://github.com/loyldg/mytelegram-webk |
| WebA | https://github.com/loyldg/mytelegram-weba |
- Склонируйте исходный код клиента.
- Найдите
YOUR_SERVER_IPво всех файлах и замените на IP вашего сервера.
В репозитории есть Telegram-бот (bot/), который слушает коды регистрации через RabbitMQ и отправляет их пользователям.
cd bot
cp .env.example .env
# Отредактируйте .env: укажите BOT_TOKEN и RABBITMQ_URL
python3 bot.pyПодключитесь к MongoDB и выполните:
// mongosh tg
db['star-transactions'].insertOne({
UserId: Long('USER_ID'),
Amount: 1000, // количество звёзд
Gift: false,
Title: 'Admin top-up',
PeerUserId: 0,
Date: new Date()
});
db['eventflow-userreadmodel'].updateOne(
{ UserId: Long('USER_ID') },
{ $inc: { StarsBalance: 1000 } }
);Замените
USER_IDна нужный ID пользователя (найти черезdb['eventflow-userreadmodel'].find({UserName: 'username'})).
Подарки хранятся в коллекции star-gifts. Чтобы добавить новый подарок:
// mongosh tg
db['star-gifts'].insertOne({
GiftId: Long('UNIQUE_GIFT_ID'), // уникальный ID (например, 1001)
Stars: 50, // цена в звёздах
Title: 'My Gift',
Description: '',
DocumentId: Long('DOCUMENT_ID'), // ID стикера/документа из Telegram
LimitedQuantity: 0, // 0 = безлимитный
SoldCount: 0,
Available: true,
FirstSaleDate: new Date(),
LastSaleDate: null
});Чтобы выдать подарок пользователю напрямую (без покупки):
db['saved-star-gifts'].insertOne({
UserId: Long('RECIPIENT_USER_ID'),
FromUserId: Long('0'),
GiftId: Long('UNIQUE_GIFT_ID'),
Stars: 50,
Message: '',
Saved: true,
Date: new Date()
});Чтобы сделать подарок апгрейдируемым:
1. Установить стоимость апгрейда на подарке:
// mongosh tg
db['star-gifts'].updateOne(
{ GiftId: Long('GIFT_ID') },
{ $set: {
UpgradeStars: 1000, // звёзд для апгрейда
AvailabilityTotal: 10000 // всего уникальных копий
}}
);2. Добавить конфиг апгрейда (атрибуты уникальной версии):
Каждый уникальный подарок получает 3 атрибута: model (стикер), backdrop (фон), pattern (узор).
Добавьте варианты в star-gift-upgrade-config:
db['star-gift-upgrade-config'].insertMany([
// Модель (вариант стикера)
{
gift_id: Long('GIFT_ID'), // 0 = применяется ко всем подаркам
type: 'model',
name: 'Редкая модель',
rarity_permille: 100, // 100 = 10% шанс (из 1000)
document_id: Long('STICKER_DOCUMENT_ID')
},
// Фон (цвета)
{
gift_id: Long('GIFT_ID'),
type: 'backdrop',
name: 'Золотой',
rarity_permille: 50,
backdrop_id: 1,
center_color: 0xF1C40F,
edge_color: 0xD4AC0D,
pattern_color: 0xF9E79F,
text_color: 0xFFFFFF
},
// Узор (стикер-оверлей)
{
gift_id: Long('GIFT_ID'),
type: 'pattern',
name: 'Звёзды',
rarity_permille: 200,
document_id: Long('PATTERN_DOCUMENT_ID')
}
]);
rarity_permille— вес из 1000 (больше = чаще выпадает).gift_id: 0— атрибуты для всех подарков.
3. Принудительный апгрейд подарка пользователю (админ):
// Найти сохранённый подарок
db['saved-star-gifts'].findOne({ OwnerUserId: Long('USER_ID'), IsUnique: false });
// Сделать апгрейд бесплатным и дать пользователю апгрейднуть самому
db['star-gifts'].updateOne(
{ GiftId: Long('GIFT_ID') },
{ $set: { UpgradeStars: 0 } }
);После деплоя сервера запустите сидер реакций для заполнения анимаций эмодзи:
cd scripts
# 1. Скачать файлы реакций из Telegram (~50MB)
TG_API_ID=your_api_id \
TG_API_HASH=your_api_hash \
TG_PHONE=+1234567890 \
python3 seed_reactions.py --download
# 2. Импортировать файлы в Minio + MongoDB
MONGO_URL=mongodb://localhost:27017 \
MINIO_ENDPOINT=localhost:9000 \
MINIO_ACCESS_KEY=your_key \
MINIO_SECRET_KEY=your_secret \
python3 seed_reactions.py --import
# 3. Сгенерировать C#-хендлер с реальными ID документов
MONGO_URL=mongodb://localhost:27017 \
HANDLER_PATH=../source/src/MyTelegram.Messenger/Handlers/LatestLayer/Messages/GetAvailableReactionsHandler.cs \
python3 seed_reactions.py --generate-handler
# 4. Пересобрать и задеплоить образы messenger
cd ../build/docker
export REGISTRY_URL="mytelegram"
bash 1.build-messenger-command-server.sh
bash 2.build-messenger-query-server.sh
cd ../../docker/compose && docker compose down && docker compose up -dПримечание: Шаги 1–3 нужно выполнить только один раз. Сгенерированный хендлер коммитится в репозиторий, последующие деплои не требуют повторного сидинга.