Telegram Desktop Chat-History Migration
A Python tool for reconstructing a Telegram conversation in a destination channel from a Telegram Desktop export (messages.html, messages2.html, etc.) and the media files that accompany it. Use it as a command-line tool (python -m telegram_migration) or import it as a Python library (from telegram_migration import Settings, run).
Scope: this program migrates a local export. It does not sign into Telegram Web or retrieve the complete live history of an unrelated channel. Most reconstructed messages are newly sent by your destination bot; the program cannot recreate arbitrary historical sender identities or original native reaction totals. This program uses Telegram's official Bot API/MTProto bot authentication to send migrated messages, rather than logging in through your personal Telegram account. It does not require your personal phone number, SMS login code, or personal account session, reducing the exposure of your personal account credentials. The bot operates under its own identity, so sending limits and restrictions primarily affect the bot.
Features
- Streams Telegram Desktop HTML exports instead of loading every page into memory at once.
- Reconstructs supported rich-text formatting (including italics, bold, underline, strike, code, links, spoilers, and blockquotes) using Telegram message entities.
- Preserves sender names and original timestamps as archival text where available.
- Handles common exported media: photos, documents, videos, GIFs, voice messages, round videos, and sticker files.
- Maps source message IDs to destination IDs to restore reply links where possible; audits available reply links.
- Persists partial-send state so interrupted runs can often resume without intentionally resending already-recorded pieces.
- Checks oversized attachments before sending and stops with instructions for manual handling.
- Uses FloodWait handling and records failures and formatting fallbacks.
- Can archive reaction metadata from a compatible companion JSON export as text notes. It cannot restore the original native reaction bar, original voters, or historical reaction counts as live Telegram reactions.
- Uses a Telegram bot session for sending. It does not require a personal-account login for the migration itself.
Requirements
- Python 3.10 or newer recommended.
- A Telegram API ID and API hash from my.telegram.org.
- A bot token from @BotFather.
- The bot must be an administrator in the destination channel with permission to post.
- A Telegram Desktop export with its HTML pages and media folders kept together.
Install
pip install telegram-migration
That's it. You get the telegram-migration command and the telegram_migration Python package.
For video-sticker metadata support (optional, only needed if your archive contains .webm stickers and ffprobe isn't already installed):
pip install "telegram-migration[media]"
From source (for development)
If you want to modify the tool or run the test suite:
git clone https://github.com/ifaac/Telegram_Migration.git
cd Telegram_Migration
python -m venv .venv
# activate the venv, then:
python -m pip install -e ".[media]"
python -m unittest discover -s tests -v
Configure
- Copy
.env.exampleto.env. - Replace the example API hash, bot token, and destination peer with your own values.
- Put your exported Telegram HTML pages and media folders in the directory specified by
TELEGRAM_EXPORT_DIR.
Example .env:
TELEGRAM_API_ID=12345678
TELEGRAM_API_HASH=replace_with_your_api_hash
TELEGRAM_BOT_TOKEN=replace_with_your_new_bot_token
TELEGRAM_CHANNEL_PEER=-1001234567890
TELEGRAM_EXPORT_DIR=./telegram_export
TELEGRAM_DRY_RUN=true
TELEGRAM_SKIP_CONFIRMATION=false
TELEGRAM_CHANNEL_PEER may be a numeric peer ID (for example -1001234567890) or a public @username. The program intentionally has no preconfigured destination ID.
TELEGRAM_API_ID has no default: use your own from my.telegram.org. The program refuses to start while any credential is missing or still an example value.
Optional settings (defaults in brackets):
| Variable | Meaning |
|---|---|
TELEGRAM_MAX_UPLOAD_MB [2000] |
Stop at any message with a file larger than this many MiB (0 disables the check). 2000 MiB is believed to be Telegram's MTProto ceiling; verify it with your bot. |
TELEGRAM_BATCH_LIMIT [1000] |
Stop after this many messages (sent + failed). |
TELEGRAM_SEND_DELAY [2.5] |
Seconds to wait between messages. |
TELEGRAM_SESSION_NAME [colab_bot_restorer_session] |
Name of the Telethon session file. |
Google Colab
Colab Secrets named TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_BOT_TOKEN and TELEGRAM_CHANNEL_PEER are read automatically (enable notebook access for each). Run the migration inside the notebook so it can read the secrets (a !python ... command starts a separate process that cannot):
!pip install -q telegram-migration
from telegram_migration import Settings, run
settings = Settings.from_env(
base_dir="/content/drive/MyDrive/ChatExport_2025-01-01", # your export folder
dry_run=True, # preview first
)
run(settings)
When the preview looks right, run the cell again with dry_run=False.
The program mounts Google Drive automatically when the export directory is under /content/drive/ and Drive is not mounted yet.
Run safely
Dry-run is enabled by default. Start by running:
python -m telegram_migration
(telegram-migration does the same once the package is installed.) Review the reported source file count, destination channel title/ID, and dry-run summary. No messages are sent in a dry run.
When you are satisfied with the preview, either set TELEGRAM_DRY_RUN=false in .env or pass --send:
python -m telegram_migration --send
The program asks you to confirm the target before sending; keep TELEGRAM_SKIP_CONFIRMATION=false (or omit --yes) so it does.
Command-line options override the environment (python -m telegram_migration --help lists them):
| Option | Effect |
|---|---|
--export-dir PATH |
Folder with messages.html and the media folders. |
--channel PEER |
Destination: numeric peer ID or @username. |
--dry-run / --send |
Preview only / really send. |
--yes |
Skip the confirmation prompt. |
--max-upload-mb N |
Stop at files larger than N MiB (0 disables). |
--batch-limit N, --delay SECONDS |
Stop after N messages / wait between messages. |
--env-file PATH |
Load this .env file instead of ./.env. |
Use as a Python library
from telegram_migration import Settings, run
settings = Settings.from_env() # TELEGRAM_* variables, .env file, Colab Secrets
run(settings) # or: run(dry_run=False)
Settings is a plain dataclass, so everything can also be given directly (Settings(api_id=..., base_dir=..., max_upload_mb=500)). run() works from scripts and notebooks; await telegram_migration.run_async(settings) is the coroutine version. Async runs scope settings to their task, so concurrent migrations do not overwrite each other's configuration. run_async() does not apply the nest_asyncio patch used by run() inside an already-running event loop. Importing the package reads no environment variables and performs no file I/O; credentials problems raise telegram_migration.ConfigError.
Telegram's message limit is 4096 UTF-16 units for every account, and media captions are limited to 1024 for a bot (2048 applies only to Premium user accounts, and a bot is not one). The defaults are text_limit=4096 and caption_limit=1024; you can lower them to split text into shorter parts. Settings.validate() rejects text_limit or caption_limit below 4, and text_limit above 4096 and caption_limit above 1024 (the actual ceiling for a bot).
Progress files and resuming
The script keeps its progress files inside the export directory. These include the ID map, partial-send state, pending replies, failure log, manual-upload list, formatting audit, and reply audit. Keep these files between runs if you want resumability. Do not commit them to a public repository: they can reveal private chat metadata.
If the program stops at a file larger than the configured upload ceiling, follow the printed manual-upload instructions before running the program again. The script can mark that source message to be skipped on the next run.
Project layout
The code is a small package instead of one long script:
| Module | Responsibility |
|---|---|
config |
The Settings dataclass, environment/.env/Colab-Secrets loading, validation. |
pipeline |
The run itself: stream the export, send each message, resume, stop cleanly. |
htmlexport, richtext, htmlutil |
Read a message out of the HTML export; turn its rich text into Telegram entities. |
exportmedia, mediatools |
Find and classify media; optional ffmpeg/hachoir helpers. |
sending, uploads, formatfallback |
Send text/albums/voice/stickers/forwards; upload files; retry without formatting if Telegram rejects it. |
progress, replylinks, reaction_archive |
ID map and resume state; reply verification; reaction notes. |
bigfiles |
Oversize-file check, manual-upload list and stop instructions. |
textutil, constants |
UTF-16 text/entity helpers; fixed Telegram limits. |
cli, runner |
Command line and the synchronous run(). |
Known limitations
- Telegram service messages (for example, join/leave events and title changes) are intentionally excluded.
- The destination messages are not identical to the original Telegram records. Sender names and timestamps are represented as text in reconstructed messages unless a native forward is possible.
- The source HTML and media folder must contain the required data. A missing attachment cannot be recreated by this program.
- If Telegram rejects formatting entities, the script can fall back to plain text for that message; check
migration_formatting_dropped.jsonl. - Reaction archives are informational text notes, not native Telegram reactions.
- Native forwards may be unavailable for source messages whose origins are inaccessible; the program's configured forwarding fallback may use a textual header.
- Files above the upload limit (
max_upload_mb) cause a clean stop and require manual handling. Files below the configured limit can still fail due to network, server, permissions, or media constraints.
Development checks
The repository includes an offline regression suite. The tests do not sign in to Telegram, contact Telegram servers, send messages, or require API credentials.
Run the tests from the repository root:
python -m pip install -r requirements.txt
python -m compileall -q telegram_migration
python -m unittest discover -s tests -v
The suite covers rich-text parsing and UTF-16 entity offsets, long-text chunking, export parsing, dates and replies, media classification, GIF/sticker upload options, upload-size checks, progress and ID-map persistence, manual-upload records, reaction metadata, settings and the command line, the module structure (no undefined names), and complete runs against a fake Telegram client: dry run, resume, deferred replies, formatting fallback, and the oversize stop. The fake clients cannot post to a real Telegram chat.
Security
- Never commit
.env, bot tokens, API hashes, Telegram session files, exports, media, or migration state. - If you have ever exposed a bot token, revoke it with BotFather and replace it before running the program.
- Treat API hashes and Telethon session files as private credentials.
- Avoid sharing migration logs publicly; they may contain source message IDs and message-specific error details.
License
GNU General Public License v3.0
Metadata
Release files for telegram-migration 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| telegram_migration-0.1.1.tar.gz | 72.9 kB | With provenance | Details for telegram_migration-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| telegram_migration-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for telegram_migration-0.1.1-py3-none-any.whl |
Total release size: 135.7 kB
Release files / telegram_migration-0.1.1.tar.gz
| Download URL | telegram_migration-0.1.1.tar.gz |
|---|---|
| Size | 72.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6b8b13e4e5a8093771c72ff801ac108a862ca438f490a3a30735a051bda308cd
|
|
BLAKE2b-256 checksum How to use checksums |
ac1deae23a8b613d3554d3a32a3828e42e41659edba1ffe609f8a0aada90f9df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logRelease files / telegram_migration-0.1.1-py3-none-any.whl
| Download URL | telegram_migration-0.1.1-py3-none-any.whl |
|---|---|
| Size | 62.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92baf4f2dc5463823eadd3b699789a2c5c6ba94ee33314656d7cb3e08f9b5f63
|
|
BLAKE2b-256 checksum How to use checksums |
66ae6043f5859e062c71248862ff6f3d9d4b7effb72a0601ea399f17d3d73aca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logSecurity for telegram-migration 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for telegram_migration-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.