Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a6e773df | ||
|
|
f0beb03344 | ||
|
|
1ff9a81ab3 | ||
|
|
9b1771c693 | ||
|
|
823e604737 | ||
|
|
97a31bdf17 | ||
|
|
ee7a198ae6 | ||
|
|
f143a51010 | ||
|
|
e483185b2c | ||
|
|
40e1c75746 | ||
|
|
456bd4693c | ||
|
|
5ac9c0f760 | ||
|
|
f621d84b83 | ||
|
|
0dd001e95a | ||
|
|
69b5235db9 | ||
|
|
a673d63b33 | ||
|
|
36485e10f2 | ||
|
|
8a1a062127 | ||
|
|
5961e0de08 | ||
|
|
60bda521a0 | ||
|
|
49f63c0f0d | ||
|
|
33910aedb8 | ||
|
|
3359a81a14 | ||
|
|
e27f850e68 | ||
|
|
07340b8f53 | ||
|
|
2f9dd7faa8 | ||
|
|
3f81fd4a87 | ||
|
|
4dd961fc66 | ||
|
|
0bd70bfb32 | ||
|
|
3ab845d2d6 | ||
|
|
443f09d5a2 | ||
|
|
3500952bb7 | ||
|
|
e98182c4b5 | ||
|
|
2337827744 | ||
|
|
556dd66af3 | ||
|
|
8bdad3f4f9 | ||
|
|
6346d4143d | ||
|
|
fd8dc88760 | ||
|
|
b2810f390e | ||
|
|
23d3cd0f4b | ||
|
|
4a3c76773d | ||
|
|
d23e7604f0 | ||
|
|
0b3f6c6eff | ||
|
|
f2869c892b | ||
|
|
3755a9529b | ||
|
|
890f0593ee | ||
|
|
e6d666b5b0 | ||
|
|
f440acf145 | ||
|
|
1ad0e0c6dd | ||
|
|
bb8691ddc0 | ||
|
|
cf3041d07b | ||
|
|
313b3d5d6b | ||
|
|
122e7336be | ||
|
|
e954453f6b | ||
|
|
55ef3d09f3 | ||
|
|
88b9cc5ac9 | ||
|
|
f07cb7470d | ||
|
|
bd874a0232 | ||
|
|
7cc4cccb9c | ||
|
|
e85a3bacb7 | ||
|
|
530dfa758f |
No files matched your search
@@ -1,5 +1,4 @@
|
||||
Dockerfile
|
||||
src/core/engines/gows/grpc
|
||||
src/core/engines/gows/proto
|
||||
.*sessions
|
||||
sessions
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
src/core/engines/gows/grpc
|
||||
src/core/engines/gows/proto
|
||||
.env
|
||||
*.heapsnapshot
|
||||
|
||||
+8
-9
@@ -18,7 +18,6 @@ RUN yarn install
|
||||
WORKDIR /git
|
||||
ADD . /git
|
||||
RUN yarn install
|
||||
RUN yarn gows:proto
|
||||
RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
|
||||
#
|
||||
@@ -58,17 +57,17 @@ RUN apt-get update \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY waha.config.json /tmp/waha.config.json
|
||||
WORKDIR /go/gows
|
||||
RUN \
|
||||
GOWS_GITHUB_REPO=$(jq -r '.waha.gows.repo' /tmp/waha.config.json) && \
|
||||
GOWS_SHA=$(jq -r '.waha.gows.ref' /tmp/waha.config.json) && \
|
||||
git clone https://github.com/${GOWS_GITHUB_REPO} gows && \
|
||||
cd gows && \
|
||||
git checkout ${GOWS_SHA}
|
||||
|
||||
WORKDIR /go/gows
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
RUN make all
|
||||
ARCH=$(uname -m) && \
|
||||
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; \
|
||||
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/${GOWS_SHA}/gows-${ARCH} && \
|
||||
chmod +x /go/gows/bin/gows
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -35,7 +35,7 @@ for-swagger:
|
||||
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
|
||||
|
||||
up-noweb:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-678
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712
|
||||
|
||||
up-noweb-libsignal:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
**WAHA** - **W**hats**A**pp **H**TTP **A**PI (REST API) that you can install on your own server and run in less than 5 minutes!
|
||||
|
||||
[](https://hub.docker.com/r/devlikeapro/waha)
|
||||
[](https://hub.docker.com/r/devlikeapro/waha)
|
||||
|
||||
- Documentation: [https://waha.devlike.pro/](https://waha.devlike.pro/)
|
||||
- Dashboard Example: [https://waha.devlike.pro/dashboard](https://waha.devlike.pro/dashboard)
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ services:
|
||||
# command:
|
||||
# - postgres
|
||||
# - "-c"
|
||||
# - "max_connections=1000"
|
||||
# - "max_connections=3000"
|
||||
|
||||
# NOTE: Only if you're using MongoDB
|
||||
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-678",
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-6712",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@aws-sdk/client-s3": "^3.633.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.633.0",
|
||||
|
||||
@@ -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://raw.githubusercontent.com/${repo}/${ref}/proto/${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' });
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Query,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
|
||||
import {
|
||||
@@ -19,8 +18,6 @@ import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
|
||||
import {
|
||||
CaptchaBody,
|
||||
OTPRequest,
|
||||
QRCodeFormat,
|
||||
QRCodeQuery,
|
||||
QRCodeValue,
|
||||
@@ -63,42 +60,6 @@ class AuthController {
|
||||
) {
|
||||
return session.requestCode(request.phoneNumber, request.method, request);
|
||||
}
|
||||
|
||||
@Post('authorize-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP authentication code.',
|
||||
})
|
||||
authorizeCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: OTPRequest,
|
||||
) {
|
||||
return session.authorizeCode(request.code);
|
||||
}
|
||||
|
||||
@Get('captcha')
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader()
|
||||
@UseInterceptors(new BufferResponseInterceptor())
|
||||
@ApiOperation({
|
||||
summary: 'Get captcha image.',
|
||||
})
|
||||
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
|
||||
const image = await session.getCaptcha();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
@Post('captcha')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Enter captcha code.',
|
||||
})
|
||||
saveCaptcha(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() body: CaptchaBody,
|
||||
) {
|
||||
return session.saveCaptcha(body.code);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthController };
|
||||
@@ -3,31 +3,51 @@ import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
import {
|
||||
Channel,
|
||||
ChannelCategory,
|
||||
ChannelCountry,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
ChannelView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
NewsletterIdApiParam,
|
||||
NewsletterIdOrInviteCodeApiParam,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { isNewsletter, WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
isNewsletter,
|
||||
parseChannelInviteLink,
|
||||
WhatsappSession,
|
||||
} from '../core/abc/session.abc';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/channels')
|
||||
@ApiTags('📢 Channels')
|
||||
export class ChannelsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
constructor(
|
||||
private manager: SessionManager,
|
||||
private channelsInfoService: ChannelsInfoServiceCore,
|
||||
) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@@ -76,12 +96,44 @@ export class ChannelsController {
|
||||
if (isNewsletter(id)) {
|
||||
return session.channelsGetChannel(id);
|
||||
} else {
|
||||
// https://www.whatsapp.com/channel/123 => 123
|
||||
const inviteCode = id.split('/').pop();
|
||||
const inviteCode = parseChannelInviteLink(id);
|
||||
return session.channelsGetChannelByInviteCode(inviteCode);
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -125,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();
|
||||
}
|
||||
}
|
||||
+119
-61
@@ -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 { 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 { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
GroupsPaginationParams,
|
||||
JoinGroupRequest,
|
||||
JoinGroupResponse,
|
||||
@@ -53,20 +63,19 @@ export class GroupsController {
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Query() query: JoinGroupRequest,
|
||||
): Promise<any> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = query.code.split('/').pop();
|
||||
const code = parseGroupInviteLink(query.code);
|
||||
return session.joinInfoGroup(code);
|
||||
}
|
||||
|
||||
@Post('join')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Join group via code' })
|
||||
async joinGroup(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: JoinGroupRequest,
|
||||
): Promise<JoinGroupResponse> {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
const code = request.code.split('/').pop();
|
||||
const code = parseGroupInviteLink(request.code);
|
||||
const id = await session.joinGroup(code);
|
||||
return { id: id };
|
||||
}
|
||||
@@ -75,14 +84,18 @@ 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;
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Refresh groups from the server.' })
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
@@ -101,6 +114,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
|
||||
@@ -162,60 +269,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
|
||||
@@ -228,6 +281,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -253,6 +307,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Add participants' })
|
||||
@@ -265,6 +320,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
@@ -279,6 +335,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
@@ -291,6 +348,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants to regular users.' })
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Put,
|
||||
UnprocessableEntityException,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
SessionApiParam,
|
||||
WorkingSessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
import { Result } from '@waha/structures/base.dto';
|
||||
import {
|
||||
MyProfile,
|
||||
ProfileNameRequest,
|
||||
ProfilePictureRequest,
|
||||
ProfileStatusRequest,
|
||||
} from '@waha/structures/profile.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/profile')
|
||||
@ApiTags('🆔 Profile')
|
||||
export class ProfileController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get my profile' })
|
||||
async getMyProfile(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<MyProfile> {
|
||||
const me = session.getSessionMeInfo();
|
||||
if (!me) {
|
||||
throw new UnprocessableEntityException('No profile found');
|
||||
}
|
||||
const picture = await session.getContactProfilePicture(me.id, false);
|
||||
return {
|
||||
id: me.id,
|
||||
name: me.pushName,
|
||||
picture: picture || null,
|
||||
};
|
||||
}
|
||||
|
||||
@Put('/name')
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Set my profile name' })
|
||||
async setProfileName(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfileNameRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.setProfileName(request.name);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Put('/status')
|
||||
@SessionApiParam
|
||||
@UsePipes(new WAHAValidationPipe())
|
||||
@ApiOperation({ summary: 'Set profile status (About)' })
|
||||
async setProfileStatus(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfileStatusRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.setProfileStatus(request.status);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Put('/picture')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Set profile picture' })
|
||||
async setProfilePicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Body() request: ProfilePictureRequest,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateProfilePicture(request.file);
|
||||
return { success: success ?? true };
|
||||
}
|
||||
|
||||
@Delete('/picture')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete profile picture' })
|
||||
async deleteProfilePicture(
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
): Promise<Result> {
|
||||
const success = await session.updateProfilePicture(null);
|
||||
return { success: success || true };
|
||||
}
|
||||
}
|
||||
@@ -57,13 +57,11 @@ export class SwaggerConfiguratorCore {
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('🖥️ Sessions', 'Control WhatsApp sessions (accounts)')
|
||||
.addTag('🔑 Auth', 'Authentication')
|
||||
.addTag('🆔 Profile', 'Your profile information')
|
||||
.addTag('🖼️ Screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('📤 Chatting', 'Chatting methods')
|
||||
.addTag('📢 Channels', 'Channels (newsletters) methods')
|
||||
.addTag(
|
||||
'🟢 Status',
|
||||
'Status (aka stories) methods. <b>NOWEB</b> engine only!',
|
||||
)
|
||||
.addTag('🟢 Status', 'Status (aka stories) methods')
|
||||
.addTag('💬 Chats', `Chats methods`)
|
||||
.addTag(
|
||||
'👤 Contacts',
|
||||
@@ -140,6 +138,7 @@ export class SwaggerConfiguratorCore {
|
||||
webhooks[event] = {
|
||||
post: {
|
||||
summary: eventMetadata.description,
|
||||
deprecated: eventMetadata.deprecated || false,
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
|
||||
@@ -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,31 @@ export abstract class SessionManager
|
||||
sessionName: string,
|
||||
expected: WAHASessionStatus[],
|
||||
): Promise<WhatsappSession> {
|
||||
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),
|
||||
|
||||
+152
-9
@@ -1,7 +1,12 @@
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSummary,
|
||||
@@ -10,6 +15,7 @@ import {
|
||||
GetChatMessagesQuery,
|
||||
} from '@waha/structures/chats.dto';
|
||||
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';
|
||||
@@ -65,6 +71,8 @@ import {
|
||||
} from '../../structures/enums.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupField,
|
||||
GroupsListFields,
|
||||
GroupsPaginationParams,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
@@ -326,21 +334,58 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public authorizeCode(code: string) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
abstract getScreenshot(): Promise<Buffer>;
|
||||
|
||||
public getSessionMeInfo(): MeInfo | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getCaptcha(): Promise<QR> {
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public setProfileName(name: string): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public saveCaptcha(code: string) {
|
||||
public setProfileStatus(status: string): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async updateProfilePicture(
|
||||
file: BinaryFile | RemoteFile | null,
|
||||
): Promise<boolean> {
|
||||
if (file) {
|
||||
await this.setProfilePicture(file);
|
||||
} else {
|
||||
await this.deleteProfilePicture();
|
||||
}
|
||||
|
||||
// Refresh profile picture after update
|
||||
setTimeout(() => {
|
||||
this.logger.debug('Refreshing my profile picture after update...');
|
||||
this.refreshMyProfilePicture()
|
||||
.then(() => {
|
||||
this.logger.debug('Refreshed my 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 async refreshMyProfilePicture() {
|
||||
const me = this.getSessionMeInfo();
|
||||
await this.getContactProfilePicture(me.id, true);
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -515,7 +560,7 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
public getContactAbout(query: ContactQuery): Promise<{ about: string }> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
@@ -537,8 +582,16 @@ export abstract class WhatsappSession {
|
||||
}
|
||||
|
||||
protected async refreshProfilePicture(id: string) {
|
||||
let fn: Promise<string>;
|
||||
if (isNewsletter(id)) {
|
||||
fn = this.channelsGetChannel(id).then(
|
||||
(channel: Channel) => channel.picture || channel.preview,
|
||||
);
|
||||
} else {
|
||||
fn = this.fetchContactProfilePicture(id);
|
||||
}
|
||||
this.profilePictures.del(id);
|
||||
const url = await this.fetchContactProfilePicture(id).catch((err) => {
|
||||
const url = await fn.catch((err) => {
|
||||
this.logger.warn('Error fetching profile picture');
|
||||
this.logger.warn(err, err.stack);
|
||||
return null;
|
||||
@@ -570,10 +623,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();
|
||||
}
|
||||
@@ -610,6 +675,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();
|
||||
}
|
||||
@@ -661,6 +763,25 @@ export abstract class WhatsappSession {
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public channelsList(query: ListChannelsQuery): Promise<Channel[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
@@ -769,6 +890,28 @@ export function isNewsletter(jid: string) {
|
||||
return jid.endsWith('@newsletter');
|
||||
}
|
||||
|
||||
export function getGroupInviteLink(code: string) {
|
||||
if (code.startsWith('https://')) {
|
||||
return code;
|
||||
}
|
||||
return `https://chat.whatsapp.com/${code}`;
|
||||
}
|
||||
|
||||
export function parseGroupInviteLink(link: string) {
|
||||
// https://chat.whatsapp.com/123 => 123
|
||||
return link.split('/').pop();
|
||||
}
|
||||
|
||||
export function getChannelInviteLink(code: string) {
|
||||
return `https://whatsapp.com/channel/${code}`;
|
||||
}
|
||||
|
||||
export function parseChannelInviteLink(link: string): string {
|
||||
// https://www.whatsapp.com/channel/123 => 123
|
||||
const code = link.split('/').pop();
|
||||
return code;
|
||||
}
|
||||
|
||||
export function getPublicUrlFromDirectPath(directPath: string) {
|
||||
return `https://pps.whatsapp.net${directPath}`;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { ChannelsController } from '@waha/api/channels.controller';
|
||||
import { ProfileController } from '@waha/api/profile.controller';
|
||||
import {
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
@@ -14,6 +15,7 @@ import { GowsEngineConfigService } from '@waha/core/config/GowsEngineConfigServi
|
||||
import { WebJSEngineConfigService } from '@waha/core/config/WebJSEngineConfigService';
|
||||
import { MediaLocalStorageModule } from '@waha/core/media/local/media.local.storage.module';
|
||||
import { MediaLocalStorageConfig } from '@waha/core/media/local/MediaLocalStorageConfig';
|
||||
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
|
||||
import { BufferJsonReplacerInterceptor } from '@waha/nestjs/BufferJsonReplacerInterceptor';
|
||||
import {
|
||||
getPinoHttpUseLevel,
|
||||
@@ -120,6 +122,7 @@ const IMPORTS = [...IMPORTS_CORE, ...IMPORTS_MEDIA];
|
||||
export const CONTROLLERS = [
|
||||
AuthController,
|
||||
SessionsController,
|
||||
ProfileController,
|
||||
ChattingController,
|
||||
ChatsController,
|
||||
ChannelsController,
|
||||
@@ -148,6 +151,7 @@ const PROVIDERS = [
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: BufferJsonReplacerInterceptor,
|
||||
},
|
||||
ChannelsInfoServiceCore,
|
||||
DashboardConfigServiceCore,
|
||||
SwaggerConfigServiceCore,
|
||||
WebJSEngineConfigService,
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
GOWSGroupParticipant,
|
||||
GroupInfoEvent,
|
||||
GroupInfoFull,
|
||||
JoinedGroupEvent,
|
||||
} from '@waha/core/engines/gows/types.group';
|
||||
import { toJID } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { MeInfo } from '@waha/structures/sessions.dto';
|
||||
|
||||
export function ToGroupV2JoinEvent(event: JoinedGroupEvent): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(event),
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: MeInfo | null,
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!event.Leave || event.Leave.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!me?.id) {
|
||||
return null;
|
||||
}
|
||||
const jid = toJID(me.id);
|
||||
const left = event.Leave;
|
||||
if (!left.includes(jid)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: event.JID,
|
||||
},
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2UpdateEvent | null {
|
||||
const group = ToGroupInfoPartial(event);
|
||||
if (!group) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: group,
|
||||
_data: event,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvents(
|
||||
event: GroupInfoEvent,
|
||||
): GroupV2ParticipantsEvent[] {
|
||||
const id: GroupId = {
|
||||
id: event.JID,
|
||||
};
|
||||
const events: GroupV2ParticipantsEvent[] = [];
|
||||
const maps = [
|
||||
{ participants: event.Leave, type: GroupParticipantType.LEAVE },
|
||||
{ participants: event.Join, type: GroupParticipantType.JOIN },
|
||||
{ participants: event.Promote, type: GroupParticipantType.PROMOTE },
|
||||
{ participants: event.Demote, type: GroupParticipantType.DEMOTE },
|
||||
];
|
||||
for (const map of maps) {
|
||||
const participants = getParticipants(map.participants, map.type);
|
||||
if (participants) {
|
||||
events.push({
|
||||
group: id,
|
||||
type: map.type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: event,
|
||||
});
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function getParticipants(jids: string[] | null, type: GroupParticipantType) {
|
||||
if (!jids) {
|
||||
return;
|
||||
}
|
||||
let role: GroupParticipantRole;
|
||||
switch (type) {
|
||||
case GroupParticipantType.JOIN:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
case GroupParticipantType.LEAVE:
|
||||
role = GroupParticipantRole.LEFT;
|
||||
break;
|
||||
case GroupParticipantType.PROMOTE:
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
break;
|
||||
case GroupParticipantType.DEMOTE:
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
break;
|
||||
}
|
||||
const participants: GroupParticipant[] = [];
|
||||
for (const jid of jids) {
|
||||
participants.push({
|
||||
id: jid,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return participants;
|
||||
}
|
||||
|
||||
function ToGroupInfo(group: GroupInfoFull): GroupInfo {
|
||||
const participants: GroupParticipant[] = extractParticipants(
|
||||
group.Participants,
|
||||
);
|
||||
return {
|
||||
id: group.JID,
|
||||
subject: group.Name,
|
||||
description: group.Topic,
|
||||
invite: undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.MemberAddMode === 'all_member_add',
|
||||
membersCanSendMessages: !!group.IsAnnounce,
|
||||
newMembersApprovalRequired: group.IsJoinApprovalRequired,
|
||||
};
|
||||
}
|
||||
|
||||
function ToGroupInfoPartial(group: GroupInfoEvent): GroupInfo | null {
|
||||
const announce: boolean | undefined = group.Announce?.IsAnnounce;
|
||||
let membersCanSendMessages: boolean | undefined;
|
||||
// revert announce or undefined
|
||||
if (announce === undefined) {
|
||||
membersCanSendMessages = undefined;
|
||||
} else {
|
||||
membersCanSendMessages = !announce;
|
||||
}
|
||||
|
||||
const info = {
|
||||
id: group.JID,
|
||||
subject: group.Name?.Name,
|
||||
description: group.Topic?.Topic,
|
||||
invite: group.NewInviteLink || undefined,
|
||||
membersCanAddNewMember: undefined,
|
||||
membersCanSendMessages: membersCanSendMessages,
|
||||
newMembersApprovalRequired:
|
||||
group.MembershipApprovalMode?.IsJoinApprovalRequired,
|
||||
participants: undefined,
|
||||
};
|
||||
// check if all fields are undefined but id - return null
|
||||
for (const key in info) {
|
||||
if (key !== 'id' && info[key] !== undefined) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractParticipants(
|
||||
participants: GOWSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
const result: GroupParticipant[] = [];
|
||||
for (const participant of participants) {
|
||||
let role: GroupParticipantRole;
|
||||
if (participant.IsSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.IsAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else {
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
}
|
||||
result.push({
|
||||
id: participant.JID,
|
||||
role: role,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
import { messages } from '@waha/core/engines/gows/grpc/gows';
|
||||
import { MessageStatus } from '@waha/core/engines/gows/types';
|
||||
import { WAMessageAck } from '@waha/structures/enums.dto';
|
||||
|
||||
export function optional(value: any, type) {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
return new type({ value: value });
|
||||
}
|
||||
|
||||
export function parseJson(value: messages.Json | { data?: string }) {
|
||||
if (value instanceof messages.Json) {
|
||||
value = value.toObject();
|
||||
}
|
||||
if (value.data === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(value.data);
|
||||
}
|
||||
|
||||
export function parseJsonList(value: messages.JsonList) {
|
||||
return value.toObject().elements.map((value) => parseJson(value));
|
||||
}
|
||||
|
||||
export function statusToAck(status: MessageStatus): WAMessageAck | null {
|
||||
switch (status) {
|
||||
case MessageStatus.Error:
|
||||
return WAMessageAck.ERROR;
|
||||
case MessageStatus.Pending:
|
||||
return WAMessageAck.PENDING;
|
||||
case MessageStatus.ServerAck:
|
||||
return WAMessageAck.SERVER;
|
||||
case MessageStatus.DeliveryAck:
|
||||
return WAMessageAck.DEVICE;
|
||||
case MessageStatus.Read:
|
||||
return WAMessageAck.READ;
|
||||
case MessageStatus.Played:
|
||||
return WAMessageAck.PLAYED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -29,9 +29,14 @@ import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
import { QR } from '@waha/core/QR';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelRole,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
@@ -81,11 +86,43 @@ 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 {
|
||||
CreateGroupRequest,
|
||||
GroupSortField,
|
||||
Participant,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
|
||||
import { PaginatorInMemory } from '@waha/utils/Paginator';
|
||||
|
||||
enum WhatsMeowEvent {
|
||||
CONNECTED = 'gows.ConnectedEventData',
|
||||
DISCONNECTED = 'events.Disconnected',
|
||||
KEEP_ALIVE_TIMEOUT = 'events.KeepAliveTimeout',
|
||||
KEEP_ALIVE_RESTORED = 'events.KeepAliveRestored',
|
||||
QR_CHANNEL_ITEM = 'whatsmeow.QRChannelItem',
|
||||
MESSAGE = 'events.Message',
|
||||
RECEIPT = 'events.Receipt',
|
||||
@@ -93,8 +130,16 @@ enum WhatsMeowEvent {
|
||||
CHAT_PRESENCE = 'events.ChatPresence',
|
||||
PUSH_NAME_SETTING = 'events.PushNameSetting',
|
||||
LOGGED_OUT = 'events.LoggedOut',
|
||||
// Groups
|
||||
GROUP_INFO = 'events.GroupInfo',
|
||||
JOINED_GROUP = 'events.JoinedGroup',
|
||||
}
|
||||
|
||||
const gRPCClientConfig = {
|
||||
'grpc.max_send_message_length': 128 * 1024 * 1024,
|
||||
'grpc.max_receive_message_length': 128 * 1024 * 1024,
|
||||
};
|
||||
|
||||
export interface GowsConfig {
|
||||
connection: string;
|
||||
}
|
||||
@@ -154,6 +199,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
this.client = new MessageServiceClient(
|
||||
this.engineConfig.connection,
|
||||
grpc.credentials.createInsecure(),
|
||||
gRPCClientConfig,
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -183,6 +229,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
const client = new messages.EventStreamClient(
|
||||
this.engineConfig.connection,
|
||||
grpc.credentials.createInsecure(),
|
||||
gRPCClientConfig,
|
||||
);
|
||||
const stream = client.StreamEvents(this.session);
|
||||
return { client, stream };
|
||||
@@ -215,6 +262,11 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
}
|
||||
});
|
||||
events.on(WhatsMeowEvent.KEEP_ALIVE_RESTORED, () => {
|
||||
if (this.status != WAHASessionStatus.WORKING) {
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
}
|
||||
});
|
||||
events.on(WhatsMeowEvent.QR_CHANNEL_ITEM, async (data) => {
|
||||
if (!data.Event) {
|
||||
return;
|
||||
@@ -319,6 +371,34 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
map((event) => this.toWahaPresences(event.From || event.Chat, [event])),
|
||||
);
|
||||
this.events2.get(WAHAEvents.PRESENCE_UPDATE).switch(presenceUpdates$);
|
||||
//
|
||||
// Group Events
|
||||
//
|
||||
const joinedGroup$ = all$.pipe(onlyEvent(WhatsMeowEvent.JOINED_GROUP));
|
||||
const groupV2Join$ = joinedGroup$.pipe(map(ToGroupV2JoinEvent));
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupInfo$ = all$.pipe(onlyEvent(WhatsMeowEvent.GROUP_INFO));
|
||||
|
||||
const groupV2Leave$ = groupInfo$.pipe(
|
||||
map((event) => ToGroupV2LeaveEvent(this.me, event)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
const groupV2Participants$ = groupInfo$.pipe(
|
||||
map(ToGroupV2ParticipantsEvents),
|
||||
mergeMap((events) => events),
|
||||
);
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants$);
|
||||
|
||||
const groupV2Update$ = groupInfo$.pipe(
|
||||
map(ToGroupV2UpdateEvent),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
}
|
||||
|
||||
async fetchContactProfilePicture(id: string): Promise<string> {
|
||||
@@ -410,6 +490,40 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public async setProfileName(name: string): Promise<boolean> {
|
||||
const request = new messages.ProfileNameRequest({
|
||||
session: this.session,
|
||||
name: name,
|
||||
});
|
||||
const response = await promisify(this.client.SetProfileName)(request);
|
||||
response.toObject();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async setProfileStatus(status: string): Promise<boolean> {
|
||||
const request = new messages.ProfileStatusRequest({
|
||||
session: this.session,
|
||||
status: status,
|
||||
});
|
||||
const response = await promisify(this.client.SetProfileStatus)(request);
|
||||
response.toObject();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
async sendText(request: MessageTextRequest) {
|
||||
const jid = toJID(this.ensureSuffix(request.chatId));
|
||||
const message = new messages.MessageRequest({
|
||||
@@ -517,11 +631,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) {
|
||||
@@ -628,7 +938,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return {
|
||||
participant: toCusFormat(data.From),
|
||||
lastKnownPresence: lastKnownPresence,
|
||||
lastSeen: parseTimestamp(data.LastSeen),
|
||||
lastSeen: parseTimestampToSeconds(data.LastSeen),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -668,6 +978,25 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected toChannel(newsletter: messages.Newsletter): Channel {
|
||||
const role: ChannelRole =
|
||||
(newsletter.role?.toUpperCase() as ChannelRole) || ChannelRole.GUEST;
|
||||
@@ -688,6 +1017,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
preview: preview,
|
||||
verified: newsletter.verified,
|
||||
role: role,
|
||||
subscribersCount: newsletter.subscriberCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -775,11 +1105,188 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
return response.toObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
|
||||
protected toWAContact(contact) {
|
||||
return {
|
||||
id: toCusFormat(contact.Jid),
|
||||
name: contact.Name,
|
||||
pushname: contact.PushName,
|
||||
};
|
||||
}
|
||||
|
||||
public async getContact(query: ContactQuery) {
|
||||
const jid = toJID(query.contactId);
|
||||
const request = new messages.EntityByIdRequest({
|
||||
session: this.session,
|
||||
id: jid,
|
||||
});
|
||||
const response = await promisify(this.client.GetContact)(request);
|
||||
const data = parseJson(response);
|
||||
return this.toWAContact(data);
|
||||
}
|
||||
|
||||
public async getContacts(pagination: PaginationParams) {
|
||||
const request = new messages.GetContactsRequest({
|
||||
session: this.session,
|
||||
pagination: new messages.Pagination({
|
||||
limit: pagination.limit,
|
||||
offset: pagination.offset,
|
||||
}),
|
||||
sortBy: new messages.SortBy({
|
||||
field: pagination.sortBy || 'id',
|
||||
order:
|
||||
pagination.sortOrder === SortOrder.DESC
|
||||
? messages.SortBy.Order.DESC
|
||||
: messages.SortBy.Order.ASC,
|
||||
}),
|
||||
});
|
||||
const response = await promisify(this.client.GetContacts)(request);
|
||||
const data = parseJsonList(response);
|
||||
return data.map(this.toWAContact.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Chats methods
|
||||
*/
|
||||
public async getChatsOverview(
|
||||
pagination: PaginationParams,
|
||||
): Promise<ChatSummary[]> {
|
||||
if (!pagination.sortBy) {
|
||||
pagination.sortBy = 'timestamp';
|
||||
}
|
||||
if (!pagination.sortOrder) {
|
||||
pagination.sortOrder = SortOrder.DESC;
|
||||
}
|
||||
const chats = await this.getChats(pagination);
|
||||
|
||||
const promises = [];
|
||||
for (const chat of chats) {
|
||||
promises.push(this.fetchChatSummary(chat));
|
||||
}
|
||||
const result = await Promise.all(promises);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async fetchChatSummary(chat): Promise<ChatSummary> {
|
||||
const id = toCusFormat(chat.id);
|
||||
const name = chat.name;
|
||||
const picture = await this.getContactProfilePicture(chat.id, false);
|
||||
const messages = await this.getChatMessages(
|
||||
chat.id,
|
||||
{ limit: 1, offset: 0, downloadMedia: false },
|
||||
{},
|
||||
);
|
||||
const message = messages.length > 0 ? messages[0] : null;
|
||||
return {
|
||||
id: id,
|
||||
name: name || null,
|
||||
picture: picture,
|
||||
lastMessage: message,
|
||||
_chat: chat,
|
||||
};
|
||||
}
|
||||
|
||||
protected toWAChat(chat) {
|
||||
return {
|
||||
id: toCusFormat(chat.Jid),
|
||||
name: chat.Name,
|
||||
conversationTimestamp: parseTimestampToSeconds(
|
||||
chat.ConversationTimestamp,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
public async getChats(pagination: PaginationParams) {
|
||||
if (pagination.sortBy === ChatSortField.CONVERSATION_TIMESTAMP) {
|
||||
pagination.sortBy = 'timestamp';
|
||||
}
|
||||
const request = new messages.GetChatsRequest({
|
||||
session: this.session,
|
||||
pagination: new messages.Pagination({
|
||||
limit: pagination.limit,
|
||||
offset: pagination.offset,
|
||||
}),
|
||||
sortBy: new messages.SortBy({
|
||||
field: pagination.sortBy || 'id',
|
||||
order:
|
||||
pagination.sortOrder === SortOrder.DESC
|
||||
? messages.SortBy.Order.DESC
|
||||
: messages.SortBy.Order.ASC,
|
||||
}),
|
||||
});
|
||||
const response = await promisify(this.client.GetChats)(request);
|
||||
const data = parseJsonList(response);
|
||||
return data.map(this.toWAChat.bind(this));
|
||||
}
|
||||
|
||||
public async getChatMessages(
|
||||
chatId: string,
|
||||
query: GetChatMessagesQuery,
|
||||
filter: GetChatMessagesFilter,
|
||||
) {
|
||||
const downloadMedia = query.downloadMedia;
|
||||
let jid: messages.OptionalString;
|
||||
if (chatId === 'all') {
|
||||
jid = null;
|
||||
} else {
|
||||
jid = new messages.OptionalString({
|
||||
value: toJID(this.ensureSuffix(chatId)),
|
||||
});
|
||||
}
|
||||
|
||||
const request = new messages.GetMessagesRequest({
|
||||
session: this.session,
|
||||
filters: new messages.MessageFilters({
|
||||
jid: jid,
|
||||
timestampGte: optional(
|
||||
filter['filter.timestamp.gte'],
|
||||
messages.OptionalUInt64,
|
||||
),
|
||||
timestampLte: optional(
|
||||
filter['filter.timestamp.lte'],
|
||||
messages.OptionalUInt64,
|
||||
),
|
||||
fromMe: optional(filter['filter.fromMe'], messages.OptionalBool),
|
||||
}),
|
||||
pagination: new messages.Pagination({
|
||||
limit: query.limit,
|
||||
offset: query.offset,
|
||||
}),
|
||||
});
|
||||
const response = await promisify(this.client.GetMessages)(request);
|
||||
const msgs = parseJsonList(response);
|
||||
const promises = [];
|
||||
for (const msg of msgs) {
|
||||
promises.push(this.processIncomingMessage(msg, downloadMedia));
|
||||
}
|
||||
let result = await Promise.all(promises);
|
||||
result = result.filter(Boolean);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getChatMessage(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
query: GetChatMessageQuery,
|
||||
): Promise<null | WAMessage> {
|
||||
const key = parseMessageIdSerialized(messageId, true);
|
||||
const request = new messages.EntityByIdRequest({
|
||||
session: this.session,
|
||||
id: key.id,
|
||||
});
|
||||
const response = await promisify(this.client.GetMessageById)(request);
|
||||
const msg = parseJson(response);
|
||||
return this.processIncomingMessage(msg, query.downloadMedia);
|
||||
}
|
||||
|
||||
//
|
||||
// END - Methods for API
|
||||
//
|
||||
|
||||
private async processIncomingMessage(message, downloadMedia = true) {
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.Message) return;
|
||||
@@ -811,19 +1318,26 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
const id = buildMessageId(message);
|
||||
const body = this.extractBody(message.Message);
|
||||
const replyTo = null; // TODO: this.extractReplyTo(message.message);
|
||||
const ack = null; // TODO: Extract
|
||||
|
||||
let ack;
|
||||
if (message.Status) {
|
||||
ack = statusToAck(message.Status);
|
||||
} else {
|
||||
ack = message.Info.IsFromMe ? WAMessageAck.SERVER : WAMessageAck.DEVICE;
|
||||
}
|
||||
const mediaContent = extractMediaContent(message.Message);
|
||||
|
||||
return {
|
||||
id: id,
|
||||
timestamp: parseTimestamp(message.Info.Timestamp),
|
||||
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.Info.IsFromMe,
|
||||
body: body,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// Media
|
||||
hasMedia: Boolean(message.media),
|
||||
media: message.media,
|
||||
hasMedia: Boolean(mediaContent),
|
||||
media: message.media || null,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: ack,
|
||||
@@ -942,7 +1456,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
|
||||
const messageId = this.buildMessageIdFromKey(reactionMessage.key);
|
||||
const reaction: WAMessageReaction = {
|
||||
id: id,
|
||||
timestamp: parseTimestamp(message.Info.Timestamp),
|
||||
timestamp: parseTimestampToSeconds(message.Info.Timestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.Info.IsFromMe,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
@@ -991,6 +1505,7 @@ export class GOWSEngineMediaProcessor implements IMediaEngineProcessor<any> {
|
||||
async getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
const data = JSON.stringify(message.Message);
|
||||
const request = new messages.DownloadMediaRequest({
|
||||
// double "session" it's not a mistake here
|
||||
session: this.session.session,
|
||||
message: data,
|
||||
});
|
||||
@@ -1007,16 +1522,6 @@ export class GOWSEngineMediaProcessor implements IMediaEngineProcessor<any> {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractMediaContent(message: any) {
|
||||
const mediaContent =
|
||||
message?.documentMessage ||
|
||||
message?.imageMessage ||
|
||||
message?.videoMessage ||
|
||||
message?.audioMessage ||
|
||||
message?.stickerMessage;
|
||||
return mediaContent;
|
||||
}
|
||||
|
||||
function isMine(message) {
|
||||
return !!message.Info.IsFromMe;
|
||||
}
|
||||
@@ -1063,3 +1568,11 @@ function parseTimestamp(timestamp: string): number {
|
||||
// "2024-12-25T14:28:42+03:00" => 1234567890
|
||||
return new Date(timestamp).getTime();
|
||||
}
|
||||
|
||||
function parseTimestampToSeconds(timestamp: string): number {
|
||||
const ms = parseTimestamp(timestamp);
|
||||
if (!ms) {
|
||||
return ms;
|
||||
}
|
||||
return Math.floor(ms / 1000);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// JID just a string alias, we dump it as String in golang
|
||||
type JID = string;
|
||||
|
||||
interface GroupName {
|
||||
Name: string;
|
||||
NameSetAt: string; // ISO string format for time
|
||||
NameSetBy: JID;
|
||||
}
|
||||
|
||||
interface GroupTopic {
|
||||
Topic: string;
|
||||
TopicID: string;
|
||||
TopicSetAt: string;
|
||||
TopicSetBy: JID;
|
||||
TopicDeleted: boolean;
|
||||
}
|
||||
|
||||
interface GroupLocked {
|
||||
IsLocked: boolean;
|
||||
}
|
||||
|
||||
interface GroupAnnounce {
|
||||
IsAnnounce: boolean;
|
||||
AnnounceVersionID: string;
|
||||
}
|
||||
|
||||
interface GroupEphemeral {
|
||||
IsEphemeral: boolean;
|
||||
DisappearingTimer: number;
|
||||
}
|
||||
|
||||
interface GroupMembershipApprovalMode {
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
interface GroupDelete {
|
||||
Deleted: boolean;
|
||||
DeleteReason: string;
|
||||
}
|
||||
|
||||
interface GroupLinkTarget {
|
||||
JID: JID;
|
||||
GroupName: GroupName;
|
||||
GroupIsDefaultSub: GroupIsDefaultSub;
|
||||
}
|
||||
|
||||
interface GroupLinkChange {
|
||||
Type: string; // Enum in Go: GroupLinkChangeType
|
||||
UnlinkReason: string; // Enum in Go: GroupUnlinkReason
|
||||
Group: GroupLinkTarget;
|
||||
}
|
||||
|
||||
interface GroupIsDefaultSub {
|
||||
IsDefaultSubGroup: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoEvent {
|
||||
JID: JID;
|
||||
Notify: string;
|
||||
Sender?: JID;
|
||||
Timestamp: string;
|
||||
|
||||
Name?: GroupName;
|
||||
Topic?: GroupTopic;
|
||||
Locked?: GroupLocked;
|
||||
Announce?: GroupAnnounce;
|
||||
Ephemeral?: GroupEphemeral;
|
||||
|
||||
MembershipApprovalMode?: GroupMembershipApprovalMode;
|
||||
Delete?: GroupDelete;
|
||||
|
||||
Link?: GroupLinkChange;
|
||||
Unlink?: GroupLinkChange;
|
||||
NewInviteLink?: string;
|
||||
|
||||
PrevParticipantVersionID: string;
|
||||
ParticipantVersionID: string;
|
||||
|
||||
JoinReason: string;
|
||||
|
||||
Join: JID[];
|
||||
Leave: JID[];
|
||||
Promote: JID[];
|
||||
Demote: JID[];
|
||||
}
|
||||
|
||||
export interface GOWSGroupParticipant {
|
||||
JID: JID;
|
||||
IsAdmin: boolean;
|
||||
IsSuperAdmin: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfoFull {
|
||||
JID: JID;
|
||||
OwnerJID: JID;
|
||||
Name: string; // Name has additional properties here
|
||||
Topic: string; // Topic has additional properties here
|
||||
IsLocked: boolean; // specifies whether the group info can only be edited by admins.
|
||||
IsAnnounce: boolean; // specifies whether only admins can send messages in the group
|
||||
Participants: GOWSGroupParticipant[];
|
||||
MemberAddMode: string; // all_member_add | admin_add
|
||||
IsJoinApprovalRequired: boolean;
|
||||
}
|
||||
|
||||
export interface JoinedGroupEvent extends GroupInfoFull {
|
||||
Reason: string;
|
||||
Type: string;
|
||||
}
|
||||
@@ -22,3 +22,20 @@ export interface ChatPresence {
|
||||
State: ChatPresenceState;
|
||||
Media: ChatPresenceMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
* StatusError Status = 0
|
||||
* StatusPending Status = 1
|
||||
* StatusServerAck Status = 2
|
||||
* StatusDeliveryAck Status = 3
|
||||
* StatusRead Status = 4
|
||||
* StatusPlayed Status = 5
|
||||
*/
|
||||
export enum MessageStatus {
|
||||
Error = 0,
|
||||
Pending = 1,
|
||||
ServerAck = 2,
|
||||
DeliveryAck = 3,
|
||||
Read = 4,
|
||||
Played = 5,
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { GroupMetadata, jidNormalizedUser } from '@adiwajshing/baileys';
|
||||
import type { Contact } from '@adiwajshing/baileys/lib/Types/Contact';
|
||||
import {
|
||||
GroupParticipant as NOWEBGroupParticipant,
|
||||
ParticipantAction,
|
||||
} from '@adiwajshing/baileys/lib/Types/GroupMetadata';
|
||||
import { getGroupInviteLink } from '@waha/core/abc/session.abc';
|
||||
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import {
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
|
||||
export function ToGroupInfo(group: Partial<GroupMetadata>): GroupInfo {
|
||||
let participants: GroupParticipant[] = undefined;
|
||||
if (group.participants && group.participants.length > 0) {
|
||||
participants = group.participants.map(ToGroupParticipant);
|
||||
}
|
||||
return {
|
||||
id: group.id,
|
||||
subject: group.subject,
|
||||
description: group.desc,
|
||||
invite: group.inviteCode ? getGroupInviteLink(group.inviteCode) : undefined,
|
||||
participants: participants,
|
||||
membersCanAddNewMember: group.memberAddMode,
|
||||
membersCanSendMessages: group.announce,
|
||||
newMembersApprovalRequired: group.joinApprovalMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2JoinEvent(group: GroupMetadata): GroupV2JoinEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupParticipant(
|
||||
participant: NOWEBGroupParticipant,
|
||||
): GroupParticipant {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.admin === 'admin') {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (participant.admin === 'superadmin') {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
}
|
||||
return {
|
||||
id: toCusFormat(participant.id),
|
||||
role: role,
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupParticipantUpdate {
|
||||
id: string;
|
||||
author: string;
|
||||
participants: string[];
|
||||
action: ParticipantAction;
|
||||
}
|
||||
|
||||
export function ToGroupV2Participants(
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2ParticipantsEvent {
|
||||
let role: GroupParticipantRole;
|
||||
let type: GroupParticipantType;
|
||||
switch (update.action) {
|
||||
case 'add':
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
type = GroupParticipantType.JOIN;
|
||||
break;
|
||||
case 'remove':
|
||||
role = GroupParticipantRole.LEFT;
|
||||
type = GroupParticipantType.LEAVE;
|
||||
break;
|
||||
case 'promote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
break;
|
||||
case 'demote':
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
break;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = update.participants.map((id) => {
|
||||
return {
|
||||
id: toCusFormat(id),
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
type: type,
|
||||
timestamp: Date.now(),
|
||||
participants: participants,
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2UpdateEvent(
|
||||
group: Partial<GroupMetadata>,
|
||||
): GroupV2UpdateEvent {
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: ToGroupInfo(group),
|
||||
_data: group,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: Contact,
|
||||
update: GroupParticipantUpdate,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (update.action !== 'remove') {
|
||||
return null;
|
||||
}
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
const meId = jidNormalizedUser(me.id);
|
||||
if (!update.participants.includes(meId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: Date.now(),
|
||||
group: {
|
||||
id: toCusFormat(update.id),
|
||||
},
|
||||
_data: update,
|
||||
};
|
||||
}
|
||||
@@ -40,6 +40,12 @@ import { MessageUserReceiptUpdate } from '@adiwajshing/baileys/lib/Types/Message
|
||||
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
|
||||
import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino';
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2Participants,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/noweb/groups.noweb';
|
||||
import { sendButtonMessage } from '@waha/core/engines/noweb/noweb.buttons';
|
||||
import { NowebInMemoryStore } from '@waha/core/engines/noweb/store/NowebInMemoryStore';
|
||||
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
@@ -48,9 +54,14 @@ import { PairingCodeResponse } from '@waha/structures/auth.dto';
|
||||
import { CallData } from '@waha/structures/calls.dto';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelRole,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSummary,
|
||||
@@ -75,6 +86,7 @@ import {
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
WAMessageRevokedBody,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
import { LoggerBuilder } from '@waha/utils/logging';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
@@ -131,6 +143,7 @@ import {
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
SettingsSecurityChangeInfo,
|
||||
} from '../../../structures/groups.dto';
|
||||
import {
|
||||
WAHAChatPresences,
|
||||
@@ -149,6 +162,7 @@ import {
|
||||
import {
|
||||
ensureSuffix,
|
||||
getChannelInviteLink,
|
||||
getPublicUrlFromDirectPath,
|
||||
isNewsletter,
|
||||
WhatsappSession,
|
||||
} from '../../abc/session.abc';
|
||||
@@ -371,7 +385,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.listenConnectionEvents();
|
||||
this.subscribeEngineEvents2();
|
||||
this.listenContactsUpdatePictureProfile();
|
||||
this.enableAutoRestart();
|
||||
// this.enableAutoRestart();
|
||||
}
|
||||
|
||||
private enableAutoRestart() {
|
||||
@@ -642,6 +656,27 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public async setProfileName(name: string): Promise<boolean> {
|
||||
await this.sock.updateProfileName(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async setProfileStatus(status: string): Promise<boolean> {
|
||||
await this.sock.updateProfileStatus(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
@@ -687,6 +722,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
text: request.text,
|
||||
mentions: request.mentions?.map(toJID),
|
||||
edit: key,
|
||||
linkPreview: this.getLinkPreview(request),
|
||||
};
|
||||
return this.sock.sendMessage(jid, message);
|
||||
}
|
||||
@@ -1104,10 +1140,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return contacts.map(this.toWAContact);
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async fetchContactProfilePicture(id: string) {
|
||||
const contact = this.ensureSuffix(id);
|
||||
if (isJidNewsletter(id)) {
|
||||
@@ -1160,6 +1192,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({});
|
||||
@@ -1168,13 +1204,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);
|
||||
}
|
||||
@@ -1316,6 +1376,25 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected toChannel(newsletter: NewsletterMetadata): Channel {
|
||||
const role =
|
||||
// @ts-ignore
|
||||
@@ -1323,20 +1402,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
(newsletter.viewer_metadata?.view_role as ChannelRole) ||
|
||||
ChannelRole.GUEST;
|
||||
const preview = newsletter.preview
|
||||
? getUrlFromDirectPath(newsletter.preview)
|
||||
? getPublicUrlFromDirectPath(newsletter.preview)
|
||||
: null;
|
||||
const picture = newsletter.picture
|
||||
? getUrlFromDirectPath(newsletter.picture)
|
||||
? getPublicUrlFromDirectPath(newsletter.picture)
|
||||
: null;
|
||||
return {
|
||||
id: newsletter.id,
|
||||
name: newsletter.name,
|
||||
description: newsletter.description,
|
||||
invite: getChannelInviteLink(newsletter.invite),
|
||||
preview: preview,
|
||||
picture: picture,
|
||||
preview: preview || picture,
|
||||
picture: picture || preview,
|
||||
verified: newsletter.verification === 'VERIFIED',
|
||||
role: role,
|
||||
subscribersCount: newsletter.subscribers,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1426,6 +1506,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.events2.get(WAHAEvents.MESSAGE).switch(messagesFromOthers$);
|
||||
this.events2.get(WAHAEvents.MESSAGE_ANY).switch(messagesFromAll$);
|
||||
|
||||
const messagesRevoked$ = messagesUpsert$.pipe(
|
||||
// @ts-ignore
|
||||
filter(
|
||||
(message) =>
|
||||
message.message.protocolMessage?.type ===
|
||||
proto.Message.ProtocolMessage.Type.REVOKE,
|
||||
),
|
||||
mergeMap(async (message): Promise<WAMessageRevokedBody> => {
|
||||
const afterMessage = await this.toWAMessage(message);
|
||||
return {
|
||||
after: afterMessage,
|
||||
before: null,
|
||||
_data: message,
|
||||
};
|
||||
}),
|
||||
);
|
||||
this.events2.get(WAHAEvents.MESSAGE_REVOKED).switch(messagesRevoked$);
|
||||
|
||||
//
|
||||
// Message Reactions
|
||||
//
|
||||
@@ -1469,12 +1567,51 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.events2
|
||||
.get(WAHAEvents.STATE_CHANGE)
|
||||
.switch(fromEvent(this.sock.ev, 'connection.update'));
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(
|
||||
|
||||
const groupsUpsert$: Observable<GroupMetadata> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.upsert',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
fromEvent<GroupMetadata[]>(this.sock.ev, 'groups.upsert').pipe(
|
||||
mergeAll(),
|
||||
),
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsUpdate$: Observable<Partial<GroupMetadata>> = fromEvent(
|
||||
this.sock.ev,
|
||||
'groups.update',
|
||||
).pipe(
|
||||
// @ts-ignore
|
||||
mergeAll(),
|
||||
);
|
||||
const groupsParticipantsUpdate$: Observable<any> = fromEvent(
|
||||
this.sock.ev,
|
||||
'group-participants.update',
|
||||
);
|
||||
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupsUpsert$);
|
||||
|
||||
const groupV2Join$ = groupsUpsert$.pipe(
|
||||
map((group) => ToGroupV2JoinEvent(group)),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupV2Update$ = merge(groupsUpdate$).pipe(map(ToGroupV2UpdateEvent));
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
const groupV2Participants$ = groupsParticipantsUpdate$.pipe(
|
||||
map(ToGroupV2Participants),
|
||||
);
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants$);
|
||||
|
||||
const groupV2Leave$ = groupsParticipantsUpdate$.pipe(
|
||||
map((group) =>
|
||||
ToGroupV2LeaveEvent(this.sock?.authState?.creds?.me, group),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
this.events2
|
||||
.get(WAHAEvents.PRESENCE_UPDATE)
|
||||
.switch(
|
||||
@@ -1636,7 +1773,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return reaction;
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message, downloadMedia = true) {
|
||||
protected async processIncomingMessage(message, downloadMedia = true) {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.message) return;
|
||||
@@ -1644,6 +1781,17 @@ 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 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 {
|
||||
@@ -1670,17 +1818,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const body = this.extractBody(message.message);
|
||||
const replyTo = this.extractReplyTo(message.message);
|
||||
const ack = message.ack || message.status - 1;
|
||||
const mediaContent = extractMediaContent(message.message);
|
||||
return Promise.resolve({
|
||||
id: id,
|
||||
timestamp: ensureNumber(message.messageTimestamp),
|
||||
from: toCusFormat(fromToParticipant.from),
|
||||
fromMe: message.key.fromMe,
|
||||
body: body,
|
||||
body: body || null,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// Media
|
||||
hasMedia: Boolean(message.media),
|
||||
media: message.media,
|
||||
hasMedia: Boolean(mediaContent),
|
||||
media: message.media || null,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: ack,
|
||||
@@ -1697,21 +1846,28 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
let body = message.conversation;
|
||||
const content = extractMessageContent(message);
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
let body = content.conversation || null;
|
||||
if (!body) {
|
||||
// Some of the messages have no conversation, but instead have text in extendedTextMessage
|
||||
// https://github.com/devlikeapro/waha/issues/90
|
||||
body = message.extendedTextMessage?.text;
|
||||
body = content.extendedTextMessage?.text;
|
||||
}
|
||||
if (!body) {
|
||||
// Populate from caption
|
||||
const mediaContent = extractMediaContent(message);
|
||||
const mediaContent = extractMediaContent(content);
|
||||
// @ts-ignore - AudioMessage doesn't have caption field
|
||||
body = mediaContent?.caption;
|
||||
}
|
||||
// Response for buttons
|
||||
if (!body) {
|
||||
body = message.templateButtonReplyMessage?.selectedDisplayText;
|
||||
body = content.templateButtonReplyMessage?.selectedDisplayText;
|
||||
}
|
||||
if (!body) {
|
||||
body = content.buttonsResponseMessage?.selectedDisplayText;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { extractMessageContent, proto } from '@adiwajshing/baileys';
|
||||
|
||||
export function extractMediaContent(
|
||||
content: proto.IMessage | null | undefined,
|
||||
content: any | proto.IMessage | null | undefined,
|
||||
) {
|
||||
content = extractMessageContent(content);
|
||||
const mediaContent =
|
||||
|
||||
@@ -18,17 +18,32 @@ const { LoadPaginator } = require('./_Paginator.js');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const ChatFactory = require('whatsapp-web.js/src/factories/ChatFactory');
|
||||
|
||||
export class WebjsClient extends Client {
|
||||
export class WebjsClientCore extends Client {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// Wait until it's READY and inject more utils
|
||||
this.on(Events.READY, async () => {
|
||||
await this.pupPage.evaluate(LoadLodash);
|
||||
await this.pupPage.evaluate(LoadPaginator);
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
await this.injectWaha();
|
||||
});
|
||||
}
|
||||
|
||||
async injectWaha() {
|
||||
await this.pupPage.evaluate(LoadLodash);
|
||||
await this.pupPage.evaluate(LoadPaginator);
|
||||
await this.pupPage.evaluate(LoadWAHA);
|
||||
}
|
||||
|
||||
async setPushName(name: string) {
|
||||
await this.pupPage.evaluate(async (pushName) => {
|
||||
return await window['WAHA'].WAWebSetPushnameConnAction.setPushname(
|
||||
pushName,
|
||||
);
|
||||
}, name);
|
||||
if (this.info) {
|
||||
this.info.pushname = name;
|
||||
}
|
||||
}
|
||||
|
||||
async unpair() {
|
||||
await this.pupPage.evaluate(async () => {
|
||||
if (
|
||||
@@ -11,4 +11,14 @@ exports.LoadWAHA = () => {
|
||||
const chatPromises = chats.map((chat) => window.WWebJS.getChatModel(chat));
|
||||
return await Promise.all(chatPromises);
|
||||
};
|
||||
|
||||
// Set push name
|
||||
window.WAHA.WAWebSetPushnameConnAction = window.require(
|
||||
'WAWebSetPushnameConnAction',
|
||||
);
|
||||
|
||||
// Get my status
|
||||
window.WAHA.WAWebTextStatusCollection = window.require(
|
||||
'WAWebTextStatusCollection',
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
GroupId,
|
||||
GroupInfo,
|
||||
GroupParticipant,
|
||||
GroupParticipantRole,
|
||||
} from '@waha/structures/groups.dto';
|
||||
import {
|
||||
GroupParticipantType,
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import {
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
GroupNotificationTypes,
|
||||
GroupParticipant as WEBJSGroupParticipant,
|
||||
} from 'whatsapp-web.js';
|
||||
|
||||
function ToGroupInfo(
|
||||
group: GroupChat,
|
||||
invite: string,
|
||||
participants = [],
|
||||
): GroupInfo {
|
||||
// @ts-ignore
|
||||
const groupMetadata = group.groupMetadata;
|
||||
const info: GroupInfo = {
|
||||
// @ts-ignore
|
||||
id: group.id._serialized,
|
||||
subject: group.name,
|
||||
description: group.description,
|
||||
invite: invite,
|
||||
membersCanAddNewMember: groupMetadata.restrict,
|
||||
membersCanSendMessages: groupMetadata.announce,
|
||||
newMembersApprovalRequired: groupMetadata.membershipApprovalMode,
|
||||
participants: participants,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
export async function ToGroupV2JoinEvent(
|
||||
client: WebjsClientCore,
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2JoinEvent | null> {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const participants = getParticipants(group.participants);
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, participants);
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: info,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
function getParticipants(
|
||||
participants: WEBJSGroupParticipant[],
|
||||
): GroupParticipant[] {
|
||||
return participants.map((participant) => {
|
||||
let role: GroupParticipantRole = GroupParticipantRole.PARTICIPANT;
|
||||
if (participant.isSuperAdmin) {
|
||||
role = GroupParticipantRole.SUPERADMIN;
|
||||
} else if (participant.isAdmin) {
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
}
|
||||
|
||||
return {
|
||||
id: participant.id._serialized,
|
||||
role: role,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function ToGroupV2LeaveEvent(
|
||||
me: string,
|
||||
notification: GroupNotification,
|
||||
): GroupV2LeaveEvent | null {
|
||||
if (!notification.recipientIds.includes(me)) {
|
||||
return null;
|
||||
}
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
timestamp: notification.timestamp,
|
||||
group: group,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export async function ToGroupV2UpdateEvent(
|
||||
client: WebjsClientCore,
|
||||
notification: GroupNotification,
|
||||
): Promise<GroupV2UpdateEvent> {
|
||||
// @ts-ignore
|
||||
const group: GroupChat = await client.getChatById(notification.id.remote);
|
||||
const invite = await group.getInviteCode();
|
||||
const info: GroupInfo = ToGroupInfo(group, invite, undefined);
|
||||
return {
|
||||
group: info,
|
||||
timestamp: notification.timestamp,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
|
||||
export function ToGroupV2ParticipantsEvent(
|
||||
notification: GroupNotification,
|
||||
): GroupV2ParticipantsEvent | null {
|
||||
let type: GroupParticipantType;
|
||||
let role: GroupParticipantRole;
|
||||
if (['add', 'invite', 'linked_group_join'].includes(notification.type)) {
|
||||
type = GroupParticipantType.JOIN;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else if (['remove', 'leave'].includes(notification.type)) {
|
||||
type = GroupParticipantType.LEAVE;
|
||||
role = GroupParticipantRole.LEFT;
|
||||
} else if (['promote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.PROMOTE;
|
||||
role = GroupParticipantRole.ADMIN;
|
||||
} else if (['demote'].includes(notification.type)) {
|
||||
type = GroupParticipantType.DEMOTE;
|
||||
role = GroupParticipantRole.PARTICIPANT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
const participants: GroupParticipant[] = notification.recipientIds.map(
|
||||
(id) => {
|
||||
return {
|
||||
id: id,
|
||||
role: role,
|
||||
};
|
||||
},
|
||||
);
|
||||
const group: GroupId = {
|
||||
// @ts-ignore
|
||||
id: notification.id.remote,
|
||||
};
|
||||
return {
|
||||
group: group,
|
||||
type: type,
|
||||
timestamp: notification.timestamp,
|
||||
participants: participants,
|
||||
_data: notification,
|
||||
};
|
||||
}
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
getChannelInviteLink,
|
||||
WhatsappSession,
|
||||
} from '@waha/core/abc/session.abc';
|
||||
import {
|
||||
ToGroupV2JoinEvent,
|
||||
ToGroupV2LeaveEvent,
|
||||
ToGroupV2ParticipantsEvent,
|
||||
ToGroupV2UpdateEvent,
|
||||
} from '@waha/core/engines/webjs/groups.webjs';
|
||||
import { LocalAuth } from '@waha/core/engines/webjs/LocalAuth';
|
||||
import { WebjsClient } from '@waha/core/engines/webjs/WebjsClient';
|
||||
import { WebjsClientCore } from '@waha/core/engines/webjs/WebjsClientCore';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
@@ -15,9 +21,14 @@ import { splitAt } from '@waha/helpers';
|
||||
import { PairingCodeResponse } from '@waha/structures/auth.dto';
|
||||
import {
|
||||
Channel,
|
||||
ChannelListResult,
|
||||
ChannelMessage,
|
||||
ChannelRole,
|
||||
ChannelSearchByText,
|
||||
ChannelSearchByView,
|
||||
CreateChannelRequest,
|
||||
ListChannelsQuery,
|
||||
PreviewChannelMessages,
|
||||
} from '@waha/structures/channels.dto';
|
||||
import {
|
||||
ChatSortField,
|
||||
@@ -52,6 +63,7 @@ import {
|
||||
WAHASessionStatus,
|
||||
WAMessageAck,
|
||||
} from '@waha/structures/enums.dto';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
GroupSortField,
|
||||
@@ -72,9 +84,9 @@ import { PaginatorInMemory } from '@waha/utils/Paginator';
|
||||
import { sleep, waitUntil } from '@waha/utils/promiseTimeout';
|
||||
import { SingleDelayedJobRunner } from '@waha/utils/SingleDelayedJobRunner';
|
||||
import * as lodash from 'lodash';
|
||||
import { fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { filter, fromEvent, merge, mergeMap, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import WAWebJS, {
|
||||
import {
|
||||
Call,
|
||||
Channel as WEBJSChannel,
|
||||
Chat,
|
||||
@@ -82,6 +94,7 @@ import WAWebJS, {
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
GroupNotification,
|
||||
Label as WEBJSLabel,
|
||||
Location,
|
||||
Message,
|
||||
@@ -106,7 +119,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
private engineStateCheckDelayedJob: SingleDelayedJobRunner;
|
||||
private shouldRestart: boolean;
|
||||
|
||||
whatsapp: WebjsClient;
|
||||
whatsapp: WebjsClientCore;
|
||||
protected qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
@@ -169,7 +182,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
rmMaxRetries: undefined,
|
||||
});
|
||||
this.addProxyConfig(clientOptions);
|
||||
return new WebjsClient(clientOptions);
|
||||
return new WebjsClientCore(clientOptions);
|
||||
}
|
||||
|
||||
private restartClient() {
|
||||
@@ -450,6 +463,30 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile methods
|
||||
*/
|
||||
public async setProfileName(name: string): Promise<boolean> {
|
||||
await this.whatsapp.setPushName(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async setProfileStatus(status: string): Promise<boolean> {
|
||||
await this.whatsapp.setStatus(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected setProfilePicture(file: BinaryFile | RemoteFile): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected deleteProfilePicture(): Promise<boolean> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Other methods
|
||||
*/
|
||||
sendText(request: MessageTextRequest) {
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(
|
||||
@@ -473,13 +510,18 @@ 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);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
@@ -499,12 +541,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
name: request.title,
|
||||
});
|
||||
const options = this.getMessageOptions(request);
|
||||
return this.whatsapp.sendMessage(request.chatId, location, options);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
location,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async forwardMessage(request: MessageForwardRequest): Promise<WAMessage> {
|
||||
const forwardMessage = this.recreateMessage(request.messageId);
|
||||
const msg = await forwardMessage.forward(request.chatId);
|
||||
const msg = await forwardMessage.forward(this.ensureSuffix(request.chatId));
|
||||
// Return "sent: true" for now
|
||||
// need to research how to get the data from WebJS
|
||||
// @ts-ignore
|
||||
@@ -512,17 +558,23 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
await chat.sendSeen();
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
await chat.sendStateTyping();
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(request.chatId),
|
||||
);
|
||||
await chat.clearState();
|
||||
}
|
||||
|
||||
@@ -658,7 +710,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async deleteChat(chatId) {
|
||||
const chat = await this.whatsapp.getChatById(chatId);
|
||||
const chat = await this.whatsapp.getChatById(this.ensureSuffix(chatId));
|
||||
return chat.delete();
|
||||
}
|
||||
|
||||
@@ -843,6 +895,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;
|
||||
}
|
||||
@@ -922,6 +981,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
/**
|
||||
* Channels methods
|
||||
*/
|
||||
public searchChannelsByView(
|
||||
query: ChannelSearchByView,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public searchChannelsByText(
|
||||
query: ChannelSearchByText,
|
||||
): Promise<ChannelListResult> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
public async previewChannelMessages(
|
||||
inviteCode: string,
|
||||
query: PreviewChannelMessages,
|
||||
): Promise<ChannelMessage[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
protected ChatToChannel(chat: WEBJSChannel): Channel {
|
||||
// @ts-ignore
|
||||
const metadata = chat.channelMetadata;
|
||||
@@ -938,6 +1016,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: null,
|
||||
verified: metadata.verified,
|
||||
role: role,
|
||||
subscribersCount: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -955,6 +1034,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
picture: metadata.pictureUrl,
|
||||
verified: metadata.isVerified,
|
||||
role: role,
|
||||
subscribersCount: metadata.subscribersCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -966,6 +1046,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
channels = channels.filter((channel) => channel.role === query.role);
|
||||
}
|
||||
|
||||
// Exclude GUEST, browser saves the data
|
||||
// when we search channels or getting messages
|
||||
channels = channels.filter((channel) => channel.role !== 'GUEST');
|
||||
|
||||
const promises = channels.map(async (channel) =>
|
||||
this.whatsapp.getProfilePicUrl(channel.id),
|
||||
);
|
||||
@@ -1164,10 +1248,52 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
//
|
||||
// Groups
|
||||
//
|
||||
const groupJoin$ = fromEvent(this.whatsapp, Events.GROUP_JOIN);
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$);
|
||||
const groupLeave$ = fromEvent(this.whatsapp, Events.GROUP_LEAVE);
|
||||
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$);
|
||||
const groupJoin$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_JOIN,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_JOIN).switch(groupJoin$); // v1
|
||||
const groupV2Join$ = groupJoin$.pipe(
|
||||
mergeMap((evt) =>
|
||||
ToGroupV2JoinEvent(this.whatsapp, this.getSessionMeInfo().id, evt),
|
||||
),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_JOIN).switch(groupV2Join$);
|
||||
|
||||
const groupLeave$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_LEAVE,
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_LEAVE).switch(groupLeave$); // v1
|
||||
const groupV2Leave$ = groupLeave$.pipe(
|
||||
map((evt) => ToGroupV2LeaveEvent(this.getSessionMeInfo().id, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_LEAVE).switch(groupV2Leave$);
|
||||
|
||||
const groupAdminChanged$ = fromEvent(
|
||||
this.whatsapp,
|
||||
Events.GROUP_ADMIN_CHANGED,
|
||||
);
|
||||
const groupV2Participants = merge(
|
||||
groupJoin$,
|
||||
groupLeave$,
|
||||
groupAdminChanged$,
|
||||
).pipe(map(ToGroupV2ParticipantsEvent), filter(Boolean));
|
||||
this.events2
|
||||
.get(WAHAEvents.GROUP_V2_PARTICIPANTS)
|
||||
.switch(groupV2Participants);
|
||||
|
||||
const groupUpdate$ = fromEvent<GroupNotification>(
|
||||
this.whatsapp,
|
||||
Events.GROUP_UPDATE,
|
||||
);
|
||||
const groupV2Update$ = groupUpdate$.pipe(
|
||||
mergeMap((evt) => ToGroupV2UpdateEvent(this.whatsapp, evt)),
|
||||
filter(Boolean),
|
||||
);
|
||||
this.events2.get(WAHAEvents.GROUP_V2_UPDATE).switch(groupV2Update$);
|
||||
|
||||
//
|
||||
// Chats
|
||||
@@ -1211,7 +1337,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
this.events2.get(WAHAEvents.CALL_RECEIVED).switch(calls$);
|
||||
}
|
||||
|
||||
private async processIncomingMessage(message: Message, downloadMedia = true) {
|
||||
protected async processIncomingMessage(
|
||||
message: Message,
|
||||
downloadMedia = true,
|
||||
) {
|
||||
if (downloadMedia) {
|
||||
try {
|
||||
message = await this.downloadMedia(message);
|
||||
@@ -1250,9 +1379,9 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.media),
|
||||
hasMedia: Boolean(message.hasMedia),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
media: message.media || null,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
@@ -1267,7 +1396,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
|
||||
protected extractReplyTo(message: Message): ReplyToMessage | null {
|
||||
// @ts-ignore
|
||||
const quotedMsg = message.rawData.quotedMsg;
|
||||
const quotedMsg = message.rawData?.quotedMsg;
|
||||
if (!quotedMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AvailableInPlusVersion } from '@waha/core/exceptions';
|
||||
import {
|
||||
ChannelCategory,
|
||||
ChannelCountry,
|
||||
ChannelView,
|
||||
} from '@waha/structures/channels.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsInfoServiceCore {
|
||||
async getCountries(): Promise<ChannelCountry[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async getCategories(): Promise<ChannelCategory[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async getViews(): Promise<ChannelView[]> {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,11 @@ function splitAt(str: string, index) {
|
||||
return [fst.join(''), snd.join('')];
|
||||
}
|
||||
|
||||
export function sortObjectByValues(obj: any) {
|
||||
return Object.fromEntries(
|
||||
// @ts-ignore
|
||||
Object.entries(obj).sort(([, v1], [, v2]) => v2 - v1),
|
||||
);
|
||||
}
|
||||
|
||||
export { flipObject, parseBool, splitAt };
|
||||
+16
-6
@@ -1,5 +1,6 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { WsAdapter } from '@nestjs/platform-ws';
|
||||
import { WAHA_WEBHOOKS } from '@waha/structures/webhooks';
|
||||
import {
|
||||
getNestJSLogLevels,
|
||||
getPinoLogLevel,
|
||||
@@ -15,7 +16,6 @@ import { WhatsappConfigService } from './config.service';
|
||||
import { AppModuleCore } from './core/app.module.core';
|
||||
import { SwaggerConfiguratorCore } from './core/SwaggerConfiguratorCore';
|
||||
import { AllExceptionsFilter } from './nestjs/AllExceptionsFilter';
|
||||
import { WAHA_WEBHOOKS } from './structures/webhooks.dto';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
|
||||
const logger: Logger = pino({
|
||||
@@ -23,12 +23,22 @@ const logger: Logger = pino({
|
||||
transport: getPinoTransport(),
|
||||
}).child({ name: 'Bootstrap' });
|
||||
|
||||
logger.info('NODE - Catching unhandled rejection enabled');
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
// @ts-ignore
|
||||
logger.error(reason.stack);
|
||||
process.on('uncaughtException', (err) => {
|
||||
logger.error('Uncaught Exception:', err);
|
||||
if (err instanceof Error) {
|
||||
logger.error(err.stack);
|
||||
}
|
||||
});
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
logger.error('Unhandled Rejection at:', promise);
|
||||
if (reason instanceof Error) {
|
||||
logger.error(reason.stack);
|
||||
} else {
|
||||
logger.error('Unhandled rejection reason:', reason);
|
||||
}
|
||||
});
|
||||
logger.info('NODE - Catching unhandled rejections and exceptions enabled');
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('SIGINT received');
|
||||
});
|
||||
|
||||
@@ -35,14 +35,6 @@ export class RequestCodeRequest {
|
||||
localeCountry: string;
|
||||
}
|
||||
|
||||
export class OTPRequest {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class CaptchaBody {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class PairingCodeResponse {
|
||||
code: string;
|
||||
}
|
||||
@@ -18,3 +18,7 @@ export class SessionQuery {
|
||||
export class SessionBaseRequest {
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
export class Result {
|
||||
success: boolean = true;
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
import { ApiParam, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { BooleanString } from '@waha/nestjs/validation/BooleanString';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateChannelRequest {
|
||||
@ApiProperty({
|
||||
@@ -38,7 +50,7 @@ export class ListChannelsQuery {
|
||||
role?: ChannelRoleFilter;
|
||||
}
|
||||
|
||||
export class Channel {
|
||||
class ChannelBase {
|
||||
@ApiProperty({
|
||||
description: 'Newsletter id',
|
||||
example: '123123123123@newsletter',
|
||||
@@ -73,9 +85,15 @@ export class Channel {
|
||||
|
||||
verified: boolean;
|
||||
|
||||
subscribersCount: number;
|
||||
}
|
||||
|
||||
export class Channel extends ChannelBase {
|
||||
role: ChannelRole;
|
||||
}
|
||||
|
||||
export class ChannelPublicInfo extends ChannelBase {}
|
||||
|
||||
export const NewsletterIdApiParam = ApiParam({
|
||||
name: 'id',
|
||||
required: true,
|
||||
@@ -96,3 +114,86 @@ export const NewsletterIdOrInviteCodeApiParam = ApiParam({
|
||||
description:
|
||||
'WhatsApp Channel ID or invite code from invite link https://www.whatsapp.com/channel/11111',
|
||||
});
|
||||
|
||||
export class ChannelCountry {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelCategory {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelView {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ChannelSearch {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
limit: number = 50;
|
||||
|
||||
@IsString()
|
||||
startCursor: string = '';
|
||||
}
|
||||
|
||||
export class ChannelSearchByView extends ChannelSearch {
|
||||
@IsString()
|
||||
view: string = 'RECOMMENDED';
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ArrayMinSize(1)
|
||||
countries: string[] = ['US'];
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
categories: string[] = [];
|
||||
}
|
||||
|
||||
export class ChannelSearchByText extends ChannelSearch {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
text: string = 'Donald Trump';
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
categories: string[] = [];
|
||||
}
|
||||
|
||||
export class ChannelPagination {
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
}
|
||||
|
||||
export class ChannelListResult {
|
||||
page: ChannelPagination;
|
||||
channels: ChannelPublicInfo[];
|
||||
}
|
||||
|
||||
export class ChannelMessage {
|
||||
message: WAMessage;
|
||||
|
||||
@ApiProperty({
|
||||
example: { '👍': 10, '❤️': 5 },
|
||||
type: Object,
|
||||
additionalProperties: { type: 'number' },
|
||||
})
|
||||
reactions: { [emoji: string]: number };
|
||||
|
||||
viewCount: number;
|
||||
}
|
||||
|
||||
export class PreviewChannelMessages {
|
||||
@Transform(BooleanString)
|
||||
@IsBoolean()
|
||||
downloadMedia: boolean = false;
|
||||
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
limit: number = 100;
|
||||
}
|
||||
@@ -165,6 +165,8 @@ export class EditMessageRequest {
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
|
||||
linkPreview?: boolean = true;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
|
||||
@@ -11,6 +11,10 @@ export enum WAHAEvents {
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
GROUP_V2_JOIN = 'group.v2.join',
|
||||
GROUP_V2_LEAVE = 'group.v2.leave',
|
||||
GROUP_V2_UPDATE = 'group.v2.update',
|
||||
GROUP_V2_PARTICIPANTS = 'group.v2.participants',
|
||||
PRESENCE_UPDATE = 'presence.update',
|
||||
POLL_VOTE = 'poll.vote',
|
||||
POLL_VOTE_FAILED = 'poll.vote.failed',
|
||||
|
||||
@@ -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',
|
||||
@@ -88,3 +105,69 @@ export class GroupsPaginationParams extends PaginationParams {
|
||||
@IsEnum(GroupSortField)
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export enum GroupParticipantRole {
|
||||
LEFT = 'left',
|
||||
PARTICIPANT = 'participant',
|
||||
ADMIN = 'admin',
|
||||
SUPERADMIN = 'superadmin',
|
||||
}
|
||||
|
||||
export class GroupParticipant {
|
||||
@ApiProperty({
|
||||
example: '123456789@c.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: GroupParticipantRole.PARTICIPANT,
|
||||
})
|
||||
role: GroupParticipantRole;
|
||||
}
|
||||
|
||||
export class GroupId {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class GroupInfo {
|
||||
@ApiProperty({
|
||||
example: '123456789@g.us',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Name',
|
||||
})
|
||||
subject: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Group Description',
|
||||
})
|
||||
description: string;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Invite URL',
|
||||
example: 'https://chat.whatsapp.com/1234567890abcdef',
|
||||
})
|
||||
invite?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can add new members',
|
||||
})
|
||||
membersCanAddNewMember: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Members can send messages to the group',
|
||||
})
|
||||
membersCanSendMessages: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Admin approval required for new members',
|
||||
})
|
||||
newMembersApprovalRequired: boolean;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { GroupId, GroupInfo, GroupParticipant } from './groups.dto';
|
||||
|
||||
/**
|
||||
* Happens when you join or are added to a group.
|
||||
*/
|
||||
export class GroupV2JoinEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupInfo;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Happens when you leave or are removed from a group.
|
||||
*/
|
||||
export class GroupV2LeaveEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: GroupId;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export class GroupV2UpdateEvent {
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
group: Partial<GroupInfo>;
|
||||
|
||||
_data: any;
|
||||
}
|
||||
|
||||
export enum GroupParticipantType {
|
||||
JOIN = 'join',
|
||||
LEAVE = 'leave',
|
||||
PROMOTE = 'promote',
|
||||
DEMOTE = 'demote',
|
||||
}
|
||||
|
||||
export class GroupV2ParticipantsEvent {
|
||||
group: GroupId;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of the event',
|
||||
})
|
||||
type: GroupParticipantType;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
participants: GroupParticipant[];
|
||||
|
||||
_data: any;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import {
|
||||
GroupV2JoinEvent,
|
||||
GroupV2LeaveEvent,
|
||||
GroupV2ParticipantsEvent,
|
||||
GroupV2UpdateEvent,
|
||||
} from '@waha/structures/groups.events.dto';
|
||||
import { WAHAWebhook } from '@waha/structures/webhooks.dto';
|
||||
|
||||
export class WebhookGroupV2Join extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you joined or were added to a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_JOIN;
|
||||
|
||||
payload: GroupV2JoinEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Leave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When you left or were removed from a group',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_LEAVE;
|
||||
|
||||
payload: GroupV2LeaveEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Update extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When group info is updated',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_UPDATE;
|
||||
|
||||
payload: GroupV2UpdateEvent;
|
||||
}
|
||||
|
||||
export class WebhookGroupV2Participants extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'When participants changed - join, leave, promote to admin',
|
||||
})
|
||||
event = WAHAEvents.GROUP_V2_PARTICIPANTS;
|
||||
|
||||
payload: GroupV2ParticipantsEvent;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
|
||||
import { ChatIdProperty } from '@waha/structures/properties.dto';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class MyProfile {
|
||||
@ChatIdProperty()
|
||||
id: string;
|
||||
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://example.com/picture.jpg',
|
||||
})
|
||||
picture: string | null;
|
||||
}
|
||||
|
||||
export class ProfileNameRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'My New Name',
|
||||
})
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ProfileStatusRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: '🎉 Hey there! I am using WhatsApp 🎉',
|
||||
})
|
||||
status: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(RemoteFile, BinaryFile)
|
||||
export class ProfilePictureRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
}
|
||||
@@ -91,6 +91,7 @@ export class PollVotePayload {
|
||||
export class WAMessageRevokedBody {
|
||||
after?: WAMessage;
|
||||
before?: WAMessage;
|
||||
_data?: any;
|
||||
}
|
||||
|
||||
export class WASessionStatusBody {
|
||||
@@ -148,14 +149,14 @@ export class WAHAWebhookSessionStatus extends WAHAWebhook {
|
||||
payload: WASessionStatusBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessage extends WAHAWebhook {
|
||||
export class WAHAWebhookMessage extends WAHAWebhook {
|
||||
@ApiProperty({ description: 'Incoming message.' })
|
||||
event = WAHAEvents.MESSAGE;
|
||||
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user reacts or removes a reaction.',
|
||||
@@ -165,7 +166,7 @@ class WAHAWebhookMessageReaction extends WAHAWebhook {
|
||||
payload: WAMessageReaction;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Fired on all message creations, including your own.',
|
||||
})
|
||||
@@ -174,7 +175,7 @@ class WAHAWebhookMessageAny extends WAHAWebhook {
|
||||
payload: WAMessage;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Receive events when server or recipient gets the message, read or played it.',
|
||||
@@ -184,7 +185,7 @@ class WAHAWebhookMessageAck extends WAHAWebhook {
|
||||
payload: WAMessageAckBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
export class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when a user, whether it be you or any other participant, ' +
|
||||
@@ -195,34 +196,37 @@ class WAHAWebhookMessageRevoked extends WAHAWebhook {
|
||||
payload: WAMessageRevokedBody;
|
||||
}
|
||||
|
||||
class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
export class WAHAWebhookStateChange extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'It’s an internal engine’s state, not session status.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.STATE_CHANGE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupJoin extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one join a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_JOIN;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
export class WAHAWebhookGroupLeave extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Some one left a group.',
|
||||
deprecated: true,
|
||||
})
|
||||
event = WAHAEvents.GROUP_LEAVE;
|
||||
|
||||
payload: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
export class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The most recent presence information for a chat.',
|
||||
})
|
||||
@@ -231,7 +235,7 @@ class WAHAWebhookPresenceUpdate extends WAHAWebhook {
|
||||
payload: WAHAChatPresences;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'With this event, you receive new votes for the poll sent.',
|
||||
})
|
||||
@@ -240,7 +244,7 @@ class WAHAWebhookPollVote extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
export class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'There may be cases when it fails to decrypt a vote from the user. ' +
|
||||
@@ -251,7 +255,7 @@ class WAHAWebhookPollVoteFailed extends WAHAWebhook {
|
||||
payload: PollVotePayload;
|
||||
}
|
||||
|
||||
class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
export class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the chat is archived or unarchived',
|
||||
@@ -261,7 +265,7 @@ class WAHAWebhookChatArchive extends WAHAWebhook {
|
||||
payload: ChatArchiveEvent;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
export class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is received by the user.',
|
||||
@@ -271,7 +275,7 @@ class WAHAWebhookCallReceived extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
export class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is accepted by the user.',
|
||||
@@ -281,7 +285,7 @@ class WAHAWebhookCallAccepted extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
export class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'The event is triggered when the call is rejected by the user.',
|
||||
@@ -291,7 +295,7 @@ class WAHAWebhookCallRejected extends WAHAWebhook {
|
||||
payload: CallData;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is created or updated',
|
||||
})
|
||||
@@ -300,7 +304,7 @@ class WAHAWebhookLabelUpsert extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted',
|
||||
})
|
||||
@@ -309,7 +313,7 @@ class WAHAWebhookLabelDeleted extends WAHAWebhook {
|
||||
payload: Label;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is added to a chat',
|
||||
})
|
||||
@@ -318,7 +322,7 @@ class WAHAWebhookLabelChatAdded extends WAHAWebhook {
|
||||
payload: LabelChatAssociation;
|
||||
}
|
||||
|
||||
class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
export class WAHAWebhookLabelChatDeleted extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'The event is triggered when a label is deleted from a chat',
|
||||
})
|
||||
@@ -332,7 +336,7 @@ export class EnginePayload {
|
||||
data: any;
|
||||
}
|
||||
|
||||
class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
export class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
@ApiProperty({
|
||||
description: 'Internal engine event.',
|
||||
})
|
||||
@@ -340,28 +344,3 @@ class WAHAWebhookEngineEvent extends WAHAWebhook {
|
||||
|
||||
payload: EnginePayload;
|
||||
}
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookStateChange,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Participants,
|
||||
WebhookGroupV2Update,
|
||||
} from '@waha/structures/groups.webhooks.dto';
|
||||
import {
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookStateChange,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
|
||||
const WAHA_WEBHOOKS = [
|
||||
WAHAWebhookSessionStatus,
|
||||
WAHAWebhookMessage,
|
||||
WAHAWebhookMessageReaction,
|
||||
WAHAWebhookMessageAny,
|
||||
WAHAWebhookMessageAck,
|
||||
WAHAWebhookMessageRevoked,
|
||||
WebhookGroupV2Join,
|
||||
WebhookGroupV2Leave,
|
||||
WebhookGroupV2Update,
|
||||
WebhookGroupV2Participants,
|
||||
WAHAWebhookPresenceUpdate,
|
||||
WAHAWebhookPollVote,
|
||||
WAHAWebhookPollVoteFailed,
|
||||
WAHAWebhookChatArchive,
|
||||
WAHAWebhookCallReceived,
|
||||
WAHAWebhookCallAccepted,
|
||||
WAHAWebhookCallRejected,
|
||||
WAHAWebhookLabelUpsert,
|
||||
WAHAWebhookLabelDeleted,
|
||||
WAHAWebhookLabelChatAdded,
|
||||
WAHAWebhookLabelChatDeleted,
|
||||
WAHAWebhookEngineEvent,
|
||||
WAHAWebhookGroupJoin,
|
||||
WAHAWebhookGroupLeave,
|
||||
WAHAWebhookStateChange,
|
||||
];
|
||||
export { WAHA_WEBHOOKS };
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2025.1.6',
|
||||
version: '2025.2.8',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
"waha": {
|
||||
"gows": {
|
||||
"repo": "devlikeapro/gows",
|
||||
"ref": "32baf0cc2e8c35388b0b6d4a1a446cca0f3525a8"
|
||||
"ref": "v1.0.0"
|
||||
},
|
||||
"dashboard": {
|
||||
"repo": "devlikeapro/dashboard",
|
||||
"ref": "a1c839d8da94d1cade182addffdfc42fc0cf7ed3"
|
||||
"ref": "d6cd941f8d7621f401305e03ad2c1db8dc0a64f0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,13 @@ __metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
|
||||
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-678":
|
||||
version: 6.7.8
|
||||
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=04738f528321857efe837e3f93c73059878ecdcb"
|
||||
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-6712":
|
||||
version: 6.7.12
|
||||
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=dcc5d0ea0827c0d57ceda4f94b9cdb260760b77e"
|
||||
dependencies:
|
||||
"@adiwajshing/keyed-db": ^0.2.4
|
||||
"@hapi/boom": ^9.1.3
|
||||
"@whiskeysockets/eslint-config": "github:whiskeysockets/eslint-config"
|
||||
async-lock: ^1.4.1
|
||||
audio-decode: ^2.1.3
|
||||
axios: ^1.6.0
|
||||
@@ -39,7 +40,7 @@ __metadata:
|
||||
optional: true
|
||||
sharp:
|
||||
optional: true
|
||||
checksum: 0f7b2031eeb25137c65aee3151028f7a8637cfed0763c7426817415ec818ca71f55267b9de88511905bd266f4006301a26206dac9bdb1821d13f1a0cbf075e32
|
||||
checksum: 24e16d0b11dbb83a562e333fcc11f9489d4f3c6735c12f726eead983dd3bbc0f4dbce9c486d849b26ddcb0e2be68972d205d21837aabfcb42ebd8b115958f053
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1242,6 +1243,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.4.0":
|
||||
version: 4.4.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.4.1"
|
||||
dependencies:
|
||||
eslint-visitor-keys: ^3.4.3
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
checksum: a7ffc838eb6a9ef594cda348458ccf38f34439ac77dc090fa1c120024bcd4eb911dfd74d5ef44d42063e7949fa7c5123ce714a015c4abb917d4124be1bd32bfe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.10.0":
|
||||
version: 4.12.1
|
||||
resolution: "@eslint-community/regexpp@npm:4.12.1"
|
||||
checksum: 0d628680e204bc316d545b4993d3658427ca404ae646ce541fcc65306b8c712c340e5e573e30fb9f85f4855c0c5f6dca9868931f2fcced06417fbe1a0c6cd2d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.12.4":
|
||||
version: 1.12.4
|
||||
resolution: "@grpc/grpc-js@npm:1.12.4"
|
||||
@@ -3453,6 +3472,29 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:^7.15.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": ^4.10.0
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/type-utils": 7.18.0
|
||||
"@typescript-eslint/utils": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
graphemer: ^1.4.0
|
||||
ignore: ^5.3.1
|
||||
natural-compare: ^1.4.0
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^7.0.0
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: dfcf150628ca2d4ccdfc20b46b0eae075c2f16ef5e70d9d2f0d746acf4c69a09f962b93befee01a529f14bbeb3e817b5aba287d7dd0edc23396bc5ed1f448c3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/experimental-utils@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/experimental-utils@npm:3.9.1"
|
||||
@@ -3486,6 +3528,51 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:^7.15.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/parser@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
debug: ^4.3.4
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 132b56ac3b2d90b588d61d005a70f6af322860974225b60201cbf45abf7304d67b7d8a6f0ade1c188ac4e339884e78d6dcd450417f1481998f9ddd155bab0801
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
checksum: b982c6ac13d8c86bb3b949c6b4e465f3f60557c2ccf4cc229799827d462df56b9e4d3eaed7711d79b875422fc3d71ec1ebcb5195db72134d07c619e3c5506b57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
"@typescript-eslint/utils": 7.18.0
|
||||
debug: ^4.3.4
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 68fd5df5146c1a08cde20d59b4b919acab06a1b06194fe4f7ba1b928674880249890785fbbc97394142f2ef5cff5a7fba9b8a940449e7d5605306505348e38bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/types@npm:3.9.1"
|
||||
@@ -3493,6 +3580,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/types@npm:7.18.0"
|
||||
checksum: 7df2750cd146a0acd2d843208d69f153b458e024bbe12aab9e441ad2c56f47de3ddfeb329c4d1ea0079e2577fea4b8c1c1ce15315a8d49044586b04fedfe7a4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:3.9.1"
|
||||
@@ -3512,6 +3606,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/visitor-keys": 7.18.0
|
||||
debug: ^4.3.4
|
||||
globby: ^11.1.0
|
||||
is-glob: ^4.0.3
|
||||
minimatch: ^9.0.4
|
||||
semver: ^7.6.0
|
||||
ts-api-utils: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: c82d22ec9654973944f779eb4eb94c52f4a6eafaccce2f0231ff7757313f3a0d0256c3252f6dfe6d43f57171d09656478acb49a629a9d0c193fb959bc3f36116
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/utils@npm:7.18.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": ^4.4.0
|
||||
"@typescript-eslint/scope-manager": 7.18.0
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
"@typescript-eslint/typescript-estree": 7.18.0
|
||||
peerDependencies:
|
||||
eslint: ^8.56.0
|
||||
checksum: 751dbc816dab8454b7dc6b26a56671dbec08e3f4ef94c2661ce1c0fc48fa2d05a64e03efe24cba2c22d03ba943cd3c5c7a5e1b7b03bbb446728aec1c640bd767
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:3.9.1":
|
||||
version: 3.9.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:3.9.1"
|
||||
@@ -3521,6 +3648,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:7.18.0":
|
||||
version: 7.18.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:7.18.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": 7.18.0
|
||||
eslint-visitor-keys: ^3.4.3
|
||||
checksum: 6e806a7cdb424c5498ea187a5a11d0fef7e4602a631be413e7d521e5aec1ab46ba00c76cfb18020adaa0a8c9802354a163bfa0deb74baa7d555526c7517bb158
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@wasm-audio-decoders/common@npm:9.0.4":
|
||||
version: 9.0.4
|
||||
resolution: "@wasm-audio-decoders/common@npm:9.0.4"
|
||||
@@ -3711,6 +3848,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whiskeysockets/eslint-config@github:whiskeysockets/eslint-config":
|
||||
version: 1.0.0
|
||||
resolution: "@whiskeysockets/eslint-config@https://github.com/whiskeysockets/eslint-config.git#commit=326b55f2842668f4e11f471451c4e39819a0e1bf"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": ^7.15.0
|
||||
"@typescript-eslint/parser": ^7.15.0
|
||||
eslint-plugin-simple-import-sort: ^12.1.1
|
||||
peerDependencies:
|
||||
eslint: "*"
|
||||
typescript: ">=4"
|
||||
checksum: a1ae2c83b0b635cf4898b51a597cd9cabc0df3f5bd4f439a705a1f35877802e8f07836f3298d02b5543d41e07c266b348bd8c8458f83ed225d975e89618b0483
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xtuc/ieee754@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@xtuc/ieee754@npm:1.2.0"
|
||||
@@ -6236,6 +6387,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-simple-import-sort@npm:^12.1.1":
|
||||
version: 12.1.1
|
||||
resolution: "eslint-plugin-simple-import-sort@npm:12.1.1"
|
||||
peerDependencies:
|
||||
eslint: ">=5.0.0"
|
||||
checksum: 6d73e43ecf6221c1952e0cc820e6867e3e1fe973575e23d438d1d3de52284ceb2a01d31e20a76de11feb158bbba98f113150a676cc5526a8b3a5844d63ca37f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.0.0, eslint-scope@npm:^5.1.0":
|
||||
version: 5.1.1
|
||||
resolution: "eslint-scope@npm:5.1.1"
|
||||
@@ -6262,6 +6422,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^3.4.3":
|
||||
version: 3.4.3
|
||||
resolution: "eslint-visitor-keys@npm:3.4.3"
|
||||
checksum: 36e9ef87fca698b6fd7ca5ca35d7b2b6eeaaf106572e2f7fd31c12d3bfdaccdb587bba6d3621067e5aece31c8c3a348b93922ab8f7b2cbc6aaab5e1d89040c60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:7.7.0":
|
||||
version: 7.7.0
|
||||
resolution: "eslint@npm:7.7.0"
|
||||
@@ -7174,7 +7341,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globby@npm:^11.0.1":
|
||||
"globby@npm:^11.0.1, globby@npm:^11.1.0":
|
||||
version: 11.1.0
|
||||
resolution: "globby@npm:11.1.0"
|
||||
dependencies:
|
||||
@@ -7211,6 +7378,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphemer@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "graphemer@npm:1.4.0"
|
||||
checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"grpc-tools@npm:^1.12.4":
|
||||
version: 1.12.4
|
||||
resolution: "grpc-tools@npm:1.12.4"
|
||||
@@ -7440,6 +7614,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ignore@npm:^5.3.1":
|
||||
version: 5.3.2
|
||||
resolution: "ignore@npm:5.3.2"
|
||||
checksum: 2acfd32a573260ea522ea0bfeff880af426d68f6831f973129e2ba7363f422923cf53aab62f8369cbf4667c7b25b6f8a3761b34ecdb284ea18e87a5262a865be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "import-fresh@npm:3.3.0"
|
||||
@@ -12050,6 +12231,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-api-utils@npm:^1.3.0":
|
||||
version: 1.4.3
|
||||
resolution: "ts-api-utils@npm:1.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.2.0"
|
||||
checksum: ea00dee382d19066b2a3d8929f1089888b05fec797e32e7a7004938eda1dccf2e77274ee2afcd4166f53fab9b8d7ee90ebb225a3183f9ba8817d636f688a148d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-jest@npm:^29.1.3":
|
||||
version: 29.1.5
|
||||
resolution: "ts-jest@npm:29.1.5"
|
||||
@@ -12559,7 +12749,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "waha@workspace:."
|
||||
dependencies:
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-678"
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-6712"
|
||||
"@adiwajshing/keyed-db": ^0.2.4
|
||||
"@aws-sdk/client-s3": ^3.633.0
|
||||
"@aws-sdk/s3-request-presigner": ^3.633.0
|
||||
@@ -12741,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=bb4525a80667d83122108a03d936e41cefd179a4"
|
||||
dependencies:
|
||||
"@pedroslopez/moduleraid": ^5.0.2
|
||||
archiver: ^5.3.1
|
||||
@@ -12759,7 +12949,7 @@ __metadata:
|
||||
optional: true
|
||||
unzipper:
|
||||
optional: true
|
||||
checksum: 441e54f450ed9ea5d5756c1ca4fd592e498ad79247d2c6508aee2a5c1b2608a9eb3f6360234f2f0ce563edb187a3d453d41773e745da9bebf657f575b9fa66b4
|
||||
checksum: a06d30c9f52a1ff0268e4e98e3c0d80112a5dc8a9445d0df460fac3cbe0168b6f1c5ad494518b1e9a98acb54da8c46a2f6644e9f41b1b2987771fac101747c55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user