Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e210d1e9fc | ||
|
|
df0f449e13 | ||
|
|
895105c900 | ||
|
|
209538709b | ||
|
|
ef7c35286b | ||
|
|
70af0828cb | ||
|
|
ef18e311b1 | ||
|
|
68de84973a | ||
|
|
ef6d4f9998 | ||
|
|
9f5413963b | ||
|
|
ec7c0a5cb7 | ||
|
|
f82bd9eadf | ||
|
|
843c064ebe | ||
|
|
8a52e1d1d0 | ||
|
|
8ec220dc9a | ||
|
|
9c4b9f4433 | ||
|
|
6059a29660 | ||
|
|
e65e6764fa | ||
|
|
1e77cb234a | ||
|
|
4a357087d1 | ||
|
|
6a3823cdff | ||
|
|
c2372ce96e | ||
|
|
0f5e79b163 | ||
|
|
fa4026c83f | ||
|
|
3e4d66ca27 | ||
|
|
2ec94dc13c | ||
|
|
6b11e08f3d | ||
|
|
e884e56b47 | ||
|
|
2d74affed4 | ||
|
|
3572b17049 | ||
|
|
4ee7091cd1 | ||
|
|
ff5e89d97c | ||
|
|
c4e82afc0c | ||
|
|
52c5d85487 | ||
|
|
fd0438f80b | ||
|
|
cb5781ca65 | ||
|
|
4d8aad9dfe | ||
|
|
39429a2e44 | ||
|
|
0a3514d81e | ||
|
|
4e5e0a7c34 | ||
|
|
f6cc6a9bef | ||
|
|
2e545e9fb9 | ||
|
|
d49ef64e7e | ||
|
|
b1e6eaef90 | ||
|
|
3420d0199b | ||
|
|
671db35571 | ||
|
|
d724f87726 | ||
|
|
5ab8565205 | ||
|
|
660c567d1b | ||
|
|
2c68d13c56 | ||
|
|
b9fc155b4f | ||
|
|
8dd5aefc81 | ||
|
|
f9aaf5dcef | ||
|
|
34a6e773df | ||
|
|
f0beb03344 | ||
|
|
1ff9a81ab3 | ||
|
|
9b1771c693 | ||
|
|
823e604737 | ||
|
|
97a31bdf17 | ||
|
|
ee7a198ae6 | ||
|
|
f143a51010 | ||
|
|
e483185b2c | ||
|
|
40e1c75746 | ||
|
|
456bd4693c | ||
|
|
5ac9c0f760 | ||
|
|
f621d84b83 | ||
|
|
0dd001e95a | ||
|
|
69b5235db9 | ||
|
|
a673d63b33 | ||
|
|
36485e10f2 | ||
|
|
8a1a062127 | ||
|
|
5961e0de08 | ||
|
|
60bda521a0 | ||
|
|
49f63c0f0d |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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' });
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageButtonReply,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
@@ -177,6 +178,16 @@ export class ChattingController {
|
||||
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) {
|
||||
|
||||
+130
-56
@@ -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.' })
|
||||
|
||||
@@ -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
@@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+106
-7
@@ -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,6 +47,7 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageButtonReply,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
@@ -71,7 +72,8 @@ import {
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupsPaginationParams,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../structures/groups.dto';
|
||||
@@ -146,6 +148,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 +184,7 @@ export abstract class WhatsappSession {
|
||||
filter(Boolean),
|
||||
map((data) => {
|
||||
data._eventId = generatePrefixedId('evt');
|
||||
data._timestampMs = Date.now();
|
||||
return data;
|
||||
}),
|
||||
retry(),
|
||||
@@ -360,10 +369,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 +399,10 @@ export abstract class WhatsappSession {
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
generateNewMessageId(): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery);
|
||||
|
||||
abstract sendText(request: MessageTextRequest);
|
||||
@@ -424,6 +437,10 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendButtonsReply(request: MessageButtonReply) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: SendSeenRequest);
|
||||
@@ -581,7 +598,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 +640,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 +692,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 +860,9 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Engine methods
|
||||
*/
|
||||
public async getEngineInfo(): Promise<any> {
|
||||
return {};
|
||||
}
|
||||
@@ -833,6 +904,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 +947,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;
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
+1810
-28
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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,9 +52,18 @@ import {
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSortField,
|
||||
ChatSummary,
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
EditMessageRequest,
|
||||
MessageButtonReply,
|
||||
MessageFileRequest,
|
||||
MessageForwardRequest,
|
||||
MessageImageRequest,
|
||||
@@ -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 = parseJson(data.message);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -620,11 +710,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) {
|
||||
@@ -916,7 +1202,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 +1375,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 +1398,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 +1406,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 +1430,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 +1456,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 +1566,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 +1690,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;
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,46 +88,6 @@ import {
|
||||
PinDuration,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
|
||||
import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import {
|
||||
Label,
|
||||
LabelChatAssociation,
|
||||
LabelDTO,
|
||||
LabelID,
|
||||
} from '@waha/structures/labels.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.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 * as Buffer from 'buffer';
|
||||
import { Agent } from 'https';
|
||||
import * as lodash from 'lodash';
|
||||
import * as NodeCache from 'node-cache';
|
||||
import {
|
||||
filter,
|
||||
fromEvent,
|
||||
identity,
|
||||
merge,
|
||||
mergeAll,
|
||||
mergeMap,
|
||||
Observable,
|
||||
partition,
|
||||
share,
|
||||
Subject,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
@@ -130,7 +107,8 @@ import {
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
WANumberExistResult,
|
||||
} from '../../../structures/chatting.dto';
|
||||
} from '@waha/structures/chatting.dto';
|
||||
import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto';
|
||||
import {
|
||||
ACK_UNKNOWN,
|
||||
SECOND,
|
||||
@@ -139,40 +117,65 @@ import {
|
||||
WAHAPresenceStatus,
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '../../../structures/enums.dto';
|
||||
} from '@waha/structures/enums.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../../../structures/groups.dto';
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
Label,
|
||||
LabelChatAssociation,
|
||||
LabelDTO,
|
||||
LabelID,
|
||||
} from '@waha/structures/labels.dto';
|
||||
import { ReplyToMessage } from '@waha/structures/message.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
WAHAPresenceData,
|
||||
} from '../../../structures/presence.dto';
|
||||
import {
|
||||
WAMessage,
|
||||
WAMessageReaction,
|
||||
} from '../../../structures/responses.dto';
|
||||
import { MeInfo } from '../../../structures/sessions.dto';
|
||||
} 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 '../../../structures/status.dto';
|
||||
} from '@waha/structures/status.dto';
|
||||
import {
|
||||
ensureSuffix,
|
||||
getChannelInviteLink,
|
||||
getPublicUrlFromDirectPath,
|
||||
isNewsletter,
|
||||
WhatsappSession,
|
||||
} from '../../abc/session.abc';
|
||||
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';
|
||||
import * as NodeCache from 'node-cache';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from '../../exceptions';
|
||||
import { toVcard } from '../../helpers';
|
||||
import { createAgentProxy } from '../../helpers.proxy';
|
||||
import { QR } from '../../QR';
|
||||
import { NowebAuthFactoryCore } from './NowebAuthFactoryCore';
|
||||
filter,
|
||||
fromEvent,
|
||||
identity,
|
||||
merge,
|
||||
mergeAll,
|
||||
mergeMap,
|
||||
Observable,
|
||||
partition,
|
||||
share,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const promiseRetry = require('promise-retry');
|
||||
|
||||
import { INowebStore } from './store/INowebStore';
|
||||
import { NowebPersistentStore } from './store/NowebPersistentStore';
|
||||
import { NowebStorageFactoryCore } from './store/NowebStorageFactoryCore';
|
||||
@@ -218,6 +221,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
store: INowebStore;
|
||||
private qr: QR;
|
||||
|
||||
private statusTracker = new StatusTracker();
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.shouldRestart = true;
|
||||
@@ -256,6 +261,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) => {
|
||||
@@ -384,7 +398,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.listenConnectionEvents();
|
||||
this.subscribeEngineEvents2();
|
||||
this.listenContactsUpdatePictureProfile();
|
||||
this.enableAutoRestart();
|
||||
// this.enableAutoRestart();
|
||||
}
|
||||
|
||||
private enableAutoRestart() {
|
||||
@@ -421,6 +435,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.startDelayedJob.schedule(async () => {
|
||||
if (!this.shouldRestart) {
|
||||
this.logger.warn(
|
||||
@@ -445,12 +460,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(
|
||||
@@ -458,12 +497,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
|
||||
@@ -693,6 +734,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 = {
|
||||
@@ -700,14 +745,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(
|
||||
@@ -721,18 +770,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) {
|
||||
@@ -796,13 +848,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> {
|
||||
@@ -818,14 +872,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) {
|
||||
@@ -1140,12 +1197,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;
|
||||
@@ -1190,6 +1241,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({});
|
||||
@@ -1198,13 +1253,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);
|
||||
}
|
||||
@@ -1276,7 +1355,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);
|
||||
}
|
||||
@@ -1292,16 +1371,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[]) {
|
||||
@@ -1311,7 +1475,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
} else {
|
||||
jids = await this.fetchMyContactsJids();
|
||||
}
|
||||
this.upsertMeInJIDs(jids);
|
||||
return jids;
|
||||
}
|
||||
|
||||
@@ -1327,10 +1490,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[]) {
|
||||
@@ -1339,7 +1510,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1728,11 +1900,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: {
|
||||
@@ -1751,12 +1925,19 @@ 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;
|
||||
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
@@ -1784,11 +1965,13 @@ 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,
|
||||
source: source,
|
||||
body: body || null,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
@@ -1852,6 +2035,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
id: contextInfo.stanzaId,
|
||||
participant: toCusFormat(contextInfo.participant),
|
||||
body: body,
|
||||
_data: quotedMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2021,15 +2205,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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2048,6 +2240,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) {
|
||||
@@ -2132,7 +2330,7 @@ export function toCusFormat(remoteJid) {
|
||||
if (isJidGroup(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (isJidStatusBroadcast(remoteJid)) {
|
||||
if (isJidBroadcast(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (isLidUser(remoteJid)) {
|
||||
@@ -2163,12 +2361,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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { GetChatMessagesQuery } from '@waha/structures/chats.dto';
|
||||
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
import {
|
||||
SessionBaseRequest,
|
||||
@@ -64,6 +64,7 @@ export class GetPresenceQuery extends ChatQuery {}
|
||||
*/
|
||||
export class ChatRequest extends SessionBaseRequest {
|
||||
@ChatIdProperty()
|
||||
@IsString()
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
@@ -158,6 +159,7 @@ export class MessageTextRequest extends ChatRequest {
|
||||
reply_to?: string;
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
linkPreviewHighQuality?: boolean = false;
|
||||
}
|
||||
|
||||
export class EditMessageRequest {
|
||||
@@ -165,6 +167,9 @@ export class EditMessageRequest {
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
linkPreviewHighQuality?: boolean = false;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
@@ -329,3 +334,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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -15,4 +15,9 @@ export class ReplyToMessage {
|
||||
example: 'Hello!',
|
||||
})
|
||||
body?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Raw data from reply's message",
|
||||
})
|
||||
_data?: any;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -105,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',
|
||||
})
|
||||
|
||||
@@ -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
@@ -33,8 +33,9 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.2.5',
|
||||
version: '2025.3.3',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
browser:
|
||||
getEngineName() === WAHAEngine.WEBJS ? getBrowserExecutablePath() : null,
|
||||
};
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "8b31d6b88166e0962ca47a3dfbd256a42f09dbc1"
|
||||
"ref": "v1.0.2"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "d6cd941f8d7621f401305e03ad2c1db8dc0a64f0"
|
||||
"ref": "0e7e2e7edc715b0c30fff89c10ee2d2fc3402d2d"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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=a6b6fde036f35043326bea8cd7d46644f4222dd6"
|
||||
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: f463ff68a64860ae729720c06d7759f76444dcecbfc81a24ce1d47b5988b68f9fb3ec5d266b7deed4239ac955a744301aeceb1797c2e452e31a2871ea973a361
|
||||
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
|
||||
|
||||
|
||||
Reference in new issue
Block a user