Compare commits

..
6 Commits
Author SHA1 Message Date
devlikepro 9362c5d6ae [core] 2025.6.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-11 15:41:56 +07:00
devlikepro 117a9108c0 [core] puppeter 24.10.0 2025-06-11 15:41:56 +07:00
devlikepro 5214e461f2 [core] chrome 137.0.7151.55 2025-06-11 15:41:56 +07:00
devlikepro 9bdb178b7d [core] Use github examples 2025-06-11 15:41:56 +07:00
devlikepro 9c526ffdba [core] Add converting voice and video files
fix #724
2025-06-11 15:41:56 +07:00
devlikepro f80a74edd1 [core] Add avi and mp3 in examples 2025-06-11 12:20:58 +07:00
20 changed files with 263 additions and 69 deletions

No files matched your search

+1 -1
View File
@@ -131,7 +131,7 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Install Chrome
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="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 \
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -97,7 +97,7 @@
"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"
},
+2 -2
View File
@@ -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,
+83
View File
@@ -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 };
+2 -2
View File
@@ -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,
+1
View File
@@ -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({
+21 -4
View File
@@ -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 {
@@ -24,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';
@@ -109,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';
@@ -168,6 +174,8 @@ export abstract class WhatsappSession {
stdTTL: 10 * 60, // 10 minutes
});
public mediaConverter: IMediaConverter = new CoreMediaConverter();
public constructor({
name,
printQR,
@@ -993,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) {
+2
View File
@@ -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 = [
{
+16
View File
@@ -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();
}
}
+4
View File
@@ -0,0 +1,4 @@
export enum WAMimeType {
VOICE = 'audio/ogg; codecs=opus',
VIDEO = 'video/mp4',
}
+2 -2
View File
@@ -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',
+32 -19
View File
@@ -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;
}
}
+11 -1
View File
@@ -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 {
+31
View File
@@ -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;
}
+8
View File
@@ -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,
+7
View File
@@ -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 {
+3 -1
View File
@@ -33,9 +33,11 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.6.1',
version: '2025.6.2',
engine: getEngineName(),
tier: getWAHAVersion(),
browser:
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
};
export const IsChrome = VERSION.browser?.includes('chrome');
+36 -36
View File
@@ -2650,20 +2650,20 @@ __metadata:
languageName: node
linkType: hard
"@puppeteer/browsers@npm:2.10.3":
version: 2.10.3
resolution: "@puppeteer/browsers@npm:2.10.3"
"@puppeteer/browsers@npm:2.10.5":
version: 2.10.5
resolution: "@puppeteer/browsers@npm:2.10.5"
dependencies:
debug: ^4.4.0
debug: ^4.4.1
extract-zip: ^2.0.1
progress: ^2.0.3
proxy-agent: ^6.5.0
semver: ^7.7.1
semver: ^7.7.2
tar-fs: ^3.0.8
yargs: ^17.7.2
bin:
browsers: lib/cjs/main-cli.js
checksum: 34bd9c915a6c05577e8bbe0ae3118b275a467cc53233843d531663a716e6bcd7ab36cb320ae8fed31872f0706beeb24afb975576f11ecd80d1937e8e44f2c473
checksum: d294a30ed91f58ad3317ccc18054a1785542e21284d3ab455364bf734bb8a1bfa93d4434dba530ff4225491908f427ca71e5b042e77d1c8fa23e57416c185aef
languageName: node
linkType: hard
@@ -5532,15 +5532,15 @@ __metadata:
languageName: node
linkType: hard
"chromium-bidi@npm:4.1.1":
version: 4.1.1
resolution: "chromium-bidi@npm:4.1.1"
"chromium-bidi@npm:5.1.0":
version: 5.1.0
resolution: "chromium-bidi@npm:5.1.0"
dependencies:
mitt: ^3.0.1
zod: ^3.24.1
peerDependencies:
devtools-protocol: "*"
checksum: 30ed480364c00f6608e1f173dc0ba224d5d7d97941994ce89352fb807ebe1bd91fdafaf0afe419282af547603f6f03a5fe9684138cbefbba901b260d05cfe226
checksum: 2f9f84305cebc5cf8a3817e2c1f220814a9d5f994ea91a3d08c758b4a77d1ed73210722953e50b935a30893903ee984c5a664818094d8e046d8891bbe2b72805
languageName: node
linkType: hard
@@ -6118,7 +6118,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:^4.3.5":
"debug@npm:^4.3.5, debug@npm:^4.4.1":
version: 4.4.1
resolution: "debug@npm:4.4.1"
dependencies:
@@ -6284,10 +6284,10 @@ __metadata:
languageName: node
linkType: hard
"devtools-protocol@npm:0.0.1439962":
version: 0.0.1439962
resolution: "devtools-protocol@npm:0.0.1439962"
checksum: f1ef3892da29d1da9fba18c8c64da5e0a46153ead084f3cb8c7f1c2cb4b61fe69b2d6a174f89555a4b3db7d52134b60efce79c8b71248768bd5faad83c63caf2
"devtools-protocol@npm:0.0.1452169":
version: 0.0.1452169
resolution: "devtools-protocol@npm:0.0.1452169"
checksum: f9d3eb8b5d88ae0a0da3bb2699e038f8310ac70160aaf714e726b06025593fbca7b261a838606bd6ecf46a20b8b36f10fa96323bfd7cfa34480e6f572d802e6a
languageName: node
linkType: hard
@@ -11164,33 +11164,33 @@ __metadata:
languageName: node
linkType: hard
"puppeteer-core@npm:24.8.0":
version: 24.8.0
resolution: "puppeteer-core@npm:24.8.0"
"puppeteer-core@npm:24.10.0":
version: 24.10.0
resolution: "puppeteer-core@npm:24.10.0"
dependencies:
"@puppeteer/browsers": 2.10.3
chromium-bidi: 4.1.1
debug: ^4.4.0
devtools-protocol: 0.0.1439962
"@puppeteer/browsers": 2.10.5
chromium-bidi: 5.1.0
debug: ^4.4.1
devtools-protocol: 0.0.1452169
typed-query-selector: ^2.12.0
ws: ^8.18.1
checksum: e89db314280404a509c00cded220c89c0704084f2162968d88ed4e334be36869cc8f2675ed5207937f5785ac1d03787b958ef0eaecdaa9d4c9c860e63e37f341
ws: ^8.18.2
checksum: 16783646bef4d3113fd0193c28f4875d881b93f8ec8f5aa7cb121aa39a0de43f40b3a374d4b8f9c4d3997abe01a2176f452a1be507155067ff87e6d46feff4a3
languageName: node
linkType: hard
"puppeteer@npm:^24.7.2":
version: 24.8.0
resolution: "puppeteer@npm:24.8.0"
"puppeteer@npm:^24.10.0":
version: 24.10.0
resolution: "puppeteer@npm:24.10.0"
dependencies:
"@puppeteer/browsers": 2.10.3
chromium-bidi: 4.1.1
"@puppeteer/browsers": 2.10.5
chromium-bidi: 5.1.0
cosmiconfig: ^9.0.0
devtools-protocol: 0.0.1439962
puppeteer-core: 24.8.0
devtools-protocol: 0.0.1452169
puppeteer-core: 24.10.0
typed-query-selector: ^2.12.0
bin:
puppeteer: lib/cjs/puppeteer/node/cli.js
checksum: 699d732b0a2097308d64e38e0fbd5caefa23897eb83c30616f4c433a9c3a41f4b47b4ade6dae60ef998df1cf99e68e431f927312e7a438e720b5d50f9e308ddd
checksum: 50a84d3beed45bcd8f7f3800d0ad3397fe9e24d0cd72b4b596bc99e601b7b8a4b41e18a017c55772f2edf7c00ba2981cae78789029c8d0d5e3fd4448ffb5b5b0
languageName: node
linkType: hard
@@ -11721,12 +11721,12 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.7.1":
version: 7.7.1
resolution: "semver@npm:7.7.1"
"semver@npm:^7.7.2":
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
semver: bin/semver.js
checksum: 586b825d36874007c9382d9e1ad8f93888d8670040add24a28e06a910aeebd673a2eb9e3bf169c6679d9245e66efb9057e0852e70d9daa6c27372aab1dda7104
checksum: dd94ba8f1cbc903d8eeb4dd8bf19f46b3deb14262b6717d0de3c804b594058ae785ef2e4b46c5c3b58733c99c83339068203002f9e37cfe44f7e2cc5e3d2f621
languageName: node
linkType: hard