Compare commits

..
37 Commits
Author SHA1 Message Date
devlikepro 07340b8f53 [core] Up NOWEB
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #809
2025-02-17 16:53:08 +07:00
devlikepro 2f9dd7faa8 [core] catch uncaughtException
fix #809
2025-02-17 16:53:07 +07:00
devlikepro 3f81fd4a87 [core] 2025.2.4 2025-02-17 16:53:07 +07:00
devlikepro 4dd961fc66 [core] Up dashboard 2025-02-17 16:53:07 +07:00
devlikepro 0bd70bfb32 [core] Deprecate webhooks: state.change, group.join, group.leave
fix #404
2025-02-17 16:53:06 +07:00
devlikepro 3ab845d2d6 [core] Groups V2 Events
fix #583
fix #527
fix #404
2025-02-17 16:53:06 +07:00
devlikepro 443f09d5a2 [core] 2025.2.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-12 17:18:00 +07:00
devlikepro 3500952bb7 [core] Up GOWS 2025-02-12 17:18:00 +07:00
devlikepro e98182c4b5 [core] Add /profile API
fix #529
2025-02-12 17:18:00 +07:00
devlikepro 2337827744 [core] GOWS hasMedia fix 2025-02-12 17:18:00 +07:00
devlikepro 556dd66af3 [core] NOWEB hasMedia fix 2025-02-12 17:18:00 +07:00
devlikepro 8bdad3f4f9 [core] WEBJS hasMedia fix 2025-02-12 17:17:59 +07:00
devlikepro 6346d4143d [core] 2025.2.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-10 08:57:14 +07:00
devlikepro fd8dc88760 [core] rm auth/authorize-code 2025-02-10 08:57:14 +07:00
devlikepro b2810f390e [core] max connections 3000 2025-02-10 08:57:14 +07:00
devlikepro 23d3cd0f4b [core] Up GOWS - build proto 2025-02-10 08:57:13 +07:00
devlikepro 4a3c76773d [core] Up GOWS 2025-02-10 08:57:12 +07:00
devlikepro d23e7604f0 [core] NOWEB Channels Search 2025-02-10 08:57:12 +07:00
devlikepro 0b3f6c6eff [core] GOWS - Search Channels 2025-02-10 08:57:12 +07:00
devlikepro f2869c892b [core] WEBJS - ensure suffix 2025-02-10 08:57:12 +07:00
devlikepro 3755a9529b [core] GOWS - Channels Search, Messages Preview 2025-02-10 08:57:11 +07:00
devlikepro 890f0593ee [core] NOWEB - Channels Search, Messages Preview 2025-02-10 08:57:11 +07:00
devlikepro e6d666b5b0 [core] WEBJS Channels Search, Messages Preview 2025-02-10 08:57:10 +07:00
devlikepro f440acf145 [core] Channels Search, Messages Preview - API 2025-02-10 08:57:10 +07:00
devlikepro 1ad0e0c6dd [core] Add proto files
fixes #784
2025-02-07 08:41:37 +07:00
devlikepro bb8691ddc0 [core] Use waha docker image
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
2025-02-02 13:25:24 +07:00
devlikepro cf3041d07b [core] swagger 2025-02-02 13:23:24 +07:00
devlikepro 313b3d5d6b [core] up NOWEB 2025-02-02 13:23:24 +07:00
devlikepro 122e7336be [core] NOWEB - use channels-6712 branch 2025-02-02 13:23:24 +07:00
devlikepro e954453f6b [core] up GOWS 2025-02-02 13:23:24 +07:00
devlikepro 55ef3d09f3 [core] 2025.2.1 2025-01-30 14:35:08 +07:00
devlikepro 88b9cc5ac9 [core] Up dashboard 2025-01-30 14:35:08 +07:00
devlikepro f07cb7470d [core] GOWS - contacts, chats 2025-01-30 14:33:50 +07:00
devlikepro bd874a0232 [core] Up NOWEB 2025-01-30 14:33:50 +07:00
devlikepro 7cc4cccb9c [core] Fetch gows bin and proto from gows releases 2025-01-30 14:33:50 +07:00
devlikepro e85a3bacb7 [core] GOWS - Add ack to incoming messages 2025-01-30 14:33:50 +07:00
devlikepro 530dfa758f [core] Handle keep alive restored 2025-01-30 14:33:50 +07:00
46 changed files with 21510 additions and 207 deletions

No files matched your search

-1
View File
@@ -1,5 +1,4 @@
Dockerfile
src/core/engines/gows/grpc
src/core/engines/gows/proto
.*sessions
sessions
-1
View File
@@ -1,4 +1,3 @@
src/core/engines/gows/grpc
src/core/engines/gows/proto
.env
*.heapsnapshot
+8 -9
View File
@@ -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/v.${GOWS_SHA}/gows-${ARCH} && \
chmod +x /go/gows/bin/gows
#
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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!
[![Docker Pulls](https://img.shields.io/docker/pulls/devlikeapro/whatsapp-http-api)](https://hub.docker.com/r/devlikeapro/waha)
[![Docker Pulls](https://img.shields.io/docker/pulls/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
View File
@@ -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
View File
@@ -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",
+1 -1
View File
@@ -53,7 +53,7 @@ function cleanDirectory(directory, suffix) {
// Helper function to download files
async function downloadFiles(repo, ref, directory) {
for (const file of PROTO_FILES) {
const url = `https://raw.githubusercontent.com/${repo}/${ref}/proto/${file}`;
const url = `https://github.com/${repo}/releases/download/v.${ref}/${file}`;
const filePath = path.join(directory, file);
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
-39
View File
@@ -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 };
+102 -5
View File
@@ -3,31 +3,129 @@ 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,
) {}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Get('')
@SessionApiParam
@@ -76,8 +174,7 @@ 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);
}
}
+3 -5
View File
@@ -18,7 +18,7 @@ import {
} from '@waha/nestjs/params/SessionApiParam';
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,
@@ -53,8 +53,7 @@ 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);
}
@@ -65,8 +64,7 @@ export class GroupsController {
@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 };
}
+94
View File
@@ -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 };
}
}
+3 -4
View File
@@ -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': {
+100 -8
View File
@@ -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';
@@ -326,21 +332,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.info('Refreshing my profile picture after update...');
this.refreshMyProfilePicture()
.then(() => {
this.logger.info('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 +558,7 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public getContactAbout(query: ContactQuery) {
public getContactAbout(query: ContactQuery): Promise<{ about: string }> {
throw new NotImplementedByEngineError();
}
@@ -537,8 +580,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;
@@ -661,6 +712,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 +839,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}`;
}
+4
View File
@@ -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,
+191
View File
@@ -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
+817
View File
@@ -0,0 +1,817 @@
// GENERATED CODE -- DO NOT EDIT!
'use strict';
var grpc = require('@grpc/grpc-js');
var gows_pb = require('./gows_pb.js');
function serialize_messages_ChatPresenceRequest(arg) {
if (!(arg instanceof gows_pb.ChatPresenceRequest)) {
throw new Error('Expected argument of type messages.ChatPresenceRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ChatPresenceRequest(buffer_arg) {
return gows_pb.ChatPresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CheckPhonesRequest(arg) {
if (!(arg instanceof gows_pb.CheckPhonesRequest)) {
throw new Error('Expected argument of type messages.CheckPhonesRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_CheckPhonesRequest(buffer_arg) {
return gows_pb.CheckPhonesRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CheckPhonesResponse(arg) {
if (!(arg instanceof gows_pb.CheckPhonesResponse)) {
throw new Error('Expected argument of type messages.CheckPhonesResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_CheckPhonesResponse(buffer_arg) {
return gows_pb.CheckPhonesResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_CreateNewsletterRequest(arg) {
if (!(arg instanceof gows_pb.CreateNewsletterRequest)) {
throw new Error('Expected argument of type messages.CreateNewsletterRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_CreateNewsletterRequest(buffer_arg) {
return gows_pb.CreateNewsletterRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_DownloadMediaRequest(arg) {
if (!(arg instanceof gows_pb.DownloadMediaRequest)) {
throw new Error('Expected argument of type messages.DownloadMediaRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_DownloadMediaRequest(buffer_arg) {
return gows_pb.DownloadMediaRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_DownloadMediaResponse(arg) {
if (!(arg instanceof gows_pb.DownloadMediaResponse)) {
throw new Error('Expected argument of type messages.DownloadMediaResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_DownloadMediaResponse(buffer_arg) {
return gows_pb.DownloadMediaResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Empty(arg) {
if (!(arg instanceof gows_pb.Empty)) {
throw new Error('Expected argument of type messages.Empty');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_Empty(buffer_arg) {
return gows_pb.Empty.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_EntityByIdRequest(arg) {
if (!(arg instanceof gows_pb.EntityByIdRequest)) {
throw new Error('Expected argument of type messages.EntityByIdRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_EntityByIdRequest(buffer_arg) {
return gows_pb.EntityByIdRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_EventJson(arg) {
if (!(arg instanceof gows_pb.EventJson)) {
throw new Error('Expected argument of type messages.EventJson');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_EventJson(buffer_arg) {
return gows_pb.EventJson.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GetChatsRequest(arg) {
if (!(arg instanceof gows_pb.GetChatsRequest)) {
throw new Error('Expected argument of type messages.GetChatsRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GetChatsRequest(buffer_arg) {
return gows_pb.GetChatsRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GetContactsRequest(arg) {
if (!(arg instanceof gows_pb.GetContactsRequest)) {
throw new Error('Expected argument of type messages.GetContactsRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GetContactsRequest(buffer_arg) {
return gows_pb.GetContactsRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GetMessagesRequest(arg) {
if (!(arg instanceof gows_pb.GetMessagesRequest)) {
throw new Error('Expected argument of type messages.GetMessagesRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GetMessagesRequest(buffer_arg) {
return gows_pb.GetMessagesRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_GetNewsletterMessagesByInviteRequest(arg) {
if (!(arg instanceof gows_pb.GetNewsletterMessagesByInviteRequest)) {
throw new Error('Expected argument of type messages.GetNewsletterMessagesByInviteRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_GetNewsletterMessagesByInviteRequest(buffer_arg) {
return gows_pb.GetNewsletterMessagesByInviteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Json(arg) {
if (!(arg instanceof gows_pb.Json)) {
throw new Error('Expected argument of type messages.Json');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_Json(buffer_arg) {
return gows_pb.Json.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_JsonList(arg) {
if (!(arg instanceof gows_pb.JsonList)) {
throw new Error('Expected argument of type messages.JsonList');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_JsonList(buffer_arg) {
return gows_pb.JsonList.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_MarkReadRequest(arg) {
if (!(arg instanceof gows_pb.MarkReadRequest)) {
throw new Error('Expected argument of type messages.MarkReadRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_MarkReadRequest(buffer_arg) {
return gows_pb.MarkReadRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_MessageReaction(arg) {
if (!(arg instanceof gows_pb.MessageReaction)) {
throw new Error('Expected argument of type messages.MessageReaction');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_MessageReaction(buffer_arg) {
return gows_pb.MessageReaction.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_MessageRequest(arg) {
if (!(arg instanceof gows_pb.MessageRequest)) {
throw new Error('Expected argument of type messages.MessageRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_MessageRequest(buffer_arg) {
return gows_pb.MessageRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_MessageResponse(arg) {
if (!(arg instanceof gows_pb.MessageResponse)) {
throw new Error('Expected argument of type messages.MessageResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_MessageResponse(buffer_arg) {
return gows_pb.MessageResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Newsletter(arg) {
if (!(arg instanceof gows_pb.Newsletter)) {
throw new Error('Expected argument of type messages.Newsletter');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_Newsletter(buffer_arg) {
return gows_pb.Newsletter.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterInfoRequest(arg) {
if (!(arg instanceof gows_pb.NewsletterInfoRequest)) {
throw new Error('Expected argument of type messages.NewsletterInfoRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterInfoRequest(buffer_arg) {
return gows_pb.NewsletterInfoRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterList(arg) {
if (!(arg instanceof gows_pb.NewsletterList)) {
throw new Error('Expected argument of type messages.NewsletterList');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterList(buffer_arg) {
return gows_pb.NewsletterList.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterListRequest(arg) {
if (!(arg instanceof gows_pb.NewsletterListRequest)) {
throw new Error('Expected argument of type messages.NewsletterListRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterListRequest(buffer_arg) {
return gows_pb.NewsletterListRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterSearchPageResult(arg) {
if (!(arg instanceof gows_pb.NewsletterSearchPageResult)) {
throw new Error('Expected argument of type messages.NewsletterSearchPageResult');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterSearchPageResult(buffer_arg) {
return gows_pb.NewsletterSearchPageResult.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterToggleFollowRequest(arg) {
if (!(arg instanceof gows_pb.NewsletterToggleFollowRequest)) {
throw new Error('Expected argument of type messages.NewsletterToggleFollowRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterToggleFollowRequest(buffer_arg) {
return gows_pb.NewsletterToggleFollowRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_NewsletterToggleMuteRequest(arg) {
if (!(arg instanceof gows_pb.NewsletterToggleMuteRequest)) {
throw new Error('Expected argument of type messages.NewsletterToggleMuteRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_NewsletterToggleMuteRequest(buffer_arg) {
return gows_pb.NewsletterToggleMuteRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_PairCodeRequest(arg) {
if (!(arg instanceof gows_pb.PairCodeRequest)) {
throw new Error('Expected argument of type messages.PairCodeRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_PairCodeRequest(buffer_arg) {
return gows_pb.PairCodeRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_PairCodeResponse(arg) {
if (!(arg instanceof gows_pb.PairCodeResponse)) {
throw new Error('Expected argument of type messages.PairCodeResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_PairCodeResponse(buffer_arg) {
return gows_pb.PairCodeResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_PresenceRequest(arg) {
if (!(arg instanceof gows_pb.PresenceRequest)) {
throw new Error('Expected argument of type messages.PresenceRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_PresenceRequest(buffer_arg) {
return gows_pb.PresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_ProfileNameRequest(arg) {
if (!(arg instanceof gows_pb.ProfileNameRequest)) {
throw new Error('Expected argument of type messages.ProfileNameRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ProfileNameRequest(buffer_arg) {
return gows_pb.ProfileNameRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_ProfilePictureRequest(arg) {
if (!(arg instanceof gows_pb.ProfilePictureRequest)) {
throw new Error('Expected argument of type messages.ProfilePictureRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ProfilePictureRequest(buffer_arg) {
return gows_pb.ProfilePictureRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_ProfilePictureResponse(arg) {
if (!(arg instanceof gows_pb.ProfilePictureResponse)) {
throw new Error('Expected argument of type messages.ProfilePictureResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ProfilePictureResponse(buffer_arg) {
return gows_pb.ProfilePictureResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_ProfileStatusRequest(arg) {
if (!(arg instanceof gows_pb.ProfileStatusRequest)) {
throw new Error('Expected argument of type messages.ProfileStatusRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_ProfileStatusRequest(buffer_arg) {
return gows_pb.ProfileStatusRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SearchNewslettersByTextRequest(arg) {
if (!(arg instanceof gows_pb.SearchNewslettersByTextRequest)) {
throw new Error('Expected argument of type messages.SearchNewslettersByTextRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SearchNewslettersByTextRequest(buffer_arg) {
return gows_pb.SearchNewslettersByTextRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SearchNewslettersByViewRequest(arg) {
if (!(arg instanceof gows_pb.SearchNewslettersByViewRequest)) {
throw new Error('Expected argument of type messages.SearchNewslettersByViewRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SearchNewslettersByViewRequest(buffer_arg) {
return gows_pb.SearchNewslettersByViewRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_Session(arg) {
if (!(arg instanceof gows_pb.Session)) {
throw new Error('Expected argument of type messages.Session');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_Session(buffer_arg) {
return gows_pb.Session.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SessionStateResponse(arg) {
if (!(arg instanceof gows_pb.SessionStateResponse)) {
throw new Error('Expected argument of type messages.SessionStateResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SessionStateResponse(buffer_arg) {
return gows_pb.SessionStateResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SetProfilePictureRequest(arg) {
if (!(arg instanceof gows_pb.SetProfilePictureRequest)) {
throw new Error('Expected argument of type messages.SetProfilePictureRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SetProfilePictureRequest(buffer_arg) {
return gows_pb.SetProfilePictureRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_StartSessionRequest(arg) {
if (!(arg instanceof gows_pb.StartSessionRequest)) {
throw new Error('Expected argument of type messages.StartSessionRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_StartSessionRequest(buffer_arg) {
return gows_pb.StartSessionRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_messages_SubscribePresenceRequest(arg) {
if (!(arg instanceof gows_pb.SubscribePresenceRequest)) {
throw new Error('Expected argument of type messages.SubscribePresenceRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_messages_SubscribePresenceRequest(buffer_arg) {
return gows_pb.SubscribePresenceRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
//
// Events
//
var EventStreamService = exports.EventStreamService = {
streamEvents: {
path: '/messages.EventStream/StreamEvents',
requestStream: false,
responseStream: true,
requestType: gows_pb.Session,
responseType: gows_pb.EventJson,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_EventJson,
responseDeserialize: deserialize_messages_EventJson,
},
};
exports.EventStreamClient = grpc.makeGenericClientConstructor(EventStreamService);
var MessageServiceService = exports.MessageServiceService = {
//
// Session management
//
startSession: {
path: '/messages.MessageService/StartSession',
requestStream: false,
responseStream: false,
requestType: gows_pb.StartSessionRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_StartSessionRequest,
requestDeserialize: deserialize_messages_StartSessionRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
stopSession: {
path: '/messages.MessageService/StopSession',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
getSessionState: {
path: '/messages.MessageService/GetSessionState',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.SessionStateResponse,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_SessionStateResponse,
responseDeserialize: deserialize_messages_SessionStateResponse,
},
requestCode: {
path: '/messages.MessageService/RequestCode',
requestStream: false,
responseStream: false,
requestType: gows_pb.PairCodeRequest,
responseType: gows_pb.PairCodeResponse,
requestSerialize: serialize_messages_PairCodeRequest,
requestDeserialize: deserialize_messages_PairCodeRequest,
responseSerialize: serialize_messages_PairCodeResponse,
responseDeserialize: deserialize_messages_PairCodeResponse,
},
logout: {
path: '/messages.MessageService/Logout',
requestStream: false,
responseStream: false,
requestType: gows_pb.Session,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_Session,
requestDeserialize: deserialize_messages_Session,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
//
// Profile
//
setProfileName: {
path: '/messages.MessageService/SetProfileName',
requestStream: false,
responseStream: false,
requestType: gows_pb.ProfileNameRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_ProfileNameRequest,
requestDeserialize: deserialize_messages_ProfileNameRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setProfileStatus: {
path: '/messages.MessageService/SetProfileStatus',
requestStream: false,
responseStream: false,
requestType: gows_pb.ProfileStatusRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_ProfileStatusRequest,
requestDeserialize: deserialize_messages_ProfileStatusRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
setProfilePicture: {
path: '/messages.MessageService/SetProfilePicture',
requestStream: false,
responseStream: false,
requestType: gows_pb.SetProfilePictureRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_SetProfilePictureRequest,
requestDeserialize: deserialize_messages_SetProfilePictureRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
//
// Actions
//
sendMessage: {
path: '/messages.MessageService/SendMessage',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageRequest,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageRequest,
requestDeserialize: deserialize_messages_MessageRequest,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
sendReaction: {
path: '/messages.MessageService/SendReaction',
requestStream: false,
responseStream: false,
requestType: gows_pb.MessageReaction,
responseType: gows_pb.MessageResponse,
requestSerialize: serialize_messages_MessageReaction,
requestDeserialize: deserialize_messages_MessageReaction,
responseSerialize: serialize_messages_MessageResponse,
responseDeserialize: deserialize_messages_MessageResponse,
},
getProfilePicture: {
path: '/messages.MessageService/GetProfilePicture',
requestStream: false,
responseStream: false,
requestType: gows_pb.ProfilePictureRequest,
responseType: gows_pb.ProfilePictureResponse,
requestSerialize: serialize_messages_ProfilePictureRequest,
requestDeserialize: deserialize_messages_ProfilePictureRequest,
responseSerialize: serialize_messages_ProfilePictureResponse,
responseDeserialize: deserialize_messages_ProfilePictureResponse,
},
sendPresence: {
path: '/messages.MessageService/SendPresence',
requestStream: false,
responseStream: false,
requestType: gows_pb.PresenceRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_PresenceRequest,
requestDeserialize: deserialize_messages_PresenceRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
sendChatPresence: {
path: '/messages.MessageService/SendChatPresence',
requestStream: false,
responseStream: false,
requestType: gows_pb.ChatPresenceRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_ChatPresenceRequest,
requestDeserialize: deserialize_messages_ChatPresenceRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
subscribePresence: {
path: '/messages.MessageService/SubscribePresence',
requestStream: false,
responseStream: false,
requestType: gows_pb.SubscribePresenceRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_SubscribePresenceRequest,
requestDeserialize: deserialize_messages_SubscribePresenceRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
markRead: {
path: '/messages.MessageService/MarkRead',
requestStream: false,
responseStream: false,
requestType: gows_pb.MarkReadRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_MarkReadRequest,
requestDeserialize: deserialize_messages_MarkReadRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
checkPhones: {
path: '/messages.MessageService/CheckPhones',
requestStream: false,
responseStream: false,
requestType: gows_pb.CheckPhonesRequest,
responseType: gows_pb.CheckPhonesResponse,
requestSerialize: serialize_messages_CheckPhonesRequest,
requestDeserialize: deserialize_messages_CheckPhonesRequest,
responseSerialize: serialize_messages_CheckPhonesResponse,
responseDeserialize: deserialize_messages_CheckPhonesResponse,
},
//
// Newsletters
//
getSubscribedNewsletters: {
path: '/messages.MessageService/GetSubscribedNewsletters',
requestStream: false,
responseStream: false,
requestType: gows_pb.NewsletterListRequest,
responseType: gows_pb.NewsletterList,
requestSerialize: serialize_messages_NewsletterListRequest,
requestDeserialize: deserialize_messages_NewsletterListRequest,
responseSerialize: serialize_messages_NewsletterList,
responseDeserialize: deserialize_messages_NewsletterList,
},
getNewsletterInfo: {
path: '/messages.MessageService/GetNewsletterInfo',
requestStream: false,
responseStream: false,
requestType: gows_pb.NewsletterInfoRequest,
responseType: gows_pb.Newsletter,
requestSerialize: serialize_messages_NewsletterInfoRequest,
requestDeserialize: deserialize_messages_NewsletterInfoRequest,
responseSerialize: serialize_messages_Newsletter,
responseDeserialize: deserialize_messages_Newsletter,
},
getNewsletterMessagesByInvite: {
path: '/messages.MessageService/GetNewsletterMessagesByInvite',
requestStream: false,
responseStream: false,
requestType: gows_pb.GetNewsletterMessagesByInviteRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_GetNewsletterMessagesByInviteRequest,
requestDeserialize: deserialize_messages_GetNewsletterMessagesByInviteRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
searchNewslettersByView: {
path: '/messages.MessageService/SearchNewslettersByView',
requestStream: false,
responseStream: false,
requestType: gows_pb.SearchNewslettersByViewRequest,
responseType: gows_pb.NewsletterSearchPageResult,
requestSerialize: serialize_messages_SearchNewslettersByViewRequest,
requestDeserialize: deserialize_messages_SearchNewslettersByViewRequest,
responseSerialize: serialize_messages_NewsletterSearchPageResult,
responseDeserialize: deserialize_messages_NewsletterSearchPageResult,
},
searchNewslettersByText: {
path: '/messages.MessageService/SearchNewslettersByText',
requestStream: false,
responseStream: false,
requestType: gows_pb.SearchNewslettersByTextRequest,
responseType: gows_pb.NewsletterSearchPageResult,
requestSerialize: serialize_messages_SearchNewslettersByTextRequest,
requestDeserialize: deserialize_messages_SearchNewslettersByTextRequest,
responseSerialize: serialize_messages_NewsletterSearchPageResult,
responseDeserialize: deserialize_messages_NewsletterSearchPageResult,
},
createNewsletter: {
path: '/messages.MessageService/CreateNewsletter',
requestStream: false,
responseStream: false,
requestType: gows_pb.CreateNewsletterRequest,
responseType: gows_pb.Newsletter,
requestSerialize: serialize_messages_CreateNewsletterRequest,
requestDeserialize: deserialize_messages_CreateNewsletterRequest,
responseSerialize: serialize_messages_Newsletter,
responseDeserialize: deserialize_messages_Newsletter,
},
newsletterToggleMute: {
path: '/messages.MessageService/NewsletterToggleMute',
requestStream: false,
responseStream: false,
requestType: gows_pb.NewsletterToggleMuteRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_NewsletterToggleMuteRequest,
requestDeserialize: deserialize_messages_NewsletterToggleMuteRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
newsletterToggleFollow: {
path: '/messages.MessageService/NewsletterToggleFollow',
requestStream: false,
responseStream: false,
requestType: gows_pb.NewsletterToggleFollowRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_NewsletterToggleFollowRequest,
requestDeserialize: deserialize_messages_NewsletterToggleFollowRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
//
// Media
//
downloadMedia: {
path: '/messages.MessageService/DownloadMedia',
requestStream: false,
responseStream: false,
requestType: gows_pb.DownloadMediaRequest,
responseType: gows_pb.DownloadMediaResponse,
requestSerialize: serialize_messages_DownloadMediaRequest,
requestDeserialize: deserialize_messages_DownloadMediaRequest,
responseSerialize: serialize_messages_DownloadMediaResponse,
responseDeserialize: deserialize_messages_DownloadMediaResponse,
},
//
// Storage
//
getMessageById: {
path: '/messages.MessageService/GetMessageById',
requestStream: false,
responseStream: false,
requestType: gows_pb.EntityByIdRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_EntityByIdRequest,
requestDeserialize: deserialize_messages_EntityByIdRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
getMessages: {
path: '/messages.MessageService/GetMessages',
requestStream: false,
responseStream: false,
requestType: gows_pb.GetMessagesRequest,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_GetMessagesRequest,
requestDeserialize: deserialize_messages_GetMessagesRequest,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
getContacts: {
path: '/messages.MessageService/GetContacts',
requestStream: false,
responseStream: false,
requestType: gows_pb.GetContactsRequest,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_GetContactsRequest,
requestDeserialize: deserialize_messages_GetContactsRequest,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
getContactById: {
path: '/messages.MessageService/GetContactById',
requestStream: false,
responseStream: false,
requestType: gows_pb.EntityByIdRequest,
responseType: gows_pb.Json,
requestSerialize: serialize_messages_EntityByIdRequest,
requestDeserialize: deserialize_messages_EntityByIdRequest,
responseSerialize: serialize_messages_Json,
responseDeserialize: deserialize_messages_Json,
},
getChats: {
path: '/messages.MessageService/GetChats',
requestStream: false,
responseStream: false,
requestType: gows_pb.GetChatsRequest,
responseType: gows_pb.JsonList,
requestSerialize: serialize_messages_GetChatsRequest,
requestDeserialize: deserialize_messages_GetChatsRequest,
responseSerialize: serialize_messages_JsonList,
responseDeserialize: deserialize_messages_JsonList,
},
};
exports.MessageServiceClient = grpc.makeGenericClientConstructor(MessageServiceService);
File diff suppressed because it is too large. Load diff
+42
View File
@@ -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;
}
+327 -21
View File
@@ -21,17 +21,19 @@ import {
toCusFormat,
toJID,
} from '@waha/core/engines/noweb/session.noweb.core';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from '@waha/core/exceptions';
import { AvailableInPlusVersion } from '@waha/core/exceptions';
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 +83,35 @@ import { promisify } from 'util';
import * as gows from './types';
import MessageServiceClient = messages.MessageServiceClient;
import {
ToGroupV2JoinEvent,
ToGroupV2LeaveEvent,
ToGroupV2ParticipantsEvents,
ToGroupV2UpdateEvent,
} from '@waha/core/engines/gows/groups.gows';
import {
optional,
parseJson,
parseJsonList,
statusToAck,
} from '@waha/core/engines/gows/helpers';
import { extractMediaContent } from '@waha/core/engines/noweb/utils';
import {
ChatSortField,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import { ContactQuery } from '@waha/structures/contacts.dto';
import { BinaryFile, RemoteFile } from '@waha/structures/files.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
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 +119,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 +188,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
this.client = new MessageServiceClient(
this.engineConfig.connection,
grpc.credentials.createInsecure(),
gRPCClientConfig,
);
try {
@@ -183,6 +218,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 +251,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 +360,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 +479,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({
@@ -628,7 +731,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return {
participant: toCusFormat(data.From),
lastKnownPresence: lastKnownPresence,
lastSeen: parseTimestamp(data.LastSeen),
lastSeen: parseTimestampToSeconds(data.LastSeen),
};
}
@@ -668,6 +771,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 +810,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
preview: preview,
verified: newsletter.verified,
role: role,
subscribersCount: newsletter.subscriberCount,
};
}
@@ -775,11 +898,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 +1111,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 +1249,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 +1298,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 +1315,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 +1361,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);
}
+108
View File
@@ -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;
}
+17
View File
@@ -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,
}
+143
View File
@@ -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,
};
}
+104 -15
View File
@@ -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,
@@ -149,6 +160,7 @@ import {
import {
ensureSuffix,
getChannelInviteLink,
getPublicUrlFromDirectPath,
isNewsletter,
WhatsappSession,
} from '../../abc/session.abc';
@@ -642,6 +654,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
*/
@@ -1104,10 +1137,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)) {
@@ -1316,6 +1345,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 +1371,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,
};
}
@@ -1469,12 +1518,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 +1724,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;
@@ -1670,6 +1758,7 @@ 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),
@@ -1679,8 +1768,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
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,
+1 -1
View File
@@ -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 (
+10
View File
@@ -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',
);
};
+154
View File
@@ -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,
};
}
+141 -20
View File
@@ -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(
@@ -479,7 +516,11 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
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 +540,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 +557,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 +709,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();
}
@@ -922,6 +973,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 +1008,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
picture: null,
verified: metadata.verified,
role: role,
subscribersCount: null,
};
}
@@ -955,6 +1026,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
picture: metadata.pictureUrl,
verified: metadata.isVerified,
role: role,
subscribersCount: metadata.subscribersCount,
};
}
@@ -966,6 +1038,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 +1240,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 +1329,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 +1371,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 +1388,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();
}
}
+7
View File
@@ -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
View File
@@ -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');
});
-8
View File
@@ -35,14 +35,6 @@ export class RequestCodeRequest {
localeCountry: string;
}
export class OTPRequest {
code: string;
}
export class CaptchaBody {
code: string;
}
export class PairingCodeResponse {
code: string;
}
+4
View File
@@ -18,3 +18,7 @@ export class SessionQuery {
export class SessionBaseRequest {
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
export class Result {
success: boolean = true;
}
+102 -1
View File
@@ -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;
}
+4
View File
@@ -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',
+66
View File
@@ -88,3 +88,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;
}
+71
View File
@@ -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;
}
+45
View File
@@ -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;
}
+45
View File
@@ -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;
}
+23 -45
View File
@@ -148,14 +148,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 +165,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 +174,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 +184,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 +195,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 +234,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 +243,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 +254,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 +264,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 +274,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 +284,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 +294,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 +303,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 +312,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 +321,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 +335,7 @@ export class EnginePayload {
data: any;
}
class WAHAWebhookEngineEvent extends WAHAWebhook {
export class WAHAWebhookEngineEvent extends WAHAWebhook {
@ApiProperty({
description: 'Internal engine event.',
})
@@ -340,28 +343,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 };
+58
View File
@@ -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
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2025.1.6',
version: '2025.2.4',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
+2 -2
View File
@@ -2,11 +2,11 @@
"waha": {
"gows": {
"repo": "devlikeapro/gows",
"ref": "32baf0cc2e8c35388b0b6d4a1a446cca0f3525a8"
"ref": "8b31d6b88166e0962ca47a3dfbd256a42f09dbc1"
},
"dashboard": {
"repo": "devlikeapro/dashboard",
"ref": "a1c839d8da94d1cade182addffdfc42fc0cf7ed3"
"ref": "d6cd941f8d7621f401305e03ad2c1db8dc0a64f0"
}
}
}
+196 -6
View File
@@ -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