Compare commits

...
86 Commits
Author SHA1 Message Date
devlikepro d745279fec [core] 2025.4.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-04-09 16:05:03 +07:00
devlikepro df34c9f2fd [core] Up GOWS 2025-04-09 16:05:03 +07:00
devlikepro 37da9aabe3 [core] Add POST /api/send/link-review
fix #880
fix #596
2025-04-09 16:05:02 +07:00
devlikepro 3a2205147c [core] API - deprecate /sendLinkPreview 2025-04-09 16:05:02 +07:00
devlikepro 5c6acaf10a [core] NOWEB - filter out sender key distribution msg
fix #891
2025-04-09 16:05:02 +07:00
devlikepro d916f2dea5 [core] NOWEB - return error in case of malformed xml 2025-04-09 16:05:01 +07:00
devlikepro 254a1aedcb [core] NOWEB - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro dce3175388 [core] GOWS - fix sending reaction to DM/Groups for other participants 2025-04-09 16:05:01 +07:00
devlikepro e2b6b45418 [core] GOWS - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro e210d1e9fc [core] 2025.3.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (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
2025-03-31 15:39:56 +07:00
devlikepro df0f449e13 [core] use isJidBroadcast 2025-03-31 15:39:56 +07:00
devlikepro 895105c900 [core] Ignore @broadcast for picture
fix #878
fix #881
2025-03-31 15:39:56 +07:00
devlikepro 209538709b [core] Up Dashboard 2025-03-31 15:39:56 +07:00
devlikepro ef7c35286b [core] NOWEB - revert presence optimizations 2025-03-31 15:39:56 +07:00
devlikepro 70af0828cb [core] NOWEB - cache presence for 1 minute 2025-03-31 15:39:56 +07:00
devlikepro ef18e311b1 [core] 2025.3.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-03-27 14:36:38 +07:00
devlikepro 68de84973a [core] GOWS - extract replyTo
fix #876
2025-03-27 14:36:38 +07:00
devlikepro ef6d4f9998 [core] Add _data (original message) to replyTo field 2025-03-27 14:36:37 +07:00
devlikepro 9f5413963b [core] NOWEB - log errors on profile picture change
fix #873
2025-03-27 14:36:36 +07:00
devlikepro ec7c0a5cb7 [core] check session name in waitUntilStatus 2025-03-27 14:36:36 +07:00
devlikepro f82bd9eadf [core] no browser for non webjs based engines 2025-03-27 14:36:35 +07:00
devlikepro 843c064ebe [core] Up GOWS 2025-03-27 14:36:35 +07:00
devlikepro 8a52e1d1d0 [core] Up Dashboard 2025-03-27 14:36:34 +07:00
devlikepro 8ec220dc9a [core] Status to 50K and more contacts
fix #858
2025-03-27 14:36:33 +07:00
devlikepro 9c4b9f4433 [core] NOWEB - fix scan qr issue 2025-03-27 14:36:33 +07:00
devlikepro 6059a29660 [core] fix message reactions empty for channels preview 2025-03-27 14:36:32 +07:00
devlikepro e65e6764fa [core] NOWEB - presence optimizations
Apply 1 hour ttl for presence information
2025-03-27 14:36:32 +07:00
devlikepro 1e77cb234a [core] NOWEB - use {} for presence 2025-03-27 14:36:31 +07:00
devlikepro 4a357087d1 [core] NOWEB - do not restart after SCAN_QR_CODE expires 2025-03-27 14:36:31 +07:00
devlikepro 6a3823cdff [core] NOWEB - fix the case when STARTING session fails the container
fix #821
fix #860
fix #869
2025-03-27 14:36:30 +07:00
devlikepro c2372ce96e [core] GOWS - fix get contact issue
fix #866
2025-03-27 14:36:30 +07:00
devlikepro 0f5e79b163 [core] Enable all features in Global Webhook
fix #855
2025-03-27 14:36:30 +07:00
devlikepro fa4026c83f [core] Allow only http/https for webhooks 2025-03-27 14:36:30 +07:00
devlikepro 3e4d66ca27 [core] Reply to button
fix #848
2025-03-27 14:36:30 +07:00
devlikepro 2ec94dc13c [core] 2025.3.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-03-16 14:05:33 +07:00
devlikepro 6b11e08f3d [core] UP GOWS - v1.0.1 2025-03-16 14:05:33 +07:00
devlikepro e884e56b47 [core] Up Dashboard 2025-03-16 14:05:32 +07:00
devlikepro 2d74affed4 [core] NOWEB - send message save ids 2025-03-16 14:05:31 +07:00
devlikepro 3572b17049 [core] NOWEB up engine 2025-03-16 14:05:30 +07:00
devlikepro 4ee7091cd1 [core] Add /api/groups/count
fix #752
2025-03-16 14:05:29 +07:00
devlikepro ff5e89d97c [core] GOWS - no sender key dis message 2025-03-16 14:05:29 +07:00
devlikepro c4e82afc0c [core] log as debug engine info errors (not working perfectly in WEBJS)
fix #850
2025-03-16 14:05:29 +07:00
devlikepro 52c5d85487 [core] source: api|app for messages.
fix #787
2025-03-16 14:05:28 +07:00
devlikepro fd0438f80b [core] GOWS send _data with full message 2025-03-16 14:05:26 +07:00
devlikepro cb5781ca65 [core] NOWEB disappearing messages
fix #736
2025-03-16 14:05:25 +07:00
devlikepro 4d8aad9dfe [core] Remove calls from message events
fix #840
2025-03-16 14:05:24 +07:00
devlikepro 39429a2e44 [core] Add event.timestamp
fix #755
2025-03-16 14:05:23 +07:00
devlikepro 0a3514d81e [core] GOWS reply on message
fix #799
2025-03-16 14:05:22 +07:00
devlikepro 4e5e0a7c34 [core] GOWS fix filename in sendFile
fix #853
2025-03-16 14:05:22 +07:00
devlikepro f6cc6a9bef [core] GOWS edit message
fix #797
2025-03-16 14:05:20 +07:00
devlikepro 2e545e9fb9 [core] GOWS delete status message
fix #754
2025-03-16 14:05:19 +07:00
devlikepro d49ef64e7e [core] API - description for message id 2025-03-16 14:05:18 +07:00
devlikepro b1e6eaef90 [core] GOWS - delete message
fix #796
2025-03-16 14:05:16 +07:00
devlikepro 3420d0199b [core] NOWEB - fix getting message by id 2025-03-16 14:05:16 +07:00
devlikepro 671db35571 [core] NOWEB - link preview HQ 2025-03-16 14:05:16 +07:00
devlikepro d724f87726 [core] GOWS - link preview 2025-03-16 14:05:14 +07:00
devlikepro 5ab8565205 [core] NOWEB - link preview in status 2025-03-16 14:05:14 +07:00
devlikepro 660c567d1b [core] GOWS - link preview
fix #763
2025-03-16 14:05:14 +07:00
devlikepro 2c68d13c56 [core] Send session name back
fix #835
2025-03-16 14:05:14 +07:00
devlikeapro b9fc155b4f Update feature_request.md 2025-02-28 10:55:19 +07:00
devlikeapro 8dd5aefc81 Update feature_request.md 2025-02-28 10:55:05 +07:00
devlikeapro f9aaf5dcef Update bug_report.md 2025-02-28 10:53:52 +07:00
devlikepro 34a6e773df [core] 2025.2.8
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-02-28 10:22:33 +07:00
devlikepro f0beb03344 [core] Link preview on edit
fix #833
2025-02-28 10:22:33 +07:00
devlikepro 1ff9a81ab3 [core] Fix channels API
fix #832
2025-02-28 10:22:33 +07:00
devlikepro 9b1771c693 [core] 2025.2.7
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-02-27 14:34:23 +07:00
devlikepro 823e604737 [core] GOWS v1.0.0 2025-02-27 14:34:23 +07:00
devlikepro 97a31bdf17 [core] NOWEB - ignore EPHEMERAL_SYNC_RESPONSE 2025-02-27 14:34:22 +07:00
devlikepro ee7a198ae6 [core] GOWS - startTyping/stopTyping 2025-02-27 14:34:22 +07:00
devlikepro f143a51010 [core] group id in picture 2025-02-27 14:34:21 +07:00
devlikepro e483185b2c [core] fields.exclude optional 2025-02-27 14:34:21 +07:00
devlikepro 40e1c75746 [core] GOWS - v1.0.0-dev.1 2025-02-27 14:34:21 +07:00
devlikepro 456bd4693c [core] GOWS - fetch proto from ref with no v 2025-02-27 14:34:21 +07:00
devlikepro 5ac9c0f760 [core] NOWEB - no auto restart 2025-02-27 14:34:20 +07:00
devlikepro f621d84b83 [core] Exclude groups participants
fix #242
2025-02-27 14:34:20 +07:00
devlikepro 0dd001e95a [core] GOWS Groups API 2025-02-27 14:34:19 +07:00
devlikepro 69b5235db9 [core] Groups API - return 200 on POST in not create API 2025-02-27 14:34:19 +07:00
devlikepro a673d63b33 [core] Groups Picture API - Get, Update, Remove
fix #802
2025-02-27 14:34:18 +07:00
devlikepro 36485e10f2 [core] NOWEB add admin only / messages admin settings API
fix #695
fix #808
2025-02-27 14:34:18 +07:00
devlikepro 8a1a062127 [core] Place Channels Search API at the end 2025-02-27 14:34:18 +07:00
devlikepro 5961e0de08 [core] Fix group.participants undefined
fix #810
2025-02-27 14:34:18 +07:00
devlikepro 60bda521a0 [core] Wait until session is running and check if exist after 2025-02-27 14:34:18 +07:00
devlikepro 49f63c0f0d [core] 2025.2.6
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
fix #820
2025-02-21 10:06:27 +07:00
devlikepro 33910aedb8 [core] 2025.2.5
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-02-19 09:20:27 +07:00
devlikepro 3359a81a14 [core] NOWEB add message.revoked event
fix #813
2025-02-19 09:20:26 +07:00
devlikepro e27f850e68 [core] NOWEB Fix body extract from messages
fix #813
2025-02-19 09:20:26 +07:00
43 changed files with 7513 additions and 452 deletions

No files matched your search

+2 -1
View File
@@ -1,9 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: '[BUG][WEBJS] - '
title: '[WEBJS] - '
labels: bug
assignees: ''
type: 'BUG'
---
### Describe the bug
+2 -1
View File
@@ -1,9 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: '[Feature Request]'
title: ''
labels: ''
assignees: ''
type: 'Feature'
---
**Is your feature request related to a problem? Please describe.** I'm always
+1 -1
View File
@@ -66,7 +66,7 @@ RUN \
elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; \
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
mkdir -p /go/gows/bin && \
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/v.${GOWS_SHA}/gows-${ARCH} && \
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/${GOWS_SHA}/gows-${ARCH} && \
chmod +x /go/gows/bin/gows
+1 -1
View File
@@ -53,7 +53,7 @@ function cleanDirectory(directory, suffix) {
// Helper function to download files
async function downloadFiles(repo, ref, directory) {
for (const file of PROTO_FILES) {
const url = `https://github.com/${repo}/releases/download/v.${ref}/${file}`;
const url = `https://github.com/${repo}/releases/download/${ref}/${file}`;
const filePath = path.join(directory, file);
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
+78 -78
View File
@@ -49,84 +49,6 @@ export class ChannelsController {
private channelsInfoService: ChannelsInfoServiceCore,
) {}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get list of know channels' })
@@ -179,6 +101,39 @@ export class ChannelsController {
}
}
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Post(':id/follow')
@SessionApiParam
@NewsletterIdApiParam
@@ -222,4 +177,49 @@ export class ChannelsController {
): Promise<void> {
return session.channelsUnmuteChannel(id);
}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
}
+38 -6
View File
@@ -18,10 +18,12 @@ import {
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageButtonReply,
MessageContactVcardRequest,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -94,6 +96,25 @@ export class ChattingController {
return whatsapp.sendVideo(request);
}
@Post('/send/link-custom-preview')
@ApiOperation({
summary: 'Send a text message with a CUSTOM link preview.',
description:
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
})
@UsePipes(new WAHAValidationPipe())
async sendLinkCustomPreview(
@Body() request: MessageLinkCustomPreviewRequest,
): Promise<any> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (!request.text.includes(request.preview.url)) {
throw new Error(
'"text" must include the URL provided in the "preview.url"',
);
}
return whatsapp.sendLinkCustomPreview(request);
}
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons (interactive message)',
@@ -165,18 +186,22 @@ export class ChattingController {
return whatsapp.sendLocation(request);
}
@Post('/sendLinkPreview')
async sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
@Post('/sendContactVcard')
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendContactVCard(request);
}
@Post('/send/buttons/reply')
@ApiOperation({
summary: 'Reply on a button message',
})
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async sendButtonsReply(@Body() request: MessageButtonReply) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendButtonsReply(request);
}
@Get('/sendText')
@ApiOperation({ summary: 'Send a text message', deprecated: true })
async sendTextGet(@Query() query: MessageTextQuery) {
@@ -225,4 +250,11 @@ export class ChattingController {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.reply(request);
}
@Post('/sendLinkPreview')
@ApiOperation({ deprecated: true })
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
}
+130 -56
View File
@@ -3,6 +3,8 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Put,
@@ -16,12 +18,20 @@ import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { CountResponse, Result } from '@waha/structures/base.dto';
import {
ChatPictureQuery,
ChatPictureResponse,
} from '@waha/structures/chats.dto';
import { ProfilePictureRequest } from '@waha/structures/profile.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
import {
CreateGroupRequest,
DescriptionRequest,
GroupField,
GroupsListFields,
GroupsPaginationParams,
JoinGroupRequest,
JoinGroupResponse,
@@ -58,6 +68,7 @@ export class GroupsController {
}
@Post('join')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Join group via code' })
async joinGroup(
@@ -73,14 +84,32 @@ export class GroupsController {
@SessionApiParam
@ApiOperation({ summary: 'Get all groups.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
getGroups(
async getGroups(
@WorkingSessionParam session: WhatsappSession,
@Query() pagination: GroupsPaginationParams,
@Query() fields: GroupsListFields,
) {
return session.getGroups(pagination);
let groups: any = await session.getGroups(pagination);
groups = session.filterGroupsFields(groups, fields);
return groups;
}
@Get('/count')
@SessionApiParam
@ApiOperation({ summary: 'Get the number of groups.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getGroupsCount(
@WorkingSessionParam session: WhatsappSession,
): Promise<CountResponse> {
const data: any = await session.getGroups({});
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
return {
count: groups.length,
};
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Refresh groups from the server.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@@ -99,6 +128,100 @@ export class GroupsController {
return session.getGroup(id);
}
@Delete(':id')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id);
}
@Get(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Get group picture' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getChatPicture(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Query() query: ChatPictureQuery,
): Promise<ChatPictureResponse> {
const url = await session.getContactProfilePicture(id, query.refresh);
return { url: url };
}
@Put(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Set group picture' })
async setPicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfilePictureRequest,
): Promise<Result> {
const success = await session.updateGroupPicture(id, request.file);
return { success: success ?? true };
}
@Delete(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete group picture' })
async deletePicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
): Promise<Result> {
const success = await session.updateGroupPicture(id, null);
return { success: success || true };
}
@Put(':id/description')
@ApiOperation({
summary: 'Updates the group description.',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
@SessionApiParam
@GroupIdApiParam
setDescription(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
return session.setDescription(id, request.description);
}
@Put(':id/subject')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Updates the group subject',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
setSubject(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@GroupIdApiParam
@@ -160,60 +283,6 @@ export class GroupsController {
return session.getMessagesAdminsOnly(id);
}
@Delete(':id')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id);
}
@Put(':id/description')
@ApiOperation({
summary: 'Updates the group description.',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
@SessionApiParam
@GroupIdApiParam
setDescription(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
return session.setDescription(id, request.description);
}
@Put(':id/subject')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Updates the group subject',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
setSubject(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Get(':id/invite-code')
@SessionApiParam
@GroupIdApiParam
@@ -226,6 +295,7 @@ export class GroupsController {
}
@Post(':id/invite-code/revoke')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
@@ -251,6 +321,7 @@ export class GroupsController {
}
@Post(':id/participants/add')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Add participants' })
@@ -263,6 +334,7 @@ export class GroupsController {
}
@Post(':id/participants/remove')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
@@ -277,6 +349,7 @@ export class GroupsController {
}
@Post(':id/admin/promote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Promote participants to admin users.' })
@@ -289,6 +362,7 @@ export class GroupsController {
}
@Post(':id/admin/demote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Demotes participants to regular users.' })
+14 -1
View File
@@ -1,9 +1,10 @@
import { Body, Controller, Post } from '@nestjs/common';
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { NewMessageIDResponse } from '@waha/structures/chatting.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
@@ -70,6 +71,18 @@ class StatusController {
) {
return session.deleteStatus(status);
}
@Get('new-message-id')
@SessionApiParam
@ApiOperation({
summary: 'Generate message ID you can use to batch contacts',
})
async getNewMessageId(
@WorkingSessionParam session: WhatsappSession,
): Promise<NewMessageIDResponse> {
const id = await session.generateNewMessageId();
return { id: id };
}
}
export { StatusController };
+18 -19
View File
@@ -1,13 +1,19 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WAHAEvents } from '@waha/structures/enums.dto';
import { GlobalWebhookConfigConfig } from '@waha/core/config/GlobalWebhookConfig';
import { parseBool } from './helpers';
import { WebhookConfig } from './structures/webhooks.config.dto';
@Injectable()
export class WhatsappConfigService {
constructor(private configService: ConfigService) {}
export class WhatsappConfigService implements OnApplicationBootstrap {
private logger: Logger;
private webhookConfig: GlobalWebhookConfigConfig;
constructor(private configService: ConfigService) {
this.logger = new Logger('WhatsappConfigService');
this.webhookConfig = new GlobalWebhookConfigConfig(configService);
}
get schema() {
return this.configService.get('WHATSAPP_API_SCHEMA', 'http');
@@ -113,21 +119,7 @@ export class WhatsappConfigService {
}
getWebhookConfig(): WebhookConfig | undefined {
const url = this.getWebhookUrl();
const events = this.getWebhookEvents();
if (!url || events.length === 0) {
return undefined;
}
return { url: url, events: events };
}
private getWebhookUrl(): string | undefined {
return this.get('WHATSAPP_HOOK_URL');
}
private getWebhookEvents(): WAHAEvents[] {
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
return value ? value.split(',') : [];
return this.webhookConfig.config;
}
getSessionMongoUrl(): string | undefined {
@@ -182,4 +174,11 @@ export class WhatsappConfigService {
const value = this.configService.get('WAHA_DEBUG_MODE', 'false');
return parseBool(value);
}
onApplicationBootstrap() {
const error = this.webhookConfig.validateConfig();
if (error) {
throw new Error(`Invalid global webhook config:\n${error}\n`);
}
}
}
+37 -1
View File
@@ -46,8 +46,10 @@ export abstract class SessionManager
protected sessionWorkerRepository: ISessionWorkerRepository;
private lock: any;
WAIT_SESSION_RUNNING_INTERVAL = 500;
WAIT_SESSION_RUNNING_TIMEOUT = 5_000;
WAIT_STATUS_INTERVAL = 500;
WAIT_STATUS_TIMEOUT = 5_000;
WAIT_STATUS_TIMEOUT = 10_000;
LOCK_TIMEOUT = 10_000;
protected constructor(
@@ -148,6 +150,37 @@ export abstract class SessionManager
sessionName: string,
expected: WAHASessionStatus[],
): Promise<WhatsappSession> {
if (!sessionName) {
throw new UnprocessableEntityException({
error: `Session name is required`,
session: sessionName,
});
}
const running = await waitUntil(
async () => this.isRunning(sessionName),
this.WAIT_SESSION_RUNNING_INTERVAL,
this.WAIT_SESSION_RUNNING_TIMEOUT,
);
if (!running) {
const exists = await this.exists(sessionName);
if (!exists) {
throw new UnprocessableEntityException({
error: `Session "${sessionName}" does not exist`,
session: sessionName,
});
}
const msg = {
error:
'Session status is not as expected. Try again later or restart the session',
session: sessionName,
status: 'STOPPED',
expected: expected,
};
throw new UnprocessableEntityException(msg);
}
const session = this.getSession(sessionName);
const valid = await waitUntil(
async () => expected.includes(session.status),
@@ -191,11 +224,14 @@ export function populateSessionInfo(
) {
return (payload: any): WAHAWebhook => {
const id = payload._eventId;
const timestampMs = payload._timestampMs;
const data = { ...payload };
delete data._eventId;
delete data._timestampMs;
const me = session.getSessionMeInfo();
return {
id: id,
timestamp: timestampMs,
event: event,
session: session.name,
metadata: session.sessionConfig?.metadata,
+113 -7
View File
@@ -1,3 +1,4 @@
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import {
Channel,
ChannelListResult,
@@ -18,7 +19,7 @@ import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { WAMessage } from '@waha/structures/responses.dto';
import { MessageSource, WAMessage } from '@waha/structures/responses.dto';
import { DefaultMap } from '@waha/utils/DefaultMap';
import { generatePrefixedId } from '@waha/utils/ids';
import { LoggerBuilder } from '@waha/utils/logging';
@@ -26,7 +27,6 @@ import { complete } from '@waha/utils/reactive/complete';
import { SwitchObservable } from '@waha/utils/reactive/SwitchObservable';
import * as fs from 'fs';
import * as lodash from 'lodash';
import { PinoLogger } from 'nestjs-pino';
import * as NodeCache from 'node-cache';
import { Logger } from 'pino';
import {
@@ -47,10 +47,12 @@ import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageButtonReply,
MessageContactVcardRequest,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
@@ -71,7 +73,8 @@ import {
} from '../../structures/enums.dto';
import {
CreateGroupRequest,
GroupsPaginationParams,
GroupField,
GroupsListFields,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '../../structures/groups.dto';
@@ -146,6 +149,12 @@ export abstract class WhatsappSession {
stdTTL: 24 * 60 * 60, // 1 day
});
// Save sent messages ids in cache so we can determine if a message was sent
// via API or APP
private sentMessageIds: NodeCache = new NodeCache({
stdTTL: 10 * 60, // 10 minutes
});
public constructor({
name,
printQR,
@@ -176,6 +185,7 @@ export abstract class WhatsappSession {
filter(Boolean),
map((data) => {
data._eventId = generatePrefixedId('evt');
data._timestampMs = Date.now();
return data;
}),
retry(),
@@ -360,10 +370,10 @@ export abstract class WhatsappSession {
// Refresh profile picture after update
setTimeout(() => {
this.logger.info('Refreshing my profile picture after update...');
this.logger.debug('Refreshing my profile picture after update...');
this.refreshMyProfilePicture()
.then(() => {
this.logger.info('Refreshed my profile picture after update');
this.logger.debug('Refreshed my profile picture after update');
})
.catch((err) => {
this.logger.error('Error refreshing my profile picture after update');
@@ -390,6 +400,10 @@ export abstract class WhatsappSession {
/**
* Other methods
*/
generateNewMessageId(): Promise<string> {
throw new NotImplementedByEngineError();
}
abstract checkNumberStatus(request: CheckNumberStatusQuery);
abstract sendText(request: MessageTextRequest);
@@ -408,6 +422,12 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new NotImplementedByEngineError();
}
abstract forwardMessage(request: MessageForwardRequest): Promise<WAMessage>;
abstract sendImage(request: MessageImageRequest);
@@ -424,6 +444,10 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
sendButtonsReply(request: MessageButtonReply) {
throw new NotImplementedByEngineError();
}
abstract reply(request: MessageReplyRequest);
abstract sendSeen(chat: SendSeenRequest);
@@ -581,7 +605,9 @@ export abstract class WhatsappSession {
protected async refreshProfilePicture(id: string) {
let fn: Promise<string>;
if (isNewsletter(id)) {
if (isJidBroadcast(id)) {
return null;
} else if (isNewsletter(id)) {
fn = this.channelsGetChannel(id).then(
(channel: Channel) => channel.picture || channel.preview,
);
@@ -621,10 +647,22 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public getGroups(pagination: PaginationParams) {
public getGroups(pagination: PaginationParams): Promise<any> {
throw new NotImplementedByEngineError();
}
public filterGroupsFields(data: any, fields: GroupsListFields) {
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
if (fields.exclude?.includes(GroupField.PARTICIPANTS)) {
groups.forEach((group) => this.removeGroupsFieldParticipant(group));
}
return data;
}
protected removeGroupsFieldParticipant(group: any) {
return;
}
public refreshGroups(): Promise<boolean> {
throw new NotImplementedByEngineError();
}
@@ -661,6 +699,43 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public async updateGroupPicture(
id: string,
file: BinaryFile | RemoteFile | null,
): Promise<boolean> {
if (file) {
await this.setGroupPicture(id, file);
} else {
await this.deleteGroupPicture(id);
}
// Refresh picture after update
setTimeout(() => {
this.logger.debug('Refreshing group profile picture after update...');
this.refreshProfilePicture(id)
.then(() => {
this.logger.debug('Refreshed group profile picture after update');
})
.catch((err) => {
this.logger.error('Error refreshing my profile picture after update');
this.logger.error(err, err.stack);
});
}, 3_000);
return true;
}
protected setGroupPicture(
id: string,
file: BinaryFile | RemoteFile,
): Promise<boolean> {
throw new NotImplementedByEngineError();
}
protected deleteGroupPicture(id: string): Promise<boolean> {
throw new NotImplementedByEngineError();
}
public setSubject(id, description) {
throw new NotImplementedByEngineError();
}
@@ -792,6 +867,9 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
/**
* Engine methods
*/
public async getEngineInfo(): Promise<any> {
return {};
}
@@ -833,6 +911,18 @@ export abstract class WhatsappSession {
);
qrcode.generate(qr.raw, { small: true });
}
protected saveSentMessageId(id: string) {
this.sentMessageIds.set(id, true);
}
protected getMessageSource(id: string): MessageSource {
if (!id) {
return MessageSource.APP;
}
const api = this.sentMessageIds.has(id);
return api ? MessageSource.API : MessageSource.APP;
}
}
export function isNewsletter(jid: string) {
@@ -864,3 +954,19 @@ export function parseChannelInviteLink(link: string): string {
export function getPublicUrlFromDirectPath(directPath: string) {
return `https://pps.whatsapp.net${directPath}`;
}
const deviceRegexp = /^.*:(\d+)@.*$/;
/**
* Extracts the device ID from a JID string.
*
* @param jid - The JID string (e.g., "123123:12@c.us")
* @return The extracted device ID (e.g., "12") or null if the format is invalid.
*/
export function extractDeviceId(jid: string): string | null {
if (!jid) {
return null;
}
const match = jid.match(deviceRegexp);
return match ? match[1] : null;
}
+167
View File
@@ -0,0 +1,167 @@
import { ConfigService } from '@nestjs/config';
import { WAHAEvents } from '@waha/structures/enums.dto';
import {
CustomHeader,
HmacConfiguration,
RetriesConfiguration,
WebhookConfig,
} from '@waha/structures/webhooks.config.dto';
import { plainToInstance } from 'class-transformer';
import { validateSync, ValidationError } from 'class-validator';
enum Env {
WHATSAPP_HOOK_URL = 'WHATSAPP_HOOK_URL',
WHATSAPP_HOOK_EVENTS = 'WHATSAPP_HOOK_EVENTS',
WHATSAPP_HOOK_RETRIES_POLICY = 'WHATSAPP_HOOK_RETRIES_POLICY',
WHATSAPP_HOOK_RETRIES_DELAY_SECONDS = 'WHATSAPP_HOOK_RETRIES_DELAY_SECONDS',
WHATSAPP_HOOK_RETRIES_ATTEMPTS = 'WHATSAPP_HOOK_RETRIES_ATTEMPTS',
WHATSAPP_HOOK_HMAC_KEY = 'WHATSAPP_HOOK_HMAC_KEY',
WHATSAPP_HOOK_CUSTOM_HEADERS = 'WHATSAPP_HOOK_CUSTOM_HEADERS',
}
export class GlobalWebhookConfigConfig {
protected _config: WebhookConfig | null = null;
constructor(private configService: ConfigService) {}
private getUrl(): string | undefined {
return this.configService.get(Env.WHATSAPP_HOOK_URL);
}
private getEvents(): WAHAEvents[] {
const value = this.configService.get(Env.WHATSAPP_HOOK_EVENTS, '');
return value ? value.split(',') : [];
}
private getHmac(): HmacConfiguration | null {
const key = this.configService.get(Env.WHATSAPP_HOOK_HMAC_KEY, '');
if (!key) {
return null;
}
return {
key: key,
};
}
private getRetries(): RetriesConfiguration | null {
const policy: any = this.configService.get(
Env.WHATSAPP_HOOK_RETRIES_POLICY,
null,
);
const delaySeconds: any = this.configService.get(
Env.WHATSAPP_HOOK_RETRIES_DELAY_SECONDS,
null,
);
const attempts: any = this.configService.get(
Env.WHATSAPP_HOOK_RETRIES_ATTEMPTS,
null,
);
if (!policy && !delaySeconds && !attempts) {
return null;
}
return {
policy: policy,
delaySeconds: delaySeconds,
attempts: attempts,
};
}
private getCustomHeaders(): CustomHeader[] {
// key:value;key2:value
const value = this.configService.get(Env.WHATSAPP_HOOK_CUSTOM_HEADERS, '');
if (!value) {
return [];
}
const headers = value.split(';');
return headers.map((header: string) => {
const parts = header.split(':');
if (parts.length !== 2) {
throw new Error(
`${Env.WHATSAPP_HOOK_CUSTOM_HEADERS} - Invalid custom header, no ':' found in '${header}'`,
);
}
return {
name: parts[0],
value: parts[1],
};
});
}
get config() {
if (!this._config) {
this._config = this.parseWebhookConfig();
}
return this._config;
}
validateConfig(): string | null {
const config = this.parseWebhookConfig();
if (!config) {
return null;
}
const errors = validateSync(config);
if (errors.length > 0) {
return this.formatErrors([], errors, []).join('\n');
}
return;
}
private getEnv(key: string): string | null {
const keys = {
url: Env.WHATSAPP_HOOK_URL,
events: Env.WHATSAPP_HOOK_EVENTS,
'retries.policy': Env.WHATSAPP_HOOK_RETRIES_POLICY,
'retries.delaySeconds': Env.WHATSAPP_HOOK_RETRIES_DELAY_SECONDS,
'retries.attempts': Env.WHATSAPP_HOOK_RETRIES_ATTEMPTS,
'hmac.key': Env.WHATSAPP_HOOK_HMAC_KEY,
};
return keys[key] || null;
}
private formatErrors(
lines: string[],
errors: ValidationError[],
path: string[],
) {
for (const err of errors) {
path = [...path, err.property];
for (const key in err.constraints) {
if (!err.constraints.hasOwnProperty(key)) {
continue;
}
const property = path.join('.');
const env = this.getEnv(property);
if (env) {
const value = this.configService.get(env, '');
const line = `- ${env} (${property}): '${value}' => '${err.value}' - ${err.constraints[key]}`;
lines.push(line);
} else {
const line = `- ${property}: '${err.value}' - ${err.constraints[key]}`;
lines.push(line);
}
}
if (err.children) {
this.formatErrors(lines, err.children, path);
}
}
return lines;
}
private parseWebhookConfig(): WebhookConfig | null {
const url = this.getUrl();
const events = this.getEvents();
if (!url || events.length === 0) {
return null;
}
const data: WebhookConfig = {
url: url,
events: events,
hmac: this.getHmac(),
retries: this.getRetries(),
customHeaders: this.getCustomHeaders(),
};
return plainToInstance(WebhookConfig, data, {
enableImplicitConversion: true,
});
}
}
File diff suppressed because it is too large. Load diff
+380 -31
View File
@@ -4,6 +4,17 @@
var grpc = require('@grpc/grpc-js');
var gows_pb = require('./gows_pb.js');
function serialize_messages_ButtonReplyRequest(arg) {
if (!(arg instanceof gows_pb.ButtonReplyRequest)) {
throw new Error('Expected argument of type messages.ButtonReplyRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ButtonReplyRequest(buffer_arg) {
return gows_pb.ButtonReplyRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_ChatPresenceRequest(arg) {
if (!(arg instanceof gows_pb.ChatPresenceRequest)) {
throw new Error('Expected argument of type messages.ChatPresenceRequest');
@@ -37,6 +48,17 @@ function deserialize_messages_CheckPhonesResponse(buffer_arg) {
return gows_pb.CheckPhonesResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CreateGroupRequest(arg) {
if (!(arg instanceof gows_pb.CreateGroupRequest)) {
throw new Error('Expected argument of type messages.CreateGroupRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_CreateGroupRequest(buffer_arg) {
return gows_pb.CreateGroupRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CreateNewsletterRequest(arg) {
if (!(arg instanceof gows_pb.CreateNewsletterRequest)) {
throw new Error('Expected argument of type messages.CreateNewsletterRequest');
@@ -70,6 +92,17 @@ function deserialize_messages_DownloadMediaResponse(buffer_arg) {
return gows_pb.DownloadMediaResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_EditMessageRequest(arg) {
if (!(arg instanceof gows_pb.EditMessageRequest)) {
throw new Error('Expected argument of type messages.EditMessageRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_EditMessageRequest(buffer_arg) {
return gows_pb.EditMessageRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Empty(arg) {
if (!(arg instanceof gows_pb.Empty)) {
throw new Error('Expected argument of type messages.Empty');
@@ -147,6 +180,50 @@ function deserialize_messages_GetNewsletterMessagesByInviteRequest(buffer_arg) {
return gows_pb.GetNewsletterMessagesByInviteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GroupCodeRequest(arg) {
if (!(arg instanceof gows_pb.GroupCodeRequest)) {
throw new Error('Expected argument of type messages.GroupCodeRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GroupCodeRequest(buffer_arg) {
return gows_pb.GroupCodeRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidBoolRequest(arg) {
if (!(arg instanceof gows_pb.JidBoolRequest)) {
throw new Error('Expected argument of type messages.JidBoolRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidBoolRequest(buffer_arg) {
return gows_pb.JidBoolRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidRequest(arg) {
if (!(arg instanceof gows_pb.JidRequest)) {
throw new Error('Expected argument of type messages.JidRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidRequest(buffer_arg) {
return gows_pb.JidRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JidStringRequest(arg) {
if (!(arg instanceof gows_pb.JidStringRequest)) {
throw new Error('Expected argument of type messages.JidStringRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JidStringRequest(buffer_arg) {
return gows_pb.JidStringRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Json(arg) {
if (!(arg instanceof gows_pb.Json)) {
throw new Error('Expected argument of type messages.Json');
@@ -213,6 +290,17 @@ function deserialize_messages_MessageResponse(buffer_arg) {
return gows_pb.MessageResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewMessageIDResponse(arg) {
if (!(arg instanceof gows_pb.NewMessageIDResponse)) {
throw new Error('Expected argument of type messages.NewMessageIDResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewMessageIDResponse(buffer_arg) {
return gows_pb.NewMessageIDResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Newsletter(arg) {
if (!(arg instanceof gows_pb.Newsletter)) {
throw new Error('Expected argument of type messages.Newsletter');
@@ -290,6 +378,17 @@ function deserialize_messages_NewsletterToggleMuteRequest(buffer_arg) {
return gows_pb.NewsletterToggleMuteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_OptionalString(arg) {
if (!(arg instanceof gows_pb.OptionalString)) {
throw new Error('Expected argument of type messages.OptionalString');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_OptionalString(buffer_arg) {
return gows_pb.OptionalString.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_PairCodeRequest(arg) {
if (!(arg instanceof gows_pb.PairCodeRequest)) {
throw new Error('Expected argument of type messages.PairCodeRequest');
@@ -367,6 +466,17 @@ function deserialize_messages_ProfileStatusRequest(buffer_arg) {
return gows_pb.ProfileStatusRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_RevokeMessageRequest(arg) {
if (!(arg instanceof gows_pb.RevokeMessageRequest)) {
throw new Error('Expected argument of type messages.RevokeMessageRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_RevokeMessageRequest(buffer_arg) {
return gows_pb.RevokeMessageRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SearchNewslettersByTextRequest(arg) {
if (!(arg instanceof gows_pb.SearchNewslettersByTextRequest)) {
throw new Error('Expected argument of type messages.SearchNewslettersByTextRequest');
@@ -411,6 +521,17 @@ function deserialize_messages_SessionStateResponse(buffer_arg) {
return gows_pb.SessionStateResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SetPictureRequest(arg) {
if (!(arg instanceof gows_pb.SetPictureRequest)) {
throw new Error('Expected argument of type messages.SetPictureRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SetPictureRequest(buffer_arg) {
return gows_pb.SetPictureRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SetProfilePictureRequest(arg) {
if (!(arg instanceof gows_pb.SetProfilePictureRequest)) {
throw new Error('Expected argument of type messages.SetProfilePictureRequest');
@@ -444,6 +565,17 @@ function deserialize_messages_SubscribePresenceRequest(buffer_arg) {
return gows_pb.SubscribePresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_UpdateParticipantsRequest(arg) {
if (!(arg instanceof gows_pb.UpdateParticipantsRequest)) {
throw new Error('Expected argument of type messages.UpdateParticipantsRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_UpdateParticipantsRequest(buffer_arg) {
return gows_pb.UpdateParticipantsRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
//
// Events
@@ -559,31 +691,179 @@ setProfileName: {
responseDeserialize: deserialize_messages_Empty,
},
//
// Groups
//
fetchGroups: {
path: '/messages.MessageService/FetchGroups',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
getGroups: {
path: '/messages.MessageService/GetGroups',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
getGroupInfo: {
path: '/messages.MessageService/GetGroupInfo',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
createGroup: {
path: '/messages.MessageService/CreateGroup',
requestStream: false,
responseStream: false,
requestType: gows_pb.CreateGroupRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_CreateGroupRequest,
requestDeserialize: deserialize_messages_CreateGroupRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
leaveGroup: {
path: '/messages.MessageService/LeaveGroup',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
getGroupInviteLink: {
path: '/messages.MessageService/GetGroupInviteLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.OptionalString,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_OptionalString,
responseDeserialize: deserialize_messages_OptionalString,
},
revokeGroupInviteLink: {
path: '/messages.MessageService/RevokeGroupInviteLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidRequest,
responseType: gows_pb.OptionalString,
requestSerialize: serialize_messages_JidRequest,
requestDeserialize: deserialize_messages_JidRequest,
responseSerialize: serialize_messages_OptionalString,
responseDeserialize: deserialize_messages_OptionalString,
},
getGroupInfoFromLink: {
path: '/messages.MessageService/GetGroupInfoFromLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.GroupCodeRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_GroupCodeRequest,
requestDeserialize: deserialize_messages_GroupCodeRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
joinGroupWithLink: {
path: '/messages.MessageService/JoinGroupWithLink',
requestStream: false,
responseStream: false,
requestType: gows_pb.GroupCodeRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_GroupCodeRequest,
requestDeserialize: deserialize_messages_GroupCodeRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
setGroupName: {
path: '/messages.MessageService/SetGroupName',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidStringRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidStringRequest,
requestDeserialize: deserialize_messages_JidStringRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupDescription: {
path: '/messages.MessageService/SetGroupDescription',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidStringRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidStringRequest,
requestDeserialize: deserialize_messages_JidStringRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupPicture: {
path: '/messages.MessageService/SetGroupPicture',
requestStream: false,
responseStream: false,
requestType: gows_pb.SetPictureRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_SetPictureRequest,
requestDeserialize: deserialize_messages_SetPictureRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setGroupLocked: {
path: '/messages.MessageService/SetGroupLocked',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidBoolRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidBoolRequest,
requestDeserialize: deserialize_messages_JidBoolRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
// change info only by admins
setGroupAnnounce: {
path: '/messages.MessageService/SetGroupAnnounce',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidBoolRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidBoolRequest,
requestDeserialize: deserialize_messages_JidBoolRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
// send messages only by admins
updateGroupParticipants: {
path: '/messages.MessageService/UpdateGroupParticipants',
requestStream: false,
responseStream: false,
requestType: gows_pb.UpdateParticipantsRequest,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_UpdateParticipantsRequest,
requestDeserialize: deserialize_messages_UpdateParticipantsRequest,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
//
// Actions
//
sendMessage: {
path: '/messages.MessageService/SendMessage',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageRequest,
requestDeserialize: deserialize_messages_MessageRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
sendReaction: {
path: '/messages.MessageService/SendReaction',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageReaction,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageReaction,
requestDeserialize: deserialize_messages_MessageReaction,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
getProfilePicture: {
getProfilePicture: {
path: '/messages.MessageService/GetProfilePicture',
requestStream: false,
responseStream: false,
@@ -627,6 +907,53 @@ sendMessage: {
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
checkPhones: {
path: '/messages.MessageService/CheckPhones',
requestStream: false,
responseStream: false,
requestType: gows_pb.CheckPhonesRequest,
responseType: gows_pb.CheckPhonesResponse,
requestSerialize: serialize_messages_CheckPhonesRequest,
requestDeserialize: deserialize_messages_CheckPhonesRequest,
responseSerialize: serialize_messages_CheckPhonesResponse,
responseDeserialize: deserialize_messages_CheckPhonesResponse,
},
//
// Message
//
generateNewMessageID: {
path: '/messages.MessageService/GenerateNewMessageID',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.NewMessageIDResponse,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_NewMessageIDResponse,
responseDeserialize: deserialize_messages_NewMessageIDResponse,
},
sendMessage: {
path: '/messages.MessageService/SendMessage',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageRequest,
requestDeserialize: deserialize_messages_MessageRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
sendReaction: {
path: '/messages.MessageService/SendReaction',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageReaction,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageReaction,
requestDeserialize: deserialize_messages_MessageReaction,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
markRead: {
path: '/messages.MessageService/MarkRead',
requestStream: false,
@@ -638,16 +965,38 @@ sendMessage: {
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
checkPhones: {
path: '/messages.MessageService/CheckPhones',
editMessage: {
path: '/messages.MessageService/EditMessage',
requestStream: false,
responseStream: false,
requestType: gows_pb.CheckPhonesRequest,
responseType: gows_pb.CheckPhonesResponse,
requestSerialize: serialize_messages_CheckPhonesRequest,
requestDeserialize: deserialize_messages_CheckPhonesRequest,
responseSerialize: serialize_messages_CheckPhonesResponse,
responseDeserialize: deserialize_messages_CheckPhonesResponse,
requestType: gows_pb.EditMessageRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_EditMessageRequest,
requestDeserialize: deserialize_messages_EditMessageRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
revokeMessage: {
path: '/messages.MessageService/RevokeMessage',
requestStream: false,
responseStream: false,
requestType: gows_pb.RevokeMessageRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_RevokeMessageRequest,
requestDeserialize: deserialize_messages_RevokeMessageRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
sendButtonReply: {
path: '/messages.MessageService/SendButtonReply',
requestStream: false,
responseStream: false,
requestType: gows_pb.ButtonReplyRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_ButtonReplyRequest,
requestDeserialize: deserialize_messages_ButtonReplyRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
//
// Newsletters
File diff suppressed because it is too large. Load diff
+377 -42
View File
@@ -1,27 +1,44 @@
import {
getContentType,
getUrlFromDirectPath,
isJidGroup,
isJidStatusBroadcast,
jidNormalizedUser,
} from '@adiwajshing/baileys';
import * as grpc from '@grpc/grpc-js';
import { connectivityState } from '@grpc/grpc-js';
import { UnprocessableEntityException } from '@nestjs/common';
import {
extractDeviceId,
getChannelInviteLink,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import { Jid } from '@waha/core/engines/const';
import { EventsFromObservable } from '@waha/core/engines/gows/EventsFromObservable';
import { GowsEventStreamObservable } from '@waha/core/engines/gows/GowsEventStreamObservable';
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
ToGroupV2ParticipantsEvents,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/gows/groups.gows';
import { messages } from '@waha/core/engines/gows/grpc/gows';
import {
optional,
parseJson,
parseJsonList,
statusToAck,
} from '@waha/core/engines/gows/helpers';
import { GowsAuthFactoryCore } from '@waha/core/engines/gows/store/GowsAuthFactoryCore';
import {
parseMessageIdSerialized,
toCusFormat,
toJID,
} from '@waha/core/engines/noweb/session.noweb.core';
import { AvailableInPlusVersion } from '@waha/core/exceptions';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import {
@@ -35,12 +52,21 @@ import {
ListChannelsQuery,
PreviewChannelMessages,
} from '@waha/structures/channels.dto';
import {
ChatSortField,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLocationRequest,
MessageReactionRequest,
MessageReplyRequest,
@@ -49,6 +75,7 @@ import {
SendSeenRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import {
ACK_UNKNOWN,
WAHAEvents,
@@ -56,14 +83,33 @@ import {
WAHASessionStatus,
WAMessageAck,
} from '@waha/structures/enums.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import {
CreateGroupRequest,
GroupSortField,
Participant,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '@waha/structures/presence.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import {
MessageSource,
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { MeInfo, ProxyConfig } from '@waha/structures/sessions.dto';
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
BROADCAST_ID,
DeleteStatusRequest,
StatusRequest,
TextStatus,
} from '@waha/structures/status.dto';
import { EnginePayload, WAMessageAckBody } from '@waha/structures/webhooks.dto';
import { PaginatorInMemory } from '@waha/utils/Paginator';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
import { onlyEvent } from '@waha/utils/reactive/ops/onlyEvent';
import * as NodeCache from 'node-cache';
@@ -83,29 +129,8 @@ import { promisify } from 'util';
import * as gows from './types';
import MessageServiceClient = messages.MessageServiceClient;
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
ToGroupV2ParticipantsEvents,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/gows/groups.gows';
import {
optional,
parseJson,
parseJsonList,
statusToAck,
} from '@waha/core/engines/gows/helpers';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
ChatSortField,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { isJidBroadcast } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { ReplyToMessage } from '@waha/structures/message.dto';
enum WhatsMeowEvent {
CONNECTED = 'gows.ConnectedEventData',
@@ -239,6 +264,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
id: toCusFormat(jidNormalizedUser(data.ID)),
pushName: data.PushName,
};
// @ts-ignore
this.me.jid = data.ID;
});
events.on(WhatsMeowEvent.DISCONNECTED, () => {
@@ -513,31 +540,76 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
/**
* Other methods
*/
async generateNewMessageId(): Promise<string> {
const response = await promisify(this.client.GenerateNewMessageID)(
this.session,
);
const data = response.toObject();
return data.id;
}
async sendText(request: MessageTextRequest) {
const jid = toJID(this.ensureSuffix(request.chatId));
const message = new messages.MessageRequest({
jid: jid,
text: request.text,
session: this.session,
linkPreview: request.linkPreview ?? true,
linkPreviewHighQuality: request.linkPreviewHighQuality,
replyTo: getMessageIdFromSerialized(request.reply_to),
});
const response = await promisify(this.client.SendMessage)(message);
const data = response.toObject();
return this.messageResponse(jid, data);
}
protected checkStatusRequest(request: StatusRequest) {
if (request.contacts && request.contacts?.length > 0) {
const msg =
"GOWS doesn't accept 'contacts'. Remove the field to send status to all contacts.";
throw new UnprocessableEntityException(msg);
public async editMessage(
chatId: string,
messageId: string,
request: EditMessageRequest,
) {
const jid = toJID(this.ensureSuffix(chatId));
const key = parseMessageIdSerialized(messageId, true);
const message = new messages.EditMessageRequest({
session: this.session,
jid: jid,
messageId: key.id,
text: request.text,
linkPreview: request.linkPreview ?? true,
linkPreviewHighQuality: request.linkPreviewHighQuality,
});
const response = await promisify(this.client.EditMessage)(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);
const message = new messages.RevokeMessageRequest({
session: this.session,
jid: jid,
sender: key.participant || '',
messageId: key.id,
});
const response = await promisify(this.client.RevokeMessage)(message);
const data = response.toObject();
return this.messageResponse(jid, data);
}
protected async prepareJidsForStatus(contacts: string[]) {
if (!contacts || contacts.length == 0) {
return [];
}
return contacts.map(toJID);
}
public async sendTextStatus(status: TextStatus) {
this.checkStatusRequest(status);
const participants = await this.prepareJidsForStatus(status.contacts);
const message = new messages.MessageRequest({
id: status.id,
jid: Jid.BROADCAST,
participants: participants,
text: status.text,
session: this.session,
backgroundColor: new messages.OptionalString({
@@ -546,23 +618,41 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
font: new messages.OptionalUInt32({
value: status.font,
}),
linkPreview: status.linkPreview ?? true,
linkPreviewHighQuality: status.linkPreviewHighQuality,
});
const response = await promisify(this.client.SendMessage)(message);
const data = response.toObject();
return this.messageResponse(Jid.BROADCAST, data);
}
public async deleteStatus(request: DeleteStatusRequest) {
const participants = await this.prepareJidsForStatus(request.contacts);
const key = parseMessageIdSerialized(request.id, true);
const message = new messages.RevokeMessageRequest({
session: this.session,
jid: BROADCAST_ID,
sender: '',
messageId: key.id,
participants: participants,
});
const response = await promisify(this.client.RevokeMessage)(message);
const data = response.toObject();
return this.messageResponse(BROADCAST_ID, data);
}
protected messageResponse(jid, data) {
const message = data.message ? parseJson(data.message) : null;
const id = buildMessageId({
ID: data.id,
IsFromMe: true,
IsGroup: isJidGroup(jid) || isJidStatusBroadcast(jid),
IsGroup: isJidGroup(jid) || isJidBroadcast(jid),
Chat: jid,
Sender: this.me.id,
});
return {
id: id,
_data: data,
_data: message,
};
}
@@ -602,6 +692,12 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
reply(request: MessageReplyRequest) {
throw new Error('Method not implemented.');
}
@@ -620,11 +716,207 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
}
startTyping(chat: ChatRequest) {
throw new Error('Method not implemented.');
return this.setPresence(WAHAPresenceStatus.TYPING, chat.chatId);
}
stopTyping(chat: ChatRequest) {
throw new Error('Method not implemented.');
return this.setPresence(WAHAPresenceStatus.PAUSED, chat.chatId);
}
/**
* Group methods
*/
public async createGroup(request: CreateGroupRequest) {
const req = new messages.CreateGroupRequest({
session: this.session,
name: request.name,
participants: request.participants.map((p) => toJID(p.id)),
});
const response = await promisify(this.client.CreateGroup)(req);
const data = parseJson(response);
return data;
}
public async joinInfoGroup(code: string): Promise<any> {
const req = new messages.GroupCodeRequest({
session: this.session,
code: code,
});
const response = await promisify(this.client.GetGroupInfoFromLink)(req);
const data = parseJson(response);
return data;
}
public async joinGroup(code: string): Promise<string> {
const req = new messages.GroupCodeRequest({
session: this.session,
code: code,
});
const response = await promisify(this.client.JoinGroupWithLink)(req);
const data = parseJson(response);
return data.jid;
}
public async getGroups(pagination: PaginationParams) {
const req = this.session;
const response = await promisify(this.client.GetGroups)(req);
const data = parseJsonList(response);
switch (pagination.sortBy) {
case GroupSortField.ID:
pagination.sortBy = 'JID';
break;
case GroupSortField.SUBJECT:
pagination.sortBy = 'Name';
break;
}
const paginator = new PaginatorInMemory(pagination);
return paginator.apply(data);
}
protected removeGroupsFieldParticipant(group: any) {
delete group.Participants;
}
public async refreshGroups(): Promise<boolean> {
const req = this.session;
await promisify(this.client.FetchGroups)(req);
return true;
}
public async getGroup(id) {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.GetGroupInfo)(req);
const data = parseJson(response);
return data;
}
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return {
adminsOnly: group.IsLocked,
};
}
public async setInfoAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
jid: id,
value: value,
});
await promisify(this.client.SetGroupLocked)(req);
return;
}
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return {
adminsOnly: group.IsAnnounce,
};
}
public async setMessagesAdminsOnly(id, value) {
const req = new messages.JidBoolRequest({
session: this.session,
jid: id,
value: value,
});
await promisify(this.client.SetGroupAnnounce)(req);
return;
}
public deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public async leaveGroup(id) {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
await promisify(this.client.LeaveGroup)(req);
}
public async setDescription(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
jid: id,
value: description,
});
await promisify(this.client.SetGroupDescription)(req);
}
public async setSubject(id, description) {
const req = new messages.JidStringRequest({
session: this.session,
jid: id,
value: description,
});
await promisify(this.client.SetGroupName)(req);
}
public async getInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.GetGroupInviteLink)(req);
const data = response.toObject();
return data.value;
}
public async revokeInviteCode(id): Promise<string> {
const req = new messages.JidRequest({
session: this.session,
jid: id,
});
const response = await promisify(this.client.RevokeGroupInviteLink)(req);
const data = response.toObject();
return data.value;
}
public async getParticipants(id) {
const group = await this.getGroup(id);
return group.Participants;
}
private async updateParticipants(
id: string,
participants: Array<Participant>,
action: messages.ParticipantAction,
): Promise<any> {
const jids = participants.map((p) => toJID(p.id));
const req = new messages.UpdateParticipantsRequest({
session: this.session,
jid: id,
participants: jids,
action: action,
});
const response = await promisify(this.client.UpdateGroupParticipants)(req);
const data = parseJsonList(response);
return data;
}
public addParticipants(id: string, request: ParticipantsRequest) {
const action = messages.ParticipantAction.ADD;
return this.updateParticipants(id, request.participants, action);
}
public removeParticipants(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.REMOVE;
return this.updateParticipants(id, request.participants, action);
}
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.PROMOTE;
return this.updateParticipants(id, request.participants, action);
}
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
const action = messages.ParticipantAction.DEMOTE;
return this.updateParticipants(id, request.participants, action);
}
async setReaction(request: MessageReactionRequest) {
@@ -634,7 +926,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
jid: key.remoteJid,
messageId: key.id,
reaction: request.reaction,
sender: key.fromMe ? this.me.id : key.participant,
sender: key.fromMe ? this.me.id : key.participant || key.remoteJid,
});
const response = await promisify(this.client.SendReaction)(message);
const data = response.toObject();
@@ -916,7 +1208,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
session: this.session,
id: jid,
});
const response = await promisify(this.client.GetContact)(request);
const response = await promisify(this.client.GetContactById)(request);
const data = parseJson(response);
return this.toWAContact(data);
}
@@ -1089,6 +1381,8 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
if (message.Message.pollUpdateMessage) return;
// Ignore protocol messages
if (message.Message.protocolMessage) return;
// Ignore key distribution messages
if (message.Message.senderKeyDistributionMessage) return;
if (downloadMedia) {
try {
@@ -1110,8 +1404,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const fromToParticipant = getFromToParticipant(message);
const id = buildMessageId(message);
const body = this.extractBody(message.Message);
const replyTo = null; // TODO: this.extractReplyTo(message.message);
const replyTo = this.extractReplyTo(message.Message);
let ack;
if (message.Status) {
ack = statusToAck(message.Status);
@@ -1119,12 +1412,14 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
ack = message.Info.IsFromMe ? WAMessageAck.SERVER : WAMessageAck.DEVICE;
}
const mediaContent = extractMediaContent(message.Message);
const source = this.getSourceDeviceByMsg(message);
return {
id: id,
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.Info.IsFromMe,
source: source,
body: body,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
@@ -1141,6 +1436,17 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
};
}
private getSourceDeviceByMsg(message): MessageSource {
if (!message.Info.IsFromMe) {
return MessageSource.APP;
}
// @ts-ignore
const myJid = this.me.jid;
const myDeviceId = extractDeviceId(myJid);
const sentDeviceId = extractDeviceId(message.Info.Sender);
return sentDeviceId === myDeviceId ? MessageSource.API : MessageSource.APP;
}
private extractBody(message) {
if (!message) {
return null;
@@ -1156,6 +1462,25 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return body;
}
protected extractReplyTo(message): ReplyToMessage | null {
const msgType = getContentType(message);
const contextInfo = message[msgType]?.contextInfo;
if (!contextInfo) {
return null;
}
const quotedMessage = contextInfo.quotedMessage;
if (!quotedMessage) {
return null;
}
const body = this.extractBody(quotedMessage);
return {
id: contextInfo.stanzaID,
participant: toCusFormat(contextInfo.participant),
body: body,
_data: quotedMessage,
};
}
public async getEngineInfo() {
const clientState = this.client?.getChannel().getConnectivityState(false);
const streamState = this.stream$?.client
@@ -1247,11 +1572,13 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
const fromToParticipant = getFromToParticipant(message);
const reactionMessage = message.Message.reactionMessage;
const messageId = this.buildMessageIdFromKey(reactionMessage.key);
const source = this.getSourceDeviceByMsg(message);
const reaction: WAMessageReaction = {
id: id,
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.Info.IsFromMe,
source: source,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
reaction: {
@@ -1369,3 +1696,11 @@ function parseTimestampToSeconds(timestamp: string): number {
}
return Math.floor(ms / 1000);
}
export function getMessageIdFromSerialized(serialized: string): string | null {
if (!serialized) {
return null;
}
const key = parseMessageIdSerialized(serialized, true);
return key.id;
}
+51
View File
@@ -0,0 +1,51 @@
import * as NodeCache from 'node-cache';
export class PresenceProxy {
private cache: NodeCache;
public proxy: Record<string, any>;
constructor() {
this.cache = new NodeCache({
stdTTL: 60, // 1 minute
checkperiod: 60, // 1 minute
useClones: false,
});
this.proxy = new Proxy(
{},
{
get: (_, prop: string) => {
return this.cache.get(prop);
},
set: (_, prop: string, value) => {
this.cache.set(prop, value);
return true;
},
deleteProperty: (_, prop: string) => {
return this.cache.del(prop) > 0;
},
has: (_, prop: string) => {
return this.cache.has(prop);
},
ownKeys: () => {
return this.cache.keys();
},
getOwnPropertyDescriptor: (_, prop: string) => {
if (this.cache.has(prop)) {
return {
enumerable: true,
configurable: true,
};
}
return undefined;
},
},
);
}
}
+387 -124
View File
@@ -5,17 +5,16 @@ import makeWASocket, {
DisconnectReason,
downloadMediaMessage,
extractMessageContent,
generateMessageIDV2,
getAggregateVotesInPollMessage,
getContentType,
getKeyAuthor,
getUrlFromDirectPath,
isJidGroup,
isJidNewsletter,
isJidStatusBroadcast,
isJidUser,
isRealMessage,
jidNormalizedUser,
makeCacheableSignalKeyStore,
MiscMessageGenerationOptions,
NewsletterMetadata,
normalizeMessageContent,
PresenceData,
@@ -37,9 +36,19 @@ import {
LabelAssociationType,
} from '@adiwajshing/baileys/lib/Types/LabelAssociation';
import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message';
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import {
isJidBroadcast,
isLidUser,
} from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino';
import { UnprocessableEntityException } from '@nestjs/common';
import {
ensureSuffix,
getChannelInviteLink,
getPublicUrlFromDirectPath,
isNewsletter,
WhatsappSession,
} from '@waha/core/abc/session.abc';
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
@@ -47,8 +56,16 @@ import {
ToGroupV2UpdateEvent,
} from '@waha/core/engines/noweb/groups.noweb';
import { sendButtonMessage } from '@waha/core/engines/noweb/noweb.buttons';
import { NowebAuthFactoryCore } from '@waha/core/engines/noweb/NowebAuthFactoryCore';
import { NowebInMemoryStore } from '@waha/core/engines/noweb/store/NowebInMemoryStore';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { toVcard } from '@waha/core/helpers';
import { createAgentProxy } from '@waha/core/helpers.proxy';
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
import { QR } from '@waha/core/QR';
import { flipObject, splitAt } from '@waha/helpers';
import { PairingCodeResponse } from '@waha/structures/auth.dto';
import { CallData } from '@waha/structures/calls.dto';
@@ -71,8 +88,43 @@ import {
PinDuration,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageContactVcardRequest,
MessageDestination,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto';
import {
ACK_UNKNOWN,
SECOND,
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '@waha/structures/enums.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto';
import {
Label,
LabelChatAssociation,
@@ -81,17 +133,31 @@ import {
} from '@waha/structures/labels.dto';
import { ReplyToMessage } from '@waha/structures/message.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '@waha/structures/presence.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import {
BROADCAST_ID,
DeleteStatusRequest,
StatusRequest,
TextStatus,
} from '@waha/structures/status.dto';
import {
EnginePayload,
PollVote,
PollVotePayload,
WAMessageAckBody,
WAMessageRevokedBody,
} from '@waha/structures/webhooks.dto';
import { LoggerBuilder } from '@waha/utils/logging';
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
import { exclude } from '@waha/utils/reactive/ops/exclude';
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
import { SinglePeriodicJobRunner } from '@waha/utils/SinglePeriodicJobRunner';
import { StatusTracker } from '@waha/utils/StatusTracker';
import * as Buffer from 'buffer';
import { Agent } from 'https';
import * as lodash from 'lodash';
@@ -106,72 +172,11 @@ import {
Observable,
partition,
share,
Subject,
} from 'rxjs';
import { map } from 'rxjs/operators';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const promiseRetry = require('promise-retry');
import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageContactVcardRequest,
MessageDestination,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
MessageTextRequest,
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../../../structures/chatting.dto';
import {
ACK_UNKNOWN,
SECOND,
WAHAEngine,
WAHAEvents,
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '../../../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../../../structures/groups.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '../../../structures/presence.dto';
import {
WAMessage,
WAMessageReaction,
} from '../../../structures/responses.dto';
import { MeInfo } from '../../../structures/sessions.dto';
import {
BROADCAST_ID,
DeleteStatusRequest,
TextStatus,
} from '../../../structures/status.dto';
import {
ensureSuffix,
getChannelInviteLink,
getPublicUrlFromDirectPath,
isNewsletter,
WhatsappSession,
} from '../../abc/session.abc';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '../../exceptions';
import { toVcard } from '../../helpers';
import { createAgentProxy } from '../../helpers.proxy';
import { QR } from '../../QR';
import { NowebAuthFactoryCore } from './NowebAuthFactoryCore';
import { INowebStore } from './store/INowebStore';
import { NowebPersistentStore } from './store/NowebPersistentStore';
import { NowebStorageFactoryCore } from './store/NowebStorageFactoryCore';
@@ -217,6 +222,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
store: INowebStore;
private qr: QR;
private statusTracker = new StatusTracker();
public constructor(config) {
super(config);
this.shouldRestart = true;
@@ -255,6 +262,15 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.authNOWEBStore = null;
}
protected set status(value: WAHASessionStatus) {
this.statusTracker.track(value);
super.status = value;
}
public get status() {
return super.status;
}
async start() {
this.status = WAHASessionStatus.STARTING;
await this.buildClient().catch((err) => {
@@ -383,7 +399,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.listenConnectionEvents();
this.subscribeEngineEvents2();
this.listenContactsUpdatePictureProfile();
this.enableAutoRestart();
// this.enableAutoRestart();
}
private enableAutoRestart() {
@@ -420,6 +436,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
);
return;
}
this.startDelayedJob.schedule(async () => {
if (!this.shouldRestart) {
this.logger.warn(
@@ -444,12 +461,36 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.resubscribeToKnownPresences();
return;
} else if (connection === 'close') {
const shouldReconnect =
// @ts-ignore: Property output does not exist on type 'Error'
lastDisconnect.error?.output?.statusCode !==
DisconnectReason.loggedOut;
this.qr.save('');
// reconnect if not logged out
const statusCode = lastDisconnect.error?.output?.statusCode;
// Restart required from the server
const restartRequired = statusCode === DisconnectReason.restartRequired;
if (restartRequired) {
this.restartClient();
return;
}
// Stuck in STARTING status
if (this.statusTracker.isStuckInStarting()) {
this.logger.error(
'Session stuck in STARTING status, force stopping the session.',
);
await this.failed();
return;
}
// Do not reconnect if the QR code has not been scanned yet
if (this.status == WAHASessionStatus.SCAN_QR_CODE) {
this.logger.warn(
'QR code has not been scanned yet, force stopping the session.',
);
await this.failed();
return;
}
// Reconnect if not logged out
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
if (shouldReconnect) {
if (lastDisconnect.error) {
this.logger.info(
@@ -457,12 +498,14 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
);
}
this.restartClient();
} else {
this.logger.error(
`Connection closed due to '${lastDisconnect.error}', do not reconnect the session.`,
);
await this.failed();
return;
}
// Unknown error or logged out
this.logger.error(
`Connection closed due to '${lastDisconnect.error}', do not reconnect the session.`,
);
await this.failed();
}
// Save QR
@@ -692,6 +735,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
};
}
async generateNewMessageId(): Promise<string> {
return this.generateMessageID();
}
async sendText(request: MessageTextRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const message = {
@@ -699,14 +746,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
mentions: request.mentions?.map(toJID),
linkPreview: this.getLinkPreview(request),
};
const options = await this.getMessageOptions(request);
const options: any = await this.getMessageOptions(request);
options.linkPreviewHighQuality = request.linkPreviewHighQuality;
return this.sock.sendMessage(chatId, message, options);
}
public deleteMessage(chatId: string, messageId: string) {
const jid = toJID(this.ensureSuffix(chatId));
const key = parseMessageIdSerialized(messageId);
return this.sock.sendMessage(jid, { delete: key });
const options = {
messageId: this.generateMessageID(),
};
return this.sock.sendMessage(jid, { delete: key }, options);
}
public editMessage(
@@ -720,18 +771,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
text: request.text,
mentions: request.mentions?.map(toJID),
edit: key,
linkPreview: this.getLinkPreview(request),
linkPreviewHighQuality: request.linkPreviewHighQuality,
};
return this.sock.sendMessage(jid, message);
const options = {
messageId: this.generateMessageID(),
};
return this.sock.sendMessage(jid, message, options);
}
async sendContactVCard(request: MessageContactVcardRequest) {
const chatId = toJID(this.ensureSuffix(request.chatId));
const contacts = request.contacts.map((el) => ({ vcard: toVcard(el) }));
await this.sock.sendMessage(chatId, {
contacts: {
contacts: contacts,
},
});
const options = await this.getMessageOptions(request);
const msg = { contacts: { contacts: contacts } };
return await this.sock.sendMessage(chatId, msg, options);
}
async sendPoll(request: MessagePollRequest) {
@@ -771,6 +825,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendLinkCustomPreview(
request: MessageLinkCustomPreviewRequest,
): Promise<any> {
throw new AvailableInPlusVersion();
}
protected async uploadMedia(
file: RemoteFile | BinaryFile,
type,
@@ -795,13 +855,15 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
);
}
sendLocation(request: MessageLocationRequest) {
return this.sock.sendMessage(request.chatId, {
async sendLocation(request: MessageLocationRequest) {
const msg = {
location: {
degreesLatitude: request.latitude,
degreesLongitude: request.longitude,
},
});
};
const options = await this.getMessageOptions(request);
return await this.sock.sendMessage(request.chatId, msg, options);
}
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
@@ -817,14 +879,17 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
forward: forwardMessage,
force: true,
};
const result = await this.sock.sendMessage(chatId, message, {});
const options = await this.getMessageOptions(request);
const result = await this.sock.sendMessage(chatId, message, options);
return this.toWAMessage(result);
}
sendLinkPreview(request: MessageLinkPreviewRequest) {
async sendLinkPreview(request: MessageLinkPreviewRequest) {
const text = `${request.title}\n${request.url}`;
const chatId = toJID(this.ensureSuffix(request.chatId));
return this.sock.sendMessage(chatId, { text: text });
const msg = { text: text };
const options = await this.getMessageOptions(request);
return this.sock.sendMessage(chatId, msg, options);
}
async sendSeen(request: SendSeenRequest) {
@@ -913,13 +978,34 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
async setReaction(request: MessageReactionRequest) {
const key = parseMessageIdSerialized(request.messageId);
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
if (isNewsletter(key.remoteJid)) {
let serverId = Number(key.id);
if (!serverId) {
const msg = await this.store.getMessageById(key.remoteJid, key.id);
if (msg) {
// @ts-ignore
serverId = Number(msg.key.server_id);
}
}
if (!serverId) {
throw new UnprocessableEntityException(
`Unable to get server id for channel message '${key.id}'`,
);
}
return this.sock.newsletterReactMessage(
key.remoteJid,
serverId.toString(),
request.reaction,
);
} else {
const reactionMessage = {
react: {
text: request.reaction,
key: key,
},
};
return this.sock.sendMessage(key.remoteJid, reactionMessage);
}
}
async setStar(request: MessageStarRequest) {
@@ -1139,12 +1225,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async fetchContactProfilePicture(id: string) {
const contact = this.ensureSuffix(id);
if (isJidNewsletter(id)) {
return null;
}
if (isJidStatusBroadcast(id)) {
return null;
}
try {
const url = await this.sock.profilePictureUrl(contact, 'image');
return url;
@@ -1189,6 +1269,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return lodash.keyBy(groups, 'id');
}
protected removeGroupsFieldParticipant(group: any) {
delete group.participants;
}
public async refreshGroups(): Promise<boolean> {
this.store.resetGroupsCache();
await this.store.getGroups({});
@@ -1197,13 +1281,37 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async getGroup(id) {
const groups = await this.getGroups({});
return groups[id];
const group = groups[id];
if (!group) {
throw new Error(`Group with id '${id}' not found`);
}
return group;
}
public async deleteGroup(id) {
throw new NotImplementedByEngineError();
}
public async getInfoAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return { adminsOnly: group.restrict };
}
public async setInfoAdminsOnly(id, value) {
const setting = value ? 'locked' : 'unlocked';
return await this.sock.groupSettingUpdate(id, setting);
}
public async getMessagesAdminsOnly(id): Promise<SettingsSecurityChangeInfo> {
const group = await this.getGroup(id);
return { adminsOnly: group.announce };
}
public async setMessagesAdminsOnly(id, value) {
const setting = value ? 'announcement' : 'not_announcement';
return await this.sock.groupSettingUpdate(id, setting);
}
public async leaveGroup(id) {
return this.sock.groupLeave(id);
}
@@ -1275,7 +1383,7 @@ 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] = [];
this.store.presences[remoteJid] = {};
await this.sock.presenceSubscribe(remoteJid);
await sleep(1000);
}
@@ -1291,16 +1399,101 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
/**
* Status methods
*/
public async sendStatusMessage(
message: any,
options: any,
jids: string[],
batchSize?: number,
) {
if (!batchSize || batchSize == 0) {
batchSize = 5_000;
}
const chunks = lodash.chunk(jids, batchSize);
if (chunks.length == 0) {
throw new UnprocessableEntityException('No participants to send status');
}
const logger = this.logger.child({
'message.id': options.messageId,
chunks: chunks.length,
size: batchSize,
});
logger.info(`Sending status message to ${jids.length} participants`);
let result = null;
for (const [index, participants] of chunks.entries()) {
const batchOptions = { ...options };
batchOptions.statusJidList = participants;
const r = await this.sendStatusMessageOneChunk(
message,
batchOptions,
logger,
index,
);
result = result || r;
}
logger.info(
`Sending status message to ${jids.length} participants - success`,
);
return result;
}
private async sendStatusMessageOneChunk(
message: any,
options: any,
logger: any,
index: number,
) {
// https://github.com/IndigoUnited/node-promise-retry
const retryOptions = {
retries: 5,
minTimeout: 1000,
maxTimeout: 6000,
};
try {
const resp = await promiseRetry((retry, number) => {
return this.sock
.sendMessage(BROADCAST_ID, message, options)
.catch(retry);
}, retryOptions);
logger.info(`Sending status message (${index + 1} chunk) - success`);
return resp;
} catch (err) {
logger.error(`Sending status message (${index + 1} chunk - failed`);
logger.error(err, err.stack);
throw err;
}
}
public async sendTextStatus(status: TextStatus) {
const message = { text: status.text };
const message = {
text: status.text,
linkPreview: this.getLinkPreview(status),
};
const jids = await this.prepareJidsForStatus(status.contacts);
const options = {
if (!status.id) {
this.upsertMeInJIDs(jids);
}
const messageId = this.prepareMessageIdForStatus(status);
const options: MiscMessageGenerationOptions = {
backgroundColor: status.backgroundColor,
font: status.font,
statusJidList: jids,
linkPreviewHighQuality: status.linkPreviewHighQuality,
messageId: messageId,
};
return await this.sendStatusMessage(
message,
options,
jids,
status.contacts?.length,
);
}
return await this.sock.sendMessage(BROADCAST_ID, message, options);
protected prepareMessageIdForStatus(status: StatusRequest) {
if (status.id) {
this.saveSentMessageId(status.id);
return status.id;
}
return this.generateMessageID();
}
protected async prepareJidsForStatus(contacts: string[]) {
@@ -1310,7 +1503,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
} else {
jids = await this.fetchMyContactsJids();
}
this.upsertMeInJIDs(jids);
return jids;
}
@@ -1326,10 +1518,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
key.fromMe = true;
key.remoteJid = BROADCAST_ID;
const jids = await this.prepareJidsForStatus(request.contacts);
this.upsertMeInJIDs(jids);
const newMessageId = this.generateMessageID();
const options = {
statusJidList: jids,
messageId: newMessageId,
};
return await this.sock.sendMessage(BROADCAST_ID, { delete: key }, options);
return await this.sendStatusMessage(
{ delete: key },
options,
jids,
request.contacts?.length,
);
}
protected upsertMeInJIDs(jids: string[]) {
@@ -1338,7 +1538,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
const myJID = jidNormalizedUser(this.sock.authState.creds.me.id);
if (!jids.includes(myJID)) {
jids.push(myJID);
// insert my jid first
jids.unshift(myJID);
}
}
@@ -1475,6 +1676,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
const messagesRevoked$ = messagesUpsert$.pipe(
// @ts-ignore
filter(
(message) =>
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE,
),
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
const afterMessage = await this.toWAMessage(message);
return {
after: afterMessage,
before: null,
_data: message,
};
}),
);
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
//
// Message Reactions
//
@@ -1709,11 +1928,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const fromToParticipant = getFromToParticipant(message);
const reactionMessage = message.message.reactionMessage;
const messageId = buildMessageId(reactionMessage.key);
const source = this.getMessageSource(message.key.id);
const reaction: WAMessageReaction = {
id: id,
timestamp: ensureNumber(message.messageTimestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
source: source,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
reaction: {
@@ -1732,6 +1953,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (message.message.reactionMessage) return;
// Ignore poll votes, we have dedicated handler for that
if (message.message.pollUpdateMessage) return;
// Ignore calls, we have dedicated handler for that
if (message.message.call?.callKey) return;
// Ignore revoke, we have a dedicated handler for that
if (
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.REVOKE
)
return;
if (
message.message.protocolMessage?.type ===
proto.Message.ProtocolMessage.Type.EPHEMERAL_SYNC_RESPONSE
)
return;
// Ignore key distribution messages
if (message.message.senderKeyDistributionMessage) return;
if (downloadMedia) {
try {
@@ -1759,12 +1995,14 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
const replyTo = this.extractReplyTo(message.message);
const ack = message.ack || message.status - 1;
const mediaContent = extractMediaContent(message.message);
const source = this.getMessageSource(message.key.id);
return Promise.resolve({
id: id,
timestamp: ensureNumber(message.messageTimestamp),
from: toCusFormat(fromToParticipant.from),
fromMe: message.key.fromMe,
body: body,
source: source,
body: body || null,
to: toCusFormat(fromToParticipant.to),
participant: toCusFormat(fromToParticipant.participant),
// Media
@@ -1786,21 +2024,28 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (!message) {
return null;
}
let body = message.conversation;
const content = extractMessageContent(message);
if (!content) {
return null;
}
let body = content.conversation || null;
if (!body) {
// Some of the messages have no conversation, but instead have text in extendedTextMessage
// https://github.com/devlikeapro/waha/issues/90
body = message.extendedTextMessage?.text;
body = content.extendedTextMessage?.text;
}
if (!body) {
// Populate from caption
const mediaContent = extractMediaContent(message);
const mediaContent = extractMediaContent(content);
// @ts-ignore - AudioMessage doesn't have caption field
body = mediaContent?.caption;
}
// Response for buttons
if (!body) {
body = message.templateButtonReplyMessage?.selectedDisplayText;
body = content.templateButtonReplyMessage?.selectedDisplayText;
}
if (!body) {
body = content.buttonsResponseMessage?.selectedDisplayText;
}
return body;
}
@@ -1820,6 +2065,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
id: contextInfo.stanzaId,
participant: toCusFormat(contextInfo.participant),
body: body,
_data: quotedMessage,
};
}
@@ -1989,15 +2235,23 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return this.mediaManager.processMedia(processor, message, this.name);
}
protected async getMessageOptions(request: any): Promise<any> {
protected async getMessageOptions(request: {
chatId: string;
reply_to?: string;
}) {
const jid = toJID(request.chatId);
let quoted;
if (request.reply_to) {
const key = parseMessageIdSerialized(request.reply_to, true);
quoted = await this.store.loadMessage(toJID(request.chatId), key.id);
quoted = await this.store.loadMessage(jid, key.id);
}
const chat = await this.store.getChat(jid);
const messageId = this.generateMessageID();
return {
quoted: quoted,
ephemeralExpiration: chat?.ephemeralExpiration,
messageId: messageId,
};
}
@@ -2016,6 +2270,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
return linkPreview;
}
protected generateMessageID() {
const id = generateMessageIDV2(this.sock.user?.id);
this.saveSentMessageId(id);
return id;
}
}
function hasPath(url: string) {
@@ -2100,7 +2360,7 @@ export function toCusFormat(remoteJid) {
if (isJidGroup(remoteJid)) {
return remoteJid;
}
if (isJidStatusBroadcast(remoteJid)) {
if (isJidBroadcast(remoteJid)) {
return remoteJid;
}
if (isLidUser(remoteJid)) {
@@ -2131,12 +2391,15 @@ export function toJID(chatId) {
if (isJidGroup(chatId)) {
return chatId;
}
if (isJidStatusBroadcast(chatId)) {
if (isJidBroadcast(chatId)) {
return chatId;
}
if (isNewsletter(chatId)) {
return chatId;
}
if (isLidUser(chatId)) {
return chatId;
}
const number = chatId.split('@')[0];
return number + '@s.whatsapp.net';
}
@@ -8,6 +8,8 @@ export interface IMessagesRepository {
upsertOne(message: any): Promise<void>;
getById(id: string): Promise<any | null>;
getAllByJid(
jid: string,
filter: GetChatMessagesFilter,
@@ -30,6 +30,8 @@ export interface INowebStore {
getChats(pagination: PaginationParams, broadcast: boolean): Promise<Chat[]>;
getChat(jid: string): Promise<Chat | null>;
getContacts(pagination: PaginationParams): Promise<Contact[]>;
getContactById(jid: string): Promise<Contact>;
@@ -26,6 +26,10 @@ export class NowebInMemoryStore implements INowebStore {
constructor() {
this.store = makeInMemoryStore({ logger: logger });
const presences = {};
this.store.presences = presences;
// Adjust inline even handler
this.store.setPresences(presences);
}
init(): Promise<void> {
@@ -65,6 +69,10 @@ export class NowebInMemoryStore implements INowebStore {
throw new BadRequestException(this.errorMessage);
}
getChat(jid: string): Promise<Chat | null> {
return null;
}
getContacts(pagination: PaginationParams): Promise<Contact[]> {
throw new BadRequestException(this.errorMessage);
}
@@ -91,7 +99,7 @@ export class NowebInMemoryStore implements INowebStore {
async getGroups(pagination: PaginationParams): Promise<GroupMetadata[]> {
const response = await this.socket?.groupFetchAllParticipating();
const groups = Object.values(response);
const groups: any[] = Object.values(response);
const paginator = new PaginatorInMemory(pagination);
return paginator.apply(groups);
}
@@ -269,6 +269,9 @@ export class NowebPersistentStore implements INowebStore {
if (!group) {
group = { id: id, participants: [] } as GroupMetadata;
}
if (!group.participants) {
group.participants = [];
}
const participantsById = new DefaultMap<string, GroupParticipant>((key) => {
return { id: key, admin: null } as GroupParticipant;
@@ -360,7 +363,12 @@ export class NowebPersistentStore implements INowebStore {
if (update.imgUrl === 'changed') {
contact.imgUrl = this.socket
? await this.socket?.profilePictureUrl(contact.id)
? await this.socket?.profilePictureUrl(contact.id).catch((error) => {
this.logger.warn(
`failed to get profile picture for contact '${contact.id}': ${error}`,
);
return undefined;
})
: undefined;
} else if (update.imgUrl === 'removed') {
delete contact.imgUrl;
@@ -427,7 +435,12 @@ export class NowebPersistentStore implements INowebStore {
}
async loadMessage(jid: string, id: string) {
const data = await this.messagesRepo.getByJidById(jid, id);
let data;
if (!jid) {
data = await this.messagesRepo.getById(id);
} else {
data = await this.messagesRepo.getByJidById(jid, id);
}
if (!data) {
return null;
}
@@ -454,6 +467,10 @@ export class NowebPersistentStore implements INowebStore {
return this.chatRepo.getAllWithMessages(pagination, broadcast);
}
async getChat(jid: string): Promise<Chat | null> {
return await this.chatRepo.getById(jid);
}
private shouldFetchGroup(): boolean {
const timePassed = new Date().getTime() - this.lastTimeGroupFetch.getTime();
return timePassed > this.GROUP_METADATA_CACHE_TIME;
+21
View File
@@ -2,9 +2,11 @@ import { GetChatMessagesFilter } from '@waha/structures/chats.dto';
import { Label } from '@waha/structures/labels.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
import { TextStatus } from '@waha/structures/status.dto';
import { EventEmitter } from 'events';
import * as lodash from 'lodash';
import { Client, Events } from 'whatsapp-web.js';
import { Message } from 'whatsapp-web.js/src/structures';
import { exposeFunctionIfAbsent } from 'whatsapp-web.js/src/util/Puppeter';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { LoadWAHA } = require('./_WAHA.js');
@@ -19,10 +21,13 @@ const { LoadPaginator } = require('./_Paginator.js');
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
export class WebjsClientCore extends Client {
public events = new EventEmitter();
constructor(options) {
super(options);
// Wait until it's READY and inject more utils
this.on(Events.READY, async () => {
await this.attachCustomEventListeners();
await this.injectWaha();
});
}
@@ -33,6 +38,22 @@ export class WebjsClientCore extends Client {
await this.pupPage.evaluate(LoadWAHA);
}
async attachCustomEventListeners() {
await exposeFunctionIfAbsent(
this.pupPage,
'onNewMessageId',
(messageId: string) => {
this.events.emit('message.id', { id: messageId });
return;
},
);
}
async destroy() {
this.events.removeAllListeners();
await super.destroy();
}
async setPushName(name: string) {
await this.pupPage.evaluate(async (pushName) => {
return await window['WAHA'].WAWebSetPushnameConnAction.setPushname(
+30 -3
View File
@@ -41,6 +41,7 @@ import {
ChatRequest,
CheckNumberStatusQuery,
EditMessageRequest,
MessageButtonReply,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
@@ -73,7 +74,11 @@ import {
import { Label, LabelDTO, LabelID } from '@waha/structures/labels.dto';
import { ReplyToMessage } from '@waha/structures/message.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { WAMessage, WAMessageReaction } from '@waha/structures/responses.dto';
import {
MessageSource,
WAMessage,
WAMessageReaction,
} from '@waha/structures/responses.dto';
import { MeInfo } from '@waha/structures/sessions.dto';
import { StatusRequest, TextStatus } from '@waha/structures/status.dto';
import {
@@ -510,6 +515,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const options = {
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as string[],
linkPreview: request.linkPreview,
};
return message.edit(request.text, options);
}
@@ -535,6 +541,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
throw new AvailableInPlusVersion();
}
sendButtonsReply(request: MessageButtonReply) {
throw new AvailableInPlusVersion();
}
async sendLocation(request: MessageLocationRequest) {
const location = new Location(request.latitude, request.longitude, {
name: request.title,
@@ -894,6 +904,13 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return paginator.apply(groups);
}
protected removeGroupsFieldParticipant(group: any) {
delete group.groupMetadata?.participants;
delete group.groupMetadata?.pendingParticipants;
delete group.groupMetadata?.pastParticipants;
delete group.groupMetadata?.membershipApprovalRequests;
}
public async refreshGroups(): Promise<boolean> {
return true;
}
@@ -1040,7 +1057,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
// Exclude GUEST, browser saves the data
// when we search channels or getting messages
channels = channels.filter((channel) => channel.role === 'GUEST');
channels = channels.filter((channel) => channel.role !== 'GUEST');
const promises = channels.map(async (channel) =>
this.whatsapp.getProfilePicUrl(channel.id),
@@ -1146,6 +1163,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
* END - Methods for API
*/
subscribeEngineEvents2() {
// Save sent message in cache
this.whatsapp.events.on('message.id', (data) => {
this.saveSentMessageId(data.id);
});
//
// All
//
@@ -1345,10 +1367,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
}
private processMessageReaction(reaction: Reaction): WAMessageReaction {
const source = this.getMessageSource(reaction.id.id);
return {
id: reaction.id._serialized,
from: reaction.senderId,
fromMe: reaction.id.fromMe,
source: source,
participant: reaction.senderId,
to: reaction.id.remote,
timestamp: reaction.timestamp,
@@ -1361,12 +1385,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
protected toWAMessage(message: Message): WAMessage {
const replyTo = this.extractReplyTo(message);
const source = this.getMessageSource(message.id.id);
// @ts-ignore
return {
id: message.id._serialized,
timestamp: message.timestamp,
from: message.from,
fromMe: message.fromMe,
source: source,
to: message.to,
body: message.body,
// Media
@@ -1396,6 +1422,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
id: quotedMsg.id?.id,
participant: quotedMsg.author || quotedMsg.from,
body: quotedMsg.caption || quotedMsg.body,
_data: quotedMsg,
};
}
@@ -1422,7 +1449,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
let mentions = request.mentions;
mentions = mentions ? mentions.map(this.ensureSuffix) : undefined;
const quotedMessageId = request.reply_to;
const quotedMessageId = request.reply_to || request.replyTo;
return {
mentions: mentions,
@@ -66,7 +66,7 @@ export class WebhookSender {
const headers = {
'content-type': 'application/json',
};
Object.assign(headers, this.getWebhookHeader());
Object.assign(headers, this.getWebhookHeader(json));
Object.assign(headers, this.getHMACHeaders(body));
const ctx = {
id: headers['X-Webhook-Request-Id'],
@@ -153,12 +153,13 @@ export class WebhookSender {
};
}
protected getWebhookHeader() {
protected getWebhookHeader(json: any) {
const timestamp = json.timestamp?.toString() || Date.now().toString();
return {
// UUID, no '-' in it
'X-Webhook-Request-Id': ulid(),
// unix timestamp with ms
'X-Webhook-Timestamp': Date.now().toString(),
'X-Webhook-Timestamp': timestamp,
};
}
+7 -5
View File
@@ -45,9 +45,11 @@ import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository
import { LocalStoreCore } from './storage/LocalStoreCore';
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
constructor() {
constructor(name: string) {
const encoded = Buffer.from(name, 'utf-8').toString('base64');
super(
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
`WAHA Core support only 'default' session. You tried to access '${name}' session (base64: ${encoded}). ` +
`If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
);
}
}
@@ -115,7 +117,7 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
private onlyDefault(name: string) {
if (name !== this.DEFAULT) {
throw new OnlyDefaultSessionIsAllowed();
throw new OnlyDefaultSessionIsAllowed(name);
}
}
@@ -362,9 +364,9 @@ export class SessionManagerCore extends SessionManager implements OnModuleInit {
try {
engineInfo = await promiseTimeout(1000, session.getEngineInfo());
} catch (error) {
this.log.warn(
this.log.debug(
{ session: session.name, error: `${error}` },
'Error while getting engine info',
'Can not get engine info',
);
}
}
+3
View File
@@ -31,6 +31,9 @@ function splitAt(str: string, index) {
}
export function sortObjectByValues(obj: any) {
if (!obj) {
return obj;
}
return Object.fromEntries(
// @ts-ignore
Object.entries(obj).sort(([, v1], [, v2]) => v2 - v1),
+2 -1
View File
@@ -4,6 +4,7 @@ export const MessageIdApiParam = ApiParam({
name: 'messageId',
required: true,
type: 'string',
description: 'Message ID',
description:
'Message ID in format <code>{fromMe}_{chat}_{message_id}[_{participant}]</code>',
example: 'true_123456789@c.us_BAE6A33293978B16',
});
+28
View File
@@ -0,0 +1,28 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
export function IsFileType(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
name: 'isFileType',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, _args: ValidationArguments) {
return (
value &&
typeof value === 'object' &&
(typeof value.data === 'string' || typeof value.url === 'string')
);
},
defaultMessage() {
return 'must contain either "data" or "url".';
},
},
});
};
}
+8
View File
@@ -16,9 +16,17 @@ export class SessionQuery {
*/
export class SessionBaseRequest {
@IsString()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
export class Result {
success: boolean = true;
}
/**
* Responses
*/
export class CountResponse {
count: number = 0;
}
+109 -1
View File
@@ -4,8 +4,17 @@ import {
ApiProperty,
getSchemaPath,
} from '@nestjs/swagger';
import { IsFileType } from '@waha/nestjs/validation/IsFileType';
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { plainToInstance, Transform, Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUrl,
ValidateNested,
} from 'class-validator';
import {
SessionBaseRequest,
@@ -14,6 +23,9 @@ import {
} from './base.dto';
import {
BinaryFile,
FileContent,
FileType,
FileURL,
RemoteFile,
VideoBinaryFile,
VideoRemoteFile,
@@ -64,6 +76,7 @@ export class GetPresenceQuery extends ChatQuery {}
*/
export class ChatRequest extends SessionBaseRequest {
@ChatIdProperty()
@IsString()
chatId: string;
}
@@ -158,6 +171,74 @@ export class MessageTextRequest extends ChatRequest {
reply_to?: string;
linkPreview?: boolean = true;
linkPreviewHighQuality?: boolean = false;
}
@ApiExtraModels(FileURL, FileContent)
export class LinkPreviewData {
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
url: string = 'https://github.com/';
@IsString()
title: string = 'Your Title';
@IsString()
description: string = 'Check this out, amazing!';
@IsOptional()
@ValidateNested()
@Transform(
({ value }) => {
if (value?.url) {
return plainToInstance(FileURL, value);
}
if (value?.data) {
return plainToInstance(FileContent, value);
}
return value;
},
{ toClassOnly: true },
)
@IsFileType({ message: 'Image must contain either "data" or "url".' })
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(FileURL) },
{ $ref: getSchemaPath(FileContent) },
],
example: {
url:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
},
})
image?: FileType;
}
export class MessageLinkCustomPreviewRequest extends ChatRequest {
@IsString()
@ApiProperty({
description:
'The text to send. MUST include the URL provided in preview.url',
})
text: string = 'Check this out! https://github.com/';
@IsBoolean()
@IsOptional()
linkPreviewHighQuality?: boolean = true;
@ValidateNested()
@Type(() => LinkPreviewData)
preview: LinkPreviewData;
@ReplyToProperty()
@IsString()
@IsOptional()
reply_to?: string;
}
export class EditMessageRequest {
@@ -165,6 +246,9 @@ export class EditMessageRequest {
@ApiHideProperty()
mentions?: string[];
linkPreview?: boolean = true;
linkPreviewHighQuality?: boolean = false;
}
export class MessageReplyRequest extends MessageTextRequest {
@@ -329,3 +413,27 @@ export class MessageDestination {
from: string;
fromMe: boolean;
}
export class MessageButtonReply extends ChatRequest {
@ReplyToProperty()
@IsString()
@IsNotEmpty()
replyTo?: string;
@IsString()
@IsNotEmpty()
selectedDisplayText: string;
@IsString()
@IsNotEmpty()
selectedButtonID: string;
}
export class NewMessageIDResponse {
@ApiProperty({
description: 'Pre-generated message id',
example: 'BBBBBBBBBBBBBBBBB',
required: true,
})
id: string;
}
+3
View File
@@ -29,6 +29,9 @@ export enum WAHAEvents {
ENGINE_EVENT = 'engine.event',
}
export type AllEventType = WAHAEvents | '*';
export const AllEvents = [...Object.values(WAHAEvents), '*'];
// All but no state.change, it's internal one
export const WAHAEventsWild = Object.values(WAHAEvents).filter(
(e) => e !== WAHAEvents.STATE_CHANGE && e !== WAHAEvents.ENGINE_EVENT,
+31
View File
@@ -1,4 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBase64, IsNotEmpty, IsString, IsUrl } from 'class-validator';
export class Base64File {
mimetype: string;
@@ -117,3 +118,33 @@ export class VideoRemoteFile {
})
url: string;
}
export class FileURL {
@IsString()
@IsNotEmpty()
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
@ApiProperty({
example:
process.env.WHATSAPP_SWAGGER_JPG_EXAMPLE_URL ||
'https://github.com/devlikeapro/waha/raw/core/examples/waha.jpg',
})
url?: string;
}
export class FileContent {
@IsString()
@IsNotEmpty()
@IsBase64()
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wCEAAQEBAQEBAQEBAQGBgUGBggHBwcHCAwJCQkJCQwTDA4MDA4MExEUEA8QFBEeFxUVFx4iHRsdIiolJSo0MjRERFwBBAQEBAQEBAQEBAYGBQYGCAcHBwcIDAkJCQkJDBMMDgwMDgwTERQQDxAUER4XFRUXHiIdGx0iKiUlKjQyNEREXP/CABEIAgACAAMBIgACEQEDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/2gAIAQEAAAAA9/AAABxix7SajXYWN1uzJzdjud1v8vkAAAAAADjRRGL6kAAbaUzDdcgAAAAAOmDQnVAAADazec9oAAAAA6K9gvQAAAA753YXcAAAAD5hNb4oAAAAMuyppyAAAAa2p4+AAAAAJBbmcAAABD6s6AAAAAAd9qy4AAAKzgIAAAAAAn9lgAAOKjiQAAAAAAJdbfIAAcU7GAAAAAAAEnuLkAAVDEwAAAAAABLbdAAKxgYAAAAAc9HR9ZXICfWYABDKoAAAAAOqLRvR4x9bSQSrbgtuXgBrKO6QAAAAPmFQfoAG/sLajuvPYgCk9CAAAABr6u1QAH1PpsN9dgBBquAAAAA1NUYwAAmNii05uB10HigAAAAwagxgPn7+XIE6nhlX13ArevQAAAAcVJpQTi2ZvteuMV1UWvBbG+LDscOig+gAAAAEQrgMr0TZde1zosiW2rm+dK6DZXHy7787xAK0AAAABxTGCO71PtPNUTD7u+6/OFYhZ0oLNnpxRWrAACPxDKnOYAR6qgvyy/JuqAXPeHkbQiQWsbS9eUfpUAANFUxsrj5AV3DRuPYfmiu74sryJ0A9WaDzkPq8fsuvfqygQAAV1DhcO1AVHpRclvePfr2z9+c6tBZnovxb1C3N0T6zOKL1QAAQqvj6uvJA5pLFHpLo86ceytn5QiQNn7N8kxUWdKDa3phUGAAD4q2Pc2VKwHNH9A9RRehVuegvKsLB2e3PLMFFkS4X5H6jAAA1FQba4ADmk8Qekurzkempr5nhFpa6vm49keSYqLMlQt3SV6AABqaf21wACo9KLjuLx0d3o+yYVM/ugqdtL0H4u6xbO9FiaeJgAAamn9rcIAruGja+xfN1bCyroluo8xxT1nEvPAXh3CWa3SgAAamn9tcAAjlWDj19WNIBx39XF4XH5F0w3ltBu8LBAAa7H4NZW2bZ4+8/IHFLYZsfZvlWNbGPh23pcfmavAsmWhnY/SAHEKhOKAA3tgbkhtdlnekYvJ+YLXWk75bZ315xggZ1zfQd3X8gDiro2AAHNnyZxUGpehpLBYItScbfHi9cVPigtKSA+nyAIPAQAAO25cxrqh6AOHH0AmViAfX31AHFJY4AACdTw0dVdQAAlFm8gd2RggGqp4AAA3tsjU1dgAAJvPwDOzNKAaGpwY/fyDr47Q2dxh1QKG/IA21h70AbvYxMA0FUD4ieJ2y7vGnjrf7sbK5AMSIxrTjjLkMrkAAJZt68A+NbF4MNVGje70QnrczkZlq9uwAfGB0fWdlcAALD3NRgx6dwwYEUJFuBD8V3TQCaWEAAAAW5IKDBHqqAaTW5u/5GLoG9zANjcoAAABfmbReqCN1aAAAAZ90cAAAAbW9OazgIRyrAAAADPujgAAACe2a0VJBo6lAAAANpcQAAAC6pA4orVjiuND0YwMHVANrnA2HbPZIAAABtL2ECrIHMeqkGk0ADfbwNjc3AAAABZc/HVQPQCP1SDFh4CXZgbG5QAAADvvvICuK8BoanAjepBtZKDZ3GAAAAWDZIOqhMUMGlwOI9pxuJDyCRWoAAAAyr67gITVYKf1IDpwGd3gLGmAAAAC0ZyAUpoA0FUAAAbK4fsAAADfXYAMCiukIlXHyAANja2eAAAB3XjswAh9Sg10M0eNxxyDjnnYSSY9gAAAC15mABWcBAcgA4AAAAE8s4AAqGJgAAAAAACV2+AAOKdjAAAAAAACT3FyAAHFRxIAAAAAAEst3kAABWcBAAAAAAE8s3kAAAQ6rOkAAAAAHdaMzAAAA19Sx8AAAAASC19mAAAAcQytcMAAAAGZY825AAAAB8V5A8cAAABkTuwO8AAAAAdUIg2qAAAG1m047wAAAAAHGiiMX1IAA20nl2+5AAAAAAA4xY/ptPrMPo6nb35mz2+5kOWAAAD//EABsBAQACAwEBAAAAAAAAAAAAAAAEBQIDBgEH/9oACAECEAAAAAA11NXBi6m2VOtLbYAACJzdLgAM7vo5YADDmeexAAZdD02YAROMggAAndnLAIHE6AAAG/tp4EThNIAAePTd3csMODhAAGuBFwbZc7Kb3mY5XnQACLV4g2223o+pInAYgANFP4trjCigtl1s7+W5CiAAKXT73EShz6Wv5dPsb7rtXzvADGqztBopnUR8ue8drztZne5/RKrjQEenX2RCrHf8D9b+YwUvqeLXmzsayhAKjdYiBXO+4Hs6+lxw7bhV1uvoNeAVm6aIlU7rjdHX9LT8/J5lfZT42kHmKt3S/cvcKPzrqWv1+zuz4DDfct2rwK6D4DKymVsH6Dh740cfoW0p68ESqAPbrZT6fHrwnWR7t0ishPfD14s5uNbDDKwnDdJgGNXFys9EFZe1ixnetUXX7ulZBPn0LCj8e2OiInoBtuwAL6z45rowAMr4ADsrX53hjS4eMto0+Pd9wADP6Jt5CiKuGzsxW61lOAAvuuROAxVkImSkaEWkwAGXfyzledRqgZsD272AA6PqRhwcJDgYA2WUgAE3vMwicJpAAAG7u5YEDiNIAADd288AicXCAABO7OWAGHM89iAAy6HpswAETnKTAAZ3fRywAAa6mrgxdbZKnWlrtAH/xAAbAQEAAgMBAQAAAAAAAAAAAAAABAUCAwYBB//aAAgBAxAAAAAAMIEGJowZ75c6fmAACPTVmIAys7mQAAxpKfwAB7cXeQAaOaigAAldLvAIvMagAAG3p5QEfltQAAePTZ1UgMeVigAGEXRizkSvZXVZCipgADRB8BnYZ3N6R+S8AAaq/wAWFhjVxWdjn1shz1UAAVuv3p49VndRKJKmWvQ4cdiB5AynDVXLzV7UeOmpoWVpl2NfzgDTXrTIjQnWcp9B4eKkXvNLPPo4NUAV+yYIsN1nJ9JErPMem5ZZbLWLEAIWySI8F1POauhuq6p3Ui0yl6NYPPEPOS99xrPOhrImHsnpeSx22Lbr8CHG8B7Mkw4vXYjVzupP3vXgjwQBZ5V+o98EqYe7NQhRnvgE2T5Cjh7LlDbuiHkDTlN1RU32ClyvWGjBs35BLl1LGs8ezNUdLRDOzAAtpvOMKwADK0AA6Sfx2ONdge7Br8e7bAAGXY589VEGOzmiFgmyQALXoUfkvEKMSd7RGJ0gAHvWyCipmmvGTE9sswAXN6MeUjI8Tzwe5TNwAJXVZBH5bUAAANvUyAIvL6wAAG3p5QBH5uKAACV0kgAMaSoxAAZW93kAA0U1XiAMrO5kAAAwgQYujW2b5U6fmAP/xABREAACAQIDAgcJCgwFAwUBAAABAgMEBQAGESExEhMgQVFhcQciMDJAQnKBkRAUFVBSU6GxwdEjMzVDVWJzgpKTorIWJDRU0mN0g0RFZMLhw//aAAgBAQABPwDyiqu1vpNlRVwx6czOAT6sVWdLPESImlm9BNB/Vpioz65BWmt4A6Xf7AMS5zvbghHiiB+Qn/InEuY73N49xk/dAT+0DD3O5P49fUntlbDTzMdWmc9rHBZjvJOAzDcSMLPMh1WZwepiMLc7kniXCpHZK2Isx3uHxLhJ+8A/9wOIs53pAA7xSgfLT/iRilz/ACJsqLeDrvZH+wjFJnSyysOOMsPpp/x1xTXW31ZHvesikPyVcE+zGvxYSBvOK3M1ooeEr1YkceZF35+jYMVuep31WgpFQfLlOp9gxV3y7VuvH10nBPmqeAvsXTyGkvt2oiOIrpQBuVjwx7G1xRZ7nTRa6kVxzvFsPsOKDNForVVI6sRSsfFl7w4BB3H4nq66koo+HUTpFGOdiBri454iTWO2wcYfnJNi+obziuvdzuOoqqtyh8xe9T2Dyehvd0tpHvWrcIPMbvk9h3YtueomKpcoDG3zke1fZvGKOto6xONpp0kj5ypB9XxHW1tLQxGaqnWNF5yfoGLpnd24UVri4I+ekG31LioqqirkMtTM8rnnc6+V09VUUkgmppnicc6nTFrzu6hYbrFwh89GNvay4oq2krIRUUsySod2h5+vy9mVQWYgADUk4vOc4KctBbAJpdxkP4tezpxV11XXymarnaV+s7B2Dm8vo66roJRNSTtG/VuPaOfFlzvFPwYLmBFLzS+Y3b8nCsGAZSCDtBHll1vNFaouMqZdCR3ka7XbsGLxmOuuxZC3FU3NEh3+kef4js+Y6+0MqK3G0+u2Jzu9E82LTeaC7RcZTy7VGrxnYwPWPKr/AJrhoOHS0RWWq3E70j7ek4qKmermeeplaSVjtZviWnqZ6SZJ6aVo5F3MuMv5shuHFUldwY6ncp3I5+/q8nZlRWZiAoGpJxmLNjTF6K1uVj3PMN7dS9XX8U5cza0BSiurloT3qzHevU334RldVZWBUjUEeSSyx08TyzOFRQWZidAAMZizLLc2alpGZKMHsMnWerq+K8uZnltTrS1TF6Jj2mP7x1YhminjSWF1ZHAKlTqCPIp5o4InllcJGgJLHYABjMOYpbtK0EJKUanYu4uRzt8W5dzFNaJVhmYtRsdq86E86/aMQzRzxJNE4aNgGVgdQQfIHdY0Z3YKqjUk7ABjMuYnukrUtMxFGjfzCOc9XR8X5ZzG9pmFNUuTRufXGTzjqwjq6q6MCrDUEbQR4fNmYjUM9son/BKdJnHnkeaOofEmh36Yeppo/wAZUwp6UijBudtG+4U381cLX0D+LXU57JVwrK+1HVvRIPgco5ka Line truncated
})
data?: string;
}
export type FileType = FileURL | FileContent;
+17
View File
@@ -74,6 +74,23 @@ export class JoinGroupResponse {
id: string;
}
export enum GroupField {
NONE = '',
PARTICIPANTS = 'participants',
}
export class GroupsListFields {
@IsOptional()
@ApiProperty({
description: 'Exclude fields',
enum: GroupField,
isArray: true,
required: false,
})
@IsEnum(GroupField, { each: true })
exclude: string[];
}
export enum GroupSortField {
ID = 'id',
SUBJECT = 'subject',
+5
View File
@@ -15,4 +15,9 @@ export class ReplyToMessage {
example: 'Hello!',
})
body?: string;
@ApiProperty({
description: "Raw data from reply's message",
})
_data?: any;
}
+12
View File
@@ -11,6 +11,11 @@ export class WALocation {
longitude: string;
}
export enum MessageSource {
API = 'api',
APP = 'app',
}
class WAMessageBase {
@MessageIdProperty()
id: string;
@@ -33,6 +38,13 @@ class WAMessageBase {
})
fromMe: boolean;
@ApiProperty({
description:
'The device that sent the message - either API or APP. Available in events (webhooks/websockets) only and only "fromMe: true" messages.',
example: MessageSource.API,
})
source: MessageSource;
@ChatIdProperty({
description: `
* ID for who this message is for.
+22 -22
View File
@@ -17,21 +17,30 @@ const ContactsProperty = ApiProperty({
required: false,
});
export interface StatusRequest {
contacts?: string[];
}
export class TextStatus {
text: string = 'Have a look! https://github.com/';
backgroundColor: string = '#38b42f';
font: number = 0;
export class StatusRequest {
@ApiProperty({
description: 'Pre-generated status message id',
example: 'BBBBBBBBBBBBBBBBB',
default: null,
required: false,
})
id?: string;
@ContactsProperty
contacts?: string[];
}
export class TextStatus extends StatusRequest {
text: string = 'Have a look! https://github.com/';
backgroundColor: string = '#38b42f';
font: number = 0;
linkPreview?: boolean = true;
linkPreviewHighQuality?: boolean = false;
}
@ApiExtraModels(RemoteFile, BinaryFile)
export class ImageStatus {
export class ImageStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(RemoteFile) },
@@ -41,13 +50,10 @@ export class ImageStatus {
file: RemoteFile | BinaryFile;
caption?: string;
@ContactsProperty
contacts?: string[];
}
@ApiExtraModels(VoiceRemoteFile, VoiceBinaryFile)
export class VoiceStatus {
export class VoiceStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceRemoteFile) },
@@ -57,13 +63,10 @@ export class VoiceStatus {
file: VoiceRemoteFile | VoiceBinaryFile;
backgroundColor: string = '#38b42f';
@ContactsProperty
contacts?: string[];
}
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
export class VideoStatus {
export class VideoStatus extends StatusRequest {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VideoRemoteFile) },
@@ -73,14 +76,11 @@ export class VideoStatus {
file: VideoRemoteFile | VideoBinaryFile;
caption?: string;
@ContactsProperty
contacts?: string[];
}
export class DeleteStatusRequest {
export class DeleteStatusRequest extends StatusRequest {
@ApiProperty({
description: 'status message id',
description: 'Status message id to delete',
example: 'AAAAAAAAAAAAAAAAA',
})
id: string;
+14 -4
View File
@@ -1,15 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { WAHAEvents } from '@waha/structures/enums.dto';
import {
AllEvents,
AllEventType,
WAHAEvents,
} from '@waha/structures/enums.dto';
import { Type } from 'class-transformer';
import {
IsArray,
IsEnum,
IsIn,
IsNumber,
IsOptional,
IsString,
IsUrl,
ValidateNested,
} from 'class-validator';
import { each } from 'lodash';
export enum RetryPolicy {
LINEAR = 'linear',
@@ -70,16 +76,20 @@ export class WebhookConfig {
description:
'You can use https://docs.webhook.site/ to test webhooks and see the payload',
})
@IsUrl({ require_protocol: true, require_tld: false })
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true,
require_tld: false,
})
url: string;
@ApiProperty({
example: ['message', 'session.status'],
required: true,
})
@IsEnum(WAHAEvents, { each: true })
@IsIn(AllEvents, { each: true })
@IsArray()
events: WAHAEvents[];
events: AllEventType[];
@ApiProperty({
example: null,
+10 -1
View File
@@ -91,6 +91,7 @@ export class PollVotePayload {
export class WAMessageRevokedBody {
after?: WAMessage;
before?: WAMessage;
_data?: any;
}
export class WASessionStatusBody {
@@ -104,10 +105,18 @@ export class WASessionStatusBody {
export class WAHAWebhook {
@ApiProperty({
example: 'evt_01jcn4pjwwg47bwy2gsey6q5sx',
example: 'evt_01aaaaaaaaaaaaaaaaaaaaaaaa',
description:
'Unique identifier for the event - lower case ULID format. https://github.com/ulid/spec',
})
id: string;
@ApiProperty({
example: 1634567890123,
description: 'Unix timestamp (ms) for when the event was created.',
})
timestamp: number;
@ApiProperty({
example: 'default',
})
+25
View File
@@ -0,0 +1,25 @@
import { WAHASessionStatus } from '../structures/enums.dto';
const STUCK_IN_STARTING_THRESHOLD = 20;
/**
* Keeps track of session statuses (with timestamps) for the last `trackPeriodMs` milliseconds.
*/
export class StatusTracker {
private numberOfStarting: number = 0;
public track(status: WAHASessionStatus): void {
if (status == WAHASessionStatus.STARTING) {
this.numberOfStarting += 1;
} else {
this.numberOfStarting = 0;
}
}
/**
* Checks if the session has been continuously 'STARTING'
*/
public isStuckInStarting(): boolean {
return this.numberOfStarting >= STUCK_IN_STARTING_THRESHOLD;
}
}
+3 -2
View File
@@ -33,8 +33,9 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.2.4',
version: '2025.4.1',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
browser:
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
};
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "8b31d6b88166e0962ca47a3dfbd256a42f09dbc1"
"ref": "v1.0.3"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "d6cd941f8d7621f401305e03ad2c1db8dc0a64f0"
"ref": "0e7e2e7edc715b0c30fff89c10ee2d2fc3402d2d"
}
}
}
+4 -4
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712":
version: 6.7.12
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=dcc5d0ea0827c0d57ceda4f94b9cdb260760b77e"
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=4e51470a1e1f6d56b3e48feabf886b80c0a75bd0"
dependencies:
"@adiwajshing/keyed-db": ^0.2.4
"@hapi/boom": ^9.1.3
@@ -40,7 +40,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: 24e16d0b11dbb83a562e333fcc11f9489d4f3c6735c12f726eead983dd3bbc0f4dbce9c486d849b26ddcb0e2be68972d205d21837aabfcb42ebd8b115958f053
checksum: d2d5fe4f4ed2a519fd30c486b079978fac1894b4ad195d337387acc841110f663e4525b89336f8dcccc63e6e7b54f4542e00006e7d42ee196dbce7b7d790b7fc
languageName: node
linkType: hard
@@ -12931,7 +12931,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-channels":
version: 1.26.0
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=5b4dd6dec206905e3aaa7b0461d7c6d4cc3126a8"
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=c73f3cfb4e1290103ee946ca7f56d5638f1a66bd"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
@@ -12949,7 +12949,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: 441e54f450ed9ea5d5756c1ca4fd592e498ad79247d2c6508aee2a5c1b2608a9eb3f6360234f2f0ce563edb187a3d453d41773e745da9bebf657f575b9fa66b4
checksum: 831a4a2819041f63c2a51a98e1ad7dd9ff8474b1005b11314b613c6dadac545f3c780e38081b7d260d32afa9ffd333a25f9c6407b630a0660bd08dadbc8ead89
languageName: node
linkType: hard