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.
Files changed (26) hide show
  1. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/PKG-INFO +62 -4
  2. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/README-zh.md +56 -3
  3. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/README.md +61 -3
  4. telegram_userbot_cli-0.3.0/ROADMAP.md +75 -0
  5. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/pyproject.toml +1 -1
  6. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/__init__.py +1 -1
  7. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/chats.py +7 -1
  8. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/cli.py +2 -2
  9. telegram_userbot_cli-0.3.0/src/tgcli/dialogs.py +269 -0
  10. telegram_userbot_cli-0.3.0/src/tgcli/groups.py +534 -0
  11. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/media.py +44 -1
  12. telegram_userbot_cli-0.3.0/src/tgcli/messages.py +365 -0
  13. telegram_userbot_cli-0.1.0/ROADMAP.md +0 -64
  14. telegram_userbot_cli-0.1.0/src/tgcli/messages.py +0 -122
  15. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.env.example +0 -0
  16. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.github/workflows/ci.yml +0 -0
  17. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.github/workflows/release.yml +0 -0
  18. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/.gitignore +0 -0
  19. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/LICENSE +0 -0
  20. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/__main__.py +0 -0
  21. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/auth.py +0 -0
  22. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/client.py +0 -0
  23. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/config.py +0 -0
  24. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/output.py +0 -0
  25. {telegram_userbot_cli-0.1.0 → telegram_userbot_cli-0.3.0}/src/tgcli/resolve.py +0 -0
  26. {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.1.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
- - Chats: list dialogs, history, pinned messages, in-chat & global search
30
- - Media: download, send files / albums, voice notes
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
- - Chats: list dialogs, history, pinned messages, in-chat & global search
17
- - Media: download, send files / albums, voice notes
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.1.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,3 +1,3 @@
1
1
  """tgcli — standalone Telegram command-line client."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.3.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
- return bool(mute_until)
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)