Skip to content

Repository files navigation

✈️ telecrawl

telecrawl banner

Telegram archive CLI.

telecrawl reads local Telegram Desktop tdata archives and native Telegram for macOS Postbox databases, stores a searchable SQLite archive in ~/.telecrawl/telecrawl.db, and can back it up to GitHub as encrypted age shards.

It is local-first:

  • Normal archive/search commands do not upload data.
  • backup push uploads only age-encrypted shards when you run it explicitly.
  • Telegram message text, chat names, sender names, contact phone numbers, contact usernames, avatar path metadata, and media metadata stay inside encrypted backup payloads.

Install

brew tap openclaw/tap
brew install telecrawl

Official Homebrew macOS binaries require macOS 13 or newer as of Telecrawl 0.4.0. They are signed with the OpenClaw Foundation Developer ID and notarized by Apple before publication. Source builds and cross-platform snapshots remain credential-free.

Or install with Go:

go install github.com/openclaw/telecrawl/cmd/telecrawl@latest

Source builds require Go 1.27.0 or newer (macOS 13 or newer on macOS). CI checks Go 1.27.0 separately; preferred builds and Docker use Go 1.27.1. This source-build requirement does not change the support claims of older released binaries.

Maintainer release gates are documented in docs/releasing.md.

Docker

docker build -t telecrawl .
docker run --rm -v "$HOME/.telecrawl:/data" -v "$HOME/Library/Application Support/Telegram Desktop/tdata:/tdata:ro" telecrawl --source /tdata doctor
docker run --rm -v "$HOME/.telecrawl:/data" -v "$HOME/Library/Application Support/Telegram Desktop/tdata:/tdata:ro" telecrawl --source /tdata import

Mount Telegram Desktop tdata read-only and keep the archive/config under /data.

Setup

No language runtime setup is required. telecrawl imports Telegram Desktop tdata and native macOS Postbox data through the Go binary.

Import

telecrawl doctor
telecrawl import
telecrawl status

Import defaults to:

  • latest 200 dialogs
  • latest 500 messages per dialog

Imports merge into the existing archive by default. Chats and messages outside the fetched window remain stored.

Use 0 for no limit:

telecrawl import --dialogs-limit 0 --messages-limit 0

For a destructive full restore, explicitly replace the archive with the import:

telecrawl import --dialogs-limit 0 --messages-limit 0 --restore

--restore deletes all existing archive rows before storing the fetched import. It cannot be combined with --chat. Default merges require the same Telegram account identity as the existing archive, even when the source path is unchanged; use --restore when intentionally switching the archive to a different source. On the first import after upgrading an archive with legacy source metadata, use --adopt-source once to assert that the current Telegram account belongs to this archive without deleting rows. Message overlap is not treated as account proof because different accounts can share the same group or channel history. --adopt-source cannot override a different already-canonical source and cannot be combined with --restore.

Large native Postbox databases may contain an all-zero SQLite reserved locking page at the 1 GiB boundary. Imports preserve that reserved page and continue to authenticate every data page; other zero pages or invalid HMACs abort the import. Native decryption still loads the database into memory, so large sources require substantial available RAM.

Maintainers can reproduce the locking-page import with a generated synthetic database (more than 1 GiB of disk and several GiB of RAM):

make build
TELECRAWL_LOCKING_PROOF_BINARY="$PWD/bin/telecrawl" go test -count=1 -timeout=10m -v ./internal/telegramdesktop -run '^TestSQLCipherLockingPageCLI$'

Native Postbox imports require a decodable authorized account peer ID. A shared lane encryption key is not account identity; missing or malformed account state now stops the import before archive writes. Existing archives bound by older versions to a key-derived identity are retained unchanged and cannot be automatically attributed or rebound. Keep those archives and their media; use a separate archive for verified imports until you have chosen an explicit operator migration or reimport procedure. --adopt-source does not override that binding.

Canonical chats, folders and memberships, topics, contacts, groups and participants, and messages retain explicit Telegram tombstones with deletion time, source, and reason. Missing rows in a bounded import are not deletions. Message identities remain stable across imports, and observable Telegram edits and explicit deletes are retained as append-only revision events. Upgrading an older archive seeds a baseline observation for every existing message before a later edit can replace its canonical payload.

Add --fetch-media when you also want Telegram cloud media that is not cached locally:

telecrawl import --dialogs-limit 0 --messages-limit 0 --fetch-media

Remote media fetches are bounded best-effort operations. Import stats report how many remote media candidates were attempted, downloaded, still missing, unavailable, timed out, or errored.

Repeat imports reuse existing archived media for the same source before remote fetch is attempted, so --fetch-media only tries media that is not already in the local archive.

Native Postbox can tag link previews, polls, geo/live-geo, service messages, or deleted messages as broad media candidates. telecrawl archives their decoded message metadata separately from binary media, and only keeps them as media rows when Telegram returns a downloadable file. metadata_json is a local source-native Postbox payload for later rendering or search; it is not a cross-source schema and can contain private Telegram metadata.

When no --source is provided on macOS, telecrawl checks Telegram Desktop tdata first, then the native Telegram for macOS group container. No backend flag is needed. To import a copied archive directly:

telecrawl import --path "$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram"

Native macOS imports include every local account-* database they find; if more than one account is present, stored chat and sender IDs are account-scoped to avoid collisions. They archive cached media by default and store Telegram peer records as contacts for message enrichment. Contacts can include phone numbers, usernames, and archived avatar paths when those values exist locally, and are visible through telecrawl contacts. --fetch-media also uses the existing native Telegram session to fetch missing cloud media when account auth data is present; this does not launch Telegram or start a login/2FA flow.

Useful reads:

telecrawl folders
telecrawl contacts
telecrawl chats --limit 20
telecrawl chats --folder FOLDER_ID
telecrawl chats --folder FOLDER_ID --unread --limit 20
telecrawl chats --unread
telecrawl topics --chat CHAT_ID
telecrawl messages --limit 20
telecrawl messages --chat CHAT_ID --after 2026-01-01
telecrawl messages --chat CHAT_ID --topic TOPIC_ID
telecrawl messages --chat CHAT_ID --pinned
telecrawl search "query"
telecrawl search "query" --chat CHAT_ID --topic TOPIC_ID

Search filters can follow the query as shown above, or precede it. For a query beginning with a hyphen, put the filters first and use -- before the query: telecrawl search --chat CHAT_ID -- "-query". Folder and unread filters combine; the limit applies to matching unread chats within that folder.

Telegram folders, forum topics, reply/thread IDs, pinned messages, edits, forwards, reactions, view/reply counts, and richer media titles are archived when the local source or Telegram API exposes them for the active account. Telegram Desktop forum pagination uses the last-message date (or creation date when Telegram explicitly selects that order). Incomplete, stalled, or failed topic fetches abort the import before archive rows are merged or replaced; reaching the 1,000-page safety bound also returns an error.

Folder rows include explicit membership from Telegram dialog filters; dynamic folder rules are recorded as metadata and may not expand to every matching chat.

Add --json before the command for machine-readable output:

telecrawl --json status
telecrawl --json search "invoice"

Data Paths

Defaults:

  • Telegram Desktop source: ~/Library/Application Support/Telegram Desktop/tdata
  • native macOS Postbox source: ~/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram
  • archive DB: ~/.telecrawl/telecrawl.db
  • archived media copied from local Telegram caches, plus Telegram cloud media when --fetch-media is used: ~/.telecrawl/media/
  • backup config: ~/.telecrawl/backup.json
  • age identity: ~/.telecrawl/age.key
  • backup checkout: ~/Projects/backup-telecrawl

Override the archive DB:

telecrawl --db /tmp/telecrawl.db status

Override the Telegram source:

telecrawl --source "/path/to/tdata" doctor
telecrawl --source "/path/to/tdata" import
telecrawl --source "/path/to/6N38VWS5BX.ru.keepcoder.Telegram" import

Backup

Create https://github.com/steipete/backup-telecrawl first, then initialize:

telecrawl backup init
telecrawl backup push

The default backup config points at:

{
  "repo": "~/Projects/backup-telecrawl",
  "remote": "https://github.com/steipete/backup-telecrawl.git",
  "identity": "~/.telecrawl/age.key"
}

Use a different repository or config path:

telecrawl backup init \
  --config ~/.telecrawl/backup.json \
  --repo ~/Projects/backup-telecrawl \
  --remote https://github.com/steipete/backup-telecrawl.git

Inspect backup metadata:

telecrawl backup status
telecrawl backup snapshots

Merge the latest backup into the current archive DB:

telecrawl backup pull
telecrawl status

Default pulls preserve destination-only rows and tombstones. Current backups carry the Telegram account identity inside an encrypted metadata shard, and a merge is rejected when that identity differs from the destination. Legacy backups without this identity can merge only into an empty archive; use explicit restore mode when the local archive must exactly match such a snapshot:

telecrawl backup pull --restore

Every changed backup is a Git commit. Add a non-moving, visible checkpoint tag when needed, then restore that tag, commit, or branch without switching the backup checkout:

telecrawl backup push --tag snapshot/before-migration
telecrawl --db /tmp/telecrawl-history.db backup pull --restore --ref snapshot/before-migration

backup snapshots --limit N lists recent manifest-changing commits and tags. Keep tag names non-sensitive because Git metadata is not encrypted.

Restore into a throwaway DB for validation:

telecrawl --db /tmp/telecrawl-restore-test.db backup pull --restore
telecrawl --db /tmp/telecrawl-restore-test.db status

Backup Security Model

Backup shards are JSONL, gzip-compressed with deterministic gzip metadata, and encrypted with age before Git sees them.

Git can still see cleartext metadata:

  • export time
  • public age recipients
  • table names
  • row counts
  • shard paths
  • encrypted byte sizes
  • plaintext shard hashes
  • backup cadence and which encrypted shards changed

Git cannot read message text, chat names, sender names, contact phone numbers, contact usernames, avatar path metadata, or media metadata without an age identity. Binary media files and cached avatar files archived in ~/.telecrawl/media/ are local only and are not included in backup shards.

Keep ~/.telecrawl/age.key private. If you lose it and no other recipient can decrypt the backup, the encrypted backup cannot be restored.

Multi-Machine Backups

On another machine:

telecrawl backup init --no-push
cat ~/.telecrawl/backup.json

Copy that machine's public recipient into the first machine's ~/.telecrawl/backup.json, then re-encrypt current shards:

telecrawl backup push

The private AGE-SECRET-KEY-... identity must not be committed or shared.

Reset

Remove local state:

rm -rf ~/.telecrawl

Remove only the archive:

rm -f ~/.telecrawl/telecrawl.db ~/.telecrawl/telecrawl.db-*

Do not delete ~/.telecrawl/age.key unless you have another working backup recipient or you no longer need to restore existing encrypted backups.

About

Telegram for Claws

Resources

Security policy

Stars

83 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages