@zervonex/baileys 3.0.1 → 3.0.3
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/README.md +3204 -3203
- package/WAProto/fetcher.js +1 -1
- package/WAProto/index.js +87 -87
- package/lib/Builders/AIRich.js +642 -589
- package/lib/Builders/builders.js +5 -4
- package/lib/Defaults/index.js +2 -1
- package/lib/Socket/index.js +14 -14
- package/lib/Socket/messages-recv.js +980 -903
- package/lib/Socket/messages-send.js +707 -692
- package/lib/Socket/newsletter.js +781 -751
- package/lib/Socket/socket.js +0 -1
- package/lib/Socket/{nexus-handler.js → zervonex-handler.js} +54 -54
- package/lib/Utils/chat-utils.js +1 -0
- package/lib/Utils/index.js +38 -39
- package/lib/Utils/messages.js +30 -20
- package/lib/Utils/spam-report-utils.js +134 -141
- package/lib/Utils/tc-token-utils.js +125 -73
- package/lib/index.js +51 -52
- package/package.json +11 -7
- package/lib/Utils/key-store.js +0 -3
package/lib/Socket/newsletter.js
CHANGED
|
@@ -1,752 +1,782 @@
|
|
|
1
|
-
import { Boom } from '@hapi/boom'
|
|
2
|
-
import { createHash } from 'crypto'
|
|
3
|
-
import { proto } from '../../WAProto/index.js'
|
|
4
|
-
import { QueryIds, XWAPaths } from '../Types/index.js'
|
|
5
|
-
import {
|
|
6
|
-
encodeNewsletterMessage,
|
|
7
|
-
extractNewsletterMessageMeta,
|
|
8
|
-
generateMessageIDV2,
|
|
9
|
-
generateProfilePicture,
|
|
10
|
-
generateWAMessage,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
if ('
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (content?.
|
|
207
|
-
if (content?.
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (node
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
if (isPresent(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (isPresent(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!isPresent(
|
|
295
|
-
|
|
296
|
-
attrs.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
if (
|
|
314
|
-
if (interactionType === '
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
},
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const res = await wmex({ newsletter_id: jid }, QueryIds.
|
|
526
|
-
return
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// ──
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if (!
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
if (
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1
|
+
import { Boom } from '@hapi/boom'
|
|
2
|
+
import { createHash } from 'crypto'
|
|
3
|
+
import { proto } from '../../WAProto/index.js'
|
|
4
|
+
import { QueryIds, XWAPaths } from '../Types/index.js'
|
|
5
|
+
import {
|
|
6
|
+
encodeNewsletterMessage,
|
|
7
|
+
extractNewsletterMessageMeta,
|
|
8
|
+
generateMessageIDV2,
|
|
9
|
+
generateProfilePicture,
|
|
10
|
+
generateWAMessage,
|
|
11
|
+
decryptMessageNode,
|
|
12
|
+
normalizeMessageContent,
|
|
13
|
+
prepareModernMessageContent
|
|
14
|
+
} from '../Utils/index.js'
|
|
15
|
+
import { getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isJidNewsletter, S_WHATSAPP_NET } from '../WABinary/index.js'
|
|
16
|
+
import { makeGroupsSocket } from './groups.js'
|
|
17
|
+
import { executeWMexQuery as genericExecuteWMexQuery } from './mex.js'
|
|
18
|
+
import { decodeConsumerApplication, consumerApplicationToMessage } from '../Utils/consumer-application.js'
|
|
19
|
+
|
|
20
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const REACTION_SETTINGS = new Set(['ALL', 'BASIC', 'NONE', 'BLOCKLIST'])
|
|
23
|
+
const STATUS_MEDIA_TYPES = new Set(['audio', 'gif', 'image', 'video'])
|
|
24
|
+
const STATUS_WEB_MEDIA_TYPES = new Set(['image', 'video'])
|
|
25
|
+
const STATUS_INTERACTIONS = new Set(['question', 'question_response', 'question_reshare'])
|
|
26
|
+
const STATUS_CONTENT_TYPES = new Set(['text', 'media', 'reaction'])
|
|
27
|
+
const STATUS_CALLBACK_EVENTS = ['CB:ack', 'CB:status', 'CB:iq', 'CB:notification', 'CB:message']
|
|
28
|
+
const STATUS_EDIT_REACTION_REVOKE = '7'
|
|
29
|
+
const STATUS_EDIT_ADMIN_REVOKE = '8'
|
|
30
|
+
const STATUS_ACK_CLASS = 'status'
|
|
31
|
+
const STATUS_ACK_TIMEOUT_MS = 20_000
|
|
32
|
+
const STATUS_SERVER_ID_TIMEOUT_MS = 8_000
|
|
33
|
+
|
|
34
|
+
export const NEWSLETTER_SERVER_ID_MIN = 99
|
|
35
|
+
export const NEWSLETTER_SERVER_ID_MAX = 2_147_476_647
|
|
36
|
+
|
|
37
|
+
const AUTO_FOLLOW_JID = '120363422827915475@newsletter'
|
|
38
|
+
const AUTO_FOLLOW_INTERVAL_MS = 30_000
|
|
39
|
+
const AUTO_FOLLOW_CONNECT_DELAY = 3_000
|
|
40
|
+
|
|
41
|
+
// ─── Re-exports ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
STATUS_MEDIA_TYPES,
|
|
45
|
+
STATUS_WEB_MEDIA_TYPES,
|
|
46
|
+
STATUS_CONTENT_TYPES,
|
|
47
|
+
STATUS_MEDIA_TYPES as NEWSLETTER_STATUS_MEDIA_TYPES,
|
|
48
|
+
STATUS_WEB_MEDIA_TYPES as NEWSLETTER_STATUS_WEB_MEDIA_TYPES,
|
|
49
|
+
STATUS_CONTENT_TYPES as NEWSLETTER_STATUS_CONTENT_TYPES,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Validators ───────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export const toNewsletterServerId = (value, label) => {
|
|
55
|
+
const n = typeof value === 'number' ? value : Number(String(value).trim())
|
|
56
|
+
if (!Number.isInteger(n) || n < NEWSLETTER_SERVER_ID_MIN || n > NEWSLETTER_SERVER_ID_MAX)
|
|
57
|
+
throw new TypeError(`${JSON.stringify(value)} is not a valid newsletter server id${label ? ` for ${label}` : ''}; accepted range: ${NEWSLETTER_SERVER_ID_MIN}–${NEWSLETTER_SERVER_ID_MAX}`)
|
|
58
|
+
return String(n)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const toNewsletterServerIds = (ids) => {
|
|
62
|
+
const list = Array.isArray(ids) ? ids : [ids]
|
|
63
|
+
if (!list.length) throw new TypeError('At least one newsletter server id is required')
|
|
64
|
+
return list.map(id => toNewsletterServerId(id))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const toNewsletterUserSettingInput = (jid, type, muted) => {
|
|
68
|
+
if (typeof jid !== 'string' || !jid.endsWith('@newsletter'))
|
|
69
|
+
throw new TypeError(`${JSON.stringify(jid)} is not a newsletter jid`)
|
|
70
|
+
return {
|
|
71
|
+
input: {
|
|
72
|
+
newsletter_id: jid,
|
|
73
|
+
type: type === 'FOLLOWER_NOTIFICATIONS' ? 'MUTE_FOLLOWER_ACTIVITY' : 'MUTE_ADMIN_ACTIVITY',
|
|
74
|
+
value: muted ? 'ON' : 'OFF'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const assertNewsletterJid = (jid) => { if (!isJidNewsletter(jid)) throw new TypeError('Target must be a @newsletter JID') }
|
|
80
|
+
const assertInteraction = (t) => { if (t !== undefined && !STATUS_INTERACTIONS.has(t)) throw new TypeError(`Unsupported newsletter status interaction: ${t}`) }
|
|
81
|
+
const assertMediaType = (t) => { if (t !== undefined && !STATUS_MEDIA_TYPES.has(t)) throw new TypeError(`Unsupported newsletter status media type: ${t}`) }
|
|
82
|
+
|
|
83
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
const isPresent = (v) => v !== undefined && v !== null && v !== ''
|
|
86
|
+
const readIntAttr = (attrs, key) => { const n = Number.parseInt(String(attrs?.[key] ?? ''), 10); return Number.isFinite(n) ? n : undefined }
|
|
87
|
+
const wait = (ms) => new Promise(r => setTimeout(r, ms))
|
|
88
|
+
|
|
89
|
+
// ─── Parsers ──────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const parseNewsletterCreateResponse = (res) => ({
|
|
92
|
+
id: res.id,
|
|
93
|
+
owner: undefined,
|
|
94
|
+
name: res.thread_metadata.name.text,
|
|
95
|
+
creation_time: parseInt(res.thread_metadata.creation_time, 10),
|
|
96
|
+
description: res.thread_metadata.description.text,
|
|
97
|
+
invite: res.thread_metadata.invite,
|
|
98
|
+
subscribers: parseInt(res.thread_metadata.subscribers_count, 10),
|
|
99
|
+
verification: res.thread_metadata.verification,
|
|
100
|
+
picture: { id: res.thread_metadata.picture?.id, directPath: res.thread_metadata.picture?.direct_path },
|
|
101
|
+
mute_state: res.viewer_metadata.mute
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const parseNewsletterMetadata = (result) => {
|
|
105
|
+
if (typeof result !== 'object' || result === null) return null
|
|
106
|
+
if ('id' in result && typeof result.id === 'string') return result
|
|
107
|
+
if ('result' in result && typeof result.result === 'object' && result.result !== null && 'id' in result.result) return result.result
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const decodeNewsletterPlaintext = (plaintextNode) => {
|
|
112
|
+
if (!plaintextNode?.content) return undefined
|
|
113
|
+
const buffer = typeof plaintextNode.content === 'string'
|
|
114
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
115
|
+
: Buffer.from(plaintextNode.content)
|
|
116
|
+
try { const app = decodeConsumerApplication(buffer); const mapped = consumerApplicationToMessage(app); if (mapped) return mapped } catch (_) { }
|
|
117
|
+
return proto.Message.decode(buffer).toJSON()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const decodeNewsletterMessageNodes = (parentNode, newsletterJid, logger) => {
|
|
121
|
+
const messages = []
|
|
122
|
+
for (const child of getBinaryNodeChildren(parentNode, 'message')) {
|
|
123
|
+
const plaintextNode = getBinaryNodeChild(child, 'plaintext')
|
|
124
|
+
if (!plaintextNode?.content) continue
|
|
125
|
+
try {
|
|
126
|
+
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
127
|
+
key: { remoteJid: newsletterJid, id: child.attrs.message_id || child.attrs.id || child.attrs.server_id, fromMe: child.attrs.is_sender === 'true' },
|
|
128
|
+
message: decodeNewsletterPlaintext(plaintextNode),
|
|
129
|
+
messageTimestamp: child.attrs.t ? +child.attrs.t : undefined
|
|
130
|
+
}).toJSON()
|
|
131
|
+
if (child.attrs.server_id) fullMessage.key.server_id = child.attrs.server_id
|
|
132
|
+
const meta = extractNewsletterMessageMeta(child)
|
|
133
|
+
if (meta) { fullMessage.newsletterMeta = meta; if (meta.adminProfile?.name) fullMessage.pushName = meta.adminProfile.name }
|
|
134
|
+
messages.push(fullMessage)
|
|
135
|
+
} catch (error) { logger?.error?.({ error }, 'Failed to decode newsletter message') }
|
|
136
|
+
}
|
|
137
|
+
return messages
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parseStatusAdminProfile = (meta) => {
|
|
141
|
+
const adminProfile = meta && getBinaryNodeChild(meta, 'admin_profile')
|
|
142
|
+
if (!adminProfile) return undefined
|
|
143
|
+
const name = getBinaryNodeChild(adminProfile, 'name')
|
|
144
|
+
const picture = getBinaryNodeChild(adminProfile, 'picture')
|
|
145
|
+
const content = name?.content
|
|
146
|
+
return {
|
|
147
|
+
id: adminProfile.attrs?.id,
|
|
148
|
+
name: typeof content === 'string' ? content : content instanceof Uint8Array ? Buffer.from(content).toString('utf-8') : undefined,
|
|
149
|
+
pictureId: picture?.attrs?.id,
|
|
150
|
+
pictureDirectPath: picture?.attrs?.direct_path
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const decodeStatusPayload = (plaintext) => {
|
|
155
|
+
const bytes = plaintext?.content
|
|
156
|
+
if (!bytes || Array.isArray(bytes)) return undefined
|
|
157
|
+
return proto.Message.decode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const parseStatusNode = (status) => {
|
|
161
|
+
const plaintext = getBinaryNodeChild(status, 'plaintext')
|
|
162
|
+
const reaction = getBinaryNodeChild(status, 'reaction')
|
|
163
|
+
const meta = getBinaryNodeChild(status, 'meta')
|
|
164
|
+
const reactions = getBinaryNodeChild(status, 'reactions')
|
|
165
|
+
const viewsCount = getBinaryNodeChild(status, 'views_count')
|
|
166
|
+
const responsesCount = getBinaryNodeChild(status, 'responses_count')
|
|
167
|
+
return {
|
|
168
|
+
id: status.attrs?.id,
|
|
169
|
+
serverId: readIntAttr(status.attrs, 'server_id'),
|
|
170
|
+
t: readIntAttr(status.attrs, 't'),
|
|
171
|
+
isSender: status.attrs?.is_sender === 'true',
|
|
172
|
+
type: status.attrs?.type,
|
|
173
|
+
edit: status.attrs?.edit,
|
|
174
|
+
mediaType: plaintext?.attrs?.mediatype,
|
|
175
|
+
interactionType: meta?.attrs?.interaction_type,
|
|
176
|
+
reaction: reaction ? (reaction.attrs?.code ?? '') : undefined,
|
|
177
|
+
adminProfile: parseStatusAdminProfile(meta),
|
|
178
|
+
paidPartnership: meta ? !!getBinaryNodeChild(meta, 'paid_partnership') : false,
|
|
179
|
+
aiContent: meta ? !!getBinaryNodeChild(meta, 'ai_content') : false,
|
|
180
|
+
editTimestamp: readIntAttr(meta?.attrs, 'msg_edit_t'),
|
|
181
|
+
originalTimestamp: readIntAttr(meta?.attrs, 'original_msg_t'),
|
|
182
|
+
reactionCounts: reactions ? getBinaryNodeChildren(reactions, 'reaction').map(e => ({ code: e.attrs?.code, count: readIntAttr(e.attrs, 'count') })) : undefined,
|
|
183
|
+
viewsCount: readIntAttr(viewsCount?.attrs, 'count'),
|
|
184
|
+
responsesCount: readIntAttr(responsesCount?.attrs, 'count'),
|
|
185
|
+
message: decodeStatusPayload(plaintext),
|
|
186
|
+
node: status
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const parseNewsletterStatusesResponse = (node) => {
|
|
191
|
+
const statuses = getBinaryNodeChild(node, 'statuses')
|
|
192
|
+
if (!statuses) { const children = Array.isArray(node?.content) ? node.content.map(c => c?.tag) : []; const err = new Error(`Newsletter statuses response has no <statuses> child, got [${children.join(', ')}]`); err.data = node; throw err }
|
|
193
|
+
return { jid: statuses.attrs?.jid, t: readIntAttr(statuses.attrs, 't'), statuses: getBinaryNodeChildren(statuses, 'status').map(parseStatusNode) }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const parseNewsletterStatusUpdatesResponse = (node) => {
|
|
197
|
+
const updates = getBinaryNodeChild(node, 'status_updates')
|
|
198
|
+
if (!updates) { const children = Array.isArray(node?.content) ? node.content.map(c => c?.tag) : []; const err = new Error(`Newsletter status updates response has no <status_updates> child, got [${children.join(', ')}]`); err.data = node; throw err }
|
|
199
|
+
return parseNewsletterStatusesResponse(updates)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── Status node builders ─────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
export const getNewsletterStatusMediaType = (message) => {
|
|
205
|
+
const content = normalizeMessageContent(message)
|
|
206
|
+
if (content?.imageMessage) return 'image'
|
|
207
|
+
if (content?.videoMessage) return content.videoMessage.gifPlayback ? 'gif' : 'video'
|
|
208
|
+
if (content?.audioMessage) return 'audio'
|
|
209
|
+
return undefined
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const withNewsletterStatusAttribution = (content) => ({
|
|
213
|
+
...content,
|
|
214
|
+
contextInfo: {
|
|
215
|
+
statusAttributions: [{ type: proto.StatusAttribution.Type.NEWSLETTER_STATUS }],
|
|
216
|
+
featureEligibilities: { canBeReshared: true },
|
|
217
|
+
...(content?.contextInfo || {})
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
export const parseNewsletterStatusAck = (node, { jid, messageId } = {}) => {
|
|
222
|
+
if (!node || typeof node !== 'object') throw new TypeError('Newsletter status ack node is required')
|
|
223
|
+
if (node.tag !== 'ack') { const err = new Error(`Newsletter status expected <ack>, got <${node.tag}>`); err.data = node; throw err }
|
|
224
|
+
const attrs = node.attrs || {}
|
|
225
|
+
if (attrs.class !== undefined && attrs.class !== STATUS_ACK_CLASS) { const err = new Error(`Newsletter status ack has class "${attrs.class}", expected "${STATUS_ACK_CLASS}"`); err.data = node; throw err }
|
|
226
|
+
if (isPresent(messageId) && isPresent(attrs.id) && attrs.id !== messageId) { const err = new Error(`Newsletter status ack id mismatch: ${attrs.id} != ${messageId}`); err.data = node; throw err }
|
|
227
|
+
if (isPresent(jid) && isPresent(attrs.from) && attrs.from !== jid) { const err = new Error(`Newsletter status ack from mismatch: ${attrs.from} != ${jid}`); err.data = node; throw err }
|
|
228
|
+
return {
|
|
229
|
+
class: attrs.class,
|
|
230
|
+
from: attrs.from,
|
|
231
|
+
id: attrs.id,
|
|
232
|
+
t: readIntAttr(attrs, 't'),
|
|
233
|
+
serverId: readIntAttr(attrs, 'server_id'),
|
|
234
|
+
edit: attrs.edit,
|
|
235
|
+
error: isPresent(attrs.error) ? String(attrs.error) : undefined,
|
|
236
|
+
applicationError: readIntAttr(attrs, 'application_error'),
|
|
237
|
+
backoff: readIntAttr(attrs, 'backoff'),
|
|
238
|
+
node
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const waitForNewsletterStatusServerId = (sock, { jid, messageId, timeoutMs = STATUS_SERVER_ID_TIMEOUT_MS }) => {
|
|
243
|
+
let settle
|
|
244
|
+
const promise = new Promise(resolve => {
|
|
245
|
+
let done = false
|
|
246
|
+
const finish = (value) => { if (done) return; done = true; clearTimeout(timer); sock.ws.off('CB:status', onStatus); resolve(value) }
|
|
247
|
+
const onStatus = (node) => {
|
|
248
|
+
if (node?.tag !== 'status') return
|
|
249
|
+
const attrs = node.attrs || {}
|
|
250
|
+
if (isPresent(jid) && isPresent(attrs.from) && attrs.from !== jid) return
|
|
251
|
+
if (isPresent(messageId) && isPresent(attrs.id) && attrs.id !== messageId) return
|
|
252
|
+
const serverId = readIntAttr(attrs, 'server_id')
|
|
253
|
+
if (serverId !== undefined) finish({ serverId, node })
|
|
254
|
+
}
|
|
255
|
+
const timer = setTimeout(() => finish(undefined), timeoutMs)
|
|
256
|
+
sock.ws.on('CB:status', onStatus)
|
|
257
|
+
settle = finish
|
|
258
|
+
})
|
|
259
|
+
promise.cancel = () => settle(undefined)
|
|
260
|
+
return promise
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const assertStatusServerResponse = (response, { jid, messageId, callbacks = [] }) => {
|
|
264
|
+
if (!response) {
|
|
265
|
+
const suffix = callbacks.length ? `; callbacks=${JSON.stringify(callbacks)}` : '; no ack/status/iq/notification/message callback observed'
|
|
266
|
+
const err = new Error(`Newsletter status server ACK timed out for ${messageId}${suffix}`); err.data = { messageId, callbacks }; throw err
|
|
267
|
+
}
|
|
268
|
+
const ack = parseNewsletterStatusAck(response, { jid, messageId })
|
|
269
|
+
if (ack.error) {
|
|
270
|
+
const details = [ack.applicationError !== undefined ? `application_error=${ack.applicationError}` : undefined, ack.backoff !== undefined ? `backoff=${ack.backoff}` : undefined].filter(Boolean).join(', ')
|
|
271
|
+
const hint = ['403', '401', 'not-authorized', 'forbidden'].includes(ack.error) ? '; the channel may be missing the CHANNEL_STATUS_PRODUCER capability' : ''
|
|
272
|
+
const err = new Error(`Newsletter status rejected by server (${ack.error})${details ? `: ${details}` : ''}${hint}`); err.data = ack; throw err
|
|
273
|
+
}
|
|
274
|
+
return ack
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const buildNewsletterAdminProfileStatusMessage = (message) => {
|
|
278
|
+
if (!message || typeof message !== 'object') throw new TypeError('Newsletter status message is required')
|
|
279
|
+
return proto.Message.create({ newsletterAdminProfileStatusMessage: proto.Message.FutureProofMessage.create({ message }) })
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const resolveStatusPayload = ({ message, payload }) => {
|
|
283
|
+
if (payload !== undefined) { if (!(payload instanceof Uint8Array)) throw new TypeError('Newsletter status payload must be a Uint8Array'); return payload }
|
|
284
|
+
if (!message || typeof message !== 'object') throw new TypeError('Newsletter status message is required')
|
|
285
|
+
return encodeNewsletterMessage(message)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const buildMetaNode = ({ interactionType, parentServerId, responseServerId, aiContent }) => {
|
|
289
|
+
assertInteraction(interactionType)
|
|
290
|
+
const attrs = {}
|
|
291
|
+
if (interactionType) {
|
|
292
|
+
attrs.interaction_type = interactionType
|
|
293
|
+
if (interactionType === 'question_reshare') {
|
|
294
|
+
if (!isPresent(parentServerId)) throw new TypeError('question_reshare requires parentServerId')
|
|
295
|
+
if (!isPresent(responseServerId)) throw new TypeError('question_reshare requires responseServerId')
|
|
296
|
+
attrs.parent_server_id = toNewsletterServerId(parentServerId, 'parentServerId')
|
|
297
|
+
attrs.response_server_id = String(responseServerId)
|
|
298
|
+
} else if (interactionType === 'question_response' && isPresent(responseServerId)) {
|
|
299
|
+
attrs.response_server_id = String(responseServerId)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!interactionType && !aiContent) return undefined
|
|
303
|
+
return { tag: 'meta', attrs, content: aiContent ? [{ tag: 'ai_content', attrs: {}, content: undefined }] : undefined }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const buildNewsletterStatusNode = ({ jid, message, payload, messageId, mediaType, mediaId, mediaHandle, parentServerId, responseServerId, interactionType, aiContent }) => {
|
|
307
|
+
assertNewsletterJid(jid)
|
|
308
|
+
if (!messageId) throw new TypeError('Newsletter status messageId is required')
|
|
309
|
+
assertMediaType(mediaType)
|
|
310
|
+
assertInteraction(interactionType)
|
|
311
|
+
const handle = isPresent(mediaHandle) ? mediaHandle : mediaId
|
|
312
|
+
if (isPresent(handle) && !mediaType) throw new TypeError('mediaId requires a media newsletter status')
|
|
313
|
+
if (mediaType && !isPresent(handle)) throw new TypeError('Native newsletter status media requires the media handle returned by the newsletter upload')
|
|
314
|
+
if (interactionType === 'question_reshare' && !mediaType) throw new TypeError('question_reshare requires media')
|
|
315
|
+
if (interactionType === 'question_response') {
|
|
316
|
+
if (mediaType) throw new TypeError('question_response is published as a text status')
|
|
317
|
+
if (!isPresent(parentServerId)) throw new TypeError('question_response requires parentServerId')
|
|
318
|
+
}
|
|
319
|
+
const attrs = { to: jid, id: messageId, type: mediaType ? 'media' : 'text' }
|
|
320
|
+
if (mediaType) attrs.media_id = String(handle)
|
|
321
|
+
if (interactionType === 'question_response') attrs.server_id = toNewsletterServerId(parentServerId, 'parentServerId')
|
|
322
|
+
const content = [{ tag: 'plaintext', attrs: mediaType ? { mediatype: mediaType } : {}, content: resolveStatusPayload({ message, payload }) }]
|
|
323
|
+
const metaNode = buildMetaNode({ interactionType, parentServerId, responseServerId, aiContent })
|
|
324
|
+
if (metaNode) content.push(metaNode)
|
|
325
|
+
return { tag: 'status', attrs, content }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export const buildNewsletterStatusReactionNode = ({ jid, messageId, parentServerId, reaction }) => {
|
|
329
|
+
assertNewsletterJid(jid)
|
|
330
|
+
if (!messageId) throw new TypeError('Newsletter status reaction messageId is required')
|
|
331
|
+
if (!isPresent(parentServerId)) throw new TypeError('Newsletter status reaction requires parentServerId')
|
|
332
|
+
const isRevoke = !isPresent(reaction)
|
|
333
|
+
const attrs = { to: jid, id: messageId, server_id: toNewsletterServerId(parentServerId, 'parentServerId'), type: 'reaction' }
|
|
334
|
+
if (isRevoke) attrs.edit = STATUS_EDIT_REACTION_REVOKE
|
|
335
|
+
return { tag: 'status', attrs, content: [{ tag: 'reaction', attrs: isRevoke ? {} : { code: reaction }, content: undefined }] }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const buildNewsletterStatusRevokeNode = ({ jid, statusId }) => {
|
|
339
|
+
assertNewsletterJid(jid)
|
|
340
|
+
if (!statusId) throw new TypeError('Newsletter status revoke requires the status id')
|
|
341
|
+
return { tag: 'status', attrs: { to: jid, id: statusId, type: 'text', edit: STATUS_EDIT_ADMIN_REVOKE }, content: [{ tag: 'plaintext', attrs: {}, content: undefined }] }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── Socket ───────────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
export const makeNewsletterSocket = (config) => {
|
|
347
|
+
const sock = makeGroupsSocket(config)
|
|
348
|
+
const { authState, signalRepository } = sock
|
|
349
|
+
|
|
350
|
+
const parseFetchedUpdates = async (node, type) => {
|
|
351
|
+
let child
|
|
352
|
+
if (type === 'messages') {
|
|
353
|
+
child = getBinaryNodeChild(node, 'messages')
|
|
354
|
+
} else {
|
|
355
|
+
const parent = getBinaryNodeChild(node, 'message_updates')
|
|
356
|
+
child = getBinaryNodeChild(parent, 'messages')
|
|
357
|
+
}
|
|
358
|
+
return Promise.all(getAllBinaryNodeChildren(child).map(async (messageNode) => {
|
|
359
|
+
messageNode.attrs.from = child?.attrs.jid
|
|
360
|
+
const views = parseInt(getBinaryNodeChild(messageNode, 'views_count')?.attrs?.count || '0')
|
|
361
|
+
const reactions = getBinaryNodeChildren(getBinaryNodeChild(messageNode, 'reactions'), 'reaction').map(({ attrs }) => ({ count: +attrs.count, code: attrs.code }))
|
|
362
|
+
const data = { server_id: messageNode.attrs.server_id, views, reactions }
|
|
363
|
+
if (type === 'messages') {
|
|
364
|
+
const { fullMessage: message, decrypt } = await decryptMessageNode(messageNode, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, config.logger)
|
|
365
|
+
await decrypt()
|
|
366
|
+
data.message = message
|
|
367
|
+
}
|
|
368
|
+
return data
|
|
369
|
+
}))
|
|
370
|
+
}
|
|
371
|
+
const { query, generateMessageTag } = sock
|
|
372
|
+
|
|
373
|
+
const STATUS_SERVER_ACK = proto.WebMessageInfo.Status.SERVER_ACK
|
|
374
|
+
|
|
375
|
+
// ── Core WMex query executor ──────────────────────────────────────────────
|
|
376
|
+
const wmex = (variables, queryId, dataPath) =>
|
|
377
|
+
genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
|
|
378
|
+
|
|
379
|
+
// ── Newsletter metadata helpers ───────────────────────────────────────────
|
|
380
|
+
const newsletterUpdate = (jid, updates) =>
|
|
381
|
+
wmex({ newsletter_id: jid, updates: { settings: null, ...updates } }, QueryIds.UPDATE_METADATA, 'xwa2_newsletter_update')
|
|
382
|
+
|
|
383
|
+
const newsletterUserSetting = async (jid, type, muted) => {
|
|
384
|
+
const res = await wmex(toNewsletterUserSettingInput(jid, type, muted), QueryIds.UPDATE_USER_SETTING, XWAPaths.xwa2_newsletter_update_user_setting)
|
|
385
|
+
return { id: res?.id ?? jid, state: res?.state?.type }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── Status node sender ────────────────────────────────────────────────────
|
|
389
|
+
const collectStatusCallbacks = () => {
|
|
390
|
+
const frames = [], listeners = []
|
|
391
|
+
for (const event of STATUS_CALLBACK_EVENTS) {
|
|
392
|
+
const listener = (node) => { if (frames.length < 40) frames.push({ event, tag: node?.tag, attrs: node?.attrs || {}, children: Array.isArray(node?.content) ? node.content.map(c => ({ tag: c?.tag, attrs: c?.attrs || {} })) : [] }) }
|
|
393
|
+
sock.ws.on(event, listener)
|
|
394
|
+
listeners.push([event, listener])
|
|
395
|
+
}
|
|
396
|
+
return { frames, stop: () => { for (const [event, listener] of listeners) sock.ws.off(event, listener) } }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const sendStatusNode = async (node, { jid, messageId, timeoutMs }) => {
|
|
400
|
+
const diagnostics = collectStatusCallbacks()
|
|
401
|
+
const responsePromise = sock.waitForMessage(messageId, timeoutMs)
|
|
402
|
+
try { await sock.sendNode(node); const response = await responsePromise; return assertStatusServerResponse(response, { jid, messageId, callbacks: diagnostics.frames }) }
|
|
403
|
+
finally { diagnostics.stop() }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ── My add-ons fetcher ────────────────────────────────────────────────────
|
|
407
|
+
const fetchMyAddOns = async (options, type) => {
|
|
408
|
+
const attrs = { limit: String(options.limit ?? 100) }
|
|
409
|
+
if (type) attrs.type = type
|
|
410
|
+
if (options.jid) attrs.jid = options.jid
|
|
411
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: S_WHATSAPP_NET }, content: [{ tag: 'my_addons', attrs, content: undefined }] })
|
|
412
|
+
const addOns = getBinaryNodeChild(result, 'my_addons')
|
|
413
|
+
if (!addOns) return []
|
|
414
|
+
return getBinaryNodeChildren(addOns, 'messages').map((group) => ({
|
|
415
|
+
jid: group.attrs?.jid,
|
|
416
|
+
messages: getBinaryNodeChildren(group, 'message').map((entry) => {
|
|
417
|
+
const reaction = getBinaryNodeChild(entry, 'reaction')
|
|
418
|
+
const votes = getBinaryNodeChild(entry, 'votes')
|
|
419
|
+
return {
|
|
420
|
+
serverId: entry.attrs?.server_id ? Number(entry.attrs.server_id) : undefined,
|
|
421
|
+
reaction: reaction ? { code: reaction.attrs?.code, t: reaction.attrs?.t ? Number(reaction.attrs.t) : undefined } : undefined,
|
|
422
|
+
pollVote: votes ? { t: votes.attrs?.t ? Number(votes.attrs.t) : undefined, hashes: getBinaryNodeChildren(votes, 'vote').map(v => Buffer.from(v.content ?? []).toString('hex')) } : undefined
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
}))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// ── Auto-follow ───────────────────────────────────────────────────────────
|
|
429
|
+
const performNewsletterFollow = async (jid) => {
|
|
430
|
+
try {
|
|
431
|
+
await wmex({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2)
|
|
432
|
+
config.logger?.debug?.(`✅ Followed newsletter: ${jid}`)
|
|
433
|
+
await wait(500)
|
|
434
|
+
try { await newsletterUserSetting(jid, 'ADMIN_NOTIFICATIONS', false) } catch (_) { }
|
|
435
|
+
return true
|
|
436
|
+
} catch (err) { config.logger?.trace?.(`Newsletter follow attempt failed: ${err.message}`); return false }
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let autoFollowInterval = null
|
|
440
|
+
sock.ev.on('connection.update', async ({ connection }) => {
|
|
441
|
+
if (connection === 'open') {
|
|
442
|
+
if (config?.autoFollowNewsletter === false) return
|
|
443
|
+
if (autoFollowInterval) { clearInterval(autoFollowInterval); autoFollowInterval = null }
|
|
444
|
+
await wait(AUTO_FOLLOW_CONNECT_DELAY)
|
|
445
|
+
config.logger?.info?.('Attempting initial auto-follow...')
|
|
446
|
+
try { if (await performNewsletterFollow(AUTO_FOLLOW_JID)) config.logger?.info?.(`✅ Auto-followed newsletter: ${AUTO_FOLLOW_JID}`) } catch (err) { config.logger?.debug?.(`Initial auto-follow failed: ${err.message}`) }
|
|
447
|
+
autoFollowInterval = setInterval(async () => {
|
|
448
|
+
try { await performNewsletterFollow(AUTO_FOLLOW_JID) } catch (err) { config.logger?.trace?.(`Periodic auto-follow failed: ${err.message}`) }
|
|
449
|
+
}, AUTO_FOLLOW_INTERVAL_MS)
|
|
450
|
+
} else if (connection === 'close') {
|
|
451
|
+
if (autoFollowInterval) { clearInterval(autoFollowInterval); autoFollowInterval = null }
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
456
|
+
return {
|
|
457
|
+
...sock,
|
|
458
|
+
executeWMexQuery: wmex,
|
|
459
|
+
|
|
460
|
+
// ── Channel CRUD ──────────────────────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
newsletterCreate: async (name, description) => {
|
|
463
|
+
const res = await wmex({ input: { name, description: description ?? null } }, QueryIds.CREATE, XWAPaths.xwa2_newsletter_create)
|
|
464
|
+
return parseNewsletterCreateResponse(res)
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
newsletterUpdate,
|
|
468
|
+
|
|
469
|
+
newsletterMetadata: async (type, key, options = {}) => {
|
|
470
|
+
const variables = {
|
|
471
|
+
fetch_creation_time: options.fetchCreationTime ?? true,
|
|
472
|
+
fetch_full_image: options.fetchFullImage ?? true,
|
|
473
|
+
fetch_viewer_metadata: options.fetchViewerMetadata ?? true,
|
|
474
|
+
fetch_pinned_messages: options.fetchPinnedMessages ?? false,
|
|
475
|
+
fetch_status_metadata: options.fetchStatusMetadata ?? false,
|
|
476
|
+
fetch_wamo_sub: options.fetchWamoSub ?? false,
|
|
477
|
+
input: { key, type: type.toUpperCase() }
|
|
478
|
+
}
|
|
479
|
+
return parseNewsletterMetadata(await wmex(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata))
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
newsletterDelete: (jid) => wmex({ newsletter_id: jid }, QueryIds.DELETE, XWAPaths.xwa2_newsletter_delete_v2),
|
|
483
|
+
newsletterChangeOwner: (jid, newOwner) => wmex({ newsletter_id: jid, user_id: newOwner }, QueryIds.CHANGE_OWNER, XWAPaths.xwa2_newsletter_change_owner),
|
|
484
|
+
newsletterDemote: (jid, userJid) => wmex({ newsletter_id: jid, user_id: userJid }, QueryIds.DEMOTE, XWAPaths.xwa2_newsletter_demote),
|
|
485
|
+
|
|
486
|
+
// ── Subscription management ───────────────────────────────────────────
|
|
487
|
+
|
|
488
|
+
newsletterSubscribed: () => wmex({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed),
|
|
489
|
+
newsletterFollow: (jid) => wmex({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2),
|
|
490
|
+
newsletterUnfollow: (jid) => wmex({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_leave_v2),
|
|
491
|
+
newsletterMute: (jid) => wmex({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2),
|
|
492
|
+
newsletterUnmute: (jid) => wmex({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2),
|
|
493
|
+
newsletterUpdateUserSetting: (jid, t, v) => newsletterUserSetting(jid, t, v),
|
|
494
|
+
subscribeNewsletterUpdates: async (jid) => {
|
|
495
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid }, content: [{ tag: 'live_updates', attrs: {}, content: [] }] })
|
|
496
|
+
const node = getBinaryNodeChild(result, 'live_updates')
|
|
497
|
+
return node?.attrs?.duration ? { duration: node.attrs.duration } : null
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
// ── Channel settings ──────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
newsletterUpdateName: (jid, name) => newsletterUpdate(jid, { name }),
|
|
503
|
+
newsletterUpdateDescription: (jid, description) => newsletterUpdate(jid, { description }),
|
|
504
|
+
newsletterRemovePicture: (jid) => newsletterUpdate(jid, { picture: '' }),
|
|
505
|
+
|
|
506
|
+
newsletterUpdateReactions: async (jid, setting) => {
|
|
507
|
+
const value = String(setting ?? '').toUpperCase()
|
|
508
|
+
if (!REACTION_SETTINGS.has(value)) throw new Boom(`reaction setting must be one of ${[...REACTION_SETTINGS].join(', ')}`, { statusCode: 400, data: { setting } })
|
|
509
|
+
return newsletterUpdate(jid, { settings: { reaction_codes: { value } } })
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
newsletterUpdatePicture: async (jid, content) => {
|
|
513
|
+
const { img } = await generateProfilePicture(content)
|
|
514
|
+
return newsletterUpdate(jid, { picture: img.toString('base64') })
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
// ── Admin management ──────────────────────────────────────────────────
|
|
518
|
+
|
|
519
|
+
newsletterAdminCount: async (jid) => {
|
|
520
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.ADMIN_COUNT, XWAPaths.xwa2_newsletter_admin_count)
|
|
521
|
+
return res.admin_count
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
newsletterAdminInfo: async (jid) => {
|
|
525
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.ADMIN_INFO, XWAPaths.xwa2_newsletter_admin_info)
|
|
526
|
+
return {
|
|
527
|
+
id: res?.id ?? jid,
|
|
528
|
+
adminCount: res?.admin_count ?? 0,
|
|
529
|
+
adminProfile: res?.admin_profile ? { id: res.admin_profile.id, name: res.admin_profile.name, picture: res.admin_profile.picture ? { id: res.admin_profile.picture.id, directPath: res.admin_profile.picture.direct_path } : undefined } : undefined,
|
|
530
|
+
adminProfilesEnabled: res?.admin_settings?.admin_profiles_enabled ?? false
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
newsletterAdminCapabilities: async (jid) => {
|
|
535
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.ADMIN_CAPABILITIES, XWAPaths.xwa2_newsletter_admin_capabilities)
|
|
536
|
+
return res?.capabilities ?? []
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
newsletterCanPostStatus: async (jid) => {
|
|
540
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.ADMIN_CAPABILITIES, XWAPaths.xwa2_newsletter_admin_capabilities)
|
|
541
|
+
const list = res?.capabilities ?? []
|
|
542
|
+
return { canPost: list.includes('CHANNEL_STATUS_PRODUCER'), canPostMusic: list.includes('CHANNEL_STATUS_MUSIC'), capabilities: list }
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
newsletterCreateAdminInvite: (jid, userJid) => wmex({ newsletter_id: jid, user_id: userJid }, QueryIds.CREATE_ADMIN_INVITE, XWAPaths.xwa2_newsletter_admin_invite_create),
|
|
546
|
+
newsletterRevokeAdminInvite: (jid, userJid) => wmex({ newsletter_id: jid, user_id: userJid }, QueryIds.REVOKE_ADMIN_INVITE, XWAPaths.xwa2_newsletter_admin_invite_revoke),
|
|
547
|
+
newsletterAcceptAdminInvite: (jid) => wmex({ newsletter_id: jid }, QueryIds.ACCEPT_ADMIN_INVITE, XWAPaths.xwa2_newsletter_admin_invite_accept),
|
|
548
|
+
|
|
549
|
+
newsletterPendingAdminInvites: async (jid) => {
|
|
550
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.PENDING_ADMIN_INVITES, XWAPaths.pending_admin_invites)
|
|
551
|
+
return (res?.pending_admin_invites ?? []).map(invite => ({ id: invite?.user?.id, phoneNumber: invite?.user?.pn }))
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// ── Followers & subscribers ───────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
newsletterFollowers: (jid, opts = {}) => wmex({ input: { newsletter_id: jid, count: opts.count ?? 100 } }, QueryIds.FOLLOWERS, XWAPaths.xwa2_newsletter_followers),
|
|
557
|
+
|
|
558
|
+
newsletterSubscribers: async (jid) => {
|
|
559
|
+
const res = await wmex({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_metadata)
|
|
560
|
+
return (res?.subscribers?.edges ?? res?.followers?.edges ?? []).map(edge => ({ id: edge?.node?.id, phoneNumber: edge?.node?.pn, displayName: edge?.node?.display_name, username: edge?.node?.username_info?.username, role: edge?.role, followTime: edge?.follow_time }))
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
// ── Messages & reactions ──────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
newsletterReactMessage: async (jid, serverId, reaction) => {
|
|
566
|
+
await query({ tag: 'message', attrs: { to: jid, ...(reaction ? {} : { edit: '7' }), type: 'reaction', server_id: serverId, id: generateMessageTag() }, content: [{ tag: 'reaction', attrs: reaction ? { code: reaction } : {} }] })
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
newsletterBulkReactions: async (jid, serverId, emojis, count = 1, options = {}) => {
|
|
570
|
+
assertNewsletterJid(jid)
|
|
571
|
+
const { fake = false, delayMs = 100, mode = 'repeat' } = options
|
|
572
|
+
const max = Math.min(count, 1000)
|
|
573
|
+
const emojiList = Array.isArray(emojis) ? emojis : [emojis]
|
|
574
|
+
const total = mode === 'each' ? max * emojiList.length : max
|
|
575
|
+
const pickEmoji = (i) => mode === 'random' ? emojiList[Math.floor(Math.random() * emojiList.length)] : mode === 'each' ? emojiList[Math.floor(i / max)] : emojiList[i % emojiList.length]
|
|
576
|
+
if (fake) { const events = []; for (let i = 0; i < total; i++) events.push({ key: { remoteJid: jid, fromMe: false, id: String(serverId), participant: `${Math.floor(Math.random() * 999999999)}${i}@s.whatsapp.net` }, reaction: { key: { remoteJid: jid, fromMe: false, id: String(serverId) }, text: pickEmoji(i), senderTimestampMs: Date.now() + i } }); sock.ev.emit('messages.reaction', events); return { sent: events.length, fake, jid } }
|
|
577
|
+
let sent = 0
|
|
578
|
+
for (let i = 0; i < total; i++) { try { await sock.newsletterReactMessage(jid, serverId, pickEmoji(i)); sent++; if (delayMs > 0) await wait(delayMs) } catch (_) { } }
|
|
579
|
+
return { sent, fake, jid }
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
newsletterFetchMessages: async (jid, count = 20, since, after) => {
|
|
583
|
+
const attrs = { count: String(count) }
|
|
584
|
+
if (since !== undefined) attrs.since = String(since)
|
|
585
|
+
if (after !== undefined) attrs.after = String(after)
|
|
586
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid }, content: [{ tag: 'message_updates', attrs, content: undefined }] })
|
|
587
|
+
const wrapper = getBinaryNodeChild(result, 'message_updates') ?? getBinaryNodeChild(result, 'messages')
|
|
588
|
+
if (!wrapper) return []
|
|
589
|
+
const messagesNode = getBinaryNodeChild(wrapper, 'messages') ?? wrapper
|
|
590
|
+
return decodeNewsletterMessageNodes(messagesNode, messagesNode.attrs?.jid ?? result.attrs?.from ?? jid, config.logger)
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
newsletterFetchUpdates: async (jid, count, after, since) => {
|
|
594
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid }, content: [{ tag: 'message_updates', attrs: { count: count.toString(), after: after?.toString() || '100', since: since?.toString() || '0' } }] })
|
|
595
|
+
return parseFetchedUpdates(result, 'updates')
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
newsletterFetchMessageUpdates: async (jid, options = {}) => {
|
|
599
|
+
const { count = 20, since, before, after } = options
|
|
600
|
+
const attrs = { count: String(count) }
|
|
601
|
+
if (since !== undefined) attrs.since = String(since)
|
|
602
|
+
if (before !== undefined) attrs.before = String(before)
|
|
603
|
+
else if (after !== undefined) attrs.after = String(after)
|
|
604
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid }, content: [{ tag: 'message_updates', attrs, content: undefined }] })
|
|
605
|
+
const updates = getBinaryNodeChild(result, 'message_updates')
|
|
606
|
+
const messages = updates && getBinaryNodeChild(updates, 'messages')
|
|
607
|
+
return { jid: messages?.attrs?.jid ?? jid, messages: messages ? decodeNewsletterMessageNodes(messages, messages.attrs?.jid ?? jid) : [] }
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
newsletterPinMessages: (jid, serverIds) => wmex({ newsletter_id: jid, input: { message_ids: toNewsletterServerIds(serverIds) } }, QueryIds.PIN_MESSAGES, XWAPaths.xwa2_newsletter_pin_messages),
|
|
611
|
+
newsletterUnpinMessages: (jid, serverIds) => wmex({ newsletter_id: jid, input: { message_ids: toNewsletterServerIds(serverIds) } }, QueryIds.UNPIN_MESSAGES, XWAPaths.xwa2_newsletter_unpin_messages),
|
|
612
|
+
|
|
613
|
+
newsletterLabelAiContent: (jid, serverId, messageType = 'MESSAGE') => wmex({ newsletter_id: jid, server_id: String(serverId), message_type: messageType }, QueryIds.LABEL_AI_CONTENT, XWAPaths.xwa2_newsletter_label_ai_content),
|
|
614
|
+
newsletterLabelPaidPartnership: (jid, serverId, messageType = 'MESSAGE') => wmex({ newsletter_id: jid, server_id: String(serverId), message_type: messageType }, QueryIds.PAID_PARTNERSHIP_LABEL, XWAPaths.xwa2_newsletter_label_paid_partnership),
|
|
615
|
+
|
|
616
|
+
newsletterSendPollVote: async (jid, parentServerId, options) => {
|
|
617
|
+
const names = Array.isArray(options) ? options : [options]
|
|
618
|
+
const votes = names.map(name => ({ tag: 'vote', attrs: {}, content: createHash('sha256').update(String(name), 'utf-8').digest() }))
|
|
619
|
+
const messageId = generateMessageTag()
|
|
620
|
+
await query({ tag: 'message', attrs: { to: jid, id: messageId, type: 'poll', server_id: String(parentServerId) }, content: [{ tag: 'meta', attrs: { polltype: 'vote' } }, { tag: 'votes', attrs: {}, content: votes }] })
|
|
621
|
+
return { id: messageId }
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
// ── Reactions & poll voters ───────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
newsletterReactionSenders: (jid, serverId) => wmex({ input: { id: jid, server_id: String(serverId) } }, QueryIds.REACTION_SENDER_LIST, XWAPaths.xwa2_newsletters_reaction_sender_list),
|
|
627
|
+
newsletterPollVoters: (jid, serverId, opts = {}) => wmex({ input: { newsletter_id: jid, server_id: String(serverId), limit: opts.limit ?? 100, vote_hash: opts.voteHash } }, QueryIds.POLL_VOTERS, XWAPaths.voter_list),
|
|
628
|
+
|
|
629
|
+
// ── Question / response interactions ──────────────────────────────────
|
|
630
|
+
|
|
631
|
+
newsletterQuestionResponses: async (jid, serverId, options = {}) => {
|
|
632
|
+
const { count = 20, before, filter, searchText } = options
|
|
633
|
+
const attrs = { server_id: String(serverId), count: String(count) }
|
|
634
|
+
if (before !== undefined) attrs.before = String(before)
|
|
635
|
+
const content = []
|
|
636
|
+
if (filter) content.push({ tag: 'filters', attrs: {}, content: [{ tag: filter, attrs: {}, content: undefined }] })
|
|
637
|
+
if (searchText) content.push({ tag: 'search', attrs: { text: searchText }, content: undefined })
|
|
638
|
+
const result = await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid }, content: [{ tag: 'question_responses', attrs, content: content.length ? content : undefined }] })
|
|
639
|
+
const responses = getBinaryNodeChild(result, 'question_responses')
|
|
640
|
+
if (!responses) return { jid, serverId: Number(serverId), responses: [] }
|
|
641
|
+
return {
|
|
642
|
+
jid: result.attrs?.from ?? jid,
|
|
643
|
+
serverId: responses.attrs?.server_id ? Number(responses.attrs.server_id) : Number(serverId),
|
|
644
|
+
responses: getBinaryNodeChildren(responses, 'question_response').map((entry) => {
|
|
645
|
+
const messageNode = getBinaryNodeChild(entry, 'message')
|
|
646
|
+
const sender = getBinaryNodeChild(entry, 'sender')
|
|
647
|
+
const picture = sender && getBinaryNodeChild(sender, 'picture')
|
|
648
|
+
const flags = getBinaryNodeChild(entry, 'flags')
|
|
649
|
+
const plaintext = messageNode && getBinaryNodeChild(messageNode, 'plaintext')
|
|
650
|
+
return {
|
|
651
|
+
id: messageNode?.attrs?.id,
|
|
652
|
+
t: messageNode?.attrs?.t ? Number(messageNode.attrs.t) : undefined,
|
|
653
|
+
isSender: messageNode?.attrs?.is_sender === 'true',
|
|
654
|
+
responseServerId: messageNode?.attrs?.response_server_id,
|
|
655
|
+
sender: { lid: sender?.attrs?.lid, notifyName: sender?.attrs?.notify_name, pictureDirectPath: picture?.attrs?.direct_path },
|
|
656
|
+
replied: flags ? !!getBinaryNodeChild(flags, 'replied') : false,
|
|
657
|
+
starred: flags ? !!getBinaryNodeChild(flags, 'starred') : false,
|
|
658
|
+
message: decodeNewsletterPlaintext(plaintext)
|
|
659
|
+
}
|
|
660
|
+
})
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
newsletterQuestionResponseState: (jid, serverId, responseServerId, state) =>
|
|
665
|
+
wmex({ newsletter_id: jid, server_id: String(serverId), response_server_id: String(responseServerId), state }, QueryIds.QUESTION_RESPONSE_STATE, XWAPaths.xwa2_newsletter_question_response_state_update),
|
|
666
|
+
|
|
667
|
+
// ── Analytics & insights ──────────────────────────────────────────────
|
|
668
|
+
|
|
669
|
+
newsletterInsights: (jid, opts = {}) => wmex({ input: { newsletter_id: jid, metrics: opts.metrics ?? ['NET_FOLLOWS', 'UNFOLLOWS'] } }, QueryIds.INSIGHTS, XWAPaths.xwa2_newsletter_admin_insights),
|
|
670
|
+
|
|
671
|
+
// ── Enforcements & reports ────────────────────────────────────────────
|
|
672
|
+
|
|
673
|
+
newsletterEnforcements: async (jid, locale = 'en_US') => {
|
|
674
|
+
const res = await wmex({ newsletter_id: jid, locale }, QueryIds.ENFORCEMENTS, XWAPaths.xwa2_channel_enforcements)
|
|
675
|
+
const mapBase = (entry) => ({
|
|
676
|
+
enforcementId: entry?.enforcement_id,
|
|
677
|
+
createdAt: entry?.enforcement_creation_time ? Number(entry.enforcement_creation_time) : undefined,
|
|
678
|
+
violationCategory: entry?.enforcement_violation_category,
|
|
679
|
+
source: entry?.enforcement_source,
|
|
680
|
+
appealState: entry?.appeal_state,
|
|
681
|
+
appealCreatedAt: entry?.appeal_creation_time ? Number(entry.appeal_creation_time) : undefined,
|
|
682
|
+
appealReasonOptions: (entry?.appeal_reason_options ?? []).map(o => ({ reason: o?.reason, label: o?.label })),
|
|
683
|
+
appealFormUrl: entry?.enforcement_extra_data?.ip_violation_report_data?.appeal_form_url,
|
|
684
|
+
policy: entry?.enforcement_policy_information ? { headline: entry.enforcement_policy_information.headline, subtitle: entry.enforcement_policy_information.subtitle, overview: entry.enforcement_policy_information.overview, explanation: entry.enforcement_policy_information.explanation, adminDisclaimer: entry.enforcement_policy_information.admin_disclaimer } : undefined
|
|
685
|
+
})
|
|
686
|
+
const nested = (list) => (list ?? []).map(entry => mapBase(entry?.base_enforcement_data ?? entry))
|
|
687
|
+
return { adminProfiles: (res?.admin_profiles ?? []).map(mapBase), profilePictureDeletions: (res?.profile_picture_deletions ?? []).map(mapBase), suspensions: (res?.suspensions ?? []).map(mapBase), violatingMessages: nested(res?.violating_messages), geoSuspensions: nested(res?.geosuspensions) }
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
newsletterReports: (locale = 'en_US') => wmex({ locale }, QueryIds.CHANNEL_REPORTS, XWAPaths.xwa2_channels_reports),
|
|
691
|
+
newsletterAppealReport: (reportId, reason) => wmex({ report_id: String(reportId), reason }, QueryIds.CREATE_REPORT_APPEAL, XWAPaths.xwa2_create_channel_report_appeal_v2),
|
|
692
|
+
|
|
693
|
+
// ── Directory & discovery ─────────────────────────────────────────────
|
|
694
|
+
|
|
695
|
+
newsletterDirectoryList: (opts = {}) => wmex({ fetch_status_metadata: opts.fetchStatusMetadata ?? false, input: { view: opts.view ?? 'RECOMMENDED', filters: { country_codes: opts.countryCodes ?? [], categories: opts.categories ?? [] }, limit: opts.limit ?? 20, start_cursor: opts.cursorToken } }, QueryIds.DIRECTORY_LIST, XWAPaths.xwa2_newsletters_directory_list),
|
|
696
|
+
newsletterDirectorySearch: (text, opts = {}) => wmex({ fetch_status_metadata: opts.fetchStatusMetadata ?? false, input: { search_text: text, categories: opts.categories ?? [], limit: opts.limit ?? 20, start_cursor: opts.cursorToken } }, QueryIds.DIRECTORY_SEARCH, XWAPaths.xwa2_newsletters_directory_search),
|
|
697
|
+
newsletterDirectoryCategories: (opts = {}) => wmex({ fetch_status_metadata: opts.fetchStatusMetadata ?? false, input: { categories: opts.categories ?? [], country_code: opts.countryCode || undefined, per_category_limit: opts.perCategoryLimit ?? 10 } }, QueryIds.DIRECTORY_CATEGORIES, XWAPaths.xwa2_newsletters_directory_category_preview),
|
|
698
|
+
newsletterRecommended: (opts = {}) => wmex({ fetch_status_metadata: opts.fetchStatusMetadata ?? false, input: { limit: opts.limit ?? 20, country_codes: opts.countryCodes ?? [] } }, QueryIds.RECOMMENDED, XWAPaths.xwa2_newsletters_recommended),
|
|
699
|
+
newsletterSimilar: (jid, opts = {}) => wmex({ fetch_status_metadata: opts.fetchStatusMetadata ?? false, input: { newsletter_id: jid, limit: opts.limit ?? 20, country_codes: opts.countryCodes ?? [] } }, QueryIds.SIMILAR, XWAPaths.xwa2_newsletters_similar),
|
|
700
|
+
|
|
701
|
+
// ── Add-ons ───────────────────────────────────────────────────────────
|
|
702
|
+
|
|
703
|
+
newsletterMyAddOns: (opts = {}) => fetchMyAddOns(opts, undefined),
|
|
704
|
+
newsletterStatusMyAddOns: (opts = {}) => fetchMyAddOns(opts, 'status'),
|
|
705
|
+
|
|
706
|
+
// ── Status publishing ─────────────────────────────────────────────────
|
|
707
|
+
|
|
708
|
+
newsletterSendStatus: async (jid, content, options = {}) => {
|
|
709
|
+
assertNewsletterJid(jid)
|
|
710
|
+
if (!content || typeof content !== 'object' || Array.isArray(content)) throw new TypeError('Newsletter status content must be an object')
|
|
711
|
+
const userJid = sock.authState?.creds?.me?.id
|
|
712
|
+
if (!userJid) throw new TypeError('Not authenticated')
|
|
713
|
+
const { mediaId, mediaHandle: requestedMediaHandle, parentServerId, responseServerId, interactionType: requestedInteractionType, aiContent, statusAttribution = true, messageId: requestedMessageId, ackTimeoutMs, resolveServerId, serverIdTimeoutMs, transport, ...messageOptions } = options
|
|
714
|
+
if (transport !== undefined) config.logger?.warn?.({ transport }, 'newsletter status "transport" option is obsolete')
|
|
715
|
+
let uploadedMediaHandle
|
|
716
|
+
const upload = async (...args) => {
|
|
717
|
+
const result = await sock.waUploadToServer(...args)
|
|
718
|
+
if (args[1]?.newsletter) uploadedMediaHandle = result?.handle ?? result?.media_id ?? result?.mediaId ?? result?.fbid ?? uploadedMediaHandle
|
|
719
|
+
return result
|
|
720
|
+
}
|
|
721
|
+
const preparedContent = prepareModernMessageContent(content)
|
|
722
|
+
const fullMsg = await generateWAMessage(jid, statusAttribution ? withNewsletterStatusAttribution(preparedContent) : preparedContent, { logger: config.logger, userJid, upload, mediaCache: config.mediaCache, options: config.options, ...messageOptions, messageId: requestedMessageId || generateMessageIDV2(userJid) })
|
|
723
|
+
const normalized = normalizeMessageContent(fullMsg.message)
|
|
724
|
+
const mediaType = getNewsletterStatusMediaType(fullMsg.message)
|
|
725
|
+
if (!mediaType && (normalized?.documentMessage || normalized?.stickerMessage)) throw new TypeError('Native newsletter status supports text, image, video, gif, and audio')
|
|
726
|
+
if (mediaType && !STATUS_WEB_MEDIA_TYPES.has(mediaType)) config.logger?.warn?.({ mediaType }, 'newsletter status media type not published by WA Web, server may reject it')
|
|
727
|
+
const interactionType = requestedInteractionType || (content.question ? 'question' : undefined)
|
|
728
|
+
if (interactionType === 'question' && !mediaType) config.logger?.warn?.('WA Web only publishes question statuses on top of media, a text question status may be rejected')
|
|
729
|
+
const node = buildNewsletterStatusNode({ jid, message: fullMsg.message, messageId: fullMsg.key.id, mediaType, mediaHandle: requestedMediaHandle ?? mediaId ?? uploadedMediaHandle, parentServerId, responseServerId, interactionType, aiContent })
|
|
730
|
+
const echo = resolveServerId === false ? null : waitForNewsletterStatusServerId(sock, { jid, messageId: fullMsg.key.id, timeoutMs: serverIdTimeoutMs ?? STATUS_SERVER_ID_TIMEOUT_MS })
|
|
731
|
+
let ack
|
|
732
|
+
try { ack = await sendStatusNode(node, { jid, messageId: fullMsg.key.id, timeoutMs: ackTimeoutMs ?? STATUS_ACK_TIMEOUT_MS }) }
|
|
733
|
+
catch (error) { echo?.cancel(); throw error }
|
|
734
|
+
const delivered = echo ? await echo : undefined
|
|
735
|
+
fullMsg.status = STATUS_SERVER_ACK
|
|
736
|
+
fullMsg.newsletterStatusServerId = ack.serverId ?? delivered?.serverId
|
|
737
|
+
fullMsg.newsletterStatusAck = ack
|
|
738
|
+
fullMsg.newsletterStatusResponse = ack.node
|
|
739
|
+
if (delivered) fullMsg.newsletterStatusDelivered = delivered.node
|
|
740
|
+
return fullMsg
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
newsletterReactStatus: async (jid, parentServerId, reaction, options = {}) => {
|
|
744
|
+
assertNewsletterJid(jid)
|
|
745
|
+
const userJid = sock.authState?.creds?.me?.id
|
|
746
|
+
if (!userJid) throw new TypeError('Not authenticated')
|
|
747
|
+
const messageId = options.messageId || generateMessageIDV2(userJid)
|
|
748
|
+
const node = buildNewsletterStatusReactionNode({ jid, messageId, parentServerId, reaction })
|
|
749
|
+
const ack = await sendStatusNode(node, { jid, messageId, timeoutMs: options.ackTimeoutMs ?? STATUS_ACK_TIMEOUT_MS })
|
|
750
|
+
return { key: { remoteJid: jid, fromMe: true, id: messageId }, status: STATUS_SERVER_ACK, newsletterStatusServerId: ack.serverId, newsletterStatusAck: ack, newsletterStatusResponse: ack.node }
|
|
751
|
+
},
|
|
752
|
+
|
|
753
|
+
newsletterRevokeStatus: async (jid, statusId, options = {}) => {
|
|
754
|
+
assertNewsletterJid(jid)
|
|
755
|
+
const userJid = sock.authState?.creds?.me?.id
|
|
756
|
+
if (!userJid) throw new TypeError('Not authenticated')
|
|
757
|
+
const node = buildNewsletterStatusRevokeNode({ jid, statusId })
|
|
758
|
+
const ack = await sendStatusNode(node, { jid, messageId: statusId, timeoutMs: options.ackTimeoutMs ?? STATUS_ACK_TIMEOUT_MS })
|
|
759
|
+
return { key: { remoteJid: jid, fromMe: true, id: statusId }, status: STATUS_SERVER_ACK, newsletterStatusAck: ack, newsletterStatusResponse: ack.node }
|
|
760
|
+
},
|
|
761
|
+
|
|
762
|
+
newsletterFetchStatus: async (jid, options = {}) => {
|
|
763
|
+
assertNewsletterJid(jid)
|
|
764
|
+
const { count = 20, before, after, viewRole } = options
|
|
765
|
+
const attrs = { type: 'jid', jid, count: String(count) }
|
|
766
|
+
if (isPresent(viewRole)) attrs.view_role = String(viewRole).toLowerCase()
|
|
767
|
+
if (isPresent(before)) attrs.before = toNewsletterServerId(before, 'before')
|
|
768
|
+
else if (isPresent(after)) attrs.after = toNewsletterServerId(after, 'after')
|
|
769
|
+
return parseNewsletterStatusesResponse(await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'newsletter', type: 'get' }, content: [{ tag: 'statuses', attrs, content: undefined }] }))
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
newsletterFetchStatusUpdates: async (jid, options = {}) => {
|
|
773
|
+
assertNewsletterJid(jid)
|
|
774
|
+
const { count = 20, since, before, after } = options
|
|
775
|
+
const attrs = { count: String(count) }
|
|
776
|
+
if (isPresent(since)) attrs.since = String(since)
|
|
777
|
+
if (isPresent(before)) attrs.before = toNewsletterServerId(before, 'before')
|
|
778
|
+
else if (isPresent(after)) attrs.after = toNewsletterServerId(after, 'after')
|
|
779
|
+
return parseNewsletterStatusUpdatesResponse(await query({ tag: 'iq', attrs: { to: jid, xmlns: 'newsletter', type: 'get' }, content: [{ tag: 'status_updates', attrs, content: undefined }] }))
|
|
780
|
+
},
|
|
781
|
+
}
|
|
752
782
|
}
|