@blckrose/baileys 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Defaults/index.js +12 -2
- package/lib/Socket/messages-send.js +1 -1
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/index.js +1 -0
- package/lib/Utils/companion-reg-client-utils.js +32 -0
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/messages-media.js +53 -38
- package/lib/Utils/messages.js +142 -143
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/rich-message-utils.js +392 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/WABinary/constants.js +73 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +22 -0
- package/lib/WAUSync/Protocols/index.js +2 -1
- package/package.json +2 -2
package/lib/Defaults/index.js
CHANGED
|
@@ -90,7 +90,8 @@ export const MEDIA_PATH_MAP = {
|
|
|
90
90
|
'md-app-state': '',
|
|
91
91
|
'md-msg-hist': '/mms/md-app-state',
|
|
92
92
|
'biz-cover-photo': '/pps/biz-cover-photo',
|
|
93
|
-
'sticker-pack': '/mms/sticker'
|
|
93
|
+
'sticker-pack': '/mms/sticker',
|
|
94
|
+
'thumbnail-sticker-pack': '/mms/sticker'
|
|
94
95
|
};
|
|
95
96
|
export const MEDIA_HKDF_KEY_MAPPING = {
|
|
96
97
|
audio: 'Audio',
|
|
@@ -112,7 +113,8 @@ export const MEDIA_HKDF_KEY_MAPPING = {
|
|
|
112
113
|
'payment-bg-image': 'Payment Background',
|
|
113
114
|
ptv: 'Video',
|
|
114
115
|
'biz-cover-photo': 'Image',
|
|
115
|
-
'sticker-pack': 'Sticker Pack'
|
|
116
|
+
'sticker-pack': 'Sticker Pack',
|
|
117
|
+
'thumbnail-sticker-pack': 'Sticker Pack'
|
|
116
118
|
};
|
|
117
119
|
export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
|
|
118
120
|
export const MIN_PREKEY_COUNT = 5;
|
|
@@ -132,4 +134,12 @@ export const TimeMs = {
|
|
|
132
134
|
Week: 7 * 24 * 60 * 60 * 1000
|
|
133
135
|
};
|
|
134
136
|
export const PHONENUMBER_MCC = phoneNumberMcc;
|
|
137
|
+
|
|
138
|
+
export const DONATE_URL = 'https://saweria.co/itsliaaa';
|
|
139
|
+
export const LEXER_REGEX = /(\/\/.*|\/\*[\s\S]*?\*\/|#.*)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[\s\S]*?`)|(\b[a-zA-Z_]\w*\b)(?=\s*\()|(\b[a-zA-Z_]\w*\b)|(\b\d+(?:\.\d+)?\b)|(\s+|[^\w\s]+)/g;
|
|
140
|
+
export const BOT_RENDERING_CONFIG_METADATA = {
|
|
141
|
+
bloksVersioningId: '0903aa5f7f47de66789d5f4c86d3bd6e05e4bc3ff85e454a9f907d5ed7fef97c',
|
|
142
|
+
pixelDensity: 2.75
|
|
143
|
+
};
|
|
144
|
+
|
|
135
145
|
//# sourceMappingURL=index.js.map
|
|
@@ -1501,7 +1501,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1501
1501
|
messageContent.contextInfo = { mentionedJid: options.mentionedJid };
|
|
1502
1502
|
}
|
|
1503
1503
|
const payload = proto.Message.InteractiveMessage.create(messageContent);
|
|
1504
|
-
const msg = generateWAMessageFromContent(jid, {
|
|
1504
|
+
const msg = generateWAMessageFromContent(jid, { interactiveMessage: payload }, { userJid, quoted: options?.quoted || null });
|
|
1505
1505
|
const additionalNodes = [{ tag: 'biz', attrs: {}, content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }] }];
|
|
1506
1506
|
await relayMessage(jid, msg.message, { messageId: msg.key.id, additionalNodes });
|
|
1507
1507
|
return msg;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export var CodeHighlightType;
|
|
2
|
+
(function (CodeHighlightType) {
|
|
3
|
+
CodeHighlightType[CodeHighlightType["DEFAULT"] = 0] = "DEFAULT";
|
|
4
|
+
CodeHighlightType[CodeHighlightType["KEYWORD"] = 1] = "KEYWORD";
|
|
5
|
+
CodeHighlightType[CodeHighlightType["METHOD"] = 2] = "METHOD";
|
|
6
|
+
CodeHighlightType[CodeHighlightType["STRING"] = 3] = "STRING";
|
|
7
|
+
CodeHighlightType[CodeHighlightType["NUMBER"] = 4] = "NUMBER";
|
|
8
|
+
CodeHighlightType[CodeHighlightType["COMMENT"] = 5] = "COMMENT";
|
|
9
|
+
})(CodeHighlightType || (CodeHighlightType = {}));
|
|
10
|
+
export var RichSubMessageType;
|
|
11
|
+
(function (RichSubMessageType) {
|
|
12
|
+
RichSubMessageType[RichSubMessageType["UNKNOWN"] = 0] = "UNKNOWN";
|
|
13
|
+
RichSubMessageType[RichSubMessageType["GRID_IMAGE"] = 1] = "GRID_IMAGE";
|
|
14
|
+
RichSubMessageType[RichSubMessageType["TEXT"] = 2] = "TEXT";
|
|
15
|
+
RichSubMessageType[RichSubMessageType["INLINE_IMAGE"] = 3] = "INLINE_IMAGE";
|
|
16
|
+
RichSubMessageType[RichSubMessageType["TABLE"] = 4] = "TABLE";
|
|
17
|
+
RichSubMessageType[RichSubMessageType["CODE"] = 5] = "CODE";
|
|
18
|
+
RichSubMessageType[RichSubMessageType["DYNAMIC"] = 6] = "DYNAMIC";
|
|
19
|
+
RichSubMessageType[RichSubMessageType["MAP"] = 7] = "MAP";
|
|
20
|
+
RichSubMessageType[RichSubMessageType["LATEX"] = 8] = "LATEX";
|
|
21
|
+
RichSubMessageType[RichSubMessageType["CONTENT_ITEMS"] = 9] = "CONTENT_ITEMS";
|
|
22
|
+
})(RichSubMessageType || (RichSubMessageType = {}));
|
package/lib/Types/index.js
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const CompanionWebClientType = {
|
|
2
|
+
UNKNOWN: 0,
|
|
3
|
+
CHROME: 1,
|
|
4
|
+
EDGE: 2,
|
|
5
|
+
FIREFOX: 3,
|
|
6
|
+
IE: 4,
|
|
7
|
+
OPERA: 5,
|
|
8
|
+
SAFARI: 6,
|
|
9
|
+
ELECTRON: 7,
|
|
10
|
+
UWP: 8,
|
|
11
|
+
OTHER_WEB_CLIENT: 9
|
|
12
|
+
};
|
|
13
|
+
const BROWSER_TO_COMPANION_WEB_CLIENT = {
|
|
14
|
+
Chrome: CompanionWebClientType.CHROME,
|
|
15
|
+
Edge: CompanionWebClientType.EDGE,
|
|
16
|
+
Firefox: CompanionWebClientType.FIREFOX,
|
|
17
|
+
IE: CompanionWebClientType.IE,
|
|
18
|
+
Opera: CompanionWebClientType.OPERA,
|
|
19
|
+
Safari: CompanionWebClientType.SAFARI
|
|
20
|
+
};
|
|
21
|
+
export const getCompanionWebClientType = ([os, browserName]) => {
|
|
22
|
+
if (browserName === 'Desktop') {
|
|
23
|
+
return os === 'Windows' ? CompanionWebClientType.UWP : CompanionWebClientType.ELECTRON;
|
|
24
|
+
}
|
|
25
|
+
return BROWSER_TO_COMPANION_WEB_CLIENT[browserName] || CompanionWebClientType.OTHER_WEB_CLIENT;
|
|
26
|
+
};
|
|
27
|
+
export const getCompanionPlatformId = (browser) => {
|
|
28
|
+
return getCompanionWebClientType(browser).toString();
|
|
29
|
+
};
|
|
30
|
+
export const buildPairingQRData = (ref, noiseKeyB64, identityKeyB64, advB64, browser) => {
|
|
31
|
+
return [ref, noiseKeyB64, identityKeyB64, advB64, getCompanionPlatformId(browser)].join(',');
|
|
32
|
+
};
|
package/lib/Utils/index.js
CHANGED
|
@@ -24,4 +24,8 @@ export * from './browser-utils.js';
|
|
|
24
24
|
export * from './identity-change-handler.js';
|
|
25
25
|
export * from './messages-newsletter.js';
|
|
26
26
|
export * from './resolve-jid.js';
|
|
27
|
+
export * from './rich-message-utils.js';
|
|
28
|
+
export * from './companion-reg-client-utils.js';
|
|
29
|
+
export * from './offline-node-processor.js';
|
|
30
|
+
export * from './stanza-ack.js';
|
|
27
31
|
//# sourceMappingURL=index.js.map
|
|
@@ -635,7 +635,8 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
|
|
|
635
635
|
const headers = {
|
|
636
636
|
...customHeaders,
|
|
637
637
|
'Content-Type': 'application/octet-stream',
|
|
638
|
-
Origin: DEFAULT_ORIGIN
|
|
638
|
+
'Origin': DEFAULT_ORIGIN,
|
|
639
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
639
640
|
};
|
|
640
641
|
// Collect buffer from Readable stream or read from file path
|
|
641
642
|
let reqBuffer;
|
|
@@ -654,50 +655,64 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
|
|
|
654
655
|
if (newsletter) {
|
|
655
656
|
mediaPath = mediaPath?.replace('/mms/', '/newsletter/newsletter-');
|
|
656
657
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
658
|
+
|
|
659
|
+
// Retry logic: try all hosts, then refresh connection and retry
|
|
660
|
+
const maxRetries = 2;
|
|
661
|
+
for (let attempt = 0; attempt < maxRetries && !urls; attempt++) {
|
|
662
|
+
if (attempt > 0) {
|
|
663
|
+
logger?.info?.(`Retrying upload (attempt ${attempt + 1}/${maxRetries})...`);
|
|
664
|
+
uploadInfo = await refreshMediaConn(true);
|
|
665
|
+
}
|
|
660
666
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
667
|
+
for (const { hostname, maxContentLengthBytes } of hosts) {
|
|
668
|
+
const auth = encodeURIComponent(uploadInfo.auth);
|
|
669
|
+
const url = `https://${hostname}${mediaPath}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
670
|
+
|
|
671
|
+
let result;
|
|
672
|
+
try {
|
|
673
|
+
// Upload buffer directly like wiley (avoids file I/O issues)
|
|
674
|
+
const axios = (await import('axios')).default;
|
|
675
|
+
const body = await axios.post(url, reqBuffer, {
|
|
676
|
+
...options,
|
|
677
|
+
headers: {
|
|
678
|
+
...headers,
|
|
679
|
+
'Accept': '*/*',
|
|
680
|
+
},
|
|
681
|
+
httpsAgent: fetchAgent,
|
|
682
|
+
timeout: timeoutMs || 60000, // Default 60s timeout
|
|
683
|
+
responseType: 'json',
|
|
684
|
+
maxBodyLength: Infinity,
|
|
685
|
+
maxContentLength: Infinity,
|
|
686
|
+
validateStatus: () => true, // Don't throw on any status code
|
|
687
|
+
});
|
|
688
|
+
result = body.data;
|
|
677
689
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
690
|
+
if (result?.url || result?.direct_path) {
|
|
691
|
+
urls = {
|
|
692
|
+
mediaUrl: result.url,
|
|
693
|
+
directPath: result.direct_path,
|
|
694
|
+
handle: result.handle,
|
|
695
|
+
meta_hmac: result.meta_hmac,
|
|
696
|
+
fbid: result.fbid,
|
|
697
|
+
ts: result.ts
|
|
698
|
+
};
|
|
699
|
+
logger?.info?.(`Upload successful to host: ${hostname}`);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
logger?.warn?.(`Upload to ${hostname} failed, reason: ${JSON.stringify(result)}`);
|
|
704
|
+
// Refresh media conn on failure
|
|
705
|
+
uploadInfo = await refreshMediaConn(true);
|
|
706
|
+
}
|
|
688
707
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
708
|
+
catch (error) {
|
|
709
|
+
const isLast = hostname === hosts[hosts.length - 1]?.hostname;
|
|
710
|
+
logger?.warn?.(`Error uploading to ${hostname} ${isLast ? '' : ', retrying...'}: ${error?.message}`);
|
|
692
711
|
}
|
|
693
712
|
}
|
|
694
|
-
catch (error) {
|
|
695
|
-
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
|
|
696
|
-
|
|
697
|
-
logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
698
|
-
}
|
|
699
713
|
}
|
|
700
714
|
if (!urls) {
|
|
715
|
+
logger?.error?.('Media upload failed on all hosts after all retries');
|
|
701
716
|
throw new Boom('Media upload failed on all hosts', { statusCode: 500 });
|
|
702
717
|
}
|
|
703
718
|
return urls;
|
package/lib/Utils/messages.js
CHANGED
|
@@ -20,104 +20,6 @@ const MIMETYPE_MAP = {
|
|
|
20
20
|
sticker: 'image/webp',
|
|
21
21
|
'product-catalog-image': 'image/jpeg'
|
|
22
22
|
};
|
|
23
|
-
/** Map ekstensi audio ke mimetype */
|
|
24
|
-
const AUDIO_MIMETYPE_MAP = {
|
|
25
|
-
ogg: 'audio/ogg; codecs=opus',
|
|
26
|
-
oga: 'audio/ogg; codecs=opus',
|
|
27
|
-
opus: 'audio/ogg; codecs=opus',
|
|
28
|
-
mp3: 'audio/mpeg',
|
|
29
|
-
mpeg: 'audio/mpeg',
|
|
30
|
-
mp4: 'audio/mp4',
|
|
31
|
-
m4a: 'audio/mp4',
|
|
32
|
-
aac: 'audio/aac',
|
|
33
|
-
wav: 'audio/wav',
|
|
34
|
-
wave: 'audio/wav',
|
|
35
|
-
flac: 'audio/flac',
|
|
36
|
-
webm: 'audio/webm',
|
|
37
|
-
amr: 'audio/amr',
|
|
38
|
-
'3gp': 'audio/3gpp',
|
|
39
|
-
'3gpp': 'audio/3gpp',
|
|
40
|
-
wma: 'audio/x-ms-wma',
|
|
41
|
-
caf: 'audio/x-caf',
|
|
42
|
-
aiff: 'audio/aiff',
|
|
43
|
-
aif: 'audio/aiff',
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Deteksi mimetype audio dari magic bytes buffer.
|
|
47
|
-
* Return null jika tidak dikenali.
|
|
48
|
-
*/
|
|
49
|
-
const detectAudioMimetypeFromBuffer = (buf) => {
|
|
50
|
-
if (!buf || buf.length < 12) return null;
|
|
51
|
-
// OGG
|
|
52
|
-
if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53)
|
|
53
|
-
return 'audio/ogg; codecs=opus';
|
|
54
|
-
// MP3 (ID3 tag atau sync bits)
|
|
55
|
-
if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) ||
|
|
56
|
-
(buf[0] === 0xFF && (buf[1] & 0xE0) === 0xE0))
|
|
57
|
-
return 'audio/mpeg';
|
|
58
|
-
// MP4/M4A (ftyp box)
|
|
59
|
-
if (buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70)
|
|
60
|
-
return 'audio/mp4';
|
|
61
|
-
// RIFF/WAV
|
|
62
|
-
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
|
|
63
|
-
buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45)
|
|
64
|
-
return 'audio/wav';
|
|
65
|
-
// FLAC
|
|
66
|
-
if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43)
|
|
67
|
-
return 'audio/flac';
|
|
68
|
-
// WEBM/MKV
|
|
69
|
-
if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3)
|
|
70
|
-
return 'audio/webm';
|
|
71
|
-
// AMR
|
|
72
|
-
if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D &&
|
|
73
|
-
buf[4] === 0x52)
|
|
74
|
-
return 'audio/amr';
|
|
75
|
-
return null;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Deteksi mimetype audio secara otomatis dari media input.
|
|
79
|
-
* Cek: 1) ekstensi URL/path, 2) magic bytes buffer, 3) fallback ke ogg/opus.
|
|
80
|
-
*/
|
|
81
|
-
const detectAudioMimetype = async (media) => {
|
|
82
|
-
// Cek ekstensi dari URL atau path string
|
|
83
|
-
if (typeof media === 'string' || (media && typeof media === 'object' && 'url' in media)) {
|
|
84
|
-
const urlStr = typeof media === 'string' ? media : media.url?.toString?.() ?? '';
|
|
85
|
-
// Ambil path tanpa query string, lalu cari semua ekstensi
|
|
86
|
-
const pathOnly = urlStr.split('?')[0];
|
|
87
|
-
// Cek ekstensi terakhir (.m4a, .mp3, dst)
|
|
88
|
-
const extMatch = pathOnly.match(/\.([a-zA-Z0-9]{2,5})(?:[^/]*)?$/);
|
|
89
|
-
if (extMatch) {
|
|
90
|
-
const ext = extMatch[1].toLowerCase();
|
|
91
|
-
if (AUDIO_MIMETYPE_MAP[ext]) return AUDIO_MIMETYPE_MAP[ext];
|
|
92
|
-
}
|
|
93
|
-
// Fallback: scan semua segmen path untuk ekstensi audio yang dikenal
|
|
94
|
-
// Contoh: ".plus.aac.ep.m4a" → cek tiap segment dari belakang
|
|
95
|
-
const segments = pathOnly.split('.');
|
|
96
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
97
|
-
const seg = segments[i].toLowerCase().split('/')[0].split('?')[0];
|
|
98
|
-
if (AUDIO_MIMETYPE_MAP[seg]) return AUDIO_MIMETYPE_MAP[seg];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Cek magic bytes jika Buffer
|
|
102
|
-
if (Buffer.isBuffer(media)) {
|
|
103
|
-
const detected = detectAudioMimetypeFromBuffer(media);
|
|
104
|
-
if (detected) return detected;
|
|
105
|
-
}
|
|
106
|
-
// Fallback: default ogg/opus
|
|
107
|
-
return MIMETYPE_MAP.audio;
|
|
108
|
-
};
|
|
109
|
-
const MessageTypeProto = {
|
|
110
|
-
image: WAProto.Message.ImageMessage,
|
|
111
|
-
video: WAProto.Message.VideoMessage,
|
|
112
|
-
audio: WAProto.Message.AudioMessage,
|
|
113
|
-
sticker: WAProto.Message.StickerMessage,
|
|
114
|
-
document: WAProto.Message.DocumentMessage
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Uses a regex to test whether the string contains a URL, and returns the URL if it does.
|
|
118
|
-
* @param text eg. hello https://google.com
|
|
119
|
-
* @returns the URL, eg. https://google.com
|
|
120
|
-
*/
|
|
121
23
|
export const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
|
|
122
24
|
export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
|
|
123
25
|
const url = extractUrlFromText(text);
|
|
@@ -127,7 +29,6 @@ export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) =>
|
|
|
127
29
|
return urlInfo;
|
|
128
30
|
}
|
|
129
31
|
catch (error) {
|
|
130
|
-
// ignore if fails
|
|
131
32
|
logger?.warn({ trace: error.stack }, 'url generation failed');
|
|
132
33
|
}
|
|
133
34
|
}
|
|
@@ -146,6 +47,13 @@ const assertColor = async (color) => {
|
|
|
146
47
|
return assertedColor;
|
|
147
48
|
}
|
|
148
49
|
};
|
|
50
|
+
const MessageTypeProto = {
|
|
51
|
+
image: WAProto.Message.ImageMessage,
|
|
52
|
+
video: WAProto.Message.VideoMessage,
|
|
53
|
+
audio: WAProto.Message.AudioMessage,
|
|
54
|
+
sticker: WAProto.Message.StickerMessage,
|
|
55
|
+
document: WAProto.Message.DocumentMessage
|
|
56
|
+
};
|
|
149
57
|
export const prepareWAMessageMedia = async (message, options) => {
|
|
150
58
|
const logger = options.logger;
|
|
151
59
|
let mediaType;
|
|
@@ -676,8 +584,8 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
676
584
|
const { stickers, cover, name, publisher, packId, description } = message.stickerPack;
|
|
677
585
|
|
|
678
586
|
// ── Validasi jumlah sticker ───────────────────────────────────────────
|
|
679
|
-
if (stickers.length >
|
|
680
|
-
throw new Boom('Sticker pack exceeds the maximum limit of
|
|
587
|
+
if (stickers.length > 120) {
|
|
588
|
+
throw new Boom('Sticker pack exceeds the maximum limit of 120 stickers', { statusCode: 400 });
|
|
681
589
|
}
|
|
682
590
|
if (stickers.length === 0) {
|
|
683
591
|
throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
|
|
@@ -715,40 +623,87 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
715
623
|
|
|
716
624
|
// ── Step 1: proses & zip semua sticker ────────────────────────────────
|
|
717
625
|
const stickerData = {};
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
626
|
+
const stickerMetadata = [];
|
|
627
|
+
|
|
628
|
+
// Process stickers sequentially to avoid memory issues
|
|
629
|
+
for (let i = 0; i < stickers.length; i++) {
|
|
630
|
+
const s = stickers[i];
|
|
631
|
+
try {
|
|
632
|
+
const { stream } = await getStream(s.data || s.sticker);
|
|
633
|
+
const buffer = await toBuffer(stream);
|
|
721
634
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
635
|
+
if (!buffer || buffer.length === 0) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
let webpBuffer;
|
|
640
|
+
let isAnimated = false;
|
|
641
|
+
if (isWebPBuffer(buffer)) {
|
|
642
|
+
isAnimated = isAnimatedWebP(buffer);
|
|
643
|
+
// Compress animated WebP with sharp
|
|
644
|
+
if ('sharp' in lib && lib.sharp) {
|
|
645
|
+
webpBuffer = await lib.sharp.default(buffer)
|
|
646
|
+
.resize(512, 512, {
|
|
647
|
+
fit: 'inside',
|
|
648
|
+
withoutEnlargement: true
|
|
649
|
+
})
|
|
650
|
+
.webp({
|
|
651
|
+
quality: 75,
|
|
652
|
+
effort: 6 // Maximum compression effort (0-6)
|
|
653
|
+
})
|
|
654
|
+
.toBuffer();
|
|
655
|
+
} else {
|
|
656
|
+
webpBuffer = buffer;
|
|
657
|
+
}
|
|
658
|
+
} else if ('sharp' in lib && lib.sharp) {
|
|
659
|
+
// Convert and compress static images
|
|
660
|
+
webpBuffer = await lib.sharp.default(buffer)
|
|
661
|
+
.resize(512, 512, {
|
|
662
|
+
fit: 'inside',
|
|
663
|
+
withoutEnlargement: true
|
|
664
|
+
})
|
|
665
|
+
.webp({
|
|
666
|
+
quality: 75,
|
|
667
|
+
effort: 6 // Maximum compression effort (0-6)
|
|
668
|
+
})
|
|
669
|
+
.toBuffer();
|
|
670
|
+
} else {
|
|
671
|
+
throw new Boom(
|
|
672
|
+
'No image processing library (sharp) available for converting sticker to WebP. ' +
|
|
673
|
+
'Either install sharp or provide stickers in WebP format.'
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Stricter size limit per sticker (1MB for larger packs)
|
|
678
|
+
const MAX_STICKER_SIZE = 1024 * 1024; // 1MB
|
|
679
|
+
if (webpBuffer.length > MAX_STICKER_SIZE) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
735
682
|
|
|
736
|
-
|
|
737
|
-
|
|
683
|
+
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
|
|
684
|
+
const fileName = `${hash}.webp`;
|
|
685
|
+
// Use compression level 6 for individual stickers in ZIP (balanced)
|
|
686
|
+
stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 6 }];
|
|
687
|
+
stickerMetadata.push({
|
|
688
|
+
fileName,
|
|
689
|
+
mimetype: 'image/webp',
|
|
690
|
+
isAnimated,
|
|
691
|
+
emojis: s.emojis || [],
|
|
692
|
+
accessibilityLabel: s.accessibilityLabel || ''
|
|
693
|
+
});
|
|
694
|
+
} catch (err) {
|
|
695
|
+
// Continue with next sticker instead of failing completely
|
|
738
696
|
}
|
|
697
|
+
}
|
|
739
698
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
accessibilityLabel: s.accessibilityLabel || ''
|
|
749
|
-
};
|
|
750
|
-
});
|
|
751
|
-
const stickerMetadata = await Promise.all(stickerPromises);
|
|
699
|
+
// Check if we have at least one valid sticker
|
|
700
|
+
if (stickerMetadata.length === 0) {
|
|
701
|
+
throw new Boom('No valid stickers could be processed', { statusCode: 400 });
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (stickerMetadata.length < stickers.length) {
|
|
705
|
+
// Some stickers were skipped
|
|
706
|
+
}
|
|
752
707
|
|
|
753
708
|
// ── Step 2: proses cover & masukkan ke dalam ZIP ──────────────────────
|
|
754
709
|
const trayIconFileName = `${stickerPackId}.webp`;
|
|
@@ -756,37 +711,79 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
756
711
|
|
|
757
712
|
let coverWebpBuffer;
|
|
758
713
|
if (isWebPBuffer(coverBuffer)) {
|
|
759
|
-
|
|
714
|
+
// Compress cover WebP
|
|
715
|
+
if ('sharp' in lib && lib.sharp) {
|
|
716
|
+
coverWebpBuffer = await lib.sharp.default(coverBuffer)
|
|
717
|
+
.resize(512, 512, {
|
|
718
|
+
fit: 'inside',
|
|
719
|
+
withoutEnlargement: true
|
|
720
|
+
})
|
|
721
|
+
.webp({
|
|
722
|
+
quality: 75,
|
|
723
|
+
effort: 6
|
|
724
|
+
})
|
|
725
|
+
.toBuffer();
|
|
726
|
+
} else {
|
|
727
|
+
coverWebpBuffer = coverBuffer;
|
|
728
|
+
}
|
|
760
729
|
} else if ('sharp' in lib && lib.sharp) {
|
|
761
|
-
coverWebpBuffer = await lib.sharp.default(coverBuffer)
|
|
730
|
+
coverWebpBuffer = await lib.sharp.default(coverBuffer)
|
|
731
|
+
.resize(512, 512, {
|
|
732
|
+
fit: 'inside',
|
|
733
|
+
withoutEnlargement: true
|
|
734
|
+
})
|
|
735
|
+
.webp({
|
|
736
|
+
quality: 75,
|
|
737
|
+
effort: 6
|
|
738
|
+
})
|
|
739
|
+
.toBuffer();
|
|
762
740
|
} else {
|
|
763
741
|
throw new Boom(
|
|
764
742
|
'No image processing library (sharp) available for converting cover to WebP. ' +
|
|
765
743
|
'Either install sharp or provide cover in WebP format.'
|
|
766
744
|
);
|
|
767
745
|
}
|
|
768
|
-
|
|
746
|
+
// Compress cover in ZIP as well (level 6 for balanced compression)
|
|
747
|
+
stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 6 }];
|
|
769
748
|
|
|
770
749
|
// ── Step 3: buat ZIP buffer ───────────────────────────────────────────
|
|
771
750
|
const zipBuffer = await new Promise((resolve, reject) => {
|
|
772
|
-
zip(stickerData, (err, data) => {
|
|
751
|
+
zip(stickerData, { level: 6, memLevel: 9 }, (err, data) => {
|
|
773
752
|
if (err) reject(err);
|
|
774
753
|
else resolve(Buffer.from(data));
|
|
775
754
|
});
|
|
776
755
|
});
|
|
777
756
|
|
|
757
|
+
// ── Validasi ukuran ZIP (WhatsApp limit ~10MB untuk sticker pack) ───
|
|
758
|
+
const MAX_STICKER_PACK_SIZE = 10 * 1024 * 1024; // 10MB
|
|
759
|
+
if (zipBuffer.length > MAX_STICKER_PACK_SIZE) {
|
|
760
|
+
throw new Boom(`Sticker pack too large: ${(zipBuffer.length / 1024 / 1024).toFixed(2)}MB (max: ${(MAX_STICKER_PACK_SIZE / 1024 / 1024).toFixed(2)}MB). Try reducing sticker count or size`, { statusCode: 400 });
|
|
761
|
+
}
|
|
762
|
+
|
|
778
763
|
// ── Step 4: encrypt ZIP (generate random mediaKey) ────────────────────
|
|
779
764
|
const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
|
|
780
765
|
logger: options.logger,
|
|
781
766
|
opts: options.options
|
|
782
767
|
});
|
|
783
768
|
|
|
769
|
+
options.logger?.info?.(`Sticker pack encrypted, fileSha256: ${stickerPackUpload.fileSha256.toString('base64')}`);
|
|
770
|
+
|
|
784
771
|
// ── Step 5: upload ZIP ────────────────────────────────────────────────
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
772
|
+
let stickerPackUploadResult;
|
|
773
|
+
try {
|
|
774
|
+
stickerPackUploadResult = await options.upload(stickerPackUpload.encWriteStream, {
|
|
775
|
+
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
|
|
776
|
+
mediaType: 'sticker-pack',
|
|
777
|
+
timeoutMs: options.mediaUploadTimeoutMs || 300000 // 300s (5 menit) untuk pack besar
|
|
778
|
+
});
|
|
779
|
+
options.logger?.info?.(`Sticker pack uploaded successfully: ${stickerPackUploadResult.directPath}`);
|
|
780
|
+
} catch (uploadError) {
|
|
781
|
+
options.logger?.error?.(`Sticker pack upload failed: ${uploadError.message}`);
|
|
782
|
+
throw new Boom(`Failed to upload sticker pack: ${uploadError.message}`, {
|
|
783
|
+
statusCode: 500,
|
|
784
|
+
data: { originalError: uploadError }
|
|
785
|
+
});
|
|
786
|
+
}
|
|
790
787
|
|
|
791
788
|
// ── Step 6: build stickerPackMessage ──────────────────────────────────
|
|
792
789
|
m.stickerPackMessage = {
|
|
@@ -810,7 +807,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
810
807
|
try {
|
|
811
808
|
let thumbnailBuffer;
|
|
812
809
|
if ('sharp' in lib && lib.sharp) {
|
|
813
|
-
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
|
|
810
|
+
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg({ quality: 80 }).toBuffer();
|
|
814
811
|
} else if ('jimp' in lib && lib.jimp) {
|
|
815
812
|
const jimpImage = await (lib.jimp.Jimp || lib.jimp.default).read(coverBuffer);
|
|
816
813
|
thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
|
|
@@ -831,7 +828,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
831
828
|
const thumbUploadResult = await options.upload(thumbUpload.encWriteStream, {
|
|
832
829
|
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
833
830
|
mediaType: 'thumbnail-sticker-pack',
|
|
834
|
-
timeoutMs: options.mediaUploadTimeoutMs
|
|
831
|
+
timeoutMs: options.mediaUploadTimeoutMs || 60000
|
|
835
832
|
});
|
|
836
833
|
|
|
837
834
|
Object.assign(m.stickerPackMessage, {
|
|
@@ -842,8 +839,10 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
842
839
|
thumbnailWidth: 252,
|
|
843
840
|
imageDataHash: sha256(thumbnailBuffer).toString('base64')
|
|
844
841
|
});
|
|
842
|
+
|
|
843
|
+
options.logger?.info?.(`Thumbnail uploaded successfully`);
|
|
845
844
|
} catch (e) {
|
|
846
|
-
options.logger?.warn?.(`Thumbnail generation failed: ${e}`);
|
|
845
|
+
options.logger?.warn?.(`Thumbnail generation/upload failed: ${e.message}`);
|
|
847
846
|
}
|
|
848
847
|
|
|
849
848
|
m.stickerPackMessage.contextInfo = {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a processor for offline stanza nodes that:
|
|
3
|
+
* - Queues nodes for sequential processing
|
|
4
|
+
* - Yields to the event loop periodically to avoid blocking
|
|
5
|
+
* - Catches handler errors to prevent the processing loop from crashing
|
|
6
|
+
*/
|
|
7
|
+
export function makeOfflineNodeProcessor(nodeProcessorMap, deps, batchSize = 10) {
|
|
8
|
+
const nodes = [];
|
|
9
|
+
let isProcessing = false;
|
|
10
|
+
const enqueue = (type, node) => {
|
|
11
|
+
nodes.push({ type, node });
|
|
12
|
+
if (isProcessing) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
isProcessing = true;
|
|
16
|
+
const promise = async () => {
|
|
17
|
+
let processedInBatch = 0;
|
|
18
|
+
while (nodes.length && deps.isWsOpen()) {
|
|
19
|
+
const { type, node } = nodes.shift();
|
|
20
|
+
const nodeProcessor = nodeProcessorMap.get(type);
|
|
21
|
+
if (!nodeProcessor) {
|
|
22
|
+
deps.onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
await nodeProcessor(node).catch(err => deps.onUnexpectedError(err, `processing offline ${type}`));
|
|
26
|
+
processedInBatch++;
|
|
27
|
+
// Yield to event loop after processing a batch
|
|
28
|
+
// This prevents blocking the event loop for too long when there are many offline nodes
|
|
29
|
+
if (processedInBatch >= batchSize) {
|
|
30
|
+
processedInBatch = 0;
|
|
31
|
+
await deps.yieldToEventLoop();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
isProcessing = false;
|
|
35
|
+
};
|
|
36
|
+
promise().catch(error => deps.onUnexpectedError(error, 'processing offline nodes'));
|
|
37
|
+
};
|
|
38
|
+
return { enqueue };
|
|
39
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lia@Changes 09-04-26 [WIP]
|
|
3
|
+
* Adds support for tables and code blocks with richResponseMessage (wrapped inside botForwardedMessage).
|
|
4
|
+
*
|
|
5
|
+
* If you use or copy this code, please credit my name or project.
|
|
6
|
+
* @itsliaaa/baileys
|
|
7
|
+
*/
|
|
8
|
+
import { getRandomValues, randomUUID, randomBytes } from 'crypto';
|
|
9
|
+
import { BOT_RENDERING_CONFIG_METADATA, DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
|
|
10
|
+
import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
|
|
11
|
+
import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
|
|
12
|
+
import { proto } from '../../WAProto/index.js';
|
|
13
|
+
import { unixTimestampSeconds } from './generics.js';
|
|
14
|
+
const NOOP = new Set([]);
|
|
15
|
+
export const tokenizeCode = (code, language = 'javascript') => {
|
|
16
|
+
const keywords = LANGUAGE_KEYWORDS[language] || NOOP;
|
|
17
|
+
const blocks = [];
|
|
18
|
+
LEXER_REGEX.lastIndex = 0;
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = LEXER_REGEX.exec(code)) !== null) {
|
|
21
|
+
if (match[1]) {
|
|
22
|
+
blocks.push({ highlightType: CodeHighlightType.COMMENT, codeContent: match[1] });
|
|
23
|
+
}
|
|
24
|
+
else if (match[2]) {
|
|
25
|
+
blocks.push({ highlightType: CodeHighlightType.STRING, codeContent: match[2] });
|
|
26
|
+
}
|
|
27
|
+
else if (match[3]) {
|
|
28
|
+
blocks.push({
|
|
29
|
+
highlightType: keywords.has(match[3]) ? CodeHighlightType.KEYWORD : CodeHighlightType.METHOD,
|
|
30
|
+
codeContent: match[3],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (match[4]) {
|
|
34
|
+
blocks.push({
|
|
35
|
+
highlightType: keywords.has(match[4]) ? CodeHighlightType.KEYWORD : CodeHighlightType.DEFAULT,
|
|
36
|
+
codeContent: match[4],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else if (match[5]) {
|
|
40
|
+
blocks.push({ highlightType: CodeHighlightType.NUMBER, codeContent: match[5] });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
blocks.push({ highlightType: CodeHighlightType.DEFAULT, codeContent: match[6] });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return blocks;
|
|
47
|
+
};
|
|
48
|
+
// Lia@Changes 09-04-26 --- Inject buffer into unifiedResponse.data to support proper rendering of rich messages (ex: tables and code blocks)
|
|
49
|
+
export const toUnified = (submessages) =>
|
|
50
|
+
({
|
|
51
|
+
response_id: randomUUID(),
|
|
52
|
+
sections: submessages.map((submessage, index) => {
|
|
53
|
+
switch (submessage.messageType) {
|
|
54
|
+
case RichSubMessageType.CODE:
|
|
55
|
+
const codeMetadata = submessage.codeMetadata;
|
|
56
|
+
return {
|
|
57
|
+
view_model: {
|
|
58
|
+
primitive: {
|
|
59
|
+
language: codeMetadata.codeLanguage,
|
|
60
|
+
code_blocks: codeMetadata.codeBlocks.map((block) => ({ content: block.codeContent, type: CodeHighlightType[block.highlightType] })),
|
|
61
|
+
__typename: 'GenAICodeUXPrimitive'
|
|
62
|
+
},
|
|
63
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
case RichSubMessageType.CONTENT_ITEMS:
|
|
67
|
+
return {
|
|
68
|
+
view_model: {
|
|
69
|
+
primitives: submessage.contentItemsMetadata.itemsMetadata.map((item) => {
|
|
70
|
+
const reelItem = item.reelItem
|
|
71
|
+
return {
|
|
72
|
+
reels_url: reelItem.videoUrl,
|
|
73
|
+
thumbnail_url: reelItem.thumbnailUrl,
|
|
74
|
+
creator: reelItem.creator || '@itsliaaa/baileys',
|
|
75
|
+
avatar_url: reelItem.profileIconUrl,
|
|
76
|
+
reels_title: reelItem.title,
|
|
77
|
+
likes_count: reelItem.likesCount || 0,
|
|
78
|
+
shares_count: reelItem.sharesCount || 0,
|
|
79
|
+
view_count: reelItem.viewCount || 0,
|
|
80
|
+
reel_source: reelItem.reelSource || 'IG',
|
|
81
|
+
is_verified: reelItem.isVerified || false,
|
|
82
|
+
__typename: 'GenAIReelPrimitive'
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
__typename: 'GenAIHScrollLayoutViewModel'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
case RichSubMessageType.LATEX:
|
|
89
|
+
const latexMetadata = submessage.latexMetadata;
|
|
90
|
+
const item = {
|
|
91
|
+
latex_expression: latexMetadata.expressions[0]?.latexExpression,
|
|
92
|
+
font_height: latexMetadata.expressions[0]?.fontHeight,
|
|
93
|
+
padding: 15,
|
|
94
|
+
latex_image: {
|
|
95
|
+
url: latexMetadata.expressions[0]?.url,
|
|
96
|
+
width: latexMetadata.expressions[0]?.width || 388,
|
|
97
|
+
height: latexMetadata.expressions[0]?.height || 160
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
view_model: {
|
|
102
|
+
primitive: {
|
|
103
|
+
item,
|
|
104
|
+
...item,
|
|
105
|
+
__typename: 'GenAILatexUXPrimitive'
|
|
106
|
+
},
|
|
107
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
case RichSubMessageType.TABLE:
|
|
111
|
+
const tableMetadata = submessage.tableMetadata;
|
|
112
|
+
return {
|
|
113
|
+
view_model: {
|
|
114
|
+
primitive: {
|
|
115
|
+
title: tableMetadata.title,
|
|
116
|
+
rows: tableMetadata.rows.map((row) => ({ is_header: row.isHeading, cells: row.items, markdown_cells: [] })),
|
|
117
|
+
__typename: 'GenATableUXPrimitive'
|
|
118
|
+
},
|
|
119
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
case RichSubMessageType.TEXT:
|
|
123
|
+
return {
|
|
124
|
+
view_model: {
|
|
125
|
+
primitive: {
|
|
126
|
+
text: submessage.messageText,
|
|
127
|
+
inline_entities: submessage.inlineEntities || [],
|
|
128
|
+
__typename: 'GenAIMarkdownTextUXPrimitive'
|
|
129
|
+
},
|
|
130
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return submessage;
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
// Lia@Note 17-04-26 --- WIP
|
|
138
|
+
export const buildAdditionalBotMetadataContext = (submessages) => {
|
|
139
|
+
const sources = [];
|
|
140
|
+
const mediaDetailsMetadataList = [];
|
|
141
|
+
for (let i = 0; i < submessages.length; i++) {
|
|
142
|
+
const submessage = submessages[i];
|
|
143
|
+
switch (submessage.messageType) {
|
|
144
|
+
case RichSubMessageType.CONTENT_ITEMS:
|
|
145
|
+
const itemsMetadata = submessage.contentItemsMetadata.itemsMetadata;
|
|
146
|
+
for (let n = 0; n < itemsMetadata.length; n++) {
|
|
147
|
+
const reelItem = itemsMetadata[n].reelItem;
|
|
148
|
+
sources.push({
|
|
149
|
+
provider: 0,
|
|
150
|
+
thumbnailCdnUrl: reelItem.thumbnailUrl,
|
|
151
|
+
sourceProviderUrl: reelItem.videoUrl,
|
|
152
|
+
sourceQuery: '',
|
|
153
|
+
faviconCdnUrl: '',
|
|
154
|
+
citationNumber: i + 1,
|
|
155
|
+
sourceTitle: reelItem.title
|
|
156
|
+
});
|
|
157
|
+
mediaDetailsMetadataList.push({
|
|
158
|
+
id: randomBytes(32).toString('hex'),
|
|
159
|
+
previewMedia: {
|
|
160
|
+
fileSha256: '',
|
|
161
|
+
mediaKey: '',
|
|
162
|
+
fileEncSha256: '',
|
|
163
|
+
directPath: '',
|
|
164
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
165
|
+
mimetype: 'image/jpeg'
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
case RichSubMessageType.LATEX:
|
|
171
|
+
const expressions = submessage.latexMetadata.expressions;
|
|
172
|
+
for (let n = 0; n < expressions.length; n++) {
|
|
173
|
+
const expression = expressions[n];
|
|
174
|
+
mediaDetailsMetadataList.push({
|
|
175
|
+
id: randomBytes(32).toString('hex'),
|
|
176
|
+
previewMedia: {
|
|
177
|
+
fileSha256: '',
|
|
178
|
+
mediaKey: '',
|
|
179
|
+
fileEncSha256: '',
|
|
180
|
+
directPath: '',
|
|
181
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
182
|
+
mimetype: 'image/jpeg'
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { sources, mediaDetailsMetadataList };
|
|
190
|
+
}
|
|
191
|
+
export const prepareRichResponseMessage = (content) => {
|
|
192
|
+
const { code, contentText, expressions, footerText, headerText, items, language, links, noHeading, richResponse, table, text, title } = content;
|
|
193
|
+
let submessages = [];
|
|
194
|
+
if (Array.isArray(richResponse)) {
|
|
195
|
+
submessages = richResponse.map((submessage) => {
|
|
196
|
+
if (submessage.text) {
|
|
197
|
+
return {
|
|
198
|
+
messageType: RichSubMessageType.TEXT,
|
|
199
|
+
messageText: submessage.text,
|
|
200
|
+
inlineEntities: submessage.inlineEntities
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
else if (submessage.code) {
|
|
204
|
+
return {
|
|
205
|
+
messageType: RichSubMessageType.CODE,
|
|
206
|
+
codeMetadata: {
|
|
207
|
+
codeLanguage: submessage.language,
|
|
208
|
+
codeBlocks: submessage.code
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
else if (submessage.expressions) {
|
|
213
|
+
return {
|
|
214
|
+
messageType: RichSubMessageType.LATEX,
|
|
215
|
+
latexMetadata: {
|
|
216
|
+
text: submessage.text,
|
|
217
|
+
expressions: submessage.expressions
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
else if (submessage.items) {
|
|
222
|
+
return {
|
|
223
|
+
messageType: RichSubMessageType.CONTENT_ITEMS,
|
|
224
|
+
contentItemsMetadata: {
|
|
225
|
+
itemsMetadata: submessage.items
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
else if (submessage.table) {
|
|
230
|
+
return {
|
|
231
|
+
messageType: RichSubMessageType.TABLE,
|
|
232
|
+
tableMetadata: {
|
|
233
|
+
title: submessage.title,
|
|
234
|
+
rows: submessage.table
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return submessage;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
if (headerText) {
|
|
243
|
+
submessages.push({
|
|
244
|
+
messageType: RichSubMessageType.TEXT,
|
|
245
|
+
messageText: headerText
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (contentText) {
|
|
249
|
+
submessages.push({
|
|
250
|
+
messageType: RichSubMessageType.TEXT,
|
|
251
|
+
messageText: contentText
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (code) {
|
|
255
|
+
language ??= 'javascript';
|
|
256
|
+
submessages.push({
|
|
257
|
+
messageType: RichSubMessageType.CODE,
|
|
258
|
+
codeMetadata: {
|
|
259
|
+
codeLanguage: language,
|
|
260
|
+
codeBlocks: tokenizeCode(code, language)
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else if (expressions) {
|
|
265
|
+
submessages.push({
|
|
266
|
+
messageType: RichSubMessageType.LATEX,
|
|
267
|
+
latexMetadata: {
|
|
268
|
+
text,
|
|
269
|
+
expressions
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else if (items) {
|
|
274
|
+
submessages.push({
|
|
275
|
+
messageType: RichSubMessageType.CONTENT_ITEMS,
|
|
276
|
+
contentItemsMetadata: {
|
|
277
|
+
itemsMetadata: items.map((item) => ({ reelItem: item })),
|
|
278
|
+
contentType: proto.AIRichResponseContentItemsMetadata.ContentType.CAROUSEL
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else if (links) {
|
|
283
|
+
links.forEach((linkField, index) => {
|
|
284
|
+
const prefix = 'SS_' + index;
|
|
285
|
+
const url = linkField.url || DONATE_URL;
|
|
286
|
+
const sources = linkField.sources?.map((sourceField) => ({
|
|
287
|
+
source_type: 'THIRD_PARTY',
|
|
288
|
+
source_display_name: sourceField.displayName || 'Donate',
|
|
289
|
+
source_subtitle: sourceField.subtitle || 'Saweria',
|
|
290
|
+
source_url: sourceField.url || url
|
|
291
|
+
}));
|
|
292
|
+
submessages.push({
|
|
293
|
+
messageType: RichSubMessageType.TEXT,
|
|
294
|
+
messageText: linkField.text + ` {{${prefix}}}¹{{/${prefix}}} `,
|
|
295
|
+
inlineEntities: [{
|
|
296
|
+
key: prefix,
|
|
297
|
+
metadata: {
|
|
298
|
+
reference_id: index + 1,
|
|
299
|
+
reference_url: url,
|
|
300
|
+
reference_title: linkField.title || 'For Donation via Saweria',
|
|
301
|
+
reference_display_name: linkField.displayName || 'Donation',
|
|
302
|
+
sources: sources || [],
|
|
303
|
+
__typename: 'GenAISearchCitationItem'
|
|
304
|
+
}
|
|
305
|
+
}]
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else if (table) {
|
|
310
|
+
submessages.push({
|
|
311
|
+
messageType: RichSubMessageType.TABLE,
|
|
312
|
+
tableMetadata: {
|
|
313
|
+
title,
|
|
314
|
+
rows: table.map((items, index) => ({
|
|
315
|
+
isHeading: !noHeading && index == 0,
|
|
316
|
+
items
|
|
317
|
+
}))
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (footerText) {
|
|
322
|
+
submessages.push({
|
|
323
|
+
messageType: RichSubMessageType.TEXT,
|
|
324
|
+
messageText: footerText
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const unified = toUnified(submessages);
|
|
329
|
+
const message = wrapToBotForwardedMessage({
|
|
330
|
+
submessages: [],
|
|
331
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
332
|
+
unifiedResponse: {
|
|
333
|
+
data: Buffer.from(JSON.stringify(unified), 'utf-8') // Lia@Note 25-04-26 --- Expects "ArrayBufferLike"
|
|
334
|
+
},
|
|
335
|
+
contextInfo: {
|
|
336
|
+
isForwarded: true,
|
|
337
|
+
forwardingScore: 1,
|
|
338
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
339
|
+
forwardOrigin: 4,
|
|
340
|
+
botMessageSharingInfo: { forwardScore: 1 }
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
// Lia@Note 17-04-26 --- TODO: Fill mediaDetailsMetadataList and sources field
|
|
344
|
+
const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadataContext(submessages);
|
|
345
|
+
const botMetadata = message.messageContextInfo.botMetadata;
|
|
346
|
+
if (sources.length > 0) {
|
|
347
|
+
botMetadata.richResponseSourcesMetadata = { sources };
|
|
348
|
+
}
|
|
349
|
+
if (mediaDetailsMetadataList.length > 0) {
|
|
350
|
+
botMetadata.unifiedResponseMutation = { mediaDetailsMetadataList };
|
|
351
|
+
}
|
|
352
|
+
return message;
|
|
353
|
+
}
|
|
354
|
+
// Lia@Note 17-04-26 --- signature and certificateChain for proofs[] field
|
|
355
|
+
export const botMetadataSignature = () => {
|
|
356
|
+
const signature = new Uint8Array(64);
|
|
357
|
+
getRandomValues(signature);
|
|
358
|
+
return signature;
|
|
359
|
+
}
|
|
360
|
+
export const botMetadataCertificate = (length = 700) => {
|
|
361
|
+
const certificate = new Uint8Array(length);
|
|
362
|
+
certificate[0] = 48;
|
|
363
|
+
certificate[1] = 130;
|
|
364
|
+
getRandomValues(certificate.subarray(2));
|
|
365
|
+
return certificate;
|
|
366
|
+
}
|
|
367
|
+
export const wrapToBotForwardedMessage = (richResponseMessage) =>
|
|
368
|
+
({
|
|
369
|
+
messageContextInfo: {
|
|
370
|
+
botMetadata: {
|
|
371
|
+
pluginMetadata: {},
|
|
372
|
+
// Lia@Note 09-04-26 --- TODO: Fill verificationMetadata field
|
|
373
|
+
verificationMetadata: {
|
|
374
|
+
proofs: [
|
|
375
|
+
{
|
|
376
|
+
certificateChain: [
|
|
377
|
+
botMetadataCertificate(684),
|
|
378
|
+
botMetadataCertificate(892)
|
|
379
|
+
],
|
|
380
|
+
version: 1,
|
|
381
|
+
useCase: 1,
|
|
382
|
+
signature: botMetadataSignature()
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
},
|
|
386
|
+
botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
botForwardedMessage: {
|
|
390
|
+
message: { richResponseMessage }
|
|
391
|
+
}
|
|
392
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds an ACK stanza for a received node.
|
|
3
|
+
* Pure function -- no I/O, no side effects.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors WhatsApp Web's ACK construction:
|
|
6
|
+
* - WAWebHandleMsgSendAck.sendAck / sendNack
|
|
7
|
+
* - WAWebCreateNackFromStanza.createNackFromStanza
|
|
8
|
+
*/
|
|
9
|
+
export function buildAckStanza(node, errorCode, meId) {
|
|
10
|
+
const { tag, attrs } = node;
|
|
11
|
+
const stanza = {
|
|
12
|
+
tag: 'ack',
|
|
13
|
+
attrs: {
|
|
14
|
+
id: attrs.id,
|
|
15
|
+
to: attrs.from,
|
|
16
|
+
class: tag
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (errorCode) {
|
|
20
|
+
stanza.attrs.error = errorCode.toString();
|
|
21
|
+
}
|
|
22
|
+
if (attrs.participant) {
|
|
23
|
+
stanza.attrs.participant = attrs.participant;
|
|
24
|
+
}
|
|
25
|
+
if (attrs.recipient) {
|
|
26
|
+
stanza.attrs.recipient = attrs.recipient;
|
|
27
|
+
}
|
|
28
|
+
// WA Web always includes type when present: `n.type || DROP_ATTR`
|
|
29
|
+
if (attrs.type) {
|
|
30
|
+
stanza.attrs.type = attrs.type;
|
|
31
|
+
}
|
|
32
|
+
// WA Web WAWebHandleMsgSendAck.sendAck/sendNack always include `from` for message-class ACKs
|
|
33
|
+
if (tag === 'message' && meId) {
|
|
34
|
+
stanza.attrs.from = meId;
|
|
35
|
+
}
|
|
36
|
+
return stanza;
|
|
37
|
+
}
|
|
@@ -1298,4 +1298,77 @@ for (const [i, DOUBLE_BYTE_TOKEN] of DOUBLE_BYTE_TOKENS.entries()) {
|
|
|
1298
1298
|
TOKEN_MAP[element] = { dict: i, index: j };
|
|
1299
1299
|
}
|
|
1300
1300
|
}
|
|
1301
|
+
|
|
1302
|
+
export const CPP_KEYWORDS = new Set([
|
|
1303
|
+
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', 'case',
|
|
1304
|
+
'catch', 'char', 'class', 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit',
|
|
1305
|
+
'const_cast', 'continue', 'co_await', 'co_return', 'co_yield', 'decltype', 'default', 'delete',
|
|
1306
|
+
'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float',
|
|
1307
|
+
'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'noexcept',
|
|
1308
|
+
'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'register',
|
|
1309
|
+
'reinterpret_cast', 'requires', 'return', 'short', 'signed', 'sizeof', 'static', 'static_assert',
|
|
1310
|
+
'static_cast', 'struct', 'switch', 'template', 'this', 'thread_local', 'throw', 'true', 'try',
|
|
1311
|
+
'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile',
|
|
1312
|
+
'wchar_t', 'while', 'xor', 'xor_eq'
|
|
1313
|
+
]);
|
|
1314
|
+
export const CSS_KEYWORDS = new Set([
|
|
1315
|
+
'import', 'media', 'font-face', 'keyframes', 'supports', 'charset',
|
|
1316
|
+
'important', 'root', 'hover', 'active', 'focus', 'visited', 'before', 'after',
|
|
1317
|
+
'not', 'nth-child', 'first-child', 'last-child', 'only-child',
|
|
1318
|
+
'none', 'inherit', 'initial', 'unset', 'auto', 'transparent', 'currentcolor'
|
|
1319
|
+
]);
|
|
1320
|
+
export const GO_KEYWORDS = new Set([
|
|
1321
|
+
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct',
|
|
1322
|
+
'chan', 'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type',
|
|
1323
|
+
'continue', 'for', 'import', 'return', 'var', 'true', 'false', 'nil'
|
|
1324
|
+
]);
|
|
1325
|
+
export const HTML_KEYWORDS = new Set([
|
|
1326
|
+
'html', 'head', 'body', 'title', 'meta', 'link', 'script', 'style',
|
|
1327
|
+
'header', 'footer', 'main', 'section', 'article', 'aside', 'nav',
|
|
1328
|
+
'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'img',
|
|
1329
|
+
'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'thead', 'tbody',
|
|
1330
|
+
'form', 'input', 'button', 'select', 'textarea', 'label', 'option',
|
|
1331
|
+
'canvas', 'svg', 'iframe', 'video', 'audio', 'source'
|
|
1332
|
+
]);
|
|
1333
|
+
export const JS_KEYWORDS = new Set([
|
|
1334
|
+
'import', 'export', 'from', 'default', 'as',
|
|
1335
|
+
'const', 'let', 'var', 'function', 'class', 'extends', 'new',
|
|
1336
|
+
'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue',
|
|
1337
|
+
'try', 'catch', 'finally', 'throw',
|
|
1338
|
+
'async', 'await', 'yield',
|
|
1339
|
+
'typeof', 'instanceof', 'in', 'of', 'delete', 'void',
|
|
1340
|
+
'true', 'false', 'null', 'undefined', 'NaN', 'Infinity',
|
|
1341
|
+
'this', 'super', 'static', 'get', 'set',
|
|
1342
|
+
'debugger', 'with'
|
|
1343
|
+
]);
|
|
1344
|
+
export const PYTHON_KEYWORDS = new Set([
|
|
1345
|
+
'import', 'from', 'as', 'def', 'class', 'return', 'if', 'elif', 'else',
|
|
1346
|
+
'for', 'while', 'break', 'continue', 'try', 'except', 'finally', 'raise',
|
|
1347
|
+
'with', 'yield', 'lambda', 'pass', 'del', 'global', 'nonlocal', 'assert',
|
|
1348
|
+
'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'async', 'await',
|
|
1349
|
+
'self', 'print'
|
|
1350
|
+
]);
|
|
1351
|
+
export const RUST_KEYWORDS = new Set([
|
|
1352
|
+
'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern', 'false', 'fn', 'for',
|
|
1353
|
+
'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return',
|
|
1354
|
+
'self', 'Self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use',
|
|
1355
|
+
'where', 'while', 'async', 'await', 'dyn', 'abstract', 'become', 'box', 'do', 'final',
|
|
1356
|
+
'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual', 'yield'
|
|
1357
|
+
]);
|
|
1358
|
+
export const LANGUAGE_KEYWORDS = {
|
|
1359
|
+
css: CSS_KEYWORDS,
|
|
1360
|
+
html: HTML_KEYWORDS,
|
|
1361
|
+
javascript: JS_KEYWORDS,
|
|
1362
|
+
typescript: JS_KEYWORDS,
|
|
1363
|
+
js: JS_KEYWORDS,
|
|
1364
|
+
ts: JS_KEYWORDS,
|
|
1365
|
+
python: PYTHON_KEYWORDS,
|
|
1366
|
+
py: PYTHON_KEYWORDS,
|
|
1367
|
+
go: GO_KEYWORDS,
|
|
1368
|
+
golang: GO_KEYWORDS,
|
|
1369
|
+
cpp: CPP_KEYWORDS,
|
|
1370
|
+
'c++': CPP_KEYWORDS,
|
|
1371
|
+
rust: RUST_KEYWORDS,
|
|
1372
|
+
rs: RUST_KEYWORDS,
|
|
1373
|
+
};
|
|
1301
1374
|
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { assertNodeErrorFree } from '../../WABinary/index.js';
|
|
2
|
+
import { USyncUser } from '../USyncUser.js';
|
|
3
|
+
export class USyncUsernameProtocol {
|
|
4
|
+
name = 'username';
|
|
5
|
+
getQueryElement() {
|
|
6
|
+
return {
|
|
7
|
+
tag: 'username',
|
|
8
|
+
attrs: {}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
getUserElement(user) {
|
|
12
|
+
void user;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
parser(node) {
|
|
16
|
+
if (node.tag === 'username') {
|
|
17
|
+
assertNodeErrorFree(node);
|
|
18
|
+
return typeof node.content === 'string' ? node.content : null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,4 +3,5 @@ export * from './USyncContactProtocol.js';
|
|
|
3
3
|
export * from './USyncStatusProtocol.js';
|
|
4
4
|
export * from './USyncDisappearingModeProtocol.js';
|
|
5
5
|
export * from './UsyncBotProfileProtocol.js';
|
|
6
|
-
export * from './UsyncLIDProtocol.js';
|
|
6
|
+
export * from './UsyncLIDProtocol.js';
|
|
7
|
+
export * from './USyncUsernameProtocol.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blckrose/baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.5",
|
|
5
5
|
"description": "A WebSockets library for interacting with WhatsApp Web",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|
|
@@ -82,4 +82,4 @@
|
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=20.0.0"
|
|
84
84
|
}
|
|
85
|
-
}
|
|
85
|
+
}
|