[core] source: api|app for messages.

fix #787
This commit is contained in:
devlikepro committed 2025-03-16 14:05:28 +07:00
1 parent fd0438f80b
commit 52c5d85487
7 files changed
+152 -39

No files matched your search

+35 -3
View File
@@ -18,7 +18,7 @@ import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { WAMessage } from '@waha/structures/responses.dto';
import { MessageSource, WAMessage } from '@waha/structures/responses.dto';
import { DefaultMap } from '@waha/utils/DefaultMap';
import { generatePrefixedId } from '@waha/utils/ids';
import { LoggerBuilder } from '@waha/utils/logging';
@@ -26,7 +26,6 @@ import { complete } from '@waha/utils/reactive/complete';
import { SwitchObservable } from '@waha/utils/reactive/SwitchObservable';
import * as fs from 'fs';
import * as lodash from 'lodash';
import { PinoLogger } from 'nestjs-pino';
import * as NodeCache from 'node-cache';
import { Logger } from 'pino';
import {
@@ -73,7 +72,6 @@ import {
CreateGroupRequest,
GroupField,
GroupsListFields,
GroupsPaginationParams,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '../../structures/groups.dto';
@@ -148,6 +146,12 @@ export abstract class WhatsappSession {
stdTTL: 24 * 60 * 60, // 1 day
});
// Save sent messages ids in cache so we can determine if a message was sent
// via API or APP
private sentMessageIds: NodeCache = new NodeCache({
stdTTL: 10 * 60, // 10 minutes
});
public constructor({
name,
printQR,
@@ -885,6 +889,18 @@ export abstract class WhatsappSession {
);
qrcode.generate(qr.raw, { small: true });
}
protected saveSentMessageId(id: string) {
this.sentMessageIds.set(id, true);
}
protected getMessageSource(id: string): MessageSource {
if (!id) {
return MessageSource.APP;
}
const api = this.sentMessageIds.has(id);
return api ? MessageSource.API : MessageSource.APP;
}
}
export function isNewsletter(jid: string) {
@@ -916,3 +932,19 @@ export function parseChannelInviteLink(link: string): string {
export function getPublicUrlFromDirectPath(directPath: string) {
return `https://pps.whatsapp.net${directPath}`;
}
const deviceRegexp = /^.*:(\d+)@.*$/;
/**
* Extracts the device ID from a JID string.
*
* @param jid - The JID string (e.g., "123123:12@c.us")
* @return The extracted device ID (e.g., "12") or null if the format is invalid.
*/
export function extractDeviceId(jid: string): string | null {
if (!jid) {
return null;
}
const match = jid.match(deviceRegexp);
return match ? match[1] : null;
}
+54 -33
View File
@@ -8,19 +8,33 @@ import * as grpc from '@grpc/grpc-js';
import { connectivityState } from '@grpc/grpc-js';
import { UnprocessableEntityException } from '@nestjs/common';
import {
extractDeviceId,
getChannelInviteLink,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import { Jid } from '@waha/core/engines/const';
import { EventsFromObservable } from '@waha/core/engines/gows/EventsFromObservable';
import { GowsEventStreamObservable } from '@waha/core/engines/gows/GowsEventStreamObservable';
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
ToGroupV2ParticipantsEvents,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/gows/groups.gows';
import { messages } from '@waha/core/engines/gows/grpc/gows';
import {
optional,
parseJson,
parseJsonList,
statusToAck,
} from '@waha/core/engines/gows/helpers';
import { GowsAuthFactoryCore } from '@waha/core/engines/gows/store/GowsAuthFactoryCore';
import {
parseMessageIdSerialized,
toCusFormat,
toJID,
} from '@waha/core/engines/noweb/session.noweb.core';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
@@ -38,6 +52,13 @@ import {
ListChannelsQuery,
PreviewChannelMessages,
} from '@waha/structures/channels.dto';
import {
ChatSortField,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
@@ -53,6 +74,7 @@ import {
SendSeenRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import {
ACK_UNKNOWN,
WAHAEvents,
@@ -60,11 +82,24 @@ import {
WAHASessionStatus,
WAMessageAck,
} from '@waha/structures/enums.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import {
CreateGroupRequest,
GroupSortField,
Participant,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '@waha/structures/presence.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import {
MessageSource,
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { MeInfo, ProxyConfig } from '@waha/structures/sessions.dto';
import {
BROADCAST_ID,
@@ -73,6 +108,7 @@ import {
TextStatus,
} from '@waha/structures/status.dto';
import { EnginePayload, WAMessageAckBody } from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
import { onlyEvent } from '@waha/utils/reactive/ops/onlyEvent';
import * as NodeCache from 'node-cache';
@@ -92,37 +128,6 @@ import { promisify } from 'util';
import * as gows from './types';
import MessageServiceClient = messages.MessageServiceClient;
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
ToGroupV2ParticipantsEvents,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/gows/groups.gows';
import {
optional,
parseJson,
parseJsonList,
statusToAck,
} from '@waha/core/engines/gows/helpers';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
ChatSortField,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import {
CreateGroupRequest,
GroupSortField,
Participant,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -256,6 +261,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
id: toCusFormat(jidNormalizedUser(data.ID)),
pushName: data.PushName,
};
// @ts-ignore
this.me.jid = data.ID;
});
events.on(WhatsMeowEvent.DISCONNECTED, () => {
@@ -1378,7 +1385,6 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const id = buildMessageId(message);
const body = this.extractBody(message.Message);
const replyTo = null; // TODO: this.extractReplyTo(message.message);
let ack;
if (message.Status) {
ack = statusToAck(message.Status);
@@ -1386,12 +1392,14 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
ack = message.Info.IsFromMe ? WAMessageAck.SERVER : WAMessageAck.DEVICE;
}
const mediaContent = extractMediaContent(message.Message);
const source = this.getSourceDeviceByMsg(message);
return {
id: id,
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.Info.IsFromMe,
source: source,
body: body,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
@@ -1408,6 +1416,17 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
private getSourceDeviceByMsg(message): MessageSource {
if (!message.Info.IsFromMe) {
return MessageSource.APP;
}
// @ts-ignore
const myJid = this.me.jid;
const myDeviceId = extractDeviceId(myJid);
const sentDeviceId = extractDeviceId(message.Info.Sender);
return sentDeviceId === myDeviceId ? MessageSource.API : MessageSource.APP;
}
private extractBody(message) {
if (!message) {
return null;
@@ -1514,11 +1533,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const fromToParticipant = getFromToParticipant(message);
const reactionMessage = message.Message.reactionMessage;
const messageId = this.buildMessageIdFromKey(reactionMessage.key);
const source = this.getSourceDeviceByMsg(message);
const reaction: WAMessageReaction = {
id: id,
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.Info.IsFromMe,
source: source,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
reaction: {
@@ -5,6 +5,7 @@ import makeWASocket, {
DisconnectReason,
downloadMediaMessage,
extractMessageContent,
generateMessageIDV2,
getAggregateVotesInPollMessage,
getContentType,
getKeyAuthor,
@@ -151,6 +152,7 @@ import {
WAHAPresenceData,
} from '../../../structures/presence.dto';
import {
MessageSource,
WAMessage,
WAMessageReaction,
} from '../../../structures/responses.dto';
@@ -1765,11 +1767,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const fromToParticipant = getFromToParticipant(message);
const reactionMessage = message.message.reactionMessage;
const messageId = buildMessageId(reactionMessage.key);
const source = this.getMessageSource(message.key.id);
const reaction: WAMessageReaction = {
id: id,
timestamp: ensureNumber(message.messageTimestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
source: source,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
reaction: {
@@ -1828,11 +1832,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const replyTo = this.extractReplyTo(message.message);
const ack = message.ack || message.status - 1;
const mediaContent = extractMediaContent(message.message);
const source = this.getMessageSource(message.key.id);
return Promise.resolve({
id: id,
timestamp: ensureNumber(message.messageTimestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
source: source,
body: body || null,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
@@ -2074,9 +2080,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
quoted = await this.store.loadMessage(jid, key.id);
}
const chat = await this.store.getChat(jid);
const messageId = this.generateMessageID();
return {
quoted: quoted,
ephemeralExpiration: chat?.ephemeralExpiration,
messageId: messageId,
};
}
@@ -2095,6 +2103,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
return linkPreview;
}
protected generateMessageID() {
const id = generateMessageIDV2(this.sock.user?.id);
this.saveSentMessageId(id);
return id;
}
}
function hasPath(url: string) {
+21
View File
@@ -2,9 +2,11 @@ import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { Label } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { TextStatus } from '@waha/structures/status.dto';
import { EventEmitter } from 'events';
import * as lodash from 'lodash';
import { Client, Events } from 'whatsapp-web.js';
import { Message } from 'whatsapp-web.js/src/structures';
import { exposeFunctionIfAbsent } from 'whatsapp-web.js/src/util/Puppeter';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadWAHA } = require('./_WAHA.js');
@@ -19,10 +21,13 @@ const { LoadPaginator } = require('./_Paginator.js');
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
export class WebjsClientCore extends Client {
public events = new EventEmitter();
constructor(options) {
super(options);
// Wait until it's READY and inject more utils
this.on(Events.READY, async () => {
await this.attachCustomEventListeners();
await this.injectWaha();
});
}
@@ -33,6 +38,22 @@ export class WebjsClientCore extends Client {
await this.pupPage.evaluate(LoadWAHA);
}
async attachCustomEventListeners() {
await exposeFunctionIfAbsent(
this.pupPage,
'onNewMessageId',
(messageId: string) => {
this.events.emit('message.id', { id: messageId });
return;
},
);
}
async destroy() {
this.events.removeAllListeners();
await super.destroy();
}
async setPushName(name: string) {
await this.pupPage.evaluate(async (pushName) => {
return await window['WAHA'].WAWebSetPushnameConnAction.setPushname(
+14 -1
View File
@@ -73,7 +73,11 @@ import {
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { ReplyToMessage } from '@waha/structures/message.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import {
MessageSource,
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
@@ -1154,6 +1158,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
* END - Methods for API
*/
subscribeEngineEvents2() {
// Save sent message in cache
this.whatsapp.events.on('message.id', (data) => {
this.saveSentMessageId(data.id);
});
//
// All
//
@@ -1353,10 +1362,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
private processMessageReaction(reaction: Reaction): WAMessageReaction {
const source = this.getMessageSource(reaction.id.id);
return {
id: reaction.id._serialized,
from: reaction.senderId,
fromMe: reaction.id.fromMe,
source: source,
participant: reaction.senderId,
to: reaction.id.remote,
timestamp: reaction.timestamp,
@@ -1369,12 +1380,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected toWAMessage(message: Message): WAMessage {
const replyTo = this.extractReplyTo(message);
const source = this.getMessageSource(message.id.id);
// @ts-ignore
return {
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
source: source,
to: message.to,
body: message.body,
// Media
+12
View File
@@ -11,6 +11,11 @@ export class WALocation {
longitude: string;
}
export enum MessageSource {
API = 'api',
APP = 'app',
}
class WAMessageBase {
@MessageIdProperty()
id: string;
@@ -33,6 +38,13 @@ class WAMessageBase {
})
fromMe: boolean;
@ApiProperty({
description:
'The device that sent the message - either API or APP. Available in events (webhooks/websockets) only and only "fromMe: true" messages.',
example: MessageSource.API,
})
source: MessageSource;
@ChatIdProperty({
description: `
* ID for who this message is for.
+2 -2
View File
@@ -12931,7 +12931,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels":
version: 1.26.0
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=bb4525a80667d83122108a03d936e41cefd179a4"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=c73f3cfb4e1290103ee946ca7f56d5638f1a66bd"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -12949,7 +12949,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: a06d30c9f52a1ff0268e4e98e3c0d80112a5dc8a9445d0df460fac3cbe0168b6f1c5ad494518b1e9a98acb54da8c46a2f6644e9f41b1b2987771fac101747c55
checksum: 831a4a2819041f63c2a51a98e1ad7dd9ff8474b1005b11314b613c6dadac545f3c780e38081b7d260d32afa9ffd333a25f9c6407b630a0660bd08dadbc8ead89
languageName: node
linkType: hard