@acedatacloud/skills 2026.714.2 → 2026.714.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.714.
|
|
3
|
+
"version": "2026.714.4",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
package/skills/telegram/SKILL.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: telegram
|
|
3
|
-
description: Full personal Telegram control over MTProto (Telethon) with the user's own account — list/search chats, read & summarize history, see unread, look up contacts & chat info, download media, and send / reply / forward / edit / delete / react / send files / mark read. Use when the user mentions Telegram, a Telegram chat/group/contact, "我的 Telegram", reading/replying/forwarding/summarizing Telegram messages, their unread Telegram, or sending a file/message on Telegram.
|
|
3
|
+
description: Full personal Telegram control over MTProto (Telethon) with the user's own account — list/search chats, read & summarize history, see unread, look up contacts & chat info, list a forum group's topics, download media, and send / reply / forward / edit / delete / react / send files / mark read / join & leave groups, including posting into a forum topic thread. Use when the user mentions Telegram, a Telegram chat/group/contact, "我的 Telegram", reading/replying/forwarding/summarizing Telegram messages, their unread Telegram, joining or leaving a Telegram group/channel, posting into a forum group's topic, or sending a file/message on Telegram.
|
|
4
4
|
when_to_use: |
|
|
5
5
|
Trigger for anything on the user's personal Telegram account: list recent
|
|
6
6
|
conversations or just the unread ones, read / summarize a chat or group,
|
|
7
7
|
search one chat or across all chats, look up a contact or a chat's info,
|
|
8
|
-
download a photo/file from a message,
|
|
9
|
-
forward, edit, delete, react, send a file,
|
|
8
|
+
download a photo/file from a message, list a forum group's topics, or take an
|
|
9
|
+
action — send, reply, forward, edit, delete, react, send a file, mark a chat
|
|
10
|
+
read, post into a forum topic, or join / leave a group or channel. This drives
|
|
10
11
|
the user's OWN account over MTProto (not a bot), so it sees everything they see.
|
|
11
12
|
connections: [telegram]
|
|
12
13
|
allowed_tools: [Bash]
|
|
13
14
|
license: Apache-2.0
|
|
14
15
|
metadata:
|
|
15
16
|
author: acedatacloud
|
|
16
|
-
version: "1.
|
|
17
|
+
version: "1.4"
|
|
17
18
|
---
|
|
18
19
|
|
|
19
20
|
We drive **personal** Telegram over MTProto with [Telethon](https://docs.telethon.dev/) —
|
|
@@ -42,7 +43,7 @@ python3 "$TG" whoami
|
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
Every state-changing command (`send`, `reply`, `send-file`, `forward`, `edit`, `delete`,
|
|
45
|
-
`react`, `mark-read`) is **gated**: without a trailing `--confirm` it only DRY-RUNS (prints what
|
|
46
|
+
`react`, `mark-read`, `join`, `leave`) is **gated**: without a trailing `--confirm` it only DRY-RUNS (prints what
|
|
46
47
|
it would do, changes nothing). Read commands run directly. `--confirm` is honored **only as the
|
|
47
48
|
last argument** so a message/caption that merely contains "--confirm" can never silently confirm.
|
|
48
49
|
|
|
@@ -67,6 +68,7 @@ https://auth.acedata.cloud/user/connections.
|
|
|
67
68
|
| Search across ALL chats | `python3 "$TG" search-global "kw" 30` |
|
|
68
69
|
| List contacts | `python3 "$TG" contacts` |
|
|
69
70
|
| Info about a chat/user | `python3 "$TG" chat-info <target>` |
|
|
71
|
+
| List a forum group's topics (threads) | `python3 "$TG" list-topics <target>` |
|
|
70
72
|
| t.me link to a message | `python3 "$TG" message-link <target> <msg_id>` |
|
|
71
73
|
|
|
72
74
|
`<target>` = numeric id (most reliable — from `list-chats`), `@username`, phone, or exact chat
|
|
@@ -100,14 +102,30 @@ argument**. Never bulk-send.
|
|
|
100
102
|
|
|
101
103
|
```sh
|
|
102
104
|
python3 "$TG" send <target> "text" # → dry_run; add --confirm to send
|
|
105
|
+
python3 "$TG" send <target> "text" --topic <top_message> --confirm # into a forum topic thread
|
|
103
106
|
python3 "$TG" reply <target> <msg_id> "text" --confirm
|
|
104
107
|
python3 "$TG" forward <from_target> <msg_id> <to_target> --confirm
|
|
105
108
|
python3 "$TG" edit <target> <msg_id> "new text" --confirm # own messages
|
|
106
109
|
python3 "$TG" delete <target> <msg_id> --confirm # destructive
|
|
107
110
|
python3 "$TG" react <target> <msg_id> "👍" --confirm
|
|
108
111
|
python3 "$TG" mark-read <target> --confirm # sends read receipts
|
|
112
|
+
python3 "$TG" join <@username|t.me/link|t.me/+invite> --confirm # join a public group/channel or a private invite
|
|
113
|
+
python3 "$TG" leave <target> --confirm # leave a group/channel (not private chats)
|
|
109
114
|
```
|
|
110
115
|
|
|
116
|
+
`join` accepts a public `@username` / `t.me/<name>` (→ JoinChannelRequest) or a private
|
|
117
|
+
invite link `t.me/+HASH` / `t.me/joinchat/HASH` (→ ImportChatInviteRequest). Joining someone
|
|
118
|
+
else's group is a real membership change on the account — treat it like any other write:
|
|
119
|
+
dry-run, confirm, then `--confirm`. Never auto-join then bulk-message; that gets accounts
|
|
120
|
+
spam-limited.
|
|
121
|
+
|
|
122
|
+
**Posting into a forum group.** Some supergroups are *forums*: messages live in topics
|
|
123
|
+
(threads), and a plain `send` to the group root is rejected with `TOPIC_CLOSED`. Run
|
|
124
|
+
`list-topics <target>` first, pick a topic with `"closed": false` (an open chat/offtopic
|
|
125
|
+
thread — often titled `Беседка`/`Флуд`/`Chat`/`Offtopic`/`General`), then post with
|
|
126
|
+
`send <target> "text" --topic <top_message> --confirm` (its `top_message` id is the thread
|
|
127
|
+
anchor).
|
|
128
|
+
|
|
111
129
|
The dry run returns `{"dry_run": true, "command": ..., "args": [...]}` — present that to the
|
|
112
130
|
user verbatim as the confirmation prompt.
|
|
113
131
|
|
|
@@ -123,6 +141,11 @@ user verbatim as the confirmation prompt.
|
|
|
123
141
|
scanning dialogs); names need an exact match, usernames need a leading `@`.
|
|
124
142
|
- **`message-link`** only works for public channels/supergroups; private 1:1 / basic groups
|
|
125
143
|
return an error (no shareable link exists).
|
|
144
|
+
- **`TOPIC_CLOSED` on send** = a forum supergroup; you can't post to the root. `list-topics`,
|
|
145
|
+
pick an open (`"closed": false`) thread, and `send ... --topic <top_message>`.
|
|
146
|
+
- **`ChatWriteForbiddenError` on send** = usually a channel's linked *discussion* group: you
|
|
147
|
+
must be subscribed to the parent channel (`join <parent_channel>`) before you can write in
|
|
148
|
+
its comment group, even though the group itself doesn't ban posting.
|
|
126
149
|
- **`edit`/`delete`** generally only apply to the user's own messages (admins can delete others
|
|
127
150
|
in groups they manage).
|
|
128
151
|
|
|
@@ -19,10 +19,10 @@ import tempfile
|
|
|
19
19
|
import urllib.request
|
|
20
20
|
from urllib.parse import urlparse
|
|
21
21
|
|
|
22
|
-
from telethon import TelegramClient
|
|
22
|
+
from telethon import TelegramClient, errors, utils
|
|
23
23
|
from telethon.sessions import StringSession
|
|
24
24
|
from telethon.tl import functions
|
|
25
|
-
from telethon.tl.types import ReactionEmoji
|
|
25
|
+
from telethon.tl.types import ReactionEmoji, User
|
|
26
26
|
|
|
27
27
|
API_ID = int(os.environ["TELEGRAM_API_ID"])
|
|
28
28
|
API_HASH = os.environ["TELEGRAM_API_HASH"]
|
|
@@ -35,7 +35,7 @@ CONFIRM = bool(_raw) and _raw[-1] == "--confirm"
|
|
|
35
35
|
a = _raw[:-1] if CONFIRM else list(_raw)
|
|
36
36
|
cmd = a[0] if a else "help"
|
|
37
37
|
args = a[1:]
|
|
38
|
-
GATED = {"send", "reply", "send-file", "forward", "edit", "delete", "react", "mark-read"}
|
|
38
|
+
GATED = {"send", "reply", "send-file", "forward", "edit", "delete", "react", "mark-read", "join", "leave"}
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def out(o):
|
|
@@ -117,6 +117,19 @@ def need(n):
|
|
|
117
117
|
raise ValueError(f"{cmd} needs {n} argument(s), got {len(args)}")
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
def _pop_opt(argv, name):
|
|
121
|
+
"""Extract `--name VALUE` from argv; return (value_or_None, remaining_argv).
|
|
122
|
+
|
|
123
|
+
Only a standalone `--name` token matches, so a message that merely contains
|
|
124
|
+
the flag text as part of one argument is left untouched.
|
|
125
|
+
"""
|
|
126
|
+
if name in argv:
|
|
127
|
+
i = argv.index(name)
|
|
128
|
+
if i + 1 < len(argv):
|
|
129
|
+
return argv[i + 1], argv[:i] + argv[i + 2:]
|
|
130
|
+
return None, argv
|
|
131
|
+
|
|
132
|
+
|
|
120
133
|
async def run():
|
|
121
134
|
if cmd in GATED and not CONFIRM:
|
|
122
135
|
out({"dry_run": True, "command": cmd, "args": args,
|
|
@@ -185,6 +198,18 @@ async def run():
|
|
|
185
198
|
pass
|
|
186
199
|
out(info)
|
|
187
200
|
|
|
201
|
+
elif cmd == "list-topics":
|
|
202
|
+
# Forum-style supergroups organize messages into topics (threads).
|
|
203
|
+
# A plain send to the group root is rejected (TOPIC_CLOSED); you must
|
|
204
|
+
# post into an open topic via `send --topic <top_message>`.
|
|
205
|
+
need(1); ent = await resolve(cl, args[0])
|
|
206
|
+
limit = int(args[1]) if len(args) > 1 else 100
|
|
207
|
+
res = await cl(functions.messages.GetForumTopicsRequest(
|
|
208
|
+
channel=ent, offset_date=0, offset_id=0, offset_topic=0, limit=limit))
|
|
209
|
+
out([{"id": getattr(t, "id", None), "title": getattr(t, "title", None),
|
|
210
|
+
"closed": getattr(t, "closed", None), "hidden": getattr(t, "hidden", None),
|
|
211
|
+
"top_message": getattr(t, "top_message", None)} for t in res.topics])
|
|
212
|
+
|
|
188
213
|
elif cmd == "message-link":
|
|
189
214
|
need(2); ent = await resolve(cl, args[0]); mid = int(args[1])
|
|
190
215
|
try:
|
|
@@ -205,9 +230,15 @@ async def run():
|
|
|
205
230
|
|
|
206
231
|
# ---- gated writes (need trailing --confirm) ----
|
|
207
232
|
elif cmd == "send":
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
# `--topic <top_message>` posts into a forum topic thread (reply_to);
|
|
234
|
+
# required for forum-style supergroups where a root send is rejected.
|
|
235
|
+
topic, sargs = _pop_opt(args, "--topic")
|
|
236
|
+
if len(sargs) < 2:
|
|
237
|
+
raise ValueError("send needs <target> <text> (optional --topic <id>)")
|
|
238
|
+
ent = await resolve(cl, sargs[0])
|
|
239
|
+
kw = {"reply_to": int(topic)} if topic is not None else {}
|
|
240
|
+
m = await cl.send_message(ent, sargs[1], **kw)
|
|
241
|
+
out({"sent": True, "id": m.id, "topic": int(topic) if topic is not None else None})
|
|
211
242
|
|
|
212
243
|
elif cmd == "reply":
|
|
213
244
|
need(3); ent = await resolve(cl, args[0])
|
|
@@ -252,6 +283,33 @@ async def run():
|
|
|
252
283
|
await cl.send_read_acknowledge(ent)
|
|
253
284
|
out({"marked_read": True})
|
|
254
285
|
|
|
286
|
+
elif cmd == "join":
|
|
287
|
+
need(1); target = args[0]
|
|
288
|
+
# A public @username / t.me/<user> resolves + JoinChannelRequest; a
|
|
289
|
+
# private invite (t.me/+HASH, joinchat/HASH, tg://join?invite=HASH)
|
|
290
|
+
# can't be resolved without joining, so import it by hash directly.
|
|
291
|
+
link_hash, is_invite = utils.parse_username(target)
|
|
292
|
+
try:
|
|
293
|
+
if is_invite:
|
|
294
|
+
res = await cl(functions.messages.ImportChatInviteRequest(link_hash))
|
|
295
|
+
chat = (getattr(res, "chats", None) or [None])[0]
|
|
296
|
+
out({"joined": True, "via": "invite",
|
|
297
|
+
"id": getattr(chat, "id", None), "title": getattr(chat, "title", None)})
|
|
298
|
+
else:
|
|
299
|
+
ent = await resolve(cl, target)
|
|
300
|
+
await cl(functions.channels.JoinChannelRequest(ent))
|
|
301
|
+
out({"joined": True, "via": "public", "id": ent.id,
|
|
302
|
+
"title": getattr(ent, "title", None), "username": getattr(ent, "username", None)})
|
|
303
|
+
except errors.UserAlreadyParticipantError:
|
|
304
|
+
out({"joined": True, "already_member": True, "target": target})
|
|
305
|
+
|
|
306
|
+
elif cmd == "leave":
|
|
307
|
+
need(1); ent = await resolve(cl, args[0])
|
|
308
|
+
if isinstance(ent, User):
|
|
309
|
+
out({"error": "leave applies to groups/channels, not private chats"}); return
|
|
310
|
+
await cl.delete_dialog(ent)
|
|
311
|
+
out({"left": True, "id": ent.id})
|
|
312
|
+
|
|
255
313
|
else:
|
|
256
314
|
out({"error": f"unknown command: {cmd}"}); sys.exit(1)
|
|
257
315
|
|