This commit is contained in:
1 parent
b6ba3951da
commit
1496274a99
5 files changed
+280
-32
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { WebJSPresence } from '@waha/core/engines/webjs/types';
|
||||
import { GetSerialized } from '@waha/core/utils/serialized';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import { Label } from '@waha/structures/labels.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
@@ -75,7 +76,7 @@ function extractReactionsByMessageKey(
|
||||
): Map<string, ChannelMessageReaction[]> {
|
||||
const reactions = new Map();
|
||||
for (const reaction of newsletterReactions) {
|
||||
const key = reaction.parentMsgKey._serialized;
|
||||
const key = GetSerialized(reaction.parentMsgKey);
|
||||
const emojiCountMap = reaction.emojiCountMap;
|
||||
const reactionList: ChannelMessageReaction[] = [];
|
||||
for (const emoji in emojiCountMap) {
|
||||
@@ -266,7 +267,8 @@ export class WebjsClientCore extends Client {
|
||||
// Filter chats by IDs if filter is provided
|
||||
if (filter && filter.ids && filter.ids.length > 0) {
|
||||
chats = chats.filter((chat) =>
|
||||
filter.ids.includes(chat.id._serialized),
|
||||
// @ts-ignore
|
||||
filter.ids.includes(window.WWebJS.GetSerialized(chat.id)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -382,7 +384,10 @@ export class WebjsClientCore extends Client {
|
||||
// Construct the initial anchor the same way wa-js does:
|
||||
// serialize to string then reconstruct via MsgKey.fromString so the
|
||||
// object has the exact shape msgFindByDirection expects.
|
||||
const lastReceivedSerialized = chat.lastReceivedKey?._serialized;
|
||||
// @ts-ignore
|
||||
const lastReceivedSerialized = window.WWebJS.GetSerialized(
|
||||
chat.lastReceivedKey,
|
||||
);
|
||||
if (!lastReceivedSerialized) return [];
|
||||
let currentAnchorKey = window
|
||||
.require('WAWebMsgKey')
|
||||
@@ -406,7 +411,8 @@ export class WebjsClientCore extends Client {
|
||||
// @ts-ignore
|
||||
const toModel = (m) => {
|
||||
if (m && typeof m.serialize === 'function') return m;
|
||||
const serializedId = m?.id?._serialized;
|
||||
// @ts-ignore
|
||||
const serializedId = window.WWebJS.GetSerialized(m?.id);
|
||||
const Msg = window.require('WAWebCollections').Msg;
|
||||
if (serializedId) {
|
||||
const stored = Msg.get(serializedId);
|
||||
@@ -437,7 +443,8 @@ export class WebjsClientCore extends Client {
|
||||
// appear in multiple batches when anchors overlap
|
||||
const seenIds = new Set();
|
||||
msgs = msgs.filter((m) => {
|
||||
const sid = m?.id?._serialized;
|
||||
// @ts-ignore
|
||||
const sid = window.WWebJS.GetSerialized(m?.id);
|
||||
if (!sid || seenIds.has(sid)) return false;
|
||||
seenIds.add(sid);
|
||||
return true;
|
||||
@@ -463,7 +470,10 @@ export class WebjsClientCore extends Client {
|
||||
// the oldest message in this batch — use it as the next anchor to
|
||||
// walk further back in history without overlap
|
||||
const oldestInBatch = batchModels[batchModels.length - 1];
|
||||
const oldestSerialized = oldestInBatch?.id?._serialized;
|
||||
// @ts-ignore
|
||||
const oldestSerialized = window.WWebJS.GetSerialized(
|
||||
oldestInBatch?.id,
|
||||
);
|
||||
if (!oldestSerialized) break;
|
||||
currentAnchorKey = window
|
||||
.require('WAWebMsgKey')
|
||||
@@ -532,9 +542,9 @@ export class WebjsClientCore extends Client {
|
||||
const result = values.map((map) => {
|
||||
return {
|
||||
// @ts-ignore
|
||||
lid: map.lid._serialized,
|
||||
lid: window.WWebJS.GetSerialized(map.lid),
|
||||
// @ts-ignore
|
||||
pn: map.phoneNumber._serialized,
|
||||
pn: window.WWebJS.GetSerialized(map.phoneNumber),
|
||||
};
|
||||
});
|
||||
// @ts-ignore
|
||||
@@ -565,7 +575,8 @@ export class WebjsClientCore extends Client {
|
||||
|
||||
const wid = WAWebWidFactory.createWid(lid);
|
||||
const result = WAWebApiContact.getPhoneNumber(wid);
|
||||
return result ? result._serialized : null;
|
||||
// @ts-ignore
|
||||
return window.WWebJS.GetSerialized(result);
|
||||
}, lid);
|
||||
return pn;
|
||||
}
|
||||
@@ -577,7 +588,8 @@ export class WebjsClientCore extends Client {
|
||||
|
||||
const wid = WAWebWidFactory.createWid(pn);
|
||||
const result = WAWebApiContact.getCurrentLid(wid);
|
||||
return result ? result._serialized : null;
|
||||
// @ts-ignore
|
||||
return window.WWebJS.GetSerialized(result);
|
||||
}, phoneNumber)) as any;
|
||||
return lid;
|
||||
}
|
||||
@@ -617,7 +629,8 @@ export class WebjsClientCore extends Client {
|
||||
}
|
||||
return chatstates.map((chatstate) => {
|
||||
return {
|
||||
participant: chatstate.id._serialized,
|
||||
// @ts-ignore
|
||||
participant: window.WWebJS.GetSerialized(chatstate.id),
|
||||
lastSeen: chatstate.t,
|
||||
state: chatstate.type,
|
||||
};
|
||||
@@ -681,7 +694,7 @@ export class WebjsClientCore extends Client {
|
||||
const messages: WebjsChannelMessage[] = messageInstances.map((msg) => {
|
||||
return {
|
||||
message: msg,
|
||||
reactions: reactions.get(msg.id._serialized) || [],
|
||||
reactions: reactions.get(GetSerialized(msg.id)) || [],
|
||||
viewCount: msg.rawData.viewCount,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
GroupParticipant as WEBJSGroupParticipant,
|
||||
} from 'whatsapp-web.js';
|
||||
import { isPnUser } from '@waha/core/utils/jids';
|
||||
import { GetSerialized } from '@waha/core/utils/serialized';
|
||||
|
||||
function ToGroupInfo(
|
||||
group: GroupChat,
|
||||
@@ -29,7 +30,7 @@ function ToGroupInfo(
|
||||
const groupMetadata = group.groupMetadata;
|
||||
const info: GroupInfo = {
|
||||
// @ts-ignore
|
||||
id: group.id._serialized,
|
||||
id: GetSerialized(group.id),
|
||||
subject: group.name,
|
||||
description: group.description,
|
||||
invite: invite,
|
||||
@@ -72,11 +73,10 @@ export function getParticipants(
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
}
|
||||
|
||||
const participantId = GetSerialized(participant.id);
|
||||
return {
|
||||
id: participant.id._serialized,
|
||||
pn: isPnUser(participant.id._serialized)
|
||||
? participant.id._serialized
|
||||
: null,
|
||||
id: participantId,
|
||||
pn: isPnUser(participantId) ? participantId : null,
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -182,6 +182,7 @@ import {
|
||||
Message as MessageInstance,
|
||||
Call as CallInstance,
|
||||
} from 'whatsapp-web.js/src/structures';
|
||||
import { GetSerialized } from '@waha/core/utils/serialized';
|
||||
|
||||
import { WAJSPresenceChatStateType, WebJSPresence } from './types';
|
||||
import { WebJSAuthFactory } from './WebJSAuthFactory';
|
||||
@@ -531,7 +532,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
const wid = clientInfo.wid;
|
||||
return {
|
||||
id: wid?._serialized,
|
||||
id: GetSerialized(wid),
|
||||
pushName: clientInfo?.pushname,
|
||||
};
|
||||
}
|
||||
@@ -738,7 +739,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
return {
|
||||
numberExists: true,
|
||||
chatId: result._serialized,
|
||||
chatId: GetSerialized(result),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1135,14 +1136,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected async fetchChatSummary(chat: Chat): Promise<ChatSummary> {
|
||||
const picture = await this.getContactProfilePicture(
|
||||
chat.id._serialized,
|
||||
GetSerialized(chat.id),
|
||||
false,
|
||||
);
|
||||
const lastMessage = chat.lastMessage
|
||||
? this.toWAMessage(chat.lastMessage)
|
||||
: null;
|
||||
return {
|
||||
id: chat.id._serialized,
|
||||
id: GetSerialized(chat.id),
|
||||
name: chat.name || null,
|
||||
picture: picture,
|
||||
lastMessage: lastMessage,
|
||||
@@ -1248,7 +1249,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
// @ts-ignore
|
||||
message.rawData.receipts = await message.getInfo().catch((error) => {
|
||||
this.logger.error(
|
||||
{ error: error, msg: message.id._serialized },
|
||||
{ error: error, msg: GetSerialized(message.id) },
|
||||
'Failed to get receipts',
|
||||
);
|
||||
return null;
|
||||
@@ -1713,7 +1714,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
role = ChannelRole.GUEST;
|
||||
}
|
||||
return {
|
||||
id: chat.id._serialized,
|
||||
id: GetSerialized(chat.id),
|
||||
name: chat.name,
|
||||
description: chat.description,
|
||||
invite: getChannelInviteLink(metadata.inviteCode),
|
||||
@@ -2212,10 +2213,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
},
|
||||
);
|
||||
const chatsArchived$ = chatArchived$.pipe(
|
||||
filter((event: any) => this.jids.include(event?.chat?.id?._serialized)),
|
||||
filter((event: any) => this.jids.include(GetSerialized(event?.chat?.id))),
|
||||
map((event) => {
|
||||
return {
|
||||
id: event.chat.id._serialized,
|
||||
id: GetSerialized(event.chat.id),
|
||||
archived: event.archived,
|
||||
timestamp: event.chat.timestamp,
|
||||
};
|
||||
@@ -2302,7 +2303,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
const source = this.getMessageSource(reaction.id.id);
|
||||
return {
|
||||
id: reaction.id._serialized,
|
||||
id: GetSerialized(reaction.id),
|
||||
from: normalizeJid(reaction.senderId),
|
||||
fromMe: reaction.id.fromMe,
|
||||
source: source,
|
||||
@@ -2311,13 +2312,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
timestamp: reaction.timestamp,
|
||||
reaction: {
|
||||
text: reaction.reaction,
|
||||
messageId: reaction.msgId._serialized,
|
||||
messageId: GetSerialized(reaction.msgId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private toPollVotePayload(vote: WebjsPollVote): PollVotePayload | null {
|
||||
const pollMessageId = vote?.parentMessage?.id?._serialized;
|
||||
const pollMessageId = GetSerialized(vote?.parentMessage?.id);
|
||||
if (!pollMessageId) {
|
||||
return null;
|
||||
}
|
||||
@@ -2397,10 +2398,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
protected toWAMessage(message: Message): WAMessage {
|
||||
const replyTo = this.extractReplyTo(message);
|
||||
const source = this.getMessageSource(message.id.id);
|
||||
const key = parseMessageIdSerialized(message.id._serialized);
|
||||
const key = parseMessageIdSerialized(GetSerialized(message.id));
|
||||
// @ts-ignore
|
||||
return {
|
||||
id: message.id._serialized,
|
||||
id: GetSerialized(message.id),
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
@@ -2474,7 +2475,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected toWAContact(contact: Contact) {
|
||||
// @ts-ignore
|
||||
contact.id = contact.id._serialized;
|
||||
contact.id = GetSerialized(contact.id);
|
||||
return contact;
|
||||
}
|
||||
|
||||
@@ -2552,7 +2553,7 @@ export class WEBJSEngineMediaProcessor
|
||||
}
|
||||
|
||||
getMessageId(message: Message): string {
|
||||
return message.id._serialized;
|
||||
return GetSerialized(message.id);
|
||||
}
|
||||
|
||||
getMimetype(message: Message): string {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
GetSerialized,
|
||||
GetSerializedMsgKey,
|
||||
GetSerializedWid,
|
||||
} from './serialized';
|
||||
|
||||
describe('serialized helpers', () => {
|
||||
describe('GetSerializedWid', () => {
|
||||
it('returns strings and nullish values as-is', () => {
|
||||
expect(GetSerializedWid('123@c.us')).toBe('123@c.us');
|
||||
expect(GetSerializedWid(null)).toBeNull();
|
||||
expect(GetSerializedWid(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('uses existing _serialized', () => {
|
||||
expect(GetSerializedWid({ _serialized: '123@c.us' })).toBe('123@c.us');
|
||||
});
|
||||
|
||||
it('ignores the minified $1 property and reconstructs from keys', () => {
|
||||
const wid: any = { $1: 'ignored', user: '123', server: 'c.us' };
|
||||
expect(GetSerializedWid(wid)).toBe('123@c.us');
|
||||
expect(wid._serialized).toBe('123@c.us');
|
||||
});
|
||||
|
||||
it('reconstructs from user/server and caches', () => {
|
||||
const wid: any = { user: '123', server: 'c.us' };
|
||||
expect(GetSerializedWid(wid)).toBe('123@c.us');
|
||||
expect(wid._serialized).toBe('123@c.us');
|
||||
});
|
||||
|
||||
it('reconstructs a group wid', () => {
|
||||
expect(GetSerializedWid({ user: '123-456', server: 'g.us' })).toBe(
|
||||
'123-456@g.us',
|
||||
);
|
||||
});
|
||||
|
||||
it('appends the device when present and truthy', () => {
|
||||
expect(GetSerializedWid({ user: '123', server: 'c.us', device: 7 })).toBe(
|
||||
'123:7@c.us',
|
||||
);
|
||||
expect(GetSerializedWid({ user: '123', server: 'c.us', device: 0 })).toBe(
|
||||
'123@c.us',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles the special "call" wid', () => {
|
||||
expect(GetSerializedWid({ user: 'call', server: 'call' })).toBe('call');
|
||||
});
|
||||
|
||||
it('returns null when it cannot reconstruct (no keys, only $1)', () => {
|
||||
expect(GetSerializedWid({ foo: 'bar' })).toBeNull();
|
||||
expect(GetSerializedWid({ $1: '123@c.us' })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSerializedMsgKey', () => {
|
||||
it('reconstructs fromMe_remote_id and caches', () => {
|
||||
const key: any = {
|
||||
fromMe: true,
|
||||
remote: { _serialized: '123@c.us' },
|
||||
id: 'AAA',
|
||||
};
|
||||
expect(GetSerializedMsgKey(key)).toBe('true_123@c.us_AAA');
|
||||
expect(key._serialized).toBe('true_123@c.us_AAA');
|
||||
});
|
||||
|
||||
it('reconstructs a remote wid that only has component keys', () => {
|
||||
const key: any = {
|
||||
fromMe: false,
|
||||
remote: { user: '123', server: 'c.us' },
|
||||
id: 'AAA',
|
||||
};
|
||||
expect(GetSerializedMsgKey(key)).toBe('false_123@c.us_AAA');
|
||||
});
|
||||
|
||||
it('appends the participant', () => {
|
||||
const key: any = {
|
||||
fromMe: false,
|
||||
remote: { _serialized: '123-456@g.us' },
|
||||
id: 'AAA',
|
||||
participant: { _serialized: '789@c.us' },
|
||||
};
|
||||
expect(GetSerializedMsgKey(key)).toBe('false_123-456@g.us_AAA_789@c.us');
|
||||
});
|
||||
|
||||
it('appends self before participant', () => {
|
||||
const key: any = {
|
||||
fromMe: true,
|
||||
remote: { _serialized: '123@c.us' },
|
||||
id: 'AAA',
|
||||
self: 'out',
|
||||
participant: { _serialized: '789@c.us' },
|
||||
};
|
||||
expect(GetSerializedMsgKey(key)).toBe('true_123@c.us_AAA_out_789@c.us');
|
||||
});
|
||||
|
||||
it('ignores $1 and reconstructs from keys', () => {
|
||||
const key: any = {
|
||||
$1: 'ignored',
|
||||
fromMe: true,
|
||||
remote: { _serialized: '999@c.us' },
|
||||
id: 'BBB',
|
||||
};
|
||||
expect(GetSerializedMsgKey(key)).toBe('true_999@c.us_BBB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetSerialized (dispatcher)', () => {
|
||||
it('dispatches wid shapes', () => {
|
||||
expect(GetSerialized({ user: '123', server: 'c.us' })).toBe('123@c.us');
|
||||
});
|
||||
|
||||
it('dispatches msgkey shapes', () => {
|
||||
expect(
|
||||
GetSerialized({
|
||||
fromMe: true,
|
||||
remote: { user: '123', server: 'c.us' },
|
||||
id: 'AAA',
|
||||
}),
|
||||
).toBe('true_123@c.us_AAA');
|
||||
});
|
||||
|
||||
it('honours existing _serialized before dispatching', () => {
|
||||
expect(GetSerialized({ _serialized: 'x@c.us' })).toBe('x@c.us');
|
||||
});
|
||||
|
||||
it('returns null for unknown shapes', () => {
|
||||
expect(GetSerialized({ foo: 'bar' })).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Serialized-ID helpers for the WEBJS engine.
|
||||
*
|
||||
* COPIED from whatsapp-web.js `src/util/Serialized.js` (fork: ../whatsapp-web.js).
|
||||
* Keep this file in sync with that upstream version whenever it changes.
|
||||
*
|
||||
* WhatsApp Web renamed the `_serialized` property on its ID objects (Wid /
|
||||
* MsgKey) to a minified name (`$1`) in the 2026-07 update. Rather than depend on
|
||||
* that unstable minified name, each helper reconstructs the serialized string
|
||||
* deterministically from the object's own component keys - existing
|
||||
* `_serialized` -> reconstruct from keys - and caches it back onto the object as
|
||||
* `_serialized` so later reads keep working.
|
||||
*/
|
||||
|
||||
interface CacheHit {
|
||||
done: boolean;
|
||||
value: string | null;
|
||||
}
|
||||
|
||||
function cached(id: any): CacheHit {
|
||||
if (id == null) {
|
||||
return { done: true, value: id };
|
||||
}
|
||||
if (typeof id === 'string') {
|
||||
return { done: true, value: id };
|
||||
}
|
||||
if (typeof id._serialized === 'string' && id._serialized !== '') {
|
||||
return { done: true, value: id._serialized };
|
||||
}
|
||||
return { done: false, value: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Wid (WhatsApp ID): `user[:device]@server`.
|
||||
*/
|
||||
export function GetSerializedWid(id: any): string | null {
|
||||
const hit = cached(id);
|
||||
if (hit.done) {
|
||||
return hit.value;
|
||||
}
|
||||
|
||||
let value: string | null;
|
||||
if (id.user != null && id.server != null) {
|
||||
value =
|
||||
id.user === 'call'
|
||||
? 'call'
|
||||
: `${id.user}${id.device ? `:${id.device}` : ''}@${id.server}`;
|
||||
} else {
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
id._serialized = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a MsgKey (message ID): `fromMe_remote_id[_self][_participant]`.
|
||||
*/
|
||||
export function GetSerializedMsgKey(id: any): string | null {
|
||||
const hit = cached(id);
|
||||
if (hit.done) {
|
||||
return hit.value;
|
||||
}
|
||||
|
||||
let value: string | null;
|
||||
if (id.remote != null && id.id != null) {
|
||||
const remote = GetSerializedWid(id.remote);
|
||||
const participant =
|
||||
id.participant != null ? GetSerializedWid(id.participant) : null;
|
||||
value =
|
||||
`${id.fromMe ? 'true' : 'false'}_${remote}_${id.id}` +
|
||||
(id.self ? `_${id.self}` : '') +
|
||||
(participant ? `_${participant}` : '');
|
||||
} else {
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
id._serialized = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic entry point: resolves `_serialized`/`$1` then dispatches to the Wid
|
||||
* or MsgKey helper based on the object's shape. Use the specific helpers when
|
||||
* the id type is known.
|
||||
*/
|
||||
export function GetSerialized(id: any): string | null {
|
||||
const hit = cached(id);
|
||||
if (hit.done) {
|
||||
return hit.value;
|
||||
}
|
||||
if (id.remote != null && id.id != null) {
|
||||
return GetSerializedMsgKey(id);
|
||||
}
|
||||
if (id.user != null && id.server != null) {
|
||||
return GetSerializedWid(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user