agent-messenger 2.3.0 → 2.4.0
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 +16 -16
- package/.claude-plugin/marketplace.json +29 -29
- package/.claude-plugin/plugin.json +5 -5
- package/CONTRIBUTING.md +1 -1
- package/README.md +8 -5
- package/bun.lock +70 -110
- package/bunfig.toml +3 -0
- package/dist/package.json +11 -3
- package/dist/src/platforms/discordbot/client.js +2 -2
- package/dist/src/platforms/discordbot/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +2 -1
- package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +52 -2
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/profile.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/commands/profile.js +19 -0
- package/dist/src/platforms/kakaotalk/commands/profile.js.map +1 -0
- package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +1 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +16 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +8 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/client.js +9 -36
- package/dist/src/platforms/line/client.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +32 -20
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/reaction.js +2 -0
- package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
- package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
- package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/cli.js +18 -0
- package/dist/src/platforms/wechatbot/cli.js.map +1 -0
- package/dist/src/platforms/wechatbot/client.d.ts +36 -0
- package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/client.js +208 -0
- package/dist/src/platforms/wechatbot/client.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
- package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
- package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts +5 -0
- package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/index.js +5 -0
- package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
- package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/message.js +80 -0
- package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
- package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
- package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
- package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/template.js +76 -0
- package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
- package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
- package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/commands/user.js +53 -0
- package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
- package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
- package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
- package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
- package/dist/src/platforms/wechatbot/index.d.ts +5 -0
- package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/index.js +4 -0
- package/dist/src/platforms/wechatbot/index.js.map +1 -0
- package/dist/src/platforms/wechatbot/types.d.ts +94 -0
- package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
- package/dist/src/platforms/wechatbot/types.js +54 -0
- package/dist/src/platforms/wechatbot/types.js.map +1 -0
- package/dist/src/platforms/whatsapp/client.d.ts +1 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +27 -13
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/commands/auth.js +21 -18
- package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
- package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
- package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +4 -4
- package/docs/content/docs/cli/channeltalk.mdx +1 -1
- package/docs/content/docs/cli/channeltalkbot.mdx +1 -1
- package/docs/content/docs/cli/discord.mdx +1 -1
- package/docs/content/docs/cli/discordbot.mdx +1 -1
- package/docs/content/docs/cli/instagram.mdx +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +1 -1
- package/docs/content/docs/cli/line.mdx +1 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/slack.mdx +1 -1
- package/docs/content/docs/cli/slackbot.mdx +1 -1
- package/docs/content/docs/cli/teams.mdx +1 -1
- package/docs/content/docs/cli/webex.mdx +1 -1
- package/docs/content/docs/cli/wechatbot.mdx +179 -0
- package/docs/content/docs/cli/whatsapp.mdx +1 -1
- package/docs/content/docs/cli/whatsappbot.mdx +1 -1
- package/docs/content/docs/sdk/meta.json +1 -1
- package/docs/content/docs/sdk/wechatbot.mdx +282 -0
- package/docs/content/docs/tui.mdx +1 -1
- package/docs/src/app/page.tsx +5 -5
- package/package.json +11 -3
- 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 +24 -1
- package/skills/agent-line/SKILL.md +7 -11
- package/skills/agent-line/references/authentication.md +13 -4
- 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-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +385 -0
- package/skills/agent-whatsapp/SKILL.md +12 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discord/credential-manager.test.ts +18 -1
- package/src/platforms/discordbot/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.test.ts +216 -0
- package/src/platforms/instagram/commands/chat.test.ts +127 -0
- package/src/platforms/instagram/commands/message.test.ts +178 -0
- package/src/platforms/kakaotalk/cli.ts +2 -1
- package/src/platforms/kakaotalk/client.test.ts +157 -0
- package/src/platforms/kakaotalk/client.ts +57 -3
- package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
- package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
- package/src/platforms/kakaotalk/commands/index.ts +1 -0
- package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
- package/src/platforms/kakaotalk/commands/profile.test.ts +84 -0
- package/src/platforms/kakaotalk/commands/profile.ts +21 -0
- package/src/platforms/kakaotalk/index.test.ts +5 -0
- package/src/platforms/kakaotalk/index.ts +2 -0
- package/src/platforms/kakaotalk/protocol/session.ts +2 -0
- package/src/platforms/kakaotalk/types.ts +18 -0
- package/src/platforms/line/client.ts +14 -39
- package/src/platforms/line/commands/auth.test.ts +141 -0
- package/src/platforms/line/commands/auth.ts +28 -19
- package/src/platforms/line/commands/chat.test.ts +110 -0
- package/src/platforms/line/commands/friend.test.ts +98 -0
- package/src/platforms/line/commands/message.test.ts +119 -0
- package/src/platforms/line/commands/profile.test.ts +85 -0
- package/src/platforms/slackbot/commands/channel.test.ts +139 -0
- package/src/platforms/slackbot/commands/message.test.ts +226 -0
- package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
- package/src/platforms/slackbot/commands/user.test.ts +143 -0
- package/src/platforms/teams/commands/reaction.test.ts +45 -61
- package/src/platforms/teams/commands/reaction.ts +2 -0
- package/src/platforms/telegram/commands/chat.test.ts +125 -0
- package/src/platforms/telegram/commands/message.test.ts +92 -0
- package/src/platforms/webex/commands/member.test.ts +65 -58
- package/src/platforms/webex/commands/message.test.ts +78 -121
- package/src/platforms/webex/commands/snapshot.test.ts +59 -46
- package/src/platforms/webex/commands/space.test.ts +49 -48
- package/src/platforms/wechatbot/cli.ts +24 -0
- package/src/platforms/wechatbot/client.test.ts +497 -0
- package/src/platforms/wechatbot/client.ts +268 -0
- package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
- package/src/platforms/wechatbot/commands/auth.ts +203 -0
- package/src/platforms/wechatbot/commands/index.ts +4 -0
- package/src/platforms/wechatbot/commands/message.test.ts +155 -0
- package/src/platforms/wechatbot/commands/message.ts +104 -0
- package/src/platforms/wechatbot/commands/shared.ts +22 -0
- package/src/platforms/wechatbot/commands/template.test.ts +199 -0
- package/src/platforms/wechatbot/commands/template.ts +102 -0
- package/src/platforms/wechatbot/commands/user.test.ts +165 -0
- package/src/platforms/wechatbot/commands/user.ts +75 -0
- package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
- package/src/platforms/wechatbot/credential-manager.ts +148 -0
- package/src/platforms/wechatbot/index.test.ts +49 -0
- package/src/platforms/wechatbot/index.ts +19 -0
- package/src/platforms/wechatbot/types.test.ts +223 -0
- package/src/platforms/wechatbot/types.ts +107 -0
- package/src/platforms/whatsapp/client.ts +24 -13
- package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
- package/src/platforms/whatsapp/commands/auth.ts +21 -17
- package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
- package/src/platforms/whatsapp/commands/message.test.ts +231 -0
- package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
- package/src/platforms/whatsapp/credential-manager.ts +17 -8
- package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
- package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
- package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { afterAll, describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
import { rmSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { WeChatBotCredentialManager } from '@/platforms/wechatbot/credential-manager'
|
|
6
|
+
|
|
7
|
+
const sendTextMessageMock = mock(() => Promise.resolve())
|
|
8
|
+
const sendImageMessageMock = mock(() => Promise.resolve())
|
|
9
|
+
const sendNewsMessageMock = mock(() => Promise.resolve())
|
|
10
|
+
|
|
11
|
+
mock.module('../client', () => ({
|
|
12
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
13
|
+
async login() {
|
|
14
|
+
return this
|
|
15
|
+
}
|
|
16
|
+
sendTextMessage = sendTextMessageMock
|
|
17
|
+
sendImageMessage = sendImageMessageMock
|
|
18
|
+
sendNewsMessage = sendNewsMessageMock
|
|
19
|
+
},
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
const { sendAction, sendNewsAction } = await import('@/platforms/wechatbot/commands/message')
|
|
23
|
+
|
|
24
|
+
const testDirs: string[] = []
|
|
25
|
+
|
|
26
|
+
function makeCredManager(): WeChatBotCredentialManager {
|
|
27
|
+
const dir = join(
|
|
28
|
+
import.meta.dir,
|
|
29
|
+
`.test-message-config-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
30
|
+
)
|
|
31
|
+
testDirs.push(dir)
|
|
32
|
+
const manager = new WeChatBotCredentialManager(dir)
|
|
33
|
+
return manager
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function makeCredManagerWithCreds(): Promise<WeChatBotCredentialManager> {
|
|
37
|
+
const manager = makeCredManager()
|
|
38
|
+
await manager.setCredentials({ app_id: 'wx123', app_secret: 'secret123', account_name: 'My Account' })
|
|
39
|
+
return manager
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
for (const dir of testDirs) {
|
|
44
|
+
rmSync(dir, { recursive: true, force: true })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('sendAction', () => {
|
|
49
|
+
test('sends text message and returns success', async () => {
|
|
50
|
+
const credManager = await makeCredManagerWithCreds()
|
|
51
|
+
const result = await sendAction('openid-123', 'Hello world', { _credManager: credManager })
|
|
52
|
+
|
|
53
|
+
expect(result.success).toBe(true)
|
|
54
|
+
expect(sendTextMessageMock).toHaveBeenCalledWith('openid-123', 'Hello world')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('returns error when client throws', async () => {
|
|
58
|
+
mock.module('../client', () => ({
|
|
59
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
60
|
+
async login() {
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
sendTextMessage = mock(() => Promise.reject(new Error('API error')))
|
|
64
|
+
},
|
|
65
|
+
}))
|
|
66
|
+
|
|
67
|
+
const { sendAction: sendActionFresh } = await import('@/platforms/wechatbot/commands/message')
|
|
68
|
+
const credManager = await makeCredManagerWithCreds()
|
|
69
|
+
const result = await sendActionFresh('openid-123', 'Hello', { _credManager: credManager })
|
|
70
|
+
|
|
71
|
+
expect(result.error).toBe('API error')
|
|
72
|
+
expect(result.success).toBeUndefined()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('sendImageAction', () => {
|
|
77
|
+
test('sends image message and returns success', async () => {
|
|
78
|
+
mock.module('../client', () => ({
|
|
79
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
80
|
+
async login() {
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
sendImageMessage = mock(() => Promise.resolve())
|
|
84
|
+
},
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
const { sendImageAction: sendImageActionFresh } = await import('@/platforms/wechatbot/commands/message')
|
|
88
|
+
const credManager = await makeCredManagerWithCreds()
|
|
89
|
+
const result = await sendImageActionFresh('openid-123', 'media-id-456', { _credManager: credManager })
|
|
90
|
+
|
|
91
|
+
expect(result.success).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('returns error when client throws', async () => {
|
|
95
|
+
mock.module('../client', () => ({
|
|
96
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
97
|
+
async login() {
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
sendImageMessage = mock(() => Promise.reject(new Error('image upload failed')))
|
|
101
|
+
},
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
const { sendImageAction: sendImageActionFresh } = await import('@/platforms/wechatbot/commands/message')
|
|
105
|
+
const credManager = await makeCredManagerWithCreds()
|
|
106
|
+
const result = await sendImageActionFresh('openid-123', 'bad-media-id', { _credManager: credManager })
|
|
107
|
+
|
|
108
|
+
expect(result.error).toBe('image upload failed')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('sendNewsAction', () => {
|
|
113
|
+
test('returns error when required options are missing', async () => {
|
|
114
|
+
const credManager = await makeCredManagerWithCreds()
|
|
115
|
+
const result = await sendNewsAction('openid-123', { _credManager: credManager })
|
|
116
|
+
|
|
117
|
+
expect(result.error).toContain('--title')
|
|
118
|
+
expect(result.success).toBeUndefined()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('returns error when title is missing', async () => {
|
|
122
|
+
const credManager = await makeCredManagerWithCreds()
|
|
123
|
+
const result = await sendNewsAction('openid-123', {
|
|
124
|
+
description: 'Test desc',
|
|
125
|
+
url: 'https://example.com',
|
|
126
|
+
picurl: 'https://example.com/pic.jpg',
|
|
127
|
+
_credManager: credManager,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
expect(result.error).toContain('--title')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('sends news message with all required options', async () => {
|
|
134
|
+
mock.module('../client', () => ({
|
|
135
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
136
|
+
async login() {
|
|
137
|
+
return this
|
|
138
|
+
}
|
|
139
|
+
sendNewsMessage = mock(() => Promise.resolve())
|
|
140
|
+
},
|
|
141
|
+
}))
|
|
142
|
+
|
|
143
|
+
const { sendNewsAction: sendNewsActionFresh } = await import('@/platforms/wechatbot/commands/message')
|
|
144
|
+
const credManager = await makeCredManagerWithCreds()
|
|
145
|
+
const result = await sendNewsActionFresh('openid-123', {
|
|
146
|
+
title: 'Test Article',
|
|
147
|
+
description: 'Test description',
|
|
148
|
+
url: 'https://example.com',
|
|
149
|
+
picurl: 'https://example.com/pic.jpg',
|
|
150
|
+
_credManager: credManager,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
expect(result.success).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
4
|
+
|
|
5
|
+
import type { WeChatBotNewsArticle } from '../types'
|
|
6
|
+
import type { AccountOption } from './shared'
|
|
7
|
+
import { getClient } from './shared'
|
|
8
|
+
|
|
9
|
+
interface MessageResult {
|
|
10
|
+
success?: boolean
|
|
11
|
+
error?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type MessageOptions = AccountOption & {
|
|
15
|
+
title?: string
|
|
16
|
+
description?: string
|
|
17
|
+
url?: string
|
|
18
|
+
picurl?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function sendAction(openId: string, text: string, options: MessageOptions): Promise<MessageResult> {
|
|
22
|
+
try {
|
|
23
|
+
const client = await getClient(options)
|
|
24
|
+
await client.sendTextMessage(openId, text)
|
|
25
|
+
return { success: true }
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return { error: (error as Error).message }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function sendImageAction(openId: string, mediaId: string, options: MessageOptions): Promise<MessageResult> {
|
|
32
|
+
try {
|
|
33
|
+
const client = await getClient(options)
|
|
34
|
+
await client.sendImageMessage(openId, mediaId)
|
|
35
|
+
return { success: true }
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { error: (error as Error).message }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function sendNewsAction(openId: string, options: MessageOptions): Promise<MessageResult> {
|
|
42
|
+
try {
|
|
43
|
+
if (!options.title || !options.description || !options.url || !options.picurl) {
|
|
44
|
+
return { error: '--title, --description, --url, and --picurl are required' }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const article: WeChatBotNewsArticle = {
|
|
48
|
+
title: options.title,
|
|
49
|
+
description: options.description,
|
|
50
|
+
url: options.url,
|
|
51
|
+
picurl: options.picurl,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const client = await getClient(options)
|
|
55
|
+
await client.sendNewsMessage(openId, [article])
|
|
56
|
+
return { success: true }
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { error: (error as Error).message }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cliOutput(result: MessageResult, pretty?: boolean): void {
|
|
63
|
+
console.log(formatOutput(result, pretty))
|
|
64
|
+
if (result.error) process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const messageCommand = new Command('message')
|
|
68
|
+
.description('Message commands')
|
|
69
|
+
.addCommand(
|
|
70
|
+
new Command('send')
|
|
71
|
+
.description('Send a text message (customer service)')
|
|
72
|
+
.argument('<open-id>', 'Recipient OpenID')
|
|
73
|
+
.argument('<text>', 'Message text')
|
|
74
|
+
.option('--account <id>', 'Account ID')
|
|
75
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
76
|
+
.action(async (openId: string, text: string, opts: MessageOptions) => {
|
|
77
|
+
cliOutput(await sendAction(openId, text, opts), opts.pretty)
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
.addCommand(
|
|
81
|
+
new Command('send-image')
|
|
82
|
+
.description('Send an image message (customer service)')
|
|
83
|
+
.argument('<open-id>', 'Recipient OpenID')
|
|
84
|
+
.argument('<media-id>', 'Media ID of the image')
|
|
85
|
+
.option('--account <id>', 'Account ID')
|
|
86
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
87
|
+
.action(async (openId: string, mediaId: string, opts: MessageOptions) => {
|
|
88
|
+
cliOutput(await sendImageAction(openId, mediaId, opts), opts.pretty)
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
.addCommand(
|
|
92
|
+
new Command('send-news')
|
|
93
|
+
.description('Send a news/article message (customer service)')
|
|
94
|
+
.argument('<open-id>', 'Recipient OpenID')
|
|
95
|
+
.requiredOption('--title <title>', 'Article title')
|
|
96
|
+
.requiredOption('--description <description>', 'Article description')
|
|
97
|
+
.requiredOption('--url <url>', 'Article URL')
|
|
98
|
+
.requiredOption('--picurl <picurl>', 'Article picture URL')
|
|
99
|
+
.option('--account <id>', 'Account ID')
|
|
100
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
101
|
+
.action(async (openId: string, opts: MessageOptions) => {
|
|
102
|
+
cliOutput(await sendNewsAction(openId, opts), opts.pretty)
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
2
|
+
|
|
3
|
+
import { WeChatBotClient } from '../client'
|
|
4
|
+
import { WeChatBotCredentialManager } from '../credential-manager'
|
|
5
|
+
|
|
6
|
+
export interface AccountOption {
|
|
7
|
+
account?: string
|
|
8
|
+
pretty?: boolean
|
|
9
|
+
_credManager?: WeChatBotCredentialManager
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getClient(options: AccountOption): Promise<WeChatBotClient> {
|
|
13
|
+
const credManager = options._credManager ?? new WeChatBotCredentialManager()
|
|
14
|
+
const creds = await credManager.getCredentials(options.account)
|
|
15
|
+
|
|
16
|
+
if (!creds) {
|
|
17
|
+
console.log(formatOutput({ error: 'No credentials. Run "auth set <app-id> <app-secret>" first.' }, options.pretty))
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return await new WeChatBotClient().login({ appId: creds.app_id, appSecret: creds.app_secret })
|
|
22
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { afterAll, describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
import { rmSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { WeChatBotCredentialManager } from '@/platforms/wechatbot/credential-manager'
|
|
6
|
+
|
|
7
|
+
const mockTemplates = [
|
|
8
|
+
{
|
|
9
|
+
template_id: 'tmpl-001',
|
|
10
|
+
title: 'Order Notification',
|
|
11
|
+
primary_industry: 'IT科技',
|
|
12
|
+
deputy_industry: '互联网|电子商务',
|
|
13
|
+
content: 'ORDER_STATUS {{status.DATA}}',
|
|
14
|
+
example: 'Order shipped',
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
mock.module('../client', () => ({
|
|
19
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
20
|
+
async login() {
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
listTemplates = mock(() => Promise.resolve(mockTemplates))
|
|
24
|
+
sendTemplateMessage = mock(() => Promise.resolve({ msgid: 12345 }))
|
|
25
|
+
deleteTemplate = mock(() => Promise.resolve())
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
const { listAction } = await import('@/platforms/wechatbot/commands/template')
|
|
30
|
+
|
|
31
|
+
const testDirs: string[] = []
|
|
32
|
+
|
|
33
|
+
function makeCredManager(): WeChatBotCredentialManager {
|
|
34
|
+
const dir = join(
|
|
35
|
+
import.meta.dir,
|
|
36
|
+
`.test-template-config-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
37
|
+
)
|
|
38
|
+
testDirs.push(dir)
|
|
39
|
+
return new WeChatBotCredentialManager(dir)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function makeCredManagerWithCreds(): Promise<WeChatBotCredentialManager> {
|
|
43
|
+
const manager = makeCredManager()
|
|
44
|
+
await manager.setCredentials({ app_id: 'wx123', app_secret: 'secret123', account_name: 'My Account' })
|
|
45
|
+
return manager
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
afterAll(() => {
|
|
49
|
+
for (const dir of testDirs) {
|
|
50
|
+
rmSync(dir, { recursive: true, force: true })
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('listAction', () => {
|
|
55
|
+
test('returns templates list', async () => {
|
|
56
|
+
const credManager = await makeCredManagerWithCreds()
|
|
57
|
+
const result = await listAction({ _credManager: credManager })
|
|
58
|
+
|
|
59
|
+
expect(result.templates).toHaveLength(1)
|
|
60
|
+
expect(result.templates?.[0].template_id).toBe('tmpl-001')
|
|
61
|
+
expect(result.templates?.[0].title).toBe('Order Notification')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('returns error when client throws', async () => {
|
|
65
|
+
mock.module('../client', () => ({
|
|
66
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
67
|
+
async login() {
|
|
68
|
+
return this
|
|
69
|
+
}
|
|
70
|
+
listTemplates = mock(() => Promise.reject(new Error('API error')))
|
|
71
|
+
},
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
const { listAction: listActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
75
|
+
const credManager = await makeCredManagerWithCreds()
|
|
76
|
+
const result = await listActionFresh({ _credManager: credManager })
|
|
77
|
+
|
|
78
|
+
expect(result.error).toBe('API error')
|
|
79
|
+
expect(result.templates).toBeUndefined()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('sendAction (template)', () => {
|
|
84
|
+
test('sends template message and returns msgid', async () => {
|
|
85
|
+
mock.module('../client', () => ({
|
|
86
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
87
|
+
async login() {
|
|
88
|
+
return this
|
|
89
|
+
}
|
|
90
|
+
sendTemplateMessage = mock(() => Promise.resolve({ msgid: 99999 }))
|
|
91
|
+
},
|
|
92
|
+
}))
|
|
93
|
+
|
|
94
|
+
const { sendAction: sendActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
95
|
+
const credManager = await makeCredManagerWithCreds()
|
|
96
|
+
const result = await sendActionFresh('openid-123', 'tmpl-001', {
|
|
97
|
+
data: JSON.stringify({ first: { value: 'Hello' } }),
|
|
98
|
+
url: 'https://example.com',
|
|
99
|
+
_credManager: credManager,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(result.msgid).toBe(99999)
|
|
103
|
+
expect(result.error).toBeUndefined()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('returns error when data is invalid JSON', async () => {
|
|
107
|
+
mock.module('../client', () => ({
|
|
108
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
109
|
+
async login() {
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
sendTemplateMessage = mock(() => Promise.resolve({ msgid: 1 }))
|
|
113
|
+
},
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
const { sendAction: sendActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
117
|
+
const credManager = await makeCredManagerWithCreds()
|
|
118
|
+
const result = await sendActionFresh('openid-123', 'tmpl-001', {
|
|
119
|
+
data: '{invalid json}',
|
|
120
|
+
_credManager: credManager,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(result.error).toContain('Invalid --data JSON')
|
|
124
|
+
expect(result.msgid).toBeUndefined()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('sends without data when not provided', async () => {
|
|
128
|
+
const sendTemplateMsg = mock(() => Promise.resolve({ msgid: 777 }))
|
|
129
|
+
mock.module('../client', () => ({
|
|
130
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
131
|
+
async login() {
|
|
132
|
+
return this
|
|
133
|
+
}
|
|
134
|
+
sendTemplateMessage = sendTemplateMsg
|
|
135
|
+
},
|
|
136
|
+
}))
|
|
137
|
+
|
|
138
|
+
const { sendAction: sendActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
139
|
+
const credManager = await makeCredManagerWithCreds()
|
|
140
|
+
const result = await sendActionFresh('openid-123', 'tmpl-001', { _credManager: credManager })
|
|
141
|
+
|
|
142
|
+
expect(result.msgid).toBe(777)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('returns error when client throws', async () => {
|
|
146
|
+
mock.module('../client', () => ({
|
|
147
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
148
|
+
async login() {
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
sendTemplateMessage = mock(() => Promise.reject(new Error('template send failed')))
|
|
152
|
+
},
|
|
153
|
+
}))
|
|
154
|
+
|
|
155
|
+
const { sendAction: sendActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
156
|
+
const credManager = await makeCredManagerWithCreds()
|
|
157
|
+
const result = await sendActionFresh('openid-123', 'tmpl-001', { _credManager: credManager })
|
|
158
|
+
|
|
159
|
+
expect(result.error).toBe('template send failed')
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('deleteAction', () => {
|
|
164
|
+
test('deletes template and returns success', async () => {
|
|
165
|
+
mock.module('../client', () => ({
|
|
166
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
167
|
+
async login() {
|
|
168
|
+
return this
|
|
169
|
+
}
|
|
170
|
+
deleteTemplate = mock(() => Promise.resolve())
|
|
171
|
+
},
|
|
172
|
+
}))
|
|
173
|
+
|
|
174
|
+
const { deleteAction: deleteActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
175
|
+
const credManager = await makeCredManagerWithCreds()
|
|
176
|
+
const result = await deleteActionFresh('tmpl-to-delete', { _credManager: credManager })
|
|
177
|
+
|
|
178
|
+
expect(result.success).toBe(true)
|
|
179
|
+
expect(result.error).toBeUndefined()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('returns error when client throws', async () => {
|
|
183
|
+
mock.module('../client', () => ({
|
|
184
|
+
WeChatBotClient: class MockWeChatBotClient {
|
|
185
|
+
async login() {
|
|
186
|
+
return this
|
|
187
|
+
}
|
|
188
|
+
deleteTemplate = mock(() => Promise.reject(new Error('template not found')))
|
|
189
|
+
},
|
|
190
|
+
}))
|
|
191
|
+
|
|
192
|
+
const { deleteAction: deleteActionFresh } = await import('@/platforms/wechatbot/commands/template')
|
|
193
|
+
const credManager = await makeCredManagerWithCreds()
|
|
194
|
+
const result = await deleteActionFresh('nonexistent-tmpl', { _credManager: credManager })
|
|
195
|
+
|
|
196
|
+
expect(result.error).toBe('template not found')
|
|
197
|
+
expect(result.success).toBeUndefined()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
4
|
+
|
|
5
|
+
import type { WeChatBotTemplate } from '../types'
|
|
6
|
+
import type { AccountOption } from './shared'
|
|
7
|
+
import { getClient } from './shared'
|
|
8
|
+
|
|
9
|
+
interface TemplateResult {
|
|
10
|
+
templates?: WeChatBotTemplate[]
|
|
11
|
+
msgid?: number
|
|
12
|
+
success?: boolean
|
|
13
|
+
error?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type TemplateOptions = AccountOption & {
|
|
17
|
+
data?: string
|
|
18
|
+
url?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function listAction(options: TemplateOptions): Promise<TemplateResult> {
|
|
22
|
+
try {
|
|
23
|
+
const client = await getClient(options)
|
|
24
|
+
const templates = await client.listTemplates()
|
|
25
|
+
return { templates }
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return { error: (error as Error).message }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function sendAction(
|
|
32
|
+
openId: string,
|
|
33
|
+
templateId: string,
|
|
34
|
+
options: TemplateOptions,
|
|
35
|
+
): Promise<TemplateResult> {
|
|
36
|
+
try {
|
|
37
|
+
let data: Record<string, { value: string }> = {}
|
|
38
|
+
if (options.data) {
|
|
39
|
+
try {
|
|
40
|
+
data = JSON.parse(options.data) as Record<string, { value: string }>
|
|
41
|
+
} catch {
|
|
42
|
+
return { error: 'Invalid --data JSON' }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const client = await getClient(options)
|
|
47
|
+
const result = await client.sendTemplateMessage(openId, templateId, data, options.url)
|
|
48
|
+
return { msgid: result.msgid }
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { error: (error as Error).message }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function deleteAction(templateId: string, options: TemplateOptions): Promise<TemplateResult> {
|
|
55
|
+
try {
|
|
56
|
+
const client = await getClient(options)
|
|
57
|
+
await client.deleteTemplate(templateId)
|
|
58
|
+
return { success: true }
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { error: (error as Error).message }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cliOutput(result: TemplateResult, pretty?: boolean): void {
|
|
65
|
+
console.log(formatOutput(result, pretty))
|
|
66
|
+
if (result.error) process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const templateCommand = new Command('template')
|
|
70
|
+
.description('Template message commands')
|
|
71
|
+
.addCommand(
|
|
72
|
+
new Command('list')
|
|
73
|
+
.description('List all private templates')
|
|
74
|
+
.option('--account <id>', 'Account ID')
|
|
75
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
76
|
+
.action(async (opts: TemplateOptions) => {
|
|
77
|
+
cliOutput(await listAction(opts), opts.pretty)
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
.addCommand(
|
|
81
|
+
new Command('send')
|
|
82
|
+
.description('Send a template message')
|
|
83
|
+
.argument('<open-id>', 'Recipient OpenID')
|
|
84
|
+
.argument('<template-id>', 'Template ID')
|
|
85
|
+
.option('--data <json>', 'Template data as JSON object')
|
|
86
|
+
.option('--url <url>', 'URL to redirect when message is clicked')
|
|
87
|
+
.option('--account <id>', 'Account ID')
|
|
88
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
89
|
+
.action(async (openId: string, templateId: string, opts: TemplateOptions) => {
|
|
90
|
+
cliOutput(await sendAction(openId, templateId, opts), opts.pretty)
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
.addCommand(
|
|
94
|
+
new Command('delete')
|
|
95
|
+
.description('Delete a private template')
|
|
96
|
+
.argument('<template-id>', 'Template ID to delete')
|
|
97
|
+
.option('--account <id>', 'Account ID')
|
|
98
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
99
|
+
.action(async (templateId: string, opts: TemplateOptions) => {
|
|
100
|
+
cliOutput(await deleteAction(templateId, opts), opts.pretty)
|
|
101
|
+
}),
|
|
102
|
+
)
|