Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9362c5d6ae | ||
|
|
117a9108c0 | ||
|
|
5214e461f2 | ||
|
|
9bdb178b7d | ||
|
|
9c526ffdba | ||
|
|
f80a74edd1 | ||
|
|
7d8c6c0a62 | ||
|
|
17b26b869b | ||
|
|
dfbe54e92f | ||
|
|
2321de409b | ||
|
|
502287a258 | ||
|
|
052ff0ea6e | ||
|
|
2b76789cdf | ||
|
|
7bbf5df231 | ||
|
|
794a04e7d3 | ||
|
|
645467f13a | ||
|
|
7e87637341 | ||
|
|
b7abeed54a | ||
|
|
d53646eef2 | ||
|
|
c54743de6c | ||
|
|
e1fdc41132 | ||
|
|
060aa82294 | ||
|
|
2df08eac3f | ||
|
|
0ecdb9d3d0 | ||
|
|
6a7fe1bd1f | ||
|
|
87c69cb3d0 | ||
|
|
c654855c22 | ||
|
|
11bb006801 | ||
|
|
e5551f3f64 | ||
|
|
0de148005f | ||
|
|
27e4ce628e | ||
|
|
19b752bf15 | ||
|
|
6528e5bba9 | ||
|
|
37e1183e87 | ||
|
|
824512cf8e | ||
|
|
82f296b17d |
No files matched your search
+6
-1
@@ -131,7 +131,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
|
||||
# Install Chrome
|
||||
# Available versions:
|
||||
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
|
||||
ARG CHROME_VERSION="135.0.7049.114-1"
|
||||
ARG CHROME_VERSION="137.0.7151.55-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 \
|
||||
@@ -146,6 +146,9 @@ RUN apt-get update \
|
||||
&& apt-get install -y libc6 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install tini for proper init process
|
||||
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the ENV for docker image
|
||||
ENV WHATSAPP_DEFAULT_ENGINE=$WHATSAPP_DEFAULT_ENGINE
|
||||
|
||||
@@ -170,4 +173,6 @@ ENV WAHA_ZIPPER=ZIPUNZIP
|
||||
|
||||
# Run command, etc
|
||||
EXPOSE 3000
|
||||
# Use tini as init system to handle zombie processes properly
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
CMD ["/entrypoint.sh"]
|
||||
@@ -41,7 +41,7 @@ up-noweb-libsignal:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
|
||||
|
||||
up-webjs:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2025-06-09
|
||||
|
||||
start-proxy:
|
||||
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
|
||||
|
||||
@@ -8,6 +8,12 @@ services:
|
||||
# WAHA Core
|
||||
# image: devlikeapro/waha:latest
|
||||
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '100m'
|
||||
max-file: '10'
|
||||
|
||||
ports:
|
||||
- '127.0.0.1:3000:3000/tcp'
|
||||
|
||||
@@ -39,6 +45,11 @@ services:
|
||||
# - postgres
|
||||
# - "-c"
|
||||
# - "max_connections=3000"
|
||||
# logging:
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "100m"
|
||||
# max-file: "10"
|
||||
|
||||
# NOTE: Only if you're using MongoDB
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||
@@ -53,6 +64,11 @@ services:
|
||||
# environment:
|
||||
# - MONGO_INITDB_ROOT_USERNAME=mongouser
|
||||
# - MONGO_INITDB_ROOT_PASSWORD=mongopassword
|
||||
# logging:
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "100m"
|
||||
# max-file: "10"
|
||||
|
||||
# NOTE: Only if you're using S3 to save media files
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
|
||||
@@ -71,6 +87,11 @@ services:
|
||||
# volumes:
|
||||
# - minio_data:/data
|
||||
# command: server /data --console-address ":9001"
|
||||
# logging:
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "100m"
|
||||
# max-file: "10"
|
||||
|
||||
volumes:
|
||||
mongodb_data: {}
|
||||
|
||||
+1
-1
@@ -7,5 +7,5 @@ uv_threadpool_size=$(($cpus * 1))
|
||||
# Set UV_THREADPOOL_SIZE as an environment variable
|
||||
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
|
||||
|
||||
# Start your application using yarn
|
||||
# Start your application using node with exec to ensure proper signal handling
|
||||
exec node dist/main
|
||||
Binary file not shown.
Binary file not shown.
+16
-15
@@ -31,18 +31,19 @@
|
||||
"@aws-sdk/client-s3": "^3.633.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.633.0",
|
||||
"@nestjs/axios": "^3.0.2",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/config": "^3.2.3",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/platform-ws": "^9.0.9",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/core": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.0",
|
||||
"@nestjs/platform-ws": "^11.0.0",
|
||||
"@nestjs/serve-static": "^5.0.0",
|
||||
"@nestjs/swagger": "^7.1.11",
|
||||
"@nestjs/terminus": "^10.2.3",
|
||||
"@nestjs/websockets": "^9.0.9",
|
||||
"@nestjs/websockets": "^11.0.0",
|
||||
"@types/better-sqlite3": "^7.6.10",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/ws": "^8.5.4",
|
||||
"adm-zip": "0.5.10",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
@@ -71,7 +72,7 @@
|
||||
"mongodb": "6.9.0",
|
||||
"nestjs-pino": "^4.1.0",
|
||||
"node-cache": "5.1.2",
|
||||
"passport": "^0.6.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"pg": "^8.13.1",
|
||||
"pino-http": "^10.2.0",
|
||||
@@ -87,7 +88,7 @@
|
||||
"sharp": "^0.33.4",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"ulid": "^2.3.0",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-channels",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2025-06-09",
|
||||
"write-file-atomic": "^6.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -96,16 +97,16 @@
|
||||
"resolutions": {
|
||||
"bufferutil": "^4.0.8",
|
||||
"ws": "^8.18.0",
|
||||
"puppeteer": "^24.7.2",
|
||||
"puppeteer": "^24.10.0",
|
||||
"whatwg-url": "13.0.0",
|
||||
"libsignal": "github:devlikeapro/libsignal-node#fork-master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grpc/grpc-js": "^1.12.4",
|
||||
"@grpc/grpc-js": "^1.13.4",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.1",
|
||||
"@nestjs/testing": "^9.0.9",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.0",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/node": "^18.14.2",
|
||||
@@ -116,7 +117,7 @@
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"grpc-tools": "^1.12.4",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^1.19.1",
|
||||
"protoc-gen-ts": "^0.8.7",
|
||||
|
||||
+2
-2
@@ -12,9 +12,9 @@ touch "$OUTPUT_FILE"
|
||||
# Find all .md files in the source directory and combine them
|
||||
find "$SOURCE_DIR" -type f -name "*.ts" | while read -r file; do
|
||||
# Add file name to OUTPUT_FILE
|
||||
echo -e "\n-------" >> "$OUTPUT_FILE"
|
||||
echo "\n-------" >> "$OUTPUT_FILE"
|
||||
echo "File: $file" >> "$OUTPUT_FILE"
|
||||
echo -e "-------\n" >> "$OUTPUT_FILE"
|
||||
echo "-------\n" >> "$OUTPUT_FILE"
|
||||
# Append the content of the file to the output file
|
||||
cat "$file" >> "$OUTPUT_FILE"
|
||||
done
|
||||
|
||||
@@ -36,8 +36,8 @@ class AuthController {
|
||||
summary: 'Get QR code for pairing WhatsApp API.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader(Base64File, QRCodeValue)
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiFileAcceptHeader('image/png', Base64File, QRCodeValue)
|
||||
@UseInterceptors(new BufferResponseInterceptor('image/png'))
|
||||
async getQR(
|
||||
@QRCodeSessionParam session: WhatsappSession,
|
||||
@Query() query: QRCodeQuery,
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewBodyRequest,
|
||||
OverviewFilter,
|
||||
OverviewPaginationParams,
|
||||
PinMessageRequest,
|
||||
ReadChatMessagesQuery,
|
||||
@@ -65,8 +67,23 @@ class ChatsController {
|
||||
getChatsOverview(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() pagination: OverviewPaginationParams,
|
||||
@Query() filter: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
return session.getChatsOverview(pagination);
|
||||
return session.getChatsOverview(pagination, filter);
|
||||
}
|
||||
|
||||
@Post('overview')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get chats overview. Use POST if you have too many "ids" params - GET can limit it',
|
||||
})
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
postChatsOverview(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() body: OverviewBodyRequest,
|
||||
): Promise<ChatSummary[]> {
|
||||
return session.getChatsOverview(body.pagination, body.filter);
|
||||
}
|
||||
|
||||
@Delete(':chatId')
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Post,
|
||||
UnprocessableEntityException,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { WAMimeType } from '@waha/core/media/WAMimeType';
|
||||
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import {
|
||||
FileDTO,
|
||||
VideoFileDTO,
|
||||
VoiceFileDTO,
|
||||
} from '@waha/structures/media.dto';
|
||||
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/media')
|
||||
@ApiTags('🖼️ Media')
|
||||
class MediaController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('convert/voice')
|
||||
@ApiOperation({
|
||||
summary: 'Convert voice to WhatsApp format (opus)',
|
||||
})
|
||||
@SessionApiParam
|
||||
@UseInterceptors(
|
||||
new BufferResponseInterceptor(WAMimeType.VOICE, 'output.opus'),
|
||||
)
|
||||
@ApiFileAcceptHeader(WAMimeType.VOICE)
|
||||
async convertVoice(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() file: VoiceFileDTO,
|
||||
): Promise<Buffer> {
|
||||
const data = await this.buffer(session, file);
|
||||
const content = await session.mediaConverter.voice(data);
|
||||
return content;
|
||||
}
|
||||
|
||||
@Post('convert/video')
|
||||
@ApiOperation({
|
||||
summary: 'Convert video to WhatsApp format (mp4)',
|
||||
})
|
||||
@SessionApiParam
|
||||
@UseInterceptors(
|
||||
new BufferResponseInterceptor(WAMimeType.VIDEO, 'output.mp4'),
|
||||
)
|
||||
@ApiFileAcceptHeader(WAMimeType.VIDEO)
|
||||
async convertVideo(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() file: VideoFileDTO,
|
||||
): Promise<Buffer> {
|
||||
const data = await this.buffer(session, file);
|
||||
const content = await session.mediaConverter.video(data);
|
||||
return content;
|
||||
}
|
||||
|
||||
private async buffer(
|
||||
session: WhatsappSession,
|
||||
file: FileDTO,
|
||||
): Promise<Buffer> {
|
||||
if ('url' in file) {
|
||||
return session.fetch(file.url);
|
||||
} else if ('data' in file) {
|
||||
return Buffer.from(file.data, 'base64');
|
||||
} else {
|
||||
throw new UnprocessableEntityException(
|
||||
'Either "url" or "data" must be specified.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { MediaController };
|
||||
@@ -21,8 +21,8 @@ export class ScreenshotController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/screenshot')
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiFileAcceptHeader()
|
||||
@UseInterceptors(new BufferResponseInterceptor('image/jpeg'))
|
||||
@ApiFileAcceptHeader('image/jpeg')
|
||||
async screenshot(
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
|
||||
@@ -233,6 +233,9 @@ class SessionsController {
|
||||
@Body() request: SessionStartDeprecatedRequest,
|
||||
): Promise<SessionDTO> {
|
||||
const name = request.name;
|
||||
if (!request.name) {
|
||||
throw new UnprocessableEntityException('Session name is required');
|
||||
}
|
||||
if (this.manager.isRunning(name)) {
|
||||
const msg = `Session '${name}' is already started.`;
|
||||
throw new UnprocessableEntityException(msg);
|
||||
@@ -255,6 +258,9 @@ class SessionsController {
|
||||
async DEPRECATED_stop(
|
||||
@Body() request: SessionStopDeprecatedRequest,
|
||||
): Promise<void> {
|
||||
if (!request.name) {
|
||||
throw new UnprocessableEntityException('Session name is required');
|
||||
}
|
||||
const name = request.name;
|
||||
if (request.logout) {
|
||||
// Old API did remove the session complete
|
||||
@@ -283,6 +289,9 @@ class SessionsController {
|
||||
async DEPRECATED_logout(
|
||||
@Body() request: SessionLogoutDeprecatedRequest,
|
||||
): Promise<void> {
|
||||
if (!request.name) {
|
||||
throw new UnprocessableEntityException('Session name is required');
|
||||
}
|
||||
const name = request.name;
|
||||
await this.withLock(name, async () => {
|
||||
await this.manager.unassign(name);
|
||||
|
||||
@@ -76,6 +76,7 @@ export class SwaggerConfiguratorCore {
|
||||
'🏷️ Labels',
|
||||
'Labels - available only for WhatsApp Business accounts',
|
||||
)
|
||||
.addTag('🖼️ Media', 'Media methods')
|
||||
.addTag('🔍 Observability', 'Other methods')
|
||||
.addTag('🗄️ Storage', 'Storage methods')
|
||||
.addApiKey({
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import {
|
||||
CoreMediaConverter,
|
||||
IMediaConverter,
|
||||
} from '@waha/core/media/IConverter';
|
||||
import { MessagesForRead } from '@waha/core/utils/convertors';
|
||||
import { isJidNewsletter } from '@waha/core/utils/jids';
|
||||
import {
|
||||
@@ -16,6 +20,7 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewFilter,
|
||||
ReadChatMessagesQuery,
|
||||
ReadChatMessagesResponse,
|
||||
} from '@waha/structures/chats.dto';
|
||||
@@ -23,16 +28,15 @@ import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import {
|
||||
LimitOffsetParams,
|
||||
PaginationParams,
|
||||
} from '@waha/structures/pagination.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import { MessageSource, WAMessage } from '@waha/structures/responses.dto';
|
||||
import { DefaultMap } from '@waha/utils/DefaultMap';
|
||||
import { generatePrefixedId } from '@waha/utils/ids';
|
||||
import { LoggerBuilder } from '@waha/utils/logging';
|
||||
import { complete } from '@waha/utils/reactive/complete';
|
||||
import { SwitchObservable } from '@waha/utils/reactive/SwitchObservable';
|
||||
import axios from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import * as fs from 'fs';
|
||||
import * as lodash from 'lodash';
|
||||
import * as NodeCache from 'node-cache';
|
||||
@@ -108,9 +112,12 @@ import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { IMediaManager } from '../media/IMediaManager';
|
||||
import { QR } from '../QR';
|
||||
import { DataStore } from './DataStore';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const qrcode = require('qrcode-terminal');
|
||||
|
||||
axiosRetry(axios, { retries: 3 });
|
||||
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
|
||||
@@ -167,6 +174,8 @@ export abstract class WhatsappSession {
|
||||
stdTTL: 10 * 60, // 10 minutes
|
||||
});
|
||||
|
||||
public mediaConverter: IMediaConverter = new CoreMediaConverter();
|
||||
|
||||
public constructor({
|
||||
name,
|
||||
printQR,
|
||||
@@ -491,6 +500,7 @@ export abstract class WhatsappSession {
|
||||
|
||||
public getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
@@ -991,6 +1001,15 @@ export abstract class WhatsappSession {
|
||||
const api = this.sentMessageIds.has(id);
|
||||
return api ? MessageSource.API : MessageSource.APP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the content from the specified URL and returns it as a Buffer.
|
||||
*/
|
||||
public async fetch(url: string): Promise<Buffer> {
|
||||
return axios.get(url, { responseType: 'arraybuffer' }).then((res) => {
|
||||
return Buffer.from(res.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getGroupInviteLink(code: string) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { EventsController } from '../api/events.controller';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
import { HealthController } from '../api/health.controller';
|
||||
import { LabelsController } from '../api/labels.controller';
|
||||
import { MediaController } from '../api/media.controller';
|
||||
import { PingController } from '../api/ping.controller';
|
||||
import { PresenceController } from '../api/presence.controller';
|
||||
import { ScreenshotController } from '../api/screenshot.controller';
|
||||
@@ -141,6 +142,7 @@ export const CONTROLLERS = [
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
VersionController,
|
||||
MediaController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
{
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { WhatsappConfigService } from '@waha/config.service';
|
||||
import { BootstrapConfig } from '@waha/core/engines/gows/GowsBootstrap';
|
||||
import { GowsConfig } from '@waha/core/engines/gows/session.gows.core';
|
||||
|
||||
@Injectable()
|
||||
export class GowsEngineConfigService {
|
||||
constructor(protected configService: ConfigService) {}
|
||||
constructor(
|
||||
protected configService: ConfigService,
|
||||
protected whatsappConfigService: WhatsappConfigService,
|
||||
) {}
|
||||
|
||||
getBootstrapConfig(): BootstrapConfig {
|
||||
return {
|
||||
path: this.configService.get('WAHA_GOWS_PATH'),
|
||||
socket: this.getSocket(),
|
||||
pprof: this.whatsappConfigService.debugModeEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export async function isUnixSocket(socketPath: string): Promise<boolean> {
|
||||
export interface BootstrapConfig {
|
||||
path: string;
|
||||
socket: string;
|
||||
pprof?: boolean;
|
||||
}
|
||||
|
||||
export class GowsBootstrap implements EngineBootstrap {
|
||||
@@ -44,6 +45,7 @@ export class GowsBootstrap implements EngineBootstrap {
|
||||
this.logger,
|
||||
this.config.path,
|
||||
this.config.socket,
|
||||
this.config.pprof,
|
||||
);
|
||||
this.gows.start(() => {
|
||||
this.logger.info(`GOWS stopped, exiting...`);
|
||||
|
||||
@@ -14,13 +14,22 @@ export class GowsSubprocess {
|
||||
private logger: Logger,
|
||||
readonly path: string,
|
||||
readonly socket: string,
|
||||
readonly pprof: boolean = false,
|
||||
) {}
|
||||
|
||||
start(onExit: (code: number) => void) {
|
||||
this.logger.info('Starting GOWS subprocess...');
|
||||
this.logger.debug(`GOWS path '${this.path}', socket: '${this.socket}'...`);
|
||||
|
||||
this.child = spawn(this.path, ['--socket', this.socket], {
|
||||
const args = ['--socket', this.socket];
|
||||
if (this.pprof) {
|
||||
this.logger.info('Debug mode enabled, adding pprof flags');
|
||||
args.push('--pprof');
|
||||
args.push('--pprof-port=6060');
|
||||
args.push('--pprof-host=0.0.0.0');
|
||||
}
|
||||
|
||||
this.child = spawn(this.path, args, {
|
||||
detached: true,
|
||||
});
|
||||
this.logger.debug(`GOWS started with PID: ${this.child.pid}`);
|
||||
|
||||
@@ -3262,6 +3262,7 @@ export namespace messages {
|
||||
preview?: LinkPreview;
|
||||
contacts?: vCardContact[];
|
||||
event?: EventMessage;
|
||||
poll?: PollMessage;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [11, 13], this.#one_of_decls);
|
||||
@@ -3308,6 +3309,9 @@ export namespace messages {
|
||||
if ("event" in data && data.event != undefined) {
|
||||
this.event = data.event;
|
||||
}
|
||||
if ("poll" in data && data.poll != undefined) {
|
||||
this.poll = data.poll;
|
||||
}
|
||||
}
|
||||
}
|
||||
get session() {
|
||||
@@ -3412,6 +3416,15 @@ export namespace messages {
|
||||
get has_event() {
|
||||
return pb_1.Message.getField(this, 14) != null;
|
||||
}
|
||||
get poll() {
|
||||
return pb_1.Message.getWrapperField(this, PollMessage, 15) as PollMessage;
|
||||
}
|
||||
set poll(value: PollMessage) {
|
||||
pb_1.Message.setWrapperField(this, 15, value);
|
||||
}
|
||||
get has_poll() {
|
||||
return pb_1.Message.getField(this, 15) != null;
|
||||
}
|
||||
static fromObject(data: {
|
||||
session?: ReturnType<typeof Session.prototype.toObject>;
|
||||
jid?: string;
|
||||
@@ -3427,6 +3440,7 @@ export namespace messages {
|
||||
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
|
||||
contacts?: ReturnType<typeof vCardContact.prototype.toObject>[];
|
||||
event?: ReturnType<typeof EventMessage.prototype.toObject>;
|
||||
poll?: ReturnType<typeof PollMessage.prototype.toObject>;
|
||||
}): MessageRequest {
|
||||
const message = new MessageRequest({});
|
||||
if (data.session != null) {
|
||||
@@ -3471,6 +3485,9 @@ export namespace messages {
|
||||
if (data.event != null) {
|
||||
message.event = EventMessage.fromObject(data.event);
|
||||
}
|
||||
if (data.poll != null) {
|
||||
message.poll = PollMessage.fromObject(data.poll);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
@@ -3489,6 +3506,7 @@ export namespace messages {
|
||||
preview?: ReturnType<typeof LinkPreview.prototype.toObject>;
|
||||
contacts?: ReturnType<typeof vCardContact.prototype.toObject>[];
|
||||
event?: ReturnType<typeof EventMessage.prototype.toObject>;
|
||||
poll?: ReturnType<typeof PollMessage.prototype.toObject>;
|
||||
} = {};
|
||||
if (this.session != null) {
|
||||
data.session = this.session.toObject();
|
||||
@@ -3532,6 +3550,9 @@ export namespace messages {
|
||||
if (this.event != null) {
|
||||
data.event = this.event.toObject();
|
||||
}
|
||||
if (this.poll != null) {
|
||||
data.poll = this.poll.toObject();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
@@ -3566,6 +3587,8 @@ export namespace messages {
|
||||
writer.writeRepeatedMessage(13, this.contacts, (item: vCardContact) => item.serialize(writer));
|
||||
if (this.has_event)
|
||||
writer.writeMessage(14, this.event, () => this.event.serialize(writer));
|
||||
if (this.has_poll)
|
||||
writer.writeMessage(15, this.poll, () => this.poll.serialize(writer));
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
@@ -3617,6 +3640,9 @@ export namespace messages {
|
||||
case 14:
|
||||
reader.readMessage(message.event, () => message.event = EventMessage.deserialize(reader));
|
||||
break;
|
||||
case 15:
|
||||
reader.readMessage(message.poll, () => message.poll = PollMessage.deserialize(reader));
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
@@ -7991,12 +8017,80 @@ export namespace messages {
|
||||
return GetContactsRequest.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
export class ChatFilter extends pb_1.Message {
|
||||
#one_of_decls: number[][] = [];
|
||||
constructor(data?: any[] | {
|
||||
jids?: string[];
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [1], this.#one_of_decls);
|
||||
if (!Array.isArray(data) && typeof data == "object") {
|
||||
if ("jids" in data && data.jids != undefined) {
|
||||
this.jids = data.jids;
|
||||
}
|
||||
}
|
||||
}
|
||||
get jids() {
|
||||
return pb_1.Message.getFieldWithDefault(this, 1, []) as string[];
|
||||
}
|
||||
set jids(value: string[]) {
|
||||
pb_1.Message.setField(this, 1, value);
|
||||
}
|
||||
static fromObject(data: {
|
||||
jids?: string[];
|
||||
}): ChatFilter {
|
||||
const message = new ChatFilter({});
|
||||
if (data.jids != null) {
|
||||
message.jids = data.jids;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
const data: {
|
||||
jids?: string[];
|
||||
} = {};
|
||||
if (this.jids != null) {
|
||||
data.jids = this.jids;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
serialize(w: pb_1.BinaryWriter): void;
|
||||
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
|
||||
const writer = w || new pb_1.BinaryWriter();
|
||||
if (this.jids.length)
|
||||
writer.writeRepeatedString(1, this.jids);
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): ChatFilter {
|
||||
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new ChatFilter();
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup())
|
||||
break;
|
||||
switch (reader.getFieldNumber()) {
|
||||
case 1:
|
||||
pb_1.Message.addToRepeatedField(message, 1, reader.readString());
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
serializeBinary(): Uint8Array {
|
||||
return this.serialize();
|
||||
}
|
||||
static deserializeBinary(bytes: Uint8Array): ChatFilter {
|
||||
return ChatFilter.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
export class GetChatsRequest extends pb_1.Message {
|
||||
#one_of_decls: number[][] = [];
|
||||
constructor(data?: any[] | {
|
||||
session?: Session;
|
||||
sortBy?: SortBy;
|
||||
pagination?: Pagination;
|
||||
filter?: ChatFilter;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls);
|
||||
@@ -8010,6 +8104,9 @@ export namespace messages {
|
||||
if ("pagination" in data && data.pagination != undefined) {
|
||||
this.pagination = data.pagination;
|
||||
}
|
||||
if ("filter" in data && data.filter != undefined) {
|
||||
this.filter = data.filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
get session() {
|
||||
@@ -8039,10 +8136,20 @@ export namespace messages {
|
||||
get has_pagination() {
|
||||
return pb_1.Message.getField(this, 3) != null;
|
||||
}
|
||||
get filter() {
|
||||
return pb_1.Message.getWrapperField(this, ChatFilter, 4) as ChatFilter;
|
||||
}
|
||||
set filter(value: ChatFilter) {
|
||||
pb_1.Message.setWrapperField(this, 4, value);
|
||||
}
|
||||
get has_filter() {
|
||||
return pb_1.Message.getField(this, 4) != null;
|
||||
}
|
||||
static fromObject(data: {
|
||||
session?: ReturnType<typeof Session.prototype.toObject>;
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
filter?: ReturnType<typeof ChatFilter.prototype.toObject>;
|
||||
}): GetChatsRequest {
|
||||
const message = new GetChatsRequest({});
|
||||
if (data.session != null) {
|
||||
@@ -8054,6 +8161,9 @@ export namespace messages {
|
||||
if (data.pagination != null) {
|
||||
message.pagination = Pagination.fromObject(data.pagination);
|
||||
}
|
||||
if (data.filter != null) {
|
||||
message.filter = ChatFilter.fromObject(data.filter);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
@@ -8061,6 +8171,7 @@ export namespace messages {
|
||||
session?: ReturnType<typeof Session.prototype.toObject>;
|
||||
sortBy?: ReturnType<typeof SortBy.prototype.toObject>;
|
||||
pagination?: ReturnType<typeof Pagination.prototype.toObject>;
|
||||
filter?: ReturnType<typeof ChatFilter.prototype.toObject>;
|
||||
} = {};
|
||||
if (this.session != null) {
|
||||
data.session = this.session.toObject();
|
||||
@@ -8071,6 +8182,9 @@ export namespace messages {
|
||||
if (this.pagination != null) {
|
||||
data.pagination = this.pagination.toObject();
|
||||
}
|
||||
if (this.filter != null) {
|
||||
data.filter = this.filter.toObject();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
@@ -8083,6 +8197,8 @@ export namespace messages {
|
||||
writer.writeMessage(2, this.sortBy, () => this.sortBy.serialize(writer));
|
||||
if (this.has_pagination)
|
||||
writer.writeMessage(3, this.pagination, () => this.pagination.serialize(writer));
|
||||
if (this.has_filter)
|
||||
writer.writeMessage(4, this.filter, () => this.filter.serialize(writer));
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
@@ -8101,6 +8217,9 @@ export namespace messages {
|
||||
case 3:
|
||||
reader.readMessage(message.pagination, () => message.pagination = Pagination.deserialize(reader));
|
||||
break;
|
||||
case 4:
|
||||
reader.readMessage(message.filter, () => message.filter = ChatFilter.deserialize(reader));
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
@@ -8790,6 +8909,119 @@ export namespace messages {
|
||||
return GetLidsRequest.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
export class PollMessage extends pb_1.Message {
|
||||
#one_of_decls: number[][] = [];
|
||||
constructor(data?: any[] | {
|
||||
name?: string;
|
||||
options?: string[];
|
||||
multipleAnswers?: boolean;
|
||||
}) {
|
||||
super();
|
||||
pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [2], this.#one_of_decls);
|
||||
if (!Array.isArray(data) && typeof data == "object") {
|
||||
if ("name" in data && data.name != undefined) {
|
||||
this.name = data.name;
|
||||
}
|
||||
if ("options" in data && data.options != undefined) {
|
||||
this.options = data.options;
|
||||
}
|
||||
if ("multipleAnswers" in data && data.multipleAnswers != undefined) {
|
||||
this.multipleAnswers = data.multipleAnswers;
|
||||
}
|
||||
}
|
||||
}
|
||||
get name() {
|
||||
return pb_1.Message.getFieldWithDefault(this, 1, "") as string;
|
||||
}
|
||||
set name(value: string) {
|
||||
pb_1.Message.setField(this, 1, value);
|
||||
}
|
||||
get options() {
|
||||
return pb_1.Message.getFieldWithDefault(this, 2, []) as string[];
|
||||
}
|
||||
set options(value: string[]) {
|
||||
pb_1.Message.setField(this, 2, value);
|
||||
}
|
||||
get multipleAnswers() {
|
||||
return pb_1.Message.getFieldWithDefault(this, 3, false) as boolean;
|
||||
}
|
||||
set multipleAnswers(value: boolean) {
|
||||
pb_1.Message.setField(this, 3, value);
|
||||
}
|
||||
static fromObject(data: {
|
||||
name?: string;
|
||||
options?: string[];
|
||||
multipleAnswers?: boolean;
|
||||
}): PollMessage {
|
||||
const message = new PollMessage({});
|
||||
if (data.name != null) {
|
||||
message.name = data.name;
|
||||
}
|
||||
if (data.options != null) {
|
||||
message.options = data.options;
|
||||
}
|
||||
if (data.multipleAnswers != null) {
|
||||
message.multipleAnswers = data.multipleAnswers;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
toObject() {
|
||||
const data: {
|
||||
name?: string;
|
||||
options?: string[];
|
||||
multipleAnswers?: boolean;
|
||||
} = {};
|
||||
if (this.name != null) {
|
||||
data.name = this.name;
|
||||
}
|
||||
if (this.options != null) {
|
||||
data.options = this.options;
|
||||
}
|
||||
if (this.multipleAnswers != null) {
|
||||
data.multipleAnswers = this.multipleAnswers;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
serialize(): Uint8Array;
|
||||
serialize(w: pb_1.BinaryWriter): void;
|
||||
serialize(w?: pb_1.BinaryWriter): Uint8Array | void {
|
||||
const writer = w || new pb_1.BinaryWriter();
|
||||
if (this.name.length)
|
||||
writer.writeString(1, this.name);
|
||||
if (this.options.length)
|
||||
writer.writeRepeatedString(2, this.options);
|
||||
if (this.multipleAnswers != false)
|
||||
writer.writeBool(3, this.multipleAnswers);
|
||||
if (!w)
|
||||
return writer.getResultBuffer();
|
||||
}
|
||||
static deserialize(bytes: Uint8Array | pb_1.BinaryReader): PollMessage {
|
||||
const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new PollMessage();
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup())
|
||||
break;
|
||||
switch (reader.getFieldNumber()) {
|
||||
case 1:
|
||||
message.name = reader.readString();
|
||||
break;
|
||||
case 2:
|
||||
pb_1.Message.addToRepeatedField(message, 2, reader.readString());
|
||||
break;
|
||||
case 3:
|
||||
message.multipleAnswers = reader.readBool();
|
||||
break;
|
||||
default: reader.skipField();
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
serializeBinary(): Uint8Array {
|
||||
return this.serialize();
|
||||
}
|
||||
static deserializeBinary(bytes: Uint8Array): PollMessage {
|
||||
return PollMessage.deserialize(bytes);
|
||||
}
|
||||
}
|
||||
interface GrpcUnaryServiceInterface<P, R> {
|
||||
(message: P, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
|
||||
(message: P, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback<R>): grpc_1.ClientUnaryCall;
|
||||
|
||||
@@ -671,7 +671,7 @@ var EventStreamService = exports.EventStreamService = {
|
||||
},
|
||||
};
|
||||
|
||||
exports.EventStreamClient = grpc.makeGenericClientConstructor(EventStreamService);
|
||||
exports.EventStreamClient = grpc.makeGenericClientConstructor(EventStreamService, 'EventStream');
|
||||
var MessageServiceService = exports.MessageServiceService = {
|
||||
//
|
||||
// Session management
|
||||
@@ -1381,4 +1381,4 @@ getMessageById: {
|
||||
},
|
||||
};
|
||||
|
||||
exports.MessageServiceClient = grpc.makeGenericClientConstructor(MessageServiceService);
|
||||
exports.MessageServiceClient = grpc.makeGenericClientConstructor(MessageServiceService, 'MessageService');
|
||||
@@ -24,6 +24,7 @@ var global = (function() {
|
||||
goog.exportSymbol('proto.messages.AudioInfo', null, global);
|
||||
goog.exportSymbol('proto.messages.ButtonReplyRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.CancelEventMessageRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.ChatFilter', null, global);
|
||||
goog.exportSymbol('proto.messages.ChatLabelRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.ChatPresence', null, global);
|
||||
goog.exportSymbol('proto.messages.ChatPresenceRequest', null, global);
|
||||
@@ -81,6 +82,7 @@ goog.exportSymbol('proto.messages.PairCodeRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.PairCodeResponse', null, global);
|
||||
goog.exportSymbol('proto.messages.ParticipantAction', null, global);
|
||||
goog.exportSymbol('proto.messages.PhoneInfo', null, global);
|
||||
goog.exportSymbol('proto.messages.PollMessage', null, global);
|
||||
goog.exportSymbol('proto.messages.Presence', null, global);
|
||||
goog.exportSymbol('proto.messages.PresenceRequest', null, global);
|
||||
goog.exportSymbol('proto.messages.ProfileNameRequest', null, global);
|
||||
@@ -1620,6 +1622,27 @@ if (goog.DEBUG && !COMPILED) {
|
||||
*/
|
||||
proto.messages.GetContactsRequest.displayName = 'proto.messages.GetContactsRequest';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.messages.ChatFilter = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, proto.messages.ChatFilter.repeatedFields_, null);
|
||||
};
|
||||
goog.inherits(proto.messages.ChatFilter, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.messages.ChatFilter.displayName = 'proto.messages.ChatFilter';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
@@ -1788,6 +1811,27 @@ if (goog.DEBUG && !COMPILED) {
|
||||
*/
|
||||
proto.messages.GetLidsRequest.displayName = 'proto.messages.GetLidsRequest';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.messages.PollMessage = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, proto.messages.PollMessage.repeatedFields_, null);
|
||||
};
|
||||
goog.inherits(proto.messages.PollMessage, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.messages.PollMessage.displayName = 'proto.messages.PollMessage';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7903,7 +7947,8 @@ proto.messages.MessageRequest.toObject = function(includeInstance, msg) {
|
||||
preview: (f = msg.getPreview()) && proto.messages.LinkPreview.toObject(includeInstance, f),
|
||||
contactsList: jspb.Message.toObjectList(msg.getContactsList(),
|
||||
proto.messages.vCardContact.toObject, includeInstance),
|
||||
event: (f = msg.getEvent()) && proto.messages.EventMessage.toObject(includeInstance, f)
|
||||
event: (f = msg.getEvent()) && proto.messages.EventMessage.toObject(includeInstance, f),
|
||||
poll: (f = msg.getPoll()) && proto.messages.PollMessage.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -8003,6 +8048,11 @@ proto.messages.MessageRequest.deserializeBinaryFromReader = function(msg, reader
|
||||
reader.readMessage(value,proto.messages.EventMessage.deserializeBinaryFromReader);
|
||||
msg.setEvent(value);
|
||||
break;
|
||||
case 15:
|
||||
var value = new proto.messages.PollMessage;
|
||||
reader.readMessage(value,proto.messages.PollMessage.deserializeBinaryFromReader);
|
||||
msg.setPoll(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -8137,6 +8187,14 @@ proto.messages.MessageRequest.serializeBinaryToWriter = function(message, writer
|
||||
proto.messages.EventMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getPoll();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
15,
|
||||
f,
|
||||
proto.messages.PollMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8545,6 +8603,43 @@ proto.messages.MessageRequest.prototype.hasEvent = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional PollMessage poll = 15;
|
||||
* @return {?proto.messages.PollMessage}
|
||||
*/
|
||||
proto.messages.MessageRequest.prototype.getPoll = function() {
|
||||
return /** @type{?proto.messages.PollMessage} */ (
|
||||
jspb.Message.getWrapperField(this, proto.messages.PollMessage, 15));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.messages.PollMessage|undefined} value
|
||||
* @return {!proto.messages.MessageRequest} returns this
|
||||
*/
|
||||
proto.messages.MessageRequest.prototype.setPoll = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 15, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.messages.MessageRequest} returns this
|
||||
*/
|
||||
proto.messages.MessageRequest.prototype.clearPoll = function() {
|
||||
return this.setPoll(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.MessageRequest.prototype.hasPoll = function() {
|
||||
return jspb.Message.getField(this, 15) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16757,6 +16852,162 @@ proto.messages.GetContactsRequest.prototype.hasPagination = function() {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* List of repeated fields within this message type.
|
||||
* @private {!Array<number>}
|
||||
* @const
|
||||
*/
|
||||
proto.messages.ChatFilter.repeatedFields_ = [1];
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.messages.ChatFilter.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.messages.ChatFilter} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.ChatFilter.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
jidsList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.messages.ChatFilter}
|
||||
*/
|
||||
proto.messages.ChatFilter.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.messages.ChatFilter;
|
||||
return proto.messages.ChatFilter.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.messages.ChatFilter} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.messages.ChatFilter}
|
||||
*/
|
||||
proto.messages.ChatFilter.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.addJids(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.messages.ChatFilter.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.messages.ChatFilter} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.ChatFilter.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getJidsList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string jids = 1;
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.getJidsList = function() {
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} value
|
||||
* @return {!proto.messages.ChatFilter} returns this
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.setJidsList = function(value) {
|
||||
return jspb.Message.setField(this, 1, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number=} opt_index
|
||||
* @return {!proto.messages.ChatFilter} returns this
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.addJids = function(value, opt_index) {
|
||||
return jspb.Message.addToRepeatedField(this, 1, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the list making it empty but non-null.
|
||||
* @return {!proto.messages.ChatFilter} returns this
|
||||
*/
|
||||
proto.messages.ChatFilter.prototype.clearJidsList = function() {
|
||||
return this.setJidsList([]);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
@@ -16790,7 +17041,8 @@ proto.messages.GetChatsRequest.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f),
|
||||
sortby: (f = msg.getSortby()) && proto.messages.SortBy.toObject(includeInstance, f),
|
||||
pagination: (f = msg.getPagination()) && proto.messages.Pagination.toObject(includeInstance, f)
|
||||
pagination: (f = msg.getPagination()) && proto.messages.Pagination.toObject(includeInstance, f),
|
||||
filter: (f = msg.getFilter()) && proto.messages.ChatFilter.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -16842,6 +17094,11 @@ proto.messages.GetChatsRequest.deserializeBinaryFromReader = function(msg, reade
|
||||
reader.readMessage(value,proto.messages.Pagination.deserializeBinaryFromReader);
|
||||
msg.setPagination(value);
|
||||
break;
|
||||
case 4:
|
||||
var value = new proto.messages.ChatFilter;
|
||||
reader.readMessage(value,proto.messages.ChatFilter.deserializeBinaryFromReader);
|
||||
msg.setFilter(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -16895,6 +17152,14 @@ proto.messages.GetChatsRequest.serializeBinaryToWriter = function(message, write
|
||||
proto.messages.Pagination.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getFilter();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
4,
|
||||
f,
|
||||
proto.messages.ChatFilter.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17009,6 +17274,43 @@ proto.messages.GetChatsRequest.prototype.hasPagination = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional ChatFilter filter = 4;
|
||||
* @return {?proto.messages.ChatFilter}
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.getFilter = function() {
|
||||
return /** @type{?proto.messages.ChatFilter} */ (
|
||||
jspb.Message.getWrapperField(this, proto.messages.ChatFilter, 4));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.messages.ChatFilter|undefined} value
|
||||
* @return {!proto.messages.GetChatsRequest} returns this
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.setFilter = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 4, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.messages.GetChatsRequest} returns this
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.clearFilter = function() {
|
||||
return this.setFilter(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.GetChatsRequest.prototype.hasFilter = function() {
|
||||
return jspb.Message.getField(this, 4) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18327,6 +18629,222 @@ proto.messages.GetLidsRequest.prototype.hasSession = function() {
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* List of repeated fields within this message type.
|
||||
* @private {!Array<number>}
|
||||
* @const
|
||||
*/
|
||||
proto.messages.PollMessage.repeatedFields_ = [2];
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.messages.PollMessage.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.messages.PollMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.PollMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
name: jspb.Message.getFieldWithDefault(msg, 1, ""),
|
||||
optionsList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f,
|
||||
multipleanswers: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.messages.PollMessage}
|
||||
*/
|
||||
proto.messages.PollMessage.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.messages.PollMessage;
|
||||
return proto.messages.PollMessage.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.messages.PollMessage} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.messages.PollMessage}
|
||||
*/
|
||||
proto.messages.PollMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setName(value);
|
||||
break;
|
||||
case 2:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.addOptions(value);
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setMultipleanswers(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.messages.PollMessage.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.messages.PollMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.messages.PollMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getName();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getOptionsList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getMultipleanswers();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
3,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string name = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.getName = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {!proto.messages.PollMessage} returns this
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.setName = function(value) {
|
||||
return jspb.Message.setProto3StringField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string options = 2;
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.getOptionsList = function() {
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 2));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} value
|
||||
* @return {!proto.messages.PollMessage} returns this
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.setOptionsList = function(value) {
|
||||
return jspb.Message.setField(this, 2, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number=} opt_index
|
||||
* @return {!proto.messages.PollMessage} returns this
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.addOptions = function(value, opt_index) {
|
||||
return jspb.Message.addToRepeatedField(this, 2, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the list making it empty but non-null.
|
||||
* @return {!proto.messages.PollMessage} returns this
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.clearOptionsList = function() {
|
||||
return this.setOptionsList([]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional bool multipleAnswers = 3;
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.getMultipleanswers = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
* @return {!proto.messages.PollMessage} returns this
|
||||
*/
|
||||
proto.messages.PollMessage.prototype.setMultipleanswers = function(value) {
|
||||
return jspb.Message.setProto3BooleanField(this, 3, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewFilter,
|
||||
ReadChatMessagesQuery,
|
||||
ReadChatMessagesResponse,
|
||||
} from '@waha/structures/chats.dto';
|
||||
@@ -72,12 +73,12 @@ import {
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageContactVcardRequest,
|
||||
MessageDestination,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkCustomPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessagePollRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextRequest,
|
||||
@@ -124,7 +125,11 @@ import {
|
||||
DeleteStatusRequest,
|
||||
TextStatus,
|
||||
} from '@waha/structures/status.dto';
|
||||
import { EnginePayload, WAMessageAckBody } from '@waha/structures/webhooks.dto';
|
||||
import {
|
||||
EnginePayload,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
import { PaginatorInMemory } from '@waha/utils/Paginator';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { onlyEvent } from '@waha/utils/reactive/ops/onlyEvent';
|
||||
@@ -181,6 +186,8 @@ enum WhatsMeowEvent {
|
||||
LABEL_ASSOCIATION_CHAT = 'events.LabelAssociationChat',
|
||||
// Events
|
||||
EVENT_MESSAGE_RESPONSE = 'gows.EventMessageResponse',
|
||||
// Polls
|
||||
POLL_VOTE_EVENT = 'gows.PollVoteEvent',
|
||||
}
|
||||
|
||||
const gRPCClientConfig = {
|
||||
@@ -472,7 +479,27 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
this.events2.get(WAHAEvents.LABEL_UPSERT).switch(labelUpsert$);
|
||||
this.events2.get(WAHAEvents.LABEL_DELETED).switch(labelDeleted$);
|
||||
|
||||
// Handle event message response
|
||||
//
|
||||
// Polls
|
||||
//
|
||||
const pollVoteEvent$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.POLL_VOTE_EVENT),
|
||||
map(this.toPollVotePayload.bind(this)),
|
||||
filter(Boolean),
|
||||
);
|
||||
|
||||
// Split into successful and failed responses
|
||||
const [pollVoteSuccess$, pollVoteFailed$] = partition(
|
||||
pollVoteEvent$,
|
||||
(payload: PollVotePayload) => !!payload.vote.selectedOptions,
|
||||
);
|
||||
|
||||
this.events2.get(WAHAEvents.POLL_VOTE).switch(pollVoteSuccess$);
|
||||
this.events2.get(WAHAEvents.POLL_VOTE_FAILED).switch(pollVoteFailed$);
|
||||
|
||||
//
|
||||
// Event Message
|
||||
//
|
||||
const eventMessageResponse$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.EVENT_MESSAGE_RESPONSE),
|
||||
map(this.toEventResponsePayload.bind(this)),
|
||||
@@ -490,6 +517,9 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
.get(WAHAEvents.EVENT_RESPONSE_FAILED)
|
||||
.switch(eventResponseFailed$);
|
||||
|
||||
//
|
||||
// Labels
|
||||
//
|
||||
// Handle label association events
|
||||
const labelAssociationEvents$ = all$.pipe(
|
||||
onlyEvent(WhatsMeowEvent.LABEL_ASSOCIATION_CHAT),
|
||||
@@ -695,6 +725,23 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return this.messageResponse(jid, data);
|
||||
}
|
||||
|
||||
async sendPoll(request: MessagePollRequest) {
|
||||
const jid = toJID(request.chatId);
|
||||
const message = new messages.MessageRequest({
|
||||
jid: jid,
|
||||
session: this.session,
|
||||
replyTo: getMessageIdFromSerialized(request.reply_to),
|
||||
poll: new messages.PollMessage({
|
||||
name: request.poll.name,
|
||||
options: request.poll.options,
|
||||
multipleAnswers: request.poll.multipleAnswers,
|
||||
}),
|
||||
});
|
||||
const response = await promisify(this.client.SendMessage)(message);
|
||||
const data = response.toObject();
|
||||
return this.messageResponse(jid, data);
|
||||
}
|
||||
|
||||
public async deleteMessage(chatId: string, messageId: string) {
|
||||
const jid = toJID(this.ensureSuffix(chatId));
|
||||
const key = parseMessageIdSerialized(messageId);
|
||||
@@ -1167,13 +1214,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
|
||||
const remoteJid = toJID(chatId);
|
||||
if (!(remoteJid in this.presences.keys())) {
|
||||
await this.subscribePresence(remoteJid);
|
||||
const jid = toJID(chatId);
|
||||
await this.subscribePresence(jid);
|
||||
if (!(jid in this.presences.keys())) {
|
||||
await sleep(1000);
|
||||
}
|
||||
const result = this.presences.get(remoteJid) || [];
|
||||
return this.toWahaPresences(remoteJid, result);
|
||||
const result = this.presences.get(jid) || [];
|
||||
return this.toWahaPresences(jid, result);
|
||||
}
|
||||
|
||||
async subscribePresence(chatId: string) {
|
||||
@@ -1468,6 +1515,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
*/
|
||||
public async getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
if (!pagination.sortBy) {
|
||||
pagination.sortBy = 'timestamp';
|
||||
@@ -1475,7 +1523,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
if (!pagination.sortOrder) {
|
||||
pagination.sortOrder = SortOrder.DESC;
|
||||
}
|
||||
const chats = await this.getChats(pagination);
|
||||
const chats = await this.getChats(pagination, filter);
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
@@ -1514,10 +1562,17 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
public async getChats(pagination: PaginationParams) {
|
||||
public async getChats(
|
||||
pagination: PaginationParams,
|
||||
filter: OverviewFilter | null = null,
|
||||
) {
|
||||
if (pagination.sortBy === ChatSortField.CONVERSATION_TIMESTAMP) {
|
||||
pagination.sortBy = 'timestamp';
|
||||
}
|
||||
let jids = [];
|
||||
if (filter?.ids && filter.ids.length > 0) {
|
||||
jids = filter.ids.map((id) => toJID(id));
|
||||
}
|
||||
const request = new messages.GetChatsRequest({
|
||||
session: this.session,
|
||||
pagination: new messages.Pagination({
|
||||
@@ -1531,6 +1586,9 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
? messages.SortBy.Order.DESC
|
||||
: messages.SortBy.Order.ASC,
|
||||
}),
|
||||
filter: new messages.ChatFilter({
|
||||
jids: jids,
|
||||
}),
|
||||
});
|
||||
const response = await promisify(this.client.GetChats)(request);
|
||||
const data = parseJsonList(response);
|
||||
@@ -1796,6 +1854,32 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
private toPollVotePayload(event: any): PollVotePayload {
|
||||
// Extract event creation message key from the message
|
||||
const creationKey = event.Message?.pollUpdateMessage.pollCreationMessageKey;
|
||||
const key: WAMessageKey = {
|
||||
remoteJid: creationKey.remoteJID,
|
||||
fromMe: creationKey.fromMe,
|
||||
id: creationKey.ID,
|
||||
participant: creationKey.participant,
|
||||
};
|
||||
const fromToParticipant = getFromToParticipant(event);
|
||||
const pollCreationKey = getDestination(key);
|
||||
return {
|
||||
poll: pollCreationKey,
|
||||
vote: {
|
||||
id: buildMessageId(event),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: event.Info.IsFromMe,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
selectedOptions: event.Votes,
|
||||
timestamp: event.Message.pollUpdateMessage.senderTimestampMS,
|
||||
},
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
private toEventResponsePayload(event: any): EventResponsePayload {
|
||||
const msg = this.toWAMessage(event);
|
||||
let response: EventResponse | null = null;
|
||||
|
||||
@@ -90,6 +90,7 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewFilter,
|
||||
PinDuration,
|
||||
ReadChatMessagesQuery,
|
||||
ReadChatMessagesResponse,
|
||||
@@ -386,7 +387,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
resubscribeToKnownPresences() {
|
||||
for (const jid in this.store.presences) {
|
||||
this.sock.presenceSubscribe(jid);
|
||||
this.subscribePresence(jid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +469,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
} else if (connection === 'open') {
|
||||
this.qr.save('');
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.resubscribeToKnownPresences();
|
||||
// Do we need to resubscribe?
|
||||
// Ideally not, we need to explicitly call interesting
|
||||
// jids every 1 minute
|
||||
// this.resubscribeToKnownPresences();
|
||||
return;
|
||||
} else if (connection === 'close') {
|
||||
this.qr.save('');
|
||||
@@ -616,7 +620,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
for (const message of messages) {
|
||||
const content = normalizeMessageContent(message.message);
|
||||
if (!content.pollUpdateMessage) {
|
||||
if (!content?.pollUpdateMessage) {
|
||||
continue;
|
||||
}
|
||||
const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey;
|
||||
@@ -1127,8 +1131,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
|
||||
public async getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
const chats = await this.store.getChats(pagination, false);
|
||||
// Convert customer format IDs to JID format if filter is provided
|
||||
let jidFilter;
|
||||
if (filter?.ids && filter.ids.length > 0) {
|
||||
jidFilter = {
|
||||
ids: filter.ids.map((id) => toJID(id)),
|
||||
};
|
||||
}
|
||||
|
||||
const chats = await this.store.getChats(pagination, false, jidFilter);
|
||||
// Remove unreadCount, it's not ready yet
|
||||
chats.forEach((chat) => delete chat.unreadCount);
|
||||
|
||||
@@ -1512,14 +1525,14 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
|
||||
const remoteJid = toJID(chatId);
|
||||
if (!(remoteJid in this.store.presences)) {
|
||||
this.store.presences[remoteJid] = {};
|
||||
await this.sock.presenceSubscribe(remoteJid);
|
||||
const jid = toJID(chatId);
|
||||
await this.subscribePresence(jid);
|
||||
if (!(jid in this.store.presences)) {
|
||||
this.store.presences[jid] = {};
|
||||
await sleep(1000);
|
||||
}
|
||||
const result = this.store.presences[remoteJid];
|
||||
return this.toWahaPresences(remoteJid, result);
|
||||
const result = this.store.presences[jid];
|
||||
return this.toWahaPresences(jid, result);
|
||||
}
|
||||
|
||||
public subscribePresence(id: string): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Chat } from '@adiwajshing/baileys';
|
||||
import { OverviewFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export interface IChatRepository {
|
||||
@@ -9,6 +10,7 @@ export interface IChatRepository {
|
||||
getAllWithMessages(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<Chat[]>;
|
||||
|
||||
getById(id: string): Promise<Chat | null>;
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
} from '@adiwajshing/baileys';
|
||||
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { Label } from '@adiwajshing/baileys/lib/Types/Label';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
OverviewFilter,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import {
|
||||
LimitOffsetParams,
|
||||
@@ -32,7 +35,11 @@ export interface INowebStore {
|
||||
|
||||
getMessageById(chatId: string, messageId: string): Promise<any>;
|
||||
|
||||
getChats(pagination: PaginationParams, broadcast: boolean): Promise<Chat[]>;
|
||||
getChats(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<Chat[]>;
|
||||
|
||||
getChat(jid: string): Promise<Chat | null>;
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import { IGroupRepository } from '@waha/core/engines/noweb/store/IGroupRepository';
|
||||
import { ILabelAssociationRepository } from '@waha/core/engines/noweb/store/ILabelAssociationsRepository';
|
||||
import { ILabelsRepository } from '@waha/core/engines/noweb/store/ILabelsRepository';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
OverviewFilter,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import {
|
||||
LimitOffsetParams,
|
||||
@@ -231,6 +234,9 @@ export class NowebPersistentStore implements INowebStore {
|
||||
for (const update of updates) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const jid = jidNormalizedUser(update.key.remoteJid!);
|
||||
if (!update.key.id) {
|
||||
continue;
|
||||
}
|
||||
const message = await this.messagesRepo.getByJidById(jid, update.key.id);
|
||||
if (!message) {
|
||||
this.logger.warn(
|
||||
@@ -521,10 +527,14 @@ export class NowebPersistentStore implements INowebStore {
|
||||
return this.messagesRepo.getByJidById(chatId, messageId);
|
||||
}
|
||||
|
||||
getChats(pagination: PaginationParams, broadcast: boolean): Promise<Chat[]> {
|
||||
getChats(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<Chat[]> {
|
||||
pagination.sortBy ||= 'conversationTimestamp';
|
||||
pagination.sortOrder ||= SortOrder.DESC;
|
||||
return this.chatRepo.getAllWithMessages(pagination, broadcast);
|
||||
return this.chatRepo.getAllWithMessages(pagination, broadcast, filter);
|
||||
}
|
||||
|
||||
async getChat(jid: string): Promise<Chat | null> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Chat } from '@adiwajshing/baileys';
|
||||
import { SqlKVRepository } from '@waha/core/storage/sql/SqlKVRepository';
|
||||
import { OverviewFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export class SqlChatMethods {
|
||||
@@ -8,15 +9,23 @@ export class SqlChatMethods {
|
||||
async getAllWithMessages(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<Chat[]> {
|
||||
// Get chats with conversationTimestamp is not Null
|
||||
let query = this.repository.select().whereNotNull('conversationTimestamp');
|
||||
|
||||
if (!broadcast) {
|
||||
// filter out chat by id if it ends at @newsletter or @broadcast
|
||||
query = query
|
||||
.andWhereNot('id', 'like', '%@broadcast')
|
||||
.andWhereNot('id', 'like', '%@newsletter');
|
||||
}
|
||||
|
||||
// Filter by IDs if provided
|
||||
if (filter?.ids && filter.ids.length > 0) {
|
||||
query = query.whereIn('id', filter.ids);
|
||||
}
|
||||
|
||||
query = this.repository.pagination(query, pagination);
|
||||
return await this.repository.all(query);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Chat } from '@adiwajshing/baileys';
|
||||
import { NowebChatSchema } from '@waha/core/engines/noweb/store/schemas';
|
||||
import { SqlChatMethods } from '@waha/core/engines/noweb/store/sql/SqlChatMethods';
|
||||
import { OverviewFilter } from '@waha/structures/chats.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import { KnexPaginator } from '@waha/utils/Paginator';
|
||||
|
||||
@@ -28,7 +29,8 @@ export class Sqlite3ChatRepository
|
||||
getAllWithMessages(
|
||||
pagination: PaginationParams,
|
||||
broadcast: boolean,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<Chat[]> {
|
||||
return this.methods.getAllWithMessages(pagination, broadcast);
|
||||
return this.methods.getAllWithMessages(pagination, broadcast, filter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Expose a function to the page if it does not exist
|
||||
*
|
||||
* NOTE:
|
||||
* Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher
|
||||
* using page.removeExposedFunction
|
||||
* https://pptr.dev/api/puppeteer.page.removeExposedFunction
|
||||
*
|
||||
* @param {import(puppeteer).Page} page
|
||||
* @param {string} name
|
||||
* @param {Function} fn
|
||||
*/
|
||||
export async function exposeFunctionIfAbsent(page, name, fn) {
|
||||
const exist = await page.evaluate((name) => {
|
||||
return !!window[name];
|
||||
}, name);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
await page.exposeFunction(name, fn);
|
||||
}
|
||||
@@ -99,6 +99,18 @@ export class WPage extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async reload(options?: any): Promise<any> {
|
||||
try {
|
||||
return await this.page.reload(options);
|
||||
} catch (err) {
|
||||
this.emit(PAGE_CALL_ERROR_EVENT, {
|
||||
method: 'reload',
|
||||
error: err,
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||
if (event === PAGE_CALL_ERROR_EVENT) {
|
||||
super.on(event, listener);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { exposeFunctionIfAbsent } from '@waha/core/engines/webjs/Puppeteer';
|
||||
import { WebJSPresence } from '@waha/core/engines/webjs/types';
|
||||
import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
|
||||
import { Label } from '@waha/structures/labels.dto';
|
||||
import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import { TextStatus } from '@waha/structures/status.dto';
|
||||
import { sleep } from '@waha/utils/promiseTimeout';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as lodash from 'lodash';
|
||||
import { Page } from 'puppeteer';
|
||||
import { Client, Events } from 'whatsapp-web.js';
|
||||
import { Message } from 'whatsapp-web.js/src/structures';
|
||||
import { exposeFunctionIfAbsent } from 'whatsapp-web.js/src/util/Puppeter';
|
||||
|
||||
import { CallErrorEvent, PAGE_CALL_ERROR_EVENT, WPage } from './WPage';
|
||||
|
||||
@@ -55,6 +57,26 @@ export class WebjsClientCore extends Client {
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @result indicating whether the UX fresh look was successfully hidden.
|
||||
*/
|
||||
hideUXFreshLook(): Promise<boolean> {
|
||||
return this.pupPage.evaluate(() => {
|
||||
const WAWebUserPrefsUiRefresh = window.require('WAWebUserPrefsUiRefresh');
|
||||
if (!WAWebUserPrefsUiRefresh) {
|
||||
return false;
|
||||
}
|
||||
if (WAWebUserPrefsUiRefresh.getUiRefreshNuxAcked()) {
|
||||
return false;
|
||||
}
|
||||
WAWebUserPrefsUiRefresh.incrementNuxViewCount();
|
||||
WAWebUserPrefsUiRefresh.setUiRefreshNuxAcked(true);
|
||||
const WAWebModalManager = window.require('WAWebModalManager');
|
||||
WAWebModalManager.ModalManager.close();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async attachCustomEventListeners() {
|
||||
await exposeFunctionIfAbsent(
|
||||
this.pupPage,
|
||||
@@ -137,7 +159,7 @@ export class WebjsClientCore extends Client {
|
||||
}, label);
|
||||
}
|
||||
|
||||
async getChats(pagination?: PaginationParams) {
|
||||
async getChats(pagination?: PaginationParams, filter?: { ids?: string[] }) {
|
||||
if (lodash.isEmpty(pagination)) {
|
||||
return await super.getChats();
|
||||
}
|
||||
@@ -146,10 +168,14 @@ export class WebjsClientCore extends Client {
|
||||
pagination.limit ||= Infinity;
|
||||
pagination.offset ||= 0;
|
||||
|
||||
const chats = await this.pupPage.evaluate(async (pagination) => {
|
||||
// @ts-ignore
|
||||
return await window.WAHA.getChats(pagination);
|
||||
}, pagination);
|
||||
const chats = await this.pupPage.evaluate(
|
||||
async (pagination, filter) => {
|
||||
// @ts-ignore
|
||||
return await window.WAHA.getChats(pagination, filter);
|
||||
},
|
||||
pagination,
|
||||
filter,
|
||||
);
|
||||
|
||||
return chats.map((chat) => ChatFactory.create(this, chat));
|
||||
}
|
||||
@@ -336,4 +362,55 @@ export class WebjsClientCore extends Client {
|
||||
}, phoneNumber)) as any;
|
||||
return lid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presences methods
|
||||
*/
|
||||
public async subscribePresence(chatId: string): Promise<void> {
|
||||
await this.pupPage.evaluate(async (chatId) => {
|
||||
const d = require;
|
||||
const WidFactory = d('WAWebWidFactory');
|
||||
|
||||
const wid = WidFactory.createWidFromWidLike(chatId);
|
||||
const chat = d('WAWebChatCollection').ChatCollection.get(wid);
|
||||
const tc = chat == null ? void 0 : chat.getTcToken();
|
||||
await d('WAWebContactPresenceBridge').subscribePresence(wid, tc);
|
||||
}, chatId);
|
||||
}
|
||||
|
||||
private async getCurrentPresence(chatId: string): Promise<WebJSPresence[]> {
|
||||
const result = await this.pupPage.evaluate(async (chatId) => {
|
||||
const d = require;
|
||||
const WidFactory = d('WAWebWidFactory');
|
||||
const PresenceCollection = d(
|
||||
'WAWebPresenceCollection',
|
||||
).PresenceCollection;
|
||||
const wid = WidFactory.createWidFromWidLike(chatId);
|
||||
const presence = PresenceCollection.get(wid);
|
||||
if (!presence) {
|
||||
return [];
|
||||
}
|
||||
let chatstates = [];
|
||||
if (chatId.endsWith('@c.us')) {
|
||||
chatstates = [presence.chatstate];
|
||||
} else {
|
||||
chatstates = presence.chatstates.getModelsArray();
|
||||
}
|
||||
return chatstates.map((chatstate) => {
|
||||
return {
|
||||
participant: chatstate.id._serialized,
|
||||
lastSeen: chatstate.t,
|
||||
state: chatstate.type,
|
||||
};
|
||||
});
|
||||
}, chatId);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getPresence(chatId: string): Promise<WebJSPresence[]> {
|
||||
await this.sendPresenceAvailable();
|
||||
await this.subscribePresence(chatId);
|
||||
await sleep(3_000);
|
||||
return await this.getCurrentPresence(chatId);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,14 @@ exports.LoadWAHA = () => {
|
||||
'WAWebBizLabelEditingAction',
|
||||
);
|
||||
|
||||
window.WAHA.getChats = async (pagination) => {
|
||||
window.WAHA.getChats = async (pagination, filter) => {
|
||||
let chats = window.Store.Chat.getModelsArray().slice();
|
||||
|
||||
// Filter chats by IDs if filter is provided
|
||||
if (filter && filter.ids && filter.ids.length > 0) {
|
||||
chats = chats.filter((chat) => filter.ids.includes(chat.id._serialized));
|
||||
}
|
||||
|
||||
const paginator = new window.Paginator(pagination);
|
||||
chats = paginator.apply(chats);
|
||||
const chatPromises = chats.map((chat) => window.WWebJS.getChatModel(chat));
|
||||
|
||||
@@ -23,7 +23,7 @@ interface Me {
|
||||
lid?: string;
|
||||
}
|
||||
|
||||
function jid(field: any) {
|
||||
export function jid(field: any) {
|
||||
if (!field) {
|
||||
return field;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { BinaryNode, WAPresence } from '@adiwajshing/baileys';
|
||||
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import { WAHAPresenceStatus } from '@waha/structures/enums.dto';
|
||||
import { WAHAChatPresences } from '@waha/structures/presence.dto';
|
||||
|
||||
import { jid } from './ack.webjs';
|
||||
|
||||
export function TagPresenceToPresence(node: BinaryNode): WAHAChatPresences {
|
||||
const { attrs } = node;
|
||||
const id = jid(attrs.from);
|
||||
const state =
|
||||
attrs.type === 'unavailable'
|
||||
? WAHAPresenceStatus.OFFLINE
|
||||
: WAHAPresenceStatus.ONLINE;
|
||||
const lastSeen = attrs.last && attrs.last !== 'deny' ? +attrs.last : null;
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
presences: [
|
||||
{
|
||||
participant: toCusFormat(id),
|
||||
lastKnownPresence: state,
|
||||
lastSeen: lastSeen,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function TagChatstateToPresence(node: BinaryNode): WAHAChatPresences {
|
||||
const { attrs, content } = node;
|
||||
const id = jid(attrs.from);
|
||||
const participant = jid(attrs.participant) || jid(attrs.from);
|
||||
|
||||
const firstChild = content[0] as BinaryNode;
|
||||
|
||||
const type = firstChild.tag as WAPresence;
|
||||
let status = WAHAPresenceStatus.OFFLINE;
|
||||
|
||||
switch (type) {
|
||||
case 'unavailable':
|
||||
status = WAHAPresenceStatus.OFFLINE;
|
||||
break;
|
||||
case 'available':
|
||||
status = WAHAPresenceStatus.ONLINE;
|
||||
break;
|
||||
case 'paused':
|
||||
status = WAHAPresenceStatus.PAUSED;
|
||||
break;
|
||||
case 'composing':
|
||||
status = WAHAPresenceStatus.TYPING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstChild.attrs?.media === 'audio') {
|
||||
status = WAHAPresenceStatus.RECORDING;
|
||||
}
|
||||
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
presences: [
|
||||
{
|
||||
participant: toCusFormat(participant),
|
||||
lastKnownPresence: status,
|
||||
lastSeen: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,10 @@ import {
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/webjs/groups.webjs';
|
||||
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
|
||||
import {
|
||||
TagChatstateToPresence,
|
||||
TagPresenceToPresence,
|
||||
} from '@waha/core/engines/webjs/presence';
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
CallErrorEvent,
|
||||
@@ -55,6 +59,7 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
OverviewFilter,
|
||||
ReadChatMessagesQuery,
|
||||
ReadChatMessagesResponse,
|
||||
} from '@waha/structures/chats.dto';
|
||||
@@ -97,10 +102,10 @@ import { LidToPhoneNumber } from '@waha/structures/lids.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
MessageSource,
|
||||
WAMessage,
|
||||
WAMessageReaction,
|
||||
} from '@waha/structures/responses.dto';
|
||||
WAHAChatPresences,
|
||||
WAHAPresenceData,
|
||||
} from '@waha/structures/presence.dto';
|
||||
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
|
||||
import {
|
||||
@@ -113,7 +118,7 @@ import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
|
||||
import * as lodash from 'lodash';
|
||||
import { ProtocolError } from 'puppeteer';
|
||||
import { filter, fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { filter, fromEvent, merge, mergeMap, Observable, share } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import {
|
||||
AuthStrategy,
|
||||
@@ -128,13 +133,14 @@ import {
|
||||
Label as WEBJSLabel,
|
||||
Location,
|
||||
Message,
|
||||
MessageAck,
|
||||
MessageMedia,
|
||||
Reaction,
|
||||
WAState,
|
||||
} from 'whatsapp-web.js';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
|
||||
import { WAJSPresenceChatStateType, WebJSPresence } from './types';
|
||||
|
||||
export interface WebJSConfig {
|
||||
webVersion?: string;
|
||||
cacheType: 'local' | 'none';
|
||||
@@ -261,7 +267,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
.then(() => {
|
||||
// Listen for browser disconnected event
|
||||
this.whatsapp.pupBrowser.on('disconnected', () => {
|
||||
this.logger.error('The browser has been disconnected');
|
||||
if (this.shouldRestart) {
|
||||
this.logger.error('The browser has been disconnected');
|
||||
} else {
|
||||
this.logger.info('The browser has been disconnected');
|
||||
}
|
||||
this.failed();
|
||||
});
|
||||
|
||||
@@ -426,6 +436,22 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.logger.info(`Session '${this.name}' is ready!`);
|
||||
});
|
||||
|
||||
//
|
||||
// Temp fix for hiding "Fresh look" modal
|
||||
// https://github.com/devlikeapro/waha/issues/987
|
||||
//
|
||||
this.whatsapp.on(Events.READY, async () => {
|
||||
try {
|
||||
const hidden = await this.whatsapp.hideUXFreshLook();
|
||||
if (hidden) {
|
||||
this.logger.info('"Fresh look" modal has been hidden');
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn('Failed to hide "Fresh look" modal');
|
||||
this.logger.warn(err, err.stack);
|
||||
}
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.AUTHENTICATED, (args) => {
|
||||
this.qr.save('');
|
||||
this.logger.info({ args: args }, `Session has been authenticated!`);
|
||||
@@ -678,7 +704,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
getChats(pagination: PaginationParams) {
|
||||
getChats(pagination: PaginationParams, filter: OverviewFilter | null = null) {
|
||||
switch (pagination.sortBy) {
|
||||
case ChatSortField.ID:
|
||||
pagination.sortBy = 'id._serialized';
|
||||
@@ -687,18 +713,19 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
pagination.sortBy = 't';
|
||||
break;
|
||||
}
|
||||
return this.whatsapp.getChats(pagination);
|
||||
return this.whatsapp.getChats(pagination, filter);
|
||||
}
|
||||
|
||||
public async getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
filter?: OverviewFilter,
|
||||
): Promise<ChatSummary[]> {
|
||||
pagination = {
|
||||
...pagination,
|
||||
sortBy: ChatSortField.CONVERSATION_TIMESTAMP,
|
||||
sortOrder: SortOrder.DESC,
|
||||
};
|
||||
const chats = await this.getChats(pagination);
|
||||
const chats = await this.getChats(pagination, filter);
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
@@ -1262,6 +1289,52 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
}
|
||||
|
||||
public getPresences(): Promise<WAHAChatPresences[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getPresence(id: string): Promise<WAHAChatPresences> {
|
||||
const chatId = toCusFormat(id);
|
||||
const presences = await this.whatsapp.getPresence(chatId);
|
||||
return this.toWahaPresences(chatId, presences);
|
||||
}
|
||||
|
||||
public async subscribePresence(id: string): Promise<any> {
|
||||
const chatId = toCusFormat(id);
|
||||
await this.whatsapp.subscribePresence(chatId);
|
||||
}
|
||||
|
||||
private toWahaPresences(
|
||||
chatId: string,
|
||||
data: WebJSPresence[],
|
||||
): WAHAChatPresences {
|
||||
const presences: WAHAPresenceData[] = data.map((presence) => {
|
||||
let status: WAHAPresenceStatus = WAHAPresenceStatus.OFFLINE;
|
||||
switch (presence.state) {
|
||||
case WAJSPresenceChatStateType.AVAILABLE:
|
||||
status = WAHAPresenceStatus.ONLINE;
|
||||
break;
|
||||
case WAJSPresenceChatStateType.UNAVAILABLE:
|
||||
status = WAHAPresenceStatus.OFFLINE;
|
||||
break;
|
||||
case WAJSPresenceChatStateType.TYPING:
|
||||
status = WAHAPresenceStatus.TYPING;
|
||||
break;
|
||||
case WAJSPresenceChatStateType.RECORDING_AUDIO:
|
||||
status = WAHAPresenceStatus.RECORDING;
|
||||
}
|
||||
return {
|
||||
participant: presence.participant,
|
||||
lastSeen: presence.lastSeen || null,
|
||||
lastKnownPresence: status,
|
||||
};
|
||||
});
|
||||
return {
|
||||
id: toCusFormat(chatId),
|
||||
presences: presences,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Status methods
|
||||
*/
|
||||
@@ -1391,6 +1464,22 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
const stateChanged$ = fromEvent(this.whatsapp, Events.STATE_CHANGED);
|
||||
this.events2.get(WAHAEvents.STATE_CHANGE).switch(stateChanged$);
|
||||
|
||||
//
|
||||
// Presence
|
||||
//
|
||||
const tagPresenceNode$ = fromEvent(this.whatsapp, Events.TAG_PRESENCE);
|
||||
const presences$ = tagPresenceNode$.pipe(
|
||||
map(TagPresenceToPresence),
|
||||
filter(Boolean),
|
||||
);
|
||||
const tagChatstateNode$ = fromEvent(this.whatsapp, 'tag:chatstate');
|
||||
const chatstatePresences$ = tagChatstateNode$.pipe(
|
||||
map(TagChatstateToPresence),
|
||||
filter(Boolean),
|
||||
);
|
||||
const presenceUpdate$ = merge(presences$, chatstatePresences$);
|
||||
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(presenceUpdate$);
|
||||
|
||||
//
|
||||
// Groups
|
||||
//
|
||||
@@ -1588,10 +1677,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
public async getEngineInfo() {
|
||||
// Add 1 seconds timeout
|
||||
if (!this.whatsapp || !this.whatsapp.pupPage) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
WWebVersion: await this.whatsapp?.getWWebVersion(),
|
||||
state: await this.whatsapp?.getState(),
|
||||
WWebVersion: await this.whatsapp.getWWebVersion(),
|
||||
state: await this.whatsapp.getState(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export enum WAJSPresenceChatStateType {
|
||||
AVAILABLE = 'available', // User is online
|
||||
UNAVAILABLE = 'unavailable', // User went offline (may include `t`)
|
||||
TYPING = 'typing', // User is typing text
|
||||
RECORDING_AUDIO = 'recording_audio', // User is recording a voice message
|
||||
}
|
||||
|
||||
export interface WebJSPresence {
|
||||
participant: string;
|
||||
lastSeen?: number;
|
||||
state: WAJSPresenceChatStateType;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { AvailableInPlusVersion } from '../exceptions';
|
||||
|
||||
export interface IMediaConverter {
|
||||
voice(content: Buffer): Promise<Buffer>;
|
||||
video(content: Buffer): Promise<Buffer>;
|
||||
}
|
||||
|
||||
export class CoreMediaConverter implements IMediaConverter {
|
||||
video(content: Buffer): Promise<Buffer> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
voice(content: Buffer): Promise<Buffer> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum WAMimeType {
|
||||
VOICE = 'audio/ogg; codecs=opus',
|
||||
VIDEO = 'video/mp4',
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as crypto from 'crypto';
|
||||
import { safeJoin } from '@waha/utils/files';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { LocalStore } from './LocalStore';
|
||||
@@ -33,14 +32,14 @@ export class LocalStoreCore extends LocalStore {
|
||||
* Get the directory where all the engines and sessions are stored
|
||||
*/
|
||||
getBaseDirectory() {
|
||||
return path.join(this.baseDirectory);
|
||||
return path.resolve(this.baseDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory where the engine sessions are stored
|
||||
*/
|
||||
getEngineDirectory() {
|
||||
return path.join(this.baseDirectory, this.engine);
|
||||
return safeJoin(this.baseDirectory, this.engine);
|
||||
}
|
||||
|
||||
getSessionDirectory(name: string): string {
|
||||
@@ -48,17 +47,17 @@ export class LocalStoreCore extends LocalStore {
|
||||
}
|
||||
|
||||
getFilePath(session: string, file: string): string {
|
||||
return path.join(this.getSessionDirectory(session), file);
|
||||
return safeJoin(this.getSessionDirectory(session), file);
|
||||
}
|
||||
|
||||
protected getDirectoryPath(name: string): string {
|
||||
return path.join(this.getEngineDirectory(), name);
|
||||
return safeJoin(this.getEngineDirectory(), name);
|
||||
}
|
||||
|
||||
getWAHADatabase(): any {
|
||||
if (!this.db) {
|
||||
const engineDir = this.getEngineDirectory();
|
||||
const database = path.join(engineDir, 'waha.sqlite3');
|
||||
const database = safeJoin(engineDir, 'waha.sqlite3');
|
||||
this.db = new Database(database);
|
||||
this.db.pragma('journal_mode = WAL;');
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ function getRefSchemaPaths(models) {
|
||||
/**
|
||||
* Decorator to add a file accept header to the swagger documentation
|
||||
*/
|
||||
export function ApiFileAcceptHeader(...models) {
|
||||
export function ApiFileAcceptHeader(mimetype: string, ...models) {
|
||||
models = models.length ? models : [Base64File];
|
||||
return applyDecorators(
|
||||
// Add extra models, otherwise it'll give a error
|
||||
@@ -20,7 +20,7 @@ export function ApiFileAcceptHeader(...models) {
|
||||
ApiResponse({
|
||||
status: 200,
|
||||
content: {
|
||||
'image/png': {
|
||||
[mimetype]: {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
|
||||
@@ -21,6 +21,11 @@ import { Base64File } from '../structures/files.dto';
|
||||
export class BufferResponseInterceptor<T>
|
||||
implements NestInterceptor<Buffer, Base64File | StreamableFile>
|
||||
{
|
||||
constructor(
|
||||
private mimetype: string,
|
||||
private filename: string = null,
|
||||
) {}
|
||||
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
@@ -28,27 +33,35 @@ export class BufferResponseInterceptor<T>
|
||||
const ctx = context.switchToHttp();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const response = ctx.getResponse<Response>();
|
||||
return next.handle().pipe(map(processBuffer));
|
||||
|
||||
function processBuffer(buffer: Buffer) {
|
||||
// Check buffer is Buffer
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
return buffer;
|
||||
}
|
||||
return next
|
||||
.handle()
|
||||
.pipe(map((buffer) => this.processBuffer(buffer, request, response)));
|
||||
}
|
||||
|
||||
const accept = request.headers['accept'];
|
||||
if (accept == 'application/json') {
|
||||
return {
|
||||
mimetype: 'image/png',
|
||||
data: buffer.toString('base64'),
|
||||
};
|
||||
}
|
||||
const file = new StreamableFile(buffer);
|
||||
response.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
return file;
|
||||
processBuffer(buffer: Buffer, request: Request, response: Response) {
|
||||
// Check buffer is Buffer
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const accept = request.headers['accept'];
|
||||
if (accept == 'application/json') {
|
||||
return {
|
||||
mimetype: this.mimetype,
|
||||
data: buffer.toString('base64'),
|
||||
};
|
||||
}
|
||||
const file = new StreamableFile(buffer);
|
||||
response.set({
|
||||
'Content-Type': this.mimetype,
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
if (this.filename) {
|
||||
response.set({
|
||||
'Content-Disposition': `attachment; filename="${this.filename}"`,
|
||||
});
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,17 @@ import {
|
||||
PaginationParams,
|
||||
} from '@waha/structures/pagination.dto';
|
||||
import { ChatIdProperty } from '@waha/structures/properties.dto';
|
||||
import { SessionConfig } from '@waha/structures/sessions.dto';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsBoolean, IsEnum, IsNumber, IsOptional } from 'class-validator';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
@@ -187,6 +196,29 @@ export class OverviewPaginationParams extends LimitOffsetParams {
|
||||
limit?: number = 20;
|
||||
}
|
||||
|
||||
export class OverviewFilter {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@Transform(({ value }) => (Array.isArray(value) ? value : [value]))
|
||||
@ApiProperty({
|
||||
description: 'Filter by chat ids',
|
||||
required: false,
|
||||
example: ['111111111@c.us'],
|
||||
})
|
||||
ids?: string[];
|
||||
}
|
||||
|
||||
export class OverviewBodyRequest {
|
||||
@ValidateNested()
|
||||
@Type(() => OverviewPaginationParams)
|
||||
pagination: OverviewPaginationParams;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => OverviewFilter)
|
||||
filter: OverviewFilter;
|
||||
}
|
||||
|
||||
export class ChatSummary {
|
||||
id: string;
|
||||
name: string | null;
|
||||
|
||||
@@ -32,7 +32,11 @@ import {
|
||||
VoiceBinaryFile,
|
||||
VoiceRemoteFile,
|
||||
} from './files.dto';
|
||||
import { ChatIdProperty, ReplyToProperty } from './properties.dto';
|
||||
import {
|
||||
ChatIdProperty,
|
||||
ConvertApiProperty,
|
||||
ReplyToProperty,
|
||||
} from './properties.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
@@ -320,6 +324,9 @@ export class MessageVoiceRequest extends ChatRequest {
|
||||
|
||||
@ReplyToProperty()
|
||||
reply_to?: string;
|
||||
|
||||
@ConvertApiProperty()
|
||||
convert: boolean;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
|
||||
@@ -346,6 +353,9 @@ export class MessageVideoRequest extends ChatRequest {
|
||||
example: false,
|
||||
})
|
||||
asNote?: boolean;
|
||||
|
||||
@ConvertApiProperty()
|
||||
convert: boolean;
|
||||
}
|
||||
|
||||
export class MessageLinkPreviewRequest extends ChatRequest {
|
||||
@@ -420,6 +430,7 @@ export class MessageDestination {
|
||||
to: string;
|
||||
from: string;
|
||||
fromMe: boolean;
|
||||
participant?: string;
|
||||
}
|
||||
|
||||
export class MessageButtonReply extends ChatRequest {
|
||||
|
||||
@@ -35,3 +35,34 @@ export class WAMedia {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
error?: object;
|
||||
}
|
||||
|
||||
export class FileDTO {
|
||||
@ApiProperty({
|
||||
description: 'The URL for the file',
|
||||
})
|
||||
url?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Base64 content of the file',
|
||||
example: null,
|
||||
})
|
||||
data?: string;
|
||||
}
|
||||
|
||||
export class VoiceFileDTO extends FileDTO {
|
||||
@ApiProperty({
|
||||
description: 'The URL for the voice file',
|
||||
example:
|
||||
process.env.WHATSAPP_SWAGGER_VIDEO_EXAMPLE_URL ||
|
||||
'https://github.com/devlikeapro/waha/raw/core/examples/voice.mp3',
|
||||
})
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class VideoFileDTO extends FileDTO {
|
||||
@ApiProperty({
|
||||
description: 'The URL for the video file',
|
||||
example: 'https://github.com/devlikeapro/waha/raw/core/examples/video.mp4',
|
||||
})
|
||||
url?: string;
|
||||
}
|
||||
@@ -30,6 +30,14 @@ function ReplyToProperty() {
|
||||
});
|
||||
}
|
||||
|
||||
export function ConvertApiProperty() {
|
||||
return ApiProperty({
|
||||
description:
|
||||
'Convert the input file to the required format using ffmpeg before sending',
|
||||
example: true,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
ChatIdProperty,
|
||||
MessageIdOnlyProperty,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { ConvertApiProperty } from '@waha/structures/properties.dto';
|
||||
|
||||
import {
|
||||
BinaryFile,
|
||||
@@ -63,6 +64,9 @@ export class VoiceStatus extends StatusRequest {
|
||||
file: VoiceRemoteFile | VoiceBinaryFile;
|
||||
|
||||
backgroundColor: string = '#38b42f';
|
||||
|
||||
@ConvertApiProperty()
|
||||
convert: boolean;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
|
||||
@@ -76,6 +80,9 @@ export class VideoStatus extends StatusRequest {
|
||||
file: VideoRemoteFile | VideoBinaryFile;
|
||||
|
||||
caption?: string;
|
||||
|
||||
@ConvertApiProperty()
|
||||
convert: boolean;
|
||||
}
|
||||
|
||||
export class DeleteStatusRequest extends StatusRequest {
|
||||
|
||||
@@ -87,6 +87,7 @@ export class PollVote extends MessageDestination {
|
||||
export class PollVotePayload {
|
||||
vote: PollVote;
|
||||
poll: MessageDestination;
|
||||
_data?: any;
|
||||
}
|
||||
|
||||
export class WAMessageRevokedBody {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as path from 'path';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs-extra');
|
||||
|
||||
@@ -9,3 +11,27 @@ export async function fileExists(filepath: string) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function safeJoin(base: string, input: string): string {
|
||||
base = path.resolve(base);
|
||||
|
||||
if (!input || typeof input !== 'string') {
|
||||
throw new Error('Invalid path');
|
||||
}
|
||||
|
||||
if (input.startsWith('~') || path.isAbsolute(input)) {
|
||||
throw new Error('Home or absolute paths not allowed');
|
||||
}
|
||||
|
||||
// handles slashes safely
|
||||
const joined = path.join(base, input);
|
||||
// normalize to an absolute path
|
||||
const resolved = path.resolve(joined);
|
||||
|
||||
// Prevent escape outside base dir
|
||||
if (!resolved.startsWith(base + path.sep)) {
|
||||
throw new Error('Access outside base dir not allowed');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
+3
-1
@@ -33,9 +33,11 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.5.4',
|
||||
version: '2025.6.2',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser:
|
||||
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
|
||||
};
|
||||
|
||||
export const IsChrome = VERSION.browser?.includes('chrome');
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "v1.0.6"
|
||||
"ref": "v1.0.8"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
|
||||
Reference in new issue
Block a user