@kelvdra/baileys 1.0.5-rc.1 → 1.0.5-rc.2

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.
@@ -2,12 +2,195 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { WAMessageAddressingMode, WAMessageStubType } from '../Types/index.js';
3
3
  import { generateMessageIDV2, unixTimestampSeconds } from '../Utils/index.js';
4
4
  import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, isLidUser, isPnUser, jidEncode, jidNormalizedUser } from '../WABinary/index.js';
5
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
5
6
  import { makeChatsSocket } from './chats.js';
7
+ const pickFirst = (...values) => values.find(v => typeof v === 'string' && v.length > 0);
8
+ const preferPnJid = (primary, pn) => {
9
+ const normalizedPrimary = primary ? jidNormalizedUser(primary) : undefined;
10
+ const normalizedPn = pn ? jidNormalizedUser(pn) : undefined;
11
+ if (isLidUser(normalizedPrimary) && isPnUser(normalizedPn)) return normalizedPn;
12
+ return normalizedPrimary;
13
+ };
14
+ const getParticipantUsername = (attrs = {}) => pickFirst(
15
+ attrs.notify,
16
+ attrs.name,
17
+ attrs.short_name,
18
+ attrs.shortName,
19
+ attrs.pushname,
20
+ attrs.verified_name,
21
+ attrs.verifiedName,
22
+ attrs.vname
23
+ );
24
+ const getJidUser = (jid) => typeof jid === 'string' ? jid.split('@')[0] : undefined;
25
+ const getContactDisplayName = (contact = {}) => pickFirst(
26
+ contact.name,
27
+ contact.notify,
28
+ contact.verifiedName,
29
+ contact.verified_name,
30
+ contact.pushname,
31
+ contact.shortName,
32
+ contact.short_name
33
+ );
34
+ const participantNameFallback = (participant = {}) => getJidUser(participant.phoneNumber || participant.jid || participant.id || participant.lid);
35
+ const buildGroupParticipant = (attrs = {}) => {
36
+ const rawJid = attrs.jid ? jidNormalizedUser(attrs.jid) : undefined;
37
+ const rawLid = attrs.lid ? jidNormalizedUser(attrs.lid) : undefined;
38
+ const rawPn = pickFirst(attrs.phone_number, attrs.phoneNumber, attrs.jid_pn, attrs.participant_pn, attrs.pn);
39
+ const pnJid = rawPn ? jidNormalizedUser(rawPn) : undefined;
40
+ const id = preferPnJid(rawJid, pnJid) || pnJid || rawJid;
41
+ const lid = isLidUser(rawJid) ? rawJid : (isLidUser(rawLid) ? rawLid : undefined);
42
+ const username = getParticipantUsername(attrs);
43
+ const participant = {
44
+ id,
45
+ jid: id,
46
+ phoneNumber: isPnUser(id) ? id : undefined,
47
+ lid,
48
+ username,
49
+ name: username,
50
+ notify: attrs.notify,
51
+ admin: (attrs.type || null)
52
+ };
53
+ const fallback = participantNameFallback(participant);
54
+ return {
55
+ ...participant,
56
+ username: username || fallback,
57
+ name: username || fallback,
58
+ notify: attrs.notify || username || fallback,
59
+ displayName: username || fallback
60
+ };
61
+ };
62
+ const normalizeGroupMetadataJids = (metadata) => ({
63
+ ...metadata,
64
+ owner: preferPnJid(metadata.owner, metadata.ownerPn),
65
+ ownerLid: isLidUser(metadata.owner) ? metadata.owner : undefined,
66
+ subjectOwner: preferPnJid(metadata.subjectOwner, metadata.subjectOwnerPn),
67
+ subjectOwnerLid: isLidUser(metadata.subjectOwner) ? metadata.subjectOwner : undefined,
68
+ descOwner: preferPnJid(metadata.descOwner, metadata.descOwnerPn),
69
+ descOwnerLid: isLidUser(metadata.descOwner) ? metadata.descOwner : undefined,
70
+ participants: Array.isArray(metadata.participants)
71
+ ? metadata.participants.map(p => ({ ...p, id: preferPnJid(p.id, p.phoneNumber) || p.id, jid: preferPnJid(p.jid || p.id, p.phoneNumber) || p.jid || p.id }))
72
+ : []
73
+ });
6
74
  export const makeGroupsSocket = (config) => {
7
75
  const sock = makeChatsSocket(config);
8
- const { authState, ev, query, upsertMessage } = sock;
76
+ const { authState, ev, query, upsertMessage, executeUSyncQuery } = sock;
9
77
  const { cachedGroupMetadata } = config;
78
+ // Cache nama kontak dari history/contact/message event.
79
+ // Catatan: metadata grup dari WA sering hanya mengirim jid/lid/phone_number,
80
+ // jadi nama member harus diperkaya dari cache kontak lokal jika tersedia.
81
+ const contactNameCache = new Map();
82
+ const putContactName = (contact = {}) => {
83
+ const ids = [contact.id, contact.jid, contact.phoneNumber, contact.lid]
84
+ .filter(Boolean)
85
+ .map(jid => {
86
+ try { return jidNormalizedUser(jid); } catch { return jid; }
87
+ });
88
+ if (!ids.length) return;
89
+ const displayName = getContactDisplayName(contact);
90
+ const cached = {
91
+ ...contact,
92
+ displayName,
93
+ username: displayName || contact.username,
94
+ name: contact.name || displayName,
95
+ notify: contact.notify || displayName
96
+ };
97
+ for (const id of ids) contactNameCache.set(id, cached);
98
+ };
99
+ const getCachedContactName = (participant = {}) => {
100
+ const ids = [participant.id, participant.jid, participant.phoneNumber, participant.lid].filter(Boolean);
101
+ for (const id of ids) {
102
+ const normalized = (() => { try { return jidNormalizedUser(id); } catch { return id; } })();
103
+ const contact = contactNameCache.get(normalized);
104
+ const name = getContactDisplayName(contact);
105
+ if (name) return name;
106
+ }
107
+ return undefined;
108
+ };
109
+ const enrichGroupMetadataNames = (metadata = {}) => ({
110
+ ...metadata,
111
+ participants: Array.isArray(metadata.participants)
112
+ ? metadata.participants.map(participant => {
113
+ const cachedName = getCachedContactName(participant);
114
+ const displayName = pickFirst(participant.username, participant.name, participant.notify, cachedName, participantNameFallback(participant));
115
+ return {
116
+ ...participant,
117
+ username: displayName,
118
+ name: displayName,
119
+ notify: pickFirst(participant.notify, displayName),
120
+ displayName
121
+ };
122
+ })
123
+ : []
124
+ });
125
+ ev.on('contacts.upsert', contacts => contacts?.forEach?.(putContactName));
126
+ ev.on('contacts.update', contacts => contacts?.forEach?.(putContactName));
127
+ ev.on('messaging-history.set', ({ contacts }) => contacts?.forEach?.(putContactName));
128
+ ev.on('messages.upsert', ({ messages }) => {
129
+ for (const msg of messages || []) {
130
+ const remoteJid = msg?.key?.remoteJid;
131
+ const participant = msg?.key?.participant;
132
+ const id = participant || (remoteJid && !remoteJid.endsWith('@g.us') ? remoteJid : undefined);
133
+ if (id && msg?.pushName) putContactName({ id, notify: msg.pushName });
134
+ }
135
+ });
10
136
  // ── Built-in group metadata cache ─────────────────────────────────────────
137
+
138
+ const fetchParticipantUsernames = async (participants = []) => {
139
+ // WA username (@username) beda dengan pushName/kontak.
140
+ // groupMetadata biasanya tidak membawa username, jadi kita query USync username.
141
+ // Jika akun belum punya username atau server tidak mengizinkan, hasilnya kosong.
142
+ const entries = participants
143
+ .map(p => ({ participant: p, jid: p.jid || p.id || p.phoneNumber || p.lid }))
144
+ .filter(({ jid }) => typeof jid === 'string' && jid.length > 0);
145
+ const result = new Map();
146
+ const chunkSize = config.groupUsernameQueryChunkSize || 50;
147
+ for (let i = 0; i < entries.length; i += chunkSize) {
148
+ const chunk = entries.slice(i, i + chunkSize);
149
+ try {
150
+ const usync = new USyncQuery().withContext('interactive').withUsernameProtocol();
151
+ for (const { jid } of chunk) {
152
+ const normalized = (() => { try { return jidNormalizedUser(jid); } catch { return jid; } })();
153
+ usync.withUser(new USyncUser().withId(normalized));
154
+ }
155
+ const res = await executeUSyncQuery(usync);
156
+ for (const item of res?.list || []) {
157
+ const username = typeof item.username === 'string' && item.username.length > 0 ? item.username : undefined;
158
+ if (username) result.set(jidNormalizedUser(item.id), username);
159
+ }
160
+ } catch (err) {
161
+ config.logger?.debug?.({ err }, 'failed to fetch group participant usernames');
162
+ }
163
+ }
164
+ return result;
165
+ };
166
+ const enrichGroupMetadataUsernames = async (metadata = {}) => {
167
+ const normalized = normalizeGroupMetadataJids(metadata);
168
+ const shouldQueryUsername = config.fetchGroupParticipantsUsername !== false;
169
+ const usernameMap = shouldQueryUsername ? await fetchParticipantUsernames(normalized.participants) : new Map();
170
+ const withNames = enrichGroupMetadataNames(normalized);
171
+ return {
172
+ ...withNames,
173
+ participants: withNames.participants.map(participant => {
174
+ const keys = [participant.jid, participant.id, participant.phoneNumber, participant.lid].filter(Boolean);
175
+ let waUsername;
176
+ for (const key of keys) {
177
+ const normalizedKey = (() => { try { return jidNormalizedUser(key); } catch { return key; } })();
178
+ waUsername = usernameMap.get(normalizedKey);
179
+ if (waUsername) break;
180
+ }
181
+ return waUsername
182
+ ? {
183
+ ...participant,
184
+ username: waUsername,
185
+ waUsername,
186
+ displayName: waUsername,
187
+ name: participant.name,
188
+ notify: participant.notify
189
+ }
190
+ : participant;
191
+ })
192
+ };
193
+ };
11
194
  const groupMetadataCache = new Map();
12
195
  const GROUP_CACHE_TTL = (config.groupCacheTTL || 5) * 60 * 1000; // default 5 menit
13
196
  const getCachedGroupMetadata = async (jid) => {
@@ -97,13 +280,13 @@ export const makeGroupsSocket = (config) => {
97
280
  const groupMetadata = async (jid) => {
98
281
  // Cek cache dulu sebelum hit network
99
282
  const cached = await getCachedGroupMetadata(jid);
100
- if (cached) return cached;
283
+ if (cached) return await enrichGroupMetadataUsernames(cached);
101
284
  // Fetch dari WA
102
285
  const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
103
286
  const meta = extractGroupMetadata(result);
104
287
  // Simpan ke cache
105
288
  setCachedGroupMetadata(jid, meta);
106
- return meta;
289
+ return await enrichGroupMetadataUsernames(meta);
107
290
  };
108
291
  const groupFetchAllParticipating = async () => {
109
292
  const result = await query({
@@ -134,7 +317,7 @@ export const makeGroupsSocket = (config) => {
134
317
  attrs: {},
135
318
  content: [groupNode]
136
319
  });
137
- data[meta.id] = meta;
320
+ data[meta.id] = enrichGroupMetadataNames(normalizeGroupMetadataJids(meta));
138
321
  }
139
322
  }
140
323
  // TODO: properly parse LID / PN DATA
@@ -167,7 +350,7 @@ export const makeGroupsSocket = (config) => {
167
350
  }))
168
351
  }
169
352
  ]);
170
- return extractGroupMetadata(result);
353
+ return enrichGroupMetadataNames(extractGroupMetadata(result));
171
354
  },
172
355
  groupLeave: async (id) => {
173
356
  await groupQuery('@g.us', 'set', [
@@ -417,8 +600,9 @@ export const extractGroupMetadata = (result) => {
417
600
  notify: group.attrs.notify,
418
601
  addressingMode: group.attrs.addressing_mode === 'lid' ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN,
419
602
  subject: group.attrs.subject,
420
- subjectOwner: group.attrs.s_o,
421
- subjectOwnerPn: group.attrs.s_o_pn,
603
+ subjectOwner: preferPnJid(group.attrs.s_o, group.attrs.s_o_pn),
604
+ subjectOwnerPn: group.attrs.s_o_pn ? jidNormalizedUser(group.attrs.s_o_pn) : undefined,
605
+ subjectOwnerLid: isLidUser(group.attrs.s_o) ? jidNormalizedUser(group.attrs.s_o) : undefined,
422
606
  subjectTime: +group.attrs.s_t,
423
607
  size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, 'participant').length,
424
608
  creation: +group.attrs.creation,
@@ -437,23 +621,9 @@ export const extractGroupMetadata = (result) => {
437
621
  isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'),
438
622
  joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
439
623
  memberAddMode,
440
- participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
441
- const isLid = isLidUser(attrs.jid);
442
- const pn = attrs.phone_number;
443
- const hasPn = isPnUser(pn);
444
- // Jika grup pakai LID addressing:
445
- // - id → pakai phoneNumber (PN) agar bisa dicompare dengan m.sender
446
- // - lid → simpan LID asli
447
- // Jika grup pakai PN addressing: id tetap PN
448
- return {
449
- id: isLid && hasPn ? pn : attrs.jid,
450
- phoneNumber: isLid && hasPn ? pn : undefined,
451
- lid: isLid ? attrs.jid : (isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined),
452
- admin: (attrs.type || null)
453
- };
454
- }),
624
+ participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => buildGroupParticipant(attrs)),
455
625
  ephemeralDuration: eph ? +eph : undefined
456
626
  };
457
- return metadata;
627
+ return normalizeGroupMetadataJids(metadata);
458
628
  };
459
629
  //# sourceMappingURL=groups.js.map
@@ -4,6 +4,183 @@ const WAProto = WaProto.proto
4
4
  import axios from "axios"
5
5
  import crypto from 'crypto'
6
6
 
7
+ function hydraExtractHyperlink(text = '') {
8
+ const hyperlink = [];
9
+ const stack = [];
10
+ let result = '';
11
+ let last = 0;
12
+ let index = 1;
13
+ let entity = 0;
14
+
15
+ for (let i = 0; i < text.length; i++) {
16
+ if (text[i] === '[' && text[i - 1] !== '\\') {
17
+ stack.push(i);
18
+ } else if (text[i] === ']' && text[i + 1] === '(') {
19
+ const start = stack.pop();
20
+ if (start == null) continue;
21
+
22
+ let end = i + 2;
23
+ let depth = 1;
24
+
25
+ while (end < text.length && depth) {
26
+ if (text[end] === '(' && text[end - 1] !== '\\') depth++;
27
+ else if (text[end] === ')' && text[end - 1] !== '\\') depth--;
28
+ end++;
29
+ }
30
+
31
+ if (depth) continue;
32
+
33
+ const txt = text.slice(start + 1, i).trim();
34
+ const url = text.slice(i + 2, end - 1);
35
+ const reference_id = txt ? 0 : index++;
36
+ const key = `HYDRA_IE_${entity++}`;
37
+ const tag = `{{${key}}}${txt || url || 'Link'}{{/${key}}}`;
38
+
39
+ result += text.slice(last, start) + tag;
40
+ last = end;
41
+
42
+ hyperlink.push({ reference_id, key, text: txt, url });
43
+ i = end - 1;
44
+ }
45
+ }
46
+
47
+ result += text.slice(last);
48
+ return { text: result, hyperlink };
49
+ }
50
+
51
+ function hydraToTableMetadata(arr = []) {
52
+ if (!Array.isArray(arr) || arr.length < 1) {
53
+ return { title: '', rows: [], unified_rows: [] };
54
+ }
55
+
56
+ const [header = [], ...rows] = arr;
57
+ const maxLen = Math.max(header.length || 0, ...rows.map((r) => Array.isArray(r) ? r.length : 0), 1);
58
+ const normalize = (r = []) => [...r, ...Array(maxLen - r.length).fill('')].map((x) => String(x ?? ''));
59
+
60
+ const unified_rows = [
61
+ { is_header: true, cells: normalize(header) },
62
+ ...rows.map((r) => ({ is_header: false, cells: normalize(Array.isArray(r) ? r : [r]) }))
63
+ ];
64
+
65
+ return {
66
+ title: '',
67
+ rows: unified_rows.map((r) => ({
68
+ items: r.cells,
69
+ ...(r.is_header ? { isHeading: true } : {})
70
+ })),
71
+ unified_rows
72
+ };
73
+ }
74
+
75
+ function hydraTokenizeCode(code = '', lang = 'javascript') {
76
+ const keywordsMap = {
77
+ javascript: new Set([
78
+ 'break', 'case', 'catch', 'continue', 'debugger', 'delete', 'do', 'else',
79
+ 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return',
80
+ 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with',
81
+ 'true', 'false', 'null', 'undefined', 'class', 'const', 'let', 'super',
82
+ 'extends', 'export', 'import', 'yield', 'static', 'constructor', 'async',
83
+ 'await', 'get', 'set'
84
+ ]),
85
+ python: new Set([
86
+ 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
87
+ 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally',
88
+ 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
89
+ 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
90
+ ])
91
+ };
92
+
93
+ const TYPE_MAP = {
94
+ 0: 'DEFAULT',
95
+ 1: 'KEYWORD',
96
+ 2: 'METHOD',
97
+ 3: 'STR',
98
+ 4: 'NUMBER',
99
+ 5: 'COMMENT'
100
+ };
101
+
102
+ const keywords = keywordsMap[lang] || keywordsMap.javascript;
103
+ const tokens = [];
104
+ let i = 0;
105
+
106
+ const push = (content, type) => {
107
+ if (!content) return;
108
+ const last = tokens[tokens.length - 1];
109
+ if (last && last.highlightType === type) last.codeContent += content;
110
+ else tokens.push({ codeContent: content, highlightType: type });
111
+ };
112
+
113
+ while (i < code.length) {
114
+ const c = code[i];
115
+
116
+ if (/\s/.test(c)) {
117
+ const s = i;
118
+ while (i < code.length && /\s/.test(code[i])) i++;
119
+ push(code.slice(s, i), 0);
120
+ continue;
121
+ }
122
+
123
+ if ((c === '/' && code[i + 1] === '/') || (c === '#' && lang === 'python')) {
124
+ const s = i;
125
+ i += c === '#' ? 1 : 2;
126
+ while (i < code.length && code[i] !== '\n') i++;
127
+ push(code.slice(s, i), 5);
128
+ continue;
129
+ }
130
+
131
+ if (c === '"' || c === "'" || c === '`') {
132
+ const s = i;
133
+ const q = c;
134
+ i++;
135
+ while (i < code.length) {
136
+ if (code[i] === '\\' && i + 1 < code.length) i += 2;
137
+ else if (code[i] === q) {
138
+ i++;
139
+ break;
140
+ } else i++;
141
+ }
142
+ push(code.slice(s, i), 3);
143
+ continue;
144
+ }
145
+
146
+ if (/[0-9]/.test(c)) {
147
+ const s = i;
148
+ while (i < code.length && /[0-9.]/.test(code[i])) i++;
149
+ push(code.slice(s, i), 4);
150
+ continue;
151
+ }
152
+
153
+ if (/[a-zA-Z_$]/.test(c)) {
154
+ const s = i;
155
+ while (i < code.length && /[a-zA-Z0-9_$]/.test(code[i])) i++;
156
+ const word = code.slice(s, i);
157
+
158
+ let type = 0;
159
+ if (keywords.has(word)) type = 1;
160
+ else {
161
+ let j = i;
162
+ while (j < code.length && /\s/.test(code[j])) j++;
163
+ if (code[j] === '(') type = 2;
164
+ }
165
+
166
+ push(word, type);
167
+ continue;
168
+ }
169
+
170
+ push(c, 0);
171
+ i++;
172
+ }
173
+
174
+ return {
175
+ codeBlock: tokens,
176
+ unified_codeBlock: tokens.map((t) => ({
177
+ content: t.codeContent,
178
+ type: TYPE_MAP[t.highlightType] || 'DEFAULT'
179
+ }))
180
+ };
181
+ }
182
+
183
+
7
184
  class hydra {
8
185
  constructor(utils, waUploadToServer, relayMessageFn) {
9
186
  this.utils = utils;
@@ -23,6 +200,7 @@ class hydra {
23
200
  if (content.groupStatus) return 'GROUP_STATUS';
24
201
  if (content.carouselMessage || content.carousel) return 'CAROUSEL';
25
202
  if (content.stickerPack) return "STICKER_PACK";
203
+ if (content.aiRich || content.airich || content.richResponse || content.richResponseMessage || content.AIRich) return 'AI_RICH';
26
204
  return null;
27
205
  }
28
206
 
@@ -753,6 +931,305 @@ class hydra {
753
931
  messageId: this.utils.generateMessageID()
754
932
  });
755
933
  }
934
+
935
+ buildAIRichPayload(data = {}) {
936
+ const submessages = [];
937
+ const sections = [];
938
+ const richResponseSources = [];
939
+
940
+ const addText = (text = '', { hyperlink = true } = {}) => {
941
+ const extracted = hyperlink ? hydraExtractHyperlink(String(text ?? '')) : { text: String(text ?? ''), hyperlink: [] };
942
+
943
+ submessages.push({
944
+ messageType: 2,
945
+ messageText: extracted.text
946
+ });
947
+
948
+ sections.push({
949
+ view_model: {
950
+ primitive: extracted.hyperlink.length
951
+ ? {
952
+ text: extracted.text,
953
+ inline_entities: extracted.hyperlink.map(({ reference_id, key, text, url }) => ({
954
+ key,
955
+ metadata: text?.trim()
956
+ ? {
957
+ display_name: text,
958
+ is_trusted: true,
959
+ url,
960
+ __typename: 'GenAIInlineLinkItem'
961
+ }
962
+ : {
963
+ reference_id,
964
+ reference_url: url,
965
+ reference_title: url,
966
+ reference_display_name: url,
967
+ sources: [],
968
+ __typename: 'GenAISearchCitationItem'
969
+ }
970
+ })),
971
+ __typename: 'GenAIMarkdownTextUXPrimitive'
972
+ }
973
+ : {
974
+ text: String(text ?? ''),
975
+ __typename: 'GenAIMarkdownTextUXPrimitive'
976
+ },
977
+ __typename: 'GenAISingleLayoutViewModel'
978
+ }
979
+ });
980
+ };
981
+
982
+ const addCode = (language = 'javascript', code = '') => {
983
+ const meta = hydraTokenizeCode(String(code ?? ''), language);
984
+
985
+ submessages.push({
986
+ messageType: 5,
987
+ codeMetadata: {
988
+ codeLanguage: language,
989
+ codeBlocks: meta.codeBlock
990
+ }
991
+ });
992
+
993
+ sections.push({
994
+ view_model: {
995
+ primitive: {
996
+ language,
997
+ code_blocks: meta.unified_codeBlock,
998
+ __typename: 'GenAICodeUXPrimitive'
999
+ },
1000
+ __typename: 'GenAISingleLayoutViewModel'
1001
+ }
1002
+ });
1003
+ };
1004
+
1005
+ const addTable = (table = []) => {
1006
+ const meta = hydraToTableMetadata(table);
1007
+
1008
+ submessages.push({
1009
+ messageType: 4,
1010
+ tableMetadata: {
1011
+ title: meta.title,
1012
+ rows: meta.rows
1013
+ }
1014
+ });
1015
+
1016
+ sections.push({
1017
+ view_model: {
1018
+ primitive: {
1019
+ rows: meta.unified_rows,
1020
+ __typename: 'GenATableUXPrimitive'
1021
+ },
1022
+ __typename: 'GenAISingleLayoutViewModel'
1023
+ }
1024
+ });
1025
+ };
1026
+
1027
+ const addImages = (images = []) => {
1028
+ const list = Array.isArray(images) ? images : [images];
1029
+ const imageUrls = list
1030
+ .filter(Boolean)
1031
+ .map((item) => {
1032
+ const url = typeof item === 'string' ? item : item.url || item.imageUrl || item.imagePreviewUrl;
1033
+ return {
1034
+ imagePreviewUrl: url,
1035
+ imageHighResUrl: item.imageHighResUrl || item.highResUrl || url,
1036
+ sourceUrl: item.sourceUrl || data.sourceUrl || 'https://google.com'
1037
+ };
1038
+ })
1039
+ .filter((x) => x.imagePreviewUrl);
1040
+
1041
+ if (!imageUrls.length) return;
1042
+
1043
+ submessages.push({
1044
+ messageType: 1,
1045
+ gridImageMetadata: {
1046
+ gridImageUrl: {
1047
+ imagePreviewUrl: imageUrls[0].imagePreviewUrl
1048
+ },
1049
+ imageUrls
1050
+ }
1051
+ });
1052
+
1053
+ imageUrls.forEach(({ imagePreviewUrl }) => {
1054
+ sections.push({
1055
+ view_model: {
1056
+ primitive: {
1057
+ media: {
1058
+ url: imagePreviewUrl,
1059
+ mime_type: 'image/jpeg'
1060
+ },
1061
+ imagine_type: 3,
1062
+ status: {
1063
+ status: 'READY'
1064
+ },
1065
+ __typename: 'GenAIImaginePrimitive'
1066
+ },
1067
+ __typename: 'GenAISingleLayoutViewModel'
1068
+ }
1069
+ });
1070
+ });
1071
+ };
1072
+
1073
+ const addSources = (sources = []) => {
1074
+ if (!Array.isArray(sources) || !sources.length) return;
1075
+
1076
+ sections.push({
1077
+ view_model: {
1078
+ primitive: {
1079
+ sources: sources.map((source) => {
1080
+ const arr = Array.isArray(source) ? source : null;
1081
+ const profileUrl = arr ? arr[0] : source.profileIconUrl || source.faviconUrl || source.thumbnailUrl || '';
1082
+ const url = arr ? arr[1] : source.url || source.sourceUrl || source.sourceProviderURL || '';
1083
+ const text = arr ? arr[2] : source.title || source.sourceTitle || source.provider || 'Source';
1084
+
1085
+ return {
1086
+ source_type: 'THIRD_PARTY',
1087
+ source_display_name: text,
1088
+ source_subtitle: source.subtitle || 'AI',
1089
+ source_url: url,
1090
+ favicon: {
1091
+ url: profileUrl,
1092
+ mime_type: 'image/jpeg',
1093
+ width: 16,
1094
+ height: 16
1095
+ }
1096
+ };
1097
+ }),
1098
+ __typename: 'GenAISearchResultPrimitive'
1099
+ },
1100
+ __typename: 'GenAISingleLayoutViewModel'
1101
+ }
1102
+ });
1103
+ };
1104
+
1105
+ const addReels = (reelsItems = []) => {
1106
+ if (!Array.isArray(reelsItems) || !reelsItems.length) return;
1107
+
1108
+ submessages.push({
1109
+ messageType: 9,
1110
+ contentItemsMetadata: {
1111
+ contentType: 1,
1112
+ itemsMetadata: reelsItems.map((item) => ({
1113
+ reelItem: {
1114
+ title: item.title || item.creator || '',
1115
+ profileIconUrl: item.profileIconUrl || item.avatar_url || '',
1116
+ thumbnailUrl: item.thumbnailUrl || item.thumbnail_url || '',
1117
+ videoUrl: item.videoUrl || item.reels_url || item.url || '',
1118
+ }
1119
+ }))
1120
+ }
1121
+ });
1122
+
1123
+ reelsItems.forEach((item, idx) => {
1124
+ richResponseSources.push({
1125
+ provider: item.provider || 'UNKNOWN',
1126
+ thumbnailCDNURL: item.thumbnailUrl || item.thumbnail_url || '',
1127
+ sourceProviderURL: item.videoUrl || item.reels_url || item.url || '',
1128
+ sourceQuery: '',
1129
+ faviconCDNURL: item.profileIconUrl || item.avatar_url || '',
1130
+ citationNumber: idx + 1,
1131
+ sourceTitle: item.title || item.creator || `Reel ${idx + 1}`
1132
+ });
1133
+ });
1134
+
1135
+ sections.push({
1136
+ view_model: {
1137
+ primitives: reelsItems.map((item) => ({
1138
+ reels_url: item.videoUrl || item.reels_url || item.url || '',
1139
+ thumbnail_url: item.thumbnailUrl || item.thumbnail_url || '',
1140
+ creator: item.title || item.creator || '',
1141
+ avatar_url: item.profileIconUrl || item.avatar_url || '',
1142
+ reels_title: item.reels_title || item.reelsTitle || item.title || '',
1143
+ likes_count: item.likes_count || item.likesCount || 0,
1144
+ shares_count: item.shares_count || item.sharesCount || 0,
1145
+ view_count: item.view_count || item.viewCount || 0,
1146
+ reel_source: item.reel_source || item.reelSource || 'IG',
1147
+ is_verified: !!(item.is_verified ?? item.isVerified),
1148
+ __typename: 'GenAIReelPrimitive'
1149
+ })),
1150
+ __typename: 'GenAIHScrollLayoutViewModel'
1151
+ }
1152
+ });
1153
+ };
1154
+
1155
+ if (data.text) addText(data.text, { hyperlink: data.hyperlink !== false });
1156
+ if (Array.isArray(data.texts)) data.texts.forEach((text) => addText(text, { hyperlink: data.hyperlink !== false }));
1157
+
1158
+ if (data.code) {
1159
+ if (typeof data.code === 'string') addCode(data.language || 'javascript', data.code);
1160
+ else addCode(data.code.language || data.language || 'javascript', data.code.content || data.code.code || '');
1161
+ }
1162
+
1163
+ if (Array.isArray(data.codes)) {
1164
+ data.codes.forEach((item) => {
1165
+ if (typeof item === 'string') addCode(data.language || 'javascript', item);
1166
+ else addCode(item.language || data.language || 'javascript', item.content || item.code || '');
1167
+ });
1168
+ }
1169
+
1170
+ if (data.table) addTable(data.table);
1171
+ if (data.image || data.images || data.gridImage) addImages(data.images || data.gridImage || data.image);
1172
+ if (data.sources) addSources(data.sources);
1173
+ if (data.reels || data.reel) addReels(data.reels || data.reel);
1174
+
1175
+ const forwarded = data.forwarded !== false;
1176
+ const includesUnifiedResponse = data.includesUnifiedResponse !== false;
1177
+
1178
+ return {
1179
+ messageContextInfo: {
1180
+ deviceListMetadata: {},
1181
+ deviceListMetadataVersion: 2,
1182
+ botMetadata: {
1183
+ messageDisclaimerText: data.disclaimerText || data.messageDisclaimerText || '',
1184
+ pluginMetadata: {},
1185
+ richResponseSourcesMetadata: {
1186
+ sources: data.richResponseSources || richResponseSources
1187
+ }
1188
+ }
1189
+ },
1190
+ botForwardedMessage: {
1191
+ message: {
1192
+ richResponseMessage: {
1193
+ messageType: data.messageType || 1,
1194
+ submessages,
1195
+ unifiedResponse: {
1196
+ data: includesUnifiedResponse
1197
+ ? Buffer.from(JSON.stringify({
1198
+ response_id: data.responseId || crypto.randomUUID(),
1199
+ sections
1200
+ })).toString('base64')
1201
+ : ''
1202
+ },
1203
+ contextInfo: forwarded
1204
+ ? {
1205
+ forwardingScore: data.forwardingScore || 1,
1206
+ isForwarded: true,
1207
+ forwardedAiBotMessageInfo: {
1208
+ botJid: data.botJid || '0@bot'
1209
+ },
1210
+ forwardOrigin: data.forwardOrigin || 4,
1211
+ ...(data.contextInfo || {})
1212
+ }
1213
+ : (data.contextInfo || {})
1214
+ }
1215
+ }
1216
+ }
1217
+ };
1218
+ }
1219
+
1220
+ async handleAIRich(content, jid, quoted, options = {}) {
1221
+ const data = content.aiRich || content.airich || content.richResponse || content.AIRich || content;
1222
+
1223
+ const msg = this.buildAIRichPayload(data);
1224
+
1225
+ return await this.relayMessage(jid, msg, {
1226
+ messageId: data.messageId || this.utils.generateMessageID?.() || crypto.randomBytes(10).toString('hex').toUpperCase(),
1227
+ ...(quoted ? { quoted } : {}),
1228
+ ...options
1229
+ });
1230
+ }
1231
+
1232
+
756
1233
  }
757
1234
 
758
1235
  export default hydra
@@ -1270,8 +1270,35 @@ export const makeMessagesSocket = (config) => {
1270
1270
  }
1271
1271
  return album
1272
1272
  },
1273
+ sendAIRich: async (jid, content = {}, options = {}) => {
1274
+ const { quoted, filter = false } = options;
1275
+ const getParticipantAttr = () => filter ? { participant: { jid } } : {};
1276
+ return await kelvdra.handleAIRich({ aiRich: content }, jid, quoted, {
1277
+ ...getParticipantAttr()
1278
+ });
1279
+ },
1273
1280
  sendMessage: async (jid, content, options = {}) => {
1274
1281
  const userJid = authState.creds.me.id;
1282
+ const normalizeDeleteKey = async (key = {}) => {
1283
+ const normalized = { ...key };
1284
+ const resolveLid = async (value) => {
1285
+ if (!value || !isLidUser(value)) return value;
1286
+ try {
1287
+ return (await signalRepository?.lidMapping?.getPNForLID(value)) || value;
1288
+ } catch {
1289
+ return value;
1290
+ }
1291
+ };
1292
+ normalized.remoteJid = await resolveLid(normalized.remoteJid || jid);
1293
+ normalized.participant = await resolveLid(normalized.participant);
1294
+ if (isJidGroup(normalized.remoteJid || jid) && !normalized.participant) {
1295
+ normalized.participant = normalized.fromMe ? userJid : undefined;
1296
+ }
1297
+ return normalized;
1298
+ };
1299
+ if (content?.delete) {
1300
+ content = { ...content, delete: await normalizeDeleteKey(content.delete) };
1301
+ }
1275
1302
  const additionalAttributes = {}
1276
1303
  const { filter = false, quoted } = options;
1277
1304
  const getParticipantAttr = () => filter ? { participant: { jid } } : {};
@@ -1314,6 +1341,11 @@ export const makeMessagesSocket = (config) => {
1314
1341
 
1315
1342
  case 'CAROUSEL':
1316
1343
  return await kelvdra.handleCarousel(content, jid, quoted);
1344
+
1345
+ case 'AI_RICH':
1346
+ return await kelvdra.handleAIRich(content, jid, quoted, {
1347
+ ...getParticipantAttr()
1348
+ });
1317
1349
  }
1318
1350
  }
1319
1351
  if (typeof content === 'object' &&
@@ -1,6 +1,19 @@
1
1
  import type { Contact } from './Contact.js';
2
2
  import type { WAMessageAddressingMode } from './Message.js';
3
3
  export type GroupParticipant = Contact & {
4
+ /** PN/JID result. If WA returns @lid with phone_number, this is normalized to @s.whatsapp.net */
5
+ jid?: string;
6
+ /** display/notify name returned by WhatsApp participant node */
7
+ username?: string;
8
+ waUsername?: string;
9
+ name?: string;
10
+ notify?: string;
11
+ /** best display name after metadata/contact-cache enrichment */
12
+ displayName?: string;
13
+ /** original LID when WhatsApp returns LID addressing */
14
+ lid?: string;
15
+ /** PN JID when available */
16
+ phoneNumber?: string;
4
17
  isAdmin?: boolean;
5
18
  isSuperAdmin?: boolean;
6
19
  admin?: 'admin' | 'superadmin' | null;
@@ -15,17 +28,20 @@ export interface GroupMetadata {
15
28
  addressingMode?: WAMessageAddressingMode;
16
29
  owner: string | undefined;
17
30
  ownerPn?: string | undefined;
31
+ ownerLid?: string | undefined;
18
32
  owner_country_code?: string | undefined;
19
33
  subject: string;
20
34
  /** group subject owner */
21
35
  subjectOwner?: string;
22
36
  subjectOwnerPn?: string;
37
+ subjectOwnerLid?: string;
23
38
  /** group subject modification date */
24
39
  subjectTime?: number;
25
40
  creation?: number;
26
41
  desc?: string;
27
42
  descOwner?: string;
28
43
  descOwnerPn?: string;
44
+ descOwnerLid?: string;
29
45
  descId?: string;
30
46
  descTime?: number;
31
47
  /** if this group is part of a community, it returns the jid of the community to which it belongs */
@@ -2,4 +2,5 @@ export * from './USyncDeviceProtocol.js';
2
2
  export * from './USyncContactProtocol.js';
3
3
  export * from './USyncStatusProtocol.js';
4
4
  export * from './USyncDisappearingModeProtocol.js';
5
+ export * from './USyncUsernameProtocol.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -2,4 +2,5 @@ export * from './USyncDeviceProtocol.js';
2
2
  export * from './USyncContactProtocol.js';
3
3
  export * from './USyncStatusProtocol.js';
4
4
  export * from './USyncDisappearingModeProtocol.js';
5
+ export * from './USyncUsernameProtocol.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  import { getBinaryNodeChild } from '../WABinary/index.js';
2
2
  import { USyncBotProfileProtocol } from './Protocols/UsyncBotProfileProtocol.js';
3
3
  import { USyncLIDProtocol } from './Protocols/UsyncLIDProtocol.js';
4
- import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol } from './Protocols/index.js';
4
+ import { USyncContactProtocol, USyncDeviceProtocol, USyncDisappearingModeProtocol, USyncStatusProtocol, USyncUsernameProtocol } from './Protocols/index.js';
5
5
  import { USyncUser } from './USyncUser.js';
6
6
  export class USyncQuery {
7
7
  constructor() {
@@ -90,5 +90,9 @@ export class USyncQuery {
90
90
  this.protocols.push(new USyncLIDProtocol());
91
91
  return this;
92
92
  }
93
+ withUsernameProtocol() {
94
+ this.protocols.push(new USyncUsernameProtocol());
95
+ return this;
96
+ }
93
97
  }
94
98
  //# sourceMappingURL=USyncQuery.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kelvdra/baileys",
3
3
  "type": "module",
4
- "version": "1.0.5-rc.1",
4
+ "version": "1.0.5-rc.2",
5
5
  "description": "Baileys fork whiskeysockets modified by kelvdra.",
6
6
  "keywords": [
7
7
  "baileys",