@lizzythelizard/whatsapp-mcp 0.1.3 → 0.1.5
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/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/index.js +32 -9
- package/dist/index.js.map +1 -1
- package/dist/store.d.ts +23 -9
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +16 -10
- package/dist/store.js.map +1 -1
- package/dist/sync.d.ts +6 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +61 -2
- package/dist/sync.js.map +1 -1
- package/dist/tools.d.ts +4 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +69 -60
- package/dist/tools.js.map +1 -1
- package/package.json +59 -45
- package/.github/dependabot.yml +0 -20
- package/.github/workflows/auto-merge.yml +0 -19
- package/.github/workflows/ci.yml +0 -75
- package/.vscode/extensions.json +0 -7
- package/.vscode/settings.json +0 -12
- package/AGENTS.md +0 -42
- package/Dockerfile +0 -25
- package/dist/syncManager.d.ts +0 -29
- package/dist/syncManager.d.ts.map +0 -1
- package/dist/syncManager.js +0 -79
- package/dist/syncManager.js.map +0 -1
- package/eslint.config.mjs +0 -32
- package/src/auth.test.ts +0 -138
- package/src/auth.ts +0 -58
- package/src/index.ts +0 -45
- package/src/store.test.ts +0 -353
- package/src/store.ts +0 -182
- package/src/sync.test.ts +0 -304
- package/src/sync.ts +0 -170
- package/src/tools.test.ts +0 -254
- package/src/tools.ts +0 -132
- package/tsconfig.json +0 -19
- package/tsconfig.test.json +0 -7
- package/vitest.config.ts +0 -7
package/src/index.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
4
|
-
import pkg from '../package.json' with { type: 'json' }
|
|
5
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
6
|
-
import { createStore, DataStore } from './store.js'
|
|
7
|
-
import { createHandler } from './sync.js'
|
|
8
|
-
import { promises as fsp } from 'fs'
|
|
9
|
-
import { registerWhatsAppTools } from './tools.js'
|
|
10
|
-
|
|
11
|
-
const dataDir = process.env.DATA_DIR ?? './data'
|
|
12
|
-
|
|
13
|
-
async function readDataFromFile(): Promise<DataStore | undefined> {
|
|
14
|
-
const canAccess = await fsp.access(dataDir).then(() => true).catch(() => false)
|
|
15
|
-
if (!canAccess) return undefined
|
|
16
|
-
const chats = await fsp.readFile(`${dataDir}/chats.json`, 'utf-8')
|
|
17
|
-
const messages = await fsp.readFile(`${dataDir}/messages.json`, 'utf-8')
|
|
18
|
-
const contacts = await fsp.readFile(`${dataDir}/contacts.json`, 'utf-8')
|
|
19
|
-
const auth = await fsp.readFile(`${dataDir}/auth.json`, 'utf-8')
|
|
20
|
-
return { chats, messages, contacts, auth }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function writeDataToFile(data: DataStore): Promise<void> {
|
|
24
|
-
await fsp.mkdir(dataDir, { recursive: true })
|
|
25
|
-
await fsp.writeFile(`${dataDir}/chats.json`, data.chats, 'utf-8')
|
|
26
|
-
await fsp.writeFile(`${dataDir}/messages.json`, data.messages, 'utf-8')
|
|
27
|
-
await fsp.writeFile(`${dataDir}/contacts.json`, data.contacts, 'utf-8')
|
|
28
|
-
await fsp.writeFile(`${dataDir}/auth.json`, data.auth, 'utf-8')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function main() {
|
|
32
|
-
const server = new McpServer({ name: pkg.name, version: pkg.version })
|
|
33
|
-
const transport = new StdioServerTransport()
|
|
34
|
-
const inputData = await readDataFromFile()
|
|
35
|
-
const whatsappStore = createStore(writeDataToFile, inputData)
|
|
36
|
-
const whatsappSync = createHandler(whatsappStore)
|
|
37
|
-
registerWhatsAppTools(server, whatsappStore, whatsappSync)
|
|
38
|
-
await server.connect(transport)
|
|
39
|
-
console.info('whatsapp-mcp server running on stdio')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
main().catch((error: unknown) => {
|
|
43
|
-
console.error('Server error:', error)
|
|
44
|
-
process.exit(1)
|
|
45
|
-
})
|
package/src/store.test.ts
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { createStore, Emitter } from './store.js'
|
|
3
|
-
import { createExportableAuth, ExportableAuthState } from './auth.js'
|
|
4
|
-
import type { BaileysEventMap } from '@whiskeysockets/baileys'
|
|
5
|
-
|
|
6
|
-
type EventHandler = (events: Partial<BaileysEventMap>) => void | Promise<void>
|
|
7
|
-
|
|
8
|
-
function createMockEmitter(): (Emitter & { emit: (events: Partial<BaileysEventMap>) => void }) {
|
|
9
|
-
let handler: EventHandler | undefined
|
|
10
|
-
return {
|
|
11
|
-
process: vi.fn((h: EventHandler) => { handler = h }),
|
|
12
|
-
emit: (events: Partial<BaileysEventMap>) => { void handler?.(events) },
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('createStore', () => {
|
|
17
|
-
it('starts with empty chats, contacts, messages', () => {
|
|
18
|
-
const store = createStore()
|
|
19
|
-
expect(store.getChats()).toEqual([])
|
|
20
|
-
expect(store.getContacts()).toEqual([])
|
|
21
|
-
expect(store.getMessages()).toEqual([])
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('loads initial data from DataStore', () => {
|
|
25
|
-
const store = createStore(undefined, {
|
|
26
|
-
chats: JSON.stringify([['c1', { id: 'c1', name: 'Chat' }]]),
|
|
27
|
-
contacts: JSON.stringify([['c1', { id: 'c1', name: 'Contact' }]]),
|
|
28
|
-
messages: JSON.stringify([['m1', { key: { id: 'm1', remoteJid: 'c1' }, message: { conversation: 'hi' } }]]),
|
|
29
|
-
auth: '',
|
|
30
|
-
})
|
|
31
|
-
expect(store.getChats()).toHaveLength(1)
|
|
32
|
-
expect(store.getChat('c1')?.name).toBe('Chat')
|
|
33
|
-
expect(store.getContacts()).toHaveLength(1)
|
|
34
|
-
expect(store.getContact('c1')?.name).toBe('Contact')
|
|
35
|
-
expect(store.getMessages()).toHaveLength(1)
|
|
36
|
-
expect(store.getMessage('m1')?.message?.conversation).toBe('hi')
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('loads auth from initial data', () => {
|
|
40
|
-
const auth = createExportableAuth()
|
|
41
|
-
const store = createStore(undefined, {
|
|
42
|
-
chats: '', contacts: '', messages: '', auth: auth.toAuthState(),
|
|
43
|
-
})
|
|
44
|
-
expect(store.getAuth().creds.registrationId).toBe(auth.creds.registrationId)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
describe('event processing', () => {
|
|
49
|
-
it('bind registers the process handler with ev.process', () => {
|
|
50
|
-
const store = createStore()
|
|
51
|
-
const ev = createMockEmitter()
|
|
52
|
-
store.bind(ev)
|
|
53
|
-
expect(ev.process).toHaveBeenCalledTimes(1)
|
|
54
|
-
expect(ev.process).toHaveBeenCalledWith(expect.any(Function))
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('processes messaging-history.set', () => {
|
|
58
|
-
const store = createStore()
|
|
59
|
-
const ev = createMockEmitter()
|
|
60
|
-
store.bind(ev)
|
|
61
|
-
ev.emit({
|
|
62
|
-
'messaging-history.set': {
|
|
63
|
-
chats: [{ id: 'c1', name: 'Old Chat' }],
|
|
64
|
-
contacts: [{ id: 'c1', name: 'Old Contact' }],
|
|
65
|
-
messages: [{ key: { id: 'm1' }, message: { conversation: 'old' } }],
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
expect(store.getChats()).toHaveLength(1)
|
|
69
|
-
expect(store.getContacts()).toHaveLength(1)
|
|
70
|
-
expect(store.getMessages()).toHaveLength(1)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('processes chats.upsert — adds new chats', () => {
|
|
74
|
-
const store = createStore()
|
|
75
|
-
const ev = createMockEmitter()
|
|
76
|
-
store.bind(ev)
|
|
77
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'New Chat' }] })
|
|
78
|
-
expect(store.getChats()).toHaveLength(1)
|
|
79
|
-
expect(store.getChat('c1')?.name).toBe('New Chat')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('processes chats.upsert — merges into existing chats', () => {
|
|
83
|
-
const store = createStore()
|
|
84
|
-
const ev = createMockEmitter()
|
|
85
|
-
store.bind(ev)
|
|
86
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'First' }] })
|
|
87
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', archived: true }] })
|
|
88
|
-
expect(store.getChats()).toHaveLength(1)
|
|
89
|
-
expect(store.getChat('c1')?.name).toBe('First')
|
|
90
|
-
expect(store.getChat('c1')?.archived).toBe(true)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('processes chats.update — merges partial updates', () => {
|
|
94
|
-
const store = createStore()
|
|
95
|
-
const ev = createMockEmitter()
|
|
96
|
-
store.bind(ev)
|
|
97
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'Chat', archived: false }] })
|
|
98
|
-
ev.emit({ 'chats.update': [{ id: 'c1', archived: true }] })
|
|
99
|
-
expect(store.getChat('c1')?.archived).toBe(true)
|
|
100
|
-
expect(store.getChat('c1')?.name).toBe('Chat')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('processes chats.delete — removes chats by id', () => {
|
|
104
|
-
const store = createStore()
|
|
105
|
-
const ev = createMockEmitter()
|
|
106
|
-
store.bind(ev)
|
|
107
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'One' }, { id: 'c2', name: 'Two' }] })
|
|
108
|
-
ev.emit({ 'chats.delete': ['c1'] })
|
|
109
|
-
expect(store.getChats()).toHaveLength(1)
|
|
110
|
-
expect(store.getChat('c2')?.name).toBe('Two')
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('processes contacts.upsert — adds new contacts', () => {
|
|
114
|
-
const store = createStore()
|
|
115
|
-
const ev = createMockEmitter()
|
|
116
|
-
store.bind(ev)
|
|
117
|
-
ev.emit({ 'contacts.upsert': [{ id: 'c1', name: 'Contact' }] })
|
|
118
|
-
expect(store.getContacts()).toHaveLength(1)
|
|
119
|
-
expect(store.getContact('c1')?.name).toBe('Contact')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('processes contacts.update — merges partial updates', () => {
|
|
123
|
-
const store = createStore()
|
|
124
|
-
const ev = createMockEmitter()
|
|
125
|
-
store.bind(ev)
|
|
126
|
-
ev.emit({ 'contacts.upsert': [{ id: 'c1', name: 'Old', status: 'away' }] })
|
|
127
|
-
ev.emit({ 'contacts.update': [{ id: 'c1', status: 'available' }] })
|
|
128
|
-
expect(store.getContact('c1')?.status).toBe('available')
|
|
129
|
-
expect(store.getContact('c1')?.name).toBe('Old')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('processes messages.upsert — adds messages from .messages sub-property', () => {
|
|
133
|
-
const store = createStore()
|
|
134
|
-
const ev = createMockEmitter()
|
|
135
|
-
store.bind(ev)
|
|
136
|
-
ev.emit({
|
|
137
|
-
'messages.upsert': {
|
|
138
|
-
messages: [{ key: { id: 'm1' }, message: { conversation: 'hello' } }],
|
|
139
|
-
type: 'notify',
|
|
140
|
-
},
|
|
141
|
-
})
|
|
142
|
-
expect(store.getMessages()).toHaveLength(1)
|
|
143
|
-
expect(store.getMessage('m1')?.message?.conversation).toBe('hello')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('processes messages.update — merges the update wrapper (existing behavior)', () => {
|
|
147
|
-
const store = createStore()
|
|
148
|
-
const ev = createMockEmitter()
|
|
149
|
-
store.bind(ev)
|
|
150
|
-
ev.emit({
|
|
151
|
-
'messages.upsert': {
|
|
152
|
-
messages: [{ key: { id: 'm1' }, message: { conversation: 'hi' } }],
|
|
153
|
-
type: 'notify',
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
ev.emit({
|
|
157
|
-
'messages.update': [{ key: { id: 'm1' }, update: { message: { conversation: 'updated' } } }],
|
|
158
|
-
})
|
|
159
|
-
const msg = store.getMessage('m1') as { message?: { conversation?: string }, update?: { message?: { conversation?: string } } }
|
|
160
|
-
expect(msg.message?.conversation).toBe('hi')
|
|
161
|
-
expect(msg.update?.message?.conversation).toBe('updated')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('processes messages.delete with keys array', () => {
|
|
165
|
-
const store = createStore()
|
|
166
|
-
const ev = createMockEmitter()
|
|
167
|
-
store.bind(ev)
|
|
168
|
-
ev.emit({
|
|
169
|
-
'messages.upsert': {
|
|
170
|
-
messages: [
|
|
171
|
-
{ key: { id: 'm1' }, message: { conversation: 'a' } },
|
|
172
|
-
{ key: { id: 'm2' }, message: { conversation: 'b' } },
|
|
173
|
-
],
|
|
174
|
-
type: 'notify',
|
|
175
|
-
},
|
|
176
|
-
})
|
|
177
|
-
ev.emit({ 'messages.delete': { keys: [{ id: 'm1' }] } })
|
|
178
|
-
expect(store.getMessages()).toHaveLength(1)
|
|
179
|
-
expect(store.getMessage('m2')).toBeDefined()
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('processes messages.delete with jid', () => {
|
|
183
|
-
const store = createStore()
|
|
184
|
-
const ev = createMockEmitter()
|
|
185
|
-
store.bind(ev)
|
|
186
|
-
ev.emit({
|
|
187
|
-
'messages.upsert': {
|
|
188
|
-
messages: [
|
|
189
|
-
{ key: { id: 'm1', remoteJid: 'c1' } },
|
|
190
|
-
{ key: { id: 'm2', remoteJid: 'c2' } },
|
|
191
|
-
],
|
|
192
|
-
type: 'notify',
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
ev.emit({ 'messages.delete': { jid: 'c1', all: true } })
|
|
196
|
-
expect(store.getMessages()).toHaveLength(1)
|
|
197
|
-
expect(store.getMessage('m2')).toBeDefined()
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('skips chat without id in mergeChats', () => {
|
|
201
|
-
const store = createStore()
|
|
202
|
-
const ev = createMockEmitter()
|
|
203
|
-
store.bind(ev)
|
|
204
|
-
ev.emit({ 'chats.upsert': [{ name: 'NoId' }] })
|
|
205
|
-
expect(store.getChats()).toHaveLength(0)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('skips message without key.id in mergeMessages', () => {
|
|
209
|
-
const store = createStore()
|
|
210
|
-
const ev = createMockEmitter()
|
|
211
|
-
store.bind(ev)
|
|
212
|
-
ev.emit({
|
|
213
|
-
'messages.upsert': {
|
|
214
|
-
messages: [{ key: { id: null }, message: { conversation: 'no key id' } }],
|
|
215
|
-
type: 'notify',
|
|
216
|
-
},
|
|
217
|
-
})
|
|
218
|
-
expect(store.getMessages()).toHaveLength(0)
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
describe('save callback', () => {
|
|
223
|
-
beforeEach(() => { vi.useFakeTimers() })
|
|
224
|
-
afterEach(() => { vi.useRealTimers() })
|
|
225
|
-
|
|
226
|
-
it('calls saveCb with serialized data after debounce', async () => {
|
|
227
|
-
const saveCb = vi.fn().mockResolvedValue(undefined)
|
|
228
|
-
const store = createStore(saveCb)
|
|
229
|
-
const ev = createMockEmitter()
|
|
230
|
-
store.bind(ev)
|
|
231
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'Chat' }] })
|
|
232
|
-
vi.advanceTimersByTime(1000)
|
|
233
|
-
await vi.waitFor(() => { expect(saveCb).toHaveBeenCalledTimes(1) })
|
|
234
|
-
const expected = expect.objectContaining({
|
|
235
|
-
chats: expect.any(String) as string,
|
|
236
|
-
contacts: expect.any(String) as string,
|
|
237
|
-
messages: expect.any(String) as string,
|
|
238
|
-
auth: expect.any(String) as string,
|
|
239
|
-
}) as Record<string, unknown>
|
|
240
|
-
expect(saveCb).toHaveBeenCalledWith(expected)
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('debounces multiple events into one save', async () => {
|
|
244
|
-
const saveCb = vi.fn().mockResolvedValue(undefined)
|
|
245
|
-
const store = createStore(saveCb)
|
|
246
|
-
const ev = createMockEmitter()
|
|
247
|
-
store.bind(ev)
|
|
248
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1', name: 'A' }] })
|
|
249
|
-
vi.advanceTimersByTime(500)
|
|
250
|
-
ev.emit({ 'chats.upsert': [{ id: 'c2', name: 'B' }] })
|
|
251
|
-
vi.advanceTimersByTime(1000)
|
|
252
|
-
await vi.waitFor(() => { expect(saveCb).toHaveBeenCalledTimes(1) })
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
it('error in saveCb is caught and does not throw', async () => {
|
|
256
|
-
const saveCb = vi.fn().mockRejectedValue(new Error('save failed'))
|
|
257
|
-
const store = createStore(saveCb)
|
|
258
|
-
const ev = createMockEmitter()
|
|
259
|
-
store.bind(ev)
|
|
260
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1' }] })
|
|
261
|
-
vi.advanceTimersByTime(1000)
|
|
262
|
-
await vi.waitFor(() => { expect(saveCb).toHaveBeenCalled() })
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
describe('getters', () => {
|
|
267
|
-
it('getChats returns all chats', () => {
|
|
268
|
-
const store = createStore()
|
|
269
|
-
const ev = createMockEmitter()
|
|
270
|
-
store.bind(ev)
|
|
271
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1' }, { id: 'c2' }] })
|
|
272
|
-
expect(store.getChats()).toHaveLength(2)
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
it('getChat returns undefined for missing id', () => {
|
|
276
|
-
expect(createStore().getChat('nonexistent')).toBeUndefined()
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('getContacts returns all contacts', () => {
|
|
280
|
-
const store = createStore()
|
|
281
|
-
const ev = createMockEmitter()
|
|
282
|
-
store.bind(ev)
|
|
283
|
-
ev.emit({ 'contacts.upsert': [{ id: 'c1' }, { id: 'c2' }] })
|
|
284
|
-
expect(store.getContacts()).toHaveLength(2)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('getContact returns undefined for missing id', () => {
|
|
288
|
-
expect(createStore().getContact('nonexistent')).toBeUndefined()
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
it('getMessages returns all messages', () => {
|
|
292
|
-
const store = createStore()
|
|
293
|
-
const ev = createMockEmitter()
|
|
294
|
-
store.bind(ev)
|
|
295
|
-
ev.emit({
|
|
296
|
-
'messages.upsert': {
|
|
297
|
-
messages: [{ key: { id: 'm1' } }, { key: { id: 'm2' } }],
|
|
298
|
-
type: 'notify',
|
|
299
|
-
},
|
|
300
|
-
})
|
|
301
|
-
expect(store.getMessages()).toHaveLength(2)
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it('getMessage returns undefined for missing id', () => {
|
|
305
|
-
expect(createStore().getMessage('nonexistent')).toBeUndefined()
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
describe('reset', () => {
|
|
310
|
-
it('clears all chats, contacts, messages', () => {
|
|
311
|
-
const store = createStore()
|
|
312
|
-
const ev = createMockEmitter()
|
|
313
|
-
store.bind(ev)
|
|
314
|
-
ev.emit({
|
|
315
|
-
'messaging-history.set': {
|
|
316
|
-
chats: [{ id: 'c1' }],
|
|
317
|
-
contacts: [{ id: 'c1' }],
|
|
318
|
-
messages: [{ key: { id: 'm1' } }],
|
|
319
|
-
},
|
|
320
|
-
})
|
|
321
|
-
store.reset()
|
|
322
|
-
expect(store.getChats()).toHaveLength(0)
|
|
323
|
-
expect(store.getContacts()).toHaveLength(0)
|
|
324
|
-
expect(store.getMessages()).toHaveLength(0)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it('resets auth to fresh state', () => {
|
|
328
|
-
const store = createStore()
|
|
329
|
-
store.reset()
|
|
330
|
-
expect(store.getAuth().creds).toBeDefined()
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('accepts new data after reset', () => {
|
|
334
|
-
const store = createStore()
|
|
335
|
-
const ev = createMockEmitter()
|
|
336
|
-
store.bind(ev)
|
|
337
|
-
ev.emit({ 'chats.upsert': [{ id: 'c1' }] })
|
|
338
|
-
store.reset()
|
|
339
|
-
ev.emit({ 'chats.upsert': [{ id: 'c2' }] })
|
|
340
|
-
expect(store.getChats()).toHaveLength(1)
|
|
341
|
-
expect(store.getChat('c2')).toBeDefined()
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
describe('getAuth', () => {
|
|
346
|
-
it('returns the internal auth state', () => {
|
|
347
|
-
const store = createStore()
|
|
348
|
-
const auth = store.getAuth() as ExportableAuthState
|
|
349
|
-
expect(auth.creds).toBeDefined()
|
|
350
|
-
expect(auth.keys).toBeDefined()
|
|
351
|
-
expect(typeof auth.toAuthState).toBe('function')
|
|
352
|
-
})
|
|
353
|
-
})
|
package/src/store.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { AuthenticationState, BaileysEventMap, BufferJSON, Chat, Contact, WAMessage, WAMessageKey } from '@whiskeysockets/baileys'
|
|
2
|
-
import { createExportableAuth } from './auth.js'
|
|
3
|
-
|
|
4
|
-
export interface ChatWithId extends Chat {
|
|
5
|
-
id: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface ContactWithId extends Contact {
|
|
9
|
-
id: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface MessageWithId extends WAMessage {
|
|
13
|
-
key: WAMessageKey & { id: string }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface Emitter {
|
|
17
|
-
process: (processor: (e: Partial<BaileysEventMap>) => void) => void
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface WhatsAppStore {
|
|
21
|
-
bind: (ev: Emitter) => void
|
|
22
|
-
getChats: () => ChatWithId[]
|
|
23
|
-
getChat: (id: string) => ChatWithId | undefined
|
|
24
|
-
getContacts: () => ContactWithId[]
|
|
25
|
-
getContact: (id: string) => ContactWithId | undefined
|
|
26
|
-
getMessages: () => MessageWithId[]
|
|
27
|
-
getMessage: (id: string) => MessageWithId | undefined
|
|
28
|
-
reset: () => void
|
|
29
|
-
getAuth: () => AuthenticationState
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface DataStore {
|
|
33
|
-
chats: string
|
|
34
|
-
contacts: string
|
|
35
|
-
messages: string
|
|
36
|
-
auth: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function fromString<T>(data: string | undefined): Map<string, T> {
|
|
40
|
-
if (!data) return new Map<string, T>()
|
|
41
|
-
const parsed = JSON.parse(data) as [string, T][]
|
|
42
|
-
return new Map<string, T>(parsed)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function toString<T>(map: Map<string, T>): string {
|
|
46
|
-
return JSON.stringify(map, BufferJSON.replacer)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createStore(saveCb?: (data: DataStore) => Promise<void>, initialData?: DataStore): WhatsAppStore {
|
|
50
|
-
// TODO: Load initial states from somewhere
|
|
51
|
-
const chats = fromString<ChatWithId>(initialData?.chats)
|
|
52
|
-
const contacts = fromString<ContactWithId>(initialData?.contacts)
|
|
53
|
-
const messages = fromString<MessageWithId>(initialData?.messages)
|
|
54
|
-
let auth = createExportableAuth(initialData?.auth)
|
|
55
|
-
let saveTimeout: NodeJS.Timeout | undefined = undefined
|
|
56
|
-
|
|
57
|
-
function mergeChats(newChats: Partial<Chat>[]) {
|
|
58
|
-
for (const c of newChats) {
|
|
59
|
-
if (!c.id) continue
|
|
60
|
-
const existing = chats.get(c.id) ?? {}
|
|
61
|
-
const merged = { ...existing, ...c } as ChatWithId
|
|
62
|
-
chats.set(c.id, merged)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function deleteChats(ids: string[]) {
|
|
67
|
-
for (const id of ids) {
|
|
68
|
-
chats.delete(id)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function mergeContacts(newContacts: Partial<Contact>[]) {
|
|
73
|
-
for (const c of newContacts) {
|
|
74
|
-
if (!c.id) continue
|
|
75
|
-
const existing = contacts.get(c.id) ?? {}
|
|
76
|
-
const merged = { ...existing, ...c } as ContactWithId
|
|
77
|
-
contacts.set(c.id, merged)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function mergeMessages(newMessages: Partial<WAMessage>[]) {
|
|
82
|
-
for (const m of newMessages) {
|
|
83
|
-
if (!m.key?.id) continue
|
|
84
|
-
const existing = messages.get(m.key.id) ?? {}
|
|
85
|
-
const merged = { ...existing, ...m } as MessageWithId
|
|
86
|
-
messages.set(m.key.id, merged)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function deleteMessages(keys: WAMessageKey[]) {
|
|
91
|
-
for (const key of keys) {
|
|
92
|
-
if (!key.id) continue
|
|
93
|
-
messages.delete(key.id)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function deleteMessage(jid: string) {
|
|
98
|
-
for (const [id, m] of messages.entries()) {
|
|
99
|
-
if (m.key.remoteJid === jid) {
|
|
100
|
-
messages.delete(id)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function save() {
|
|
106
|
-
if (!saveCb) return
|
|
107
|
-
const data: DataStore = {
|
|
108
|
-
chats: toString(chats),
|
|
109
|
-
contacts: toString(contacts),
|
|
110
|
-
messages: toString(messages),
|
|
111
|
-
auth: auth.toAuthState(),
|
|
112
|
-
}
|
|
113
|
-
void saveCb(data).catch((error: unknown) => {
|
|
114
|
-
console.error('Error saving WhatsApp store data:', error)
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function process(e: Partial<BaileysEventMap>) {
|
|
119
|
-
// Store data if no new events come in for 1 second
|
|
120
|
-
if (saveTimeout) clearTimeout(saveTimeout)
|
|
121
|
-
if (saveCb) saveTimeout = setTimeout(save, 1000)
|
|
122
|
-
for (const key of Object.keys(e) as (keyof BaileysEventMap)[]) {
|
|
123
|
-
try {
|
|
124
|
-
switch (key) {
|
|
125
|
-
case 'messaging-history.set':
|
|
126
|
-
mergeChats(e[key]?.chats ?? [])
|
|
127
|
-
mergeContacts(e[key]?.contacts ?? [])
|
|
128
|
-
mergeMessages(e[key]?.messages ?? [])
|
|
129
|
-
break
|
|
130
|
-
case 'chats.delete':
|
|
131
|
-
deleteChats(e[key] ?? [])
|
|
132
|
-
break
|
|
133
|
-
case 'chats.update':
|
|
134
|
-
case 'chats.upsert':
|
|
135
|
-
mergeChats(e[key] ?? [])
|
|
136
|
-
break
|
|
137
|
-
case 'contacts.update':
|
|
138
|
-
case 'contacts.upsert':
|
|
139
|
-
mergeContacts(e[key] ?? [])
|
|
140
|
-
break
|
|
141
|
-
case 'messages.delete':
|
|
142
|
-
if (!e[key]) break
|
|
143
|
-
if ('keys' in e[key])
|
|
144
|
-
deleteMessages(e[key].keys)
|
|
145
|
-
else
|
|
146
|
-
deleteMessage(e[key].jid)
|
|
147
|
-
break
|
|
148
|
-
case 'messages.update':
|
|
149
|
-
mergeMessages(e[key] ?? [])
|
|
150
|
-
break
|
|
151
|
-
case 'messages.upsert':
|
|
152
|
-
mergeMessages(e[key]?.messages ?? [])
|
|
153
|
-
break
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
handleError(error)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function handleError(error: unknown) {
|
|
163
|
-
console.error(`Error processing WhatsApp store event: ${error instanceof Error ? error.message : String(error)}`)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
bind: (ev) => { ev.process(process) },
|
|
168
|
-
getChats: () => Array.from(chats.values()),
|
|
169
|
-
getChat: id => chats.get(id),
|
|
170
|
-
getContacts: () => Array.from(contacts.values()),
|
|
171
|
-
getContact: id => contacts.get(id),
|
|
172
|
-
getMessages: () => Array.from(messages.values()),
|
|
173
|
-
getMessage: id => messages.get(id),
|
|
174
|
-
reset: () => {
|
|
175
|
-
chats.clear()
|
|
176
|
-
contacts.clear()
|
|
177
|
-
messages.clear()
|
|
178
|
-
auth = createExportableAuth(undefined)
|
|
179
|
-
},
|
|
180
|
-
getAuth: () => auth,
|
|
181
|
-
}
|
|
182
|
-
}
|