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
|
@@ -2,34 +2,13 @@ import { mkdirSync } from 'node:fs'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const INSTALL_HINT = '@evex/linejs is required for LINE support. Install it with: bun add @evex/linejs@npm:@jsr/evex__linejs'
|
|
13
|
-
|
|
14
|
-
async function getLinejs(): Promise<LinejsModule> {
|
|
15
|
-
if (cachedLinejs) return cachedLinejs
|
|
16
|
-
try {
|
|
17
|
-
cachedLinejs = await import('@evex/linejs')
|
|
18
|
-
return cachedLinejs
|
|
19
|
-
} catch {
|
|
20
|
-
throw new Error(INSTALL_HINT)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function getLinejsStorage(): Promise<LinejsStorageModule> {
|
|
25
|
-
if (cachedLinejsStorage) return cachedLinejsStorage
|
|
26
|
-
try {
|
|
27
|
-
cachedLinejsStorage = await import('@evex/linejs/storage')
|
|
28
|
-
return cachedLinejsStorage
|
|
29
|
-
} catch {
|
|
30
|
-
throw new Error(INSTALL_HINT)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
loginWithQR as linejsLoginWithQR,
|
|
7
|
+
loginWithPassword as linejsLoginWithPassword,
|
|
8
|
+
loginWithAuthToken as linejsLoginWithAuthToken,
|
|
9
|
+
type Client,
|
|
10
|
+
} from '@evex/linejs'
|
|
11
|
+
import { FileStorage } from '@evex/linejs/storage'
|
|
33
12
|
|
|
34
13
|
import { LineCredentialManager } from './credential-manager'
|
|
35
14
|
import type {
|
|
@@ -61,8 +40,7 @@ function getDefaultDevice(): LineDevice {
|
|
|
61
40
|
return 'ANDROIDSECONDARY'
|
|
62
41
|
}
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
const { FileStorage } = await getLinejsStorage()
|
|
43
|
+
function createStorage(accountId?: string): FileStorage {
|
|
66
44
|
const dir = join(homedir(), '.config', 'agent-messenger', 'line-storage')
|
|
67
45
|
mkdirSync(dir, { recursive: true })
|
|
68
46
|
return new FileStorage(join(dir, `${accountId ?? 'default'}.json`))
|
|
@@ -82,11 +60,10 @@ export class LineClient {
|
|
|
82
60
|
onPincode: (pin: string) => void
|
|
83
61
|
}): Promise<LineLoginResult> {
|
|
84
62
|
try {
|
|
85
|
-
const linejs = await getLinejs()
|
|
86
63
|
const device: LineDevice = options.device ?? getDefaultDevice()
|
|
87
|
-
const storage =
|
|
64
|
+
const storage = createStorage()
|
|
88
65
|
|
|
89
|
-
const client = await
|
|
66
|
+
const client = await linejsLoginWithQR(
|
|
90
67
|
{
|
|
91
68
|
onReceiveQRUrl: (url) => options.onQRUrl(url),
|
|
92
69
|
onPincodeRequest: (pin) => options.onPincode(pin),
|
|
@@ -126,11 +103,10 @@ export class LineClient {
|
|
|
126
103
|
onPincode: (pin: string) => void
|
|
127
104
|
}): Promise<LineLoginResult> {
|
|
128
105
|
try {
|
|
129
|
-
const linejs = await getLinejs()
|
|
130
106
|
const device: LineDevice = options.device ?? getDefaultDevice()
|
|
131
|
-
const storage =
|
|
107
|
+
const storage = createStorage()
|
|
132
108
|
|
|
133
|
-
const client = await
|
|
109
|
+
const client = await linejsLoginWithPassword(
|
|
134
110
|
{
|
|
135
111
|
email: options.email,
|
|
136
112
|
password: options.password,
|
|
@@ -178,11 +154,10 @@ export class LineClient {
|
|
|
178
154
|
creds = account
|
|
179
155
|
}
|
|
180
156
|
|
|
181
|
-
const linejs = await getLinejs()
|
|
182
157
|
const device: LineDevice = creds.device ?? getDefaultDevice()
|
|
183
|
-
const storage =
|
|
158
|
+
const storage = createStorage()
|
|
184
159
|
|
|
185
|
-
this.client = await
|
|
160
|
+
this.client = await linejsLoginWithAuthToken(creds.auth_token, { device, storage })
|
|
186
161
|
return this
|
|
187
162
|
} catch (error) {
|
|
188
163
|
throw wrapError(error, 'login_failed')
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const originalConsoleLog = console.log
|
|
4
|
+
|
|
5
|
+
import { LineCredentialManager } from '../credential-manager'
|
|
6
|
+
import { authCommand } from './auth'
|
|
7
|
+
|
|
8
|
+
let getAccountSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let listAccountsSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let setCurrentAccountSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let removeAccountSpy: ReturnType<typeof spyOn>
|
|
12
|
+
let clearAllSpy: ReturnType<typeof spyOn>
|
|
13
|
+
let consoleLogSpy: ReturnType<typeof mock>
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
getAccountSpy = spyOn(LineCredentialManager.prototype, 'getAccount').mockResolvedValue({
|
|
17
|
+
account_id: 'u123',
|
|
18
|
+
auth_token: 'token-abc',
|
|
19
|
+
device: 'ANDROIDSECONDARY',
|
|
20
|
+
display_name: 'Test User',
|
|
21
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
22
|
+
updated_at: '2024-01-01T00:00:00Z',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
listAccountsSpy = spyOn(LineCredentialManager.prototype, 'listAccounts').mockResolvedValue([
|
|
26
|
+
{
|
|
27
|
+
account_id: 'u123',
|
|
28
|
+
display_name: 'Test User',
|
|
29
|
+
device: 'ANDROIDSECONDARY',
|
|
30
|
+
is_current: true,
|
|
31
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
account_id: 'u456',
|
|
35
|
+
display_name: 'Other User',
|
|
36
|
+
device: 'DESKTOPMAC',
|
|
37
|
+
is_current: false,
|
|
38
|
+
created_at: '2024-01-02T00:00:00Z',
|
|
39
|
+
},
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
setCurrentAccountSpy = spyOn(LineCredentialManager.prototype, 'setCurrentAccount').mockResolvedValue(undefined)
|
|
43
|
+
removeAccountSpy = spyOn(LineCredentialManager.prototype, 'removeAccount').mockResolvedValue(undefined)
|
|
44
|
+
clearAllSpy = spyOn(LineCredentialManager.prototype, 'clearAll').mockResolvedValue(undefined)
|
|
45
|
+
consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
getAccountSpy?.mockRestore()
|
|
50
|
+
listAccountsSpy?.mockRestore()
|
|
51
|
+
setCurrentAccountSpy?.mockRestore()
|
|
52
|
+
removeAccountSpy?.mockRestore()
|
|
53
|
+
clearAllSpy?.mockRestore()
|
|
54
|
+
console.log = originalConsoleLog
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('status: outputs account info when account exists', async () => {
|
|
58
|
+
// when
|
|
59
|
+
await authCommand.parseAsync(['node', 'auth', 'status'])
|
|
60
|
+
|
|
61
|
+
// then
|
|
62
|
+
expect(getAccountSpy).toHaveBeenCalledTimes(1)
|
|
63
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
64
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
65
|
+
expect(output.account_id).toBe('u123')
|
|
66
|
+
expect(output.display_name).toBe('Test User')
|
|
67
|
+
expect(output.device).toBe('ANDROIDSECONDARY')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('status: outputs error when no account configured', async () => {
|
|
71
|
+
// given
|
|
72
|
+
getAccountSpy.mockResolvedValue(null)
|
|
73
|
+
|
|
74
|
+
// when
|
|
75
|
+
await authCommand.parseAsync(['node', 'auth', 'status'])
|
|
76
|
+
|
|
77
|
+
// then
|
|
78
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
79
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
80
|
+
expect(output.error).toBe('No LINE account configured')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('list: outputs all accounts', async () => {
|
|
84
|
+
// when
|
|
85
|
+
await authCommand.parseAsync(['node', 'auth', 'list'])
|
|
86
|
+
|
|
87
|
+
// then
|
|
88
|
+
expect(listAccountsSpy).toHaveBeenCalledTimes(1)
|
|
89
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
90
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
91
|
+
expect(output).toHaveLength(2)
|
|
92
|
+
expect(output[0].account_id).toBe('u123')
|
|
93
|
+
expect(output[1].account_id).toBe('u456')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('list: outputs empty array when no accounts', async () => {
|
|
97
|
+
// given
|
|
98
|
+
listAccountsSpy.mockResolvedValue([])
|
|
99
|
+
|
|
100
|
+
// when
|
|
101
|
+
await authCommand.parseAsync(['node', 'auth', 'list'])
|
|
102
|
+
|
|
103
|
+
// then
|
|
104
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
105
|
+
expect(output).toHaveLength(0)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('use: sets current account and outputs result', async () => {
|
|
109
|
+
// when
|
|
110
|
+
await authCommand.parseAsync(['node', 'auth', 'use', 'u456'])
|
|
111
|
+
|
|
112
|
+
// then
|
|
113
|
+
expect(setCurrentAccountSpy).toHaveBeenCalledWith('u456')
|
|
114
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
115
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
116
|
+
expect(output.current_account).toBe('u456')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('logout: removes specific account when account-id provided', async () => {
|
|
120
|
+
// when
|
|
121
|
+
await authCommand.parseAsync(['node', 'auth', 'logout', 'u123'])
|
|
122
|
+
|
|
123
|
+
// then
|
|
124
|
+
expect(removeAccountSpy).toHaveBeenCalledWith('u123')
|
|
125
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
126
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
127
|
+
expect(output.success).toBe(true)
|
|
128
|
+
expect(output.message).toContain('u123')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('logout: clears all accounts when no account-id provided', async () => {
|
|
132
|
+
// when
|
|
133
|
+
await authCommand.parseAsync(['node', 'auth', 'logout'])
|
|
134
|
+
|
|
135
|
+
// then
|
|
136
|
+
expect(clearAllSpy).toHaveBeenCalledTimes(1)
|
|
137
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
138
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
139
|
+
expect(output.success).toBe(true)
|
|
140
|
+
expect(output.message).toBe('Logged out')
|
|
141
|
+
})
|
|
@@ -13,7 +13,6 @@ import { info } from '@/shared/utils/stderr'
|
|
|
13
13
|
import { LineClient } from '../client'
|
|
14
14
|
import { LineCredentialManager } from '../credential-manager'
|
|
15
15
|
import type { LineDevice } from '../types'
|
|
16
|
-
import { LINE_NEXT_ACTIONS } from '../types'
|
|
17
16
|
|
|
18
17
|
function isInteractiveSession(): boolean {
|
|
19
18
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
@@ -23,7 +22,7 @@ function getDefaultDevice(): LineDevice {
|
|
|
23
22
|
return 'ANDROIDSECONDARY'
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
async function
|
|
25
|
+
async function createQRHtmlFile(url: string): Promise<string> {
|
|
27
26
|
const svgString = await QRCode.toString(url, { type: 'svg', margin: 2 })
|
|
28
27
|
const html = `<!DOCTYPE html>
|
|
29
28
|
<html><head><meta charset="utf-8"><title>LINE QR Login</title>
|
|
@@ -35,18 +34,20 @@ svg{width:280px;height:280px}</style></head>
|
|
|
35
34
|
|
|
36
35
|
const htmlPath = join(tmpdir(), `line-qr-${Date.now()}.html`)
|
|
37
36
|
writeFileSync(htmlPath, html)
|
|
37
|
+
setTimeout(() => { try { unlinkSync(htmlPath) } catch {} }, 300_000).unref()
|
|
38
|
+
return htmlPath
|
|
39
|
+
}
|
|
38
40
|
|
|
41
|
+
function openInBrowser(filePath: string): void {
|
|
39
42
|
try {
|
|
40
43
|
if (process.platform === 'darwin') {
|
|
41
|
-
execSync(`open "${
|
|
44
|
+
execSync(`open "${filePath}"`, { stdio: 'ignore' })
|
|
42
45
|
} else if (process.platform === 'win32') {
|
|
43
|
-
execSync(`start "" "${
|
|
46
|
+
execSync(`start "" "${filePath}"`, { stdio: 'ignore' })
|
|
44
47
|
} else {
|
|
45
|
-
execSync(`xdg-open "${
|
|
48
|
+
execSync(`xdg-open "${filePath}"`, { stdio: 'ignore' })
|
|
46
49
|
}
|
|
47
50
|
} catch {}
|
|
48
|
-
|
|
49
|
-
setTimeout(() => { try { unlinkSync(htmlPath) } catch {} }, 30_000)
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
async function loginAction(options: {
|
|
@@ -94,21 +95,29 @@ async function loginAction(options: {
|
|
|
94
95
|
})
|
|
95
96
|
console.log(formatOutput(result, options.pretty))
|
|
96
97
|
} else {
|
|
97
|
-
if (!interactive) {
|
|
98
|
-
console.log(formatOutput(LINE_NEXT_ACTIONS.run_interactive, options.pretty))
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
98
|
const result = await client.loginWithQR({
|
|
103
99
|
device,
|
|
104
100
|
onQRUrl: async (url) => {
|
|
105
|
-
await
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
101
|
+
const htmlPath = await createQRHtmlFile(url).catch(() => null)
|
|
102
|
+
if (htmlPath) openInBrowser(htmlPath)
|
|
103
|
+
|
|
104
|
+
if (interactive) {
|
|
105
|
+
try {
|
|
106
|
+
const qrAscii = await QRCode.toString(url, { type: 'terminal', small: true })
|
|
107
|
+
info('\nScan this QR code with the LINE mobile app:\n')
|
|
108
|
+
info(qrAscii)
|
|
109
|
+
} catch {
|
|
110
|
+
info(`\nOpen the QR code in the browser window, or scan this URL:\n${url}\n`)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
console.log(formatOutput({
|
|
114
|
+
next_action: 'scan_qr',
|
|
115
|
+
qr_url: url,
|
|
116
|
+
qr_html_path: htmlPath,
|
|
117
|
+
message: htmlPath
|
|
118
|
+
? 'QR code opened in browser. Scan with LINE mobile app to complete login.'
|
|
119
|
+
: 'QR code generated. Open qr_url to scan with LINE mobile app.',
|
|
120
|
+
}, options.pretty))
|
|
112
121
|
}
|
|
113
122
|
},
|
|
114
123
|
onPincode: (pin) => {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const originalConsoleLog = console.log
|
|
4
|
+
|
|
5
|
+
import { LineClient } from '../client'
|
|
6
|
+
import { chatCommand } from './chat'
|
|
7
|
+
|
|
8
|
+
let loginSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let getChatsSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let closeSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let consoleLogSpy: ReturnType<typeof mock>
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
loginSpy = spyOn(LineClient.prototype, 'login').mockImplementation(async function (this: LineClient) {
|
|
15
|
+
return this
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
getChatsSpy = spyOn(LineClient.prototype, 'getChats').mockResolvedValue([
|
|
19
|
+
{
|
|
20
|
+
chat_id: 'c111',
|
|
21
|
+
type: 'user',
|
|
22
|
+
display_name: 'Alice',
|
|
23
|
+
member_count: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
chat_id: 'c222',
|
|
27
|
+
type: 'group',
|
|
28
|
+
display_name: 'Team Chat',
|
|
29
|
+
member_count: 10,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
chat_id: 'c333',
|
|
33
|
+
type: 'room',
|
|
34
|
+
display_name: 'Project Room',
|
|
35
|
+
member_count: 5,
|
|
36
|
+
},
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
closeSpy = spyOn(LineClient.prototype, 'close').mockImplementation(() => {})
|
|
40
|
+
consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
loginSpy?.mockRestore()
|
|
45
|
+
getChatsSpy?.mockRestore()
|
|
46
|
+
closeSpy?.mockRestore()
|
|
47
|
+
console.log = originalConsoleLog
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('list: fetches and outputs chats', async () => {
|
|
51
|
+
// when
|
|
52
|
+
await chatCommand.parseAsync(['node', 'chat', 'list'])
|
|
53
|
+
|
|
54
|
+
// then
|
|
55
|
+
expect(loginSpy).toHaveBeenCalledTimes(1)
|
|
56
|
+
expect(getChatsSpy).toHaveBeenCalledWith({ limit: 50 })
|
|
57
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
58
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
59
|
+
expect(output).toHaveLength(3)
|
|
60
|
+
expect(output[0].chat_id).toBe('c111')
|
|
61
|
+
expect(output[1].chat_id).toBe('c222')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('list: uses default limit of 50 when no limit provided', async () => {
|
|
65
|
+
// when
|
|
66
|
+
await chatCommand.parseAsync(['node', 'chat', 'list'])
|
|
67
|
+
|
|
68
|
+
// then
|
|
69
|
+
expect(getChatsSpy).toHaveBeenCalledWith({ limit: 50 })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('list: uses custom limit when --limit option provided', async () => {
|
|
73
|
+
// when
|
|
74
|
+
await chatCommand.parseAsync(['node', 'chat', 'list', '--limit', '10'])
|
|
75
|
+
|
|
76
|
+
// then
|
|
77
|
+
expect(getChatsSpy).toHaveBeenCalledWith({ limit: 10 })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('list: closes client after fetching chats', async () => {
|
|
81
|
+
// when
|
|
82
|
+
await chatCommand.parseAsync(['node', 'chat', 'list'])
|
|
83
|
+
|
|
84
|
+
// then
|
|
85
|
+
expect(closeSpy).toHaveBeenCalledTimes(1)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('list: outputs chat metadata', async () => {
|
|
89
|
+
// when
|
|
90
|
+
await chatCommand.parseAsync(['node', 'chat', 'list'])
|
|
91
|
+
|
|
92
|
+
// then
|
|
93
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
94
|
+
const chat = output[0]
|
|
95
|
+
expect(chat.chat_id).toBeDefined()
|
|
96
|
+
expect(chat.type).toBeDefined()
|
|
97
|
+
expect(chat.display_name).toBeDefined()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('list: includes different chat types', async () => {
|
|
101
|
+
// when
|
|
102
|
+
await chatCommand.parseAsync(['node', 'chat', 'list'])
|
|
103
|
+
|
|
104
|
+
// then
|
|
105
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
106
|
+
const types = output.map((c: { type: string }) => c.type)
|
|
107
|
+
expect(types).toContain('user')
|
|
108
|
+
expect(types).toContain('group')
|
|
109
|
+
expect(types).toContain('room')
|
|
110
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const originalConsoleLog = console.log
|
|
4
|
+
|
|
5
|
+
import { LineClient } from '../client'
|
|
6
|
+
import { friendCommand } from './friend'
|
|
7
|
+
|
|
8
|
+
let loginSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let getFriendsSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let closeSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let consoleLogSpy: ReturnType<typeof mock>
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
loginSpy = spyOn(LineClient.prototype, 'login').mockImplementation(async function (this: LineClient) {
|
|
15
|
+
return this
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
getFriendsSpy = spyOn(LineClient.prototype, 'getFriends').mockResolvedValue([
|
|
19
|
+
{
|
|
20
|
+
mid: 'u111',
|
|
21
|
+
display_name: 'Alice',
|
|
22
|
+
picture_url: 'https://example.com/alice.jpg',
|
|
23
|
+
status_message: 'Hey!',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
mid: 'u222',
|
|
27
|
+
display_name: 'Bob',
|
|
28
|
+
picture_url: 'https://example.com/bob.jpg',
|
|
29
|
+
status_message: 'Hello',
|
|
30
|
+
},
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
closeSpy = spyOn(LineClient.prototype, 'close').mockImplementation(() => {})
|
|
34
|
+
consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
loginSpy?.mockRestore()
|
|
39
|
+
getFriendsSpy?.mockRestore()
|
|
40
|
+
closeSpy?.mockRestore()
|
|
41
|
+
console.log = originalConsoleLog
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('list: fetches and outputs friends', async () => {
|
|
45
|
+
// when
|
|
46
|
+
await friendCommand.parseAsync(['node', 'friend', 'list'])
|
|
47
|
+
|
|
48
|
+
// then
|
|
49
|
+
expect(loginSpy).toHaveBeenCalledTimes(1)
|
|
50
|
+
expect(getFriendsSpy).toHaveBeenCalledTimes(1)
|
|
51
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
52
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
53
|
+
expect(output).toHaveLength(2)
|
|
54
|
+
expect(output[0].mid).toBe('u111')
|
|
55
|
+
expect(output[0].display_name).toBe('Alice')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('list: outputs friends with metadata', async () => {
|
|
59
|
+
// when
|
|
60
|
+
await friendCommand.parseAsync(['node', 'friend', 'list'])
|
|
61
|
+
|
|
62
|
+
// then
|
|
63
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
64
|
+
const friend = output[0]
|
|
65
|
+
expect(friend.mid).toBeDefined()
|
|
66
|
+
expect(friend.display_name).toBeDefined()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('list: closes client after fetching friends', async () => {
|
|
70
|
+
// when
|
|
71
|
+
await friendCommand.parseAsync(['node', 'friend', 'list'])
|
|
72
|
+
|
|
73
|
+
// then
|
|
74
|
+
expect(closeSpy).toHaveBeenCalledTimes(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('list: outputs empty array when no friends', async () => {
|
|
78
|
+
// given
|
|
79
|
+
getFriendsSpy.mockResolvedValue([])
|
|
80
|
+
|
|
81
|
+
// when
|
|
82
|
+
await friendCommand.parseAsync(['node', 'friend', 'list'])
|
|
83
|
+
|
|
84
|
+
// then
|
|
85
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
86
|
+
expect(output).toHaveLength(0)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('list: includes all friend fields', async () => {
|
|
90
|
+
// when
|
|
91
|
+
await friendCommand.parseAsync(['node', 'friend', 'list'])
|
|
92
|
+
|
|
93
|
+
// then
|
|
94
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
95
|
+
expect(output[0].picture_url).toBe('https://example.com/alice.jpg')
|
|
96
|
+
expect(output[0].status_message).toBe('Hey!')
|
|
97
|
+
expect(output[1].display_name).toBe('Bob')
|
|
98
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
const originalConsoleLog = console.log
|
|
4
|
+
|
|
5
|
+
import { LineClient } from '../client'
|
|
6
|
+
import { messageCommand } from './message'
|
|
7
|
+
|
|
8
|
+
let loginSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let getMessagesSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let sendMessageSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let closeSpy: ReturnType<typeof spyOn>
|
|
12
|
+
let consoleLogSpy: ReturnType<typeof mock>
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
loginSpy = spyOn(LineClient.prototype, 'login').mockImplementation(async function (this: LineClient) {
|
|
16
|
+
return this
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
getMessagesSpy = spyOn(LineClient.prototype, 'getMessages').mockResolvedValue([
|
|
20
|
+
{
|
|
21
|
+
message_id: 'msg-1',
|
|
22
|
+
chat_id: 'chat-1',
|
|
23
|
+
author_id: 'u123',
|
|
24
|
+
text: 'Hello world',
|
|
25
|
+
content_type: 'NONE',
|
|
26
|
+
sent_at: '2024-01-01T10:00:00Z',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
message_id: 'msg-2',
|
|
30
|
+
chat_id: 'chat-1',
|
|
31
|
+
author_id: 'u456',
|
|
32
|
+
text: 'Hi there',
|
|
33
|
+
content_type: 'NONE',
|
|
34
|
+
sent_at: '2024-01-01T09:00:00Z',
|
|
35
|
+
},
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
sendMessageSpy = spyOn(LineClient.prototype, 'sendMessage').mockResolvedValue({
|
|
39
|
+
success: true,
|
|
40
|
+
chat_id: 'chat-1',
|
|
41
|
+
message_id: 'msg-new',
|
|
42
|
+
sent_at: '2024-01-01T11:00:00Z',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
closeSpy = spyOn(LineClient.prototype, 'close').mockImplementation(() => {})
|
|
46
|
+
consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
loginSpy?.mockRestore()
|
|
51
|
+
getMessagesSpy?.mockRestore()
|
|
52
|
+
sendMessageSpy?.mockRestore()
|
|
53
|
+
closeSpy?.mockRestore()
|
|
54
|
+
console.log = originalConsoleLog
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('list: fetches and outputs messages for a chat', async () => {
|
|
58
|
+
// when
|
|
59
|
+
await messageCommand.parseAsync(['node', 'message', 'list', 'chat-1'])
|
|
60
|
+
|
|
61
|
+
// then
|
|
62
|
+
expect(loginSpy).toHaveBeenCalledTimes(1)
|
|
63
|
+
expect(getMessagesSpy).toHaveBeenCalledWith('chat-1', { count: 20 })
|
|
64
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
65
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
66
|
+
expect(output).toHaveLength(2)
|
|
67
|
+
expect(output[0].message_id).toBe('msg-1')
|
|
68
|
+
expect(output[0].text).toBe('Hello world')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('list: uses custom count when --count option provided', async () => {
|
|
72
|
+
// when
|
|
73
|
+
await messageCommand.parseAsync(['node', 'message', 'list', 'chat-1', '--count', '5'])
|
|
74
|
+
|
|
75
|
+
// then
|
|
76
|
+
expect(getMessagesSpy).toHaveBeenCalledWith('chat-1', { count: 5 })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('list: closes client after fetching messages', async () => {
|
|
80
|
+
// when
|
|
81
|
+
await messageCommand.parseAsync(['node', 'message', 'list', 'chat-1'])
|
|
82
|
+
|
|
83
|
+
// then
|
|
84
|
+
expect(closeSpy).toHaveBeenCalledTimes(1)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('send: sends message and outputs result', async () => {
|
|
88
|
+
// when
|
|
89
|
+
await messageCommand.parseAsync(['node', 'message', 'send', 'chat-1', 'Hello!'])
|
|
90
|
+
|
|
91
|
+
// then
|
|
92
|
+
expect(loginSpy).toHaveBeenCalledTimes(1)
|
|
93
|
+
expect(sendMessageSpy).toHaveBeenCalledWith('chat-1', 'Hello!')
|
|
94
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
95
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
96
|
+
expect(output.success).toBe(true)
|
|
97
|
+
expect(output.message_id).toBe('msg-new')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('send: closes client after sending message', async () => {
|
|
101
|
+
// when
|
|
102
|
+
await messageCommand.parseAsync(['node', 'message', 'send', 'chat-1', 'Hello!'])
|
|
103
|
+
|
|
104
|
+
// then
|
|
105
|
+
expect(closeSpy).toHaveBeenCalledTimes(1)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('list: outputs messages with metadata', async () => {
|
|
109
|
+
// when
|
|
110
|
+
await messageCommand.parseAsync(['node', 'message', 'list', 'chat-1'])
|
|
111
|
+
|
|
112
|
+
// then
|
|
113
|
+
const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
|
|
114
|
+
const msg = output[0]
|
|
115
|
+
expect(msg.message_id).toBeDefined()
|
|
116
|
+
expect(msg.chat_id).toBeDefined()
|
|
117
|
+
expect(msg.author_id).toBeDefined()
|
|
118
|
+
expect(msg.sent_at).toBeDefined()
|
|
119
|
+
})
|