Parsing public Telegram channels via the web preview (t.me/s/). No API keys, no MTProto, zero config.
Before launching
Before running any scripts, create a working copy of the skill in /home/claude/telegram-channel-parser and work from it:
bash scripts/prepare_runtime.sh
cd /home/claude/telegram-channel-parser
prepare_runtime.sh copies the skill and, if there is config/.env, normalizes it via sed -i: unquoted values with spaces or Cyrillic are wrapped in quotes in the working copy.
Config
No tokens are required. For digests — copy .env.example:
cp config/.env.example config/.env
Without .env: the skill works, but channels must be passed explicitly via --channel / --channels.
С .env: the AI channels digest is ready out of the box. The user can add their own categories.
Structure of .env (digest categories):
TG_CATEGORIES=ai,crypto # реестр доступных категорий
TG_DEFAULT_CATEGORY=ai # дефолтная при запросе "дайджест"
TG_CHANNELS_AI_LABEL="AI и технологии"
TG_CHANNELS_AI=countwithsasha,evilfreelancer,...
TG_CHANNELS_CRYPTO_LABEL="Криптовалюты"
TG_CHANNELS_CRYPTO=channel1,channel2
Agent algorithm when a digest is requested:
- Read
config/.env(if present) - Parse
TG_CATEGORIES— get the list of available categories - For each category:
TG_CHANNELS_<ID>= channels,TG_CHANNELS_<ID>_LABEL= name - Determine the needed category:
- The user named a topic → match it with
_LABEL - Did not specify → use
TG_DEFAULT_CATEGORY - Several categories fit → offer a choice
- The user named a topic → match it with
- Pass the channels of the needed category via
--channels
If .env is absent → ask which channels to parse, suggest cp config/.env.example config/.env.
Priority: --channels explicit > category from .env > agent asks.
Details: config/README.md.
Philosophy
- Always fresh — data is requested in real time on every call. You will never miss a fresh post.
- Context window hygiene — stdout is limited to 30 lines. Full data in TSV/CSV. The LLM works with a compact format, not with raw HTML.
- Rate limit — there is a 1.5s pause between requests to t.me. We don't get greedy.
- Pure POSIX sh — no dependencies other than curl, sed, awk, grep.
In the JSON digest and channel comparison, the last page is used both for posts and for channel information, without re-loading. On each run it is loaded anew; history pages are also requested anew as needed.
Workflow
Parsing a single channel
-
Get posts:
bash scripts/fetch_posts.sh --channel countwithsasha --limit 50Outputs the latest 50 posts in TSV (id, date, views, reactions, fwd_from, fwd_link, text, media_url).
-
Channel info:
bash scripts/channel_info.sh --channel countwithsasha -
Search posts:
bash scripts/search_posts.sh --channel countwithsasha --query "скилл" -
Top posts (share parade):
bash scripts/top_posts.sh --channel countwithsasha --limit 50 --sort reactions -
Posting schedule:
bash scripts/posting_schedule.sh --channel countwithsasha --limit 100 -
Export:
bash scripts/export_csv.sh --channel countwithsasha --limit 100 --csv cache/export.csv
Digest for multiple channels
# Явный список каналов
bash scripts/digest.sh --channels "countwithsasha,evilfreelancer,aostrikov_ai_agents" --period today
# Дефолтный набор (без --channels)
bash scripts/digest.sh --period today
Periods: today, yesterday, week, N (last N days).
Channel comparison
bash scripts/compare_channels.sh --channels "channel1,channel2,channel3" --limit 30
Table: subscribers, average views, posting frequency, engagement.
React artifact for the digest
When a digest is requested — render the results as a React artifact (a feed of cards).
Algorithm (4 steps):
- Run
digest_json.sh— it writes JSON to a file (not stdout!) and returns the path:bash scripts/digest_json.sh --period today # → prints: cache/digest_today.json - Read the JSON file at the output path
- Read the template
assets/digest-feed.tsx - Replace
__DIGEST_DATA__in the template with the JSON contents, render as a React artifact
Important: the script writes to a file rather than stdout in order to bypass the sandbox buffer limit (~200KB). For a 30-day digest of 15 channels, the JSON can be 500KB+.
Posts are automatically sorted by date (newest on top), interleaved between channels. The user filters by period and channel via the UI.
Scripts
General invocation pattern:
bash scripts/<script>.sh --channel <username> [--limit N] [--before <post_id>] [--csv path]
| Script | Description | Special params |
|---|---|---|
fetch_posts.sh |
Channel posts → TSV | --limit, --before, --after-date YYYY-MM-DD |
channel_info.sh |
Name, description, subscribers | — |
search_posts.sh |
Full-text search | --query "text" |
top_posts.sh |
Post ranking | --sort views|reactions, --limit |
posting_schedule.sh |
Posting time analysis | --limit |
export_csv.sh |
Export to CSV | --csv path |
digest.sh |
Digest of multiple channels | --channels "a,b,c", --period today|yesterday|week|N |
digest_json.sh |
Digest → JSON file (for a React artifact) | --channels "a,b,c", --period today|yesterday|week|N |
compare_channels.sh |
Comparison table | --channels "a,b,c", --limit N (positive integer) |
General parameters
| Param | Required | Default | Description |
|---|---|---|---|
--channel |
yes* | — | Channel username (without @) |
--channels |
no | from .env | Multiple channels separated by commas |
--limit |
no | 20 | How many posts to load |
--before |
no | — | Post ID for pagination |
--after-date |
no | — | Do not load posts older than the date (YYYY-MM-DD) |
--csv |
no | — | Path for export |
*--channel for a single channel, --channels for multi-channel commands.
Channel input
The skill accepts a channel in any format:
countwithsasha— just a username@countwithsasha— with an @https://t.me/countwithsasha— a direct linkhttps://t.me/s/countwithsasha— a web preview linkt.me/countwithsasha?before=500— with parameters
Everything is automatically normalized to a bare username.
Limitations
- Only public channels (those that have a t.me/s/ preview)
- Telegram does not serve some posts via the web preview. If instead of text you get a suggestion to open Telegram, the text field in TSV/JSON contains
[Текст недоступен в веб-превью Telegram: https://t.me/<channel>/<id>]. This is a skill marker, not the author's text: report the skip and give the link, do not retell such a post. Media without a caption remains with empty text if Telegram has not marked it as unsupported. - The share counter (shares) is unavailable — t.me/s/ does not provide it, only the MTProto API
- However, where the post was forwarded from is parsed (fwd_from + link to the original)
- Reactions are parsed in aggregate (total count across all emojis)
- Pagination: ~20 posts per page, for 100 posts = 5 requests
- Rate limit: 1.5s between requests to t.me