@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/sync.test.ts
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
|
|
3
|
-
const mockMakeWASocket = vi.hoisted(() => vi.fn())
|
|
4
|
-
|
|
5
|
-
vi.mock('@whiskeysockets/baileys', async (importOriginal) => {
|
|
6
|
-
const actual: object = await importOriginal()
|
|
7
|
-
return { ...actual, default: mockMakeWASocket, makeWASocket: mockMakeWASocket }
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
import { createHandler } from './sync.js'
|
|
11
|
-
import { createStore } from './store.js'
|
|
12
|
-
|
|
13
|
-
function createStoreMockEmitter() {
|
|
14
|
-
return { process: vi.fn() }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function newMockSocket() {
|
|
18
|
-
return {
|
|
19
|
-
ev: {
|
|
20
|
-
on: vi.fn(),
|
|
21
|
-
process: vi.fn(),
|
|
22
|
-
},
|
|
23
|
-
end: vi.fn().mockResolvedValue(undefined),
|
|
24
|
-
sendMessage: vi.fn().mockResolvedValue({ key: { id: 'mock', remoteJid: 'test' }, message: { conversation: 'ok' } }),
|
|
25
|
-
chatModify: vi.fn().mockResolvedValue(undefined),
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getConnectionUpdateHandler(socket: ReturnType<typeof newMockSocket>): ((update: Record<string, unknown>) => void) | undefined {
|
|
30
|
-
const call = (socket.ev.on.mock.calls as [string, (update: Record<string, unknown>) => void][])
|
|
31
|
-
.find(([name]) => name === 'connection.update')
|
|
32
|
-
return call?.[1]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function emitConnectionUpdate(update: Record<string, unknown>) {
|
|
36
|
-
const results = mockMakeWASocket.mock.results as { value: ReturnType<typeof newMockSocket>, type: string }[]
|
|
37
|
-
if (results.length === 0) return
|
|
38
|
-
const socket = results[results.length - 1].value
|
|
39
|
-
const handler = getConnectionUpdateHandler(socket)
|
|
40
|
-
if (handler) handler(update)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getLatestSocket(): ReturnType<typeof newMockSocket> {
|
|
44
|
-
const results = mockMakeWASocket.mock.results as { value: ReturnType<typeof newMockSocket>, type: string }[]
|
|
45
|
-
return results[results.length - 1].value
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
mockMakeWASocket.mockReset()
|
|
50
|
-
mockMakeWASocket.mockReturnValue(newMockSocket())
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
describe('createHandler', () => {
|
|
54
|
-
it('initial state is connecting', () => {
|
|
55
|
-
const handler = createHandler(createStore())
|
|
56
|
-
expect(handler.getStatus()).toEqual({ type: 'connecting' })
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('transitions to needAuth on QR', () => {
|
|
60
|
-
const handler = createHandler(createStore())
|
|
61
|
-
emitConnectionUpdate({ qr: 'base64qr===' })
|
|
62
|
-
expect(handler.getStatus()).toEqual({ type: 'needAuth', qr: 'base64qr===' })
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('transitions to ready on connection open', () => {
|
|
66
|
-
const handler = createHandler(createStore())
|
|
67
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
68
|
-
expect(handler.getStatus()).toEqual({ type: 'ready' })
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('transitions to closed on connection close without error', () => {
|
|
72
|
-
const handler = createHandler(createStore())
|
|
73
|
-
emitConnectionUpdate({ connection: 'close' })
|
|
74
|
-
expect(handler.getStatus()).toEqual({ type: 'closed' })
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('transitions to closed with error on connection close with error', () => {
|
|
78
|
-
const handler = createHandler(createStore())
|
|
79
|
-
const error = new Error('generic failure')
|
|
80
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
81
|
-
expect(handler.getStatus()).toEqual({ type: 'closed', error })
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('ignores intermediate connection updates', () => {
|
|
85
|
-
const handler = createHandler(createStore())
|
|
86
|
-
emitConnectionUpdate({ connection: 'connecting' })
|
|
87
|
-
expect(handler.getStatus()).toEqual({ type: 'connecting' })
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('error handling', () => {
|
|
92
|
-
it('handles 401 error by resetting store and restarting', () => {
|
|
93
|
-
const store = createStore()
|
|
94
|
-
const handler = createHandler(store)
|
|
95
|
-
const error = Object.assign(new Error('auth failed'), { output: { statusCode: 401 } })
|
|
96
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
97
|
-
expect(handler.getStatus()).toEqual({ type: 'connecting' })
|
|
98
|
-
expect(mockMakeWASocket).toHaveBeenCalledTimes(2)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('handles 515 error by resetting store and restarting with re-login', () => {
|
|
102
|
-
const store = createStore()
|
|
103
|
-
const handler = createHandler(store)
|
|
104
|
-
const error = Object.assign(new Error('reconnect needed'), { output: { statusCode: 515 } })
|
|
105
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
106
|
-
expect(handler.getStatus()).toEqual({ type: 'connecting' })
|
|
107
|
-
expect(mockMakeWASocket).toHaveBeenCalledTimes(2)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('non-401/515 error stays closed with error', () => {
|
|
111
|
-
const handler = createHandler(createStore())
|
|
112
|
-
const error = Object.assign(new Error('other error'), { output: { statusCode: 500 } })
|
|
113
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
114
|
-
expect(handler.getStatus()).toEqual({ type: 'closed', error })
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('plain Error without output does not trigger reconnection', () => {
|
|
118
|
-
const handler = createHandler(createStore())
|
|
119
|
-
const error = new Error('no output property')
|
|
120
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
121
|
-
expect(handler.getStatus()).toEqual({ type: 'closed', error })
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('non-Error value falls through to closed with the value as error', () => {
|
|
125
|
-
const handler = createHandler(createStore())
|
|
126
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error: 'string error' } })
|
|
127
|
-
expect(handler.getStatus().type).toBe('closed')
|
|
128
|
-
expect(handler.getStatus()).toHaveProperty('error', 'string error')
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('401 error resets store data', () => {
|
|
132
|
-
const store = createStore()
|
|
133
|
-
const ev = createStoreMockEmitter()
|
|
134
|
-
store.bind(ev)
|
|
135
|
-
createHandler(store)
|
|
136
|
-
ev.process.mockClear()
|
|
137
|
-
const error = Object.assign(new Error('auth'), { output: { statusCode: 401 } })
|
|
138
|
-
emitConnectionUpdate({ connection: 'close', lastDisconnect: { error } })
|
|
139
|
-
expect(store.getChats()).toHaveLength(0)
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
describe('getStatus', () => {
|
|
144
|
-
it('returns the current state', () => {
|
|
145
|
-
const store = createStore()
|
|
146
|
-
const handler = createHandler(store)
|
|
147
|
-
expect(handler.getStatus().type).toBe('connecting')
|
|
148
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
149
|
-
expect(handler.getStatus().type).toBe('ready')
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('sendMessage', () => {
|
|
154
|
-
it('throws when state is connecting', async () => {
|
|
155
|
-
const handler = createHandler(createStore())
|
|
156
|
-
await expect(handler.sendMessage('test@s.whatsapp.net', 'hello')).rejects.toThrow('Server still connecting')
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('throws when state is closed', async () => {
|
|
160
|
-
const handler = createHandler(createStore())
|
|
161
|
-
emitConnectionUpdate({ connection: 'close' })
|
|
162
|
-
await expect(handler.sendMessage('test@s.whatsapp.net', 'hello')).rejects.toThrow('Connection closed')
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('throws when state is needAuth', async () => {
|
|
166
|
-
const handler = createHandler(createStore())
|
|
167
|
-
emitConnectionUpdate({ qr: 'qr' })
|
|
168
|
-
await expect(handler.sendMessage('test@s.whatsapp.net', 'hello')).rejects.toThrow('Authentication needed')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('calls sock.sendMessage when ready', async () => {
|
|
172
|
-
const handler = createHandler(createStore())
|
|
173
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
174
|
-
const socket = getLatestSocket()
|
|
175
|
-
const result = await handler.sendMessage('test@s.whatsapp.net', 'Hello!')
|
|
176
|
-
expect(socket.sendMessage).toHaveBeenCalledWith('test@s.whatsapp.net', { text: 'Hello!' })
|
|
177
|
-
expect(result).toBeDefined()
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('throws when sock.sendMessage returns null', async () => {
|
|
181
|
-
const handler = createHandler(createStore())
|
|
182
|
-
getLatestSocket().sendMessage.mockResolvedValue(null)
|
|
183
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
184
|
-
await expect(handler.sendMessage('test@s.whatsapp.net', 'hi')).rejects.toThrow('Failed to send message')
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
describe('setArchived', () => {
|
|
189
|
-
it('throws when state is connecting', async () => {
|
|
190
|
-
const handler = createHandler(createStore())
|
|
191
|
-
await expect(handler.setArchived('test@s.whatsapp.net', true)).rejects.toThrow('Server still connecting')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('throws when chat is not found', async () => {
|
|
195
|
-
const handler = createHandler(createStore())
|
|
196
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
197
|
-
await expect(handler.setArchived('unknown@s.whatsapp.net', true)).rejects.toThrow('No chat found')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('calls chatModify with empty lastMessages when chat has no last message', async () => {
|
|
201
|
-
const store = createStore()
|
|
202
|
-
const ev = createStoreMockEmitter()
|
|
203
|
-
store.bind(ev)
|
|
204
|
-
const handler = createHandler(store)
|
|
205
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
206
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net' })
|
|
207
|
-
const socket = getLatestSocket()
|
|
208
|
-
await handler.setArchived('test@s.whatsapp.net', true)
|
|
209
|
-
expect(socket.chatModify).toHaveBeenCalledWith(
|
|
210
|
-
{ archive: true, lastMessages: [] },
|
|
211
|
-
'test@s.whatsapp.net',
|
|
212
|
-
)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('throws when last message has no key', async () => {
|
|
216
|
-
const store = createStore()
|
|
217
|
-
const ev = createStoreMockEmitter()
|
|
218
|
-
store.bind(ev)
|
|
219
|
-
const handler = createHandler(store)
|
|
220
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
221
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net', messages: [{ message: {} }] })
|
|
222
|
-
await expect(handler.setArchived('test@s.whatsapp.net', true)).rejects.toThrow('has no key')
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('calls chatModify with archive and last message when valid', async () => {
|
|
226
|
-
const store = createStore()
|
|
227
|
-
const ev = createStoreMockEmitter()
|
|
228
|
-
store.bind(ev)
|
|
229
|
-
const handler = createHandler(store)
|
|
230
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
231
|
-
const lastMsg = { key: { id: 'last', remoteJid: 'test@s.whatsapp.net' }, message: { conversation: 'bye' } }
|
|
232
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net', messages: [{ message: lastMsg }] })
|
|
233
|
-
const socket = getLatestSocket()
|
|
234
|
-
await handler.setArchived('test@s.whatsapp.net', true)
|
|
235
|
-
expect(socket.chatModify).toHaveBeenCalledWith(
|
|
236
|
-
{ archive: true, lastMessages: [lastMsg] },
|
|
237
|
-
'test@s.whatsapp.net',
|
|
238
|
-
)
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
describe('setRead', () => {
|
|
243
|
-
it('throws when state is connecting', async () => {
|
|
244
|
-
const handler = createHandler(createStore())
|
|
245
|
-
await expect(handler.setRead('test@s.whatsapp.net', true)).rejects.toThrow('Server still connecting')
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
it('throws when chat is not found', async () => {
|
|
249
|
-
const handler = createHandler(createStore())
|
|
250
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
251
|
-
await expect(handler.setRead('unknown@s.whatsapp.net', true)).rejects.toThrow('No chat found')
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('calls chatModify with empty lastMessages when chat has no last message', async () => {
|
|
255
|
-
const store = createStore()
|
|
256
|
-
const ev = createStoreMockEmitter()
|
|
257
|
-
store.bind(ev)
|
|
258
|
-
const handler = createHandler(store)
|
|
259
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
260
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net' })
|
|
261
|
-
const socket = getLatestSocket()
|
|
262
|
-
await handler.setRead('test@s.whatsapp.net', true)
|
|
263
|
-
expect(socket.chatModify).toHaveBeenCalledWith(
|
|
264
|
-
{ markRead: true, lastMessages: [] },
|
|
265
|
-
'test@s.whatsapp.net',
|
|
266
|
-
)
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('throws when last message has no key', async () => {
|
|
270
|
-
const store = createStore()
|
|
271
|
-
const ev = createStoreMockEmitter()
|
|
272
|
-
store.bind(ev)
|
|
273
|
-
const handler = createHandler(store)
|
|
274
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
275
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net', messages: [{ message: {} }] })
|
|
276
|
-
await expect(handler.setRead('test@s.whatsapp.net', true)).rejects.toThrow('has no key')
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('calls chatModify with markRead and last message when valid', async () => {
|
|
280
|
-
const store = createStore()
|
|
281
|
-
const ev = createStoreMockEmitter()
|
|
282
|
-
store.bind(ev)
|
|
283
|
-
const handler = createHandler(store)
|
|
284
|
-
emitConnectionUpdate({ connection: 'open' })
|
|
285
|
-
const lastMsg = { key: { id: 'last', remoteJid: 'test@s.whatsapp.net' }, message: { conversation: 'hi' } }
|
|
286
|
-
store.getChat = vi.fn().mockReturnValue({ id: 'test@s.whatsapp.net', messages: [{ message: lastMsg }] })
|
|
287
|
-
const socket = getLatestSocket()
|
|
288
|
-
await handler.setRead('test@s.whatsapp.net', true)
|
|
289
|
-
expect(socket.chatModify).toHaveBeenCalledWith(
|
|
290
|
-
{ markRead: true, lastMessages: [lastMsg] },
|
|
291
|
-
'test@s.whatsapp.net',
|
|
292
|
-
)
|
|
293
|
-
})
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
describe('close', () => {
|
|
297
|
-
it('sets state to closed and calls sock.end', () => {
|
|
298
|
-
const handler = createHandler(createStore())
|
|
299
|
-
const socket = getLatestSocket()
|
|
300
|
-
handler.close()
|
|
301
|
-
expect(socket.end).toHaveBeenCalled()
|
|
302
|
-
expect(handler.getStatus()).toEqual({ type: 'closed' })
|
|
303
|
-
})
|
|
304
|
-
})
|
package/src/sync.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import makeWASocket, { WABrowserDescription, ConnectionState, WAMessage, proto } from '@whiskeysockets/baileys'
|
|
2
|
-
import { createStore } from './store.js'
|
|
3
|
-
|
|
4
|
-
export type SyncStatus = { type: 'connecting' } | { type: 'needAuth', qr: string } | { type: 'ready' } | { type: 'closed', error?: Error }
|
|
5
|
-
|
|
6
|
-
export interface WhatsAppHandler {
|
|
7
|
-
close: () => void
|
|
8
|
-
getStatus: () => SyncStatus
|
|
9
|
-
sendMessage: (jid: string, text: string) => Promise<WAMessage>
|
|
10
|
-
setRead: (jid: string, read: boolean) => Promise<void>
|
|
11
|
-
setArchived: (jid: string, archived: boolean) => Promise<void>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function createHandler(store: ReturnType<typeof createStore>): WhatsAppHandler {
|
|
15
|
-
let state: SyncStatus = { type: 'connecting' }
|
|
16
|
-
let sock: ReturnType<typeof makeWASocket> | undefined
|
|
17
|
-
const browser = ['Gutschi.site', 'Desktop', '1.0.0'] as WABrowserDescription
|
|
18
|
-
|
|
19
|
-
function onError(error: unknown): void {
|
|
20
|
-
const arg = error instanceof Error ? error : new Error(String(error))
|
|
21
|
-
console.error(`Closing WhatsApp sync due to error ${arg}`)
|
|
22
|
-
close(arg)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function close(error?: Error): void {
|
|
26
|
-
if (sock) {
|
|
27
|
-
const sockCpy = sock
|
|
28
|
-
sock = undefined
|
|
29
|
-
sockCpy.end(error).catch(onClosed)
|
|
30
|
-
}
|
|
31
|
-
state = { type: 'closed', error }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function onClosed(error?: Error): void {
|
|
35
|
-
if (!error) {
|
|
36
|
-
console.log(`WhatsApp sync connection closed without error`)
|
|
37
|
-
state = { type: 'closed' }
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
if (isInvalidAuthError(error)) {
|
|
41
|
-
console.warn(`WhatsApp sync requires re-authentication due to invalid auth state`)
|
|
42
|
-
store.reset()
|
|
43
|
-
start()
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
if (isRequiredReconnectError(error)) {
|
|
47
|
-
console.log(`WhatsApp sync connection closed due to required reconnect`)
|
|
48
|
-
store.reset()
|
|
49
|
-
startAgainAfterLogin()
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
state = { type: 'closed', error }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function start() {
|
|
56
|
-
sock = makeWASocket({ auth: store.getAuth(), browser, logger: baileysLogger, markOnlineOnConnect: false, syncFullHistory: true, emitOwnEvents: true })
|
|
57
|
-
sock.ev.on('connection.update', (update) => {
|
|
58
|
-
connectionUpdate(update, onClosed,
|
|
59
|
-
qr => state = { type: 'needAuth', qr },
|
|
60
|
-
() => state = { type: 'ready' })
|
|
61
|
-
},
|
|
62
|
-
)
|
|
63
|
-
store.bind(sock.ev)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function startAgainAfterLogin() {
|
|
67
|
-
sock = makeWASocket({ auth: store.getAuth(), browser, logger: baileysLogger, markOnlineOnConnect: false, syncFullHistory: true, emitOwnEvents: true })
|
|
68
|
-
sock.ev.on('connection.update', (update) => { connectionUpdateAfterLogin(update, onClosed, onError) })
|
|
69
|
-
// wait untill no new messages are received for 2 seconds, then set state to ready
|
|
70
|
-
let timeout = setTimeout(() => state = { type: 'ready' }, 2000)
|
|
71
|
-
sock.ev.process(() => {
|
|
72
|
-
clearTimeout(timeout)
|
|
73
|
-
timeout = setTimeout(() => state = { type: 'ready' }, 2000)
|
|
74
|
-
})
|
|
75
|
-
store.bind(sock.ev)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function sendMessage(jid: string, message: string): Promise<WAMessage> {
|
|
79
|
-
if (state.type === 'connecting') throw new Error('Server still connecting, please wait')
|
|
80
|
-
if (state.type === 'closed') throw new Error('Connection closed, please restart server')
|
|
81
|
-
if (state.type === 'needAuth') throw new Error('Authentication needed, please authenticate yourself first')
|
|
82
|
-
if (sock === undefined) throw new Error(`No Socket defined but state is ${state.type}. This is invalid, please restart server`)
|
|
83
|
-
const result = await sock.sendMessage(jid, { text: message })
|
|
84
|
-
if (!result) throw new Error(`Failed to send message to ${jid}`)
|
|
85
|
-
return result
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function setArchived(jid: string, archived: boolean): Promise<void> {
|
|
89
|
-
if (state.type === 'connecting') throw new Error('Server still connecting, please wait')
|
|
90
|
-
if (state.type === 'closed') throw new Error('Connection closed, please restart server')
|
|
91
|
-
if (state.type === 'needAuth') throw new Error('Authentication needed, please authenticate yourself first')
|
|
92
|
-
if (sock === undefined) throw new Error(`No Socket defined but state is ${state.type}. This is invalid, please restart server`)
|
|
93
|
-
const chat = store.getChat(jid)
|
|
94
|
-
if (!chat) throw new Error(`No chat found for ${jid}`)
|
|
95
|
-
const lastMessage = chat.messages?.[0].message
|
|
96
|
-
if (!lastMessage) {
|
|
97
|
-
await sock.chatModify({ archive: archived, lastMessages: [] }, jid)
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
if (!lastMessage.key) throw new Error(`Last message for ${jid} has no key, cannot archive chat`)
|
|
101
|
-
await sock.chatModify({ archive: archived, lastMessages: [lastMessage as proto.IWebMessageInfo & { key: typeof lastMessage.key }] }, jid)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function setRead(jid: string, read: boolean): Promise<void> {
|
|
105
|
-
if (state.type === 'connecting') throw new Error('Server still connecting, please wait')
|
|
106
|
-
if (state.type === 'closed') throw new Error('Connection closed, please restart server')
|
|
107
|
-
if (state.type === 'needAuth') throw new Error('Authentication needed, please authenticate yourself first')
|
|
108
|
-
if (sock === undefined) throw new Error(`No Socket defined but state is ${state.type}. This is invalid, please restart server`)
|
|
109
|
-
const chat = store.getChat(jid)
|
|
110
|
-
if (!chat) throw new Error(`No chat found for ${jid}`)
|
|
111
|
-
const lastMessage = chat.messages?.[0].message
|
|
112
|
-
if (!lastMessage) {
|
|
113
|
-
await sock.chatModify({ markRead: read, lastMessages: [] }, jid)
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
if (!lastMessage.key) throw new Error(`Last message for ${jid} has no key, cannot mark chat as unread`)
|
|
117
|
-
await sock.chatModify({ markRead: read, lastMessages: [lastMessage as proto.IWebMessageInfo & { key: typeof lastMessage.key }] }, jid)
|
|
118
|
-
}
|
|
119
|
-
start()
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
getStatus: () => state,
|
|
123
|
-
sendMessage: sendMessage,
|
|
124
|
-
setArchived: setArchived,
|
|
125
|
-
setRead: setRead,
|
|
126
|
-
close: close,
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const baileysLogger = {
|
|
131
|
-
level: 'error',
|
|
132
|
-
child: () => baileysLogger,
|
|
133
|
-
trace: () => { /* empty */ },
|
|
134
|
-
debug: () => { /* empty */ },
|
|
135
|
-
info: () => { /* empty */ },
|
|
136
|
-
warn: () => { /* empty */ },
|
|
137
|
-
error: () => { /* empty */ },
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function connectionUpdate(update: Partial<ConnectionState>, onClose: (error?: Error) => void, onQr?: (qr: string) => void, onReady?: () => void): void {
|
|
141
|
-
if (update.qr) onQr?.(update.qr)
|
|
142
|
-
else if (update.connection === 'open') onReady?.()
|
|
143
|
-
else if (update.connection !== 'close') { /* do nothing, wait for next update */ }
|
|
144
|
-
else onClose(update.lastDisconnect?.error)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isInvalidAuthError(error: unknown): boolean {
|
|
148
|
-
if (!(error instanceof Error)) return false
|
|
149
|
-
if (!('output' in error)) return false
|
|
150
|
-
if (typeof error.output !== 'object' || error.output === null) return false
|
|
151
|
-
if (!('statusCode' in error.output)) return false
|
|
152
|
-
if (error.output.statusCode !== 401) return false
|
|
153
|
-
return true
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function isRequiredReconnectError(error: unknown): boolean {
|
|
157
|
-
if (!(error instanceof Error)) return false
|
|
158
|
-
if (!('output' in error)) return false
|
|
159
|
-
if (typeof error.output !== 'object' || error.output === null) return false
|
|
160
|
-
if (!('statusCode' in error.output)) return false
|
|
161
|
-
if (error.output.statusCode !== 515) return false
|
|
162
|
-
return true
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function connectionUpdateAfterLogin(update: Partial<ConnectionState>, onClose: (error?: Error) => void, onErr: (error: Error) => void): void {
|
|
166
|
-
if (update.qr) onErr(new Error('Received QR code update during WhatsApp sync after login'))
|
|
167
|
-
else if (update.connection === 'open') { /* do nothing, wait for next update */ }
|
|
168
|
-
else if (update.connection !== 'close') { /* do nothing, wait for next update */ }
|
|
169
|
-
else onClose(update.lastDisconnect?.error)
|
|
170
|
-
}
|