@kelvdra/baileys 1.0.4 → 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.
- package/LICENSE +21 -0
- package/WAProto/index.js +65472 -137440
- package/lib/Defaults/index.d.ts +1 -1
- package/lib/Defaults/index.js +22 -3
- package/lib/Socket/chats.js +12 -13
- package/lib/Socket/groups.js +321 -18
- package/lib/Socket/hydra.js +521 -0
- package/lib/Socket/messages-recv.js +736 -324
- package/lib/Socket/messages-send.js +526 -113
- package/lib/Socket/mex.js +44 -6
- package/lib/Socket/newsletter.d.ts +16 -9
- package/lib/Socket/newsletter.js +259 -70
- package/lib/Types/GroupMetadata.d.ts +16 -0
- package/lib/Types/Mex.d.ts +141 -0
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/State.js +54 -1
- package/lib/Utils/auth-utils.js +12 -1
- package/lib/Utils/chat-utils.js +36 -2
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/decode-wa-message.js +23 -4
- package/lib/Utils/generics.js +4 -1
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +1 -1
- package/lib/Utils/message-retry-manager.js +25 -1
- package/lib/Utils/messages-media.js +162 -43
- package/lib/Utils/messages.d.ts +1 -1
- package/lib/Utils/messages.js +230 -9
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.js +45 -1
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/index.d.ts +1 -0
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/USyncQuery.js +5 -1
- package/package.json +3 -1
package/lib/Defaults/index.d.ts
CHANGED
package/lib/Defaults/index.js
CHANGED
|
@@ -2,14 +2,15 @@ import { proto } from '../../WAProto/index.js';
|
|
|
2
2
|
import { makeLibSignalRepository } from '../Signal/libsignal.js';
|
|
3
3
|
import { Browsers } from '../Utils/browser-utils.js';
|
|
4
4
|
import logger from '../Utils/logger.js';
|
|
5
|
-
const version = [2, 3000,
|
|
6
|
-
import
|
|
7
|
-
export PHONENUMBER_MCC;
|
|
5
|
+
const version = [2, 3000, 1033105955];
|
|
6
|
+
import PHONENUMBER_MCC_1 from './phonenumber-mcc.json' with { type: 'json' };
|
|
7
|
+
export const PHONENUMBER_MCC = PHONENUMBER_MCC_1;
|
|
8
8
|
export const UNAUTHORIZED_CODES = [401, 403, 419];
|
|
9
9
|
export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
|
|
10
10
|
export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
|
|
11
11
|
export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
|
|
12
12
|
export const DEF_CALLBACK_PREFIX = 'CB:';
|
|
13
|
+
export const STATUS_EXPIRY_SECONDS = 24 * 60 * 60;
|
|
13
14
|
export const DEF_TAG_PREFIX = 'TAG:';
|
|
14
15
|
export const PHONE_CONNECTION_CB = 'CB:Pong';
|
|
15
16
|
export const WA_ADV_ACCOUNT_SIG_PREFIX = Buffer.from([6, 0]);
|
|
@@ -19,6 +20,7 @@ export const WA_ADV_HOSTED_DEVICE_SIG_PREFIX = Buffer.from([6, 6]);
|
|
|
19
20
|
export const WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60;
|
|
20
21
|
export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0';
|
|
21
22
|
export const DICT_VERSION = 3;
|
|
23
|
+
export const PLACEHOLDER_MAX_AGE_SECONDS = 14 * 24 * 60 * 60;
|
|
22
24
|
export const KEY_BUNDLE_TYPE = Buffer.from([5]);
|
|
23
25
|
export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]); // last is "DICT_VERSION"
|
|
24
26
|
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
|
@@ -76,6 +78,8 @@ export const MEDIA_PATH_MAP = {
|
|
|
76
78
|
document: '/mms/document',
|
|
77
79
|
audio: '/mms/audio',
|
|
78
80
|
sticker: '/mms/image',
|
|
81
|
+
'sticker-pack': '/mms/sticker',
|
|
82
|
+
'thumbnail-sticker-pack': '/mms/sticker',
|
|
79
83
|
'thumbnail-link': '/mms/image',
|
|
80
84
|
'product-catalog-image': '/product/image',
|
|
81
85
|
'md-app-state': '',
|
|
@@ -91,6 +95,8 @@ export const MEDIA_HKDF_KEY_MAPPING = {
|
|
|
91
95
|
product: 'Image',
|
|
92
96
|
ptt: 'Audio',
|
|
93
97
|
sticker: 'Image',
|
|
98
|
+
'sticker-pack': 'Sticker Pack',
|
|
99
|
+
'thumbnail-sticker-pack': 'Sticker Pack',
|
|
94
100
|
video: 'Video',
|
|
95
101
|
'thumbnail-document': 'Document Thumbnail',
|
|
96
102
|
'thumbnail-image': 'Image Thumbnail',
|
|
@@ -115,3 +121,16 @@ export const DEFAULT_CACHE_TTLS = {
|
|
|
115
121
|
USER_DEVICES: 5 * 60 // 5 minutes
|
|
116
122
|
};
|
|
117
123
|
//# sourceMappingURL=index.js.map
|
|
124
|
+
|
|
125
|
+
// Added from Baileys v7.0.0-rc10
|
|
126
|
+
export const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120000;
|
|
127
|
+
|
|
128
|
+
// Added from Baileys v7.0.0-rc10
|
|
129
|
+
export const TimeMs = {
|
|
130
|
+
Minute: 60 * 1000,
|
|
131
|
+
Hour: 60 * 60 * 1000,
|
|
132
|
+
Day: 24 * 60 * 60 * 1000,
|
|
133
|
+
Week: 7 * 24 * 60 * 60 * 1000
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
136
|
+
|
package/lib/Socket/chats.js
CHANGED
|
@@ -166,26 +166,25 @@ export const makeChatsSocket = (config) => {
|
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
168
|
/** update the profile picture for yourself or a group */
|
|
169
|
-
const updateProfilePicture = async (jid, content) => {
|
|
170
|
-
let targetJid
|
|
171
|
-
|
|
169
|
+
const updateProfilePicture = async (jid, content, dimensions) => {
|
|
170
|
+
let targetJid;
|
|
172
171
|
if (!jid) {
|
|
173
|
-
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
|
|
172
|
+
throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update');
|
|
174
173
|
}
|
|
175
|
-
|
|
176
174
|
if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id)) {
|
|
177
|
-
targetJid = jidNormalizedUser(jid) // in case it is someone other than us
|
|
175
|
+
targetJid = jidNormalizedUser(jid); // in case it is someone other than us
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
targetJid = undefined;
|
|
178
179
|
}
|
|
179
|
-
|
|
180
|
-
const { img } = await generateProfilePicture(content)
|
|
181
|
-
|
|
180
|
+
const { img } = await generateProfilePicture(content, dimensions);
|
|
182
181
|
await query({
|
|
183
182
|
tag: 'iq',
|
|
184
183
|
attrs: {
|
|
185
|
-
target: targetJid,
|
|
186
184
|
to: S_WHATSAPP_NET,
|
|
187
185
|
type: 'set',
|
|
188
|
-
xmlns: 'w:profile:picture'
|
|
186
|
+
xmlns: 'w:profile:picture',
|
|
187
|
+
...(targetJid ? { target: targetJid } : {})
|
|
189
188
|
},
|
|
190
189
|
content: [
|
|
191
190
|
{
|
|
@@ -194,8 +193,8 @@ export const makeChatsSocket = (config) => {
|
|
|
194
193
|
content: img
|
|
195
194
|
}
|
|
196
195
|
]
|
|
197
|
-
})
|
|
198
|
-
}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
199
198
|
/** remove the profile picture for yourself or a group */
|
|
200
199
|
const removeProfilePicture = async (jid) => {
|
|
201
200
|
let targetJid;
|
package/lib/Socket/groups.js
CHANGED
|
@@ -2,10 +2,272 @@ 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;
|
|
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
|
+
});
|
|
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
|
+
};
|
|
194
|
+
const groupMetadataCache = new Map();
|
|
195
|
+
const GROUP_CACHE_TTL = (config.groupCacheTTL || 5) * 60 * 1000; // default 5 menit
|
|
196
|
+
const getCachedGroupMetadata = async (jid) => {
|
|
197
|
+
// 1. Cek user-provided cachedGroupMetadata (dari config makeWASocket)
|
|
198
|
+
if (cachedGroupMetadata) {
|
|
199
|
+
const cached = await cachedGroupMetadata(jid);
|
|
200
|
+
if (cached && Array.isArray(cached.participants)) return cached;
|
|
201
|
+
}
|
|
202
|
+
// 2. Cek internal Map cache
|
|
203
|
+
const entry = groupMetadataCache.get(jid);
|
|
204
|
+
if (entry && Date.now() - entry.ts < GROUP_CACHE_TTL) {
|
|
205
|
+
return entry.data;
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
};
|
|
209
|
+
const setCachedGroupMetadata = (jid, data) => {
|
|
210
|
+
groupMetadataCache.set(jid, { data, ts: Date.now() });
|
|
211
|
+
};
|
|
212
|
+
// Update cache saat groups.update event
|
|
213
|
+
ev.on('groups.update', (updates) => {
|
|
214
|
+
for (const update of updates) {
|
|
215
|
+
const entry = groupMetadataCache.get(update.id);
|
|
216
|
+
if (entry) {
|
|
217
|
+
// Merge update ke cache yang ada
|
|
218
|
+
groupMetadataCache.set(update.id, {
|
|
219
|
+
data: { ...entry.data, ...update },
|
|
220
|
+
ts: entry.ts
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
// Update cache saat participant berubah
|
|
226
|
+
// Debounce map to prevent duplicate refresh calls for same group
|
|
227
|
+
const _refreshDebounce = new Map();
|
|
228
|
+
const _refreshGroupMetadata = async (jid) => {
|
|
229
|
+
// Debounce: skip jika refresh sudah dijadwalkan dalam 2 detik terakhir
|
|
230
|
+
if (_refreshDebounce.has(jid)) return;
|
|
231
|
+
_refreshDebounce.set(jid, true);
|
|
232
|
+
setTimeout(() => _refreshDebounce.delete(jid), 2000);
|
|
233
|
+
try {
|
|
234
|
+
const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
|
|
235
|
+
const meta = extractGroupMetadata(result);
|
|
236
|
+
setCachedGroupMetadata(jid, meta);
|
|
237
|
+
// Emit groups.update agar subscriber luar (makeInMemoryStore dll) ikut terupdate
|
|
238
|
+
ev.emit('groups.update', [meta]);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
// Ignore jika gagal (bot mungkin sudah keluar dari grup)
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
ev.on('group-participants.update', ({ id, participants, action }) => {
|
|
244
|
+
const entry = groupMetadataCache.get(id);
|
|
245
|
+
if (entry && Array.isArray(entry.data?.participants)) {
|
|
246
|
+
// Fast-path: update cache lokal secara optimistis tanpa tunggu network
|
|
247
|
+
const meta = { ...entry.data, participants: [...entry.data.participants] };
|
|
248
|
+
if (action === 'add') {
|
|
249
|
+
const existing = new Set(meta.participants.map(p => p.id));
|
|
250
|
+
for (const jid of participants) {
|
|
251
|
+
if (!existing.has(jid)) meta.participants.push({ id: jid, admin: null });
|
|
252
|
+
}
|
|
253
|
+
} else if (action === 'remove') {
|
|
254
|
+
meta.participants = meta.participants.filter(p => !participants.includes(p.id));
|
|
255
|
+
} else if (action === 'promote') {
|
|
256
|
+
meta.participants = meta.participants.map(p =>
|
|
257
|
+
participants.includes(p.id) ? { ...p, admin: 'admin' } : p
|
|
258
|
+
);
|
|
259
|
+
} else if (action === 'demote') {
|
|
260
|
+
meta.participants = meta.participants.map(p =>
|
|
261
|
+
participants.includes(p.id) ? { ...p, admin: null } : p
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
groupMetadataCache.set(id, { data: meta, ts: entry.ts });
|
|
265
|
+
}
|
|
266
|
+
// Auto-refresh dari network untuk semua aksi participant
|
|
267
|
+
// Ini memastikan data selalu akurat dari server WA
|
|
268
|
+
_refreshGroupMetadata(id);
|
|
269
|
+
});
|
|
270
|
+
// ── End group metadata cache ───────────────────────────────────────────────
|
|
9
271
|
const groupQuery = async (jid, type, content) => query({
|
|
10
272
|
tag: 'iq',
|
|
11
273
|
attrs: {
|
|
@@ -16,8 +278,15 @@ export const makeGroupsSocket = (config) => {
|
|
|
16
278
|
content
|
|
17
279
|
});
|
|
18
280
|
const groupMetadata = async (jid) => {
|
|
281
|
+
// Cek cache dulu sebelum hit network
|
|
282
|
+
const cached = await getCachedGroupMetadata(jid);
|
|
283
|
+
if (cached) return await enrichGroupMetadataUsernames(cached);
|
|
284
|
+
// Fetch dari WA
|
|
19
285
|
const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
|
|
20
|
-
|
|
286
|
+
const meta = extractGroupMetadata(result);
|
|
287
|
+
// Simpan ke cache
|
|
288
|
+
setCachedGroupMetadata(jid, meta);
|
|
289
|
+
return await enrichGroupMetadataUsernames(meta);
|
|
21
290
|
};
|
|
22
291
|
const groupFetchAllParticipating = async () => {
|
|
23
292
|
const result = await query({
|
|
@@ -48,9 +317,10 @@ export const makeGroupsSocket = (config) => {
|
|
|
48
317
|
attrs: {},
|
|
49
318
|
content: [groupNode]
|
|
50
319
|
});
|
|
51
|
-
data[meta.id] = meta;
|
|
320
|
+
data[meta.id] = enrichGroupMetadataNames(normalizeGroupMetadataJids(meta));
|
|
52
321
|
}
|
|
53
322
|
}
|
|
323
|
+
// TODO: properly parse LID / PN DATA
|
|
54
324
|
sock.ev.emit('groups.update', Object.values(data));
|
|
55
325
|
return data;
|
|
56
326
|
};
|
|
@@ -80,7 +350,7 @@ export const makeGroupsSocket = (config) => {
|
|
|
80
350
|
}))
|
|
81
351
|
}
|
|
82
352
|
]);
|
|
83
|
-
return extractGroupMetadata(result);
|
|
353
|
+
return enrichGroupMetadataNames(extractGroupMetadata(result));
|
|
84
354
|
},
|
|
85
355
|
groupLeave: async (id) => {
|
|
86
356
|
await groupQuery('@g.us', 'set', [
|
|
@@ -237,7 +507,7 @@ export const makeGroupsSocket = (config) => {
|
|
|
237
507
|
participant: key.remoteJid
|
|
238
508
|
},
|
|
239
509
|
messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD,
|
|
240
|
-
messageStubParameters: [authState.creds.me
|
|
510
|
+
messageStubParameters: [JSON.stringify(authState.creds.me)],
|
|
241
511
|
participant: key.remoteJid,
|
|
242
512
|
messageTimestamp: unixTimestampSeconds()
|
|
243
513
|
}, 'notify');
|
|
@@ -264,7 +534,47 @@ export const makeGroupsSocket = (config) => {
|
|
|
264
534
|
{ tag: 'membership_approval_mode', attrs: {}, content: [{ tag: 'group_join', attrs: { state: mode } }] }
|
|
265
535
|
]);
|
|
266
536
|
},
|
|
267
|
-
groupFetchAllParticipating
|
|
537
|
+
groupFetchAllParticipating,
|
|
538
|
+
/**
|
|
539
|
+
* Auto detect isAdmin & isBotAdmin dari groupMetadata
|
|
540
|
+
* @param {string} groupJid - JID grup
|
|
541
|
+
* @param {string} senderJid - JID pengirim pesan
|
|
542
|
+
* @returns {Promise<{isAdmin: boolean, isBotAdmin: boolean}>}
|
|
543
|
+
*/
|
|
544
|
+
getAdminStatus: async (groupJid, senderJid) => {
|
|
545
|
+
const normalizeJid = (jid) => {
|
|
546
|
+
if (!jid) return null;
|
|
547
|
+
try {
|
|
548
|
+
return jidNormalizedUser(jid).split('@')[0];
|
|
549
|
+
} catch {
|
|
550
|
+
return String(jid).split('@')[0];
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
const botJid = sock.authState?.creds?.me?.id;
|
|
554
|
+
const meta = await sock.groupMetadata(groupJid).catch(() => null);
|
|
555
|
+
if (!meta || !Array.isArray(meta.participants)) {
|
|
556
|
+
return { isAdmin: false, isBotAdmin: false };
|
|
557
|
+
}
|
|
558
|
+
const senderNorm = normalizeJid(senderJid);
|
|
559
|
+
const botNorm = normalizeJid(botJid);
|
|
560
|
+
const isAdmin = meta.participants.some(p => {
|
|
561
|
+
const pid = normalizeJid(p.jid || p.id || p.lid);
|
|
562
|
+
return pid === senderNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
563
|
+
});
|
|
564
|
+
// Cek isBotAdmin: via participants
|
|
565
|
+
let isBotAdmin = meta.participants.some(p => {
|
|
566
|
+
const pid = normalizeJid(p.jid || p.id || p.lid);
|
|
567
|
+
return pid === botNorm && (p.admin === 'admin' || p.admin === 'superadmin');
|
|
568
|
+
});
|
|
569
|
+
// Fallback: cek via owner/subjectOwner
|
|
570
|
+
if (!isBotAdmin) {
|
|
571
|
+
const owners = [meta.owner, meta.subjectOwner, meta.ownerPn]
|
|
572
|
+
.filter(Boolean)
|
|
573
|
+
.map(normalizeJid);
|
|
574
|
+
if (owners.includes(botNorm)) isBotAdmin = true;
|
|
575
|
+
}
|
|
576
|
+
return { isAdmin, isBotAdmin };
|
|
577
|
+
}
|
|
268
578
|
};
|
|
269
579
|
};
|
|
270
580
|
export const extractGroupMetadata = (result) => {
|
|
@@ -290,8 +600,9 @@ export const extractGroupMetadata = (result) => {
|
|
|
290
600
|
notify: group.attrs.notify,
|
|
291
601
|
addressingMode: group.attrs.addressing_mode === 'lid' ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN,
|
|
292
602
|
subject: group.attrs.subject,
|
|
293
|
-
subjectOwner: group.attrs.s_o,
|
|
294
|
-
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,
|
|
295
606
|
subjectTime: +group.attrs.s_t,
|
|
296
607
|
size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, 'participant').length,
|
|
297
608
|
creation: +group.attrs.creation,
|
|
@@ -310,17 +621,9 @@ export const extractGroupMetadata = (result) => {
|
|
|
310
621
|
isCommunityAnnounce: !!getBinaryNodeChild(group, 'default_sub_group'),
|
|
311
622
|
joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
|
|
312
623
|
memberAddMode,
|
|
313
|
-
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) =>
|
|
314
|
-
// TODO: Store LID MAPPINGS
|
|
315
|
-
return {
|
|
316
|
-
id: attrs.jid,
|
|
317
|
-
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
318
|
-
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
319
|
-
admin: (attrs.type || null)
|
|
320
|
-
};
|
|
321
|
-
}),
|
|
624
|
+
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => buildGroupParticipant(attrs)),
|
|
322
625
|
ephemeralDuration: eph ? +eph : undefined
|
|
323
626
|
};
|
|
324
|
-
return metadata;
|
|
627
|
+
return normalizeGroupMetadataJids(metadata);
|
|
325
628
|
};
|
|
326
629
|
//# sourceMappingURL=groups.js.map
|