A self-hosted Telegram channel mirroring bot that silently copies posts from a source channel to a mirror channel — without revealing the original source. Built with Python, Pyrogram, PostgreSQL, and Docker.
- Silent mirroring — messages are copied (not forwarded), so no source attribution appears in the mirror channel
- Smart forwarding — if a post in the source channel is itself a forward from another user or channel, that attribution is preserved
- Fast polling — polls the source channel every 10 seconds using a lightweight single-message check; on new message detection, fetches only the delta and queues it immediately (~10s delivery delay)
- Startup catchup — on every launch, checks the last
HISTORY_DEPTHmessages and queues any that were missed during downtime - Periodic safety net — full catchup runs every 5 minutes as a fallback in case of silent disconnects
- Smart album sync — dynamically polls
get_media_groupuntil the part count stabilizes (two equal consecutive checks), then forwards — no fixed delay, no partial albums - All media types — text, photos, videos, voice messages, video notes (circles), documents, audio, stickers
- Duplicate protection — PostgreSQL-backed deduplication prevents re-sending already mirrored messages
- Watchdog — automatically restarts the worker if it crashes silently
- Service message handling — service events (pins, channel creation, etc.) are logged and skipped cleanly without errors
- Web dashboard — real-time activity log via Streamlit UI (auto-refreshes every 10 seconds)
- Docker-ready — fully containerized, designed to run on any Linux server
telegram-watcher/
├── main.py # Bot core: event handler, worker queue, watchdog, startup catchup
├── storage.py # PostgreSQL async storage (asyncpg)
├── dashboard.py # Streamlit monitoring dashboard
├── scan.py # Utility: scan and print real channel IDs
├── reset_db.py # Utility: manually remove specific message IDs from DB
├── config.example.py # Configuration template (copy to config.py and fill in)
├── requirements.txt # Python dependencies
├── Dockerfile # Container image definition
├── start.sh # Entrypoint: starts dashboard + bot
└── .gitignore
⚠️ config.pyand*.sessionfiles are not included in the repository and must never be committed.
Copy config.example.py to config.py and fill in your values:
API_ID = 123456
API_HASH = "your_api_hash"
SOURCE_CHANNEL = -1001234567890 # Source channel ID
TARGET_CHANNEL = -1009876543210 # Mirror channel ID
HISTORY_DEPTH = 200 # How many recent messages to check for catchup on startup
# If PostgreSQL runs on the host: use "localhost"
# If PostgreSQL runs in a Docker container on the same network: use the container name
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "telegram_watcher"
DB_USER = "postgres"
DB_PASS = "your_password"
PROXY = None # or {"scheme": "socks5", "hostname": "host", "port": 1234}If Telegram is blocked in your region, set PROXY in config.py:
PROXY = {"scheme": "socks5", "hostname": "your.proxy.host", "port": 1234}
# With authentication:
PROXY = {"scheme": "socks5", "hostname": "your.proxy.host", "port": 1234, "username": "user", "password": "pass"}Both SOCKS5 and HTTP proxies are supported. Set PROXY = None to connect directly.
Note: Some VPS and cloud providers perform Deep Packet Inspection (DPI) that blocks MTProto traffic even when Telegram's IP addresses are reachable via TCP. If Pyrogram keeps logging
Connection timed outin a loop, a proxy is required regardless of your server's geographic location.
- Docker
- A Telegram account (not a bot token — a user account session is required)
- PostgreSQL database (can run directly on the host or in a Docker container)
Run scan.py locally to authenticate and generate the session file:
pip install pyrogram tgcrypto
python scan.pyThis creates scanner_session.session. To generate my_mirror_bot.session (the name the bot expects) directly, run:
python -c "from pyrogram import Client; from config import API_ID, API_HASH; Client('my_mirror_bot', api_id=API_ID, api_hash=API_HASH).run()"Follow the Pyrogram auth flow (phone number + code from Telegram). Once done, press Ctrl+C.
cp config.example.py config.py
# Edit config.py with your valuesUse scan.py to find the real numeric IDs of your channels — send a message to a channel and the ID will appear in the console output.
Create the database and user in PostgreSQL:
CREATE DATABASE telegram_watcher;
CREATE USER tg_mirror WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE telegram_watcher TO tg_mirror;
ALTER DATABASE telegram_watcher OWNER TO tg_mirror;If PostgreSQL runs in Docker, execute via the container:
docker exec -it <postgres_container_name> psql -U postgres -c "CREATE DATABASE telegram_watcher;"
docker exec -it <postgres_container_name> psql -U postgres -c "CREATE USER tg_mirror WITH PASSWORD 'your_password';"
docker exec -it <postgres_container_name> psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE telegram_watcher TO tg_mirror;"
docker exec -it <postgres_container_name> psql -U postgres -c "ALTER DATABASE telegram_watcher OWNER TO tg_mirror;"Then set DB_HOST in config.py to the container name (not localhost), and make sure both containers are on the same Docker network (see step 4).
Simple setup (PostgreSQL on host):
docker build -t telegram-watcher .
docker run -d \
--name telegram-watcher \
--restart unless-stopped \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherPostgreSQL also in Docker — both containers must share a network:
docker network create tg-net
docker network connect tg-net <postgres_container_name>
docker run -d \
--name telegram-watcher \
--restart unless-stopped \
--network tg-net \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherOpen http://localhost:8501 in your browser.
git pull
docker build -t telegram-watcher .
docker rm -f telegram-watcher
docker run -d --name telegram-watcher --restart unless-stopped --network tg-net \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherThe Streamlit dashboard shows a live activity log with color-coded event types:
| Color | Event Type | Meaning |
|---|---|---|
| 🟢 Green | SENT | Message mirrored successfully |
| 🔴 Red | ERROR | Something went wrong |
| 🟠 Orange | WARNING | FloodWait or soft issue |
All timestamps are displayed in Moscow Time (UTC+3).
Listens for incoming messages and prints the real numeric chat ID of any channel that sends a message. Use this to find SOURCE_CHANNEL and TARGET_CHANNEL values.
python scan.pyRemoves specific message IDs from the posted_messages table, forcing the bot to re-mirror them on the next startup catchup. Edit the TARGET_IDS list inside the script before running.
python reset_db.py| Component | Technology |
|---|---|
| Bot framework | Pyrogram 2.x |
| Database | PostgreSQL via asyncpg |
| Dashboard | Streamlit |
| Containerization | Docker |
| Hosting | Any Linux server |
- Never commit
config.pyor.sessionfiles to version control - Both are already covered by
.gitignore - The
.sessionfile grants full access to your Telegram account
MIT License. Use at your own risk. Mirroring channels may violate Telegram's Terms of Service depending on the content and context.
Самохостируемый бот для зеркалирования Telegram-каналов. Копирует посты из канала-источника в канал-зеркало без указания источника. Написан на Python с использованием Pyrogram, PostgreSQL и Docker.
- Тихое зеркалирование — сообщения копируются, а не пересылаются, поэтому в канале-зеркале не отображается источник
- Умная пересылка — если пост в канале-источнике сам является пересылкой от другого пользователя или канала, авторство сохраняется
- Быстрый опрос — каждые 10 секунд берёт одно последнее сообщение (дешёвый запрос), сравнивает ID с последним известным, при новых — забирает только дельту (~10 секунд задержки доставки)
- Catchup при запуске — при каждом старте бот проверяет последние
HISTORY_DEPTHсообщений и ставит в очередь всё, что пропустил во время простоя - Страховочный catchup — полная проверка запускается раз в 5 минут на случай тихого отключения
- Умная синхронизация альбомов — динамически опрашивает
get_media_groupпока количество частей не стабилизируется (два одинаковых результата подряд), только потом пересылает — никаких фиксированных задержек и неполных альбомов - Все типы медиа — текст, фото, видео, голосовые, кружочки, документы, музыка, стикеры
- Защита от дублей — PostgreSQL хранит ID обработанных сообщений, повторная отправка исключена
- Watchdog — автоматически перезапускает воркер при тихом сбое
- Обработка сервисных сообщений — события типа «закреп», «создание канала» и т.д. логируются и пропускаются без ошибок
- Веб-дашборд — лог активности в реальном времени через Streamlit (обновляется каждые 10 секунд)
- Docker-ready — полностью контейнеризирован, рассчитан на запуск на любом Linux-сервере
telegram-watcher/
├── main.py # Ядро бота: event handler, очередь воркера, watchdog, catchup при старте
├── storage.py # Асинхронное хранилище PostgreSQL (asyncpg)
├── dashboard.py # Дашборд мониторинга на Streamlit
├── scan.py # Утилита: поиск реального числового ID канала
├── reset_db.py # Утилита: удаление конкретных сообщений из БД
├── config.example.py # Шаблон конфигурации (скопируй в config.py и заполни)
├── requirements.txt # Python-зависимости
├── Dockerfile # Образ контейнера
├── start.sh # Точка входа: запускает дашборд и бота
└── .gitignore
⚠️ Файлыconfig.pyи*.sessionне включены в репозиторий и никогда не должны туда попадать.
Скопируй config.example.py в config.py и заполни своими значениями:
API_ID = 123456
API_HASH = "твой_api_hash"
SOURCE_CHANNEL = -1001234567890 # ID канала-источника
TARGET_CHANNEL = -1009876543210 # ID канала-зеркала
HISTORY_DEPTH = 200 # Сколько последних сообщений проверять при catchup на старте
# Если PostgreSQL запущен на хосте — используй "localhost"
# Если PostgreSQL запущен в Docker-контейнере в той же сети — используй имя контейнера
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "telegram_watcher"
DB_USER = "postgres"
DB_PASS = "твой_пароль"
PROXY = None # или {"scheme": "socks5", "hostname": "host", "port": 1234}Если Telegram заблокирован в твоём регионе, задай PROXY в config.py:
PROXY = {"scheme": "socks5", "hostname": "твой.прокси.хост", "port": 1234}
# С авторизацией:
PROXY = {"scheme": "socks5", "hostname": "твой.прокси.хост", "port": 1234, "username": "user", "password": "pass"}Поддерживаются SOCKS5 и HTTP прокси. Чтобы подключаться напрямую — оставь PROXY = None.
Важно: Некоторые VPS и облачные провайдеры применяют DPI (глубокую инспекцию трафика), которая блокирует MTProto даже если IP-адреса Telegram доступны по TCP. Если Pyrogram бесконечно пишет
Connection timed outв логах — прокси нужен вне зависимости от географии сервера.
- Docker
- Аккаунт Telegram (не бот-токен — требуется сессия пользовательского аккаунта)
- База данных PostgreSQL (может быть на хосте или в отдельном Docker-контейнере)
Запусти scan.py локально, чтобы пройти авторизацию:
pip install pyrogram tgcrypto
python scan.pyЧтобы сразу создать файл с нужным именем (my_mirror_bot.session), выполни:
python -c "from pyrogram import Client; from config import API_ID, API_HASH; Client('my_mirror_bot', api_id=API_ID, api_hash=API_HASH).run()"Введи номер телефона и код из Telegram. После успешного входа нажми Ctrl+C.
cp config.example.py config.py
# Отредактируй config.py, вписав свои значенияИспользуй scan.py, чтобы найти реальные числовые ID каналов — напиши что-нибудь в канал и ID появится в выводе консоли.
Выполни в PostgreSQL:
CREATE DATABASE telegram_watcher;
CREATE USER tg_mirror WITH PASSWORD 'твой_пароль';
GRANT ALL PRIVILEGES ON DATABASE telegram_watcher TO tg_mirror;
ALTER DATABASE telegram_watcher OWNER TO tg_mirror;Если PostgreSQL запущен в Docker, выполни через контейнер:
docker exec -it <имя_контейнера_postgres> psql -U postgres -c "CREATE DATABASE telegram_watcher;"
docker exec -it <имя_контейнера_postgres> psql -U postgres -c "CREATE USER tg_mirror WITH PASSWORD 'твой_пароль';"
docker exec -it <имя_контейнера_postgres> psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE telegram_watcher TO tg_mirror;"
docker exec -it <имя_контейнера_postgres> psql -U postgres -c "ALTER DATABASE telegram_watcher OWNER TO tg_mirror;"Затем укажи в config.py значение DB_HOST равное имени контейнера (не localhost) и убедись, что оба контейнера находятся в одной Docker-сети (см. шаг 4).
Простой вариант (PostgreSQL на хосте):
docker build -t telegram-watcher .
docker run -d \
--name telegram-watcher \
--restart unless-stopped \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherPostgreSQL тоже в Docker — оба контейнера должны быть в одной сети:
docker network create tg-net
docker network connect tg-net <имя_контейнера_postgres>
docker run -d \
--name telegram-watcher \
--restart unless-stopped \
--network tg-net \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherПерейди в браузере по адресу http://localhost:8501.
git pull
docker build -t telegram-watcher .
docker rm -f telegram-watcher
docker run -d --name telegram-watcher --restart unless-stopped --network tg-net \
-p 8501:8501 \
-v $(pwd)/config.py:/app/config.py \
-v $(pwd)/my_mirror_bot.session:/app/my_mirror_bot.session \
telegram-watcherStreamlit-дашборд показывает лог активности с цветовой индикацией событий:
| Цвет | Тип события | Значение |
|---|---|---|
| 🟢 Зелёный | SENT | Сообщение успешно зеркалировано |
| 🔴 Красный | ERROR | Произошла ошибка |
| 🟠 Оранжевый | WARNING | FloodWait или мягкая проблема |
Все временные метки отображаются по московскому времени (UTC+3).
Слушает входящие сообщения и выводит реальный числовой ID любого канала, который пришлёт сообщение. Используй для определения SOURCE_CHANNEL и TARGET_CHANNEL.
python scan.pyУдаляет конкретные ID сообщений из таблицы posted_messages, заставляя бота повторно зеркалировать их при следующем catchup на старте. Перед запуском отредактируй список TARGET_IDS внутри скрипта.
python reset_db.py| Компонент | Технология |
|---|---|
| Фреймворк бота | Pyrogram 2.x |
| База данных | PostgreSQL через asyncpg |
| Дашборд | Streamlit |
| Контейнеризация | Docker |
| Хостинг | Любой Linux-сервер |
- Никогда не коммить
config.pyи.sessionфайлы в репозиторий - Оба файла уже добавлены в
.gitignore - Файл
.sessionдаёт полный доступ к твоему аккаунту Telegram
MIT License. Используй на свой страх и риск. Зеркалирование каналов может нарушать Условия использования Telegram в зависимости от контента и контекста.