Compare commits

..
41 Commits
Author SHA1 Message Date
devlikepro 47a2c21824 [core] Up GOWS
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-05 13:08:45 +07:00
devlikepro aca5547965 [core] 2025.4.2 2025-05-05 13:08:45 +07:00
devlikepro 0e07876630 [core] Use POST in /api/chats/messages/read 2025-05-05 13:08:45 +07:00
devlikepro a43901b4a4 [core] /api/sendSeen - If no message id provided - switch to readChatMessages 2025-05-05 13:08:44 +07:00
devlikepro 6515fac16d [core] WEBJS - add message._data.receipts in group/status messages 2025-05-05 13:08:44 +07:00
devlikepro fdaa7e9f48 [core] Up Dashboard 2025-05-05 13:08:44 +07:00
devlikepro cd779850de [core] GOWS - fix missing DEVICE ack for groups 2025-05-05 13:08:44 +07:00
devlikepro ef939f4357 [core] Add "message.ack" event for groups and status
fix #495
fix #900
2025-05-05 13:08:41 +07:00
devlikepro bfb9dd7009 [core] WEBJS - Update chrome 2025-05-05 13:08:39 +07:00
devlikepro f282f14eff [core] WEBJS - Update puppeter 2025-05-05 13:08:38 +07:00
devlikepro 992b289500 [core] WEBJS - catch puppeter ProtocolError and "Promise was collected" and restart the browser
fix #244 - Promise was collected
fix #714 - Promise was collected
fix #685 - ProtocolError
fix #936 - ProtocolError
2025-05-05 13:08:37 +07:00
devlikepro 9dcdebcab2 [core] Add /api/chats/messages/read
fix #783
2025-05-05 13:08:36 +07:00
devlikepro c0b8bacab0 [core] Fix missing first message - GOWS, NOWEB 2025-05-05 13:08:35 +07:00
devlikepro f96e6420fc [core] Get message by id - filter by 'all' 2025-05-05 13:08:35 +07:00
devlikepro d7f97480cc [core] Do not send seen on empty keys 2025-05-05 13:08:35 +07:00
devlikepro 26df41b571 [core] Add isJidCus method 2025-05-05 13:08:34 +07:00
devlikepro 4ca336017d [core] GOWS - /api/sendSeen messageIds 2025-05-05 13:08:33 +07:00
devlikepro 94252ea54c [core] NOWEB - /api/sendSeen messageIds 2025-05-05 13:08:32 +07:00
devlikepro f8220fec1f [core] GOWS - send seen use messageIds 2025-05-05 13:08:32 +07:00
devlikepro a5ded396c9 [core] Add "audio-decode" to dependencies 2025-05-05 13:08:31 +07:00
devlikepro c457cf8064 [core] Add "node-cache" to dependencies 2025-05-05 13:08:30 +07:00
devlikepro 3079f90118 [core] Up NOWEB 2025-05-05 13:08:29 +07:00
devlikepro 4fbb331015 [core] Copy-paste in-memory store
Got removed from the upstream
2025-05-05 13:08:28 +07:00
devlikepro 640c0d305c [core] - Add 'filter.ack' to /messages 2025-05-05 13:08:26 +07:00
devlikepro 0b044c67bb [core] NOWEB fix ListMessages "Cannot read properties of undefined (reading 'protocolMessage')"
fix #932
2025-05-05 13:08:26 +07:00
devlikepro 7a37ba8145 [core] WEBJS - stop processing if we reach out the earliest message we need 2025-05-05 13:08:25 +07:00
devlikepro b447863d9f [core] NOWEB - update message status on /api/sendSeen API call
fix #635
2025-05-05 13:08:25 +07:00
devlikepro 6dd7e14f6f [core] NOWEB - set DEVICE message status for incoming messages if no status found 2025-05-05 13:08:24 +07:00
devlikepro b09ef98dce [core] default limit to 10 in messages 2025-05-05 13:08:24 +07:00
devlikepro ff1cf8a288 [core] gpt 2025-05-05 13:08:24 +07:00
devlikepro 603793662a [core] Wait 60 times in STARTING, approximately 2 minutes
fix #904
2025-05-05 13:08:24 +07:00
devlikepro 4cdab0dbc7 [core] admin/admin 2025-04-16 09:26:32 +07:00
devlikepro d745279fec [core] 2025.4.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-04-09 16:05:03 +07:00
devlikepro df34c9f2fd [core] Up GOWS 2025-04-09 16:05:03 +07:00
devlikepro 37da9aabe3 [core] Add POST /api/send/link-review
fix #880
fix #596
2025-04-09 16:05:02 +07:00
devlikepro 3a2205147c [core] API - deprecate /sendLinkPreview 2025-04-09 16:05:02 +07:00
devlikepro 5c6acaf10a [core] NOWEB - filter out sender key distribution msg
fix #891
2025-04-09 16:05:02 +07:00
devlikepro d916f2dea5 [core] NOWEB - return error in case of malformed xml 2025-04-09 16:05:01 +07:00
devlikepro 254a1aedcb [core] NOWEB - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro dce3175388 [core] GOWS - fix sending reaction to DM/Groups for other participants 2025-04-09 16:05:01 +07:00
devlikepro e2b6b45418 [core] GOWS - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
41 changed files with 2752 additions and 538 deletions

No files matched your search

+5 -5
View File
@@ -23,9 +23,9 @@ WAHA_BASE_URL=http://localhost:3000
#
# https://waha.devlike.pro/docs/how-to/security/
#
WHATSAPP_API_KEY=321
WAHA_DASHBOARD_USERNAME=waha
WAHA_DASHBOARD_PASSWORD=waha
WHATSAPP_API_KEY=admin
WAHA_DASHBOARD_USERNAME=admin
WAHA_DASHBOARD_PASSWORD=admin
#WHATSAPP_SWAGGER_USERNAME=admin
#WHATSAPP_SWAGGER_PASSWORD=admin
@@ -35,8 +35,8 @@ WAHA_DASHBOARD_PASSWORD=waha
# https://waha.devlike.pro/docs/how-to/waha-dashboard/#configuration
# Enabled by default
#WAHA_DASHBOARD_ENABLED=true
#WAHA_DASHBOARD_USERNAME=waha
#WAHA_DASHBOARD_PASSWORD=waha
#WAHA_DASHBOARD_USERNAME=admin
#WAHA_DASHBOARD_PASSWORD=admin
#
# Logging
+1 -1
View File
@@ -131,7 +131,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Install Chrome
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="130.0.6723.69-1"
ARG CHROME_VERSION="135.0.7049.114-1"
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
+1 -1
View File
@@ -35,7 +35,7 @@ for-swagger:
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
up-noweb:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-05-01
up-noweb-libsignal:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
+2 -2
View File
@@ -11,8 +11,8 @@ services:
- './.media:/app/.media'
environment:
- WHATSAPP_API_KEY=321
- WAHA_DASHBOARD_USERNAME=waha
- WAHA_DASHBOARD_PASSWORD=waha
- WAHA_DASHBOARD_USERNAME=admin
- WAHA_DASHBOARD_PASSWORD=admin
- WHATSAPP_DEFAULT_ENGINE=WEBJS
- WAHA_PRINT_QR=False
- WAHA_MEDIA_STORAGE=LOCAL
+2 -2
View File
@@ -36,8 +36,8 @@ services:
# Username and password for WAHA Dashboard
# you can use the same username and password for both Swagger and Dashboard
# Default - waha / waha
- WAHA_DASHBOARD_USERNAME=waha
- WAHA_DASHBOARD_PASSWORD=waha
- WAHA_DASHBOARD_USERNAME=admin
- WAHA_DASHBOARD_PASSWORD=admin
# Engine specific environment variables
# - WAHA_WEBJS_WEB_VERSION=2.2412.54-videofix
restart: always
+4 -2
View File
@@ -26,7 +26,7 @@
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
},
"dependencies": {
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-6712",
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-05-01",
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-s3": "^3.633.0",
"@aws-sdk/s3-request-presigner": "^3.633.0",
@@ -47,6 +47,7 @@
"adm-zip": "0.5.10",
"agentkeepalive": "^4.5.0",
"async-lock": "^1.4.1",
"audio-decode": "^2.2.2",
"axios": "^1.7.7",
"axios-retry": "^4.5.0",
"better-sqlite3": "11.3.0",
@@ -69,6 +70,7 @@
"mime-types": "^2.1.27",
"mongodb": "6.9.0",
"nestjs-pino": "^4.1.0",
"node-cache": "5.1.2",
"passport": "^0.6.0",
"passport-headerapikey": "^1.2.2",
"pg": "^8.13.1",
@@ -94,7 +96,7 @@
"resolutions": {
"bufferutil": "^4.0.8",
"ws": "^8.18.0",
"puppeteer": "^23.6.0",
"puppeteer": "^24.7.2",
"whatwg-url": "13.0.0",
"libsignal": "github:devlikeapro/libsignal-node#fork-master"
},
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# Set source and output file
SOURCE_DIR="src"
OUTPUT_DIR="$HOME/Downloads/chatgpt"
OUTPUT_FILE="$OUTPUT_DIR/waha.ts"
# Create or clear the output file
rm -f "$OUTPUT_FILE"
touch "$OUTPUT_FILE"
# Find all .md files in the source directory and combine them
find "$SOURCE_DIR" -type f -name "*.ts" | while read -r file; do
# Add file name to OUTPUT_FILE
echo -e "\n-------" >> "$OUTPUT_FILE"
echo "File: $file" >> "$OUTPUT_FILE"
echo -e "-------\n" >> "$OUTPUT_FILE"
# Append the content of the file to the output file
cat "$file" >> "$OUTPUT_FILE"
done
echo "All .ts files combined into $OUTPUT_FILE with separators."
+3 -3
View File
@@ -12,6 +12,7 @@ import {
} from '@nestjs/common';
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
import { isJidNewsletter } from '@waha/core/utils/jids';
import {
SessionApiParam,
WorkingSessionParam,
@@ -35,7 +36,6 @@ import {
import { SessionManager } from '../core/abc/manager.abc';
import {
isNewsletter,
parseChannelInviteLink,
WhatsappSession,
} from '../core/abc/session.abc';
@@ -93,7 +93,7 @@ export class ChannelsController {
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<Channel> {
if (isNewsletter(id)) {
if (isJidNewsletter(id)) {
return session.channelsGetChannel(id);
} else {
const inviteCode = parseChannelInviteLink(id);
@@ -126,7 +126,7 @@ export class ChannelsController {
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
if (isJidNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
+18
View File
@@ -1,3 +1,4 @@
import { isJidGroup } from '@adiwajshing/baileys';
import {
Body,
Controller,
@@ -31,6 +32,9 @@ import {
GetChatMessagesQuery,
OverviewPaginationParams,
PinMessageRequest,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
transformAck,
} from '../structures/chats.dto';
import { EditMessageRequest } from '../structures/chatting.dto';
@@ -100,9 +104,23 @@ class ChatsController {
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
filter = transformAck(filter);
return session.getChatMessages(chatId, query, filter);
}
@Post(':chatId/messages/read')
@SessionApiParam
@ApiOperation({ summary: 'Read unread messages in the chat' })
@ChatIdApiParam
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
readChatMessages(
@Query() query: ReadChatMessagesQuery,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<ReadChatMessagesResponse> {
return session.readChatMessages(chatId, query);
}
@Get(':chatId/messages/:messageId')
@SessionApiParam
@ApiOperation({ summary: 'Gets message by id' })
+42 -7
View File
@@ -10,7 +10,11 @@ import {
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import {
GetChatMessagesFilter,
ReadChatMessagesQuery,
transformAck,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { SessionManager } from '../core/abc/manager.abc';
@@ -23,6 +27,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -95,6 +100,25 @@ export class ChattingController {
return whatsapp.sendVideo(request);
}
@Post('/send/link-custom-preview')
@ApiOperation({
summary: 'Send a text message with a CUSTOM link preview.',
description:
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
})
@UsePipes(new WAHAValidationPipe())
async sendLinkCustomPreview(
@Body() request: MessageLinkCustomPreviewRequest,
): Promise<any> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (!request.text.includes(request.preview.url)) {
throw new Error(
'"text" must include the URL provided in the "preview.url"',
);
}
return whatsapp.sendLinkCustomPreview(request);
}
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons (interactive message)',
@@ -116,6 +140,15 @@ export class ChattingController {
@Post('/sendSeen')
async sendSeen(@Body() chat: SendSeenRequest) {
const hasMessageId = chat.messageIds?.length > 0 || Boolean(chat.messageId);
if (!hasMessageId) {
const whatsapp = await this.manager.getWorkingSession(chat.session);
const query: ReadChatMessagesQuery = {
messages: null,
days: 7,
};
return whatsapp.readChatMessages(chat.chatId, query);
}
const whatsapp = await this.manager.getWorkingSession(chat.session);
return whatsapp.sendSeen(chat);
}
@@ -166,12 +199,6 @@ export class ChattingController {
return whatsapp.sendLocation(request);
}
@Post('/sendLinkPreview')
async sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
@Post('/sendContactVcard')
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
@@ -209,6 +236,7 @@ export class ChattingController {
@Query() query: GetMessageQuery,
@Query() filter: GetChatMessagesFilter,
) {
filter = transformAck(filter);
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getChatMessages(query.chatId, query, filter);
}
@@ -236,4 +264,11 @@ export class ChattingController {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.reply(request);
}
@Post('/sendLinkPreview')
@ApiOperation({ deprecated: true })
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
}
+38 -6
View File
@@ -1,4 +1,6 @@
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { MessagesForRead } from '@waha/core/utils/convertors';
import { isJidNewsletter } from '@waha/core/utils/jids';
import {
Channel,
ChannelListResult,
@@ -14,6 +16,8 @@ import {
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
@@ -52,6 +56,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -421,6 +426,12 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new NotImplementedByEngineError();
}
abstract forwardMessage(request: MessageForwardRequest): Promise<WAMessage>;
abstract sendImage(request: MessageImageRequest);
@@ -476,10 +487,35 @@ export abstract class WhatsappSession {
chatId: string,
query: GetChatMessagesQuery,
filter: GetChatMessagesFilter,
) {
): Promise<WAMessage[]> {
throw new NotImplementedByEngineError();
}
abstract readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
): Promise<ReadChatMessagesResponse>;
protected async readChatMessagesWSImpl(
chatId: string,
request: ReadChatMessagesQuery,
): Promise<ReadChatMessagesResponse> {
const { query, filter } = MessagesForRead(chatId, request);
const messages = await this.getChatMessages(chatId, query, filter);
this.logger.debug(`Found ${messages.length} messages to read`);
if (messages.length === 0) {
return { ids: [] };
}
const ids = messages.map((m) => m.id);
const seen: SendSeenRequest = {
chatId: chatId,
messageIds: ids,
session: '',
};
await this.sendSeen(seen);
return { ids: ids };
}
public getChatMessage(
chatId: string,
messageId: string,
@@ -600,7 +636,7 @@ export abstract class WhatsappSession {
let fn: Promise<string>;
if (isJidBroadcast(id)) {
return null;
} else if (isNewsletter(id)) {
} else if (isJidNewsletter(id)) {
fn = this.channelsGetChannel(id).then(
(channel: Channel) => channel.picture || channel.preview,
);
@@ -918,10 +954,6 @@ export abstract class WhatsappSession {
}
}
export function isNewsletter(jid: string) {
return jid.endsWith('@newsletter');
}
export function getGroupInviteLink(code: string) {
if (code.startsWith('https://')) {
return code;
+1 -1
View File
@@ -4,7 +4,7 @@ import {
GroupInfoFull,
JoinedGroupEvent,
} from '@waha/core/engines/gows/types.group';
import { toJID } from '@waha/core/engines/noweb/session.noweb.core';
import { toJID } from '@waha/core/utils/jids';
import {
GroupId,
GroupInfo,
+212 -1
View File
@@ -2535,6 +2535,142 @@ export namespace messages {
return Media.deserialize(bytes);
}
}
export class LinkPreview extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("url" in data && data.url != undefined) {
this.url = data.url;
}
if ("title" in data && data.title != undefined) {
this.title = data.title;
}
if ("description" in data && data.description != undefined) {
this.description = data.description;
}
if ("image" in data && data.image != undefined) {
this.image = data.image;
}
}
}
get url() {
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
}
set url(value: string) {
pb_1.Message.setField(this, 1, value);
}
get title() {
return pb_1.Message.getFieldWithDefault(this, 2, "") as string;
}
set title(value: string) {
pb_1.Message.setField(this, 2, value);
}
get description() {
return pb_1.Message.getFieldWithDefault(this, 3, "") as string;
}
set description(value: string) {
pb_1.Message.setField(this, 3, value);
}
get image() {
return pb_1.Message.getFieldWithDefault(this, 4, new Uint8Array(0)) as Uint8Array;
}
set image(value: Uint8Array) {
pb_1.Message.setField(this, 4, value);
}
static fromObject(data: {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
}): LinkPreview {
const message = new LinkPreview({});
if (data.url != null) {
message.url = data.url;
}
if (data.title != null) {
message.title = data.title;
}
if (data.description != null) {
message.description = data.description;
}
if (data.image != null) {
message.image = data.image;
}
return message;
}
toObject() {
const data: {
url?: string;
title?: string;
description?: string;
image?: Uint8Array;
} = {};
if (this.url != null) {
data.url = this.url;
}
if (this.title != null) {
data.title = this.title;
}
if (this.description != null) {
data.description = this.description;
}
if (this.image != null) {
data.image = this.image;
}
return data;
}
serialize(): Uint8Array;
serialize(w: pb_1.BinaryWriter): void;
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
const writer = w || new pb_1.BinaryWriter();
if (this.url.length)
writer.writeString(1, this.url);
if (this.title.length)
writer.writeString(2, this.title);
if (this.description.length)
writer.writeString(3, this.description);
if (this.image.length)
writer.writeBytes(4, this.image);
if (!w)
return writer.getResultBuffer();
}
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): LinkPreview {
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new LinkPreview();
while (reader.nextField()) {
if (reader.isEndGroup())
break;
switch (reader.getFieldNumber()) {
case 1:
message.url = reader.readString();
break;
case 2:
message.title = reader.readString();
break;
case 3:
message.description = reader.readString();
break;
case 4:
message.image = reader.readBytes();
break;
default: reader.skipField();
}
}
return message;
}
serializeBinary(): Uint8Array {
return this.serialize();
}
static deserializeBinary(bytes: Uint8Array): LinkPreview {
return LinkPreview.deserialize(bytes);
}
}
export class MessageRequest extends pb_1.Message {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
@@ -2549,6 +2685,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: LinkPreview;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [11], this.#one_of_decls);
@@ -2586,6 +2723,9 @@ export namespace messages {
if ("participants" in data && data.participants != undefined) {
this.participants = data.participants;
}
if ("preview" in data && data.preview != undefined) {
this.preview = data.preview;
}
}
}
get session() {
@@ -2666,6 +2806,15 @@ export namespace messages {
set participants(value: string[]) {
pb_1.Message.setField(this, 11, value);
}
get preview() {
return pb_1.Message.getWrapperField(this, LinkPreview, 12) as LinkPreview;
}
set preview(value: LinkPreview) {
pb_1.Message.setWrapperField(this, 12, value);
}
get has_preview() {
return pb_1.Message.getField(this, 12) != null;
}
static fromObject(data: {
session?: ReturnType<typeof Session.prototype.toObject>;
jid?: string;
@@ -2678,6 +2827,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
}): MessageRequest {
const message = new MessageRequest({});
if (data.session != null) {
@@ -2713,6 +2863,9 @@ export namespace messages {
if (data.participants != null) {
message.participants = data.participants;
}
if (data.preview != null) {
message.preview = LinkPreview.fromObject(data.preview);
}
return message;
}
toObject() {
@@ -2728,6 +2881,7 @@ export namespace messages {
replyTo?: string;
id?: string;
participants?: string[];
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
} = {};
if (this.session != null) {
data.session = this.session.toObject();
@@ -2762,6 +2916,9 @@ export namespace messages {
if (this.participants != null) {
data.participants = this.participants;
}
if (this.preview != null) {
data.preview = this.preview.toObject();
}
return data;
}
serialize(): Uint8Array;
@@ -2790,6 +2947,8 @@ export namespace messages {
writer.writeString(10, this.id);
if (this.participants.length)
writer.writeRepeatedString(11, this.participants);
if (this.has_preview)
writer.writeMessage(12, this.preview, () => this.preview.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
@@ -2832,6 +2991,9 @@ export namespace messages {
case 11:
pb_1.Message.addToRepeatedField(message, 11, reader.readString());
break;
case 12:
reader.readMessage(message.preview, () => message.preview = LinkPreview.deserialize(reader));
break;
default: reader.skipField();
}
}
@@ -3821,9 +3983,10 @@ export namespace messages {
sender?: string;
messageId?: string;
type?: ReceiptType;
messageIds?: string[];
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [6], this.#one_of_decls);
if (!Array.isArray(data) && typeof data == "object") {
if ("session" in data && data.session != undefined) {
this.session = data.session;
@@ -3840,6 +4003,9 @@ export namespace messages {
if ("type" in data && data.type != undefined) {
this.type = data.type;
}
if ("messageIds" in data && data.messageIds != undefined) {
this.messageIds = data.messageIds;
}
}
}
get session() {
@@ -3875,12 +4041,19 @@ export namespace messages {
set type(value: ReceiptType) {
pb_1.Message.setField(this, 5, value);
}
get messageIds() {
return pb_1.Message.getFieldWithDefault(this, 6, []) as string[];
}
set messageIds(value: string[]) {
pb_1.Message.setField(this, 6, value);
}
static fromObject(data: {
session?: ReturnType<typeof Session.prototype.toObject>;
jid?: string;
sender?: string;
messageId?: string;
type?: ReceiptType;
messageIds?: string[];
}): MarkReadRequest {
const message = new MarkReadRequest({});
if (data.session != null) {
@@ -3898,6 +4071,9 @@ export namespace messages {
if (data.type != null) {
message.type = data.type;
}
if (data.messageIds != null) {
message.messageIds = data.messageIds;
}
return message;
}
toObject() {
@@ -3907,6 +4083,7 @@ export namespace messages {
sender?: string;
messageId?: string;
type?: ReceiptType;
messageIds?: string[];
} = {};
if (this.session != null) {
data.session = this.session.toObject();
@@ -3923,6 +4100,9 @@ export namespace messages {
if (this.type != null) {
data.type = this.type;
}
if (this.messageIds != null) {
data.messageIds = this.messageIds;
}
return data;
}
serialize(): Uint8Array;
@@ -3939,6 +4119,8 @@ export namespace messages {
writer.writeString(4, this.messageId);
if (this.type != ReceiptType.READ)
writer.writeEnum(5, this.type);
if (this.messageIds.length)
writer.writeRepeatedString(6, this.messageIds);
if (!w)
return writer.getResultBuffer();
}
@@ -3963,6 +4145,9 @@ export namespace messages {
case 5:
message.type = reader.readEnum();
break;
case 6:
pb_1.Message.addToRepeatedField(message, 6, reader.readString());
break;
default: reader.skipField();
}
}
@@ -6772,6 +6957,7 @@ export namespace messages {
timestampGte?: OptionalUInt64;
timestampLte?: OptionalUInt64;
fromMe?: OptionalBool;
status?: OptionalUInt32;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
@@ -6788,6 +6974,9 @@ export namespace messages {
if ("fromMe" in data && data.fromMe != undefined) {
this.fromMe = data.fromMe;
}
if ("status" in data && data.status != undefined) {
this.status = data.status;
}
}
}
get jid() {
@@ -6826,11 +7015,21 @@ export namespace messages {
get has_fromMe() {
return pb_1.Message.getField(this, 4) != null;
}
get status() {
return pb_1.Message.getWrapperField(this, OptionalUInt32, 5) as OptionalUInt32;
}
set status(value: OptionalUInt32) {
pb_1.Message.setWrapperField(this, 5, value);
}
get has_status() {
return pb_1.Message.getField(this, 5) != null;
}
static fromObject(data: {
jid?: ReturnType<typeof OptionalString.prototype.toObject>;
timestampGte?: ReturnType<typeof OptionalUInt64.prototype.toObject>;
timestampLte?: ReturnType<typeof OptionalUInt64.prototype.toObject>;
fromMe?: ReturnType<typeof OptionalBool.prototype.toObject>;
status?: ReturnType<typeof OptionalUInt32.prototype.toObject>;
}): MessageFilters {
const message = new MessageFilters({});
if (data.jid != null) {
@@ -6845,6 +7044,9 @@ export namespace messages {
if (data.fromMe != null) {
message.fromMe = OptionalBool.fromObject(data.fromMe);
}
if (data.status != null) {
message.status = OptionalUInt32.fromObject(data.status);
}
return message;
}
toObject() {
@@ -6853,6 +7055,7 @@ export namespace messages {
timestampGte?: ReturnType<typeof OptionalUInt64.prototype.toObject>;
timestampLte?: ReturnType<typeof OptionalUInt64.prototype.toObject>;
fromMe?: ReturnType<typeof OptionalBool.prototype.toObject>;
status?: ReturnType<typeof OptionalUInt32.prototype.toObject>;
} = {};
if (this.jid != null) {
data.jid = this.jid.toObject();
@@ -6866,6 +7069,9 @@ export namespace messages {
if (this.fromMe != null) {
data.fromMe = this.fromMe.toObject();
}
if (this.status != null) {
data.status = this.status.toObject();
}
return data;
}
serialize(): Uint8Array;
@@ -6880,6 +7086,8 @@ export namespace messages {
writer.writeMessage(3, this.timestampLte, () => this.timestampLte.serialize(writer));
if (this.has_fromMe)
writer.writeMessage(4, this.fromMe, () => this.fromMe.serialize(writer));
if (this.has_status)
writer.writeMessage(5, this.status, () => this.status.serialize(writer));
if (!w)
return writer.getResultBuffer();
}
@@ -6901,6 +7109,9 @@ export namespace messages {
case 4:
reader.readMessage(message.fromMe, () => message.fromMe = OptionalBool.deserialize(reader));
break;
case 5:
reader.readMessage(message.status, () => message.status = OptionalUInt32.deserialize(reader));
break;
default: reader.skipField();
}
}
+428 -4
View File
@@ -45,6 +45,7 @@ goog.exportSymbol('proto.messages.JidRequest', null, global);
goog.exportSymbol('proto.messages.JidStringRequest', null, global);
goog.exportSymbol('proto.messages.Json', null, global);
goog.exportSymbol('proto.messages.JsonList', null, global);
goog.exportSymbol('proto.messages.LinkPreview', null, global);
goog.exportSymbol('proto.messages.LogLevel', null, global);
goog.exportSymbol('proto.messages.MarkReadRequest', null, global);
goog.exportSymbol('proto.messages.Media', null, global);
@@ -662,6 +663,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.messages.Media.displayName = 'proto.messages.Media';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.messages.LinkPreview = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.messages.LinkPreview, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.messages.LinkPreview.displayName = 'proto.messages.LinkPreview';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -883,7 +905,7 @@ if (goog.DEBUG && !COMPILED) {
* @constructor
*/
proto.messages.MarkReadRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
jspb.Message.initialize(this, opt_data, 0, -1, proto.messages.MarkReadRequest.repeatedFields_, null);
};
goog.inherits(proto.messages.MarkReadRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
@@ -6338,6 +6360,250 @@ proto.messages.Media.prototype.setFilename = function(value) {
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.messages.LinkPreview.prototype.toObject = function(opt_includeInstance) {
return proto.messages.LinkPreview.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.messages.LinkPreview} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.messages.LinkPreview.toObject = function(includeInstance, msg) {
var f, obj = {
url: jspb.Message.getFieldWithDefault(msg, 1, ""),
title: jspb.Message.getFieldWithDefault(msg, 2, ""),
description: jspb.Message.getFieldWithDefault(msg, 3, ""),
image: msg.getImage_asB64()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.messages.LinkPreview}
*/
proto.messages.LinkPreview.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.messages.LinkPreview;
return proto.messages.LinkPreview.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.messages.LinkPreview} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.messages.LinkPreview}
*/
proto.messages.LinkPreview.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setUrl(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setTitle(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setDescription(value);
break;
case 4:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setImage(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.messages.LinkPreview.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.messages.LinkPreview.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.messages.LinkPreview} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.messages.LinkPreview.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getUrl();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getTitle();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getDescription();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
f = message.getImage_asU8();
if (f.length > 0) {
writer.writeBytes(
4,
f
);
}
};
/**
* optional string url = 1;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getUrl = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setUrl = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string title = 2;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getTitle = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setTitle = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string description = 3;
* @return {string}
*/
proto.messages.LinkPreview.prototype.getDescription = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/**
* @param {string} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setDescription = function(value) {
return jspb.Message.setProto3StringField(this, 3, value);
};
/**
* optional bytes image = 4;
* @return {!(string|Uint8Array)}
*/
proto.messages.LinkPreview.prototype.getImage = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 4, ""));
};
/**
* optional bytes image = 4;
* This is a type-conversion wrapper around `getImage()`
* @return {string}
*/
proto.messages.LinkPreview.prototype.getImage_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getImage()));
};
/**
* optional bytes image = 4;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getImage()`
* @return {!Uint8Array}
*/
proto.messages.LinkPreview.prototype.getImage_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getImage()));
};
/**
* @param {!(string|Uint8Array)} value
* @return {!proto.messages.LinkPreview} returns this
*/
proto.messages.LinkPreview.prototype.setImage = function(value) {
return jspb.Message.setProto3BytesField(this, 4, value);
};
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
@@ -6386,7 +6652,8 @@ proto.messages.MessageRequest.toObject = function(includeInstance, msg) {
linkpreviewhighquality: jspb.Message.getBooleanFieldWithDefault(msg, 8, false),
replyto: jspb.Message.getFieldWithDefault(msg, 9, ""),
id: jspb.Message.getFieldWithDefault(msg, 10, ""),
participantsList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f
participantsList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f,
preview: (f = msg.getPreview()) && proto.messages.LinkPreview.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -6471,6 +6738,11 @@ proto.messages.MessageRequest.deserializeBinaryFromReader = function(msg, reader
var value = /** @type {string} */ (reader.readString());
msg.addParticipants(value);
break;
case 12:
var value = new proto.messages.LinkPreview;
reader.readMessage(value,proto.messages.LinkPreview.deserializeBinaryFromReader);
msg.setPreview(value);
break;
default:
reader.skipField();
break;
@@ -6581,6 +6853,14 @@ proto.messages.MessageRequest.serializeBinaryToWriter = function(message, writer
f
);
}
f = message.getPreview();
if (f != null) {
writer.writeMessage(
12,
f,
proto.messages.LinkPreview.serializeBinaryToWriter
);
}
};
@@ -6877,6 +7157,43 @@ proto.messages.MessageRequest.prototype.clearParticipantsList = function() {
};
/**
* optional LinkPreview preview = 12;
* @return {?proto.messages.LinkPreview}
*/
proto.messages.MessageRequest.prototype.getPreview = function() {
return /** @type{?proto.messages.LinkPreview} */ (
jspb.Message.getWrapperField(this, proto.messages.LinkPreview, 12));
};
/**
* @param {?proto.messages.LinkPreview|undefined} value
* @return {!proto.messages.MessageRequest} returns this
*/
proto.messages.MessageRequest.prototype.setPreview = function(value) {
return jspb.Message.setWrapperField(this, 12, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.messages.MessageRequest} returns this
*/
proto.messages.MessageRequest.prototype.clearPreview = function() {
return this.setPreview(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.messages.MessageRequest.prototype.hasPreview = function() {
return jspb.Message.getField(this, 12) != null;
};
@@ -8645,6 +8962,13 @@ proto.messages.SubscribePresenceRequest.prototype.setJid = function(value) {
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
* @const
*/
proto.messages.MarkReadRequest.repeatedFields_ = [6];
if (jspb.Message.GENERATE_TO_OBJECT) {
@@ -8680,7 +9004,8 @@ proto.messages.MarkReadRequest.toObject = function(includeInstance, msg) {
jid: jspb.Message.getFieldWithDefault(msg, 2, ""),
sender: jspb.Message.getFieldWithDefault(msg, 3, ""),
messageid: jspb.Message.getFieldWithDefault(msg, 4, ""),
type: jspb.Message.getFieldWithDefault(msg, 5, 0)
type: jspb.Message.getFieldWithDefault(msg, 5, 0),
messageidsList: (f = jspb.Message.getRepeatedField(msg, 6)) == null ? undefined : f
};
if (includeInstance) {
@@ -8738,6 +9063,10 @@ proto.messages.MarkReadRequest.deserializeBinaryFromReader = function(msg, reade
var value = /** @type {!proto.messages.ReceiptType} */ (reader.readEnum());
msg.setType(value);
break;
case 6:
var value = /** @type {string} */ (reader.readString());
msg.addMessageids(value);
break;
default:
reader.skipField();
break;
@@ -8803,6 +9132,13 @@ proto.messages.MarkReadRequest.serializeBinaryToWriter = function(message, write
f
);
}
f = message.getMessageidsList();
if (f.length > 0) {
writer.writeRepeatedString(
6,
f
);
}
};
@@ -8915,6 +9251,43 @@ proto.messages.MarkReadRequest.prototype.setType = function(value) {
};
/**
* repeated string messageIds = 6;
* @return {!Array<string>}
*/
proto.messages.MarkReadRequest.prototype.getMessageidsList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 6));
};
/**
* @param {!Array<string>} value
* @return {!proto.messages.MarkReadRequest} returns this
*/
proto.messages.MarkReadRequest.prototype.setMessageidsList = function(value) {
return jspb.Message.setField(this, 6, value || []);
};
/**
* @param {string} value
* @param {number=} opt_index
* @return {!proto.messages.MarkReadRequest} returns this
*/
proto.messages.MarkReadRequest.prototype.addMessageids = function(value, opt_index) {
return jspb.Message.addToRepeatedField(this, 6, value, opt_index);
};
/**
* Clears the list making it empty but non-null.
* @return {!proto.messages.MarkReadRequest} returns this
*/
proto.messages.MarkReadRequest.prototype.clearMessageidsList = function() {
return this.setMessageidsList([]);
};
/**
* List of repeated fields within this message type.
@@ -14206,7 +14579,8 @@ proto.messages.MessageFilters.toObject = function(includeInstance, msg) {
jid: (f = msg.getJid()) && proto.messages.OptionalString.toObject(includeInstance, f),
timestampgte: (f = msg.getTimestampgte()) && proto.messages.OptionalUInt64.toObject(includeInstance, f),
timestamplte: (f = msg.getTimestamplte()) && proto.messages.OptionalUInt64.toObject(includeInstance, f),
fromme: (f = msg.getFromme()) && proto.messages.OptionalBool.toObject(includeInstance, f)
fromme: (f = msg.getFromme()) && proto.messages.OptionalBool.toObject(includeInstance, f),
status: (f = msg.getStatus()) && proto.messages.OptionalUInt32.toObject(includeInstance, f)
};
if (includeInstance) {
@@ -14263,6 +14637,11 @@ proto.messages.MessageFilters.deserializeBinaryFromReader = function(msg, reader
reader.readMessage(value,proto.messages.OptionalBool.deserializeBinaryFromReader);
msg.setFromme(value);
break;
case 5:
var value = new proto.messages.OptionalUInt32;
reader.readMessage(value,proto.messages.OptionalUInt32.deserializeBinaryFromReader);
msg.setStatus(value);
break;
default:
reader.skipField();
break;
@@ -14324,6 +14703,14 @@ proto.messages.MessageFilters.serializeBinaryToWriter = function(message, writer
proto.messages.OptionalBool.serializeBinaryToWriter
);
}
f = message.getStatus();
if (f != null) {
writer.writeMessage(
5,
f,
proto.messages.OptionalUInt32.serializeBinaryToWriter
);
}
};
@@ -14475,6 +14862,43 @@ proto.messages.MessageFilters.prototype.hasFromme = function() {
};
/**
* optional OptionalUInt32 status = 5;
* @return {?proto.messages.OptionalUInt32}
*/
proto.messages.MessageFilters.prototype.getStatus = function() {
return /** @type{?proto.messages.OptionalUInt32} */ (
jspb.Message.getWrapperField(this, proto.messages.OptionalUInt32, 5));
};
/**
* @param {?proto.messages.OptionalUInt32|undefined} value
* @return {!proto.messages.MessageFilters} returns this
*/
proto.messages.MessageFilters.prototype.setStatus = function(value) {
return jspb.Message.setWrapperField(this, 5, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.messages.MessageFilters} returns this
*/
proto.messages.MessageFilters.prototype.clearStatus = function() {
return this.setStatus(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.messages.MessageFilters.prototype.hasStatus = function() {
return jspb.Message.getField(this, 5) != null;
};
+74 -34
View File
@@ -1,9 +1,12 @@
import {
aggregateMessageKeysNotFromMe,
getContentType,
getUrlFromDirectPath,
isJidGroup,
jidNormalizedUser,
normalizeMessageContent,
} from '@adiwajshing/baileys';
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import * as grpc from '@grpc/grpc-js';
import { connectivityState } from '@grpc/grpc-js';
import { UnprocessableEntityException } from '@nestjs/common';
@@ -29,11 +32,7 @@ import {
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 { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
AvailableInPlusVersion,
@@ -41,6 +40,9 @@ import {
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import { ExtractMessageKeysForRead } from '@waha/core/utils/convertors';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { toJID } from '@waha/core/utils/jids';
import {
Channel,
ChannelListResult,
@@ -58,15 +60,17 @@ import {
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageButtonReply,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
@@ -91,6 +95,7 @@ import {
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { ReplyToMessage } from '@waha/structures/message.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import {
WAHAChatPresences,
@@ -105,7 +110,6 @@ import { MeInfo, ProxyConfig } from '@waha/structures/sessions.dto';
import {
BROADCAST_ID,
DeleteStatusRequest,
StatusRequest,
TextStatus,
} from '@waha/structures/status.dto';
import { EnginePayload, WAMessageAckBody } from '@waha/structures/webhooks.dto';
@@ -115,8 +119,10 @@ import { onlyEvent } from '@waha/utils/reactive/ops/onlyEvent';
import * as NodeCache from 'node-cache';
import {
debounceTime,
distinct,
filter,
groupBy,
interval,
merge,
mergeMap,
Observable,
@@ -128,9 +134,8 @@ import { map } from 'rxjs/operators';
import { promisify } from 'util';
import * as gows from './types';
import { MessageStatus } from './types';
import MessageServiceClient = messages.MessageServiceClient;
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { ReplyToMessage } from '@waha/structures/message.dto';
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -347,10 +352,18 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
let [messagesFromMe$, messagesFromOthers$] = partition(messages$, isMine);
messagesFromMe$ = messagesFromMe$.pipe(
map((msg) => {
msg.Status = MessageStatus.ServerAck;
return msg;
}),
mergeMap((msg) => this.processIncomingMessage(msg, true)),
share(), // share it so we don't process twice in message.any
);
messagesFromOthers$ = messagesFromOthers$.pipe(
map((msg) => {
msg.Status = MessageStatus.DeliveryAck;
return msg;
}),
mergeMap((msg) => this.processIncomingMessage(msg, true)),
share(), // share it so we don't process twice in message.any
);
@@ -361,13 +374,10 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const receipt$ = all$.pipe(onlyEvent(WhatsMeowEvent.RECEIPT));
const messageAck$ = receipt$.pipe(
mergeMap(this.receiptToMessageAck.bind(this)),
// Create a composite key for deduplication
groupBy((message) => `${message.id}-${message.ack}`),
mergeMap((group$) =>
group$.pipe(
debounceTime(1000), // Wait 1 second for deduplication
map((message) => message), // Pass the latest message after debounce
),
// emit only if we haven’t seen this key since the last flush
distinct(
(msg: WAMessageAckBody) => `${msg.id}-${msg.ack}-${msg.participant}`,
interval(60_000),
),
);
this.events2.get(WAHAEvents.MESSAGE_ACK).switch(messageAck$);
@@ -642,7 +652,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
protected messageResponse(jid, data) {
const message = parseJson(data.message);
const message = data.message ? parseJson(data.message) : null;
const id = buildMessageId({
ID: data.id,
IsFromMe: true,
@@ -692,20 +702,35 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
reply(request: MessageReplyRequest) {
throw new Error('Method not implemented.');
}
async sendSeen(request: SendSeenRequest) {
const key = parseMessageIdSerialized(request.messageId);
const req = new messages.MarkReadRequest({
session: this.session,
jid: key.remoteJid,
messageId: key.id,
sender: key.fromMe ? this.me.id : key.participant,
});
const response = await promisify(this.client.MarkRead)(req);
response.toObject();
const keys = ExtractMessageKeysForRead(request);
if (keys.length === 0) {
return;
}
const receipts = aggregateMessageKeysNotFromMe(keys);
for (const receipt of receipts) {
if (receipt.messageIds.length === 0) {
return;
}
const req = new messages.MarkReadRequest({
session: this.session,
jid: receipt.jid,
messageIds: receipt.messageIds,
sender: receipt.participant,
});
const response = await promisify(this.client.MarkRead)(req);
response.toObject();
}
return;
}
@@ -920,7 +945,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
jid: key.remoteJid,
messageId: key.id,
reaction: request.reaction,
sender: key.fromMe ? this.me.id : key.participant,
sender: key.fromMe ? this.me.id : key.participant || key.remoteJid,
});
const response = await promisify(this.client.SendReaction)(message);
const data = response.toObject();
@@ -1316,6 +1341,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
});
}
const status =
filter['filter.ack'] != null ? filter['filter.ack'] + 1 : null;
const request = new messages.GetMessagesRequest({
session: this.session,
filters: new messages.MessageFilters({
@@ -1329,6 +1356,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
messages.OptionalUInt64,
),
fromMe: optional(filter['filter.fromMe'], messages.OptionalBool),
status: optional(status, messages.OptionalUInt32),
}),
pagination: new messages.Pagination({
limit: query.limit,
@@ -1346,6 +1374,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return result;
}
public readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
): Promise<ReadChatMessagesResponse> {
return this.readChatMessagesWSImpl(chatId, request);
}
public async getChatMessage(
chatId: string,
messageId: string,
@@ -1375,8 +1410,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
if (message.Message.pollUpdateMessage) return;
// Ignore protocol messages
if (message.Message.protocolMessage) return;
// Ignore key distribution messages
if (message.Message.senderKeyDistributionMessage) return;
const normalizedContent = normalizeMessageContent(message.Message);
const hasSomeContent = !!getContentType(normalizedContent);
if (!hasSomeContent) {
// Ignore key distribution messages
if (message.message.senderKeyDistributionMessage) return;
}
if (downloadMedia) {
try {
@@ -1399,11 +1439,11 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const id = buildMessageId(message);
const body = this.extractBody(message.Message);
const replyTo = this.extractReplyTo(message.Message);
let ack;
if (message.Status) {
ack = statusToAck(message.Status);
} else {
ack = message.Info.IsFromMe ? WAMessageAck.SERVER : WAMessageAck.DEVICE;
let ack = statusToAck(message.Status);
if (ack === WAMessageAck.ERROR) {
// GOWS error because of how golang treats it as null
// It'll be UNKNOWN instead of ERROR
ack = null;
}
const mediaContent = extractMediaContent(message.Message);
const source = this.getSourceDeviceByMsg(message);
+95 -86
View File
@@ -46,7 +46,6 @@ import {
ensureSuffix,
getChannelInviteLink,
getPublicUrlFromDirectPath,
isNewsletter,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import {
@@ -66,6 +65,9 @@ import { toVcard } from '@waha/core/helpers';
import { createAgentProxy } from '@waha/core/helpers.proxy';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import { ExtractMessageKeysForRead } from '@waha/core/utils/convertors';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { isJidNewsletter, toJID } from '@waha/core/utils/jids';
import { flipObject, splitAt } from '@waha/helpers';
import { PairingCodeResponse } from '@waha/structures/auth.dto';
import { CallData } from '@waha/structures/calls.dto';
@@ -86,6 +88,8 @@ import {
GetChatMessagesFilter,
GetChatMessagesQuery,
PinDuration,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import {
@@ -97,6 +101,7 @@ import {
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -173,13 +178,13 @@ import {
share,
} from 'rxjs';
import { map } from 'rxjs/operators';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const promiseRetry = require('promise-retry');
import { INowebStore } from './store/INowebStore';
import { NowebPersistentStore } from './store/NowebPersistentStore';
import { NowebStorageFactoryCore } from './store/NowebStorageFactoryCore';
import { ensureNumber, extractMediaContent } from './utils';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const promiseRetry = require('promise-retry');
export const BaileysEvents = {
CONNECTION_UPDATE: 'connection.update',
@@ -390,6 +395,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.sock = await this.makeSocket();
this.issueMessageUpdateOnEdits();
this.fixMessageUpsertStatus();
this.issuePresenceUpdateOnMessageUpsert();
if (this.isDebugEnabled()) {
this.listenEngineEventsInDebugMode();
@@ -559,6 +565,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
await this.store?.close();
}
private fixMessageUpsertStatus() {
// If no status - set it to WAMessageAck.DEVICE + 1
this.sock.ev.on('messages.upsert', ({ messages }) => {
for (const message of messages) {
if (message.status == null) {
message.status = WAMessageAck.DEVICE + 1;
}
}
});
}
private issueMessageUpdateOnEdits() {
// Remove it after it's been merged
// https://github.com/WhiskeySockets/Baileys/pull/855/
@@ -824,6 +841,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
protected async uploadMedia(
file: RemoteFile | BinaryFile,
type,
@@ -886,16 +909,20 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
async sendSeen(request: SendSeenRequest) {
const key = parseMessageIdSerialized(request.messageId);
const participant = request.participant
? toJID(this.ensureSuffix(request.participant))
: undefined;
const data = {
remoteJid: key.remoteJid,
id: key.id,
participant: participant,
};
return this.sock.readMessages([data]);
const keys = ExtractMessageKeysForRead(request);
if (keys.length === 0) {
return;
}
// Send read
await this.sock.readMessages(keys);
// Emit events for our reads
const updates = keys.map((key) => ({
key: key,
update: { status: WAMessageAck.READ + 1 },
}));
this.sock?.ev.emit('messages.update', updates);
}
async startTyping(request: ChatRequest) {
@@ -930,6 +957,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return result;
}
public readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
): Promise<ReadChatMessagesResponse> {
return this.readChatMessagesWSImpl(chatId, request);
}
public async getChatMessage(
chatId: string,
messageId: string,
@@ -971,13 +1005,34 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
async setReaction(request: MessageReactionRequest) {
const key = parseMessageIdSerialized(request.messageId);
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
if (isJidNewsletter(key.remoteJid)) {
let serverId = Number(key.id);
if (!serverId) {
const msg = await this.store.getMessageById(key.remoteJid, key.id);
if (msg) {
// @ts-ignore
serverId = Number(msg.key.server_id);
}
}
if (!serverId) {
throw new UnprocessableEntityException(
`Unable to get server id for channel message '${key.id}'`,
);
}
return this.sock.newsletterReactMessage(
key.remoteJid,
serverId.toString(),
request.reaction,
);
} else {
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
}
}
async setStar(request: MessageStarRequest) {
@@ -1652,7 +1707,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
// @ts-ignore
filter(
(message) =>
message.message.protocolMessage?.type ===
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE,
),
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
@@ -1897,7 +1952,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (!message.message.reactionMessage) return null;
const id = buildMessageId(message.key);
const fromToParticipant = getFromToParticipant(message);
const fromToParticipant = getFromToParticipant(message.key);
const reactionMessage = message.message.reactionMessage;
const messageId = buildMessageId(reactionMessage.key);
const source = this.getMessageSource(message.key.id);
@@ -1929,16 +1984,23 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (message.message.call?.callKey) return;
// Ignore revoke, we have a dedicated handler for that
if (
message.message.protocolMessage?.type ===
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE
)
return;
if (
message.message.protocolMessage?.type ===
message.message?.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
)
return;
const normalizedContent = normalizeMessageContent(message.message);
const hasSomeContent = !!getContentType(normalizedContent);
if (!hasSomeContent) {
// Ignore key distribution messages
if (message.message.senderKeyDistributionMessage) return;
}
if (downloadMedia) {
try {
message = await this.downloadMedia(message);
@@ -1959,7 +2021,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
protected toWAMessage(message): Promise<WAMessage> {
const fromToParticipant = getFromToParticipant(message);
const fromToParticipant = getFromToParticipant(message.key);
const id = buildMessageId(message.key);
const body = this.extractBody(message.message);
const replyTo = this.extractReplyTo(message.message);
@@ -2050,7 +2112,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
protected convertMessageUpdateToMessageAck(event): WAMessageAckBody {
const message = event;
const fromToParticipant = getFromToParticipant(message);
const fromToParticipant = getFromToParticipant(message.key);
const id = buildMessageId(message.key);
const ack = message.update.status - 1;
const body: WAMessageAckBody = {
@@ -2066,7 +2128,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
protected convertMessageReceiptUpdateToMessageAck(event): WAMessageAckBody {
const fromToParticipant = getFromToParticipant(event);
const fromToParticipant = getFromToParticipant(event.key);
const id = buildMessageId(event.key);
const receipt = event.receipt;
@@ -2336,7 +2398,7 @@ export function toCusFormat(remoteJid) {
if (isLidUser(remoteJid)) {
return remoteJid;
}
if (isNewsletter(remoteJid)) {
if (isJidNewsletter(remoteJid)) {
return remoteJid;
}
if (!remoteJid) {
@@ -2353,27 +2415,6 @@ export function toCusFormat(remoteJid) {
export const ALL_JID = 'all@s.whatsapp.net';
/**
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
* @param chatId
*/
export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
if (isJidBroadcast(chatId)) {
return chatId;
}
if (isNewsletter(chatId)) {
return chatId;
}
if (isLidUser(chatId)) {
return chatId;
}
const number = chatId.split('@')[0];
return number + '@s.whatsapp.net';
}
/**
* Build WAHA message id from engine one
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
@@ -2388,38 +2429,6 @@ function buildMessageId({ id, remoteJid, fromMe, participant }: WAMessageKey) {
return parts.join('_');
}
/**
* Parse message id from WAHA to engine
* false_11111111111@c.us_AAA
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
*/
export function parseMessageIdSerialized(
messageId: string,
soft: boolean = false,
): WAMessageKey {
if (!messageId.includes('_') && soft) {
return { id: messageId };
}
const parts = messageId.split('_');
if (parts.length != 3 && parts.length != 4) {
throw new Error(
'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA[_participant]',
);
}
const fromMe = parts[0] == 'true';
const chatId = parts[1];
const remoteJid = toJID(chatId);
const id = parts[2];
const participant = parts[3] ? toJID(parts[3]) : undefined;
return {
fromMe: fromMe,
id: id,
remoteJid: remoteJid,
participant: participant,
};
}
function getId(object) {
return object.id;
}
@@ -2436,15 +2445,15 @@ function isAckUpdateMessageEvent(event) {
return event?.update.status != null;
}
function getFromToParticipant(message) {
const isGroupMessage = Boolean(message.key.participant);
export function getFromToParticipant(key) {
const isGroupMessage = Boolean(key.participant);
let participant: string;
let to: string;
if (isGroupMessage) {
participant = message.key.participant;
to = message.key.remoteJid;
participant = key.participant;
to = key.remoteJid;
}
const from = message.key.remoteJid;
const from = key.remoteJid;
return {
from: from,
to: to,
@@ -2,7 +2,6 @@ import makeWASocket, {
Chat,
Contact,
GroupMetadata,
makeInMemoryStore,
proto,
} from '@adiwajshing/baileys';
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
@@ -12,6 +11,7 @@ import { PaginationParams } from '@waha/structures/pagination.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { INowebStore } from './INowebStore';
import makeInMemoryStore from './memory/make-in-memory-store';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const logger = require('pino')();
@@ -0,0 +1,564 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type {
BaileysEventEmitter,
Chat,
ConnectionState,
Contact,
GroupMetadata,
PresenceData,
WAMessage,
WAMessageCursor,
WAMessageKey,
} from '@adiwajshing/baileys/lib/Types';
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
import {
LabelAssociation,
LabelAssociationType,
MessageLabelAssociation,
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import {
md5,
toNumber,
updateMessageWithReaction,
updateMessageWithReceipt,
} from '@adiwajshing/baileys/lib/Utils';
import {
jidDecode,
jidNormalizedUser,
} from '@adiwajshing/baileys/lib/WABinary';
import { proto } from '@adiwajshing/baileys/WAProto';
import type { Comparable } from '@adiwajshing/keyed-db/lib/Types';
import type { Logger } from 'pino';
import makeOrderedDictionary from './make-ordered-dictionary';
import { ObjectRepository } from './object-repository';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const KeyedDB = require('@adiwajshing/keyed-db').default;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { writeFileSync } = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { readFileSync, existsSync } = require('fs');
type WASocket = ReturnType<any>;
export const waChatKey = (pin: boolean) => ({
key: (c: Chat) =>
(pin ? (c.pinned ? '1' : '0') : '') +
(c.archived ? '0' : '1') +
(c.conversationTimestamp
? c.conversationTimestamp.toString(16).padStart(8, '0')
: '') +
c.id,
compare: (k1: string, k2: string) => k2.localeCompare(k1),
});
export const waMessageID = (m: WAMessage) => m.key.id || '';
export const waLabelAssociationKey: Comparable<LabelAssociation, string> = {
key: (la: LabelAssociation) =>
la.type === LabelAssociationType.Chat
? la.chatId + la.labelId
: la.chatId + la.messageId + la.labelId,
compare: (k1: string, k2: string) => k2.localeCompare(k1),
};
export type BaileysInMemoryStoreConfig = {
chatKey?: Comparable<Chat, string>;
labelAssociationKey?: Comparable<LabelAssociation, string>;
logger: Logger;
socket?: WASocket;
};
const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID);
export default (config: BaileysInMemoryStoreConfig) => {
const socket = config.socket;
const chatKey = config.chatKey || waChatKey(true);
const labelAssociationKey =
config.labelAssociationKey || waLabelAssociationKey;
const logger: Logger = config.logger;
const chats = new KeyedDB(chatKey, (c) => c.id) as any;
const messages: { [_: string]: ReturnType<typeof makeMessagesDictionary> } =
{};
const contacts: { [_: string]: Contact } = {};
const groupMetadata: { [_: string]: GroupMetadata } = {};
let presences: { [id: string]: { [participant: string]: PresenceData } } = {};
const state: ConnectionState = { connection: 'close' };
const labels = new ObjectRepository<Label>();
const labelAssociations = new KeyedDB(
labelAssociationKey,
labelAssociationKey.key,
) as any;
const assertMessageList = (jid: string) => {
if (!messages[jid]) {
messages[jid] = makeMessagesDictionary();
}
return messages[jid];
};
const contactsUpsert = (newContacts: Contact[]) => {
const oldContacts = new Set(Object.keys(contacts));
for (const contact of newContacts) {
oldContacts.delete(contact.id);
contacts[contact.id] = Object.assign(contacts[contact.id] || {}, contact);
}
return oldContacts;
};
const labelsUpsert = (newLabels: Label[]) => {
for (const label of newLabels) {
labels.upsertById(label.id, label);
}
};
/**
* binds to a BaileysEventEmitter.
* It listens to all events and constructs a state that you can query accurate data from.
* Eg. can use the store to fetch chats, contacts, messages etc.
* @param ev typically the event emitter from the socket connection
*/
const bind = (ev: BaileysEventEmitter) => {
ev.on('connection.update', (update) => {
Object.assign(state, update);
});
ev.on(
'messaging-history.set',
({
chats: newChats,
contacts: newContacts,
messages: newMessages,
isLatest,
syncType,
}) => {
if (syncType === proto.HistorySync.HistorySyncType.ON_DEMAND) {
return; // FOR NOW,
//TODO: HANDLE
}
if (isLatest) {
chats.clear();
for (const id in messages) {
delete messages[id];
}
}
const chatsAdded = chats.insertIfAbsent(...newChats).length;
logger.debug({ chatsAdded }, 'synced chats');
const oldContacts = contactsUpsert(newContacts);
if (isLatest) {
for (const jid of oldContacts) {
delete contacts[jid];
}
}
logger.debug(
{ deletedContacts: isLatest ? oldContacts.size : 0, newContacts },
'synced contacts',
);
for (const msg of newMessages) {
const jid = msg.key.remoteJid!;
const list = assertMessageList(jid);
list.upsert(msg, 'prepend');
}
logger.debug({ messages: newMessages.length }, 'synced messages');
},
);
ev.on('contacts.upsert', (contacts) => {
contactsUpsert(contacts);
});
ev.on('contacts.update', async (updates) => {
for (const update of updates) {
let contact: Contact;
if (contacts[update.id!]) {
contact = contacts[update.id!];
} else {
const contactHashes = await Promise.all(
Object.keys(contacts).map(async (contactId) => {
const { user } = jidDecode(contactId)!;
return [
contactId,
(await md5(Buffer.from(user + 'WA_ADD_NOTIF', 'utf8')))
.toString('base64')
.slice(0, 3),
];
}),
);
contact =
contacts[contactHashes.find(([, b]) => b === update.id)?.[0] || '']; // find contact by attrs.hash, when user is not saved as a contact
}
if (contact) {
if (update.imgUrl === 'changed') {
contact.imgUrl = socket
? await socket?.profilePictureUrl(contact.id)
: undefined;
} else if (update.imgUrl === 'removed') {
delete contact.imgUrl;
}
} else {
return logger.debug(
{ update },
'got update for non-existant contact',
);
}
Object.assign(contacts[contact.id], contact);
}
});
ev.on('chats.upsert', (newChats) => {
chats.upsert(...newChats);
});
ev.on('chats.update', (updates) => {
for (let update of updates) {
const result = chats.update(update.id!, (chat) => {
if (update.unreadCount! > 0) {
update = { ...update };
update.unreadCount = (chat.unreadCount || 0) + update.unreadCount!;
}
Object.assign(chat, update);
});
if (!result) {
logger.debug({ update }, 'got update for non-existant chat');
}
}
});
ev.on('labels.edit', (label: Label) => {
if (label.deleted) {
return labels.deleteById(label.id);
}
// WhatsApp can store only up to 20 labels
if (labels.count() < 20) {
return labels.upsertById(label.id, label);
}
logger.error('Labels count exceed');
});
ev.on('labels.association', ({ type, association }) => {
switch (type) {
case 'add':
labelAssociations.upsert(association);
break;
case 'remove':
labelAssociations.delete(association);
break;
default:
console.error(`unknown operation type [${type}]`);
}
});
ev.on('presence.update', ({ id, presences: update }) => {
presences[id] = presences[id] || {};
Object.assign(presences[id], update);
});
ev.on('chats.delete', (deletions) => {
for (const item of deletions) {
if (chats.get(item)) {
chats.deleteById(item);
}
}
});
ev.on('messages.upsert', ({ messages: newMessages, type }) => {
switch (type) {
case 'append':
case 'notify':
for (const msg of newMessages) {
const jid = jidNormalizedUser(msg.key.remoteJid!);
const list = assertMessageList(jid);
list.upsert(msg, 'append');
if (type === 'notify' && !chats.get(jid)) {
ev.emit('chats.upsert', [
{
id: jid,
conversationTimestamp: toNumber(msg.messageTimestamp),
unreadCount: 1,
},
]);
}
}
break;
}
});
ev.on('messages.update', (updates) => {
for (const { update, key } of updates) {
const list = assertMessageList(jidNormalizedUser(key.remoteJid!));
if (update?.status) {
const listStatus = list.get(key.id!)?.status;
if (listStatus && update?.status <= listStatus) {
logger.debug(
{ update, storedStatus: listStatus },
'status stored newer then update',
);
delete update.status;
logger.debug({ update }, 'new update object');
}
}
const result = list.updateAssign(key.id!, update);
if (!result) {
logger.debug({ update }, 'got update for non-existent message');
}
}
});
ev.on('messages.delete', (item) => {
if ('all' in item) {
const list = messages[item.jid];
list?.clear();
} else {
const jid = item.keys[0].remoteJid!;
const list = messages[jid];
if (list) {
const idSet = new Set(item.keys.map((k) => k.id));
list.filter((m) => !idSet.has(m.key.id));
}
}
});
ev.on('groups.update', (updates) => {
for (const update of updates) {
const id = update.id!;
if (groupMetadata[id]) {
Object.assign(groupMetadata[id], update);
} else {
logger.debug(
{ update },
'got update for non-existant group metadata',
);
}
}
});
ev.on('group-participants.update', ({ id, participants, action }) => {
const metadata = groupMetadata[id];
if (metadata) {
switch (action) {
case 'add':
metadata.participants.push(
...participants.map((id) => ({
id,
isAdmin: false,
isSuperAdmin: false,
})),
);
break;
case 'demote':
case 'promote':
for (const participant of metadata.participants) {
if (participants.includes(participant.id)) {
participant.isAdmin = action === 'promote';
}
}
break;
case 'remove':
metadata.participants = metadata.participants.filter(
(p) => !participants.includes(p.id),
);
break;
}
}
});
ev.on('message-receipt.update', (updates) => {
for (const { key, receipt } of updates) {
const obj = messages[key.remoteJid!];
const msg = obj?.get(key.id!);
if (msg) {
updateMessageWithReceipt(msg, receipt);
}
}
});
ev.on('messages.reaction', (reactions) => {
for (const { key, reaction } of reactions) {
const obj = messages[key.remoteJid!];
const msg = obj?.get(key.id!);
if (msg) {
updateMessageWithReaction(msg, reaction);
}
}
});
};
const toJSON = () => ({
chats,
contacts,
messages,
labels,
labelAssociations,
});
const fromJSON = (json: {
chats: Chat[];
contacts: { [id: string]: Contact };
messages: { [id: string]: WAMessage[] };
labels: { [labelId: string]: Label };
labelAssociations: LabelAssociation[];
}) => {
chats.upsert(...json.chats);
labelAssociations.upsert(...(json.labelAssociations || []));
contactsUpsert(Object.values(json.contacts));
labelsUpsert(Object.values(json.labels || {}));
for (const jid in json.messages) {
const list = assertMessageList(jid);
for (const msg of json.messages[jid]) {
list.upsert(proto.WebMessageInfo.fromObject(msg), 'append');
}
}
};
return {
chats,
contacts,
messages,
groupMetadata,
state,
presences,
setPresences: (obj: any) => {
presences = obj;
},
labels,
labelAssociations,
bind,
/** loads messages from the store, if not found -- uses the legacy connection */
loadMessages: async (
jid: string,
count: number,
cursor: WAMessageCursor,
) => {
const list = assertMessageList(jid);
const mode = !cursor || 'before' in cursor ? 'before' : 'after';
const cursorKey = !!cursor
? 'before' in cursor
? cursor.before
: cursor.after
: undefined;
const cursorValue = cursorKey ? list.get(cursorKey.id!) : undefined;
let messages: WAMessage[];
if (list && mode === 'before' && (!cursorKey || cursorValue)) {
if (cursorValue) {
const msgIdx = list.array.findIndex(
(m) => m.key.id === cursorKey?.id,
);
messages = list.array.slice(0, msgIdx);
} else {
messages = list.array;
}
const diff = count - messages.length;
if (diff < 0) {
messages = messages.slice(-count); // get the last X messages
}
} else {
messages = [];
}
return messages;
},
/**
* Get all available labels for profile
*
* Keep in mind that the list is formed from predefined tags and tags
* that were "caught" during their editing.
*/
getLabels: () => {
return labels;
},
/**
* Get labels for chat
*
* @returns Label IDs
**/
getChatLabels: (chatId: string) => {
return labelAssociations.filter((la) => la.chatId === chatId).all();
},
/**
* Get labels for message
*
* @returns Label IDs
**/
getMessageLabels: (messageId: string) => {
const associations = labelAssociations
.filter((la: MessageLabelAssociation) => la.messageId === messageId)
.all();
return associations.map(({ labelId }) => labelId);
},
loadMessage: async (jid: string, id: string) => messages[jid]?.get(id),
mostRecentMessage: async (jid: string) => {
const message: WAMessage | undefined = messages[jid]?.array.slice(-1)[0];
return message;
},
fetchImageUrl: async (jid: string, sock: WASocket | undefined) => {
const contact = contacts[jid];
if (!contact) {
return sock?.profilePictureUrl(jid);
}
if (typeof contact.imgUrl === 'undefined') {
contact.imgUrl = await sock?.profilePictureUrl(jid);
}
return contact.imgUrl;
},
fetchGroupMetadata: async (jid: string, sock: WASocket | undefined) => {
if (!groupMetadata[jid]) {
const metadata = await sock?.groupMetadata(jid);
if (metadata) {
groupMetadata[jid] = metadata;
}
}
return groupMetadata[jid];
},
// fetchBroadcastListInfo: async(jid: string, sock: WASocket | undefined) => {
// if(!groupMetadata[jid]) {
// const metadata = await sock?.getBroadcastListInfo(jid)
// if(metadata) {
// groupMetadata[jid] = metadata
// }
// }
// return groupMetadata[jid]
// },
fetchMessageReceipts: async ({ remoteJid, id }: WAMessageKey) => {
const list = messages[remoteJid!];
const msg = list?.get(id!);
return msg?.userReceipt;
},
toJSON,
fromJSON,
writeToFile: (path: string) => {
writeFileSync(path, JSON.stringify(toJSON()));
},
readFromFile: (path: string) => {
if (existsSync(path)) {
logger.debug({ path }, 'reading from file');
const jsonStr = readFileSync(path, { encoding: 'utf-8' });
const json = JSON.parse(jsonStr);
fromJSON(json);
}
},
};
};
@@ -0,0 +1,86 @@
function makeOrderedDictionary<T>(idGetter: (item: T) => string) {
const array: T[] = [];
const dict: { [_: string]: T } = {};
const get = (id: string): T | undefined => dict[id];
const update = (item: T) => {
const id = idGetter(item);
const idx = array.findIndex((i) => idGetter(i) === id);
if (idx >= 0) {
array[idx] = item;
dict[id] = item;
}
return false;
};
const upsert = (item: T, mode: 'append' | 'prepend') => {
const id = idGetter(item);
if (get(id)) {
update(item);
} else {
if (mode === 'append') {
array.push(item);
} else {
array.splice(0, 0, item);
}
dict[id] = item;
}
};
const remove = (item: T) => {
const id = idGetter(item);
const idx = array.findIndex((i) => idGetter(i) === id);
if (idx >= 0) {
array.splice(idx, 1);
delete dict[id];
return true;
}
return false;
};
return {
array,
get,
upsert,
update,
remove,
updateAssign: (id: string, update: Partial<T>) => {
const item = get(id);
if (item) {
Object.assign(item, update);
delete dict[id];
dict[idGetter(item)] = item;
return true;
}
return false;
},
clear: () => {
array.splice(0, array.length);
for (const key of Object.keys(dict)) {
delete dict[key];
}
},
filter: (contain: (item: T) => boolean) => {
let i = 0;
while (i < array.length) {
if (!contain(array[i])) {
delete dict[idGetter(array[i])];
array.splice(i, 1);
} else {
i += 1;
}
}
},
toJSON: () => array,
fromJSON: (newItems: T[]) => {
array.splice(0, array.length, ...newItems);
},
};
}
export default makeOrderedDictionary;
@@ -0,0 +1,32 @@
export class ObjectRepository<T extends any> {
readonly entityMap: Map<string, T>;
constructor(entities: Record<string, T> = {}) {
this.entityMap = new Map(Object.entries(entities));
}
findById(id: string) {
return this.entityMap.get(id);
}
findAll() {
return Array.from(this.entityMap.values());
}
upsertById(id: string, entity: T) {
// @ts-ignore
return this.entityMap.set(id, { ...entity });
}
deleteById(id: string) {
return this.entityMap.delete(id);
}
count() {
return this.entityMap.size;
}
toJSON() {
return this.findAll();
}
}
@@ -35,14 +35,26 @@ export class SqlMessagesMethods {
}
if (filter['filter.fromMe'] != null) {
// filter by data json inside
const sql = this.repository.filterJson('data', 'key.fromMe');
query = query.whereRaw(sql, [filter['filter.fromMe'] ? 'true' : 'false']);
const [sql, value] = this.repository.filterJson(
'data',
'key.fromMe',
filter['filter.fromMe'],
);
query = query.whereRaw(sql, [value]);
}
if (filter['filter.ack'] != null) {
const status = filter['filter.ack'] + 1;
const [sql, value] = this.repository.filterJson('data', 'status', status);
query = query.whereRaw(sql, [value]);
}
query = this.repository.pagination(query, pagination);
return this.repository.all(query);
}
async getByJidById(jid: string, id: string): Promise<any> {
if (jid === ALL_JID) {
return this.repository.getBy({ id: id });
}
return this.repository.getBy({ jid: jid, id: id });
}
+110
View File
@@ -0,0 +1,110 @@
import { sleep } from '@waha/utils/promiseTimeout';
import { EventEmitter } from 'events';
import { Key } from 'node-cache';
import { Page } from 'puppeteer';
export interface CallErrorEvent {
method: string | symbol;
error: unknown;
}
export const PAGE_CALL_ERROR_EVENT = 'page.call.error';
/**
* WPage is a puppeter.Page wrapper that use ALL original methods from Page,
* but in case if there were any errors in the call (original) -
* it calls the errorCallback with method name, error and arguments
*
* Find in webjs used functions like "pupPage.{function}"
* >>> cd src
* >>> grep -rho 'page\.[a-zA-Z0-9_]\+(' . --include="*.js" | sed 's/($//' | sort -u
*
* Find in webjs used functions like "page.{function}"
* >>> cd src
* >>> grep -rho 'page\.[a-zA-Z0-9_]\+(' . --include="*.js" | sed 's/($//' | sort -u
*
*/
export class WPage extends EventEmitter {
constructor(private page: Page) {
super();
}
async evaluate(fn, ...args: any[]) {
try {
return await this.page.evaluate(fn, ...args);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, { method: 'evaluate', error: err });
throw err;
}
}
async screenshot(options) {
try {
return await this.page.screenshot(options);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, { method: 'screenshot', error: err });
throw err;
}
}
// waitForNavigation
async waitForNavigation(options: any) {
try {
return await this.page.waitForNavigation(options);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, {
method: 'waitForNavigation',
error: err,
});
throw err;
}
}
// waitForFunction
async waitForFunction(fn, options, ...args) {
try {
return await this.page.waitForFunction(fn, options, ...args);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, {
method: 'waitForFunction',
error: err,
});
throw err;
}
}
// setRequestInterception
async setRequestInterception(value: boolean) {
try {
return await this.page.setRequestInterception(value);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, {
method: 'setRequestInterception',
error: err,
});
throw err;
}
}
// exposeFunction
async exposeFunction(name: string, fn: (...args: any[]) => any) {
try {
return await this.page.exposeFunction(name, fn);
} catch (err) {
this.emit(PAGE_CALL_ERROR_EVENT, {
method: 'exposeFunction',
error: err,
});
throw err;
}
}
on(event: string | symbol, listener: (...args: any[]) => void): this {
if (event === PAGE_CALL_ERROR_EVENT) {
super.on(event, listener);
return this;
}
this.page.on(event, listener);
return this.page as any;
}
}
+30 -1
View File
@@ -4,10 +4,13 @@ import { PaginationParams } from '@waha/structures/pagination.dto';
import { TextStatus } from '@waha/structures/status.dto';
import { EventEmitter } from 'events';
import * as lodash from 'lodash';
import { Page } from 'puppeteer';
import { Client, Events } from 'whatsapp-web.js';
import { Message } from 'whatsapp-web.js/src/structures';
import { exposeFunctionIfAbsent } from 'whatsapp-web.js/src/util/Puppeter';
import { CallErrorEvent, PAGE_CALL_ERROR_EVENT, WPage } from './WPage';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadWAHA } = require('./_WAHA.js');
@@ -22,6 +25,7 @@ const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
export class WebjsClientCore extends Client {
public events = new EventEmitter();
private wpage: WPage = null;
constructor(options) {
super(options);
@@ -32,6 +36,18 @@ export class WebjsClientCore extends Client {
});
}
async initialize() {
const result = await super.initialize();
if (this.pupPage && !(this.pupPage instanceof WPage)) {
this.wpage = new WPage(this.pupPage);
this.wpage.on(PAGE_CALL_ERROR_EVENT as any, (event: CallErrorEvent) => {
this.events.emit(PAGE_CALL_ERROR_EVENT as any, event);
});
this.pupPage = this.wpage as any as Page;
}
return result;
}
async injectWaha() {
await this.pupPage.evaluate(LoadLodash);
await this.pupPage.evaluate(LoadPaginator);
@@ -51,6 +67,7 @@ export class WebjsClientCore extends Client {
async destroy() {
this.events.removeAllListeners();
this.wpage?.removeAllListeners();
await super.destroy();
}
@@ -199,6 +216,9 @@ export class WebjsClientCore extends Client {
) {
return false;
}
if (filter['filter.ack'] != null && m.ack !== filter['filter.ack']) {
return false;
}
return true;
};
@@ -210,8 +230,17 @@ export class WebjsClientCore extends Client {
const loadedMessages =
// @ts-ignore
await window.Store.ConversationMsgs.loadEarlierMsgs(chat);
if (!loadedMessages || !loadedMessages.length) break;
if (!loadedMessages || loadedMessages.length == 0) break;
msgs = [...loadedMessages.filter(msgFilter), ...msgs];
msgs = msgs.sort((a, b) => b.t - a.t);
// Check if the earliest message is already outside the timerange filter
const earliest = msgs[msgs.length - 1];
if (earliest.t < (filter['filter.timestamp.gte'] || Infinity)) {
// Add only messages that pass the filter and stop loading more
break;
}
}
if (msgs.length > pagination.limit + pagination.offset) {
+163
View File
@@ -0,0 +1,163 @@
import {
areJidsSameUser,
BinaryNode,
getBinaryNodeChildren,
getStatusFromReceiptType,
isJidGroup,
isJidStatusBroadcast,
jidEncode,
jidNormalizedUser,
proto,
} from '@adiwajshing/baileys';
export interface ReceiptEvent {
key: proto.IMessageKey;
participant?: string;
messageIds: string[];
status: number;
_node: any;
}
interface Me {
id: string;
lid?: string;
}
function jid(field: any) {
if (!field) {
return field;
}
const data = field['$1'];
if (!data) {
return data;
}
let server = data.server;
if (!server) {
server =
data.domainType === 0 || data.domainType === 128
? 's.whatsapp.net'
: 'lid';
}
return jidEncode(data.user, server, data.device);
}
export function TagReceiptNodeToReceiptEvent(
node: BinaryNode,
me: Me,
): ReceiptEvent[] {
const { attrs, content } = node;
const status = getStatusFromReceiptType(attrs.type);
if (status == null) {
return null;
}
const from = jidNormalizedUser(jid(attrs.from));
const participant = jidNormalizedUser(jid(attrs.participant));
const recipient = jidNormalizedUser(jid(attrs.recipient));
const isLid = from.includes('lid');
const isNodeFromMe = areJidsSameUser(
participant || from,
isLid ? me?.lid : me?.id,
);
const remoteJid = !isNodeFromMe || isJidGroup(from) ? from : recipient;
const fromMe = !recipient || (attrs.type === 'retry' && isNodeFromMe);
// basically, we only want to know when a message from us has been delivered to/read by the other person
// or another device of ours has read some messages
if (status < proto.WebMessageInfo.Status.SERVER_ACK && isNodeFromMe) {
return [];
}
const key: proto.IMessageKey = {
remoteJid: remoteJid,
id: '',
fromMe: fromMe,
};
const ids = [attrs.id];
if (Array.isArray(content)) {
const items = getBinaryNodeChildren(content[0], 'item');
ids.push(...items.map((i) => i.attrs.id));
}
if (isJidGroup(remoteJid) || isJidStatusBroadcast(remoteJid)) {
if (participant) {
key.participant = fromMe ? (isLid ? me.lid : me.id) : recipient;
const eventParticipant = fromMe ? participant : isLid ? me.lid : me.id;
return [
{
key: key,
messageIds: ids,
status: status as any,
participant: eventParticipant,
_node: node,
},
];
} else {
// Handle grouped receipts
return handleGroupedReceipts(node, key, status, fromMe, isLid, me);
}
}
return [
{
key: key,
messageIds: ids,
status: status as any,
_node: node,
},
];
}
function handleGroupedReceipts(
node: BinaryNode,
key: proto.IMessageKey,
status: number,
fromMe: boolean,
isLid: boolean,
me: Me,
): ReceiptEvent[] | null {
const { content } = node;
if (!Array.isArray(content)) {
return [];
}
const participantsTags = content.filter((c) => c.tag === 'participants');
if (participantsTags.length === 0) {
return null;
}
const receiptEvents: ReceiptEvent[] = [];
for (const participants of participantsTags) {
const participantKey = participants.attrs?.key;
if (!participantKey) continue;
const users = getBinaryNodeChildren(participants, 'user');
for (const user of users) {
const userAttrs = user.attrs;
if (!userAttrs) continue;
const userJid = jidNormalizedUser(jid(userAttrs.jid));
if (!userJid) continue;
key.participant = fromMe ? (isLid ? me.lid : me.id) : userJid;
const eventParticipant = fromMe ? userJid : isLid ? me.lid : me.id;
const receiptEvent: ReceiptEvent = {
key: {
...key,
id: participantKey,
},
messageIds: [participantKey],
status: status as any,
participant: eventParticipant,
_node: node,
};
receiptEvents.push(receiptEvent);
}
}
return receiptEvents;
}
+126 -5
View File
@@ -1,8 +1,17 @@
import { isJidGroup, isJidStatusBroadcast } from '@adiwajshing/baileys';
import { UnprocessableEntityException } from '@nestjs/common';
import {
getChannelInviteLink,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import {
getFromToParticipant,
toCusFormat,
} from '@waha/core/engines/noweb/session.noweb.core';
import {
ReceiptEvent,
TagReceiptNodeToReceiptEvent,
} from '@waha/core/engines/webjs/ack.webjs';
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
@@ -11,12 +20,20 @@ import {
} from '@waha/core/engines/webjs/groups.webjs';
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
import {
CallErrorEvent,
PAGE_CALL_ERROR_EVENT,
} from '@waha/core/engines/webjs/WPage';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import {
parseMessageIdSerialized,
SerializeMessageKey,
} from '@waha/core/utils/ids';
import { splitAt } from '@waha/helpers';
import { PairingCodeResponse } from '@waha/structures/auth.dto';
import {
@@ -36,6 +53,8 @@ import {
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
@@ -83,13 +102,25 @@ import { MeInfo } from '@waha/structures/sessions.dto';
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
EnginePayload,
WAMessageAckBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
import * as lodash from 'lodash';
import { filter, fromEvent, merge, mergeMap, Observable } from 'rxjs';
import { ProtocolError } from 'puppeteer';
import {
debounceTime,
distinct,
filter,
fromEvent,
groupBy,
interval,
merge,
mergeMap,
Observable,
} from 'rxjs';
import { map } from 'rxjs/operators';
import {
Call,
@@ -103,6 +134,7 @@ import {
Label as WEBJSLabel,
Location,
Message,
MessageAck,
MessageMedia,
Reaction,
WAState,
@@ -238,12 +270,29 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
this.logger.error('The browser has been disconnected');
this.failed();
});
// Listen for page close event
this.whatsapp.pupPage.on('close', () => {
this.logger.error('The WhatsApp Web page has been closed');
this.failed();
});
// Listen for function call errors
this.whatsapp.events.on(
PAGE_CALL_ERROR_EVENT,
(event: CallErrorEvent) => {
if (event.error instanceof ProtocolError) {
this.logger.error(
`ProtocolError when calling page method: ${String(
event.method,
)}, restarting client...`,
);
this.logger.error(event.error);
this.failed();
}
},
);
// Listen for page error event
if (this.isDebugEnabled()) {
this.logger.debug("Logging 'console' event for web page");
@@ -691,6 +740,17 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return result;
}
public async readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
): Promise<ReadChatMessagesResponse> {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(chatId),
);
await chat.sendSeen();
return { ids: null };
}
public async getChatMessage(
chatId: string,
messageId: string,
@@ -698,6 +758,19 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
): Promise<null | WAMessage> {
const message = await this.whatsapp.getMessageById(messageId);
if (!message) return null;
if (
isJidGroup(message.id.remote) ||
isJidStatusBroadcast(message.id.remote)
) {
// @ts-ignore
message.rawData.receipts = await message.getInfo().catch((error) => {
this.logger.error(
{ error: error, msg: message.id._serialized },
'Failed to get receipts',
);
return null;
});
}
return await this.processIncomingMessage(message, query.downloadMedia);
}
@@ -1240,18 +1313,37 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
);
this.events2.get(WAHAEvents.MESSAGE_REACTION).switch(messagesReaction$);
const messageAck$ = fromEvent(
const messageAckWEBJS$ = fromEvent(
this.whatsapp,
Events.MESSAGE_ACK,
(message, ack) => {
return { message, ack };
},
);
const messagesAck$ = messageAck$.pipe(
const messagesAckDM$ = messageAckWEBJS$.pipe(
map((event) => event.message),
map(this.toWAMessage.bind(this)),
map<any, WAMessage>(this.toWAMessage.bind(this)),
filter((ack) => !isJidGroup(ack.to) && !isJidStatusBroadcast(ack.to)),
);
this.events2.get(WAHAEvents.MESSAGE_ACK).switch(messagesAck$);
const tagReceiptNode$ = fromEvent(this.whatsapp, Events.TAG_RECEIPT);
const messageAckGroups$ = tagReceiptNode$.pipe(
mergeMap((node) =>
TagReceiptNodeToReceiptEvent(node as any, this.getSessionMeInfo()),
),
mergeMap(this.TagReceiptToMessageAck.bind(this)),
filter((ack) => isJidGroup(ack.to) || isJidStatusBroadcast(ack.to)),
);
const messageAckAll$ = merge(messagesAckDM$, messageAckGroups$);
const messageAck$ = messageAckAll$.pipe(
// emit only if we haven’t seen this key since the last flush
distinct(
(msg: WAMessageAckBody) => `${msg.id}-${msg.ack}-${msg.participant}`,
interval(60_000),
),
);
this.events2.get(WAHAEvents.MESSAGE_ACK).switch(messageAck$);
//
// Others
@@ -1383,15 +1475,44 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
protected TagReceiptToMessageAck(receipt: ReceiptEvent): WAMessageAckBody[] {
const ids = receipt.messageIds;
const acks = [];
for (const id_ of ids) {
const messageKey = {
fromMe: receipt.key.fromMe,
remoteJid: toCusFormat(receipt.key.remoteJid),
participant: toCusFormat(receipt.key.participant),
id: id_,
};
const fromToParticipant = getFromToParticipant(messageKey);
const id = SerializeMessageKey(messageKey);
const ack = receipt.status - 1;
acks.push({
id: id,
from: fromToParticipant.from,
to: fromToParticipant.to,
participant: toCusFormat(receipt.participant),
fromMe: !receipt.key.fromMe, // reverted, it's right
ack: ack,
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
_data: receipt._node,
});
}
return acks;
}
protected toWAMessage(message: Message): WAMessage {
const replyTo = this.extractReplyTo(message);
const source = this.getMessageSource(message.id.id);
const key = parseMessageIdSerialized(message.id._serialized);
// @ts-ignore
return {
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
participant: toCusFormat(key.participant),
source: source,
to: message.to,
body: message.body,
+1 -1
View File
@@ -1,4 +1,4 @@
export interface IJsonQuery {
filter(field: string, key: string): string;
filter(field: string, key: string, value: any): [string, string];
sortBy(field: string, sortBy: string, direction: string): string;
}
+2 -2
View File
@@ -187,8 +187,8 @@ export class SqlKVRepository<Entity> {
/**
* JSON helpers
*/
public filterJson(field: string, key: string): string {
return this.jsonQuery.filter(field, key);
public filterJson(field: string, key: string, value: any): [string, string] {
return this.jsonQuery.filter(field, key, value);
}
protected stringify(data: any): string {
+2 -2
View File
@@ -1,9 +1,9 @@
import { IJsonQuery } from '@waha/core/storage/sql/IJsonQuery';
export class Sqlite3JsonQuery implements IJsonQuery {
filter(field: string, key: string): string {
filter(field: string, key: string, value: any): [string, string] {
const jsonPath = `$.${key}`;
return `${field}->'${jsonPath}' = ?`;
return [`${field}->'${jsonPath}' = ?`, `${value}`];
}
sortBy(field: string, sortBy: string, direction: string): string {
+72
View File
@@ -0,0 +1,72 @@
import { isJidGroup, WAMessageKey } from '@adiwajshing/baileys';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { toJID } from '@waha/core/utils/jids';
import {
GetChatMessagesFilter,
GetChatMessagesQuery,
ReadChatMessagesQuery,
} from '@waha/structures/chats.dto';
import { SendSeenRequest } from '@waha/structures/chatting.dto';
import { WAMessageAck } from '@waha/structures/enums.dto';
export function ExtractMessageKeysForRead(
request: SendSeenRequest,
): WAMessageKey[] {
const jid = toJID(request.chatId);
const defaults = {
remoteJid: jid,
participant: request.participant,
};
const ids = request.messageIds || [];
if (request.messageId) {
ids.push(request.messageId);
}
const keys: WAMessageKey[] = [];
for (const messageId of ids) {
const parsed = parseMessageIdSerialized(messageId, false);
if (parsed.fromMe) {
continue;
}
const key: WAMessageKey = {
id: parsed.id,
remoteJid: parsed.remoteJid
? toJID(parsed.remoteJid)
: defaults.remoteJid,
participant: parsed.participant
? toJID(parsed.participant)
: defaults.participant,
};
keys.push(key);
}
return keys;
}
function daysToMs(days: number) {
return days * 24 * 60 * 60 * 1000;
}
export function MessagesForRead(
chatId: string,
request: ReadChatMessagesQuery,
): {
query: GetChatMessagesQuery;
filter: GetChatMessagesFilter;
} {
const limit = request.messages || isJidGroup(chatId) ? 100 : 30;
const query: GetChatMessagesQuery = {
offset: 0,
limit: limit,
downloadMedia: false,
};
const afterMs = Date.now() - daysToMs(request.days);
const after = Math.floor(afterMs / 1000);
const filter: GetChatMessagesFilter = {
'filter.ack': WAMessageAck.DEVICE,
'filter.fromMe': false,
'filter.timestamp.gte': after,
};
return {
query: query,
filter: filter,
};
}
+40
View File
@@ -0,0 +1,40 @@
import { WAMessageKey } from '@adiwajshing/baileys';
import { toJID } from '@waha/core/utils/jids';
/**
* Parse message id from WAHA to engine
* false_11111111111@c.us_AAA
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
*/
export function parseMessageIdSerialized(
messageId: string,
soft: boolean = false,
): WAMessageKey {
if (!messageId.includes('_') && soft) {
return { id: messageId };
}
const parts = messageId.split('_');
if (parts.length != 3 && parts.length != 4) {
throw new Error(
'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA[_participant]',
);
}
const fromMe = parts[0] == 'true';
const chatId = parts[1];
const remoteJid = toJID(chatId);
const id = parts[2];
const participant = parts[3] ? toJID(parts[3]) : undefined;
return {
fromMe: fromMe,
id: id,
remoteJid: remoteJid,
participant: participant,
};
}
export function SerializeMessageKey(key: WAMessageKey) {
const { fromMe, id, remoteJid, participant } = key;
const participantStr = participant ? `_${participant}` : '';
return `${fromMe ? 'true' : 'false'}_${remoteJid}_${id}${participantStr}`;
}
+37
View File
@@ -0,0 +1,37 @@
import { isJidGroup, isJidMetaIa } from '@adiwajshing/baileys';
import {
isJidBroadcast,
isLidUser,
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
export function isJidNewsletter(jid: string) {
return jid.endsWith('@newsletter');
}
export function isJidCus(jid: string) {
return jid?.endsWith('@c.us');
}
/**
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
* @param chatId
*/
export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
if (isJidBroadcast(chatId)) {
return chatId;
}
if (isJidNewsletter(chatId)) {
return chatId;
}
if (isLidUser(chatId)) {
return chatId;
}
if (isJidMetaIa(chatId)) {
return chatId;
}
const number = chatId.split('@')[0];
return number + '@s.whatsapp.net';
}
+28
View File
@@ -0,0 +1,28 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
export function IsFileType(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: 'isFileType',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, _args: ValidationArguments) {
return (
value &&
typeof value === 'object' &&
(typeof value.data === 'string' || typeof value.url === 'string')
);
},
defaultMessage() {
return 'must contain either "data" or "url".';
},
},
});
};
}
+1 -1
View File
@@ -195,5 +195,5 @@ export class PreviewChannelMessages {
@IsNumber()
@Type(() => Number)
limit: number = 100;
limit: number = 10;
}
+56 -1
View File
@@ -1,5 +1,7 @@
import { BadRequestException } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { BooleanString } from '@waha/nestjs/validation/BooleanString';
import { WAMessageAck, WAMessageAckName } from '@waha/structures/enums.dto';
import {
LimitOffsetParams,
PaginationParams,
@@ -39,6 +41,30 @@ export class GetChatMessagesFilter {
@IsBoolean()
@IsOptional()
'filter.fromMe'?: boolean;
@ApiProperty({
required: false,
description: 'Filter messages by acknowledgment status',
enum: WAMessageAckName,
})
@IsEnum(WAMessageAckName)
@IsOptional()
'filter.ack'?: WAMessageAck;
}
export function transformAck(
filter: GetChatMessagesFilter,
): GetChatMessagesFilter {
if (!filter) return filter;
if (!filter['filter.ack']) return filter;
const ackName = filter['filter.ack'];
// @ts-ignore
const ack: WAMessageAck = WAMessageAck[ackName];
if (ack == null) {
throw new BadRequestException(`Invalid ack: '${ackName}'`);
}
filter['filter.ack'] = ack;
return filter;
}
export class ChatPictureQuery {
@@ -62,7 +88,7 @@ export class GetChatMessagesQuery {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit: number = 100;
limit: number = 10;
@IsNumber()
@IsOptional()
@@ -80,6 +106,35 @@ export class GetChatMessagesQuery {
downloadMedia: boolean = true;
}
export class ReadChatMessagesQuery {
@ApiProperty({
example: 30,
required: false,
description: 'How much messages to read (latest first)',
})
@Type(() => Number)
@IsNumber()
@IsOptional()
messages: number;
@ApiProperty({
required: false,
description: 'How much days to read (latest first)',
})
@Type(() => Number)
@IsNumber()
@IsOptional()
days: number = 7;
}
export class ReadChatMessagesResponse {
@ApiProperty({
required: false,
description: 'Messages IDs that have been read',
})
ids?: string[];
}
export class GetChatMessageQuery {
@ApiProperty({
example: true,
+89 -5
View File
@@ -4,8 +4,17 @@ import {
ApiProperty,
getSchemaPath,
} from '@nestjs/swagger';
import { IsFileType } from '@waha/nestjs/validation/IsFileType';
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
import { IsNotEmpty, IsString } from 'class-validator';
import { plainToInstance, Transform, Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUrl,
ValidateNested,
} from 'class-validator';
import {
SessionBaseRequest,
@@ -14,6 +23,9 @@ import {
} from './base.dto';
import {
BinaryFile,
FileContent,
FileType,
FileURL,
RemoteFile,
VideoBinaryFile,
VideoRemoteFile,
@@ -70,19 +82,24 @@ export class ChatRequest extends SessionBaseRequest {
export class SendSeenRequest extends ChatRequest {
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
example: null,
deprecated: true,
required: false,
description:
"NOWEB engine only - it's important to mark ALL messages as seen",
})
messageId?: string;
@ApiProperty({
example: ['false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA'],
required: false,
})
messageIds?: string[];
@ApiProperty({
example: '11111111111@c.us',
required: false,
default: null,
description:
'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)',
'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)',
})
participant?: string;
}
@@ -162,6 +179,73 @@ export class MessageTextRequest extends ChatRequest {
linkPreviewHighQuality?: boolean = false;
}
@ApiExtraModels(FileURL, FileContent)
export class LinkPreviewData {
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
url: string = 'https://github.com/';
@IsString()
title: string = 'Your Title';
@IsString()
description: string = 'Check this out, amazing!';
@IsOptional()
@ValidateNested()
@Transform(
({ value }) => {
if (value?.url) {
return plainToInstance(FileURL, value);
}
if (value?.data) {
return plainToInstance(FileContent, value);
}
return value;
},
{ toClassOnly: true },
)
@IsFileType({ message: 'Image must contain either "data" or "url".' })
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(FileURL) },
{ $ref: getSchemaPath(FileContent) },
],
example: {
url:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
},
})
image?: FileType;
}
export class MessageLinkCustomPreviewRequest extends ChatRequest {
@IsString()
@ApiProperty({
description:
'The text to send. MUST include the URL provided in preview.url',
})
text: string = 'Check this out! https://github.com/';
@IsBoolean()
@IsOptional()
linkPreviewHighQuality?: boolean = true;
@ValidateNested()
@Type(() => LinkPreviewData)
preview: LinkPreviewData;
@ReplyToProperty()
@IsString()
@IsOptional()
reply_to?: string;
}
export class EditMessageRequest {
text: string = 'Hello, world!';
+9
View File
@@ -68,4 +68,13 @@ export enum WAMessageAck {
PLAYED = 4,
}
export enum WAMessageAckName {
ERROR = 'ERROR',
PENDING = 'PENDING',
SERVER = 'SERVER',
DEVICE = 'DEVICE',
READ = 'READ',
PLAYED = 'PLAYED',
}
export const ACK_UNKNOWN = 'UNKNOWN';
+31
View File
@@ -1,4 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBase64, IsNotEmpty, IsString, IsUrl } from 'class-validator';
export class Base64File {
mimetype: string;
@@ -117,3 +118,33 @@ export class VideoRemoteFile {
})
url: string;
}
export class FileURL {
@IsString()
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
@ApiProperty({
example:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
})
url?: string;
}
export class FileContent {
@IsString()
@IsNotEmpty()
@IsBase64()
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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 Line truncated
})
data?: string;
}
export type FileType = FileURL | FileContent;
+1 -1
View File
@@ -1,6 +1,6 @@
import { WAHASessionStatus } from '../structures/enums.dto';
const STUCK_IN_STARTING_THRESHOLD = 20;
const STUCK_IN_STARTING_THRESHOLD = 60;
/**
* Keeps track of session statuses (with timestamps) for the last `trackPeriodMs` milliseconds.
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.3.3',
version: '2025.4.2',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.2"
"ref": "v1.0.4"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "0e7e2e7edc715b0c30fff89c10ee2d2fc3402d2d"
"ref": "8f156ca15f460c68bea87535cf6d9f5c51c07f1f"
}
}
}
+306 -358
View File
File diff suppressed because it is too large. Load diff