@stone-wpp/ofc 1.0.5 → 1.0.7

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.
@@ -0,0 +1,571 @@
1
+ /**
2
+ * Stone WPP - proteção de grupos baseada em sinais observados.
3
+ * Não modifica mensagens, não falsifica provas e não substitui o retry criptográfico.
4
+ */
5
+ const PAYMENT_FIELDS = new Set([
6
+ 'requestPaymentMessage', 'sendPaymentMessage', 'paymentInviteMessage',
7
+ 'cancelPaymentRequestMessage', 'declinePaymentRequestMessage',
8
+ 'orderMessage', 'invoiceMessage'
9
+ ])
10
+ const PAYMENT_BUTTONS = new Set(['payment_info', 'review_and_pay', 'payment_instruction'])
11
+ const DEFAULTS = Object.freeze({
12
+ enabled: true,
13
+ mode: 'alert', // alert | delete | remove | protect
14
+ paymentBurst: 3,
15
+ messageBurst: 14,
16
+ windowMs: 8000,
17
+ lockMs: 12000,
18
+ observeInvisible: true,
19
+ observePayments: true,
20
+ observeQuotes: true,
21
+ observeFlood: true,
22
+ editNotice: false,
23
+ observeReceipts: true,
24
+ receiptBurst: 6,
25
+ immunity: [],
26
+ historyLimit: 100
27
+ })
28
+
29
+ const GROUP_SUFFIX = '@g.us'
30
+ const MAX_MONITORS = 3
31
+ const MAX_SEEN = 6000
32
+ const SEEN_TTL = 120000
33
+
34
+ // Compara apenas o mesmo domínio de JID: PN e LID não são intercambiáveis.
35
+ const canonicalJid = jid => {
36
+ if (typeof jid !== 'string' || !jid.includes('@')) return ''
37
+ const [user, domain] = jid.split('@')
38
+ return user.split(':')[0] + '@' + domain.toLowerCase()
39
+ }
40
+ const sameUser = (a, b) => !!canonicalJid(a) && canonicalJid(a) === canonicalJid(b)
41
+ const isImmune = (identity, list) => Array.isArray(list) && list.some(jid => sameUser(jid, identity))
42
+ const STORE_PREFIX = 'stone:groupProtection:'
43
+ const storedSettings = settings => Object.fromEntries(Object.keys(DEFAULTS).map(key => [key, settings[key]]))
44
+ const recordSafe = event => ({
45
+ jid: event.jid, participant: event.participant || null,
46
+ reason: event.reason || null, severity: event.severity || null,
47
+ action: event.action || null, mode: event.mode || event.attemptedMode || null,
48
+ deleted: !!event.deleted, removed: !!event.removed, locked: !!event.locked,
49
+ receivedAt: event.receivedAt || Date.now()
50
+ })
51
+ const isAdmin = p => !!p?.admin
52
+ const boundedInt = (v, min, max, key) => {
53
+ if (!Number.isInteger(v) || v < min || v > max) {
54
+ throw new RangeError(`${key} deve ser um inteiro entre ${min} e ${max}`)
55
+ }
56
+ return v
57
+ }
58
+
59
+ function scanPayment(root) {
60
+ if (!root || typeof root !== 'object') return null
61
+ const seen = new WeakSet()
62
+ const todo = [{ node: root, depth: 0 }]
63
+ let checked = 0
64
+ while (todo.length && checked++ < 180) {
65
+ const { node, depth } = todo.pop()
66
+ if (!node || typeof node !== 'object' || seen.has(node) || depth > 7) continue
67
+ seen.add(node)
68
+ for (const [key, value] of Object.entries(node)) {
69
+ if (!value || key === 'quotedMessage') continue
70
+ if (PAYMENT_FIELDS.has(key)) return key
71
+ if (key === 'nativeFlowMessage' && Array.isArray(value.buttons)) {
72
+ for (const button of value.buttons) {
73
+ if (PAYMENT_BUTTONS.has(String(button?.name || '').toLowerCase())) return 'nativeFlowPayment'
74
+ }
75
+ }
76
+ if (typeof value === 'object') todo.push({ node: value, depth: depth + 1 })
77
+ }
78
+ }
79
+ return null
80
+ }
81
+
82
+ function findQuoted(root) {
83
+ let message = root
84
+ for (let i = 0; i < 5 && message && typeof message === 'object'; i++) {
85
+ for (const key of ['extendedTextMessage', 'imageMessage', 'videoMessage', 'documentMessage', 'buttonsResponseMessage', 'templateButtonReplyMessage']) {
86
+ const context = message[key]?.contextInfo
87
+ if (context?.quotedMessage) return { message: context.quotedMessage, participant: context.participant, stanzaId: context.stanzaId }
88
+ }
89
+ message = message.ephemeralMessage?.message || message.viewOnceMessage?.message || message.viewOnceMessageV2?.message || message.documentWithCaptionMessage?.message
90
+ }
91
+ return null
92
+ }
93
+
94
+ /** Instala proteção opcional em um socket sem alterar a recepção nativa. */
95
+ export function createGroupProtectionMethods(socket) {
96
+ const groups = new Map()
97
+ const observers = new Map()
98
+ const seen = new Map()
99
+ const buckets = new Map()
100
+ const busy = new Set()
101
+ const recentActions = new Map()
102
+ const locks = new Map()
103
+ const persistence = new Map()
104
+ const managed = new Map()
105
+
106
+ function markOnce(key) {
107
+ const now = Date.now()
108
+ if (seen.has(key) && now - seen.get(key) < SEEN_TTL) return false
109
+ seen.set(key, now)
110
+ if (seen.size > MAX_SEEN) {
111
+ for (const [k, t] of seen) if (now - t >= SEEN_TTL) seen.delete(k)
112
+ while (seen.size > MAX_SEEN) seen.delete(seen.keys().next().value)
113
+ }
114
+ return true
115
+ }
116
+
117
+ function count(group, participant, kind, windowMs, mark) {
118
+ const key = `${group}\u0000${participant}\u0000${kind}`
119
+ const now = Date.now()
120
+ const old = (buckets.get(key) || []).filter(item => now - item.at < windowMs)
121
+ if (mark && !old.some(item => item.id === mark)) old.push({ id: mark, at: now })
122
+ buckets.set(key, old.slice(-80))
123
+ if (buckets.size > 3000) {
124
+ for (const [k, entries] of buckets) {
125
+ if (!entries.length || now - entries[entries.length - 1].at > 2 * windowMs) buckets.delete(k)
126
+ }
127
+ }
128
+ return old.length
129
+ }
130
+
131
+ function appendHistory(state, type, incident) {
132
+ const entry = { type, ...recordSafe(incident) }
133
+ state.history.push(entry)
134
+ if (state.history.length > state.settings.historyLimit) state.history.splice(0, state.history.length - state.settings.historyLimit)
135
+ if (state.store) {
136
+ const key = STORE_PREFIX + 'history:' + entry.jid
137
+ const previous = persistence.get(key) || Promise.resolve()
138
+ const task = previous.catch(() => {}).then(async () => {
139
+ await state.store.set(key, state.history.slice())
140
+ })
141
+ persistence.set(key, task)
142
+ task.catch(error => socket.logger?.warn?.({ error }, 'groupProtection history save failed'))
143
+ }
144
+ }
145
+ function emit(name, data) {
146
+ const state = groups.get(data?.jid)
147
+ if (state && (name === 'group-protection.alert' || name === 'group-protection.action')) {
148
+ appendHistory(state, name.endsWith('.alert') ? 'alert' : 'action', data)
149
+ }
150
+ try { socket.ev.emit(name, data) } catch (error) {
151
+ socket.logger?.warn?.({ error }, 'groupProtection event handler failed')
152
+ }
153
+ }
154
+
155
+ async function act(incident, message) {
156
+ const state = groups.get(incident.jid)
157
+ if (!state || state.settings.mode === 'alert' || incident.severity !== 'high' || !incident.verified) return
158
+ const lockKey = `${incident.jid}\u0000${incident.participant}`
159
+ if (busy.has(lockKey) || Date.now() - (recentActions.get(lockKey) || 0) < 30000) return
160
+ busy.add(lockKey)
161
+ recentActions.set(lockKey, Date.now())
162
+ if (recentActions.size > 3000) {
163
+ for (const [key, at] of recentActions) if (Date.now() - at > 60000) recentActions.delete(key)
164
+ }
165
+ const result = { ...incident, attemptedMode: state.settings.mode, deleted: false, removed: false, locked: false }
166
+ let notice = null
167
+ try {
168
+ const metadata = await socket.groupMetadata(incident.jid)
169
+ const members = Array.isArray(metadata?.participants) ? metadata.participants : []
170
+ const self = members.find(p => sameUser(p.id, socket.user?.id) || sameUser(p.id, socket.authState?.creds?.me?.lid))
171
+ const actor = members.find(p => sameUser(p.id, incident.participant))
172
+ if (!isAdmin(self)) {
173
+ result.error = 'bot_not_admin'
174
+ return
175
+ }
176
+ if (!actor || isAdmin(actor) || sameUser(actor.id, self.id)) {
177
+ result.error = 'target_unverified_or_admin'
178
+ return
179
+ }
180
+ if (isImmune(actor.id, state.settings.immunity)
181
+ || isImmune(actor.phoneNumber, state.settings.immunity)
182
+ || isImmune(actor.lid, state.settings.immunity)
183
+ || isImmune(incident.participant, state.settings.immunity)) {
184
+ result.error = 'immune_participant'
185
+ return
186
+ }
187
+ if (state.settings.editNotice) {
188
+ try {
189
+ notice = await socket.sendMessage(incident.jid, { text: 'Stone Protection: ameaça identificada. Verificando e aplicando as ações de moderação...' })
190
+ } catch (error) { result.noticeError = String(error?.message || error) }
191
+ }
192
+ if (state.settings.mode === 'protect' && !metadata.announce && !locks.has(incident.jid)) {
193
+ try {
194
+ await socket.groupSettingUpdate(incident.jid, 'announcement')
195
+ result.locked = true
196
+ const timer = setTimeout(async () => {
197
+ locks.delete(incident.jid)
198
+ try {
199
+ const latest = await socket.groupMetadata(incident.jid)
200
+ if (latest?.announce === true) await socket.groupSettingUpdate(incident.jid, 'not_announcement')
201
+ }
202
+ catch (error) { emit('group-protection.action', { jid: incident.jid, action: 'reopen', ok: false, error: String(error?.message || error) }) }
203
+ }, state.settings.lockMs)
204
+ timer.unref?.()
205
+ locks.set(incident.jid, timer)
206
+ } catch (error) { result.lockError = String(error?.message || error) }
207
+ }
208
+ if (message?.key?.id && message.key.remoteJid === incident.jid) {
209
+ try {
210
+ await socket.sendMessage(incident.jid, { delete: message.key })
211
+ result.deleted = true
212
+ } catch (error) { result.deleteError = String(error?.message || error) }
213
+ }
214
+ if (state.settings.mode === 'remove' || state.settings.mode === 'protect') {
215
+ try {
216
+ const response = await socket.groupParticipantsUpdate(incident.jid, [actor.id], 'remove')
217
+ result.removed = Array.isArray(response) && response.some(r => String(r.status) === '200')
218
+ } catch (error) { result.removeError = String(error?.message || error) }
219
+ }
220
+ } catch (error) { result.error = String(error?.message || error) }
221
+ finally {
222
+ if (notice?.key?.id && notice.key.fromMe === true) {
223
+ const status = result.removed ? 'participante removido' : result.deleted ? 'mensagem removida' : 'ações parciais ou indisponíveis'
224
+ try {
225
+ await socket.sendMessage(incident.jid, {
226
+ text: `Stone Protection: verificação concluída — ${status}.`,
227
+ edit: notice.key
228
+ })
229
+ result.noticeEdited = true
230
+ } catch (error) {
231
+ result.noticeEditError = String(error?.message || error)
232
+ }
233
+ }
234
+ busy.delete(lockKey)
235
+ state.actions++
236
+ emit('group-protection.action', result)
237
+ }
238
+ }
239
+
240
+ function report(incident, message) {
241
+ const state = groups.get(incident.jid)
242
+ if (!state) return
243
+ const evidenceKey = `${incident.jid}\u0000${incident.messageId}\u0000${incident.reason}\u0000${incident.participant}`
244
+ if (!markOnce(evidenceKey)) return
245
+ state.alerts++
246
+ emit('group-protection.alert', { ...incident, mode: state.settings.mode, receivedAt: Date.now() })
247
+ void act(incident, message).catch(error => socket.logger?.warn?.({ error }, 'groupProtection action failed'))
248
+ }
249
+
250
+ function handleSignal(data, source) {
251
+ if (!data?.remoteJid?.endsWith(GROUP_SUFFIX) || !data.messageId) return
252
+ const state = groups.get(data.remoteJid)
253
+ if (!state?.settings.observeInvisible) return
254
+ const actor = data.participant || data.participantAlt
255
+ if (!actor) return
256
+ const signature = String(data.decryptFail || '').toLowerCase()
257
+ const highSignal = signature === 'hide'
258
+ if (!markOnce(`signal\u0000${data.remoteJid}\u0000${data.messageId}\u0000${actor}`)) return
259
+ const hits = count(data.remoteJid, actor, 'ciphertext', state.settings.windowMs, data.messageId)
260
+ // Ciphertext não é prova de ataque; nunca punir só por falha de descriptografia.
261
+ if (highSignal || hits >= state.settings.paymentBurst) {
262
+ report({ jid: data.remoteJid, participant: actor, messageId: data.messageId, reason: 'invisible-suspected', severity: highSignal ? 'medium' : 'low', verified: false, source, evidence: { decryptFail: signature || null, ciphertextCount: hits } })
263
+ }
264
+ }
265
+
266
+ // Recibos são somente telemetria: pedidos de retransmissão são normais
267
+ // em redes instáveis e NÃO identificam culpabilidade de um participante.
268
+ function handleReceipt(data, source) {
269
+ if (!data?.remoteJid?.endsWith(GROUP_SUFFIX) || !data.messageId || !data.participant) return
270
+ const state = groups.get(data.remoteJid)
271
+ if (!state?.settings.observeReceipts || data.type !== 'retry') return
272
+ const mark = `receipt\u0000${data.remoteJid}\u0000${data.messageId}\u0000${data.participant}`
273
+ if (!markOnce(mark)) return
274
+ const n = count(data.remoteJid, data.participant, 'retry-receipt', state.settings.windowMs, data.messageId)
275
+ if (n === state.settings.receiptBurst || (n > state.settings.receiptBurst && n % state.settings.receiptBurst === 0)) {
276
+ report({ jid: data.remoteJid, participant: data.participant,
277
+ messageId: data.messageId, reason: 'receipt-suspected', severity: 'low',
278
+ verified: false, source, evidence: { type: data.type, receiptCount: n } })
279
+ }
280
+ }
281
+
282
+ function handlePdo(data, source) {
283
+ if (!data?.remoteJid?.endsWith(GROUP_SUFFIX)) return
284
+ const state = groups.get(data.remoteJid)
285
+ if (!state?.settings.observeInvisible || !data.messageId) return
286
+ emit('group-protection.pdo-observed', {
287
+ jid: data.remoteJid, messageId: data.messageId,
288
+ status: data.status, source, receivedAt: Date.now()
289
+ })
290
+ }
291
+
292
+ function handleMessage(message, source, kind = 'notify') {
293
+ const jid = message?.key?.remoteJid
294
+ if (!jid?.endsWith(GROUP_SUFFIX) || message.key.fromMe) return
295
+ const state = groups.get(jid)
296
+ if (!state || (kind !== 'notify' && kind !== 'append')) return
297
+ const id = message.key.id
298
+ const participant = message.key.participant || message.participant || message.key.participantAlt
299
+ if (!id || !participant || !message.message) return
300
+ const stamp = Number(message.messageTimestamp || 0)
301
+ if (stamp && Math.abs(Date.now() - stamp * 1000) > 120000) return
302
+ if (!markOnce(`message\u0000${jid}\u0000${id}`)) return
303
+ const settings = state.settings
304
+ const paymentType = settings.observePayments ? scanPayment(message.message) : null
305
+ const volume = settings.observeFlood ? count(jid, participant, 'messages', settings.windowMs, id) : 0
306
+ if (paymentType) {
307
+ const payments = count(jid, participant, 'payment', settings.windowMs, id)
308
+ report({ jid, participant, messageId: id, reason: 'payment', severity: payments >= settings.paymentBurst ? 'high' : 'low', verified: payments >= settings.paymentBurst, source, evidence: { paymentType, paymentCount: payments } }, message)
309
+ }
310
+ if (settings.observeFlood && volume >= settings.messageBurst) {
311
+ report({ jid, participant, messageId: id, reason: 'flood', severity: 'high', verified: true, source, evidence: { messageCount: volume } }, message)
312
+ }
313
+ // Citações são evidência auxiliar, nunca prova para punir outro participante.
314
+ if (settings.observeQuotes) {
315
+ const quoted = findQuoted(message.message)
316
+ const quotePayment = quoted && scanPayment(quoted.message)
317
+ if (quotePayment) {
318
+ report({ jid, participant: quoted.participant || participant, messageId: quoted.stanzaId || id, reason: 'quoted-payment', severity: 'medium', verified: false, source, evidence: { quotedBy: participant, paymentType: quotePayment } })
319
+ }
320
+ }
321
+ }
322
+
323
+ function connectObserver(monitor, index) {
324
+ if (!monitor?.ev || typeof monitor.ev.on !== 'function') throw new TypeError('monitor deve ser um socket Stone WPP conectado')
325
+ if (observers.has(monitor)) return
326
+ const source = index === -1 ? 'primary' : `monitor-${index + 1}`
327
+ const allowed = groupJid => monitor === socket || groups.get(groupJid)?.monitors.includes(monitor)
328
+ const onUpsert = payload => {
329
+ for (const message of payload?.messages || []) {
330
+ if (allowed(message?.key?.remoteJid)) handleMessage(message, source, payload?.type)
331
+ }
332
+ }
333
+ const onSignal = payload => {
334
+ if (allowed(payload?.remoteJid)) handleSignal(payload, source)
335
+ }
336
+ monitor.ev.on('messages.upsert', onUpsert)
337
+ const onReceipt = payload => {
338
+ if (allowed(payload?.remoteJid)) handleReceipt(payload, source)
339
+ }
340
+ const onPdo = payload => {
341
+ if (allowed(payload?.remoteJid)) handlePdo(payload, source)
342
+ }
343
+ monitor.ev.on('group-protection.ciphertext', onSignal)
344
+ monitor.ev.on('group-protection.receipt', onReceipt)
345
+ monitor.ev.on('group-protection.pdo', onPdo)
346
+ observers.set(monitor, () => {
347
+ monitor.ev.off('messages.upsert', onUpsert)
348
+ monitor.ev.off('group-protection.ciphertext', onSignal)
349
+ monitor.ev.off('group-protection.receipt', onReceipt)
350
+ monitor.ev.off('group-protection.pdo', onPdo)
351
+ })
352
+ }
353
+
354
+ function pruneObservers() {
355
+ const active = new Set([...groups.values()].flatMap(g => g.monitors))
356
+ for (const [monitor, detach] of observers) {
357
+ if (monitor !== socket && !active.has(monitor)) { detach(); observers.delete(monitor) }
358
+ }
359
+ }
360
+ function statusFor(jid, state, extra = {}) {
361
+ if (!state) return { jid, enabled: false, monitorCount: 0, alerts: 0, actions: 0, ...extra }
362
+ return { jid, ...state.settings, monitorCount: state.monitors.length,
363
+ alerts: state.alerts, actions: state.actions, ...extra }
364
+ }
365
+ function stopManaged(jid) {
366
+ for (const [key, active] of managed) {
367
+ if (!key.startsWith(jid + '\u0000')) continue
368
+ active.cancelled = true
369
+ if (active.timer) clearTimeout(active.timer)
370
+ if (active.socket && active.onUpdate) active.socket.ev.off('connection.update', active.onUpdate)
371
+ managed.delete(key)
372
+ }
373
+ }
374
+ async function startManaged(jid, index, factory, state, reconnectMs) {
375
+ const key = jid + '\u0000' + index
376
+ if (managed.get(key)?.factory === factory) return
377
+ const current = managed.get(key)
378
+ if (current) {
379
+ current.cancelled = true
380
+ if (current.timer) clearTimeout(current.timer)
381
+ if (current.socket && current.onUpdate) current.socket.ev.off('connection.update', current.onUpdate)
382
+ }
383
+ const item = { factory, timer: null, socket: null, onUpdate: null, attempt: 0, cancelled: false }
384
+ managed.set(key, item)
385
+ const attach = async () => {
386
+ if (item.cancelled || groups.get(jid) !== state) return
387
+ item.attempt++
388
+ try {
389
+ const create = typeof factory === 'function' ? factory : factory.create
390
+ const monitor = await create({ jid, attempt: item.attempt })
391
+ if (!monitor?.ev || typeof monitor.ev.on !== 'function' || monitor === socket) {
392
+ throw new TypeError('monitorFactory deve retornar outro socket autenticado')
393
+ }
394
+ if (item.cancelled || groups.get(jid) !== state) return
395
+ // Pareamento autorizado: o proprietário recebe o código via callback.
396
+ // Nenhuma sessão é vinculada sem a confirmação no próprio WhatsApp.
397
+ if (typeof factory === 'object' && factory.phoneNumber
398
+ && monitor.authState?.creds?.registered === false) {
399
+ if (typeof monitor.requestPairingCode !== 'function') {
400
+ throw new TypeError('socket auxiliar não suporta requestPairingCode')
401
+ }
402
+ const code = await monitor.requestPairingCode(factory.phoneNumber)
403
+ await factory.onPairingCode({ jid, index, code })
404
+ }
405
+ if (item.socket && item.onUpdate) item.socket.ev.off('connection.update', item.onUpdate)
406
+ const previous = item.socket
407
+ item.socket = monitor
408
+ if (previous) {
409
+ state.monitors = state.monitors.filter(m => m !== previous)
410
+ }
411
+ if (!state.monitors.includes(monitor)) state.monitors.push(monitor)
412
+ connectObserver(monitor, index)
413
+ pruneObservers()
414
+ item.onUpdate = update => {
415
+ if (update?.connection === 'open') {
416
+ item.attempt = 0
417
+ emit('group-protection.monitor', { jid, index, status: 'connected' })
418
+ }
419
+ if (update?.connection !== 'close' || item.cancelled) return
420
+ const code = update?.lastDisconnect?.error?.output?.statusCode
421
+ // Logged out: é necessário parear novamente com consentimento.
422
+ if (code === 401) {
423
+ monitor.ev.off('connection.update', item.onUpdate)
424
+ state.monitors = state.monitors.filter(m => m !== monitor)
425
+ item.socket = null
426
+ pruneObservers()
427
+ emit('group-protection.monitor', { jid, index, status: 'logged-out' })
428
+ return
429
+ }
430
+ monitor.ev.off('connection.update', item.onUpdate)
431
+ state.monitors = state.monitors.filter(m => m !== monitor)
432
+ pruneObservers()
433
+ schedule()
434
+ }
435
+ monitor.ev.on('connection.update', item.onUpdate)
436
+ emit('group-protection.monitor', { jid, index, status: 'attached' })
437
+ } catch (error) {
438
+ emit('group-protection.monitor', { jid, index, status: 'error',
439
+ error: String(error?.message || error) })
440
+ schedule()
441
+ }
442
+ }
443
+ const schedule = () => {
444
+ if (item.cancelled || groups.get(jid) !== state) return
445
+ const ms = Math.min(60000, reconnectMs * Math.pow(2, Math.min(item.attempt - 1, 5)))
446
+ item.timer = setTimeout(() => { item.timer = null; void attach() }, ms)
447
+ item.timer.unref?.()
448
+ }
449
+ await attach()
450
+ }
451
+
452
+ connectObserver(socket, -1)
453
+
454
+ return {
455
+ /**
456
+ * Consulta / configura a proteção. Monitors: sockets Stone extras autenticados
457
+ * separadamente e presentes nos mesmos grupos (até três). A Stone não cria sessões
458
+ * extras automaticamente nem promete acessar mensagens omitidas pelo WhatsApp.
459
+ */
460
+ async groupProtection(jid, options) {
461
+ if (typeof jid !== 'string' || !jid.endsWith(GROUP_SUFFIX)) {
462
+ throw new TypeError('groupProtection requer JID de grupo (@g.us)')
463
+ }
464
+ const old = groups.get(jid)
465
+ if (options === undefined) return statusFor(jid, old)
466
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
467
+ throw new TypeError('options deve ser um objeto')
468
+ }
469
+ const store = options.store || old?.store
470
+ if (store && (typeof store.get !== 'function' || typeof store.set !== 'function')) {
471
+ throw new TypeError('store requer métodos get e set')
472
+ }
473
+ if (options.getHistory === true) {
474
+ const limit = options.limit === undefined ? 100 : boundedInt(options.limit, 1, 500, 'limit')
475
+ const saved = store ? await store.get(STORE_PREFIX + 'history:' + jid) : null
476
+ const history = old?.history?.length ? old.history : (Array.isArray(saved) ? saved : [])
477
+ return statusFor(jid, old, { history: history.slice(-limit) })
478
+ }
479
+ if (options.enabled === false) {
480
+ const savedStore = store
481
+ groups.delete(jid)
482
+ stopManaged(jid)
483
+ pruneObservers()
484
+ if (savedStore) {
485
+ await savedStore.set(STORE_PREFIX + 'settings:' + jid, { enabled: false })
486
+ }
487
+ return statusFor(jid, null)
488
+ }
489
+ let restored = null
490
+ if (options.restore === true) {
491
+ if (!store) throw new TypeError('restore requer store')
492
+ restored = await store.get(STORE_PREFIX + 'settings:' + jid)
493
+ if (!restored?.enabled) return statusFor(jid, old, { restored: false })
494
+ }
495
+ const input = { ...(old?.settings || DEFAULTS), ...(restored || {}), ...options, enabled: true }
496
+ if (!['alert', 'delete', 'remove', 'protect'].includes(input.mode)) {
497
+ throw new TypeError('mode: alert, delete, remove ou protect')
498
+ }
499
+ for (const key of ['observeInvisible', 'observePayments', 'observeQuotes', 'observeFlood', 'editNotice', 'observeReceipts']) {
500
+ if (typeof input[key] !== 'boolean') throw new TypeError(`${key} deve ser booleano`)
501
+ }
502
+ input.paymentBurst = boundedInt(input.paymentBurst, 2, 30, 'paymentBurst')
503
+ input.messageBurst = boundedInt(input.messageBurst, 3, 100, 'messageBurst')
504
+ input.receiptBurst = boundedInt(input.receiptBurst, 3, 50, 'receiptBurst')
505
+ input.windowMs = boundedInt(input.windowMs, 1000, 60000, 'windowMs')
506
+ input.lockMs = boundedInt(input.lockMs, 3000, 120000, 'lockMs')
507
+ input.historyLimit = boundedInt(input.historyLimit, 20, 500, 'historyLimit')
508
+ if (!Array.isArray(input.immunity) || input.immunity.length > 200
509
+ || !input.immunity.every(jid => typeof jid === 'string' && canonicalJid(jid))) {
510
+ throw new TypeError('immunity deve conter até 200 JIDs válidos')
511
+ }
512
+ input.immunity = [...new Set(input.immunity.map(canonicalJid))]
513
+ const staticMonitors = options.monitors === undefined ? (old?.staticMonitors || []) : options.monitors
514
+ const factories = options.monitorFactories === undefined ? (old?.factories || []) : options.monitorFactories
515
+ if (!Array.isArray(staticMonitors) || !Array.isArray(factories)
516
+ || staticMonitors.length + factories.length > MAX_MONITORS
517
+ || new Set(staticMonitors).size !== staticMonitors.length || staticMonitors.includes(socket)
518
+ || !factories.every(f => typeof f === 'function' ||
519
+ (f && typeof f === 'object' && typeof f.create === 'function'
520
+ && (!f.phoneNumber || (typeof f.phoneNumber === 'string'
521
+ && /^\d{8,15}$/.test(f.phoneNumber)
522
+ && typeof f.onPairingCode === 'function'))))) {
523
+ throw new TypeError('Até 3 monitores: sockets distintos e/ou monitorFactories')
524
+ }
525
+ for (const monitor of staticMonitors) {
526
+ if (!monitor?.ev || typeof monitor.ev.on !== 'function') {
527
+ throw new TypeError('monitor deve ser socket Stone válido')
528
+ }
529
+ }
530
+ const reconnectMs = options.monitorReconnectMs === undefined
531
+ ? (old?.monitorReconnectMs || 3000)
532
+ : boundedInt(options.monitorReconnectMs, 1000, 60000, 'monitorReconnectMs')
533
+ const settings = storedSettings(input)
534
+ const state = old || { alerts: 0, actions: 0, history: [], monitors: [] }
535
+ state.settings = settings
536
+ state.staticMonitors = staticMonitors
537
+ state.factories = factories
538
+ state.monitorReconnectMs = reconnectMs
539
+ state.store = store
540
+ state.monitors = [...staticMonitors, ...[...managed.entries()]
541
+ .filter(([key, item]) => key.startsWith(jid + '\u0000')
542
+ && !item.cancelled && item.socket && factories.includes(item.factory))
543
+ .map(([, item]) => item.socket)]
544
+ if (store && !old?.history?.length) {
545
+ const history = await store.get(STORE_PREFIX + 'history:' + jid)
546
+ if (Array.isArray(history)) state.history = history.slice(-settings.historyLimit)
547
+ }
548
+ groups.set(jid, state)
549
+ staticMonitors.forEach((monitor, index) => connectObserver(monitor, index))
550
+ for (const [key, item] of managed) {
551
+ if (!key.startsWith(jid + '\u0000')) continue
552
+ const index = Number(key.slice((jid + '\u0000').length))
553
+ if (factories[index] !== item.factory) {
554
+ item.cancelled = true
555
+ if (item.timer) clearTimeout(item.timer)
556
+ if (item.socket && item.onUpdate) item.socket.ev.off('connection.update', item.onUpdate)
557
+ state.monitors = state.monitors.filter(monitor => monitor !== item.socket)
558
+ managed.delete(key)
559
+ }
560
+ }
561
+ for (let i = 0; i < factories.length; i++) {
562
+ await startManaged(jid, i, factories[i], state, reconnectMs)
563
+ }
564
+ pruneObservers()
565
+ if (store) {
566
+ await store.set(STORE_PREFIX + 'settings:' + jid, settings)
567
+ }
568
+ return statusFor(jid, state, { ...(options.restore ? { restored: true } : {}) })
569
+ }
570
+ }
571
+ }
@@ -256,5 +256,5 @@ declare const makeWASocket: (config: UserFacingSocketConfig) => {
256
256
  }[] | undefined>;
257
257
  fetchAccountReachoutTimelock: () => Promise<import("../index.js").ReachoutTimelockState>;
258
258
  fetchNewChatMessageCap: () => Promise<import("../index.js").NewChatMessageCapInfo>;
259
- } & import('./stone-features.js').StoneAdditionalMethods;
259
+ } & import('./stone-features.js').StoneAdditionalMethods & import('./group-protection.js').GroupProtectionMethods & { calls: import('./stone-calls.js').StoneCalls };
260
260
  export default makeWASocket;
@@ -1,6 +1,8 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
2
  import { makeCommunitiesSocket } from './communities.js';
3
3
  import { createStoneAdditionalMethods } from './stone-features.js';
4
+ import { createGroupProtectionMethods } from './group-protection.js';
5
+ import { createStoneCalls } from './stone-calls.js';
4
6
  // export the last socket layer
5
7
  const makeWASocket = (config) => {
6
8
  const newConfig = {
@@ -8,6 +10,6 @@ const makeWASocket = (config) => {
8
10
  ...config
9
11
  };
10
12
  const socket = makeCommunitiesSocket(newConfig);
11
- return Object.assign(socket, createStoneAdditionalMethods(socket));
13
+ return Object.assign(socket, createStoneAdditionalMethods(socket), createGroupProtectionMethods(socket), { calls: createStoneCalls(socket) });
12
14
  };
13
15
  export default makeWASocket;