agent-messenger 2.12.0 → 2.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/README.md +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/CONTRIBUTING.md +12 -0
- package/README.md +30 -4
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +22 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +93 -7
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.d.ts +4 -7
- package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.js +43 -72
- package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
- package/dist/src/platforms/telegrambot/cli.d.ts +5 -0
- package/dist/src/platforms/telegrambot/cli.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/cli.js +29 -0
- package/dist/src/platforms/telegrambot/cli.js.map +1 -0
- package/dist/src/platforms/telegrambot/client.d.ts +85 -0
- package/dist/src/platforms/telegrambot/client.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/client.js +282 -0
- package/dist/src/platforms/telegrambot/client.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/auth.d.ts +31 -0
- package/dist/src/platforms/telegrambot/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/auth.js +173 -0
- package/dist/src/platforms/telegrambot/commands/auth.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/chat.d.ts +25 -0
- package/dist/src/platforms/telegrambot/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/chat.js +69 -0
- package/dist/src/platforms/telegrambot/commands/chat.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/index.d.ts +6 -0
- package/dist/src/platforms/telegrambot/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/index.js +6 -0
- package/dist/src/platforms/telegrambot/commands/index.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/message.d.ts +39 -0
- package/dist/src/platforms/telegrambot/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/message.js +145 -0
- package/dist/src/platforms/telegrambot/commands/message.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/reaction.d.ts +16 -0
- package/dist/src/platforms/telegrambot/commands/reaction.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/reaction.js +49 -0
- package/dist/src/platforms/telegrambot/commands/reaction.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/shared.d.ts +12 -0
- package/dist/src/platforms/telegrambot/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/shared.js +21 -0
- package/dist/src/platforms/telegrambot/commands/shared.js.map +1 -0
- package/dist/src/platforms/telegrambot/commands/whoami.d.ts +17 -0
- package/dist/src/platforms/telegrambot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/commands/whoami.js +30 -0
- package/dist/src/platforms/telegrambot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/telegrambot/credential-manager.d.ts +17 -0
- package/dist/src/platforms/telegrambot/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/credential-manager.js +113 -0
- package/dist/src/platforms/telegrambot/credential-manager.js.map +1 -0
- package/dist/src/platforms/telegrambot/index.d.ts +7 -0
- package/dist/src/platforms/telegrambot/index.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/index.js +5 -0
- package/dist/src/platforms/telegrambot/index.js.map +1 -0
- package/dist/src/platforms/telegrambot/listener.d.ts +30 -0
- package/dist/src/platforms/telegrambot/listener.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/listener.js +186 -0
- package/dist/src/platforms/telegrambot/listener.js.map +1 -0
- package/dist/src/platforms/telegrambot/types.d.ts +256 -0
- package/dist/src/platforms/telegrambot/types.d.ts.map +1 -0
- package/dist/src/platforms/telegrambot/types.js +96 -0
- package/dist/src/platforms/telegrambot/types.js.map +1 -0
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/telegrambot.mdx +149 -0
- package/docs/content/docs/index.mdx +10 -9
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/meta.json +1 -0
- package/docs/content/docs/sdk/telegrambot.mdx +216 -0
- package/e2e/config.ts +24 -0
- package/e2e/helpers.ts +1 -0
- package/e2e/telegrambot.e2e.test.ts +185 -0
- package/examples/telegrambot-listen.ts +54 -0
- package/package.json +10 -2
- package/scripts/postbuild.ts +1 -0
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +12 -5
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +357 -0
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/kakaotalk/client-listener-integration.test.ts +411 -0
- package/src/platforms/kakaotalk/client.test.ts +3 -0
- package/src/platforms/kakaotalk/client.ts +108 -9
- package/src/platforms/kakaotalk/listener.test.ts +155 -149
- package/src/platforms/kakaotalk/listener.ts +46 -80
- package/src/platforms/telegrambot/cli.ts +34 -0
- package/src/platforms/telegrambot/client.test.ts +454 -0
- package/src/platforms/telegrambot/client.ts +404 -0
- package/src/platforms/telegrambot/commands/auth.test.ts +244 -0
- package/src/platforms/telegrambot/commands/auth.ts +220 -0
- package/src/platforms/telegrambot/commands/chat.ts +96 -0
- package/src/platforms/telegrambot/commands/index.ts +5 -0
- package/src/platforms/telegrambot/commands/message.ts +235 -0
- package/src/platforms/telegrambot/commands/reaction.ts +70 -0
- package/src/platforms/telegrambot/commands/shared.ts +32 -0
- package/src/platforms/telegrambot/commands/whoami.ts +45 -0
- package/src/platforms/telegrambot/credential-manager.test.ts +196 -0
- package/src/platforms/telegrambot/credential-manager.ts +141 -0
- package/src/platforms/telegrambot/index.ts +44 -0
- package/src/platforms/telegrambot/listener.test.ts +398 -0
- package/src/platforms/telegrambot/listener.ts +198 -0
- package/src/platforms/telegrambot/types.test.ts +128 -0
- package/src/platforms/telegrambot/types.ts +282 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Telegram Bot
|
|
3
|
+
description: TypeScript SDK reference for Telegram Bot — client, real-time long-polling listener, credential management, and types.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install agent-messenger
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import {
|
|
14
|
+
TelegramBotClient,
|
|
15
|
+
TelegramBotCredentialManager,
|
|
16
|
+
TelegramBotError,
|
|
17
|
+
TelegramBotListener,
|
|
18
|
+
} from 'agent-messenger/telegrambot'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## TelegramBotClient
|
|
22
|
+
|
|
23
|
+
The main client wrapping Telegram's HTTP [Bot API](https://core.telegram.org/bots/api). Includes automatic 429 (rate-limit) handling using `retry_after`, network retries with exponential backoff, and structured errors via `TelegramBotError`.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const client = await new TelegramBotClient().login({ token: 'YOUR_BOT_TOKEN' })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or use stored credentials — read from `~/.config/agent-messenger/telegrambot-credentials.json` (managed by `agent-telegrambot auth set`):
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const client = await new TelegramBotClient().login()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Authentication
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const me = await client.getMe()
|
|
39
|
+
// → TelegramBotUser { id, is_bot, first_name, username?, ... }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Messages
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Send text message
|
|
46
|
+
const msg = await client.sendMessage(chatId, 'Hello', {
|
|
47
|
+
parse_mode: 'HTML',
|
|
48
|
+
reply_to_message_id: 42,
|
|
49
|
+
disable_notification: true,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Edit a chat message
|
|
53
|
+
await client.editMessageText({ chat_id: chatId, message_id: msg.message_id }, 'Edited text')
|
|
54
|
+
|
|
55
|
+
// Edit an inline-mode message (returned by inline query results)
|
|
56
|
+
await client.editMessageText({ inline_message_id: 'abc123' }, 'Edited text')
|
|
57
|
+
|
|
58
|
+
// Delete
|
|
59
|
+
await client.deleteMessage(chatId, msg.message_id)
|
|
60
|
+
|
|
61
|
+
// Forward
|
|
62
|
+
await client.forwardMessage(toChatId, fromChatId, messageId)
|
|
63
|
+
|
|
64
|
+
// Upload document (multipart)
|
|
65
|
+
await client.sendDocument(chatId, '/path/to/file.pdf', { caption: 'Report' })
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Chats
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Full chat info (title, description, member count, etc.)
|
|
72
|
+
const chat = await client.getChat(chatId)
|
|
73
|
+
|
|
74
|
+
// Single member's status
|
|
75
|
+
const member = await client.getChatMember(chatId, userId)
|
|
76
|
+
|
|
77
|
+
// Member count for groups, supergroups, channels
|
|
78
|
+
const count = await client.getChatMemberCount(chatId)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Reactions
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Set a reaction (Telegram replaces previous bot reaction)
|
|
85
|
+
await client.setMessageReaction(chatId, messageId, [{ type: 'emoji', emoji: '👍' }])
|
|
86
|
+
|
|
87
|
+
// Clear all bot reactions
|
|
88
|
+
await client.setMessageReaction(chatId, messageId, [])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Updates (Manual Polling)
|
|
92
|
+
|
|
93
|
+
If you don't need the listener abstraction, you can call `getUpdates` directly:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const updates = await client.getUpdates({
|
|
97
|
+
offset: 0,
|
|
98
|
+
limit: 100,
|
|
99
|
+
timeout: 30,
|
|
100
|
+
allowed_updates: ['message', 'callback_query'],
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## TelegramBotListener
|
|
105
|
+
|
|
106
|
+
A long-polling listener for real-time updates. Telegram Bot API does not support WebSockets — long-polling is the canonical streaming mode used by frameworks like grammy and telegraf. No public HTTPS endpoint required.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { TelegramBotClient, TelegramBotListener } from 'agent-messenger/telegrambot'
|
|
110
|
+
|
|
111
|
+
const client = await new TelegramBotClient().login({ token: 'YOUR_BOT_TOKEN' })
|
|
112
|
+
const listener = new TelegramBotListener(client, {
|
|
113
|
+
timeoutSeconds: 30,
|
|
114
|
+
limit: 100,
|
|
115
|
+
allowedUpdates: ['message', 'callback_query'],
|
|
116
|
+
dropPendingUpdates: false,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
listener.on('connected', ({ user }) => {
|
|
120
|
+
console.log(`Connected as @${user.username}`)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
listener.on('message', (message) => {
|
|
124
|
+
console.log(`${message.chat.id}: ${message.text}`)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
listener.on('callback_query', (query) => {
|
|
128
|
+
console.log(`Button clicked: ${query.data}`)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
listener.on('disconnected', () => {
|
|
132
|
+
console.log('Disconnected, will retry…')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
listener.on('error', (error) => {
|
|
136
|
+
console.error('Fatal error:', error)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
await listener.start()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Lifecycle
|
|
143
|
+
|
|
144
|
+
- `start()` — Removes any active webhook (mutex with polling), calls `getMe` to confirm auth, then enters the polling loop.
|
|
145
|
+
- `stop()` — Aborts the in-flight `getUpdates` request and stops the loop. Safe to call multiple times.
|
|
146
|
+
|
|
147
|
+
### Events
|
|
148
|
+
|
|
149
|
+
| Event | Payload | Notes |
|
|
150
|
+
| --------------------- | ----------------------------- | ----------------------------------------------------------------------- |
|
|
151
|
+
| `connected` | `{ user: TelegramBotUser }` | Once after `start()` succeeds |
|
|
152
|
+
| `disconnected` | `[]` | After a recoverable error; listener will retry with exponential backoff |
|
|
153
|
+
| `error` | `[Error]` | Fatal error (Unauthorized 401, Conflict 409). Listener stops. |
|
|
154
|
+
| `message` | `[TelegramMessage]` | New text/media messages in private chats and groups |
|
|
155
|
+
| `edited_message` | `[TelegramMessage]` | Message edited |
|
|
156
|
+
| `channel_post` | `[TelegramMessage]` | New post in a channel where the bot is admin |
|
|
157
|
+
| `edited_channel_post` | `[TelegramMessage]` | Channel post edited |
|
|
158
|
+
| `callback_query` | `[TelegramCallbackQuery]` | Inline keyboard button pressed |
|
|
159
|
+
| `inline_query` | `[TelegramInlineQuery]` | Inline-mode query (`@yourbot ...`) |
|
|
160
|
+
| `my_chat_member` | `[TelegramChatMemberUpdated]` | Bot's own membership status changed |
|
|
161
|
+
| `chat_member` | `[TelegramChatMemberUpdated]` | Other member's status changed (requires opt-in) |
|
|
162
|
+
| `telegram_update` | `[TelegramUpdate]` | Catch-all — every raw update |
|
|
163
|
+
|
|
164
|
+
### Reconnection
|
|
165
|
+
|
|
166
|
+
On recoverable errors (network failure, 5xx, transient API errors), the listener emits `disconnected` and retries with exponential backoff capped at 30 seconds. On `Unauthorized` (401) or `Conflict` (409 — another polling instance is running), it emits `error` and stops; restart your process after fixing the cause.
|
|
167
|
+
|
|
168
|
+
## TelegramBotCredentialManager
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const manager = new TelegramBotCredentialManager()
|
|
172
|
+
|
|
173
|
+
// Store credentials
|
|
174
|
+
await manager.setCredentials({ token, bot_id, bot_name })
|
|
175
|
+
|
|
176
|
+
// Retrieve current
|
|
177
|
+
const creds = await manager.getCredentials()
|
|
178
|
+
|
|
179
|
+
// Multi-bot support
|
|
180
|
+
const all = await manager.listAll()
|
|
181
|
+
await manager.setCurrent('deploy')
|
|
182
|
+
await manager.removeBot('alert')
|
|
183
|
+
await manager.clearCredentials()
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Credentials are stored under `~/.config/agent-messenger/telegrambot-credentials.json` with `0600` file permissions. Override the directory with the `AGENT_MESSENGER_CONFIG_DIR` environment variable, or pass `new TelegramBotCredentialManager(customDir)`.
|
|
187
|
+
|
|
188
|
+
## Error Handling
|
|
189
|
+
|
|
190
|
+
All client methods throw `TelegramBotError` with a `code` property:
|
|
191
|
+
|
|
192
|
+
| Code | Meaning |
|
|
193
|
+
| ------------------- | ----------------------------------------------- |
|
|
194
|
+
| `missing_token` | Empty token passed to `login` |
|
|
195
|
+
| `not_authenticated` | Used a method before `.login()` |
|
|
196
|
+
| `no_credentials` | No stored credentials and none provided |
|
|
197
|
+
| `unauthorized` | 401 — invalid token |
|
|
198
|
+
| `conflict` | 409 — another polling instance running |
|
|
199
|
+
| `forbidden` | 403 — bot kicked or user hasn't started the bot |
|
|
200
|
+
| `bad_request` | 400 — usually invalid chat ID or message ID |
|
|
201
|
+
| `not_found` | 404 |
|
|
202
|
+
| `rate_limited` | 429 (after retries exhausted) |
|
|
203
|
+
| `network_error` | Fetch failed after retries |
|
|
204
|
+
| `invalid_response` | Telegram returned non-JSON |
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { TelegramBotError } from 'agent-messenger/telegrambot'
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await client.sendMessage(chatId, 'Hello')
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof TelegramBotError && error.code === 'forbidden') {
|
|
213
|
+
console.log('User has not started the bot yet')
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
package/e2e/config.ts
CHANGED
|
@@ -242,6 +242,30 @@ export async function validateTelegramEnvironment(): Promise<boolean> {
|
|
|
242
242
|
return true
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
// Telegram Bot Test Environment
|
|
246
|
+
export const TELEGRAMBOT_TEST_CHAT_ID = process.env.E2E_TELEGRAMBOT_CHAT_ID || ''
|
|
247
|
+
|
|
248
|
+
export async function validateTelegramBotEnvironment(): Promise<boolean> {
|
|
249
|
+
if (!TELEGRAMBOT_TEST_CHAT_ID) {
|
|
250
|
+
console.warn('Skipping Telegram Bot E2E: set E2E_TELEGRAMBOT_CHAT_ID to run against a dedicated test chat.')
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const { runCLI, parseJSON } = await import('./helpers')
|
|
255
|
+
|
|
256
|
+
const result = await runCLI('telegrambot', ['auth', 'status'])
|
|
257
|
+
if (result.exitCode !== 0) {
|
|
258
|
+
throw new Error('Telegram Bot authentication failed. Run: agent-telegrambot auth set <token>')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const data = parseJSON<{ valid: boolean }>(result.stdout)
|
|
262
|
+
if (!data?.valid) {
|
|
263
|
+
throw new Error('Telegram Bot credentials invalid or expired. Run: agent-telegrambot auth set <token>')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
268
|
+
|
|
245
269
|
// WhatsApp Test Environment
|
|
246
270
|
export const WHATSAPP_TEST_CHAT_ID = process.env.E2E_WHATSAPP_CHAT_ID || ''
|
|
247
271
|
|
package/e2e/helpers.ts
CHANGED
|
@@ -15,6 +15,7 @@ export async function runCLI(platform: string, args: string[]): Promise<CLIResul
|
|
|
15
15
|
channeltalk: 'agent-channeltalk',
|
|
16
16
|
webex: 'agent-webex',
|
|
17
17
|
telegram: 'agent-telegram',
|
|
18
|
+
telegrambot: 'agent-telegrambot',
|
|
18
19
|
whatsapp: 'agent-whatsapp',
|
|
19
20
|
whatsappbot: 'agent-whatsappbot',
|
|
20
21
|
line: 'agent-line',
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { TELEGRAMBOT_TEST_CHAT_ID, validateTelegramBotEnvironment } from './config'
|
|
4
|
+
import { generateTestId, parseJSON, runCLI, waitForRateLimit } from './helpers'
|
|
5
|
+
|
|
6
|
+
let telegramBotAvailable = false
|
|
7
|
+
let trackedMessageIds: number[] = []
|
|
8
|
+
|
|
9
|
+
async function deleteTrackedMessages(): Promise<void> {
|
|
10
|
+
for (const messageId of trackedMessageIds) {
|
|
11
|
+
try {
|
|
12
|
+
await runCLI('telegrambot', ['message', 'delete', TELEGRAMBOT_TEST_CHAT_ID, String(messageId), '--force'])
|
|
13
|
+
await waitForRateLimit(300)
|
|
14
|
+
} catch {
|
|
15
|
+
// best-effort cleanup
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
trackedMessageIds = []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('Telegram Bot E2E Tests', () => {
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
telegramBotAvailable = await validateTelegramBotEnvironment()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
if (trackedMessageIds.length > 0) {
|
|
28
|
+
await deleteTrackedMessages()
|
|
29
|
+
}
|
|
30
|
+
await waitForRateLimit()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('auth', () => {
|
|
34
|
+
it('auth status returns valid bot info', async () => {
|
|
35
|
+
if (!telegramBotAvailable) return
|
|
36
|
+
|
|
37
|
+
const result = await runCLI('telegrambot', ['auth', 'status'])
|
|
38
|
+
expect(result.exitCode).toBe(0)
|
|
39
|
+
|
|
40
|
+
const data = parseJSON<{ valid: boolean; bot_id: string }>(result.stdout)
|
|
41
|
+
expect(data?.valid).toBe(true)
|
|
42
|
+
expect(data?.bot_id).toBeTruthy()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('auth list returns bots array', async () => {
|
|
46
|
+
if (!telegramBotAvailable) return
|
|
47
|
+
|
|
48
|
+
const result = await runCLI('telegrambot', ['auth', 'list'])
|
|
49
|
+
expect(result.exitCode).toBe(0)
|
|
50
|
+
|
|
51
|
+
const data = parseJSON<{ bots: Array<{ bot_id: string }> }>(result.stdout)
|
|
52
|
+
expect(Array.isArray(data?.bots)).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('whoami', () => {
|
|
57
|
+
it('whoami returns bot identity', async () => {
|
|
58
|
+
if (!telegramBotAvailable) return
|
|
59
|
+
|
|
60
|
+
const result = await runCLI('telegrambot', ['whoami'])
|
|
61
|
+
expect(result.exitCode).toBe(0)
|
|
62
|
+
|
|
63
|
+
const data = parseJSON<{ id: number; is_bot: boolean }>(result.stdout)
|
|
64
|
+
expect(data?.id).toBeTruthy()
|
|
65
|
+
expect(data?.is_bot).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('chat', () => {
|
|
70
|
+
it('chat info returns chat details', async () => {
|
|
71
|
+
if (!telegramBotAvailable) return
|
|
72
|
+
|
|
73
|
+
const result = await runCLI('telegrambot', ['chat', 'info', TELEGRAMBOT_TEST_CHAT_ID])
|
|
74
|
+
expect(result.exitCode).toBe(0)
|
|
75
|
+
|
|
76
|
+
const data = parseJSON<{ id: number; type: string }>(result.stdout)
|
|
77
|
+
expect(data?.id).toBeTruthy()
|
|
78
|
+
expect(data?.type).toBeTruthy()
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('message', () => {
|
|
83
|
+
it('message send delivers message and returns id', async () => {
|
|
84
|
+
if (!telegramBotAvailable) return
|
|
85
|
+
|
|
86
|
+
const testId = generateTestId()
|
|
87
|
+
const result = await runCLI('telegrambot', ['message', 'send', TELEGRAMBOT_TEST_CHAT_ID, `Bot e2e ${testId}`])
|
|
88
|
+
expect(result.exitCode).toBe(0)
|
|
89
|
+
|
|
90
|
+
const data = parseJSON<{ message: { message_id: number; chat_id: number; text: string } }>(result.stdout)
|
|
91
|
+
expect(data?.message?.message_id).toBeTruthy()
|
|
92
|
+
expect(data?.message?.text).toContain(testId)
|
|
93
|
+
if (data?.message?.message_id) trackedMessageIds.push(data.message.message_id)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('message update edits previously sent message', async () => {
|
|
97
|
+
if (!telegramBotAvailable) return
|
|
98
|
+
|
|
99
|
+
const testId = generateTestId()
|
|
100
|
+
const sendResult = await runCLI('telegrambot', [
|
|
101
|
+
'message',
|
|
102
|
+
'send',
|
|
103
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
104
|
+
`Original ${testId}`,
|
|
105
|
+
])
|
|
106
|
+
const sent = parseJSON<{ message: { message_id: number } }>(sendResult.stdout)
|
|
107
|
+
expect(sent?.message?.message_id).toBeTruthy()
|
|
108
|
+
if (sent?.message?.message_id) trackedMessageIds.push(sent.message.message_id)
|
|
109
|
+
|
|
110
|
+
await waitForRateLimit()
|
|
111
|
+
|
|
112
|
+
const updateResult = await runCLI('telegrambot', [
|
|
113
|
+
'message',
|
|
114
|
+
'update',
|
|
115
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
116
|
+
String(sent!.message.message_id),
|
|
117
|
+
`Edited ${testId}`,
|
|
118
|
+
])
|
|
119
|
+
expect(updateResult.exitCode).toBe(0)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('message delete removes message', async () => {
|
|
123
|
+
if (!telegramBotAvailable) return
|
|
124
|
+
|
|
125
|
+
const testId = generateTestId()
|
|
126
|
+
const sendResult = await runCLI('telegrambot', [
|
|
127
|
+
'message',
|
|
128
|
+
'send',
|
|
129
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
130
|
+
`Delete me ${testId}`,
|
|
131
|
+
])
|
|
132
|
+
const sent = parseJSON<{ message: { message_id: number } }>(sendResult.stdout)
|
|
133
|
+
expect(sent?.message?.message_id).toBeTruthy()
|
|
134
|
+
|
|
135
|
+
await waitForRateLimit()
|
|
136
|
+
|
|
137
|
+
const deleteResult = await runCLI('telegrambot', [
|
|
138
|
+
'message',
|
|
139
|
+
'delete',
|
|
140
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
141
|
+
String(sent!.message.message_id),
|
|
142
|
+
'--force',
|
|
143
|
+
])
|
|
144
|
+
expect(deleteResult.exitCode).toBe(0)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('reaction', () => {
|
|
149
|
+
it('reaction set and clear lifecycle', async () => {
|
|
150
|
+
if (!telegramBotAvailable) return
|
|
151
|
+
|
|
152
|
+
const testId = generateTestId()
|
|
153
|
+
const sendResult = await runCLI('telegrambot', [
|
|
154
|
+
'message',
|
|
155
|
+
'send',
|
|
156
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
157
|
+
`Reaction test ${testId}`,
|
|
158
|
+
])
|
|
159
|
+
const sent = parseJSON<{ message: { message_id: number } }>(sendResult.stdout)
|
|
160
|
+
expect(sent?.message?.message_id).toBeTruthy()
|
|
161
|
+
if (sent?.message?.message_id) trackedMessageIds.push(sent.message.message_id)
|
|
162
|
+
|
|
163
|
+
await waitForRateLimit(2000)
|
|
164
|
+
|
|
165
|
+
const setResult = await runCLI('telegrambot', [
|
|
166
|
+
'reaction',
|
|
167
|
+
'set',
|
|
168
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
169
|
+
String(sent!.message.message_id),
|
|
170
|
+
'👍',
|
|
171
|
+
])
|
|
172
|
+
expect(setResult.exitCode).toBe(0)
|
|
173
|
+
|
|
174
|
+
await waitForRateLimit(2000)
|
|
175
|
+
|
|
176
|
+
const clearResult = await runCLI('telegrambot', [
|
|
177
|
+
'reaction',
|
|
178
|
+
'clear',
|
|
179
|
+
TELEGRAMBOT_TEST_CHAT_ID,
|
|
180
|
+
String(sent!.message.message_id),
|
|
181
|
+
])
|
|
182
|
+
expect(clearResult.exitCode).toBe(0)
|
|
183
|
+
}, 15000)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Run `agent-telegrambot auth set <token>` first so login() can pick up stored creds.
|
|
3
|
+
import { TelegramBotClient } from '../src/platforms/telegrambot/client'
|
|
4
|
+
import { TelegramBotListener } from '../src/platforms/telegrambot/listener'
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const client = await new TelegramBotClient().login()
|
|
8
|
+
|
|
9
|
+
const listener = new TelegramBotListener(client, {
|
|
10
|
+
allowedUpdates: ['message', 'edited_message', 'callback_query', 'my_chat_member'],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
listener.on('connected', ({ user }) => {
|
|
14
|
+
console.log(`Connected as @${user.username ?? user.first_name} (id: ${user.id})`)
|
|
15
|
+
console.log('Listening for events. Press Ctrl+C to stop.\n')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
listener.on('disconnected', () => {
|
|
19
|
+
console.log('[disconnected] retrying...')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
listener.on('message', (message) => {
|
|
23
|
+
if (message.from?.is_bot) return
|
|
24
|
+
const time = new Date(message.date * 1000).toLocaleTimeString()
|
|
25
|
+
const sender = message.from?.username ?? message.from?.first_name ?? 'unknown'
|
|
26
|
+
console.log(`[${time}] message in ${message.chat.id} <${sender}>: ${message.text ?? '(non-text)'}`)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
listener.on('edited_message', (message) => {
|
|
30
|
+
console.log(`[edit] message ${message.message_id} in ${message.chat.id}`)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
listener.on('callback_query', (query) => {
|
|
34
|
+
console.log(`[callback] data=${query.data ?? '(none)'} from ${query.from.username ?? query.from.id}`)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
listener.on('my_chat_member', (event) => {
|
|
38
|
+
console.log(`[my_chat_member] ${event.chat.id}: ${event.old_chat_member.status} -> ${event.new_chat_member.status}`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
listener.on('error', (err) => {
|
|
42
|
+
console.error(`[error] ${err.message}`)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
process.on('SIGINT', () => {
|
|
46
|
+
console.log('\nStopping...')
|
|
47
|
+
listener.stop()
|
|
48
|
+
process.exit(130)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
await listener.start()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-messenger",
|
|
3
|
-
"version": "2.12.
|
|
4
|
-
"description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
|
|
3
|
+
"version": "2.12.2",
|
|
4
|
+
"description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/agent-messenger/agent-messenger"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"agent-slackbot": "dist/src/platforms/slackbot/cli.js",
|
|
20
20
|
"agent-teams": "dist/src/platforms/teams/cli.js",
|
|
21
21
|
"agent-telegram": "dist/src/platforms/telegram/cli.js",
|
|
22
|
+
"agent-telegrambot": "dist/src/platforms/telegrambot/cli.js",
|
|
22
23
|
"agent-webex": "dist/src/platforms/webex/cli.js",
|
|
23
24
|
"agent-wechatbot": "dist/src/platforms/wechatbot/cli.js",
|
|
24
25
|
"agent-whatsapp": "dist/src/platforms/whatsapp/cli.js",
|
|
@@ -71,6 +72,9 @@
|
|
|
71
72
|
],
|
|
72
73
|
"channeltalkbot": [
|
|
73
74
|
"./dist/src/platforms/channeltalkbot/index.d.ts"
|
|
75
|
+
],
|
|
76
|
+
"telegrambot": [
|
|
77
|
+
"./dist/src/platforms/telegrambot/index.d.ts"
|
|
74
78
|
]
|
|
75
79
|
}
|
|
76
80
|
},
|
|
@@ -135,6 +139,10 @@
|
|
|
135
139
|
"./channeltalkbot": {
|
|
136
140
|
"types": "./dist/src/platforms/channeltalkbot/index.d.ts",
|
|
137
141
|
"default": "./dist/src/platforms/channeltalkbot/index.js"
|
|
142
|
+
},
|
|
143
|
+
"./telegrambot": {
|
|
144
|
+
"types": "./dist/src/platforms/telegrambot/index.d.ts",
|
|
145
|
+
"default": "./dist/src/platforms/telegrambot/index.js"
|
|
138
146
|
}
|
|
139
147
|
},
|
|
140
148
|
"scripts": {
|
package/scripts/postbuild.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-channeltalk
|
|
3
3
|
description: Interact with Channel Talk using extracted desktop app or browser credentials - read chats, send messages, search messages, manage groups
|
|
4
|
-
version: 2.12.
|
|
4
|
+
version: 2.12.2
|
|
5
5
|
allowed-tools: Bash(agent-channeltalk:*)
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-kakaotalk
|
|
3
3
|
description: Interact with KakaoTalk - send messages, read chats, manage conversations
|
|
4
|
-
version: 2.12.
|
|
4
|
+
version: 2.12.2
|
|
5
5
|
allowed-tools: Bash(agent-kakaotalk:*)
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -56,7 +56,7 @@ Registers the CLI as a sub-device (tablet slot by default). Your desktop app kee
|
|
|
56
56
|
agent-kakaotalk auth login
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
In interactive mode, this prompts for email and password.
|
|
59
|
+
In interactive mode, this prompts for email and password. On macOS and Windows, the CLI first tries to extract cached credentials from the desktop app so you may not need to type anything. On Linux there is no desktop app, so always pass credentials explicitly via `--email` and `--password` (or `--password-file`).
|
|
60
60
|
|
|
61
61
|
For AI agents (non-interactive), provide credentials via flags:
|
|
62
62
|
|
|
@@ -488,8 +488,7 @@ See the [KakaoTalk SDK documentation](https://agent-messenger.dev/docs/sdk/kakao
|
|
|
488
488
|
|
|
489
489
|
## Limitations
|
|
490
490
|
|
|
491
|
-
- macOS and Windows only (desktop
|
|
492
|
-
- No Linux support (KakaoTalk desktop not available on Linux)
|
|
491
|
+
- Auto-extraction of email/password from the desktop app is **macOS and Windows only** (KakaoTalk desktop is not available on Linux). Linux users must pass `--email` and `--password` (or `--password-file`) explicitly — the LOCO protocol, login flow, and all messaging features work on Linux.
|
|
493
492
|
- No file upload or download
|
|
494
493
|
- No channel/chat room creation or management
|
|
495
494
|
- No friend list management
|
|
@@ -526,7 +525,15 @@ pnpm dlx --package agent-messenger agent-kakaotalk chat list --pretty
|
|
|
526
525
|
|
|
527
526
|
### Password prompt on fresh install
|
|
528
527
|
|
|
529
|
-
On fresh installs, the desktop app (macOS or Windows) may hash or omit the password from its cache, so the CLI cannot extract it automatically. The CLI will prompt for the password once to register the device — via a native dialog on macOS (AppKit)
|
|
528
|
+
On fresh installs, the desktop app (macOS or Windows) may hash or omit the password from its cache, so the CLI cannot extract it automatically. The CLI will prompt for the password once to register the device — via a native dialog on macOS (AppKit), Windows (PowerShell WinForms), or Linux (`zenity` / `kdialog`), or via a TTY prompt if a terminal is available. After registration, the password is never needed again.
|
|
529
|
+
|
|
530
|
+
On Linux there is no desktop app to extract from, so always provide credentials explicitly:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
agent-kakaotalk auth login --email user@example.com --password-file /tmp/.kakao-pw
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`--password-file` reads the file then immediately deletes it, so the password never appears in shell history or process listings.
|
|
530
537
|
|
|
531
538
|
When the CLI returns `{"next_action": "run_interactive", ...}`, use a tmux session to let the user type their password securely. See "Handling `run_interactive`" above for the exact steps.
|
|
532
539
|
|