Compare commits

..
43 Commits
Author SHA1 Message Date
devlikepro 163b11aa36 [core] 2025.11.4
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-12-01 11:46:58 +07:00
devlikepro f4171a9086 [core] Up WEBJS
fix #1678
2025-12-01 11:46:57 +07:00
devlikepro dfbae9c71e [core] GOWS - 128Mb max back in grpc (shouldn't happen tho) 2025-12-01 11:46:57 +07:00
devlikepro 91caa7ba25 [core] nodejs 24.11 2025-12-01 11:46:56 +07:00
devlikepro 2ac57bb6c3 [core] GOWS - send media via file 2025-12-01 11:46:56 +07:00
devlikepro e01647eb48 [core] GOWS - fetch media via file 2025-12-01 11:46:55 +07:00
devlikepro 90bc05ecf3 [core] Use uniqid in webhook for request id 2025-12-01 11:46:54 +07:00
devlikepro 21998cbfae [core] log event 2025-12-01 11:46:53 +07:00
devlikepro 87c49a8766 [core] downgrade google-protobug 2025-12-01 11:46:53 +07:00
devlikepro 4e78d559a8 [core] Disable cpu-prof when WAHA_DEBUG_MODE=true
fix #1681
2025-12-01 11:46:53 +07:00
devlikepro 18ce902c60 [core] Up GOWS to 1.0.24 2025-12-01 11:46:53 +07:00
devlikepro 5784db8055 [core] 2025.11.3
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-11-28 14:02:15 +07:00
devlikepro 6862063654 [core] Up GOWS 2025-11-28 14:02:14 +07:00
devlikepro 4aea282356 [core] ChatWoot - do not log binary 2025-11-28 14:02:13 +07:00
devlikepro 82ff15ae56 [core] GOWS - 10 mins deadline for media fetch 2025-11-28 14:02:12 +07:00
devlikepro 6cbf8cded2 [core] Up Dashboard - app id is uuid now 2025-11-28 14:02:11 +07:00
devlikepro 35e9418988 [core] add cpu profiling for troubleshooting 2025-11-28 14:02:10 +07:00
devlikepro a58be84e56 [core] GOWS - Use one grpc client to handle all sessions (but per session stream) 2025-11-28 14:02:09 +07:00
devlikepro 2994e0a54d [core] ignore tmp 2025-11-28 14:02:09 +07:00
devlikepro f16100e1d9 [core] GOWS - logs for grpc client closing 2025-11-28 14:02:07 +07:00
devlikepro e57be58d48 [core] GOWS - up grpc 2025-11-28 14:02:06 +07:00
devlikepro 7909910dc6 [core] ChatWoot - limit names to 255
fix #1556
2025-11-28 14:02:05 +07:00
devlikepro 7f1778ceb0 [core] ChatWoot - do not create new conversation when closed on message.ack
close devlikeapro/waha-plus#28
fix #1646
2025-11-28 14:02:05 +07:00
devlikepro aba90574d8 [core] ChatWoot - remove unused imports 2025-11-28 14:02:05 +07:00
devlikepro 33c945bf95 [core] NOWEB - Up engine 2025-11-28 14:02:04 +07:00
devlikepro d40e82eb02 [core] WEBJS - up engine 2025-11-28 14:02:04 +07:00
devlikepro 6e82d6f40b [core] GOWS - force to use cgo dns
fix #1649
2025-11-28 14:02:03 +07:00
devlikepro e193eb5696 [core] Up go 1.24
Tho we don't built anything here
2025-11-28 14:02:03 +07:00
devlikepro 8c63c4b097 [core] WEBJS - fix code pairing
fix #1651
2025-11-28 14:02:03 +07:00
devlikepro 4127d609cf [core] for-swagger 2025-11-28 14:02:03 +07:00
devlikepro 57f6db4252 [core] 2025.11.2
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-11-14 10:23:33 +07:00
devlikepro 68a91cb55c [core] up GOWS v1.0.21 2025-11-14 10:23:32 +07:00
devlikepro 869a343e43 [core] API - mentions for send image, file, video 2025-11-14 10:23:32 +07:00
devlikepro fd2b22b339 [core] Up better-sqlite3 to 12.4.1 2025-11-14 10:23:31 +07:00
devlikepro 90d3a5b1ad [core] NOWEB - add pool for sqlite3 2025-11-14 10:23:31 +07:00
devlikepro b4a2ad6b96 [core] WAHA_SQLITE_ENGINE - Add a way to use better-sqlite3 (sync sqlite3 engine) 2025-11-14 10:23:31 +07:00
devlikepro 27ff7e7a21 [core] Reset presence on engine restart (end) 2025-11-14 10:23:30 +07:00
devlikepro 94ae624662 [core] Set to OFFLINE always 2025-11-14 10:23:30 +07:00
devlikepro 52e018aa93 [core] Use timestamps.activity 2025-11-14 10:23:29 +07:00
Berg Pinheiro 8bd13a233e [core] Add automatic presence management and activity tracking
fix #1584
closes #1586

This prevents WhatsApp from disconnecting sessions due to inactivity by
signaling activity through presence updates while avoiding constant online
status that could generate unnecessary notifications.
2025-11-14 10:23:28 +07:00
Emir Muhammadzadeh 87f79c1dc5 [core] NOWEB, GOWS - add location and vcards
closes #1609
2025-11-14 10:23:28 +07:00
devlikepro 31533b5d19 [core] GOWS - move waproto helper to engine 2025-11-14 10:23:27 +07:00
devlikepro 27cc9f4764 [core] Add opustags 2025-11-14 10:23:27 +07:00
55 changed files with 1163 additions and 178 deletions

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
tmp/*
Dockerfile
docker-compose
src/core/engines/gows/proto
+6
View File
@@ -32,6 +32,12 @@ WAHA_BASE_URL=http://localhost:3000
# WHATSAPP_API_HOSTNAME=localhost
# ===================
# ====== GOWS =======
# ===================
# By default, GOWS uses cgo (libc) DNS resolver
# You can ask to use golang one, but it's less reliable
# GODEBUG=netdns=go
# ===================
# ===== LOGGING =====
+1
View File
@@ -1,3 +1,4 @@
tmp/*
src/core/engines/gows/proto
.env
.envs/*
+1 -1
View File
@@ -1 +1 @@
v22.16
v24.11
+22 -2
View File
@@ -1,5 +1,5 @@
ARG NODE_IMAGE_TAG=22.16-bookworm-slim
ARG GOLANG_IMAGE_TAG=1.23-bookworm
ARG NODE_IMAGE_TAG=24.11-bookworm-slim
ARG GOLANG_IMAGE_TAG=1.24-bookworm
#
# Build
@@ -163,6 +163,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="140.0.7339.80-1"
ARG OPUSTAGS_VERSION="1.10.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 \
@@ -176,6 +177,22 @@ RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# Build and install opustags so audio metadata can be cleaned up inside the container
RUN set -eux; \
buildDeps='build-essential cmake pkg-config libogg-dev'; \
apt-get update; \
apt-get install -y --no-install-recommends ${buildDeps}; \
mkdir -p /tmp/opustags; \
curl -L https://github.com/fmang/opustags/archive/refs/tags/${OPUSTAGS_VERSION}.tar.gz \
| tar -xz -C /tmp/opustags; \
cd /tmp/opustags/opustags-${OPUSTAGS_VERSION}; \
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release; \
cmake --build build --config Release; \
cmake --install build; \
rm -rf /tmp/opustags; \
apt-get purge -y --auto-remove ${buildDeps}; \
rm -rf /var/lib/apt/lists/*
# GOWS requirements
# libc6
RUN apt-get update \
@@ -212,6 +229,9 @@ ENV CHOKIDAR_INTERVAL=5000
# WAHA variables
ENV WAHA_ZIPPER=ZIPUNZIP
# GOWS - use libc DNS resolver
ENV GODEBUG netdns=cgo
# Run command, etc
EXPOSE 3000
# Use tini as init system to handle zombie processes properly
+2 -2
View File
@@ -32,10 +32,10 @@ push:
docker push devlikeapro/waha
for-swagger:
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true WHATSAPP_SWAGGER_PASSWORD=666 . ${NVM_DIR}/nvm.sh && nvm exec yarn start
up-noweb:
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-11-07
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-11-21
up-noweb-libsignal:
yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
+5 -4
View File
@@ -26,7 +26,7 @@
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
},
"dependencies": {
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-11-07",
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-11-21",
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-s3": "^3.633.0",
"@aws-sdk/s3-request-presigner": "^3.633.0",
@@ -59,7 +59,7 @@
"audio-decode": "^2.2.2",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
"better-sqlite3": "11.3.0",
"better-sqlite3": "^12.4.1",
"bullmq": "^5.48.1",
"check-disk-space": "^3.4.0",
"chokidar": "^3.6.0",
@@ -106,6 +106,7 @@
"swagger-ui-express": "^4.1.4",
"ulid": "^2.3.0",
"undici": "^7.16.0",
"uniqid": "^5.4.0",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2025-11-05",
"write-file-atomic": "^6.0.0",
"yaml": "^2.7.1"
@@ -121,8 +122,8 @@
"axios": "^1.9.0"
},
"devDependencies": {
"@grpc/grpc-js": "^1.13.4",
"@grpc/proto-loader": "^0.7.13",
"@grpc/grpc-js": "^1.14.1",
"@grpc/proto-loader": "^0.8.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0",
+18 -22
View File
@@ -5,7 +5,6 @@ import {
Post,
Put,
Query,
UnprocessableEntityException,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
@@ -45,20 +44,10 @@ import {
WANumberExistResult,
} from '../structures/chatting.dto';
import { WAMessage } from '../structures/responses.dto';
import { isJidGroup } from '@waha/core/utils/jids';
function validateRequestMentions(request: MessageTextRequest) {
if (!isJidGroup(request.chatId)) {
throw new UnprocessableEntityException(
`"mentions":["all"] can be used only in group chats, not in '${request.chatId}'`,
);
}
if (request.mentions.length > 1) {
throw new UnprocessableEntityException(
`"mentions":["all"] cannot be used with other mentions`,
);
}
}
import {
mentionsAll,
validateRequestMentions,
} from '@waha/core/utils/mentions.all';
@ApiSecurity('api_key')
@Controller('api')
@@ -70,14 +59,9 @@ export class ChattingController {
@ApiOperation({ summary: 'Send a text message' })
async sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (request.mentions && request.mentions.includes('all')) {
if (mentionsAll(request)) {
validateRequestMentions(request);
const participants = await whatsapp.getGroupParticipants(request.chatId);
let mentions = participants.map((p) => p.id);
// Exclude my ids
const me = whatsapp.getSessionMeInfo();
mentions = mentions.filter((id) => id !== me.id && id !== me.lid);
request.mentions = mentions;
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendText(request);
}
@@ -90,6 +74,10 @@ export class ChattingController {
})
async sendImage(@Body() request: MessageImageRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendImage(request);
}
@@ -101,6 +89,10 @@ export class ChattingController {
})
async sendFile(@Body() request: MessageFileRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendFile(request);
}
@@ -123,6 +115,10 @@ export class ChattingController {
})
async sendVideo(@Body() request: MessageVideoRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendVideo(request);
}
+56 -1
View File
@@ -1,4 +1,6 @@
import * as inspector from 'node:inspector';
import * as v8 from 'node:v8';
import { Readable } from 'node:stream';
import {
Controller,
@@ -18,7 +20,10 @@ import {
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { BrowserTraceQuery } from '@waha/structures/server.debug.dto';
import {
BrowserTraceQuery,
CpuProfileQuery,
} from '@waha/structures/server.debug.dto';
import { createReadStream } from 'fs';
@ApiSecurity('api_key')
@@ -36,6 +41,56 @@ export class ServerDebugController {
this.enabled = this.config.debugModeEnabled;
}
@Get('cpu')
@ApiOperation({
summary: 'Collect and return a CPU profile for the current nodejs process',
description: 'Uses the Node.js inspector profiler to capture a .cpuprofile',
})
@UsePipes(new WAHAValidationPipe())
async cpuProfile(@Query() query: CpuProfileQuery) {
if (!this.enabled) {
throw new NotFoundException('WAHA_DEBUG_MODE is disabled');
}
const { seconds } = query;
this.logger.log(`Collecting CPU profile for ${seconds}s...`);
const session = new inspector.Session();
session.connect();
const profile = await new Promise<inspector.Profiler.Profile>(
(resolve, reject) => {
session.post('Profiler.enable', (enableError) => {
if (enableError) {
session.disconnect();
return reject(enableError);
}
session.post('Profiler.start', (startError) => {
if (startError) {
session.disconnect();
return reject(startError);
}
setTimeout(() => {
session.post('Profiler.stop', (stopError, params) => {
session.disconnect();
if (stopError) {
return reject(stopError);
}
resolve(params.profile);
});
}, seconds * 1000);
});
});
},
);
const filename = `CPU.${Date.now()}.${process.pid}.cpuprofile`;
const stream = Readable.from([JSON.stringify(profile)]);
return new StreamableFile(stream, {
type: 'application/json',
disposition: `attachment; filename=${filename}`,
});
}
@Get('heapsnapshot')
@ApiOperation({
summary: 'Return a heapsnapshot for the current nodejs process',
+14 -5
View File
@@ -29,13 +29,22 @@ export class AxiosLogging {
const { method, url } = response.config;
const status = response.status;
const methodStr = method?.toUpperCase() ?? 'GET';
const responseData = JSON.stringify(response.data);
if (status >= 200 && status < 400) {
this.logger.debug(`${methodStr} ${status}:OK ${url}`);
this.logger.trace(`${methodStr} ${status}:OK ${url} ${responseData}`);
const type: any = response.headers['content-type'];
// Log warnings for non-2xx/3xx responses
if (status < 200 || status >= 400) {
const data = JSON.stringify(response.data);
this.logger.warn(`${methodStr} ${status} ${url} ${data}`);
return response;
}
// Log debug for 2xx/3xx responses
this.logger.debug(`${methodStr} ${status}:OK ${url}`);
if (type.startsWith('application/json')) {
const data = JSON.stringify(response.data);
this.logger.trace(`${methodStr} ${status}:OK ${url} ${data}`);
} else {
this.logger.warn(`${methodStr} ${status} ${url} ${responseData}`);
this.logger.trace(`${methodStr} ${status}:OK ${url} [${type}]`);
}
return response;
@@ -10,6 +10,7 @@ import {
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
import {
ContactIds,
ConversationResult,
ConversationService,
} from '@waha/apps/chatwoot/client/ConversationService';
import { ChatWootAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
@@ -43,9 +44,10 @@ export class ContactConversationService {
this.cache = CacheForConfig(config);
}
public async upsertByContactInfo(
private async getByContactInfo(
contactInfo: ContactInfo,
): Promise<ContactIds> {
upsert: boolean,
): Promise<ContactIds | null> {
const chatId = contactInfo.ChatId();
// Check cache for chat id
@@ -77,10 +79,21 @@ export class ContactConversationService {
//
// Get or create a conversation for this inbox
//
const conversation = await this.conversationService.upsert({
const contactIds = {
id: cwContact.data.id,
sourceId: cwContact.sourceId,
});
};
let conversation: ConversationResult;
if (upsert) {
conversation = await this.conversationService.upsert(contactIds);
} else {
conversation = await this.conversationService.find(contactIds);
if (!conversation) {
// Nothing found, do not save it in cache
return null;
}
}
this.logger.debug(
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${cwContact.sourceId}`,
);
@@ -94,11 +107,15 @@ export class ContactConversationService {
return ids;
}
public async ConversationByContact(
private async conversationByContact(
contactInfo: ContactInfo,
): Promise<Conversation> {
upsert: boolean,
): Promise<Conversation | null> {
const chatId = contactInfo.ChatId();
const ids = await this.upsertByContactInfo(contactInfo);
const ids = await this.getByContactInfo(contactInfo, upsert);
if (!ids) {
return null;
}
const conversation = new Conversation(
this.accountAPI,
this.config.accountId,
@@ -118,6 +135,26 @@ export class ContactConversationService {
return conversation;
}
/**
* Find or create if not exists conversation for the contact
*/
public async ConversationByContact(contactInfo: ContactInfo) {
const conversation = await this.conversationByContact(contactInfo, true);
if (!conversation) {
// Shouldn't happen, but for type safety
throw new Error('Conversation could not be created or found');
}
return conversation;
}
/**
* Find suitable conversation for the contact (based on filters)
* If nothing found - it doesn't create a new one
*/
public async FindConversationByContact(contactInfo: ContactInfo) {
return this.conversationByContact(contactInfo, false);
}
public ConversationById(conversationId: number): Conversation {
return new Conversation(
this.accountAPI,
@@ -0,0 +1,23 @@
import { sanitizeName } from '@waha/apps/chatwoot/client/ContactService';
describe('sanitizeName', () => {
it('should return the same name', () => {
const name = 'John Doe';
expect(sanitizeName(name)).toBe(name);
});
it('should return 254 symbols max', () => {
const name = 'a'.repeat(300);
expect(sanitizeName(name)).toBe('a'.repeat(255));
});
it('should remove bidi characters and trim the name', () => {
const name =
'‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪‏‪+123123‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏‬‏';
expect(sanitizeName(name)).toBe('+123123');
});
it('should not remove bidi characters if within limit', () => {
const name = 'محمد +123';
expect(sanitizeName(name)).toBe(name);
});
});
@@ -26,6 +26,20 @@ export enum AvatarUpdateMode {
ALWAYS,
}
export function sanitizeName(name: string) {
// 255 chars max
const limit = 255;
if (!name) {
return name;
}
if (name.length < limit) {
return name;
}
// remove only bidi controls
const clean = name.replace(/[\u200E\u200F\u202A-\u202E\u2066-\u2069]/g, '');
return clean.slice(0, 255).trim();
}
export class ContactService {
constructor(
private config: ChatWootAPIConfig,
@@ -139,6 +153,7 @@ export class ContactService {
chatId: string,
payload: public_contact_create_update_payload,
): Promise<ContactResponse> {
payload.name = sanitizeName(payload.name);
const contact = await this.inboxAPI.contacts.create({
inboxIdentifier: this.config.inboxIdentifier,
data: payload,
@@ -6,7 +6,6 @@ import {
} from '@waha/apps/chatwoot/client/interfaces';
import type { conversation } from '@figuro/chatwoot-sdk/dist/models/conversation';
import { ConversationSelector } from '@waha/apps/chatwoot/services/ConversationSelector';
import axios from 'axios';
import { ChatWootInboxNewAPI } from '@waha/apps/chatwoot/client/ChatWootInboxNewAPI';
export type ConversationResult = Pick<conversation, 'id' | 'account_id'>;
@@ -25,14 +24,20 @@ export class ConversationService {
private logger: ILogger,
) {}
private async find(contact: ContactIds): Promise<ConversationResult | null> {
public async find(contact: ContactIds): Promise<ConversationResult | null> {
const result: { payload: contact_conversations } =
(await this.accountAPI.contacts.listConversations({
accountId: this.config.accountId,
id: contact.id,
})) as any;
const conversations = result.payload;
return this.selector.select(conversations);
const conversation = this.selector.select(conversations);
if (conversation) {
this.logger.debug(
`Found existing conversation.id: ${conversation.id} for contact.id: ${contact.id}, contact.sourceId: ${contact.sourceId}`,
);
}
return conversation;
}
private async create(contact: ContactIds): Promise<ConversationResult> {
@@ -51,9 +56,6 @@ export class ConversationService {
if (!conversation) {
conversation = await this.create(contact);
}
this.logger.debug(
`Using conversation.id: ${conversation.id} for contact.id: ${contact.id}, contact.sourceId: ${contact.sourceId}`,
);
return conversation;
}
@@ -18,7 +18,6 @@ import { WAHAWebhookMessageAck } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ShouldMarkAsReadInChatWoot } from '@waha/apps/chatwoot/consumers/waha/message.ack.utils';
import { toCusFormat } from '@waha/core/utils/jids';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { MessageMappingService } from '@waha/apps/chatwoot/storage';
@@ -95,7 +94,16 @@ class MessageAckHandler {
this.locale,
);
const conversation =
await this.contactConversationService.ConversationByContact(contactInfo);
await this.contactConversationService.FindConversationByContact(
contactInfo,
);
if (!conversation) {
this.logger.debug(
`No suitable conversation found to mark as read for chat.id: ${payload.from}`,
);
return;
}
this.info.onConversationId(conversation.conversationId);
const sourceId = conversation.sourceId;
@@ -77,7 +77,7 @@ export class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
): Promise<ChatWootMessagePartial> {
let converter: MessageToChatWootConverter;
let msg: ChatWootMessagePartial;
const protoMessage = resolveProtoMessage(payload);
const protoMessage = resolveProtoMessage(payload._data);
// Check for Facebook Ad first - but let it use the normal flow later
converter = new FacebookAdMessage(this.l, this.logger);
@@ -65,7 +65,7 @@ class MessageEditedHandler extends MessageBaseHandler<WAMessageEditedBody> {
protected async getMessage(
payload: WAMessageEditedBody,
): Promise<ChatWootMessagePartial> {
const protoMessage = resolveProtoMessage(payload);
const protoMessage = resolveProtoMessage(payload._data);
const converter: MessageToChatWootConverter = new MessageEdited(this.l);
return converter.convert(payload, protoMessage);
}
@@ -1,7 +1,7 @@
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base';
import { isEmptyString } from './utils/proto';
import { isEmptyString } from './utils/text';
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
import { WAMessage } from '@waha/structures/responses.dto';
@@ -2,7 +2,7 @@ import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chatwoot';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/proto';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/text';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import type { proto } from '@adiwajshing/baileys';
import { WAMessage } from '@waha/structures/responses.dto';
@@ -2,7 +2,7 @@ import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chatwoot';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/proto';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/text';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
@@ -1,7 +1,7 @@
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base';
import { isEmptyString } from './utils/proto';
import { isEmptyString } from './utils/text';
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
import { WAMessage } from '@waha/structures/responses.dto';
@@ -103,7 +103,7 @@ describe('PixMessage - pt-BR', () => {
// Test
const logger = JestLogger();
const protoMessage = resolveProtoMessage(payload);
const protoMessage = resolveProtoMessage(payload._data);
const converter = new PixMessage(locale, logger);
const result = converter.convert(payload, protoMessage);
@@ -222,7 +222,7 @@ describe('PixMessage - pt-BR', () => {
// Test
const logger = JestLogger();
const protoMessage = resolveProtoMessage(payload);
const protoMessage = resolveProtoMessage(payload._data);
const converter = new PixMessage(locale, logger);
const result = converter.convert(payload, protoMessage);
@@ -3,7 +3,7 @@ import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { PixTemplatePayload, TKey } from '@waha/apps/chatwoot/i18n/templates';
import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chatwoot';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/proto';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/text';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
@@ -2,7 +2,7 @@ import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { PollCreationMessage, TKey } from '@waha/apps/chatwoot/i18n/templates';
import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chatwoot';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/proto';
import { isEmptyString } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/text';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/messages/to/chatwoot/utils/markdown';
import type { proto } from '@adiwajshing/baileys';
import { WAMessage } from '@waha/structures/responses.dto';
@@ -4,7 +4,7 @@ import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base'
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { WAHASelf } from '@waha/apps/app_sdk/waha/WAHASelf';
import { isEmptyString } from './utils/proto';
import { isEmptyString } from './utils/text';
import type { proto } from '@adiwajshing/baileys';
import { WAMessage } from '@waha/structures/responses.dto';
import { MessageToChatWootConverter } from '@waha/apps/chatwoot/messages/to/chatwoot';
@@ -2,7 +2,6 @@ import { WAMessage } from '@waha/structures/responses.dto';
import type { proto } from '@adiwajshing/baileys';
import { ChatWootMessagePartial } from '@waha/apps/chatwoot/consumers/waha/base';
export { resolveProtoMessage } from './utils/proto';
export { TextMessage } from './TextMessage';
export { LocationMessage } from './LocationMessage';
export { ShareContactMessage } from './ShareContactMessage';
@@ -23,3 +22,4 @@ export interface MessageToChatWootConverter {
protoMessage: proto.Message | null,
): Awaitable<ChatWootMessagePartial | null>;
}
export { resolveProtoMessage } from '@waha/core/engines/gows/waproto';
@@ -0,0 +1,6 @@
export function isEmptyString(content: string) {
if (!content) {
return true;
}
return content === '' || content === '\n';
}
+25
View File
@@ -0,0 +1,25 @@
import type { WhatsappSession } from '@waha/core/abc/session.abc';
/**
* Decorator to mark a method as an activity that
* keeps the WhatsApp session online.
* @constructor
*/
export function Activity() {
return function <T extends (...args: any[]) => Promise<any>>(
target: WhatsappSession,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>,
) {
const original = descriptor.value!;
descriptor.value = async function (
this: WhatsappSession,
...args: Parameters<T>
): Promise<ReturnType<T>> {
await this.maintainPresenceOnline();
return await original.apply(this, args);
} as T;
return descriptor;
};
}
+125 -3
View File
@@ -6,6 +6,7 @@ import { MessagesForRead } from '@waha/core/utils/convertors';
import {
IgnoreJidConfig,
isJidBroadcast,
isJidGroup,
isJidNewsletter,
isNullJid,
JidFilter,
@@ -131,6 +132,11 @@ import { IMediaManager } from '../media/IMediaManager';
import { QR } from '../QR';
import { DataStore } from './DataStore';
import { fetchBuffer } from '@waha/utils/fetch';
import {
PRESENCE_AUTO_ONLINE,
PRESENCE_AUTO_ONLINE_DURATION_SECONDS,
} from '@waha/core/env';
import { Activity } from '@waha/core/abc/activity';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
@@ -175,7 +181,7 @@ export abstract class WhatsappSession {
public name: string;
protected mediaManager: IMediaManager;
public loggerBuilder: LoggerBuilder;
protected logger: Logger;
public logger: Logger;
protected sessionStore: DataStore;
protected proxyConfig?: ProxyConfig;
public sessionConfig?: SessionConfig;
@@ -184,6 +190,16 @@ export abstract class WhatsappSession {
protected jids: JidFilter;
private _status: WAHASessionStatus;
private _presence:
| WAHAPresenceStatus.ONLINE
| WAHAPresenceStatus.OFFLINE
| null = null;
private lastActivityTimestamp?: number;
protected presenceAutoOnlineConfig = {
enabled: PRESENCE_AUTO_ONLINE,
duration: PRESENCE_AUTO_ONLINE_DURATION_SECONDS * 1000,
};
private shouldPrintQR: boolean;
protected events2: DefaultMap<WAHAEvents, SwitchObservable<any>>;
private status$: Subject<WAHASessionStatus>;
@@ -197,6 +213,8 @@ export abstract class WhatsappSession {
stdTTL: 10 * 60, // 10 minutes
});
private presenceOfflineTimeout?: ReturnType<typeof setTimeout>;
public mediaConverter: IMediaConverter = new CoreMediaConverter();
public constructor({
@@ -317,6 +335,30 @@ export abstract class WhatsappSession {
return this._status;
}
protected set presence(value: WAHAPresenceStatus) {
switch (value) {
case null:
this._presence = null;
break;
case WAHAPresenceStatus.ONLINE:
this._presence = WAHAPresenceStatus.ONLINE;
break;
case WAHAPresenceStatus.OFFLINE:
this._presence = WAHAPresenceStatus.OFFLINE;
break;
default:
// Ignore chat relates presence
return;
}
}
public get presence():
| WAHAPresenceStatus.ONLINE
| WAHAPresenceStatus.OFFLINE
| null {
return this._presence;
}
getBrowserExecutablePath() {
return getBrowserExecutablePath();
}
@@ -535,10 +577,79 @@ export abstract class WhatsappSession {
abstract sendSeen(chat: SendSeenRequest);
abstract startTyping(chat: ChatRequest);
abstract startTyping(chat: ChatRequest): Promise<void>;
abstract stopTyping(chat: ChatRequest);
/**
* Activity tracking and presence management
*/
/**
* Returns the timestamp of the last "activity" in the session
* @returns Timestamp in milliseconds or undefined if there was never any activity
*/
public getLastActivityTimestamp(): number | undefined {
return this.lastActivityTimestamp;
}
/**
* Maintains ONLINE presence active while there is activity
* Resets the timer on each activity, only goes OFFLINE after Xs without activity
*/
async maintainPresenceOnline(): Promise<void> {
if (!this.presenceAutoOnlineConfig.enabled) {
return;
}
if (this.status !== WAHASessionStatus.WORKING) {
return;
}
this.lastActivityTimestamp = Date.now();
// If not ONLINE yet, send ONLINE
if (this._presence !== WAHAPresenceStatus.ONLINE) {
try {
// Force set ONLINE in case of many requests comes at the same time
// So we'll set ONLINE exactly once
this.presence = WAHAPresenceStatus.ONLINE;
await this.setPresence(WAHAPresenceStatus.ONLINE);
this.logger.debug('Set presence to ONLINE due to activity');
} catch (error) {
this.logger.debug('Failed to set presence ONLINE', error);
return;
}
}
// Cancel the previous timeout (if exists)
this.cleanupPresenceTimeout();
// Schedule to go back OFFLINE after timeout without activity
this.presenceOfflineTimeout = setTimeout(async () => {
try {
const working = this.status === WAHASessionStatus.WORKING;
const online = this.presence === WAHAPresenceStatus.ONLINE;
if (!working || !online) {
// Nothing to do
return;
}
await this.setPresence(WAHAPresenceStatus.OFFLINE);
this.logger.debug(
'Auto-set presence to OFFLINE after time without activity',
);
} catch (error) {
this.presence = WAHAPresenceStatus.OFFLINE;
this.logger.debug('Failed to set presence OFFLINE', error);
}
this.cleanupPresenceTimeout();
}, this.presenceAutoOnlineConfig.duration);
}
/**
* Cleans up the timeout when the session stops
*/
protected cleanupPresenceTimeout() {
clearTimeout(this.presenceOfflineTimeout);
this.presenceOfflineTimeout = null;
}
abstract setReaction(request: MessageReactionRequest);
setStar(request: MessageStarRequest): Promise<void> {
@@ -923,7 +1034,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public setPresence(presence: WAHAPresenceStatus, chatId?: string) {
public setPresence(
presence: WAHAPresenceStatus,
chatId?: string,
): Promise<void> {
throw new NotImplementedByEngineError();
}
@@ -1085,6 +1199,14 @@ export abstract class WhatsappSession {
public fetch(url: string): Promise<Buffer> {
return fetchBuffer(url);
}
public async resolveMentionsAll(chatId: string): Promise<string[]> {
const participants = await this.getGroupParticipants(chatId);
let mentions = participants.map((p) => p.id);
// Exclude my ids
const me = this.getSessionMeInfo();
return mentions.filter((id) => id !== me.id && id !== me.lid);
}
}
export function getGroupInviteLink(code: string) {
+1 -1
View File
@@ -6,7 +6,7 @@ export interface SValue {
generated: boolean;
}
function rand() {
export function rand() {
return crypto.randomUUID().toString().replace(/-/g, '');
}
@@ -4,6 +4,7 @@ import { EnginePayload } from '@waha/structures/webhooks.dto';
import { sleep } from '@waha/utils/promiseTimeout';
import { Logger } from 'pino';
import { Observable } from 'rxjs';
import { rand } from '@waha/core/auth/config';
/**
* Observable that listens to a gRPC stream and emits EnginePayload objects.
@@ -14,17 +15,35 @@ export class GowsEventStreamObservable extends Observable<EnginePayload> {
CLIENT_CLOSE_TIMEOUT = 1_000;
constructor(
private logger: Logger,
logger: Logger,
factory: () => {
client: grpc.Client;
stream: grpc.ClientReadableStream<messages.EventJson>;
},
) {
super((subscriber) => {
this.logger.debug('Creating grpc client and stream...');
logger.debug('Creating grpc stream...');
logger.setBindings({ id: rand() });
const { client, stream } = factory();
this._client = client;
let closed = false;
const cleanup = async (reason: string) => {
if (closed) {
return;
}
closed = true;
logger.debug({ reason }, 'Cancelling gRPC stream...');
try {
stream.cancel();
} catch (err) {
logger.warn({ err }, 'Failed to cancel gRPC stream');
}
await sleep(this.CLIENT_CLOSE_TIMEOUT);
};
stream.on('data', (raw) => {
const obj = raw.toObject();
obj.data = JSON.parse(obj.data);
@@ -32,18 +51,21 @@ export class GowsEventStreamObservable extends Observable<EnginePayload> {
});
stream.on('end', (...args) => {
this.logger.debug('Stream ended', args);
logger.debug('Stream ended', args);
subscriber?.complete();
subscriber = null;
void cleanup('end');
});
stream.on('error', async (err: any) => {
const CLIENT_CANCELLED_CODE = grpc.status.CANCELLED;
if (err.code === CLIENT_CANCELLED_CODE) {
this.logger.debug('Stream cancelled by client');
logger.debug('Stream cancelled by client');
await cleanup('cancelled');
return;
}
this.logger.error(err, 'Stream error');
logger.error(err, 'Stream error');
await cleanup('error');
// Give some time to node event loop to process the error
await sleep(100);
subscriber?.error(err);
@@ -51,14 +73,7 @@ export class GowsEventStreamObservable extends Observable<EnginePayload> {
});
return async () => {
this.logger.debug('Closing stream client...');
client.close();
await sleep(this.CLIENT_CLOSE_TIMEOUT);
this.logger.debug('Stream client closed');
this.logger.debug('Cancelling stream...');
stream.cancel();
this.logger.debug('Stream cancelled');
await cleanup('teardown');
};
});
}
+35
View File
@@ -0,0 +1,35 @@
import * as grpc from '@grpc/grpc-js';
import { messages } from '@waha/core/engines/gows/grpc/gows';
let messageServiceClient: messages.MessageServiceClient | null = null;
let eventStreamClient: messages.EventStreamClient | null = null;
export const MessageServiceClientSingleton = (
address: string,
credentials: grpc.ChannelCredentials,
): messages.MessageServiceClient => {
if (!messageServiceClient) {
messageServiceClient = new messages.MessageServiceClient(
address,
credentials,
{
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
},
);
}
return messageServiceClient;
};
export const EventStreamClientSingleton = (
address: string,
credentials: grpc.ChannelCredentials,
): messages.EventStreamClient => {
if (!eventStreamClient) {
eventStreamClient = new messages.EventStreamClient(address, credentials, {
'grpc.max_send_message_length': 128 * 1024 * 1024,
'grpc.max_receive_message_length': 128 * 1024 * 1024,
});
}
return eventStreamClient;
};
+69
View File
@@ -2687,6 +2687,7 @@ export namespace messages {
mimetype?: string;
audio?: AudioInfo;
filename?: string;
contentPath?: string;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
@@ -2706,6 +2707,9 @@ export namespace messages {
if ("filename" in data && data.filename != undefined) {
this.filename = data.filename;
}
if ("contentPath" in data && data.contentPath != undefined) {
this.contentPath = data.contentPath;
}
}
}
get content() {
@@ -2741,12 +2745,19 @@ export namespace messages {
set filename(value: string) {
pb_1.Message.setField(this, 5, value);
}
get contentPath() {
return pb_1.Message.getFieldWithDefault(this, 6, "") as string;
}
set contentPath(value: string) {
pb_1.Message.setField(this, 6, value);
}
static fromObject(data: {
content?: Uint8Array;
type?: MediaType;
mimetype?: string;
audio?: ReturnType<typeof AudioInfo.prototype.toObject>;
filename?: string;
contentPath?: string;
}): Media {
const message = new Media({});
if (data.content != null) {
@@ -2764,6 +2775,9 @@ export namespace messages {
if (data.filename != null) {
message.filename = data.filename;
}
if (data.contentPath != null) {
message.contentPath = data.contentPath;
}
return message;
}
toObject() {
@@ -2773,6 +2787,7 @@ export namespace messages {
mimetype?: string;
audio?: ReturnType<typeof AudioInfo.prototype.toObject>;
filename?: string;
contentPath?: string;
} = {};
if (this.content != null) {
data.content = this.content;
@@ -2789,6 +2804,9 @@ export namespace messages {
if (this.filename != null) {
data.filename = this.filename;
}
if (this.contentPath != null) {
data.contentPath = this.contentPath;
}
return data;
}
serialize(): Uint8Array;
@@ -2805,6 +2823,8 @@ export namespace messages {
writer.writeMessage(4, this.audio, () => this.audio.serialize(writer));
if (this.filename.length)
writer.writeString(5, this.filename);
if (this.contentPath.length)
writer.writeString(6, this.contentPath);
if (!w)
return writer.getResultBuffer();
}
@@ -2829,6 +2849,9 @@ export namespace messages {
case 5:
message.filename = reader.readString();
break;
case 6:
message.contentPath = reader.readString();
break;
default: reader.skipField();
}
}
@@ -7949,6 +7972,7 @@ export namespace messages {
message?: string;
jid?: string;
messageId?: string;
contentPath?: string;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
@@ -7965,6 +7989,9 @@ export namespace messages {
if ("messageId" in data && data.messageId != undefined) {
this.messageId = data.messageId;
}
if ("contentPath" in data && data.contentPath != undefined) {
this.contentPath = data.contentPath;
}
}
}
get session() {
@@ -7994,11 +8021,18 @@ export namespace messages {
set messageId(value: string) {
pb_1.Message.setField(this, 4, value);
}
get contentPath() {
return pb_1.Message.getFieldWithDefault(this, 5, "") as string;
}
set contentPath(value: string) {
pb_1.Message.setField(this, 5, value);
}
static fromObject(data: {
session?: ReturnType<typeof Session.prototype.toObject>;
message?: string;
jid?: string;
messageId?: string;
contentPath?: string;
}): DownloadMediaRequest {
const message = new DownloadMediaRequest({});
if (data.session != null) {
@@ -8013,6 +8047,9 @@ export namespace messages {
if (data.messageId != null) {
message.messageId = data.messageId;
}
if (data.contentPath != null) {
message.contentPath = data.contentPath;
}
return message;
}
toObject() {
@@ -8021,6 +8058,7 @@ export namespace messages {
message?: string;
jid?: string;
messageId?: string;
contentPath?: string;
} = {};
if (this.session != null) {
data.session = this.session.toObject();
@@ -8034,6 +8072,9 @@ export namespace messages {
if (this.messageId != null) {
data.messageId = this.messageId;
}
if (this.contentPath != null) {
data.contentPath = this.contentPath;
}
return data;
}
serialize(): Uint8Array;
@@ -8048,6 +8089,8 @@ export namespace messages {
writer.writeString(3, this.jid);
if (this.messageId.length)
writer.writeString(4, this.messageId);
if (this.contentPath.length)
writer.writeString(5, this.contentPath);
if (!w)
return writer.getResultBuffer();
}
@@ -8069,6 +8112,9 @@ export namespace messages {
case 4:
message.messageId = reader.readString();
break;
case 5:
message.contentPath = reader.readString();
break;
default: reader.skipField();
}
}
@@ -8085,6 +8131,7 @@ export namespace messages {
#one_of_decls: number[][] = [];
constructor(data?: any[] | {
content?: Uint8Array;
contentPath?: string;
}) {
super();
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
@@ -8092,6 +8139,9 @@ export namespace messages {
if ("content" in data && data.content != undefined) {
this.content = data.content;
}
if ("contentPath" in data && data.contentPath != undefined) {
this.contentPath = data.contentPath;
}
}
}
get content() {
@@ -8100,22 +8150,36 @@ export namespace messages {
set content(value: Uint8Array) {
pb_1.Message.setField(this, 1, value);
}
get contentPath() {
return pb_1.Message.getFieldWithDefault(this, 5, "") as string;
}
set contentPath(value: string) {
pb_1.Message.setField(this, 5, value);
}
static fromObject(data: {
content?: Uint8Array;
contentPath?: string;
}): DownloadMediaResponse {
const message = new DownloadMediaResponse({});
if (data.content != null) {
message.content = data.content;
}
if (data.contentPath != null) {
message.contentPath = data.contentPath;
}
return message;
}
toObject() {
const data: {
content?: Uint8Array;
contentPath?: string;
} = {};
if (this.content != null) {
data.content = this.content;
}
if (this.contentPath != null) {
data.contentPath = this.contentPath;
}
return data;
}
serialize(): Uint8Array;
@@ -8124,6 +8188,8 @@ export namespace messages {
const writer = w || new pb_1.BinaryWriter();
if (this.content.length)
writer.writeBytes(1, this.content);
if (this.contentPath.length)
writer.writeString(5, this.contentPath);
if (!w)
return writer.getResultBuffer();
}
@@ -8136,6 +8202,9 @@ export namespace messages {
case 1:
message.content = reader.readBytes();
break;
case 5:
message.contentPath = reader.readString();
break;
default: reader.skipField();
}
}
+93 -3
View File
@@ -7115,7 +7115,8 @@ proto.messages.Media.toObject = function(includeInstance, msg) {
type: jspb.Message.getFieldWithDefault(msg, 2, 0),
mimetype: jspb.Message.getFieldWithDefault(msg, 3, ""),
audio: (f = msg.getAudio()) && proto.messages.AudioInfo.toObject(includeInstance, f),
filename: jspb.Message.getFieldWithDefault(msg, 5, "")
filename: jspb.Message.getFieldWithDefault(msg, 5, ""),
contentpath: jspb.Message.getFieldWithDefault(msg, 6, "")
};
if (includeInstance) {
@@ -7173,6 +7174,10 @@ proto.messages.Media.deserializeBinaryFromReader = function(msg, reader) {
var value = /** @type {string} */ (reader.readString());
msg.setFilename(value);
break;
case 6:
var value = /** @type {string} */ (reader.readString());
msg.setContentpath(value);
break;
default:
reader.skipField();
break;
@@ -7238,6 +7243,13 @@ proto.messages.Media.serializeBinaryToWriter = function(message, writer) {
f
);
}
f = message.getContentpath();
if (f.length > 0) {
writer.writeString(
6,
f
);
}
};
@@ -7374,6 +7386,24 @@ proto.messages.Media.prototype.setFilename = function(value) {
};
/**
* optional string contentPath = 6;
* @return {string}
*/
proto.messages.Media.prototype.getContentpath = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 6, ""));
};
/**
* @param {string} value
* @return {!proto.messages.Media} returns this
*/
proto.messages.Media.prototype.setContentpath = function(value) {
return jspb.Message.setProto3StringField(this, 6, value);
};
@@ -16673,7 +16703,8 @@ proto.messages.DownloadMediaRequest.toObject = function(includeInstance, msg) {
session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f),
message: jspb.Message.getFieldWithDefault(msg, 2, ""),
jid: jspb.Message.getFieldWithDefault(msg, 3, ""),
messageid: jspb.Message.getFieldWithDefault(msg, 4, "")
messageid: jspb.Message.getFieldWithDefault(msg, 4, ""),
contentpath: jspb.Message.getFieldWithDefault(msg, 5, "")
};
if (includeInstance) {
@@ -16727,6 +16758,10 @@ proto.messages.DownloadMediaRequest.deserializeBinaryFromReader = function(msg,
var value = /** @type {string} */ (reader.readString());
msg.setMessageid(value);
break;
case 5:
var value = /** @type {string} */ (reader.readString());
msg.setContentpath(value);
break;
default:
reader.skipField();
break;
@@ -16785,6 +16820,13 @@ proto.messages.DownloadMediaRequest.serializeBinaryToWriter = function(message,
f
);
}
f = message.getContentpath();
if (f.length > 0) {
writer.writeString(
5,
f
);
}
};
@@ -16879,6 +16921,24 @@ proto.messages.DownloadMediaRequest.prototype.setMessageid = function(value) {
};
/**
* optional string contentPath = 5;
* @return {string}
*/
proto.messages.DownloadMediaRequest.prototype.getContentpath = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, ""));
};
/**
* @param {string} value
* @return {!proto.messages.DownloadMediaRequest} returns this
*/
proto.messages.DownloadMediaRequest.prototype.setContentpath = function(value) {
return jspb.Message.setProto3StringField(this, 5, value);
};
@@ -16911,7 +16971,8 @@ proto.messages.DownloadMediaResponse.prototype.toObject = function(opt_includeIn
*/
proto.messages.DownloadMediaResponse.toObject = function(includeInstance, msg) {
var f, obj = {
content: msg.getContent_asB64()
content: msg.getContent_asB64(),
contentpath: jspb.Message.getFieldWithDefault(msg, 5, "")
};
if (includeInstance) {
@@ -16952,6 +17013,10 @@ proto.messages.DownloadMediaResponse.deserializeBinaryFromReader = function(msg,
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setContent(value);
break;
case 5:
var value = /** @type {string} */ (reader.readString());
msg.setContentpath(value);
break;
default:
reader.skipField();
break;
@@ -16988,6 +17053,13 @@ proto.messages.DownloadMediaResponse.serializeBinaryToWriter = function(message,
f
);
}
f = message.getContentpath();
if (f.length > 0) {
writer.writeString(
5,
f
);
}
};
@@ -17033,6 +17105,24 @@ proto.messages.DownloadMediaResponse.prototype.setContent = function(value) {
};
/**
* optional string contentPath = 5;
* @return {string}
*/
proto.messages.DownloadMediaResponse.prototype.getContentpath = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, ""));
};
/**
* @param {string} value
* @return {!proto.messages.DownloadMediaResponse} returns this
*/
proto.messages.DownloadMediaResponse.prototype.setContentpath = function(value) {
return jspb.Message.setProto3StringField(this, 5, value);
};
+123 -26
View File
@@ -171,9 +171,20 @@ import {
isLabelChatAddedEvent,
isLabelUpsertEvent,
} from './labels.gows';
import {
EventStreamClientSingleton,
MessageServiceClientSingleton,
} from '@waha/core/engines/gows/clients';
import esm from '@waha/vendor/esm';
import { IsEditedMessage } from '@waha/core/utils/pwa';
import { GoToJSWAProto } from '@waha/core/engines/gows/waproto';
import { extractWALocation } from '@waha/core/engines/waproto/locaiton';
import { extractVCards } from '@waha/core/engines/waproto/vcards';
import { Activity } from '@waha/core/abc/activity';
import { TmpDir } from '@waha/utils/tmpdir';
import * as path from 'path';
import MessageServiceClient = messages.MessageServiceClient;
import * as fsp from 'fs/promises';
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -199,11 +210,6 @@ enum WhatsMeowEvent {
POLL_VOTE_EVENT = 'gows.PollVoteEvent',
}
const gRPCClientConfig = {
'grpc.max_send_message_length': 512 * 1024 * 1024,
'grpc.max_receive_message_length': 512 * 1024 * 1024,
};
export interface GowsConfig {
connection: string;
}
@@ -268,10 +274,9 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}),
});
this.client = new MessageServiceClient(
this.client = MessageServiceClientSingleton(
this.engineConfig.connection,
grpc.credentials.createInsecure(),
gRPCClientConfig,
);
try {
@@ -298,10 +303,9 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.stream$ = new GowsEventStreamObservable(
this.loggerBuilder.child({ grpc: 'stream' }),
() => {
const client = new messages.EventStreamClient(
const client = EventStreamClientSingleton(
this.engineConfig.connection,
grpc.credentials.createInsecure(),
gRPCClientConfig,
);
const stream = client.StreamEvents(this.session);
return { client, stream };
@@ -333,11 +337,15 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
events.on(WhatsMeowEvent.DISCONNECTED, () => {
if (this.status != WAHASessionStatus.STARTING) {
this.cleanupPresenceTimeout();
this.presence = null;
this.status = WAHASessionStatus.STARTING;
}
});
events.on(WhatsMeowEvent.KEEP_ALIVE_TIMEOUT, () => {
if (this.status != WAHASessionStatus.STARTING) {
this.cleanupPresenceTimeout();
this.presence = null;
this.status = WAHASessionStatus.STARTING;
}
});
@@ -666,6 +674,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.events2.get(WAHAEvents.LABEL_CHAT_DELETED).switch(labelChatDeleted$);
}
@Activity()
async fetchContactProfilePicture(id: string): Promise<string> {
const jid = toJID(this.ensureSuffix(id));
const request = new messages.ProfilePictureRequest({
@@ -684,6 +693,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
async stop(): Promise<void> {
this.cleanupPresenceTimeout();
if (this.client) {
const response = await promisify(this.client.StopSession)(this.session);
response.toObject();
@@ -691,7 +701,6 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.status = WAHASessionStatus.STOPPED;
this.events?.stop();
this.stopEvents();
this.client?.close();
this.mediaManager.close();
}
@@ -758,6 +767,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
/**
* Profile methods
*/
@Activity()
public async setProfileName(name: string): Promise<boolean> {
const request = new messages.ProfileNameRequest({
session: this.session,
@@ -768,6 +778,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return true;
}
@Activity()
public async setProfileStatus(status: string): Promise<boolean> {
const request = new messages.ProfileStatusRequest({
session: this.session,
@@ -797,6 +808,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return data.id;
}
@Activity()
async sendText(request: MessageTextRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const message = new messages.MessageRequest({
@@ -813,6 +825,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(jid, data);
}
@Activity()
public async editMessage(
chatId: string,
messageId: string,
@@ -833,6 +846,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(jid, data);
}
@Activity()
async sendContactVCard(request: MessageContactVcardRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const contacts = request.contacts.map((el) => ({ vcard: toVcardV3(el) }));
@@ -847,6 +861,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(jid, data);
}
@Activity()
async sendPoll(request: MessagePollRequest) {
const jid = toJID(request.chatId);
const message = new messages.MessageRequest({
@@ -872,6 +887,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
@Activity()
public async deleteMessage(chatId: string, messageId: string) {
const jid = toJID(this.ensureSuffix(chatId));
const key = parseMessageIdSerialized(messageId);
@@ -893,6 +909,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return contacts.map(toJID);
}
@Activity()
public async sendTextStatus(status: TextStatus) {
const participants = await this.prepareJidsForStatus(status.contacts);
const message = new messages.MessageRequest({
@@ -962,6 +979,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
@Activity()
async sendLocation(request: MessageLocationRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const message = new messages.MessageRequest({
@@ -980,7 +998,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
throw new Error('Method not implemented.');
throw new NotImplementedByEngineError();
}
sendImage(request: MessageImageRequest) {
@@ -1001,10 +1019,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
@Activity()
reply(request: MessageReplyRequest) {
return this.sendText(request);
}
@Activity()
async sendSeen(request: SendSeenRequest) {
const keys = ExtractMessageKeysForRead(request);
if (keys.length === 0) {
@@ -1027,10 +1047,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return;
}
startTyping(chat: ChatRequest) {
return this.setPresence(WAHAPresenceStatus.TYPING, chat.chatId);
@Activity()
async startTyping(chat: ChatRequest): Promise<void> {
await this.setPresence(WAHAPresenceStatus.TYPING, chat.chatId);
}
@Activity()
stopTyping(chat: ChatRequest) {
return this.setPresence(WAHAPresenceStatus.PAUSED, chat.chatId);
}
@@ -1038,6 +1060,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
/**
* Group methods
*/
@Activity()
public async createGroup(request: CreateGroupRequest) {
const req = new messages.CreateGroupRequest({
session: this.session,
@@ -1049,6 +1072,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return data;
}
@Activity()
public async joinInfoGroup(code: string): Promise<any> {
const req = new messages.GroupCodeRequest({
session: this.session,
@@ -1059,6 +1083,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return data;
}
@Activity()
public async joinGroup(code: string): Promise<string> {
const req = new messages.GroupCodeRequest({
session: this.session,
@@ -1089,6 +1114,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
delete group.Participants;
}
@Activity()
public async refreshGroups(): Promise<boolean> {
const req = this.session;
await promisify(this.client.FetchGroups)(req);
@@ -1120,6 +1146,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
@Activity()
public async setInfoAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
@@ -1137,6 +1164,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
@Activity()
public async setMessagesAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
@@ -1151,6 +1179,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new NotImplementedByEngineError();
}
@Activity()
public async leaveGroup(id) {
const req = new messages.JidRequest({
session: this.session,
@@ -1159,6 +1188,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
await promisify(this.client.LeaveGroup)(req);
}
@Activity()
public async setDescription(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
@@ -1168,6 +1198,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
await promisify(this.client.SetGroupDescription)(req);
}
@Activity()
public async setSubject(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
@@ -1177,6 +1208,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
await promisify(this.client.SetGroupName)(req);
}
@Activity()
public async getInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
@@ -1187,6 +1219,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return data.value;
}
@Activity()
public async revokeInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
@@ -1219,26 +1252,31 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return data;
}
@Activity()
public addParticipants(id: string, request: ParticipantsRequest) {
const action = messages.ParticipantAction.ADD;
return this.updateParticipants(id, request.participants, action);
}
@Activity()
public removeParticipants(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.REMOVE;
return this.updateParticipants(id, request.participants, action);
}
@Activity()
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.PROMOTE;
return this.updateParticipants(id, request.participants, action);
}
@Activity()
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.DEMOTE;
return this.updateParticipants(id, request.participants, action);
}
@Activity()
async setReaction(request: MessageReactionRequest) {
const key = parseMessageIdSerialized(request.messageId);
const message = new messages.MessageReaction({
@@ -1253,6 +1291,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(key.remoteJid, data);
}
@Activity()
async sendEvent(request: EventMessageRequest): Promise<WAMessage> {
const jid = toJID(this.ensureSuffix(request.chatId));
const event = request.event;
@@ -1291,6 +1330,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.messageResponse(jid, data) as any;
}
@Activity()
async cancelEvent(eventId: string): Promise<WAMessage> {
throw new Error('Method not implemented.');
@@ -1326,6 +1366,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
method = this.client.SendPresence;
break;
case WAHAPresenceStatus.TYPING:
await this.maintainPresenceOnline();
request = new messages.ChatPresenceRequest({
session: this.session,
jid: jid,
@@ -1334,6 +1375,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
method = this.client.SendChatPresence;
break;
case WAHAPresenceStatus.RECORDING:
await this.maintainPresenceOnline();
request = new messages.ChatPresenceRequest({
session: this.session,
jid: jid,
@@ -1342,6 +1384,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
method = this.client.SendChatPresence;
break;
case WAHAPresenceStatus.PAUSED:
await this.maintainPresenceOnline();
request = new messages.ChatPresenceRequest({
session: this.session,
jid: jid,
@@ -1354,6 +1397,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new Error('Invalid presence status');
}
await promisify(method)(request);
this.presence = presence;
}
public async getPresences(): Promise<WAHAChatPresences[]> {
@@ -1375,6 +1419,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.toWahaPresences(jid, result);
}
@Activity()
async subscribePresence(chatId: string) {
const jid = toJID(chatId);
const req = new messages.SubscribePresenceRequest({
@@ -1496,6 +1541,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return channels;
}
@Activity()
public async channelsCreateChannel(
request: CreateChannelRequest,
): Promise<Channel> {
@@ -1509,10 +1555,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.toChannel(newsletter);
}
@Activity()
public async channelsGetChannel(id: string): Promise<Channel> {
return await this.channelsGetChannelByInviteCode(id);
}
@Activity()
public async channelsGetChannelByInviteCode(
inviteCode: string,
): Promise<Channel> {
@@ -1525,10 +1573,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return this.toChannel(newsletter);
}
@Activity()
public channelsFollowChannel(id: string): Promise<any> {
return this.channelsToggleFollow(id, true);
}
@Activity()
public channelsUnfollowChannel(id: string): Promise<any> {
return this.channelsToggleFollow(id, false);
}
@@ -1545,10 +1595,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return response.toObject();
}
@Activity()
public channelsMuteChannel(id: string): Promise<void> {
return this.channelsToggleMute(id, true);
}
@Activity()
public channelsUnmuteChannel(id: string): Promise<void> {
return this.channelsToggleMute(id, false);
}
@@ -1566,7 +1618,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
/**
* Contacts methods
*/
@Activity()
public async upsertContact(chatId: string, body: ContactUpdateBody) {
const jid = toJID(chatId);
const request = new messages.UpdateContactRequest({
@@ -1814,6 +1866,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return result;
}
@Activity()
public readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
@@ -1849,6 +1902,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async createLabel(labelDto: LabelDTO): Promise<Label> {
const labels = await this.getLabels();
const highestLabelId = lodash.max(
@@ -1874,6 +1928,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
@Activity()
public async updateLabel(label: Label): Promise<Label> {
const request = new messages.UpsertLabelRequest({
session: this.session,
@@ -1887,6 +1942,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return label;
}
@Activity()
public async deleteLabel(label: Label): Promise<void> {
const request = new messages.DeleteLabelRequest({
session: this.session,
@@ -1924,6 +1980,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async chatsUnreadChat(chatId: string): Promise<any> {
const jid = toJID(this.ensureSuffix(chatId));
const request = new messages.ChatUnreadRequest({
@@ -1935,6 +1992,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return { success: true };
}
@Activity()
public async putLabelsToChat(chatId: string, labels: LabelID[]) {
const jid = toJID(chatId);
const labelsIds = labels.map((label) => label.id);
@@ -2041,6 +2099,16 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const mediaContent = extractMediaContent(message.Message);
const source = this.getSourceDeviceByMsg(message);
let waproto: proto.Message | null = null;
try {
waproto = GoToJSWAProto(message.Message);
} catch (e) {
this.logger.error(
'Failed to resolve proto message from GOWS to JS format',
);
this.logger.error(e, e.stack);
}
return {
id: id,
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
@@ -2056,7 +2124,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
mediaUrl: message.media?.url,
// @ts-ignore
ack: ack,
// @ts-ignore
location: extractWALocation(waproto),
vCards: extractVCards(waproto),
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
replyTo: replyTo,
_data: message,
@@ -2299,19 +2368,47 @@ export class GOWSEngineMediaProcessor implements IMediaEngineProcessor<any> {
}
async getMediaBuffer(message: any): Promise<Buffer | null> {
const mediaDownloadTimeoutMs = 600_000; // 10 minutes
const data = JSON.stringify(message.Message);
const request = new messages.DownloadMediaRequest({
// double "session" it's not a mistake here
session: this.session.session,
message: data,
jid: message.Info.Chat,
messageId: message.Info.ID,
});
const response = await promisify(this.session.client.DownloadMedia)(
request,
const tmpdir = new TmpDir(
this.session.logger,
`waha-media-${this.session.name}-`,
);
const obj = response.toObject();
return Buffer.from(obj.content);
return await tmpdir.use(async (dir) => {
const file = path.join(dir, 'content.tmp');
const request = new messages.DownloadMediaRequest({
// double "session" it's not a mistake here
session: this.session.session,
message: data,
jid: message.Info.Chat,
messageId: message.Info.ID,
contentPath: file,
});
const opts = {
deadline: new Date(Date.now() + mediaDownloadTimeoutMs),
};
const call = promisify(
this.session.client.DownloadMedia.bind(this.session.client),
);
try {
const response = await call(request, opts);
const obj = response.toObject();
if (!obj.contentPath) {
// Read directly from grpc response
return Buffer.from(obj.content);
} else {
// Read from the file
return await fsp.readFile(obj.contentPath);
}
} catch (err) {
if (err?.code === grpc.status.DEADLINE_EXCEEDED) {
err.message = `DownloadMedia timed out after ${mediaDownloadTimeoutMs}ms for message '${message?.Info?.ID}'`;
}
throw err;
}
});
}
getFilename(message: any): string | null {
@@ -1,4 +1,4 @@
import { camelCaseKeysDeep } from './proto';
import { camelCaseKeysDeep } from '@waha/core/engines/gows/waproto';
describe('camelCaseKeysDeep', () => {
it('URL to url', () => {
@@ -1,6 +1,5 @@
import { WAMessage } from '@waha/structures/responses.dto';
import type { proto } from '@adiwajshing/baileys';
import * as lodash from 'lodash';
import type { proto } from '@adiwajshing/baileys';
export function camelCaseKeysDeep<T = any>(input: unknown): T {
if (Array.isArray(input)) return input.map(camelCaseKeysDeep) as unknown as T;
@@ -13,26 +12,29 @@ export function camelCaseKeysDeep<T = any>(input: unknown): T {
return input as T;
}
export function resolveProtoMessage(payload: WAMessage): proto.Message | null {
/**
* Converts GoToJS WA Proto to Baileys Proto Message
*/
export function GoToJSWAProto(data: any): proto.Message | null {
if (!data) {
return data;
}
return camelCaseKeysDeep(data) as proto.Message;
}
export function resolveProtoMessage(data: any): proto.Message | null {
// GOWS
if (payload._data.Message) {
const protoMessage = payload._data.Message;
if (data.Message) {
const protoMessage = data.Message;
// mediaURL => mediaUrl
// otherAttributes => otherAttributes
return camelCaseKeysDeep(protoMessage);
}
// NOWEB
if (payload._data.message) {
return payload._data.message;
if (data.message) {
return data.message;
}
// WEBJS - not available
return null;
}
export function isEmptyString(content: string) {
if (!content) {
return true;
}
return content === '' || content === '\n';
}
+75 -12
View File
@@ -1,5 +1,5 @@
import type { WABrowserDescription } from '@adiwajshing/baileys';
import makeWASocket, {
Browsers,
Chat,
Contact,
decryptPollVote,
@@ -10,7 +10,6 @@ import makeWASocket, {
getAggregateVotesInPollMessage,
getContentType,
getKeyAuthor,
isJidGroup,
isPnUser,
isRealMessage,
jidNormalizedUser,
@@ -24,7 +23,6 @@ import makeWASocket, {
WAMessageKey,
WAMessageUpdate,
} from '@adiwajshing/baileys';
import type { WABrowserDescription } from '@adiwajshing/baileys';
import { WACallEvent } from '@adiwajshing/baileys/lib/Types/Call';
import { BaileysEventMap } from '@adiwajshing/baileys/lib/Types/Events';
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
@@ -38,13 +36,9 @@ import {
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message';
import { ILogger } from '@adiwajshing/baileys/lib/Utils/logger';
import {
isJidBroadcast,
isLidUser,
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { UnprocessableEntityException } from '@nestjs/common';
import {
ensureSuffix,
getChannelInviteLink,
getPublicUrlFromDirectPath,
WhatsappSession,
@@ -205,6 +199,9 @@ import {
IsEditedMessage,
IsHistorySyncNotification,
} from '@waha/core/utils/pwa';
import { extractWALocation } from '@waha/core/engines/waproto/locaiton';
import { extractVCards } from '@waha/core/engines/waproto/vcards';
import { Activity } from '@waha/core/abc/activity';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const promiseRetry = require('promise-retry');
@@ -727,6 +724,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
private async end() {
this.cleanupPresenceTimeout();
this.presence = null;
this.autoRestartJob.stop();
// @ts-ignore
this.sock?.ev?.removeAllListeners();
@@ -809,11 +808,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
/**
* Profile methods
*/
@Activity()
public async setProfileName(name: string): Promise<boolean> {
await this.sock.updateProfileName(name);
return true;
}
@Activity()
public async setProfileStatus(status: string): Promise<boolean> {
await this.sock.updateProfileStatus(status);
return true;
@@ -849,6 +850,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.generateMessageID();
}
@Activity()
async sendText(request: MessageTextRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const message = {
@@ -861,6 +863,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.sendMessage(chatId, message, options);
}
@Activity()
public deleteMessage(chatId: string, messageId: string) {
const jid = toJID(this.ensureSuffix(chatId));
const key = parseMessageIdSerialized(messageId);
@@ -870,6 +873,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.sendMessage(jid, { delete: key }, options);
}
@Activity()
public editMessage(
chatId: string,
messageId: string,
@@ -890,6 +894,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.sendMessage(jid, message, options);
}
@Activity()
async sendContactVCard(request: MessageContactVcardRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const contacts = request.contacts.map((el) => ({ vcard: toVcardV3(el) }));
@@ -898,6 +903,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return await this.sock.sendMessage(chatId, msg, options);
}
@Activity()
async sendPoll(request: MessagePollRequest) {
const requestPoll = request.poll;
const poll = {
@@ -914,6 +920,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.toWAMessage(result);
}
@Activity()
async reply(request: MessageReplyRequest) {
const options = await this.getMessageOptions(request);
const message = {
@@ -951,6 +958,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return;
}
@Activity()
async sendButtons(request: SendButtonsRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const headerImage = await this.uploadMedia(request.headerImage, 'image');
@@ -969,6 +977,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
@Activity()
async sendLocation(request: MessageLocationRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const msg = {
@@ -982,6 +991,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return await this.sock.sendMessage(chatId, msg, options);
}
@Activity()
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
const key = parseMessageIdSerialized(request.messageId);
const forwardMessage = await this.store.loadMessage(key.remoteJid, key.id);
@@ -1000,6 +1010,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.toWAMessage(result);
}
@Activity()
async sendLinkPreview(request: MessageLinkPreviewRequest) {
const text = `${request.title}\n${request.url}`;
const chatId = toJID(this.ensureSuffix(request.chatId));
@@ -1008,6 +1019,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.sock.sendMessage(chatId, msg, options);
}
@Activity()
async sendSeen(request: SendSeenRequest) {
const keys = ExtractMessageKeysForRead(request);
if (keys.length === 0) {
@@ -1025,11 +1037,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.sock?.ev.emit('messages.update', updates);
}
async startTyping(request: ChatRequest) {
@Activity()
async startTyping(request: ChatRequest): Promise<void> {
const chatId = toJID(this.ensureSuffix(request.chatId));
return this.sock.sendPresenceUpdate('composing', chatId);
await this.sock.sendPresenceUpdate('composing', chatId);
}
@Activity()
async stopTyping(request: ChatRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
return this.sock.sendPresenceUpdate('paused', chatId);
@@ -1057,6 +1071,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return result;
}
@Activity()
public readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
@@ -1075,6 +1090,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return await this.processIncomingMessage(message, query.downloadMedia);
}
@Activity()
public async pinMessage(
chatId: string,
messageId: string,
@@ -1090,6 +1106,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return true;
}
@Activity()
public async unpinMessage(
chatId: string,
messageId: string,
@@ -1103,6 +1120,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return true;
}
@Activity()
async setReaction(request: MessageReactionRequest) {
const key = parseMessageIdSerialized(request.messageId);
if (isJidNewsletter(key.remoteJid)) {
@@ -1135,6 +1153,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
}
@Activity()
async setStar(request: MessageStarRequest) {
const key = parseMessageIdSerialized(request.messageId);
await this.sock.chatModify(
@@ -1208,6 +1227,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
};
}
@Activity()
protected async chatsPutArchive(
chatId: string,
archive: boolean,
@@ -1220,14 +1240,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
);
}
@Activity()
public chatsArchiveChat(chatId: string): Promise<any> {
return this.chatsPutArchive(chatId, true);
}
@Activity()
public chatsUnarchiveChat(chatId: string): Promise<any> {
return this.chatsPutArchive(chatId, false);
}
@Activity()
public async chatsUnreadChat(chatId: string): Promise<any> {
const jid = toJID(chatId);
const messages = await this.store.getMessagesByJid(jid, {}, { limit: 1 });
@@ -1246,6 +1269,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async createLabel(label: LabelDTO): Promise<Label> {
const labels = await this.store.getLabels();
const highestLabelId = lodash.max(
@@ -1269,6 +1293,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
};
}
@Activity()
public async updateLabel(label: Label): Promise<Label> {
const labelAction: LabelActionBody = {
id: label.id,
@@ -1281,6 +1306,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return label;
}
@Activity()
public async deleteLabel(label: Label): Promise<void> {
const labelAction: LabelActionBody = {
id: label.id,
@@ -1305,6 +1331,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async putLabelsToChat(chatId: string, labels: LabelID[]) {
const jid = toJID(chatId);
const labelsIds = labels.map((label) => label.id);
@@ -1346,6 +1373,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
* Contacts methods
*/
@Activity()
public async upsertContact(chatId: string, body: ContactUpdateBody) {
const jid = toJID(chatId);
let fullName = body.firstName;
@@ -1381,6 +1409,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return contacts.map(this.toWAContact);
}
@Activity()
public async fetchContactProfilePicture(id: string) {
const contact = this.ensureSuffix(id);
try {
@@ -1446,15 +1475,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
/**
* Group methods
*/
@Activity()
public createGroup(request: CreateGroupRequest) {
const participants = request.participants.map(getId);
return this.sock.groupCreate(request.name, participants);
}
@Activity()
public joinGroup(code: string) {
return this.sock.groupAcceptInvite(code);
}
@Activity()
public joinInfoGroup(code: string) {
return this.sock.groupGetInviteInfo(code);
}
@@ -1469,6 +1501,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
delete group.participants;
}
@Activity()
public async refreshGroups(): Promise<boolean> {
this.store.resetGroupsCache();
await this.store.getGroups({});
@@ -1501,6 +1534,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return { adminsOnly: group.restrict };
}
@Activity()
public async setInfoAdminsOnly(id, value) {
const setting = value ? 'locked' : 'unlocked';
return await this.sock.groupSettingUpdate(id, setting);
@@ -1511,27 +1545,33 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return { adminsOnly: group.announce };
}
@Activity()
public async setMessagesAdminsOnly(id, value) {
const setting = value ? 'announcement' : 'not_announcement';
return await this.sock.groupSettingUpdate(id, setting);
}
@Activity()
public async leaveGroup(id) {
return this.sock.groupLeave(id);
}
@Activity()
public async setDescription(id, description) {
return this.sock.groupUpdateDescription(id, description);
}
@Activity()
public async setSubject(id, subject) {
return this.sock.groupUpdateSubject(id, subject);
}
@Activity()
public async getInviteCode(id): Promise<string> {
return this.sock.groupInviteCode(id);
}
@Activity()
public async revokeInviteCode(id): Promise<string> {
await this.sock.groupRevokeInvite(id);
return this.sock.groupInviteCode(id);
@@ -1542,27 +1582,37 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return groups[id].participants;
}
@Activity()
public async addParticipants(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'add');
}
@Activity()
public async removeParticipants(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'remove');
}
@Activity()
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'promote');
}
@Activity()
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const participants = request.participants.map(getId);
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
}
public async setPresence(presence: WAHAPresenceStatus, chatId?: string) {
switch (presence) {
case WAHAPresenceStatus.TYPING:
case WAHAPresenceStatus.RECORDING:
case WAHAPresenceStatus.PAUSED:
await this.maintainPresenceOnline();
}
const enginePresence = ToEnginePresenceStatus[presence];
if (!enginePresence) {
throw new NotImplementedByEngineError(
@@ -1573,6 +1623,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
chatId = toJID(this.ensureSuffix(chatId));
}
await this.sock.sendPresenceUpdate(enginePresence, chatId);
this.presence = presence;
}
public async getPresences(): Promise<WAHAChatPresences[]> {
@@ -1595,6 +1646,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.toWahaPresences(jid, result);
}
@Activity()
public subscribePresence(id: string): Promise<void> {
const jid = toJID(id);
return this.sock.presenceSubscribe(jid);
@@ -1603,6 +1655,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
/**
* Status methods
*/
@Activity()
public async sendStatusMessage(
message: any,
options: any,
@@ -1668,6 +1721,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
}
@Activity()
public async sendTextStatus(status: TextStatus) {
const message = {
text: status.text,
@@ -1716,6 +1770,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return jids.filter((jid) => jid.endsWith('@s.whatsapp.net'));
}
@Activity()
public async deleteStatus(request: DeleteStatusRequest) {
const messageId = request.id;
const key = parseMessageIdSerialized(messageId, true);
@@ -1806,6 +1861,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return channels;
}
@Activity()
public async channelsCreateChannel(request: CreateChannelRequest) {
const newsletter = await this.sock.newsletterCreate(
request.name,
@@ -1819,27 +1875,33 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.toChannel(toNewsletterMetadata(newsletter));
}
@Activity()
public async channelsGetChannelByInviteCode(inviteCode: string) {
const newsletter = await this.sock.newsletterMetadata('invite', inviteCode);
return this.toChannel(toNewsletterMetadata(newsletter));
}
@Activity()
public async channelsDeleteChannel(id: string) {
return await this.sock.newsletterDelete(id);
}
@Activity()
public async channelsFollowChannel(id: string): Promise<any> {
return await this.sock.newsletterFollow(id);
}
@Activity()
public async channelsUnfollowChannel(id: string): Promise<any> {
return await this.sock.newsletterUnfollow(id);
}
@Activity()
public async channelsMuteChannel(id: string): Promise<any> {
return await this.sock.newsletterMute(id);
}
@Activity()
public async channelsUnmuteChannel(id: string): Promise<any> {
return await this.sock.newsletterUnmute(id);
}
@@ -2280,6 +2342,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const ack = message.ack || StatusToAck(message.status);
const mediaContent = extractMediaContent(message.message);
const source = this.getMessageSource(message.key.id);
const waproto = message.message;
return {
id: id,
timestamp: ensureNumber(message.messageTimestamp),
@@ -2297,8 +2360,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
ack: ack,
// @ts-ignore
ackName: WAMessageAck[ack] || ACK_UNKNOWN,
location: message.location,
vCards: message.vCards,
location: extractWALocation(waproto),
vCards: extractVCards(waproto),
replyTo: replyTo,
_data: message,
};
@@ -14,6 +14,7 @@ import { Sqlite3ChatRepository } from './Sqlite3ChatRepository';
import { Sqlite3ContactRepository } from './Sqlite3ContactRepository';
import { Sqlite3MessagesRepository } from './Sqlite3MessagesRepository';
import { Sqlite3SchemaValidation } from './Sqlite3SchemaValidation';
import { KNEX_SQLITE_CLIENT } from '@waha/core/env';
export class Sqlite3Storage extends INowebStorage {
private readonly tables: Schema[];
@@ -22,9 +23,16 @@ export class Sqlite3Storage extends INowebStorage {
constructor(filePath: string) {
super();
this.knex = Knex({
client: 'sqlite3',
client: KNEX_SQLITE_CLIENT,
connection: { filename: filePath },
useNullAsDefault: true,
pool: {
min: 1,
max: 10,
idleTimeoutMillis: 60_000,
createTimeoutMillis: 120_000,
acquireTimeoutMillis: 120_000,
},
});
this.tables = NOWEB_STORE_SCHEMA;
}
+34
View File
@@ -0,0 +1,34 @@
import type { proto } from '@adiwajshing/baileys';
import { WALocation } from '@waha/structures/responses.dto';
import * as lodash from 'lodash';
import { ensureBase64 } from '@waha/utils/bytes';
export function extractWALocation(waproto: proto.Message): WALocation | null {
if (!waproto) {
return null;
}
if (!lodash.isEmpty(waproto.locationMessage)) {
const location = waproto.locationMessage;
return {
live: false,
latitude: location.degreesLatitude?.toString(),
longitude: location.degreesLongitude?.toString(),
name: location.name,
address: location.address,
url: location.url,
description: location.comment,
thumbnail: ensureBase64(location.jpegThumbnail),
};
}
if (!lodash.isEmpty(waproto.liveLocationMessage)) {
const location = waproto.liveLocationMessage;
return {
live: true,
latitude: location.degreesLatitude?.toString(),
longitude: location.degreesLongitude?.toString(),
description: location.caption,
thumbnail: ensureBase64(location.jpegThumbnail),
};
}
return null;
}
+18
View File
@@ -0,0 +1,18 @@
import type { proto } from '@adiwajshing/baileys';
import * as lodash from 'lodash';
export function extractVCards(waproto: proto.Message): string[] | null {
if (!waproto) {
return null;
}
if (!lodash.isEmpty(waproto.contactMessage)) {
return [waproto.contactMessage.vcard];
}
if (!lodash.isEmpty(waproto.contactsArrayMessage)) {
return waproto.contactsArrayMessage.contacts.map(
(contact) => contact.vcard,
);
}
return null;
}
+85 -4
View File
@@ -110,7 +110,11 @@ import {
WAHAChatPresences,
WAHAPresenceData,
} from '@waha/structures/presence.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import {
WALocation,
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { BrowserTraceQuery } from '@waha/structures/server.debug.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
@@ -155,6 +159,7 @@ import {
normalizeJid,
toCusFormat,
} from '@waha/core/utils/jids';
import { Activity } from '@waha/core/abc/activity';
export interface WebJSConfig {
webVersion?: string;
@@ -366,6 +371,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
async stop() {
this.cleanupPresenceTimeout();
this.shouldRestart = false;
this.status = WAHASessionStatus.STOPPED;
this.stopEvents();
@@ -389,6 +395,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
private async end() {
this.cleanupPresenceTimeout();
this.presence = null;
this.engineStateCheckDelayedJob.cancel();
this.whatsapp?.removeAllListeners();
this.whatsapp?.pupBrowser?.removeAllListeners();
@@ -620,11 +628,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
/**
* Profile methods
*/
@Activity()
public async setProfileName(name: string): Promise<boolean> {
await this.whatsapp.setPushName(name);
return true;
}
@Activity()
public async setProfileStatus(status: string): Promise<boolean> {
await this.whatsapp.setStatus(status);
return true;
@@ -641,6 +651,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
/**
* Other methods
*/
@Activity()
sendText(request: MessageTextRequest) {
const options = this.getMessageOptions(request);
return this.whatsapp.sendMessage(
@@ -650,11 +661,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
);
}
@Activity()
public deleteMessage(chatId: string, messageId: string) {
const message = this.recreateMessage(messageId);
return message.delete(true);
}
@Activity()
public editMessage(
chatId: string,
messageId: string,
@@ -669,6 +682,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.edit(request.text, options);
}
@Activity()
async sendContactVCard(request: MessageContactVcardRequest) {
const chatId = this.ensureSuffix(request.chatId);
const vcards = request.contacts.map((el) => toVcardV3(el as any));
@@ -691,7 +705,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.sendMessage(chatId, '', { ...options, extra });
}
reply(request: MessageReplyRequest) {
@Activity()
async reply(request: MessageReplyRequest) {
const options = this.getMessageOptions(request);
return this.whatsapp.sendMessage(
this.ensureSuffix(request.chatId),
@@ -716,6 +731,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
@Activity()
async sendLocation(request: MessageLocationRequest) {
const location = new Location(request.latitude, request.longitude, {
name: request.title,
@@ -728,6 +744,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
);
}
@Activity()
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
const forwardMessage = this.recreateMessage(request.messageId);
const msg = await forwardMessage.forward(this.ensureSuffix(request.chatId));
@@ -737,6 +754,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return { sent: msg || false };
}
@Activity()
async sendSeen(request: SendSeenRequest) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(request.chatId),
@@ -744,13 +762,15 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await chat.sendSeen();
}
async startTyping(request: ChatRequest) {
@Activity()
async startTyping(request: ChatRequest): Promise<void> {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(request.chatId),
);
await chat.sendStateTyping();
}
@Activity()
async stopTyping(request: ChatRequest) {
const chat: Chat = await this.whatsapp.getChatById(
this.ensureSuffix(request.chatId),
@@ -758,6 +778,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await chat.clearState();
}
@Activity()
async setReaction(request: MessageReactionRequest) {
const message = this.recreateMessage(request.messageId);
return message.react(request.reaction);
@@ -774,6 +795,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return new MessageInstance(this.whatsapp, data);
}
@Activity()
async setStar(request: MessageStarRequest) {
const message = this.recreateMessage(request.messageId);
if (request.star) {
@@ -863,6 +885,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return result;
}
@Activity()
public async readChatMessages(
chatId: string,
request: ReadChatMessagesQuery,
@@ -897,6 +920,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return await this.processIncomingMessage(message, query.downloadMedia);
}
@Activity()
public async pinMessage(
chatId: string,
messageId: string,
@@ -906,6 +930,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.pin(duration);
}
@Activity()
public async unpinMessage(
chatId: string,
messageId: string,
@@ -914,26 +939,31 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message.unpin();
}
@Activity()
async deleteChat(chatId) {
const chat = await this.whatsapp.getChatById(this.ensureSuffix(chatId));
return chat.delete();
}
@Activity()
async clearMessages(chatId) {
const chat = await this.whatsapp.getChatById(chatId);
return chat.clearMessages();
}
@Activity()
public chatsArchiveChat(chatId: string): Promise<any> {
const id = this.ensureSuffix(chatId);
return this.whatsapp.archiveChat(id);
}
@Activity()
public chatsUnarchiveChat(chatId: string): Promise<any> {
const id = this.ensureSuffix(chatId);
return this.whatsapp.unarchiveChat(id);
}
@Activity()
public chatsUnreadChat(chatId: string): Promise<any> {
const id = this.ensureSuffix(chatId);
return this.whatsapp.markChatUnread(id);
@@ -949,6 +979,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async createLabel(label: LabelDTO): Promise<Label> {
const labelId = await this.whatsapp.createLabel(label.name, label.color);
return {
@@ -959,14 +990,17 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
@Activity()
public async updateLabel(label: Label): Promise<Label> {
return await this.whatsapp.updateLabel(label);
}
@Activity()
public deleteLabel(label: Label): Promise<void> {
return this.whatsapp.deleteLabel(label);
}
@Activity()
public getChatsByLabelId(labelId: string) {
return this.whatsapp.getChatsByLabelId(labelId);
}
@@ -977,6 +1011,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return labels.map(this.toLabel);
}
@Activity()
public async putLabelsToChat(chatId: string, labels: LabelID[]) {
const labelIds = labels.map((label) => label.id);
const chatIds = [this.ensureSuffix(chatId)];
@@ -996,6 +1031,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
/**
* Contacts methods
*/
@Activity()
public async upsertContact(chatId: string, body: ContactUpdateBody) {
const phoneNumber = chatId.split('@')[0];
await this.whatsapp.saveOrEditAddressbookContact(
@@ -1026,12 +1062,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return { about: await contact.getAbout() };
}
@Activity()
public async fetchContactProfilePicture(id: string) {
const contact = await this.whatsapp.getContactById(this.ensureSuffix(id));
const url = await contact.getProfilePicUrl();
return url;
}
@Activity()
public async blockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(request.contactId),
@@ -1039,6 +1077,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await contact.block();
}
@Activity()
public async unblockContact(request: ContactRequest) {
const contact = await this.whatsapp.getContactById(
this.ensureSuffix(request.contactId),
@@ -1081,6 +1120,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
/**
* Group methods
*/
@Activity()
public createGroup(request: CreateGroupRequest) {
const participantIds = request.participants.map(
(participant) => participant.id,
@@ -1088,10 +1128,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return this.whatsapp.createGroup(request.name, participantIds);
}
@Activity()
public joinGroup(code: string) {
return this.whatsapp.acceptInvite(code);
}
@Activity()
public joinInfoGroup(code: string) {
return this.whatsapp.getInviteInfo(code);
}
@@ -1105,6 +1147,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
@Activity()
public async setInfoAdminsOnly(id, value) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setInfoAdminsOnly(value);
@@ -1120,6 +1163,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
@Activity()
public async setMessagesAdminsOnly(id, value) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setMessagesAdminsOnly(value);
@@ -1162,31 +1206,37 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return getParticipants(group.participants);
}
@Activity()
public async deleteGroup(id) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.delete();
}
@Activity()
public async leaveGroup(id) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.leave();
}
@Activity()
public async setDescription(id, description) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setDescription(description);
}
@Activity()
public async setSubject(id, subject) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.setSubject(subject);
}
@Activity()
public async getInviteCode(id): Promise<string> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.getInviteCode();
}
@Activity()
public async revokeInviteCode(id): Promise<string> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
await groupChat.revokeInvite();
@@ -1198,6 +1248,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.participants;
}
@Activity()
public async addParticipants(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
@@ -1206,6 +1257,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.addParticipants(participantIds);
}
@Activity()
public async removeParticipants(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
@@ -1214,6 +1266,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.removeParticipants(participantIds);
}
@Activity()
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
@@ -1222,6 +1275,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.promoteParticipants(participantIds);
}
@Activity()
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
const participantIds = request.participants.map(
@@ -1292,6 +1346,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
@Activity()
public async channelsList(query: ListChannelsQuery): Promise<Channel[]> {
const data = await this.whatsapp.getChannels();
let channels = data.map(this.ChatToChannel);
@@ -1326,6 +1381,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return await this.channelsGetChannelByInviteCode(id);
}
@Activity()
public async channelsGetChannelByInviteCode(
inviteCode: string,
): Promise<Channel> {
@@ -1370,14 +1426,17 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
await this.whatsapp.sendPresenceUnavailable();
break;
case WAHAPresenceStatus.TYPING:
await this.maintainPresenceOnline();
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateTyping();
break;
case WAHAPresenceStatus.RECORDING:
await this.maintainPresenceOnline();
chat = await this.whatsapp.getChatById(chatId);
await chat.sendStateRecording();
break;
case WAHAPresenceStatus.PAUSED:
await this.maintainPresenceOnline();
chat = await this.whatsapp.getChatById(chatId);
await chat.clearState();
break;
@@ -1386,18 +1445,21 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
`WEBJS engine doesn't support '${presence}' presence.`,
);
}
this.presence = presence;
}
public getPresences(): Promise<WAHAChatPresences[]> {
throw new NotImplementedByEngineError();
}
@Activity()
public async getPresence(id: string): Promise<WAHAChatPresences> {
const chatId = toCusFormat(id);
const presences = await this.whatsapp.getPresence(chatId);
return this.toWahaPresences(chatId, presences);
}
@Activity()
public async subscribePresence(id: string): Promise<any> {
const chatId = toCusFormat(id);
await this.whatsapp.subscribePresence(chatId);
@@ -1445,6 +1507,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
}
@Activity()
public sendTextStatus(status: TextStatus) {
this.checkStatusRequest(status);
return this.whatsapp.sendTextStatus(status);
@@ -1802,7 +1865,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
// @ts-ignore
ack: message.ack,
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
location: message.location,
location: this.extractLocation(message),
vCards: message.vCards,
replyTo: replyTo,
_data: message.rawData,
@@ -1823,6 +1886,24 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
};
}
protected extractLocation(message: Message): WALocation | null {
const location = message.location;
if (lodash.isEmpty(location)) {
return null;
}
const rawData: any = message.rawData;
return {
live: Boolean(rawData.isLive),
latitude: location.latitude,
longitude: location.longitude,
name: location.name,
address: location.address,
description: rawData.comment || location.description,
url: location.url,
thumbnail: message.body,
};
}
public async getEngineInfo() {
if (!this.whatsapp || !this.whatsapp.pupPage) {
return null;
+24
View File
@@ -0,0 +1,24 @@
import { parseBool } from '@waha/helpers';
//
// Presence
//
// Automatically mark session as ONLINE on any messages activity
export const PRESENCE_AUTO_ONLINE = process.env.WAHA_PRESENCE_AUTO_ONLINE
? parseBool(process.env.WAHA_PRESENCE_AUTO_ONLINE)
: true;
// Duration (in seconds) to keep session ONLINE after activity
// 25 seconds is default web timeout with no activity
export const PRESENCE_AUTO_ONLINE_DURATION_SECONDS =
parseInt(process.env.WAHA_PRESENCE_AUTO_ONLINE_DURATION_SECONDS) || 25;
//
// Local - sqlite3 engine
//
let KNEX_SQLITE_CLIENT = process.env.WAHA_SQLITE_ENGINE;
if (KNEX_SQLITE_CLIENT != 'sqlite3' && KNEX_SQLITE_CLIENT != 'better-sqlite3') {
KNEX_SQLITE_CLIENT = 'sqlite3';
}
export { KNEX_SQLITE_CLIENT };
@@ -9,8 +9,9 @@ import axios, { AxiosInstance } from 'axios';
import axiosRetry, { retryAfter } from 'axios-retry';
import * as crypto from 'crypto';
import { Logger } from 'pino';
import { ulid } from 'ulid';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const uniqid = require('uniqid');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const HttpAgent = require('agentkeepalive');
// eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -71,6 +72,7 @@ export class WebhookSender {
const ctx = {
id: headers['X-Webhook-Request-Id'],
['event.id']: json.id,
event: json.event,
url: this.url,
};
this.logger.info(ctx, `Sending POST...`);
@@ -133,7 +135,9 @@ export class WebhookSender {
retryCondition: (error) => true,
onRetry: (retryCount, error, requestConfig) => {
this.logger.warn(
{ id: requestConfig.headers['X-Webhook-Request-Id'] },
{
id: requestConfig.headers['X-Webhook-Request-Id'],
},
`Error sending POST request: '${error.message}'. Retrying ${retryCount}/${attempts}...`,
);
},
@@ -157,7 +161,7 @@ export class WebhookSender {
const timestamp = json.timestamp?.toString() || Date.now().toString();
return {
// UUID, no '-' in it
'X-Webhook-Request-Id': ulid(),
'X-Webhook-Request-Id': uniqid(),
// unix timestamp with ms
'X-Webhook-Timestamp': timestamp,
};
+12 -1
View File
@@ -349,6 +349,10 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
status: WAHASessionStatus.STOPPED,
config: this.sessionConfig,
me: null,
presence: null,
timestamps: {
activity: null,
},
},
];
}
@@ -367,6 +371,10 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
status: session.status,
config: session.sessionConfig,
me: me,
presence: session.presence,
timestamps: {
activity: session?.getLastActivityTimestamp(),
},
},
];
}
@@ -400,7 +408,10 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
}
const session = sessions[0];
const engine = await this.fetchEngineInfo();
return { ...session, engine: engine };
return {
...session,
engine: engine,
};
}
protected stopEvents() {
+2 -1
View File
@@ -4,6 +4,7 @@ import Knex from 'knex';
import * as path from 'path';
import { LocalStore } from './LocalStore';
import { KNEX_SQLITE_CLIENT } from '@waha/core/env';
export class LocalStoreCore extends LocalStore {
protected readonly baseDirectory: string =
@@ -69,7 +70,7 @@ export class LocalStoreCore extends LocalStore {
const engineDir = this.getEngineDirectory();
const database = path.join(engineDir, 'waha.sqlite3');
return Knex({
client: 'sqlite3',
client: KNEX_SQLITE_CLIENT,
connection: { filename: database },
useNullAsDefault: true,
acquireConnectionTimeout: 120_000,
+24
View File
@@ -0,0 +1,24 @@
import { isJidGroup } from '@waha/core/utils/jids';
import { UnprocessableEntityException } from '@nestjs/common';
const ALL = 'all';
export function mentionsAll(request: { mentions?: string[] }) {
return request.mentions && request.mentions.includes(ALL);
}
export function validateRequestMentions(request: {
chatId: string;
mentions?: string[];
}) {
if (!isJidGroup(request.chatId)) {
throw new UnprocessableEntityException(
`"mentions":["all"] can be used only in group chats, not in '${request.chatId}'`,
);
}
if (request.mentions.length > 1) {
throw new UnprocessableEntityException(
`"mentions":["all"] cannot be used with other mentions`,
);
}
}
+9
View File
@@ -303,6 +303,9 @@ class FileRequest extends ChatRequest {
export class MessageImageRequest extends FileRequest {
caption?: string;
@ApiHideProperty()
mentions?: string[];
@ReplyToProperty()
reply_to?: string;
}
@@ -310,6 +313,9 @@ export class MessageImageRequest extends FileRequest {
export class MessageFileRequest extends FileRequest {
caption?: string;
@ApiHideProperty()
mentions?: string[];
@ReplyToProperty()
reply_to?: string;
}
@@ -343,6 +349,9 @@ export class MessageVideoRequest extends ChatRequest {
caption?: string = 'Just watch at this!';
@ApiHideProperty()
mentions?: string[];
@ApiProperty({
description:
'The ID of the message to reply to - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
+6 -1
View File
@@ -6,9 +6,14 @@ import { WAMessageAck } from './enums.dto';
import { ChatIdProperty, MessageIdProperty } from './properties.dto';
export class WALocation {
description?: string;
latitude: string;
longitude: string;
live: boolean;
name?: string;
address?: string;
url?: string;
description?: string; // "comment" in proto
thumbnail?: string;
}
export enum MessageSource {
+14 -1
View File
@@ -1,6 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator';
import { IsArray, IsNumber, IsOptional, IsString, Min } from 'class-validator';
export class CpuProfileQuery {
@Type(() => Number)
@IsNumber()
@Min(1)
@ApiProperty({
description: 'How many seconds to sample CPU',
example: 30,
required: false,
default: 30,
})
seconds: number = 30;
}
export class BrowserTraceQuery {
@Type(() => Number)
+6 -1
View File
@@ -12,7 +12,7 @@ import {
ValidateNested,
} from 'class-validator';
import { WAHASessionStatus } from './enums.dto';
import { WAHAPresenceStatus, WAHASessionStatus } from './enums.dto';
import { ChatIdProperty } from './properties.dto';
import { WebhookConfig } from './webhooks.config.dto';
@@ -237,6 +237,11 @@ export class MeInfo {
export class SessionInfo extends SessionDTO {
me?: MeInfo;
assignedWorker?: string;
// Timestamp of the last activity in milliseconds
presence: WAHAPresenceStatus.ONLINE | WAHAPresenceStatus.OFFLINE | null;
timestamps: {
activity: number | null;
};
}
export class SessionDetailedInfo extends SessionInfo {
+22
View File
@@ -0,0 +1,22 @@
export function ensureBase64(
data: string | Uint8Array | ArrayBuffer | null | undefined,
): string | null | undefined {
// Preserves null or undefined
if (!data) {
return data as any;
}
// Already base64
if (typeof data === 'string') {
return data.trim();
}
if (data instanceof Uint8Array) {
return Buffer.from(data).toString('base64');
}
if (data instanceof ArrayBuffer) {
return Buffer.from(new Uint8Array(data)).toString('base64');
}
return null;
}
+1 -1
View File
@@ -30,7 +30,7 @@ export function getWAHAVersion(): WAHAVersion {
}
export const VERSION: WAHAEnvironment = {
version: '2025.11.1',
version: '2025.11.4',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "v1.0.20"
"ref": "v1.0.24"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "3861e0854f4dba106e3e223e6f5df15d58b61949"
"ref": "d982212d20191adab7065216cad45ff064b483d8"
}
}
}
+53 -25
View File
@@ -5,9 +5,9 @@ __metadata:
version: 6
cacheKey: 8
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-11-07":
version: 7.0.0-rc.6
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=e828866d7936785b054e16f360c32115f118f291"
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-11-21":
version: 7.0.0-rc.9
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=4fb1d817fbc9e25c8011dcd0bd0836b11a340c5a"
dependencies:
"@cacheable/node-cache": ^1.4.0
"@hapi/boom": ^9.1.3
@@ -31,7 +31,7 @@ __metadata:
optional: true
link-preview-js:
optional: true
checksum: 0a02bc71fe13e998047f10e205126cca978ffde85a50fabc8ccf25c79a3ff6baecc857a395956e6c37b10abbeb9e390e4fcbc870ea11e3a22224cbc8c562c41e
checksum: b355cd7012afb96ae79d4d7debd3a888ed844d3ddef0ba83b9cd965f3d901bf2d42630be93064e0abcb1abd842f62d89c09cc6f7545ed3086d14422f789a80ec
languageName: node
linkType: hard
@@ -1290,27 +1290,27 @@ __metadata:
languageName: node
linkType: hard
"@grpc/grpc-js@npm:^1.13.4":
version: 1.13.4
resolution: "@grpc/grpc-js@npm:1.13.4"
"@grpc/grpc-js@npm:^1.14.1":
version: 1.14.1
resolution: "@grpc/grpc-js@npm:1.14.1"
dependencies:
"@grpc/proto-loader": ^0.7.13
"@grpc/proto-loader": ^0.8.0
"@js-sdsl/ordered-map": ^4.4.2
checksum: fe5db84bbbcd07cc1b68d1683b7fbe9cfcc5c3a60655ecc17fb3e1cd2adc4c1ce891b15e6e9a9c2140f6891def6f93b509a60d2bce253d13b317f9136e968451
checksum: 3f208c23cc985789934d806045599ad3bfa1b6fd9877151cf9af423b4d708cbb3b0cb1347a147061c6ed36470958fd522fbefbf6ae4ca8246f0860bd647f0e5e
languageName: node
linkType: hard
"@grpc/proto-loader@npm:^0.7.13":
version: 0.7.15
resolution: "@grpc/proto-loader@npm:0.7.15"
"@grpc/proto-loader@npm:^0.8.0":
version: 0.8.0
resolution: "@grpc/proto-loader@npm:0.8.0"
dependencies:
lodash.camelcase: ^4.3.0
long: ^5.0.0
protobufjs: ^7.2.5
protobufjs: ^7.5.3
yargs: ^17.7.2
bin:
proto-loader-gen-types: build/bin/proto-loader-gen-types.js
checksum: 9f19f4c611a17cd33aec0d6e3686a76696495f40593f7c284933c4b7877f58dfa5a225ddc20705860a632311f4dc0d143cb6a0da7b51b6f5ffd7de26938df308
checksum: 97570e2e8c29a35b999fa929acad506765e6752d3219764825fecc0ab632492b1ac2e4dd4c0a96cc2e01754d99bfa838dff0729c6608a02f7b027fd19bc1d1e4
languageName: node
linkType: hard
@@ -4867,14 +4867,14 @@ __metadata:
languageName: node
linkType: hard
"better-sqlite3@npm:11.3.0":
version: 11.3.0
resolution: "better-sqlite3@npm:11.3.0"
"better-sqlite3@npm:^12.4.1":
version: 12.4.1
resolution: "better-sqlite3@npm:12.4.1"
dependencies:
bindings: ^1.5.0
node-gyp: latest
prebuild-install: ^7.1.1
checksum: 364058b4cc739abd5774f511521298eac101d1e6f0362da65b56d9135e6e0728af38c2fa182ec2b56b4c76d7f7ff8efefd753df0c1b8f0192051b06d21e5d1f7
checksum: 39141f425a661fcb6ad413c791132c58463026f30d652a672ed9e0fa9fd286021826c0d2c3700762d6bafcccef8de5af1f0fb79eb8c2f31f005f7f0b47069292
languageName: node
linkType: hard
@@ -10456,7 +10456,7 @@ __metadata:
languageName: node
linkType: hard
"protobufjs@npm:^7.2.4, protobufjs@npm:^7.2.5":
"protobufjs@npm:^7.2.4":
version: 7.5.3
resolution: "protobufjs@npm:7.5.3"
dependencies:
@@ -10476,6 +10476,26 @@ __metadata:
languageName: node
linkType: hard
"protobufjs@npm:^7.5.3":
version: 7.5.4
resolution: "protobufjs@npm:7.5.4"
dependencies:
"@protobufjs/aspromise": ^1.1.2
"@protobufjs/base64": ^1.1.2
"@protobufjs/codegen": ^2.0.4
"@protobufjs/eventemitter": ^1.1.0
"@protobufjs/fetch": ^1.1.0
"@protobufjs/float": ^1.0.2
"@protobufjs/inquire": ^1.1.0
"@protobufjs/path": ^1.1.2
"@protobufjs/pool": ^1.1.0
"@protobufjs/utf8": ^1.1.0
"@types/node": ">=13.7.0"
long: ^5.0.0
checksum: 53bf83b9a726b05d43da35bb990dba7536759787dccea9a67b8f31be9df470ba17f1f1b982ca19956cfc7726f3ec7e0e883ca4ad93b5ec753cc025a637fc704f
languageName: node
linkType: hard
"protoc-gen-ts@npm:^0.8.7":
version: 0.8.7
resolution: "protoc-gen-ts@npm:0.8.7"
@@ -12372,6 +12392,13 @@ __metadata:
languageName: node
linkType: hard
"uniqid@npm:^5.4.0":
version: 5.4.0
resolution: "uniqid@npm:5.4.0"
checksum: 69fc28e7b2b5b24227b4295e51aa7c1d4085a60655ad071db8fc350876cc9044d68ee0781c0d27c2c98691380aa418970e0d1b02a1e4564480f9d19b0dc1707b
languageName: node
linkType: hard
"unique-filename@npm:^1.1.1":
version: 1.1.1
resolution: "unique-filename@npm:1.1.1"
@@ -12532,7 +12559,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "waha@workspace:."
dependencies:
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-11-07"
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-11-21"
"@adiwajshing/keyed-db": ^0.2.4
"@aws-sdk/client-s3": ^3.633.0
"@aws-sdk/s3-request-presigner": ^3.633.0
@@ -12540,8 +12567,8 @@ __metadata:
"@bull-board/express": ^6.9.1
"@bull-board/nestjs": ^6.9.1
"@figuro/chatwoot-sdk": ^1.1.17
"@grpc/grpc-js": ^1.13.4
"@grpc/proto-loader": ^0.7.13
"@grpc/grpc-js": ^1.14.1
"@grpc/proto-loader": ^0.8.0
"@liaoliaots/nestjs-redis": ^9
"@nestjs/axios": ^3.0.2
"@nestjs/bullmq": ^11.0.2
@@ -12576,7 +12603,7 @@ __metadata:
audio-decode: ^2.2.2
axios: ^1.9.0
axios-retry: ^4.5.0
better-sqlite3: 11.3.0
better-sqlite3: ^12.4.1
bufferutil: ^4.0.8
bullmq: ^5.48.1
check-disk-space: ^3.4.0
@@ -12636,6 +12663,7 @@ __metadata:
typescript: 5.9.2
ulid: ^2.3.0
undici: ^7.16.0
uniqid: ^5.4.0
whatsapp-web.js: "github:devlikeapro/whatsapp-web.js#fork-main-2025-11-05"
write-file-atomic: ^6.0.0
yaml: ^2.7.1
@@ -12739,7 +12767,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2025-11-05":
version: 1.34.2
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=c84b4af4dfd01f326d37f4de6acd7bebc06496d0"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=59a904a9a1f2869d39754117289267e0a925a7af"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -12757,7 +12785,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: d9d615d29ba14f6113080a19d8f7e1396ea7dde3cbe4983db04e8df512483198c3f73236e9e8c366afb035ee700bd5718d72bc18ee01caeb7cd86bcadfdbb056
checksum: a59e4cee66a41f2a59d0340c4a765848f9fe76bdf4cfdd749c50deb5246f61bb8f9974fe4b35a7cc13d3adde6244975cea02caf72ca6e746b18b5d9c5c425b69
languageName: node
linkType: hard