fix(WEBJS): fix presence.update

This commit is contained in:
devlikepro committed 2026-09-11 12:38:39 +07:00
1 parent b5702f1a48
commit 8e36c7a14c
4 files changed
+124 -87

No files matched your search

+105 -2
View File
@@ -1,4 +1,7 @@
import { WebJSPresence } from '@waha/core/engines/webjs/types';
import {
WebJSPresence,
WebJSPresenceUpdate,
} 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';
@@ -198,11 +201,111 @@ export class WebjsClientCore extends Client {
return;
},
);
await exposeFunctionIfAbsent(
this.pupPage,
'onPresenceUpdate',
(data: WebJSPresenceUpdate) => {
this.events.emit('presence.update', data);
return;
},
);
await this.attachPresenceEvents();
if (this.tags) {
await this.attachTagsEvents();
}
}
/**
* Presence lands in PresenceCollection on the main thread even when WhatsApp runs comms in a worker,
* so listen to the models instead of raw stanzas
*/
async attachPresenceEvents() {
await this.pupPage.evaluate(() => {
// @ts-ignore
if (window.presenceEventsOn) {
return;
}
// @ts-ignore
window.presenceEventsOn = true;
const d = require;
const PresenceCollection = d(
'WAWebPresenceCollection',
).PresenceCollection;
const WAWebApiContact = d('WAWebApiContact');
// Prefer the phone number id when the LID mapping is known
const toId = (wid) => {
let id = wid;
if (wid.isLid()) {
id = WAWebApiContact.getPhoneNumber(wid) ?? wid;
}
// @ts-ignore
return window.WWebJS.GetSerialized(id);
};
const toPresences = (presence) => {
if (!presence.isGroup) {
return [
{
participant: toId(presence.id),
lastSeen: presence.chatstate.t,
state: presence.chatstate.type,
},
];
}
return presence.chatstates
.getModelsArray()
.filter((chatstate) => !!chatstate.type)
.map((chatstate) => {
return {
participant: toId(chatstate.id),
lastSeen: chatstate.t,
state: chatstate.type,
};
});
};
// type and t change in one set() - coalesce into a single event per presence
const pending = new Set<any>();
const flush = () => {
for (const presence of pending) {
// @ts-ignore
window.onPresenceUpdate({
id: toId(presence.id),
presences: toPresences(presence),
});
}
pending.clear();
};
const findPresence = (chatstate) => {
const presence = PresenceCollection.get(chatstate.id);
if (presence && presence.chatstate === chatstate) {
return presence;
}
// Group chatstate id is the participant, not the group
return PresenceCollection.getModelsArray().find(
(model) => model.chatstate === chatstate,
);
};
PresenceCollection.on(
'change:chatstate.type change:chatstate.t',
(chatstate) => {
const presence = findPresence(chatstate);
if (!presence || !presence.hasData) {
return;
}
if (pending.size === 0) {
queueMicrotask(flush);
}
pending.add(presence);
},
);
});
}
async attachTagsEvents() {
await this.pupPage.evaluate(() => {
// @ts-ignore
@@ -210,7 +313,7 @@ export class WebjsClientCore extends Client {
return;
}
const tags = ['receipt', 'presence', 'chatstate'];
const tags = ['receipt'];
const WAWap = window.require('WAWap');
// @ts-ignore
window.decodeStanzaBack = WAWap.decodeStanza;
-67
View File
@@ -1,67 +0,0 @@
import type { BinaryNode, WAPresence } from '@adiwajshing/baileys';
import { WAHAPresenceStatus } from '@waha/structures/enums.dto';
import { WAHAChatPresences } from '@waha/structures/presence.dto';
import { jid } from './ack.webjs';
import { toCusFormat } from '@waha/core/utils/jids';
export function TagPresenceToPresence(node: BinaryNode): WAHAChatPresences {
const { attrs } = node;
const id = jid(attrs.from);
const state =
attrs.type === 'unavailable'
? WAHAPresenceStatus.OFFLINE
: WAHAPresenceStatus.ONLINE;
const lastSeen = attrs.last && attrs.last !== 'deny' ? +attrs.last : null;
return {
id: toCusFormat(id),
presences: [
{
participant: toCusFormat(id),
lastKnownPresence: state,
lastSeen: lastSeen,
},
],
};
}
export function TagChatstateToPresence(node: BinaryNode): WAHAChatPresences {
const { attrs, content } = node;
const id = jid(attrs.from);
const participant = jid(attrs.participant) || jid(attrs.from);
const firstChild = content[0] as BinaryNode;
const type = firstChild.tag as WAPresence;
let status = WAHAPresenceStatus.OFFLINE;
switch (type) {
case 'unavailable':
status = WAHAPresenceStatus.OFFLINE;
break;
case 'available':
status = WAHAPresenceStatus.ONLINE;
break;
case 'paused':
status = WAHAPresenceStatus.PAUSED;
break;
case 'composing':
status = WAHAPresenceStatus.TYPING;
break;
}
if (firstChild.attrs?.media === 'audio') {
status = WAHAPresenceStatus.RECORDING;
}
return {
id: toCusFormat(id),
presences: [
{
participant: toCusFormat(participant),
lastKnownPresence: status,
lastSeen: null,
},
],
};
}
+14 -18
View File
@@ -24,10 +24,6 @@ import {
ToGroupV2ParticipantsEvent,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/webjs/groups.webjs';
import {
TagChatstateToPresence,
TagPresenceToPresence,
} from '@waha/core/engines/webjs/presence';
import {
WebjsChannelMessage,
WebjsClientCore,
@@ -208,7 +204,11 @@ import {
} from 'whatsapp-web.js/src/structures';
import { GetSerialized } from '@waha/core/utils/serialized';
import { WAJSPresenceChatStateType, WebJSPresence } from './types';
import {
WAJSPresenceChatStateType,
WebJSPresence,
WebJSPresenceUpdate,
} from './types';
import { WebJSAuthFactory } from './WebJSAuthFactory';
import {
isJidCus,
@@ -351,7 +351,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected getWebjsTagsFlag() {
// Emit 'tag:*' events only when explicitly enabled in session config.
// This flag is required for presence.update and message.ack events.
// This flag is required for message.ack events.
// Disabled by default for performance and stability reasons.
return !!this.sessionConfig?.webjs?.tagsEventsOn;
}
@@ -2504,19 +2504,15 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
//
// Presence
//
const tagPresenceNode$ = fromEvent(this.whatsapp, Events.TAG_PRESENCE);
const presences$ = tagPresenceNode$.pipe(
map(TagPresenceToPresence),
filter(Boolean),
filter((presence: any) => this.jids.include(presence.id)),
const presenceUpdate$ = fromEvent(
this.whatsapp.events,
'presence.update',
).pipe(
map((data: WebJSPresenceUpdate) =>
this.toWahaPresences(data.id, data.presences),
),
filter((presence) => this.jids.include(presence.id)),
);
const tagChatstateNode$ = fromEvent(this.whatsapp, 'tag:chatstate');
const chatstatePresences$ = tagChatstateNode$.pipe(
map(TagChatstateToPresence),
filter(Boolean),
filter((presence: any) => this.jids.include(presence.id)),
);
const presenceUpdate$ = merge(presences$, chatstatePresences$);
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(presenceUpdate$);
//
+5
View File
@@ -10,3 +10,8 @@ export interface WebJSPresence {
lastSeen?: number;
state: WAJSPresenceChatStateType;
}
export interface WebJSPresenceUpdate {
id: string;
presences: WebJSPresence[];
}