telegram-userbot-cli 0.1.0__tar.gz → 0.3.0__tar.gz
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.
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/PKG-INFO +62 -4
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/README-zh.md +56 -3
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/README.md +61 -3
- telegram_userbot_cli-0.3.0/ROADMAP.md +75 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/pyproject.toml +1 -1
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/__init__.py +1 -1
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/chats.py +7 -1
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/cli.py +2 -2
- telegram_userbot_cli-0.3.0/src/tgcli/dialogs.py +269 -0
- telegram_userbot_cli-0.3.0/src/tgcli/groups.py +534 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/media.py +44 -1
- telegram_userbot_cli-0.3.0/src/tgcli/messages.py +365 -0
- telegram_userbot_cli-0.1.0/ROADMAP.md +0 -64
- telegram_userbot_cli-0.1.0/src/tgcli/messages.py +0 -122
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.env.example +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.github/workflows/ci.yml +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.github/workflows/release.yml +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.gitignore +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/LICENSE +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/__main__.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/auth.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/client.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/config.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/output.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/resolve.py +0 -0
- {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: telegram-userbot-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Full-featured Telegram command-line client powered by a Telethon userbot (your own account, not a bot token)
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -25,9 +25,16 @@ no daemon, no external service**. Everything the CLI does lives in the CLI.
|
|
|
25
25
|
## Features
|
|
26
26
|
|
|
27
27
|
- QR-code login (`tg login --qr`) with an independent session
|
|
28
|
-
- Messaging: send / reply / edit / delete / forward / polls
|
|
29
|
-
|
|
30
|
-
-
|
|
28
|
+
- Messaging: send / reply / edit / delete / forward / polls / schedule /
|
|
29
|
+
drafts / reactions / pinning
|
|
30
|
+
- Group administration: create groups/channels, invite / kick / ban,
|
|
31
|
+
admin promotion with granular rights, slow mode, forum topics, admin log
|
|
32
|
+
- Chat management: archive, mute, edit title / description / photo,
|
|
33
|
+
invite links, clear history
|
|
34
|
+
- Media: download, send files / albums / voice notes / stickers / GIFs,
|
|
35
|
+
contact cards
|
|
36
|
+
- Chats: list dialogs, history, pinned messages, in-chat & global search,
|
|
37
|
+
online status, common chats
|
|
31
38
|
- Contacts & profile management
|
|
32
39
|
- Chat resolution by name fuzzy match, `@username`, numeric id, or `me`
|
|
33
40
|
(Saved Messages), plus local aliases
|
|
@@ -79,6 +86,8 @@ tg logout # remove the session
|
|
|
79
86
|
|
|
80
87
|
## Commands
|
|
81
88
|
|
|
89
|
+
### Core
|
|
90
|
+
|
|
82
91
|
| Command | Description |
|
|
83
92
|
|---|---|
|
|
84
93
|
| `tg me` | Show your account info |
|
|
@@ -100,6 +109,55 @@ tg logout # remove the session
|
|
|
100
109
|
| `tg profile --name/--bio/--photo` | Update your profile |
|
|
101
110
|
| `tg alias set/list/rm` | Local chat aliases (`~/.config/tg/aliases.json`) |
|
|
102
111
|
|
|
112
|
+
### Messaging & chat management (v2)
|
|
113
|
+
|
|
114
|
+
| Command | Description |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `tg schedule <chat> <time> <text...>` | Schedule a message; time is `+30m`/`+2h`/`+1d`, `2026-09-07 14:30`, or a unix ts |
|
|
117
|
+
| `tg scheduled <chat>` | List scheduled messages (`--json` supported) |
|
|
118
|
+
| `tg unschedule <chat> <ids...>` | Delete scheduled messages |
|
|
119
|
+
| `tg draft <chat> [text]` | Save a draft (with text) or show the current draft |
|
|
120
|
+
| `tg draft-clear <chat>` | Clear the draft |
|
|
121
|
+
| `tg react <chat> <msg_id> <emoji>` | React to a message; `--big` for the big animation |
|
|
122
|
+
| `tg unreact <chat> <msg_id>` | Remove your reaction |
|
|
123
|
+
| `tg pin <chat> <msg_id>` / `tg unpin <chat> [msg_id]` | Pin / unpin (no id = unpin all) |
|
|
124
|
+
| `tg read <chat>` | Mark the chat as read (also clears mention badges) |
|
|
125
|
+
| `tg msg-link <chat> <msg_id>` | Export a t.me message link (channels/supergroups only) |
|
|
126
|
+
| `tg contact-card <chat> "<name>" <phone>` | Send a contact card |
|
|
127
|
+
| `tg sticker <chat> <file>` | Send a sticker file (`.webp` / `.tgs` / `.webm`) |
|
|
128
|
+
| `tg gif <chat> <query>` | Search GIFs and send the first match; `-n N` sends the Nth |
|
|
129
|
+
| `tg archive <chat>` / `tg unarchive <chat>` | Archive / unarchive a dialog |
|
|
130
|
+
| `tg mute <chat> [hours]` / `tg unmute <chat>` | Mute notifications (permanent unless hours given) / unmute |
|
|
131
|
+
| `tg chat-title <chat> <title>` | Change group/channel title |
|
|
132
|
+
| `tg chat-about <chat> <text>` | Change chat description |
|
|
133
|
+
| `tg chat-photo <chat> <file>` | Change the chat photo |
|
|
134
|
+
| `tg invite-link <chat>` | Export a chat invite link |
|
|
135
|
+
| `tg join <link>` | Join via `t.me/+hash`, `t.me/joinchat/…`, or `t.me/<username>` |
|
|
136
|
+
| `tg clear-history <chat>` | Delete all messages (both sides; `--self-only` keeps the other copy) |
|
|
137
|
+
| `tg status <user>` | Show a user's online status |
|
|
138
|
+
| `tg common-chats <user>` | List chats you share with a user |
|
|
139
|
+
|
|
140
|
+
### Group administration (v3)
|
|
141
|
+
|
|
142
|
+
| Command | Description |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `tg new-group <title> <user...>` | Create a basic group with initial members |
|
|
145
|
+
| `tg new-channel <title> [--about text]` | Create a channel; `--group` creates a supergroup |
|
|
146
|
+
| `tg invite <chat> <user...>` | Invite users (supergroups & basic groups) |
|
|
147
|
+
| `tg kick <chat> <user...>` | Remove users (supergroup kick = view-messages ban; `unban` to reverse) |
|
|
148
|
+
| `tg leave <chat>` | Leave a group/channel |
|
|
149
|
+
| `tg ban <chat> <user> [days]` | Ban a user (permanent unless days given); supergroups only |
|
|
150
|
+
| `tg unban <chat> <user>` | Unban a user |
|
|
151
|
+
| `tg banned <chat>` | List banned users (`--json` supported) |
|
|
152
|
+
| `tg admins <chat>` | List admins with ranks |
|
|
153
|
+
| `tg promote <chat> <user> [--title rank]` | Promote with the standard right set |
|
|
154
|
+
| `tg demote <chat> <user>` | Remove admin rights |
|
|
155
|
+
| `tg admin-rights <chat> <user> <flags...>` | Set exact rights: `--change-info --post-messages --edit-messages --delete-messages --ban-users --invite-users --pin-messages --add-admins --anonymous --manage-call --other --manage-topics …`; `--rank` |
|
|
156
|
+
| `tg slow-mode <chat> [seconds]` | Set slow mode (0 = disable); supergroups only |
|
|
157
|
+
| `tg topics <chat>` | List forum topics |
|
|
158
|
+
| `tg topic-create <chat> <title> [text]` | Create a forum topic, optionally with a first message |
|
|
159
|
+
| `tg admin-log <chat> [-n]` | Recent admin actions |
|
|
160
|
+
|
|
103
161
|
Global flags: `--account` (v1: default only), `--json` (raw JSON output).
|
|
104
162
|
Exit codes: `0` ok, `1` command error, `2` config/connection/session error,
|
|
105
163
|
`130` interrupted.
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
## 特性
|
|
13
13
|
|
|
14
14
|
- 扫码登录(`tg login --qr`),使用独立会话
|
|
15
|
-
- 消息:发送 / 引用回复 / 编辑 / 删除 / 转发 / 投票
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- 消息:发送 / 引用回复 / 编辑 / 删除 / 转发 / 投票 / 定时 / 草稿 / 表情回应 / 置顶
|
|
16
|
+
- 群组管理:建群 / 建频道、邀请 / 踢人 / 封禁、管理员升降级(细粒度权限)、慢速模式、论坛话题、管理日志
|
|
17
|
+
- 聊天管理:归档、免打扰、修改群名 / 简介 / 群头像、邀请链接、清空历史
|
|
18
|
+
- 媒体:下载、发文件 / 相册 / 语音条 / 贴纸 / GIF、发送名片
|
|
19
|
+
- 聊天:会话列表、历史记录、置顶消息、会话内与全局搜索、在线状态、共同群组
|
|
18
20
|
- 联系人与个人资料管理
|
|
19
21
|
- 会话解析:名称模糊匹配、`@username`、数字 id、`me`(已保存消息),以及本地别名
|
|
20
22
|
|
|
@@ -62,6 +64,8 @@ tg logout # 退出登录并删除会话
|
|
|
62
64
|
|
|
63
65
|
## 命令
|
|
64
66
|
|
|
67
|
+
### 基础
|
|
68
|
+
|
|
65
69
|
| 命令 | 说明 |
|
|
66
70
|
|---|---|
|
|
67
71
|
| `tg me` | 查看自己的账号信息 |
|
|
@@ -83,6 +87,55 @@ tg logout # 退出登录并删除会话
|
|
|
83
87
|
| `tg profile --name/--bio/--photo` | 修改个人资料 |
|
|
84
88
|
| `tg alias set/list/rm` | 本地会话别名(`~/.config/tg/aliases.json`) |
|
|
85
89
|
|
|
90
|
+
### 消息增强与聊天管理(v2)
|
|
91
|
+
|
|
92
|
+
| 命令 | 说明 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `tg schedule <会话> <时间> <文本...>` | 定时发送;时间支持 `+30m`/`+2h`/`+1d`、`2026-09-07 14:30` 或 unix 时间戳 |
|
|
95
|
+
| `tg scheduled <会话>` | 列出定时消息(支持 `--json`) |
|
|
96
|
+
| `tg unschedule <会话> <id...>` | 撤销定时消息 |
|
|
97
|
+
| `tg draft <会话> [文本]` | 存草稿(带文本)或查看当前草稿 |
|
|
98
|
+
| `tg draft-clear <会话>` | 清空草稿 |
|
|
99
|
+
| `tg react <会话> <消息id> <emoji>` | 对消息发表情回应;`--big` 大动画 |
|
|
100
|
+
| `tg unreact <会话> <消息id>` | 撤销自己的表情回应 |
|
|
101
|
+
| `tg pin <会话> <消息id>` / `tg unpin <会话> [消息id]` | 置顶 / 取消置顶(不带 id = 全部取消) |
|
|
102
|
+
| `tg read <会话>` | 标记已读(同时清除 @ 提醒) |
|
|
103
|
+
| `tg msg-link <会话> <消息id>` | 导出 t.me 消息链接(仅频道/超级群组) |
|
|
104
|
+
| `tg contact-card <会话> "<姓名>" <电话>` | 发送联系人名片 |
|
|
105
|
+
| `tg sticker <会话> <文件>` | 发贴纸文件(`.webp` / `.tgs` / `.webm`) |
|
|
106
|
+
| `tg gif <会话> <关键词>` | 搜索 GIF 并发送第一条;`-n N` 发第 N 条 |
|
|
107
|
+
| `tg archive <会话>` / `tg unarchive <会话>` | 归档 / 取消归档会话 |
|
|
108
|
+
| `tg mute <会话> [小时]` / `tg unmute <会话>` | 免打扰(不带小时数 = 永久)/ 取消免打扰 |
|
|
109
|
+
| `tg chat-title <会话> <标题>` | 修改群/频道标题 |
|
|
110
|
+
| `tg chat-about <会话> <文本>` | 修改群简介 |
|
|
111
|
+
| `tg chat-photo <会话> <文件>` | 修改群头像 |
|
|
112
|
+
| `tg invite-link <会话>` | 导出群邀请链接 |
|
|
113
|
+
| `tg join <链接>` | 通过 `t.me/+hash`、`t.me/joinchat/…` 或 `t.me/<用户名>` 加群 |
|
|
114
|
+
| `tg clear-history <会话>` | 清空聊天记录(双方删除;`--self-only` 仅删自己一侧) |
|
|
115
|
+
| `tg status <用户>` | 查看用户在线状态 |
|
|
116
|
+
| `tg common-chats <用户>` | 列出与该用户的共同群组 |
|
|
117
|
+
|
|
118
|
+
### 群组管理(v3)
|
|
119
|
+
|
|
120
|
+
| 命令 | 说明 |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `tg new-group <标题> <用户...>` | 建基本群并拉入初始成员 |
|
|
123
|
+
| `tg new-channel <标题> [--about 简介]` | 建频道;`--group` 建超级群组 |
|
|
124
|
+
| `tg invite <会话> <用户...>` | 邀请用户(超级群组与基本群都支持) |
|
|
125
|
+
| `tg kick <会话> <用户...>` | 移出用户(超级群组的踢人 = 禁言封禁,`unban` 可恢复) |
|
|
126
|
+
| `tg leave <会话>` | 退出群组/频道 |
|
|
127
|
+
| `tg ban <会话> <用户> [天数]` | 封禁(不带天数 = 永久);仅超级群组 |
|
|
128
|
+
| `tg unban <会话> <用户>` | 解除封禁 |
|
|
129
|
+
| `tg banned <会话>` | 封禁名单(支持 `--json`) |
|
|
130
|
+
| `tg admins <会话>` | 管理员列表(含头衔) |
|
|
131
|
+
| `tg promote <会话> <用户> [--title 头衔]` | 升级为管理员(标准权限集) |
|
|
132
|
+
| `tg demote <会话> <用户>` | 撤销管理员 |
|
|
133
|
+
| `tg admin-rights <会话> <用户> <权限...>` | 设置精确权限:`--change-info --post-messages --edit-messages --delete-messages --ban-users --invite-users --pin-messages --add-admins --anonymous --manage-call --other --manage-topics …`;`--rank` 定头衔 |
|
|
134
|
+
| `tg slow-mode <会话> [秒]` | 设置慢速模式(0 = 关闭);仅超级群组 |
|
|
135
|
+
| `tg topics <会话>` | 列出论坛话题 |
|
|
136
|
+
| `tg topic-create <会话> <标题> [首条消息]` | 创建论坛话题 |
|
|
137
|
+
| `tg admin-log <会话> [-n]` | 近期管理操作日志 |
|
|
138
|
+
|
|
86
139
|
全局选项:`--account`(v1 仅 default)、`--json`(原始 JSON 输出)。
|
|
87
140
|
退出码:`0` 成功、`1` 命令错误、`2` 配置/连接/会话错误、`130` 中断。
|
|
88
141
|
|
|
@@ -12,9 +12,16 @@ no daemon, no external service**. Everything the CLI does lives in the CLI.
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- QR-code login (`tg login --qr`) with an independent session
|
|
15
|
-
- Messaging: send / reply / edit / delete / forward / polls
|
|
16
|
-
|
|
17
|
-
-
|
|
15
|
+
- Messaging: send / reply / edit / delete / forward / polls / schedule /
|
|
16
|
+
drafts / reactions / pinning
|
|
17
|
+
- Group administration: create groups/channels, invite / kick / ban,
|
|
18
|
+
admin promotion with granular rights, slow mode, forum topics, admin log
|
|
19
|
+
- Chat management: archive, mute, edit title / description / photo,
|
|
20
|
+
invite links, clear history
|
|
21
|
+
- Media: download, send files / albums / voice notes / stickers / GIFs,
|
|
22
|
+
contact cards
|
|
23
|
+
- Chats: list dialogs, history, pinned messages, in-chat & global search,
|
|
24
|
+
online status, common chats
|
|
18
25
|
- Contacts & profile management
|
|
19
26
|
- Chat resolution by name fuzzy match, `@username`, numeric id, or `me`
|
|
20
27
|
(Saved Messages), plus local aliases
|
|
@@ -66,6 +73,8 @@ tg logout # remove the session
|
|
|
66
73
|
|
|
67
74
|
## Commands
|
|
68
75
|
|
|
76
|
+
### Core
|
|
77
|
+
|
|
69
78
|
| Command | Description |
|
|
70
79
|
|---|---|
|
|
71
80
|
| `tg me` | Show your account info |
|
|
@@ -87,6 +96,55 @@ tg logout # remove the session
|
|
|
87
96
|
| `tg profile --name/--bio/--photo` | Update your profile |
|
|
88
97
|
| `tg alias set/list/rm` | Local chat aliases (`~/.config/tg/aliases.json`) |
|
|
89
98
|
|
|
99
|
+
### Messaging & chat management (v2)
|
|
100
|
+
|
|
101
|
+
| Command | Description |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `tg schedule <chat> <time> <text...>` | Schedule a message; time is `+30m`/`+2h`/`+1d`, `2026-09-07 14:30`, or a unix ts |
|
|
104
|
+
| `tg scheduled <chat>` | List scheduled messages (`--json` supported) |
|
|
105
|
+
| `tg unschedule <chat> <ids...>` | Delete scheduled messages |
|
|
106
|
+
| `tg draft <chat> [text]` | Save a draft (with text) or show the current draft |
|
|
107
|
+
| `tg draft-clear <chat>` | Clear the draft |
|
|
108
|
+
| `tg react <chat> <msg_id> <emoji>` | React to a message; `--big` for the big animation |
|
|
109
|
+
| `tg unreact <chat> <msg_id>` | Remove your reaction |
|
|
110
|
+
| `tg pin <chat> <msg_id>` / `tg unpin <chat> [msg_id]` | Pin / unpin (no id = unpin all) |
|
|
111
|
+
| `tg read <chat>` | Mark the chat as read (also clears mention badges) |
|
|
112
|
+
| `tg msg-link <chat> <msg_id>` | Export a t.me message link (channels/supergroups only) |
|
|
113
|
+
| `tg contact-card <chat> "<name>" <phone>` | Send a contact card |
|
|
114
|
+
| `tg sticker <chat> <file>` | Send a sticker file (`.webp` / `.tgs` / `.webm`) |
|
|
115
|
+
| `tg gif <chat> <query>` | Search GIFs and send the first match; `-n N` sends the Nth |
|
|
116
|
+
| `tg archive <chat>` / `tg unarchive <chat>` | Archive / unarchive a dialog |
|
|
117
|
+
| `tg mute <chat> [hours]` / `tg unmute <chat>` | Mute notifications (permanent unless hours given) / unmute |
|
|
118
|
+
| `tg chat-title <chat> <title>` | Change group/channel title |
|
|
119
|
+
| `tg chat-about <chat> <text>` | Change chat description |
|
|
120
|
+
| `tg chat-photo <chat> <file>` | Change the chat photo |
|
|
121
|
+
| `tg invite-link <chat>` | Export a chat invite link |
|
|
122
|
+
| `tg join <link>` | Join via `t.me/+hash`, `t.me/joinchat/…`, or `t.me/<username>` |
|
|
123
|
+
| `tg clear-history <chat>` | Delete all messages (both sides; `--self-only` keeps the other copy) |
|
|
124
|
+
| `tg status <user>` | Show a user's online status |
|
|
125
|
+
| `tg common-chats <user>` | List chats you share with a user |
|
|
126
|
+
|
|
127
|
+
### Group administration (v3)
|
|
128
|
+
|
|
129
|
+
| Command | Description |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `tg new-group <title> <user...>` | Create a basic group with initial members |
|
|
132
|
+
| `tg new-channel <title> [--about text]` | Create a channel; `--group` creates a supergroup |
|
|
133
|
+
| `tg invite <chat> <user...>` | Invite users (supergroups & basic groups) |
|
|
134
|
+
| `tg kick <chat> <user...>` | Remove users (supergroup kick = view-messages ban; `unban` to reverse) |
|
|
135
|
+
| `tg leave <chat>` | Leave a group/channel |
|
|
136
|
+
| `tg ban <chat> <user> [days]` | Ban a user (permanent unless days given); supergroups only |
|
|
137
|
+
| `tg unban <chat> <user>` | Unban a user |
|
|
138
|
+
| `tg banned <chat>` | List banned users (`--json` supported) |
|
|
139
|
+
| `tg admins <chat>` | List admins with ranks |
|
|
140
|
+
| `tg promote <chat> <user> [--title rank]` | Promote with the standard right set |
|
|
141
|
+
| `tg demote <chat> <user>` | Remove admin rights |
|
|
142
|
+
| `tg admin-rights <chat> <user> <flags...>` | Set exact rights: `--change-info --post-messages --edit-messages --delete-messages --ban-users --invite-users --pin-messages --add-admins --anonymous --manage-call --other --manage-topics …`; `--rank` |
|
|
143
|
+
| `tg slow-mode <chat> [seconds]` | Set slow mode (0 = disable); supergroups only |
|
|
144
|
+
| `tg topics <chat>` | List forum topics |
|
|
145
|
+
| `tg topic-create <chat> <title> [text]` | Create a forum topic, optionally with a first message |
|
|
146
|
+
| `tg admin-log <chat> [-n]` | Recent admin actions |
|
|
147
|
+
|
|
90
148
|
Global flags: `--account` (v1: default only), `--json` (raw JSON output).
|
|
91
149
|
Exit codes: `0` ok, `1` command error, `2` config/connection/session error,
|
|
92
150
|
`130` interrupted.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
The v1 release covers the core daily workflow. The goal is full coverage of
|
|
4
|
+
the Telegram feature surface reachable from a userbot, roughly matching the
|
|
5
|
+
126-tool scope of [chigwell/telegram-mcp](https://github.com/chigwell/telegram-mcp).
|
|
6
|
+
|
|
7
|
+
Status legend: ✅ done · 🚧 planned · ❌ deliberately out of scope.
|
|
8
|
+
|
|
9
|
+
## Milestones
|
|
10
|
+
|
|
11
|
+
### v2 — Messaging enhancements & chat management (shipped)
|
|
12
|
+
|
|
13
|
+
Messaging:
|
|
14
|
+
|
|
15
|
+
- ✅ `schedule <chat> <time> <text...>` — send scheduled messages
|
|
16
|
+
(`send_message(..., schedule=...)`), list/delete scheduled
|
|
17
|
+
(`scheduled <chat>` / `unschedule <chat> <ids...>`)
|
|
18
|
+
- ✅ `draft <chat> [text]` / `draft-clear <chat>` — save/get/clear drafts
|
|
19
|
+
- ✅ `react <chat> <msg_id> <emoji>` / `unreact` — send/remove reactions
|
|
20
|
+
(note: reacting to Saved Messages is premium-only on Telegram's side)
|
|
21
|
+
- ✅ `sticker <chat> <file.webp>` — send stickers (telethon handles via `send_file`)
|
|
22
|
+
- ✅ `gif <chat> <query>` — search GIFs (inline query to the built-in @gif bot;
|
|
23
|
+
`messages.searchGifs` was removed from the TL layer) and send
|
|
24
|
+
- ✅ `contact-card <chat> <name> <phone>` — send a contact card
|
|
25
|
+
- ✅ `pin <chat> <msg_id>` / `unpin <chat> [msg_id]` — pin/unpin messages
|
|
26
|
+
- ✅ `read <chat>` — mark a chat as read
|
|
27
|
+
- ✅ `msg-link <chat> <msg_id>` — export a t.me message link (channels/supergroups)
|
|
28
|
+
|
|
29
|
+
Chat management:
|
|
30
|
+
|
|
31
|
+
- ✅ `archive <chat>` / `unarchive <chat>` — archive/unarchive dialogs
|
|
32
|
+
- ✅ `mute <chat> [hours]` / `unmute <chat>` — mute/unmute notifications
|
|
33
|
+
- ✅ `chat-title <chat> <title>` / `chat-about <chat> <text>` /
|
|
34
|
+
`chat-photo <chat> <file>` — edit chat info
|
|
35
|
+
- ✅ `invite-link <chat>` / `join <link>` — export/import invite links
|
|
36
|
+
- ✅ `clear-history <chat>` — delete all messages in a chat (`--self-only` to
|
|
37
|
+
keep the other side's copy)
|
|
38
|
+
- ✅ `status <user>` / `common-chats <user>` — online status, common chats
|
|
39
|
+
|
|
40
|
+
### v3 — Group administration (shipped)
|
|
41
|
+
|
|
42
|
+
- ✅ `new-group <title> <user...>` / `new-channel <title>` — create groups
|
|
43
|
+
(`--group` flag makes a supergroup) /channels
|
|
44
|
+
- ✅ `invite <chat> <user...>` / `kick <chat> <user...>` / `leave <chat>`
|
|
45
|
+
(basic-group kick/leave use `messages.DeleteChatUser`, supergroup kick is a
|
|
46
|
+
view-messages ban like official clients)
|
|
47
|
+
- ✅ `ban <chat> <user> [days]` / `unban <chat> <user>` / `banned <chat>`
|
|
48
|
+
(the working participant filter on the current TL layer is
|
|
49
|
+
`ChannelParticipantsKicked`, not `ChannelParticipantsBanned`)
|
|
50
|
+
- ✅ `admins <chat>` / `promote <chat> <user> [--title]` / `demote <chat> <user>` /
|
|
51
|
+
`admin-rights <chat> <user> ...` — admin management with granular rights
|
|
52
|
+
(basic groups use `messages.EditChatAdmin`, no titles)
|
|
53
|
+
- ✅ `slow-mode <chat> [seconds]` — set/disable slow mode
|
|
54
|
+
- ✅ `topics <chat>` / `topic-create <chat> <title> [text]` — forum topics
|
|
55
|
+
(list/create)
|
|
56
|
+
- ✅ `admin-log <chat>` — recent admin actions
|
|
57
|
+
|
|
58
|
+
### v4 — Contacts, profile & misc
|
|
59
|
+
|
|
60
|
+
- 🚧 `contact add/del` — add/delete contacts
|
|
61
|
+
- 🚧 `contact import <file>` / `contact export` — bulk import/export
|
|
62
|
+
- 🚧 `photo-del` — delete profile photo
|
|
63
|
+
- 🚧 `privacy get/set` — privacy settings (last seen, phone, …)
|
|
64
|
+
- 🚧 `block <user>` / `unblock <user>` / `blocked` — block management
|
|
65
|
+
- 🚧 `folder ...` — dialog folders (create/assign/reorder)
|
|
66
|
+
- 🚧 `accounts` — multi-account support (the `--account` hook already exists;
|
|
67
|
+
v1 resolves the default account only)
|
|
68
|
+
- 🚧 `vtr <chat> <msg_id>` — voice transcription. telethon 1.44 has no
|
|
69
|
+
transcribe support; options: Groq HTTP API, or raw
|
|
70
|
+
`messages.transcribeAudio` TL (Telegram Premium)
|
|
71
|
+
|
|
72
|
+
### Out of scope
|
|
73
|
+
|
|
74
|
+
- ❌ Agent-oriented event tools (`wait_for_settled_message`, incoming feed
|
|
75
|
+
watchers) — meaningless for an interactive CLI.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "telegram-userbot-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Full-featured Telegram command-line client powered by a Telethon userbot (your own account, not a bot token)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Chat / search / contacts / profile commands."""
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
3
5
|
from telethon import functions, types
|
|
4
6
|
|
|
5
7
|
from .client import run_with_client
|
|
@@ -70,7 +72,11 @@ def muted_of(d) -> bool:
|
|
|
70
72
|
# The custom Dialog wrapper drops the mute state; read it from the raw dialog.
|
|
71
73
|
notify = getattr(getattr(d, "dialog", None), "notify_settings", None)
|
|
72
74
|
mute_until = getattr(notify, "mute_until", None)
|
|
73
|
-
|
|
75
|
+
if not mute_until:
|
|
76
|
+
return False
|
|
77
|
+
# an unmuted chat sends mute_until=0, which telethon deserializes as
|
|
78
|
+
# datetime(1970, ...) — truthy but in the past, i.e. not muted
|
|
79
|
+
return mute_until > datetime.now(mute_until.tzinfo)
|
|
74
80
|
|
|
75
81
|
|
|
76
82
|
async def cmd_hist(args) -> None:
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
|
|
7
7
|
from telethon import errors
|
|
8
8
|
|
|
9
|
-
from . import __version__, auth, chats, media, messages, resolve
|
|
9
|
+
from . import __version__, auth, chats, dialogs, groups, media, messages, resolve
|
|
10
10
|
from .output import TgError
|
|
11
11
|
|
|
12
12
|
|
|
@@ -41,7 +41,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
41
41
|
sp2 = sub.add_parser("logout", parents=[common], help="Log out and remove the session")
|
|
42
42
|
sp2.set_defaults(func=auth.cmd_logout)
|
|
43
43
|
|
|
44
|
-
for mod in (messages, chats, media):
|
|
44
|
+
for mod in (messages, chats, media, dialogs, groups):
|
|
45
45
|
mod.setup(sub, common)
|
|
46
46
|
|
|
47
47
|
resolve.setup_alias(sub, common)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Chat management: archive, mute, chat info, invite links, history, user status."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from telethon import functions, types
|
|
9
|
+
|
|
10
|
+
from .client import run_with_client
|
|
11
|
+
from .output import TgError, fmt_date, json_pp
|
|
12
|
+
from .resolve import resolve_chat
|
|
13
|
+
|
|
14
|
+
# "forever" mute: the official clients pin mute_until to a far-future timestamp
|
|
15
|
+
_MUTE_FOREVER = 2**31 - 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def _input_peer(client, chat):
|
|
19
|
+
return await client.get_input_entity(await resolve_chat(client, chat))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def cmd_archive(args) -> None:
|
|
23
|
+
await _set_folder(args, 1, "Archived")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def cmd_unarchive(args) -> None:
|
|
27
|
+
await _set_folder(args, 0, "Unarchived")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def _set_folder(args, folder: int, done: str) -> None:
|
|
31
|
+
async with run_with_client(args) as client:
|
|
32
|
+
entity = await resolve_chat(client, args.chat)
|
|
33
|
+
await client.edit_folder(entity, folder=folder)
|
|
34
|
+
print(done)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def cmd_mute(args) -> None:
|
|
38
|
+
if args.hours:
|
|
39
|
+
until = int(time.time()) + args.hours * 3600
|
|
40
|
+
human = f"muted for {args.hours}h"
|
|
41
|
+
else:
|
|
42
|
+
until = _MUTE_FOREVER
|
|
43
|
+
human = "muted permanently"
|
|
44
|
+
await _update_notify(args, until, human)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def cmd_unmute(args) -> None:
|
|
48
|
+
await _update_notify(args, 0, "unmuted")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def _update_notify(args, mute_until: int, done: str) -> None:
|
|
52
|
+
async with run_with_client(args) as client:
|
|
53
|
+
entity = await resolve_chat(client, args.chat)
|
|
54
|
+
await client(
|
|
55
|
+
functions.account.UpdateNotifySettingsRequest(
|
|
56
|
+
peer=entity,
|
|
57
|
+
settings=types.InputPeerNotifySettings(mute_until=mute_until),
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
print(done)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def cmd_chat_title(args) -> None:
|
|
64
|
+
async with run_with_client(args) as client:
|
|
65
|
+
peer = await _input_peer(client, args.chat)
|
|
66
|
+
if isinstance(peer, types.InputPeerChannel):
|
|
67
|
+
await client(functions.channels.EditTitleRequest(channel=peer, title=args.title))
|
|
68
|
+
elif isinstance(peer, types.InputPeerChat):
|
|
69
|
+
await client(
|
|
70
|
+
functions.messages.EditChatTitleRequest(chat_id=peer.chat_id, title=args.title)
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
raise TgError("chat titles can only be changed for groups and channels")
|
|
74
|
+
print("Chat title updated")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def cmd_chat_about(args) -> None:
|
|
78
|
+
async with run_with_client(args) as client:
|
|
79
|
+
entity = await resolve_chat(client, args.chat)
|
|
80
|
+
await client(functions.messages.EditChatAboutRequest(peer=entity, about=args.text))
|
|
81
|
+
print("Chat description updated")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def cmd_chat_photo(args) -> None:
|
|
85
|
+
path = os.path.expanduser(args.file)
|
|
86
|
+
if not os.path.exists(path):
|
|
87
|
+
raise TgError(f"file not found: {path}")
|
|
88
|
+
async with run_with_client(args) as client:
|
|
89
|
+
peer = await _input_peer(client, args.chat)
|
|
90
|
+
uploaded = await client.upload_file(path)
|
|
91
|
+
photo = types.InputChatUploadedPhoto(file=uploaded)
|
|
92
|
+
if isinstance(peer, types.InputPeerChannel):
|
|
93
|
+
await client(functions.channels.EditPhotoRequest(channel=peer, photo=photo))
|
|
94
|
+
elif isinstance(peer, types.InputPeerChat):
|
|
95
|
+
await client(functions.messages.EditChatPhotoRequest(chat_id=peer.chat_id, photo=photo))
|
|
96
|
+
else:
|
|
97
|
+
raise TgError("chat photos can only be changed for groups and channels")
|
|
98
|
+
print("Chat photo updated")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def cmd_invite_link(args) -> None:
|
|
102
|
+
async with run_with_client(args) as client:
|
|
103
|
+
entity = await resolve_chat(client, args.chat)
|
|
104
|
+
res = await client(functions.messages.ExportChatInviteRequest(peer=entity))
|
|
105
|
+
link = getattr(res, "link", None)
|
|
106
|
+
if not link:
|
|
107
|
+
raise TgError("no invite link returned (check your admin rights)")
|
|
108
|
+
print(link)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _invite_hash(link: str) -> str | None:
|
|
112
|
+
m = re.search(r"t\.me/(?:\+|joinchat/)([A-Za-z0-9_-]+)", link)
|
|
113
|
+
return m.group(1) if m else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _invite_username(link: str) -> str | None:
|
|
117
|
+
m = re.search(r"t\.me/([A-Za-z0-9_]{4,})", link)
|
|
118
|
+
return m.group(1) if m else link.lstrip("@")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def cmd_join(args) -> None:
|
|
122
|
+
async with run_with_client(args) as client:
|
|
123
|
+
h = _invite_hash(args.link)
|
|
124
|
+
if h:
|
|
125
|
+
await client(functions.messages.ImportChatInviteRequest(hash=h))
|
|
126
|
+
print("Joined private group")
|
|
127
|
+
return
|
|
128
|
+
name = _invite_username(args.link)
|
|
129
|
+
await client(functions.channels.JoinChannelRequest(channel=name))
|
|
130
|
+
print(f"Joined @{name}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def cmd_clear_history(args) -> None:
|
|
134
|
+
async with run_with_client(args) as client:
|
|
135
|
+
peer = await client.get_input_entity(await resolve_chat(client, args.chat))
|
|
136
|
+
if isinstance(peer, types.InputPeerChannel):
|
|
137
|
+
# supergroups/channels reject messages.DeleteHistoryRequest
|
|
138
|
+
await client(
|
|
139
|
+
functions.channels.DeleteHistoryRequest(
|
|
140
|
+
channel=peer, max_id=0, for_everyone=not args.self_only
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
await client(
|
|
145
|
+
functions.messages.DeleteHistoryRequest(
|
|
146
|
+
peer=peer,
|
|
147
|
+
max_id=0,
|
|
148
|
+
revoke=not args.self_only,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
scope = "for both sides" if not args.self_only else "locally only"
|
|
152
|
+
print(f"History cleared ({scope})")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _status_text(status) -> str:
|
|
156
|
+
if isinstance(status, types.UserStatusOnline):
|
|
157
|
+
if status.expires > datetime.now(status.expires.tzinfo):
|
|
158
|
+
return "online"
|
|
159
|
+
return f"last seen {fmt_date(status.expires)}"
|
|
160
|
+
if isinstance(status, types.UserStatusOffline):
|
|
161
|
+
return f"last seen {fmt_date(status.was_online)}"
|
|
162
|
+
if isinstance(status, types.UserStatusRecently):
|
|
163
|
+
return "last seen recently"
|
|
164
|
+
if isinstance(status, types.UserStatusLastWeek):
|
|
165
|
+
return "last seen within a week"
|
|
166
|
+
if isinstance(status, types.UserStatusLastMonth):
|
|
167
|
+
return "last seen within a month"
|
|
168
|
+
if isinstance(status, types.UserStatusLongAgo):
|
|
169
|
+
return "last seen a long time ago"
|
|
170
|
+
return str(status)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def cmd_status(args) -> None:
|
|
174
|
+
async with run_with_client(args) as client:
|
|
175
|
+
user = await client.get_entity(await resolve_chat(client, args.user))
|
|
176
|
+
name = " ".join(x for x in (user.first_name, user.last_name) if x)
|
|
177
|
+
print(f"{name} ({user.id}): {_status_text(user.status)}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
async def cmd_common_chats(args) -> None:
|
|
181
|
+
async with run_with_client(args) as client:
|
|
182
|
+
peer = await _input_peer(client, args.user)
|
|
183
|
+
if isinstance(peer, types.InputPeerSelf):
|
|
184
|
+
raise TgError("cannot list common chats with yourself")
|
|
185
|
+
if not isinstance(peer, types.InputPeerUser):
|
|
186
|
+
raise TgError(f"'{args.user}' is not a user")
|
|
187
|
+
user = types.InputUser(user_id=peer.user_id, access_hash=peer.access_hash)
|
|
188
|
+
res = await client(
|
|
189
|
+
functions.messages.GetCommonChatsRequest(user_id=user, max_id=0, limit=100)
|
|
190
|
+
)
|
|
191
|
+
chats = getattr(res, "chats", []) or []
|
|
192
|
+
if args.json:
|
|
193
|
+
print(json_pp([c.to_dict() for c in chats]))
|
|
194
|
+
return
|
|
195
|
+
if not chats:
|
|
196
|
+
print("(no common chats)")
|
|
197
|
+
return
|
|
198
|
+
for c in chats:
|
|
199
|
+
title = getattr(c, "title", None) or "?"
|
|
200
|
+
uname = getattr(c, "username", None) or ""
|
|
201
|
+
print(f"{c.id:>14} {title} {'@' + uname if uname else ''}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def setup(subparsers, common=None) -> None:
|
|
205
|
+
parents = [common] if common else []
|
|
206
|
+
sp = subparsers.add_parser("archive", parents=parents, help="Archive a chat")
|
|
207
|
+
sp.add_argument("chat")
|
|
208
|
+
sp.set_defaults(func=cmd_archive)
|
|
209
|
+
|
|
210
|
+
sp = subparsers.add_parser("unarchive", parents=parents, help="Unarchive a chat")
|
|
211
|
+
sp.add_argument("chat")
|
|
212
|
+
sp.set_defaults(func=cmd_unarchive)
|
|
213
|
+
|
|
214
|
+
sp = subparsers.add_parser(
|
|
215
|
+
"mute", parents=parents, help="Mute notifications (permanent unless [hours] given)"
|
|
216
|
+
)
|
|
217
|
+
sp.add_argument("chat")
|
|
218
|
+
sp.add_argument("hours", nargs="?", type=int, default=None)
|
|
219
|
+
sp.set_defaults(func=cmd_mute)
|
|
220
|
+
|
|
221
|
+
sp = subparsers.add_parser("unmute", parents=parents, help="Unmute notifications")
|
|
222
|
+
sp.add_argument("chat")
|
|
223
|
+
sp.set_defaults(func=cmd_unmute)
|
|
224
|
+
|
|
225
|
+
sp = subparsers.add_parser("chat-title", parents=parents, help="Change group/channel title")
|
|
226
|
+
sp.add_argument("chat")
|
|
227
|
+
sp.add_argument("title")
|
|
228
|
+
sp.set_defaults(func=cmd_chat_title)
|
|
229
|
+
|
|
230
|
+
sp = subparsers.add_parser("chat-about", parents=parents, help="Change chat description")
|
|
231
|
+
sp.add_argument("chat")
|
|
232
|
+
sp.add_argument("text")
|
|
233
|
+
sp.set_defaults(func=cmd_chat_about)
|
|
234
|
+
|
|
235
|
+
sp = subparsers.add_parser("chat-photo", parents=parents, help="Change the chat photo")
|
|
236
|
+
sp.add_argument("chat")
|
|
237
|
+
sp.add_argument("file", help="path to an image")
|
|
238
|
+
sp.set_defaults(func=cmd_chat_photo)
|
|
239
|
+
|
|
240
|
+
sp = subparsers.add_parser("invite-link", parents=parents, help="Export a chat invite link")
|
|
241
|
+
sp.add_argument("chat")
|
|
242
|
+
sp.set_defaults(func=cmd_invite_link)
|
|
243
|
+
|
|
244
|
+
sp = subparsers.add_parser(
|
|
245
|
+
"join", parents=parents, help="Join via t.me link, @username, or invite hash"
|
|
246
|
+
)
|
|
247
|
+
sp.add_argument("link", help="e.g. t.me/+abc123, t.me/joinchat/abc123, or t.me/somechannel")
|
|
248
|
+
sp.set_defaults(func=cmd_join)
|
|
249
|
+
|
|
250
|
+
sp = subparsers.add_parser(
|
|
251
|
+
"clear-history",
|
|
252
|
+
parents=parents,
|
|
253
|
+
help="Delete all messages in a chat (both sides; --self-only keeps the other copy)",
|
|
254
|
+
)
|
|
255
|
+
sp.add_argument("chat")
|
|
256
|
+
sp.add_argument(
|
|
257
|
+
"--self-only", action="store_true", help="delete only your own copy (private chats)"
|
|
258
|
+
)
|
|
259
|
+
sp.set_defaults(func=cmd_clear_history)
|
|
260
|
+
|
|
261
|
+
sp = subparsers.add_parser("status", parents=parents, help="Show a user's online status")
|
|
262
|
+
sp.add_argument("user")
|
|
263
|
+
sp.set_defaults(func=cmd_status)
|
|
264
|
+
|
|
265
|
+
sp = subparsers.add_parser(
|
|
266
|
+
"common-chats", parents=parents, help="List chats you share with a user"
|
|
267
|
+
)
|
|
268
|
+
sp.add_argument("user")
|
|
269
|
+
sp.set_defaults(func=cmd_common_chats)
|