Install
openclaw skills install sergei-mikhailov-tg-channel-readerRead posts and comments from Telegram channels via MTProto (Pyrogram or Telethon). Fetch recent messages and discussion replies from public or private channels by time window.
openclaw skills install sergei-mikhailov-tg-channel-readerRead posts and comments from Telegram channels using MTProto (Pyrogram or Telethon). Works with any public channel and private channels the user is subscribed to. Supports fetching discussion replies (comments) for individual posts.
Security notice: This skill requires
TG_API_IDandTG_API_HASHfrom my.telegram.org. The session file grants full Telegram account access — store it securely and never share it.
Just installed via
clawhub install? Complete Setup & Installation (below) first — the skill needspip install, credentials, and a session file before exec approvals matter.
OpenClaw blocks unknown CLI commands by default. The user must approve tg-reader commands before they can run. If the command hangs or the user says nothing is happening — exec approval is likely pending.
Run from the skill directory — checks prerequisites, installs pip packages if needed, and prints the approval commands to run:
cd ~/.openclaw/workspace/skills/sergei-mikhailov-tg-channel-reader
bash setup-tg-reader.sh
openclaw approvals allowlist add --gateway "$(which tg-reader)"
openclaw approvals allowlist add --gateway "$(which tg-reader-check)"
openclaw approvals allowlist add --gateway "$(which tg-reader-telethon)"
http://localhost:18789/, find the pending approval for tg-reader, click "Always allow". Docs<id>. Reply: /approve <id> allow-always. Other options: allow-once, deny.The approval prompt appears in the Control UI or as a bot message — not in the agent's conversation. This is a common source of confusion.
tg-reader info# 1. Run pre-flight diagnostic (fast, no Telegram connection)
tg-reader-check
# 2. Get channel info
tg-reader info @channel_name
# 3. Fetch recent posts
tg-reader fetch @channel_name --since 24h
tg-reader: command not found? Runbash setup-tg-reader.shfrom the skill directory (it will install the package), or manually:cd ~/.openclaw/workspace/skills/sergei-mikhailov-tg-channel-reader && pip install .
tg-reader-check — Pre-flight DiagnosticAlways run before fetching. Fast offline check — no Telegram connection needed.
tg-reader-check
tg-reader-check --config-file /path/to/config.json
tg-reader-check --session-file /path/to/session
Returns JSON with "status": "ok" or "status": "error" plus a problems array.
Verifies:
~/.tg-reader.json)tg-reader info — Channel Infotg-reader info @channel_name
Returns title, description, subscriber count, and link.
tg-reader fetch — Read Posts# Last 24 hours (default)
tg-reader fetch @channel_name --since 24h
# Last 7 days, up to 200 posts
tg-reader fetch @channel_name --since 7d --limit 200
# Multiple channels (fetched sequentially with 10s delay between each)
tg-reader fetch @channel1 @channel2 @channel3 --since 24h
# Custom delay between channels (seconds)
tg-reader fetch @channel1 @channel2 @channel3 --since 24h --delay 5
# Fetch posts with comments (single channel only, limit auto-drops to 30)
tg-reader fetch @channel_name --since 7d --comments
# More comments per post, custom delay between posts
tg-reader fetch @channel_name --since 24h --comments --comment-limit 20 --comment-delay 5
# Skip posts without text (media-only, no caption)
tg-reader fetch @channel_name --since 24h --text-only
# Human-readable output
tg-reader fetch @channel_name --since 24h --format text
# Write output to file instead of stdout (saves tokens)
tg-reader fetch @channel_name --since 24h --output
tg-reader fetch @channel_name --since 24h --comments --output comments.json
# Use Telethon instead of Pyrogram (one-time)
tg-reader fetch @channel_name --since 24h --telethon
# Read unread mode — only fetch new (unread) posts, no --since needed
# Requires "read_unread": true in ~/.tg-reader.json
tg-reader fetch @channel_name
# Override read_unread mode (fetch everything, don't update state)
tg-reader fetch @channel_name --since 7d --all
# Custom state file location
tg-reader fetch @channel_name --since 24h --state-file /path/to/state.json
tg-reader auth — First-time Authenticationtg-reader auth
Creates a session file. Only needed once.
Only return new (unread) posts — the skill remembers what you've already seen. Useful for daily digests and monitoring workflows.
Option A — config file (~/.tg-reader.json):
{
"api_id": 12345,
"api_hash": "...",
"read_unread": true
}
Option B — env var (works with ~/.openclaw/openclaw.json):
export TG_READ_UNREAD=true
Env vars take priority over the config file. This lets you enable read_unread via openclaw.json Docker env alongside TG_API_ID/TG_API_HASH.
State is stored in ~/.tg-reader-state.json (configurable via "state_file" in config, TG_STATE_FILE env var, or --state-file flag).
--since is not needed when read_unread is enabled — the skill automatically returns all unread posts regardless of time--since applies as usual (default 24h); state file created--since is ignored--all flag: bypasses read_unread mode — fetches everything by --since without updating state (preserves your position)count: 0 returned# With read_unread enabled — just fetch, no --since needed
tg-reader fetch @channel_name
# First run for a new channel — --since determines initial window
tg-reader fetch @new_channel --since 7d
# Override: fetch everything, don't update tracking state
tg-reader fetch @channel_name --since 7d --all
When read_unread mode is active, the JSON output includes a read_unread field:
{
"channel": "@channel_name",
"read_unread": {"enabled": true},
"count": 5,
"messages": [...]
}
With --all: "read_unread": {"enabled": true, "overridden": true}
tg-reader-check reports tracking status:
{
"tracking": {
"read_unread": true,
"state_file": "~/.tg-reader-state.json",
"state_file_exists": true,
"tracked_channels": 3
}
}
info{
"id": -1001234567890,
"title": "Channel Name",
"username": "channel_name",
"description": "About this channel...",
"members_count": 42000,
"link": "https://t.me/channel_name"
}
fetch{
"channel": "@channel_name",
"fetched_at": "2026-02-22T10:00:00Z",
"since": "2026-02-21T10:00:00Z",
"count": 12,
"messages": [
{
"id": 1234,
"date": "2026-02-22T09:30:00Z",
"text": "Post content...",
"views": 5200,
"forwards": 34,
"link": "https://t.me/channel_name/1234",
"has_media": true,
"media_type": "MessageMediaType.PHOTO"
}
]
}
Optional web_page field — present only when a post carries a Telegram link-preview card (e.g. Instant View articles, link shares). Photos / videos / documents are reported via has_media + media_type, not here.
{
"id": 9876,
"text": "Card title\n\nCard description...\n\nhttps://example.com/article",
"has_media": false,
"web_page": {
"url": "https://example.com/article",
"display_url": "example.com/article",
"title": "Card title",
"description": "Card description...",
"site_name": "Example Site"
}
}
When the message has no text of its own (a common pattern for channels publishing via Instant View), the text field is synthesized from title + description + url so the post still surfaces. The web_page object carries the original structured data for agents that want them separately.
fetch with --comments{
"channel": "@channel_name",
"fetched_at": "2026-02-28T10:00:00Z",
"since": "2026-02-27T10:00:00Z",
"count": 5,
"comments_enabled": true,
"comments_available": true,
"messages": [
{
"id": 1234,
"text": "Post content...",
"has_media": false,
"comment_count": 2,
"comments": [
{
"id": 5678,
"date": "2026-02-28T09:35:00Z",
"text": "Great post!",
"from_user": "username123"
}
]
}
]
}
Notes:
comments_available: false — channel has no linked discussion group (no comments possible)comments_error on a message — rate limit hit for that post's commentsfrom_user may be null for anonymous comments--comments is active (override with --limit)has_media: true and a media_type field. Their text is in the text field (from the caption). Do not skip posts just because they have media — they often contain important text.web_page object (URL, title, description, site name). For Instant-View articles the text field is synthesized from the card, so these posts surface in summaries just like text posts — do not skip them even when has_media: false.comments_enabled: true, analyze comment sentiment and key themes alongside the main postsmemory/YYYY-MM-DD.md if user wants to track over timeUse --output when the result is large (especially with --comments) and you don't need to analyze it immediately. The full data goes to a file, and stdout returns only a short confirmation — this saves tokens.
Periodic updates pattern: set up a cron task that runs tg-reader fetch @channel --comments --output comments.json on schedule. The file gets updated regularly. When the user asks to analyze comments — read the file instead of re-fetching. This avoids consuming tokens on every fetch.
When --output is used without a filename, the default is tg-output.json. Stdout confirmation:
{"status": "ok", "output_file": "/absolute/path/to/tg-output.json", "count": 12}
Store tracked channels in TOOLS.md:
## Telegram Channels
- @channel1 — why tracked
- @channel2 — why tracked
Errors include an error_type and action field to help agents decide what to do automatically.
error_type | Meaning | action |
|---|---|---|
access_denied | Channel is private, you were kicked, or access is restricted | remove_from_list_or_rejoin — ask user if they still have access; if not, remove the channel |
banned | You are banned from this channel | remove_from_list — remove the channel, tell the user |
not_found | Channel doesn't exist or username is wrong | check_username — verify the @username with the user |
invite_expired | Invite link is expired or invalid | request_new_invite — ask user for a new invite link |
flood_wait | Telegram rate limit | wait_Ns — waits ≤ 60 s are retried automatically; longer waits return this error |
comments_multi_channel | --comments used with multiple channels | remove_extra_channels_or_drop_comments — use one channel at a time |
| Error | Action |
|---|---|
Session file not found | Run tg-reader-check — use the suggestion from output |
Missing credentials | Guide user through Setup (Step 1-2 below) |
tg-reader: command not found | Run bash setup-tg-reader.sh from the skill directory, or manually: pip install . Fallback: python3 -m tg_reader_unified |
AUTH_KEY_UNREGISTERED | Session expired — delete and re-auth (see below) |
rm -f ~/.tg-reader-session.session
tg-reader auth
Use the verbose debug script for full MTProto-level logs:
python3 debug_auth.py
Warning:
debug_auth.pydeletes existing session files before re-authenticating. It will ask for confirmation first.
Two MTProto backends are supported:
| Backend | Command | Notes |
|---|---|---|
| Pyrofork (default) | tg-reader or tg-reader-pyrogram | Drop-in fork of Pyrogram with current Telegram TL schema. Installs as the pyrogram package. The CLI command keeps the pyrogram name for backwards compatibility. |
| Telethon | tg-reader-telethon | Alternative backend with separate implementation |
Why pyrofork instead of pyrogram: the upstream
pyrogrampackage on PyPI (2.0.106, Aug 2023) does not parse TelegramMessageTL constructor IDs introduced in May 2026, so recent posts come through with emptymessage/media/entities.pyroforkis a community fork that ships the current schema. Sessions are format-compatible — switching does not require re-authentication.
Switch persistently: export TG_USE_TELETHON=true
Switch one-time: tg-reader fetch @channel --since 24h --telethon
Full details in README.md.
Go to https://my.telegram.org → API Development Tools → create an app → copy api_id and api_hash.
Recommended (works in agents and servers):
cat > ~/.tg-reader.json << 'EOF'
{
"api_id": YOUR_ID,
"api_hash": "YOUR_HASH"
}
EOF
chmod 600 ~/.tg-reader.json
Alternative (interactive shell only):
export TG_API_ID=YOUR_ID
export TG_API_HASH="YOUR_HASH"
Set these in your current shell session. Avoid writing TG_API_HASH to shell profiles (~/.bashrc) — use ~/.tg-reader.json instead for persistent storage.
Note: Agents and servers don't load shell profiles. Use
~/.tg-reader.json(the recommended method above) for non-interactive environments.
npx clawhub@latest install sergei-mikhailov-tg-channel-reader
cd ~/.openclaw/workspace/skills/sergei-mikhailov-tg-channel-reader
bash setup-tg-reader.sh
The setup script: installs Python packages (pip install .), checks credentials and session, runs tg-reader-check, and prints the exec approval commands for you to run manually.
On Linux with managed Python (Ubuntu/Debian), use a venv before running the setup script:
python3 -m venv ~/.venv/tg-reader
echo 'export PATH="$HOME/.venv/tg-reader/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
<details>
<summary>Manual install (without setup script)</summary>
cd ~/.openclaw/workspace/skills/sergei-mikhailov-tg-channel-reader
# pyrofork replaces pyrogram; uninstall pyrogram first if it was already installed
pip uninstall pyrogram -y 2>/dev/null
pip install pyrofork tgcrypto telethon && pip install .
openclaw approvals allowlist add --gateway "$(which tg-reader)"
openclaw approvals allowlist add --gateway "$(which tg-reader-check)"
</details>
tg-reader auth
Pyrogram will ask to confirm the phone number — answer y. The code arrives in the Telegram app (not SMS).
tg-reader-check
Should return "status": "ok". If not — fix the reported issues and re-run bash setup-tg-reader.sh.
This skill needs network access (MTProto connection to Telegram servers) and a session file. How you configure OpenClaw cron depends on the session target.
Important: When setting up a scheduled task that uses
tg-reader, tell the user which approach you're using and what it means — so they can make an informed choice.
sessionTarget: "main" (recommended)The cron task sends a reminder to the main agent session. The agent then runs tg-reader in the main environment where the skill, credentials, and session file are already available.
Pros: No extra configuration — everything works out of the box.
Cons: Not fully autonomous — the task sends a system event, the agent picks it up and executes. Requires payload.kind: "systemEvent" (OpenClaw cron API limitation for main target).
How to set up:
sessionTarget: "main" and payload.kind: "systemEvent"tg-reader command to runsessionTarget: "isolated" (autonomous, complex setup)The cron task runs in a Docker container — fully autonomous, no agent interaction needed. However, the container starts empty: no skill, no credentials, no session file.
Pros: Fully autonomous — runs on schedule without agent involvement. Cons: Requires Docker setup; session file must be mounted into the container (may not work reliably — session files are tied to the machine and Telegram may invalidate them in a new environment).
Required configuration in ~/.openclaw/openclaw.json:
{
"agents": {
"defaults": {
"sandbox": {
"docker": {
"setupCommand": "clawhub install sergei-mikhailov-tg-channel-reader && cd ~/.openclaw/workspace/skills/sergei-mikhailov-tg-channel-reader && pip uninstall pyrogram -y 2>/dev/null; pip install pyrofork tgcrypto telethon && pip install .",
"env": {
"TG_API_ID": "YOUR_ID",
"TG_API_HASH": "YOUR_HASH",
"TG_READ_UNREAD": "true"
}
}
}
}
}
}
Session file caveat: The Telegram session file (~/.tg-reader-session.session) must also be available inside the container. This may require Docker volume mounting and might not work reliably — Telegram can invalidate sessions when they appear from a different environment. If you encounter AUTH_KEY_UNREGISTERED errors in isolated mode, switch to Option A.
When ~/ is not available or points to a different location, use explicit paths:
tg-reader-check \
--config-file /home/user/.tg-reader.json \
--session-file /home/user/.tg-reader-session
tg-reader fetch @channel --since 6h \
--config-file /home/user/.tg-reader.json \
--session-file /home/user/.tg-reader-session
Both flags work with all subcommands and both backends.
~/.tg-reader-session.session) grants full account access — keep it safeTG_API_HASH or session filesTG_API_HASH is a secret — store in env vars or config file, never in git