agent-messenger 2.12.0 → 2.12.1

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 (108) hide show
  1. package/.claude-plugin/README.md +11 -1
  2. package/.claude-plugin/marketplace.json +14 -1
  3. package/.claude-plugin/plugin.json +4 -2
  4. package/CONTRIBUTING.md +12 -0
  5. package/README.md +30 -4
  6. package/dist/package.json +10 -2
  7. package/dist/src/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +3 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/platforms/telegrambot/cli.d.ts +5 -0
  11. package/dist/src/platforms/telegrambot/cli.d.ts.map +1 -0
  12. package/dist/src/platforms/telegrambot/cli.js +29 -0
  13. package/dist/src/platforms/telegrambot/cli.js.map +1 -0
  14. package/dist/src/platforms/telegrambot/client.d.ts +85 -0
  15. package/dist/src/platforms/telegrambot/client.d.ts.map +1 -0
  16. package/dist/src/platforms/telegrambot/client.js +282 -0
  17. package/dist/src/platforms/telegrambot/client.js.map +1 -0
  18. package/dist/src/platforms/telegrambot/commands/auth.d.ts +31 -0
  19. package/dist/src/platforms/telegrambot/commands/auth.d.ts.map +1 -0
  20. package/dist/src/platforms/telegrambot/commands/auth.js +173 -0
  21. package/dist/src/platforms/telegrambot/commands/auth.js.map +1 -0
  22. package/dist/src/platforms/telegrambot/commands/chat.d.ts +25 -0
  23. package/dist/src/platforms/telegrambot/commands/chat.d.ts.map +1 -0
  24. package/dist/src/platforms/telegrambot/commands/chat.js +69 -0
  25. package/dist/src/platforms/telegrambot/commands/chat.js.map +1 -0
  26. package/dist/src/platforms/telegrambot/commands/index.d.ts +6 -0
  27. package/dist/src/platforms/telegrambot/commands/index.d.ts.map +1 -0
  28. package/dist/src/platforms/telegrambot/commands/index.js +6 -0
  29. package/dist/src/platforms/telegrambot/commands/index.js.map +1 -0
  30. package/dist/src/platforms/telegrambot/commands/message.d.ts +39 -0
  31. package/dist/src/platforms/telegrambot/commands/message.d.ts.map +1 -0
  32. package/dist/src/platforms/telegrambot/commands/message.js +145 -0
  33. package/dist/src/platforms/telegrambot/commands/message.js.map +1 -0
  34. package/dist/src/platforms/telegrambot/commands/reaction.d.ts +16 -0
  35. package/dist/src/platforms/telegrambot/commands/reaction.d.ts.map +1 -0
  36. package/dist/src/platforms/telegrambot/commands/reaction.js +49 -0
  37. package/dist/src/platforms/telegrambot/commands/reaction.js.map +1 -0
  38. package/dist/src/platforms/telegrambot/commands/shared.d.ts +12 -0
  39. package/dist/src/platforms/telegrambot/commands/shared.d.ts.map +1 -0
  40. package/dist/src/platforms/telegrambot/commands/shared.js +21 -0
  41. package/dist/src/platforms/telegrambot/commands/shared.js.map +1 -0
  42. package/dist/src/platforms/telegrambot/commands/whoami.d.ts +17 -0
  43. package/dist/src/platforms/telegrambot/commands/whoami.d.ts.map +1 -0
  44. package/dist/src/platforms/telegrambot/commands/whoami.js +30 -0
  45. package/dist/src/platforms/telegrambot/commands/whoami.js.map +1 -0
  46. package/dist/src/platforms/telegrambot/credential-manager.d.ts +17 -0
  47. package/dist/src/platforms/telegrambot/credential-manager.d.ts.map +1 -0
  48. package/dist/src/platforms/telegrambot/credential-manager.js +113 -0
  49. package/dist/src/platforms/telegrambot/credential-manager.js.map +1 -0
  50. package/dist/src/platforms/telegrambot/index.d.ts +7 -0
  51. package/dist/src/platforms/telegrambot/index.d.ts.map +1 -0
  52. package/dist/src/platforms/telegrambot/index.js +5 -0
  53. package/dist/src/platforms/telegrambot/index.js.map +1 -0
  54. package/dist/src/platforms/telegrambot/listener.d.ts +30 -0
  55. package/dist/src/platforms/telegrambot/listener.d.ts.map +1 -0
  56. package/dist/src/platforms/telegrambot/listener.js +186 -0
  57. package/dist/src/platforms/telegrambot/listener.js.map +1 -0
  58. package/dist/src/platforms/telegrambot/types.d.ts +256 -0
  59. package/dist/src/platforms/telegrambot/types.d.ts.map +1 -0
  60. package/dist/src/platforms/telegrambot/types.js +96 -0
  61. package/dist/src/platforms/telegrambot/types.js.map +1 -0
  62. package/docs/content/docs/cli/meta.json +1 -0
  63. package/docs/content/docs/cli/telegrambot.mdx +149 -0
  64. package/docs/content/docs/index.mdx +10 -9
  65. package/docs/content/docs/quick-start.mdx +2 -0
  66. package/docs/content/docs/sdk/meta.json +1 -0
  67. package/docs/content/docs/sdk/telegrambot.mdx +216 -0
  68. package/e2e/config.ts +24 -0
  69. package/e2e/helpers.ts +1 -0
  70. package/e2e/telegrambot.e2e.test.ts +185 -0
  71. package/examples/telegrambot-listen.ts +54 -0
  72. package/package.json +10 -2
  73. package/scripts/postbuild.ts +1 -0
  74. package/skills/agent-channeltalk/SKILL.md +1 -1
  75. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  76. package/skills/agent-discord/SKILL.md +1 -1
  77. package/skills/agent-discordbot/SKILL.md +1 -1
  78. package/skills/agent-instagram/SKILL.md +1 -1
  79. package/skills/agent-kakaotalk/SKILL.md +12 -5
  80. package/skills/agent-line/SKILL.md +1 -1
  81. package/skills/agent-slack/SKILL.md +1 -1
  82. package/skills/agent-slackbot/SKILL.md +1 -1
  83. package/skills/agent-teams/SKILL.md +1 -1
  84. package/skills/agent-telegram/SKILL.md +1 -1
  85. package/skills/agent-telegrambot/SKILL.md +357 -0
  86. package/skills/agent-webex/SKILL.md +1 -1
  87. package/skills/agent-wechatbot/SKILL.md +1 -1
  88. package/skills/agent-whatsapp/SKILL.md +1 -1
  89. package/skills/agent-whatsappbot/SKILL.md +1 -1
  90. package/src/cli.ts +4 -0
  91. package/src/platforms/telegrambot/cli.ts +34 -0
  92. package/src/platforms/telegrambot/client.test.ts +454 -0
  93. package/src/platforms/telegrambot/client.ts +404 -0
  94. package/src/platforms/telegrambot/commands/auth.test.ts +244 -0
  95. package/src/platforms/telegrambot/commands/auth.ts +220 -0
  96. package/src/platforms/telegrambot/commands/chat.ts +96 -0
  97. package/src/platforms/telegrambot/commands/index.ts +5 -0
  98. package/src/platforms/telegrambot/commands/message.ts +235 -0
  99. package/src/platforms/telegrambot/commands/reaction.ts +70 -0
  100. package/src/platforms/telegrambot/commands/shared.ts +32 -0
  101. package/src/platforms/telegrambot/commands/whoami.ts +45 -0
  102. package/src/platforms/telegrambot/credential-manager.test.ts +196 -0
  103. package/src/platforms/telegrambot/credential-manager.ts +141 -0
  104. package/src/platforms/telegrambot/index.ts +44 -0
  105. package/src/platforms/telegrambot/listener.test.ts +398 -0
  106. package/src/platforms/telegrambot/listener.ts +198 -0
  107. package/src/platforms/telegrambot/types.test.ts +128 -0
  108. package/src/platforms/telegrambot/types.ts +282 -0
@@ -0,0 +1,220 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
+ import { formatOutput } from '@/shared/utils/output'
5
+
6
+ import { TelegramBotClient } from '../client'
7
+ import { TelegramBotCredentialManager } from '../credential-manager'
8
+ import type { ClientFactory } from './shared'
9
+
10
+ interface ActionOptions {
11
+ pretty?: boolean
12
+ bot?: string
13
+ _credManager?: TelegramBotCredentialManager
14
+ _clientFactory?: ClientFactory
15
+ }
16
+
17
+ interface ActionResult {
18
+ success?: boolean
19
+ error?: string
20
+ bot_id?: string
21
+ bot_name?: string
22
+ username?: string
23
+ valid?: boolean
24
+ bots?: Array<{
25
+ bot_id: string
26
+ bot_name: string
27
+ is_current: boolean
28
+ }>
29
+ }
30
+
31
+ export async function setAction(token: string, options: ActionOptions): Promise<ActionResult> {
32
+ try {
33
+ const client = options._clientFactory ? options._clientFactory() : new TelegramBotClient()
34
+ await client.login({ token })
35
+ const me = await client.getMe()
36
+
37
+ if (!me.is_bot) {
38
+ return { error: 'Token does not belong to a bot account.' }
39
+ }
40
+
41
+ const botId = options.bot || me.username || String(me.id)
42
+ const botName = me.username ?? me.first_name
43
+
44
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
45
+ await credManager.setCredentials({
46
+ token,
47
+ bot_id: botId,
48
+ bot_name: botName,
49
+ })
50
+
51
+ return {
52
+ success: true,
53
+ bot_id: botId,
54
+ bot_name: botName,
55
+ username: me.username,
56
+ }
57
+ } catch (error) {
58
+ return { error: (error as Error).message }
59
+ }
60
+ }
61
+
62
+ export async function clearAction(options: ActionOptions): Promise<ActionResult> {
63
+ try {
64
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
65
+ await credManager.clearCredentials()
66
+ return { success: true }
67
+ } catch (error) {
68
+ return { error: (error as Error).message }
69
+ }
70
+ }
71
+
72
+ export async function statusAction(options: ActionOptions): Promise<ActionResult> {
73
+ try {
74
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
75
+ const creds = await credManager.getCredentials(options.bot)
76
+
77
+ if (!creds) {
78
+ return {
79
+ valid: false,
80
+ error: options.bot
81
+ ? `Bot "${options.bot}" not found. Run "auth list" to see available bots.`
82
+ : 'No credentials configured. Run "auth set <token>" first.',
83
+ }
84
+ }
85
+
86
+ let valid = false
87
+ let botName = creds.bot_name
88
+ let username: string | undefined
89
+
90
+ try {
91
+ const client = options._clientFactory ? options._clientFactory() : new TelegramBotClient()
92
+ await client.login({ token: creds.token })
93
+ const me = await client.getMe()
94
+ valid = me.is_bot === true
95
+ botName = me.username ?? me.first_name
96
+ username = me.username
97
+ } catch {
98
+ valid = false
99
+ }
100
+
101
+ return {
102
+ valid,
103
+ bot_id: creds.bot_id,
104
+ bot_name: botName,
105
+ username,
106
+ }
107
+ } catch (error) {
108
+ return { error: (error as Error).message }
109
+ }
110
+ }
111
+
112
+ export async function listAction(options: ActionOptions): Promise<ActionResult> {
113
+ try {
114
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
115
+ const all = await credManager.listAll()
116
+
117
+ return {
118
+ bots: all.map((b) => ({
119
+ bot_id: b.bot_id,
120
+ bot_name: b.bot_name,
121
+ is_current: b.is_current,
122
+ })),
123
+ }
124
+ } catch (error) {
125
+ return { error: (error as Error).message }
126
+ }
127
+ }
128
+
129
+ export async function useAction(botId: string, options: ActionOptions): Promise<ActionResult> {
130
+ try {
131
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
132
+ const found = await credManager.setCurrent(botId)
133
+
134
+ if (!found) {
135
+ return { error: `Bot "${botId}" not found. Run "auth list" to see available bots.` }
136
+ }
137
+
138
+ const creds = await credManager.getCredentials()
139
+ return {
140
+ success: true,
141
+ bot_id: creds?.bot_id,
142
+ bot_name: creds?.bot_name,
143
+ }
144
+ } catch (error) {
145
+ return { error: (error as Error).message }
146
+ }
147
+ }
148
+
149
+ export async function removeAction(botId: string, options: ActionOptions): Promise<ActionResult> {
150
+ try {
151
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
152
+ const removed = await credManager.removeBot(botId)
153
+
154
+ if (!removed) {
155
+ return { error: `Bot "${botId}" not found. Run "auth list" to see available bots.` }
156
+ }
157
+
158
+ return { success: true }
159
+ } catch (error) {
160
+ return { error: (error as Error).message }
161
+ }
162
+ }
163
+
164
+ export const authCommand = new Command('auth')
165
+ .description('Bot authentication commands')
166
+ .addCommand(
167
+ new Command('set')
168
+ .description('Set bot token (validates against Telegram API)')
169
+ .argument('<token>', 'Bot token from @BotFather')
170
+ .option('--bot <id>', 'Bot identifier for switching later')
171
+ .option('--pretty', 'Pretty print JSON output')
172
+ .action(async (token: string, opts: { bot?: string; pretty?: boolean }) => {
173
+ cliOutput(await setAction(token, opts), opts.pretty)
174
+ }),
175
+ )
176
+ .addCommand(
177
+ new Command('clear')
178
+ .description('Clear all stored credentials')
179
+ .option('--pretty', 'Pretty print JSON output')
180
+ .action(async (opts: { pretty?: boolean }) => {
181
+ cliOutput(await clearAction(opts), opts.pretty)
182
+ }),
183
+ )
184
+ .addCommand(
185
+ new Command('status')
186
+ .description('Show authentication status')
187
+ .option('--bot <id>', 'Check specific bot (default: current)')
188
+ .option('--pretty', 'Pretty print JSON output')
189
+ .action(async (opts: { bot?: string; pretty?: boolean }) => {
190
+ const result = await statusAction(opts)
191
+ console.log(formatOutput(result, opts.pretty))
192
+ if (!result.valid) process.exit(1)
193
+ }),
194
+ )
195
+ .addCommand(
196
+ new Command('list')
197
+ .description('List all stored bots')
198
+ .option('--pretty', 'Pretty print JSON output')
199
+ .action(async (opts: { pretty?: boolean }) => {
200
+ cliOutput(await listAction(opts), opts.pretty)
201
+ }),
202
+ )
203
+ .addCommand(
204
+ new Command('use')
205
+ .description('Switch active bot')
206
+ .argument('<bot>', 'Bot ID')
207
+ .option('--pretty', 'Pretty print JSON output')
208
+ .action(async (botId: string, opts: { pretty?: boolean }) => {
209
+ cliOutput(await useAction(botId, opts), opts.pretty)
210
+ }),
211
+ )
212
+ .addCommand(
213
+ new Command('remove')
214
+ .description('Remove a stored bot')
215
+ .argument('<bot>', 'Bot ID')
216
+ .option('--pretty', 'Pretty print JSON output')
217
+ .action(async (botId: string, opts: { pretty?: boolean }) => {
218
+ cliOutput(await removeAction(botId, opts), opts.pretty)
219
+ }),
220
+ )
@@ -0,0 +1,96 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
+
5
+ import type { BotOption } from './shared'
6
+ import { getClient, parseChatId } from './shared'
7
+
8
+ interface ChatResult {
9
+ id?: number
10
+ type?: string
11
+ title?: string
12
+ username?: string
13
+ first_name?: string
14
+ last_name?: string
15
+ description?: string
16
+ invite_link?: string
17
+ member_count?: number
18
+ is_forum?: boolean
19
+ member?: {
20
+ user_id: number
21
+ username?: string
22
+ status: string
23
+ }
24
+ error?: string
25
+ }
26
+
27
+ export async function infoAction(chat: string, options: BotOption): Promise<ChatResult> {
28
+ try {
29
+ const client = await getClient(options)
30
+ const info = await client.getChat(parseChatId(chat))
31
+
32
+ let memberCount = info.member_count
33
+ if (memberCount === undefined && (info.type === 'group' || info.type === 'supergroup' || info.type === 'channel')) {
34
+ try {
35
+ memberCount = await client.getChatMemberCount(info.id)
36
+ } catch {
37
+ memberCount = undefined
38
+ }
39
+ }
40
+
41
+ return {
42
+ id: info.id,
43
+ type: info.type,
44
+ title: info.title,
45
+ username: info.username,
46
+ first_name: info.first_name,
47
+ last_name: info.last_name,
48
+ description: info.description,
49
+ invite_link: info.invite_link,
50
+ member_count: memberCount,
51
+ is_forum: info.is_forum,
52
+ }
53
+ } catch (error) {
54
+ return { error: (error as Error).message }
55
+ }
56
+ }
57
+
58
+ export async function memberAction(chat: string, userId: string, options: BotOption): Promise<ChatResult> {
59
+ try {
60
+ const client = await getClient(options)
61
+ const member = await client.getChatMember(parseChatId(chat), Number(userId))
62
+ return {
63
+ member: {
64
+ user_id: member.user.id,
65
+ username: member.user.username,
66
+ status: member.status,
67
+ },
68
+ }
69
+ } catch (error) {
70
+ return { error: (error as Error).message }
71
+ }
72
+ }
73
+
74
+ export const chatCommand = new Command('chat')
75
+ .description('Chat commands')
76
+ .addCommand(
77
+ new Command('info')
78
+ .description('Get chat information')
79
+ .argument('<chat>', 'Chat ID or @username')
80
+ .option('--bot <id>', 'Use specific bot')
81
+ .option('--pretty', 'Pretty print JSON output')
82
+ .action(async (chat: string, opts: BotOption) => {
83
+ cliOutput(await infoAction(chat, opts), opts.pretty)
84
+ }),
85
+ )
86
+ .addCommand(
87
+ new Command('member')
88
+ .description('Get chat member info')
89
+ .argument('<chat>', 'Chat ID or @username')
90
+ .argument('<user-id>', 'User ID (numeric)')
91
+ .option('--bot <id>', 'Use specific bot')
92
+ .option('--pretty', 'Pretty print JSON output')
93
+ .action(async (chat: string, userId: string, opts: BotOption) => {
94
+ cliOutput(await memberAction(chat, userId, opts), opts.pretty)
95
+ }),
96
+ )
@@ -0,0 +1,5 @@
1
+ export { authCommand } from './auth'
2
+ export { chatCommand } from './chat'
3
+ export { messageCommand } from './message'
4
+ export { reactionCommand } from './reaction'
5
+ export { whoamiCommand } from './whoami'
@@ -0,0 +1,235 @@
1
+ import { resolve } from 'node:path'
2
+
3
+ import { Command } from 'commander'
4
+
5
+ import { cliOutput } from '@/shared/utils/cli-output'
6
+
7
+ import type { TelegramMessage } from '../types'
8
+ import type { BotOption } from './shared'
9
+ import { getClient, parseChatId } from './shared'
10
+
11
+ interface MessageOutput {
12
+ message_id: number
13
+ chat_id: number
14
+ text?: string
15
+ from?: string
16
+ date: number
17
+ edit_date?: number
18
+ reply_to?: number
19
+ }
20
+
21
+ interface MessageResult {
22
+ message?: MessageOutput
23
+ deleted?: number
24
+ error?: string
25
+ }
26
+
27
+ function formatMessage(msg: TelegramMessage): MessageOutput {
28
+ const fromName = msg.from?.username ?? msg.from?.first_name ?? msg.sender_chat?.title
29
+ return {
30
+ message_id: msg.message_id,
31
+ chat_id: msg.chat.id,
32
+ text: msg.text ?? msg.caption,
33
+ from: fromName,
34
+ date: msg.date,
35
+ edit_date: msg.edit_date,
36
+ reply_to: msg.reply_to_message?.message_id,
37
+ }
38
+ }
39
+
40
+ export async function sendAction(
41
+ chat: string,
42
+ text: string,
43
+ options: BotOption & {
44
+ parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2'
45
+ replyTo?: string
46
+ silent?: boolean
47
+ threadId?: string
48
+ },
49
+ ): Promise<MessageResult> {
50
+ try {
51
+ const client = await getClient(options)
52
+ const message = await client.sendMessage(parseChatId(chat), text, {
53
+ parse_mode: options.parseMode,
54
+ reply_to_message_id: options.replyTo ? Number(options.replyTo) : undefined,
55
+ disable_notification: options.silent,
56
+ message_thread_id: options.threadId ? Number(options.threadId) : undefined,
57
+ })
58
+ return { message: formatMessage(message) }
59
+ } catch (error) {
60
+ return { error: (error as Error).message }
61
+ }
62
+ }
63
+
64
+ export async function updateAction(
65
+ chat: string,
66
+ messageId: string,
67
+ text: string,
68
+ options: BotOption & { parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2' },
69
+ ): Promise<MessageResult> {
70
+ try {
71
+ const client = await getClient(options)
72
+ const result = await client.editMessageText({ chat_id: parseChatId(chat), message_id: Number(messageId) }, text, {
73
+ parse_mode: options.parseMode,
74
+ })
75
+ if (result === true) {
76
+ return { error: 'editMessageText returned true; expected a Message object for chat-message edits.' }
77
+ }
78
+ return { message: formatMessage(result) }
79
+ } catch (error) {
80
+ return { error: (error as Error).message }
81
+ }
82
+ }
83
+
84
+ export async function deleteAction(
85
+ chat: string,
86
+ messageId: string,
87
+ options: BotOption & { force?: boolean },
88
+ ): Promise<MessageResult> {
89
+ if (!options.force) {
90
+ return { error: 'Use --force to confirm deletion' }
91
+ }
92
+ try {
93
+ const client = await getClient(options)
94
+ await client.deleteMessage(parseChatId(chat), Number(messageId))
95
+ return { deleted: Number(messageId) }
96
+ } catch (error) {
97
+ return { error: (error as Error).message }
98
+ }
99
+ }
100
+
101
+ export async function forwardAction(
102
+ toChat: string,
103
+ fromChat: string,
104
+ messageId: string,
105
+ options: BotOption & { silent?: boolean; threadId?: string },
106
+ ): Promise<MessageResult> {
107
+ try {
108
+ const client = await getClient(options)
109
+ const message = await client.forwardMessage(parseChatId(toChat), parseChatId(fromChat), Number(messageId), {
110
+ disable_notification: options.silent,
111
+ message_thread_id: options.threadId ? Number(options.threadId) : undefined,
112
+ })
113
+ return { message: formatMessage(message) }
114
+ } catch (error) {
115
+ return { error: (error as Error).message }
116
+ }
117
+ }
118
+
119
+ export async function uploadAction(
120
+ chat: string,
121
+ filePath: string,
122
+ options: BotOption & { caption?: string; parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2' },
123
+ ): Promise<MessageResult> {
124
+ try {
125
+ const client = await getClient(options)
126
+ const message = await client.sendDocument(parseChatId(chat), resolve(filePath), {
127
+ caption: options.caption,
128
+ parse_mode: options.parseMode,
129
+ })
130
+ return { message: formatMessage(message) }
131
+ } catch (error) {
132
+ return { error: (error as Error).message }
133
+ }
134
+ }
135
+
136
+ export const messageCommand = new Command('message')
137
+ .description('Message commands')
138
+ .addCommand(
139
+ new Command('send')
140
+ .description('Send a text message')
141
+ .argument('<chat>', 'Chat ID or @username')
142
+ .argument('<text>', 'Message text')
143
+ .option('--parse-mode <mode>', 'Parse mode: HTML | Markdown | MarkdownV2')
144
+ .option('--reply-to <id>', 'Reply to message ID')
145
+ .option('--silent', 'Send silently (no notification)')
146
+ .option('--thread-id <id>', 'Forum topic thread ID')
147
+ .option('--bot <id>', 'Use specific bot')
148
+ .option('--pretty', 'Pretty print JSON output')
149
+ .action(
150
+ async (
151
+ chat: string,
152
+ text: string,
153
+ opts: BotOption & {
154
+ parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2'
155
+ replyTo?: string
156
+ silent?: boolean
157
+ threadId?: string
158
+ },
159
+ ) => {
160
+ cliOutput(await sendAction(chat, text, opts), opts.pretty)
161
+ },
162
+ ),
163
+ )
164
+ .addCommand(
165
+ new Command('update')
166
+ .description("Edit a message (bot's own messages only)")
167
+ .argument('<chat>', 'Chat ID or @username')
168
+ .argument('<message-id>', 'Message ID')
169
+ .argument('<text>', 'New message text')
170
+ .option('--parse-mode <mode>', 'Parse mode: HTML | Markdown | MarkdownV2')
171
+ .option('--bot <id>', 'Use specific bot')
172
+ .option('--pretty', 'Pretty print JSON output')
173
+ .action(
174
+ async (
175
+ chat: string,
176
+ messageId: string,
177
+ text: string,
178
+ opts: BotOption & { parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2' },
179
+ ) => {
180
+ cliOutput(await updateAction(chat, messageId, text, opts), opts.pretty)
181
+ },
182
+ ),
183
+ )
184
+ .addCommand(
185
+ new Command('delete')
186
+ .description('Delete a message')
187
+ .argument('<chat>', 'Chat ID or @username')
188
+ .argument('<message-id>', 'Message ID')
189
+ .option('--force', 'Confirm deletion')
190
+ .option('--bot <id>', 'Use specific bot')
191
+ .option('--pretty', 'Pretty print JSON output')
192
+ .action(async (chat: string, messageId: string, opts: BotOption & { force?: boolean }) => {
193
+ cliOutput(await deleteAction(chat, messageId, opts), opts.pretty)
194
+ }),
195
+ )
196
+ .addCommand(
197
+ new Command('forward')
198
+ .description('Forward a message from one chat to another')
199
+ .argument('<to-chat>', 'Destination chat ID or @username')
200
+ .argument('<from-chat>', 'Source chat ID or @username')
201
+ .argument('<message-id>', 'Message ID to forward')
202
+ .option('--silent', 'Forward silently')
203
+ .option('--thread-id <id>', 'Destination forum topic thread ID')
204
+ .option('--bot <id>', 'Use specific bot')
205
+ .option('--pretty', 'Pretty print JSON output')
206
+ .action(
207
+ async (
208
+ toChat: string,
209
+ fromChat: string,
210
+ messageId: string,
211
+ opts: BotOption & { silent?: boolean; threadId?: string },
212
+ ) => {
213
+ cliOutput(await forwardAction(toChat, fromChat, messageId, opts), opts.pretty)
214
+ },
215
+ ),
216
+ )
217
+ .addCommand(
218
+ new Command('upload')
219
+ .description('Upload a document to a chat')
220
+ .argument('<chat>', 'Chat ID or @username')
221
+ .argument('<file-path>', 'File path')
222
+ .option('--caption <text>', 'Caption for the document')
223
+ .option('--parse-mode <mode>', 'Caption parse mode: HTML | Markdown | MarkdownV2')
224
+ .option('--bot <id>', 'Use specific bot')
225
+ .option('--pretty', 'Pretty print JSON output')
226
+ .action(
227
+ async (
228
+ chat: string,
229
+ filePath: string,
230
+ opts: BotOption & { caption?: string; parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2' },
231
+ ) => {
232
+ cliOutput(await uploadAction(chat, filePath, opts), opts.pretty)
233
+ },
234
+ ),
235
+ )
@@ -0,0 +1,70 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
+
5
+ import type { BotReactionType } from '../client'
6
+ import type { BotOption } from './shared'
7
+ import { getClient, parseChatId } from './shared'
8
+
9
+ interface ReactionResult {
10
+ success?: boolean
11
+ chat_id?: number | string
12
+ message_id?: number
13
+ emoji?: string
14
+ error?: string
15
+ }
16
+
17
+ export async function setAction(
18
+ chat: string,
19
+ messageId: string,
20
+ emoji: string,
21
+ options: BotOption & { big?: boolean },
22
+ ): Promise<ReactionResult> {
23
+ try {
24
+ const client = await getClient(options)
25
+ const reaction: BotReactionType[] = [{ type: 'emoji', emoji }]
26
+ await client.setMessageReaction(parseChatId(chat), Number(messageId), reaction, {
27
+ is_big: options.big,
28
+ })
29
+ return { success: true, chat_id: parseChatId(chat), message_id: Number(messageId), emoji }
30
+ } catch (error) {
31
+ return { error: (error as Error).message }
32
+ }
33
+ }
34
+
35
+ export async function clearAction(chat: string, messageId: string, options: BotOption): Promise<ReactionResult> {
36
+ try {
37
+ const client = await getClient(options)
38
+ await client.setMessageReaction(parseChatId(chat), Number(messageId), [])
39
+ return { success: true, chat_id: parseChatId(chat), message_id: Number(messageId) }
40
+ } catch (error) {
41
+ return { error: (error as Error).message }
42
+ }
43
+ }
44
+
45
+ export const reactionCommand = new Command('reaction')
46
+ .description('Reaction commands')
47
+ .addCommand(
48
+ new Command('set')
49
+ .description('Set a reaction on a message (replaces existing)')
50
+ .argument('<chat>', 'Chat ID or @username')
51
+ .argument('<message-id>', 'Message ID')
52
+ .argument('<emoji>', 'Emoji (e.g. 👍)')
53
+ .option('--big', 'Show big animation for the reaction')
54
+ .option('--bot <id>', 'Use specific bot')
55
+ .option('--pretty', 'Pretty print JSON output')
56
+ .action(async (chat: string, messageId: string, emoji: string, opts: BotOption & { big?: boolean }) => {
57
+ cliOutput(await setAction(chat, messageId, emoji, opts), opts.pretty)
58
+ }),
59
+ )
60
+ .addCommand(
61
+ new Command('clear')
62
+ .description('Clear all reactions from a message')
63
+ .argument('<chat>', 'Chat ID or @username')
64
+ .argument('<message-id>', 'Message ID')
65
+ .option('--bot <id>', 'Use specific bot')
66
+ .option('--pretty', 'Pretty print JSON output')
67
+ .action(async (chat: string, messageId: string, opts: BotOption) => {
68
+ cliOutput(await clearAction(chat, messageId, opts), opts.pretty)
69
+ }),
70
+ )
@@ -0,0 +1,32 @@
1
+ import { formatOutput } from '@/shared/utils/output'
2
+
3
+ import { TelegramBotClient } from '../client'
4
+ import { TelegramBotCredentialManager } from '../credential-manager'
5
+
6
+ export type ClientFactory = () => TelegramBotClient
7
+
8
+ export interface BotOption {
9
+ bot?: string
10
+ pretty?: boolean
11
+ _credManager?: TelegramBotCredentialManager
12
+ _clientFactory?: ClientFactory
13
+ }
14
+
15
+ export async function getClient(options: BotOption): Promise<TelegramBotClient> {
16
+ const credManager = options._credManager ?? new TelegramBotCredentialManager()
17
+ const creds = await credManager.getCredentials(options.bot)
18
+
19
+ if (!creds) {
20
+ console.log(formatOutput({ error: 'No credentials. Run "auth set <token>" first.' }, options.pretty))
21
+ process.exit(1)
22
+ }
23
+
24
+ const client = options._clientFactory ? options._clientFactory() : new TelegramBotClient()
25
+ return client.login({ token: creds.token })
26
+ }
27
+
28
+ export function parseChatId(chat: string): number | string {
29
+ if (/^-?\d+$/.test(chat)) return Number(chat)
30
+ if (chat.startsWith('@')) return chat
31
+ return `@${chat}`
32
+ }