We drive
personal Telegram over MTProto with
Telethon —
this acts as the user's own account (a "userbot"), so unlike the Bot API it can read full
history, list every conversation, and act on anyone the user can reach.
Credentials are injected as env vars by the connector:
- — app id
- — app hash — secret, never echo
- — Telethon = full account access. Never log,
echo, or print it. Treat it like the account password.
CLI
The skill ships
— self-contained (the only third-party dep is
, preinstalled in the sandbox). Point a var at the shipped path and call it; no heredoc
to re-create per turn, so a multi-step flow (dry-run → confirm) can't lose the helper between calls:
sh
TG="$SKILL_DIR/scripts/tg.py"
python3 "$TG" whoami
Every state-changing command (
,
,
,
,
,
,
,
) is
gated: without a trailing
it only DRY-RUNS (prints what
it would do, changes nothing). Read commands run directly.
is honored
only as the
last argument so a message/caption that merely contains "--confirm" can never silently confirm.
Verify the connection first
sh
python3 "$TG" whoami
# → {"id": 8367450178, "username": "GermeyAce", "name": "Germey", "phone": "..."}
On an auth/session error the stored session is dead — tell the user to reconnect at
https://auth.acedata.cloud/user/connections.
Read recipes
| Goal | Command |
|---|
| Recent conversations | python3 "$TG" list-chats 20
|
| Only chats with unread (ranked) | |
| A chat's history (oldest→newest) | python3 "$TG" get-messages <target> 50
|
| Search inside one chat | python3 "$TG" search <target> "kw" 30
|
| Search across ALL chats | python3 "$TG" search-global "kw" 30
|
| List contacts | |
| Info about a chat/user | python3 "$TG" chat-info <target>
|
| t.me link to a message | python3 "$TG" message-link <target> <msg_id>
|
= numeric id (most reliable — from
),
, phone, or exact chat
name. In message rows,
= sent by the user;
= has an attachment.
Summarize-unread pattern:
→ pick the chats that matter →
on
each → summarize. Don't dump 20k messages; sample the most-unread / most-relevant.
Media
sh
# Download an attachment from a message → returns the saved path
python3 "$TG" download-media <target> <msg_id> ./tg_downloads
# Send a local file OR an http(s) URL (optional caption) — GATED
python3 "$TG" send-file <target> /path/or/https-url "caption" --confirm
An
URL is downloaded to a real local file first (with the right
extension from the URL /
) and then uploaded, so a remote image
lands as a
photo — not a document, and not a silent failure. This is the
reliable way to "发图": pass the CDN URL straight to
.
To hand a downloaded file back to the user as a link, upload it to the CDN (see the
skill) after
.
Write recipes — all GATED (dry-run unless trailing )
Sending/editing/deleting acts as the
real user. Always run the dry run first, show the user
exactly what will happen, get an explicit "yes", then re-run with
as the
last
argument. Never bulk-send.
sh
python3 "$TG" send <target> "text" # → dry_run; add --confirm to send
python3 "$TG" reply <target> <msg_id> "text" --confirm
python3 "$TG" forward <from_target> <msg_id> <to_target> --confirm
python3 "$TG" edit <target> <msg_id> "new text" --confirm # own messages
python3 "$TG" delete <target> <msg_id> --confirm # destructive
python3 "$TG" react <target> <msg_id> "👍" --confirm
python3 "$TG" mark-read <target> --confirm # sends read receipts
The dry run returns
{"dry_run": true, "command": ..., "args": [...]}
— present that to the
user verbatim as the confirmation prompt.
Gotchas — surface before the user is surprised
- This is the user's real account. Confirm before any write; reading exposes private chats.
- : Telegram rate-limits userbots. On a flood-wait of N seconds, tell the
user to retry after N — never loop/retry aggressively (escalates toward a ban).
- Dead session: revoked from Telegram → Settings → Devices, or ~6-month inactivity. On
/unauthorized, reconnect the connector (don't retry).
- Never print / — full-account secrets.
- Targets: prefer the numeric from (the helper recovers its access hash by
scanning dialogs); names need an exact match, usernames need a leading .
- only works for public channels/supergroups; private 1:1 / basic groups
return an error (no shareable link exists).
- / generally only apply to the user's own messages (admins can delete others
in groups they manage).
Record the output
After you successfully publish and obtain the live result URL, call the built-in
tool ONCE so the user can track this deliverable in
My Outputs:
publish_artifact(kind="message", channel="telegram", title="<title>", url="<the REAL returned URL>", status="delivered")
Use the real returned URL — never fabricate one. Call it once per published item,
only after delivery is confirmed; skip it (or use
) if publishing failed.
See
.