[core] GOWS - add call events

fix #1673
This commit is contained in:
devlikepro committed 2025-12-05 00:13:12 -08:00
1 parent 449df53c19
commit a8b520012f
1 file changed
+108 -1
+108 -1
View File
@@ -123,6 +123,7 @@ import {
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { CallData } from '@waha/structures/calls.dto';
import { MeInfo, ProxyConfig } from '@waha/structures/sessions.dto';
import {
BROADCAST_ID,
@@ -142,6 +143,7 @@ import { onlyEvent } from '@waha/utils/reactive/ops/onlyEvent';
import * as NodeCache from 'node-cache';
import {
filter,
groupBy,
merge,
mergeMap,
Observable,
@@ -150,7 +152,7 @@ import {
share,
Subject,
} from 'rxjs';
import { map } from 'rxjs/operators';
import { map, debounceTime } from 'rxjs/operators';
import { promisify } from 'util';
import * as gows from './types';
@@ -208,6 +210,12 @@ enum WhatsMeowEvent {
EVENT_MESSAGE_RESPONSE = 'gows.EventMessageResponse',
// Polls
POLL_VOTE_EVENT = 'gows.PollVoteEvent',
// Calls
CALL_OFFER = 'events.CallOffer',
CALL_ACCEPT = 'events.CallAccept',
CALL_REJECT = 'events.CallReject',
CALL_TERMINATE = 'events.CallTerminate',
CALL_OFFER_NOTICE = 'events.CallOfferNotice',
}
export interface GowsConfig {
@@ -542,6 +550,52 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
);
this.events2.get(WAHAEvents.MESSAGE_REACTION).switch(messageReactions$);
//
// Calls
//
const callOffer$ = all$.pipe(
onlyEvent(WhatsMeowEvent.CALL_OFFER),
filter(this.shouldProcessCallEvent.bind(this)),
map(this.toCallData.bind(this)),
);
const callOfferNotice$ = all$.pipe(
onlyEvent(WhatsMeowEvent.CALL_OFFER_NOTICE),
filter(this.shouldProcessCallEvent.bind(this)),
map(this.toCallData.bind(this)),
);
this.events2
.get(WAHAEvents.CALL_RECEIVED)
.switch(merge(callOffer$, callOfferNotice$));
const callAccept$ = all$.pipe(
onlyEvent(WhatsMeowEvent.CALL_ACCEPT),
filter(this.shouldProcessCallEvent.bind(this)),
map(this.toCallData.bind(this)),
);
this.events2.get(WAHAEvents.CALL_ACCEPTED).switch(callAccept$);
const callReject$ = all$.pipe(
onlyEvent(WhatsMeowEvent.CALL_REJECT),
filter(this.shouldProcessCallEvent.bind(this)),
);
const callTerminate$ = all$.pipe(
onlyEvent(WhatsMeowEvent.CALL_TERMINATE),
filter(this.shouldProcessCallEvent.bind(this)),
// Skip terminate events that only mean the call was accepted or rejected on another device
filter(
(call: any) =>
call?.Reason !== 'accepted_elsewhere' &&
call?.Reason !== 'rejected_elsewhere',
),
);
// Debounce per call to collapse duplicate reject/terminate events
const callRejected$ = merge(callReject$, callTerminate$).pipe(
groupBy((call: any) => this.getCallId(call) || 'unknown'),
mergeMap((group$) => group$.pipe(debounceTime(1_000))),
map(this.toCallData.bind(this)),
);
this.events2.get(WAHAEvents.CALL_REJECTED).switch(callRejected$);
const presence$ = all$.pipe(
onlyEvent(WhatsMeowEvent.PRESENCE),
filter((event: any) => this.jids.include(event?.From)),
@@ -2165,6 +2219,59 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
private getCallId(call: any): string | null {
return call?.CallID || call?.Data?.Attrs?.['call-id'] || null;
}
private shouldProcessCallEvent(call: any): boolean {
if (!call) {
return false;
}
if (call.GroupJID && this.jids.include(call.GroupJID)) {
return true;
}
return this.jids.include(call.From);
}
private toCallData(call: any): CallData {
const date = call?.Timestamp ? new Date(call.Timestamp) : new Date();
const timestamp = date.getTime() / 1000;
const isVideo = this.isVideoCall(call);
const isGroup = this.isGroupCall(call);
const from = call?.From || call?.GroupJID;
return {
id: this.getCallId(call),
from: from ? toCusFormat(from) : undefined,
timestamp: timestamp,
isVideo: Boolean(isVideo),
isGroup: isGroup,
_data: call,
};
}
private isVideoCall(call: any): boolean {
if (!call) return false;
if (call?.Media === 'video') return true;
const attrsMedia = call?.Data?.Attrs?.media || call?.Data?.Attrs?.type;
if (attrsMedia === 'video') return true;
const content = call?.Data?.Content;
if (Array.isArray(content)) {
const hasVideoTag = content.some((item: any) => item?.Tag === 'video');
if (hasVideoTag) {
return true;
}
}
return false;
}
private isGroupCall(call: any): boolean {
if (!call) return false;
if (call?.GroupJID) return true;
const attrs = call?.Data?.Attrs;
if (!attrs) return false;
return attrs.type === 'group' || Boolean(attrs['group-jid']);
}
private toEventResponsePayload(event: any): EventResponsePayload {
const msg = this.toWAMessage(event);
let response: EventResponse | null = null;