Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85e2498fd | ||
|
|
507382b6d2 | ||
|
|
f343cf8549 | ||
|
|
832d5baf35 | ||
|
|
0253f32574 | ||
|
|
6173180272 | ||
|
|
2d21931fb1 | ||
|
|
bdaa7187fd | ||
|
|
e7ad6e06ec | ||
|
|
7c14483852 | ||
|
|
8b4b1f934e | ||
|
|
713eb3f169 | ||
|
|
48297c80d1 | ||
|
|
315ffdac92 | ||
|
|
7ddc17e6e5 | ||
|
|
e3b8073c82 | ||
|
|
80517e416c | ||
|
|
4b71aade9d | ||
|
|
1b52698c54 | ||
|
|
b8f372d83f | ||
|
|
7bc5f55fb2 | ||
|
|
7eeafd03b3 | ||
|
|
f88480d271 | ||
|
|
9b8cce85bd | ||
|
|
aa364b57e8 | ||
|
|
ea973d0de4 | ||
|
|
1a1b64cbb2 | ||
|
|
09c7e11dbc | ||
|
|
3450a56756 | ||
|
|
f37f4466ae | ||
|
|
451834b219 | ||
|
|
91924353e2 | ||
|
|
67cf94fc95 | ||
|
|
36a343c09f | ||
|
|
4a37aebbf1 | ||
|
|
d5fa9f8d72 | ||
|
|
08c244334b | ||
|
|
23dd1c2726 | ||
|
|
23a309d394 | ||
|
|
3c1bb4e145 | ||
|
|
87b320a3e3 | ||
|
|
e4fb9615c7 | ||
|
|
efad4de0af | ||
|
|
04f62700a2 | ||
|
|
08415f7931 | ||
|
|
8bc86af3c8 | ||
|
|
fa2c5e42dc | ||
|
|
e3d3952b81 |
No files matched your search
@@ -1,3 +1,4 @@
|
||||
*.heapsnapshot
|
||||
.yarn
|
||||
example.ts
|
||||
.*sessions
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Build
|
||||
#
|
||||
ARG NODE_VERSION=20.12.2-bullseye
|
||||
ARG NODE_VERSION=22.8-bullseye
|
||||
FROM node:${NODE_VERSION} as build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
@@ -9,6 +9,8 @@ ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
WORKDIR /src
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
ENV YARN_CHECKSUM_BEHAVIOR=update
|
||||
RUN npm install -g corepack && corepack enable
|
||||
RUN yarn set version 3.6.3
|
||||
RUN yarn install
|
||||
|
||||
@@ -24,7 +26,7 @@ RUN yarn build && find ./dist -name "*.d.ts" -delete
|
||||
FROM node:${NODE_VERSION} as dashboard
|
||||
|
||||
# Download WAHA Dashboard
|
||||
ENV WAHA_DASHBOARD_SHA c1a91f879a35f0ae32d46ddb2e061ac1819a3d4f
|
||||
ENV WAHA_DASHBOARD_SHA 9afbd7b68618ea67be01bdefa978cf98a6336611
|
||||
RUN \
|
||||
wget https://github.com/devlikeapro/dashboard/archive/${WAHA_DASHBOARD_SHA}.zip \
|
||||
&& unzip ${WAHA_DASHBOARD_SHA}.zip -d /tmp/dashboard \
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
services:
|
||||
waha:
|
||||
restart: always
|
||||
container_name: waha
|
||||
# image: devlikeapro/waha:latest
|
||||
image: devlikeapro/waha-plus:latest
|
||||
ports:
|
||||
- '3000:3000/tcp'
|
||||
volumes:
|
||||
- './.sessions:/app/.sessions'
|
||||
- './.media:/app/.media'
|
||||
environment:
|
||||
- WHATSAPP_API_KEY=321
|
||||
- WAHA_DASHBOARD_USERNAME=waha
|
||||
- WAHA_DASHBOARD_PASSWORD=waha
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
- WAHA_PRINT_QR=False
|
||||
- WAHA_MEDIA_STORAGE=LOCAL
|
||||
- WHATSAPP_FILES_LIFETIME=0
|
||||
- WHATSAPP_FILES_FOLDER=/app/.media
|
||||
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
container_name: n8n
|
||||
restart: always
|
||||
environment:
|
||||
- WEBHOOK_URL=http://n8n:5678
|
||||
ports:
|
||||
- '5678:5678'
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
|
||||
volumes:
|
||||
n8n_data: {}
|
||||
+2
-2
@@ -85,10 +85,10 @@ services:
|
||||
# Logging
|
||||
# https://waha.devlike.pro/docs/how-to/observability/#logging
|
||||
#
|
||||
- WAHA_LOG_FORMAT=PRETTY
|
||||
# Set log format to JSON if you consume logs with log management systems
|
||||
#- WAHA_LOG_FORMAT=JSON
|
||||
- WAHA_LOG_FORMAT=JSON
|
||||
- WAHA_LOG_LEVEL=info
|
||||
# - WAHA_LOG_FORMAT=PRETTY
|
||||
# If you want to see more logs, you can set the log level to debug
|
||||
#- WAHA_LOG_LEVEL=debug
|
||||
# DEBUG=1 # shortcut for setting log level to debug
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ uv_threadpool_size=$(($cpus * 2))
|
||||
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
|
||||
|
||||
# Start your application using yarn
|
||||
yarn start:prod
|
||||
node dist/main
|
||||
+1
-2
@@ -43,7 +43,7 @@
|
||||
"@types/ws": "^8.5.4",
|
||||
"adm-zip": "0.5.10",
|
||||
"async-lock": "^1.4.1",
|
||||
"better-sqlite3": "9.6.0",
|
||||
"better-sqlite3": "11.3.0",
|
||||
"check-disk-space": "^3.4.0",
|
||||
"chokidar": "^3.6.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -77,7 +77,6 @@
|
||||
"rxjs": "^7.1.0",
|
||||
"sharp": "^0.33.4",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"venom-bot": "5.0.1",
|
||||
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-webpack-exodus-channels"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ docker run -d \
|
||||
--env WHATSAPP_API_KEY=321 \
|
||||
--env WAHA_DASHBOARD_USERNAME=waha \
|
||||
--env WAHA_DASHBOARD_PASSWORD=waha \
|
||||
--env WAHA_LOG_FORMAT=PRETTY \
|
||||
--env WAHA_LOG_FORMAT=JSON \
|
||||
--env WAHA_LOG_LEVEL=info \
|
||||
--env WHATSAPP_DEFAULT_ENGINE=WEBJS \
|
||||
--env WHATSAPP_RESTART_ALL_SESSIONS=True \
|
||||
|
||||
@@ -36,7 +36,7 @@ class AuthController {
|
||||
|
||||
@Get('qr')
|
||||
@ApiOperation({
|
||||
summary: 'Get QR code for pairing WhatsApp Web.',
|
||||
summary: 'Get QR code for pairing WhatsApp API.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@ApiFileAcceptHeader(Base64File, QRCodeValue)
|
||||
|
||||
@@ -61,7 +61,10 @@ export class ChannelsController {
|
||||
@SessionApiParam
|
||||
@NewsletterIdOrInviteCodeApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Get the channel info - either by id @newsletter OR invite code.',
|
||||
summary: 'Get the channel info',
|
||||
description:
|
||||
'You can use either id (123@newsletter) ' +
|
||||
'OR invite code (https://www.whatsapp.com/channel/123)',
|
||||
})
|
||||
get(
|
||||
@SessionParam session: WhatsappSession,
|
||||
|
||||
@@ -44,6 +44,7 @@ class ChatsController {
|
||||
@Delete(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Deletes the chat' })
|
||||
@ChatIdApiParam
|
||||
deleteChat(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
@@ -54,6 +55,7 @@ class ChatsController {
|
||||
@Get(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets messages in the chat' })
|
||||
@ChatIdApiParam
|
||||
getChatMessages(
|
||||
@Query() query: GetChatMessagesQuery,
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -66,6 +68,7 @@ class ChatsController {
|
||||
@Delete(':chatId/messages')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Clears all messages from the chat' })
|
||||
@ChatIdApiParam
|
||||
clearMessages(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
|
||||
@@ -30,21 +30,6 @@ import { WAMessage } from '../structures/responses.dto';
|
||||
export class ChattingController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({ summary: 'Check number status' })
|
||||
async checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Post('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
@@ -52,31 +37,11 @@ export class ChattingController {
|
||||
return whatsapp.sendText(request);
|
||||
}
|
||||
|
||||
@Post('/sendPoll')
|
||||
@ApiOperation({
|
||||
summary: 'Send a poll with options. You can use it as buttons replace',
|
||||
})
|
||||
sendPoll(@Body() request: MessagePollRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendPoll(request);
|
||||
}
|
||||
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
summary: 'Send an image',
|
||||
description:
|
||||
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
@@ -85,8 +50,9 @@ export class ChattingController {
|
||||
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
summary: 'Send a file',
|
||||
description:
|
||||
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
@@ -95,8 +61,9 @@ export class ChattingController {
|
||||
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
summary: 'Send an voice message',
|
||||
description:
|
||||
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
@@ -105,8 +72,9 @@ export class ChattingController {
|
||||
|
||||
@Post('/sendVideo')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a video. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
summary: 'Send a video',
|
||||
description:
|
||||
'Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVideo(@Body() request: MessageVideoRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
@@ -149,6 +117,28 @@ export class ChattingController {
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('/sendPoll')
|
||||
@ApiOperation({
|
||||
summary: 'Send a poll with options',
|
||||
description: 'You can use it as buttons or list replacement',
|
||||
})
|
||||
sendPoll(@Body() request: MessagePollRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendPoll(request);
|
||||
}
|
||||
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Get('/messages')
|
||||
@ApiOperation({ summary: 'Get messages in a chat' })
|
||||
getMessages(@Query() query: GetMessageQuery) {
|
||||
@@ -166,6 +156,25 @@ export class ChattingController {
|
||||
return whatsapp.sendText(msg);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({
|
||||
summary: 'Check number status',
|
||||
description: 'DEPRECATED. Use "POST /contacts/check-exists" instead',
|
||||
deprecated: true,
|
||||
})
|
||||
async DEPRECATED_checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Post('/reply')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
|
||||
@@ -17,8 +17,9 @@ export class ContactsController {
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
|
||||
summary: 'Get contact basic info',
|
||||
description:
|
||||
'The method always return result, even if the phone number is not registered in WhatsApp. For that - use /contacts/check-exists endpoint below.',
|
||||
})
|
||||
get(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
@@ -43,8 +44,9 @@ export class ContactsController {
|
||||
|
||||
@Get('/about')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
|
||||
summary: 'Gets the Contact\'s "about" info',
|
||||
description:
|
||||
'Returns null if you do not have permission to read their status.',
|
||||
})
|
||||
getAbout(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
@@ -53,8 +55,9 @@ export class ContactsController {
|
||||
|
||||
@Get('/profile-picture')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Returns the contact's profile picture URL, if privacy settings allow it.",
|
||||
summary: "Get contact's profile picture URL",
|
||||
description:
|
||||
'If privacy settings do not allow to get the picture, the method will return null.',
|
||||
})
|
||||
getProfilePicture(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { GroupIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||
import {
|
||||
SessionApiParam,
|
||||
SessionParam,
|
||||
@@ -47,6 +48,7 @@ export class GroupsController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@GroupIdApiParam
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get the group.' })
|
||||
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
@@ -55,9 +57,11 @@ export class GroupsController {
|
||||
|
||||
@Put(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
summary: 'Updates the group "info admin only" settings.',
|
||||
description:
|
||||
'You can allow only admins to edit group info (title, description, photo).',
|
||||
})
|
||||
setInfoAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -69,9 +73,11 @@ export class GroupsController {
|
||||
|
||||
@Get(':id/settings/security/info-admin-only')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the group settings to only allow admins to edit group info (title, description, photo).',
|
||||
summary: "Get the group's 'info admin only' settings.",
|
||||
description:
|
||||
'You can allow only admins to edit group info (title, description, photo).',
|
||||
})
|
||||
getInfoAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -82,8 +88,10 @@ export class GroupsController {
|
||||
|
||||
@Put(':id/settings/security/messages-admin-only')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
summary: 'Update settings - who can send messages',
|
||||
description:
|
||||
'Updates the group settings to only allow admins to send messages.',
|
||||
})
|
||||
setMessagesAdminOnly(
|
||||
@@ -96,8 +104,10 @@ export class GroupsController {
|
||||
|
||||
@Get(':id/settings/security/messages-admin-only')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Gets the group settings to only allow admins to send messages.',
|
||||
summary: 'Get settings - who can send messages',
|
||||
description: 'The group settings to only allow admins to send messages.',
|
||||
})
|
||||
getMessagesAdminOnly(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -108,6 +118,7 @@ export class GroupsController {
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.deleteGroup(id);
|
||||
@@ -115,6 +126,7 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/leave')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.leaveGroup(id);
|
||||
@@ -122,11 +134,12 @@ export class GroupsController {
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group description.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
summary: 'Updates the group description.',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
setDescription(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@@ -137,10 +150,11 @@ export class GroupsController {
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group subject.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
summary: 'Updates the group subject',
|
||||
description:
|
||||
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
|
||||
})
|
||||
setSubject(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -152,7 +166,8 @@ export class GroupsController {
|
||||
|
||||
@Get(':id/invite-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Gets the invite code for the group.' })
|
||||
getInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@@ -162,6 +177,7 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Invalidates the current group invite code and generates a new one.',
|
||||
@@ -175,7 +191,8 @@ export class GroupsController {
|
||||
|
||||
@Get(':id/participants/')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Get participants' })
|
||||
getParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@@ -185,7 +202,8 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Add participants' })
|
||||
addParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@@ -196,8 +214,9 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Removes a list of participants by ID to the group.',
|
||||
summary: 'Remove participants',
|
||||
})
|
||||
removeParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -209,6 +228,7 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@SessionApiParam
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
promoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -220,7 +240,8 @@ export class GroupsController {
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
|
||||
@GroupIdApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants to regular users.' })
|
||||
demoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
|
||||
@@ -13,7 +13,9 @@ export class HealthController {
|
||||
@Get()
|
||||
@HealthCheck()
|
||||
@ApiOperation({
|
||||
summary: 'Check the health of the server, performing all health checks.',
|
||||
summary: 'Check the health of the server',
|
||||
description:
|
||||
"Perform all health checks and return the server's health status.",
|
||||
})
|
||||
async check() {
|
||||
return this.wahaHealth.check();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { WhatsappSession } from '@waha/core/abc/session.abc';
|
||||
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||
import {
|
||||
SessionApiParam,
|
||||
SessionParam,
|
||||
} from '@waha/nestjs/params/SessionApiParam';
|
||||
import { Label, LabelID, SetLabelsRequest } from '@waha/structures/labels.dto';
|
||||
import { Label, SetLabelsRequest } from '@waha/structures/labels.dto';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
|
||||
@@ -24,6 +25,7 @@ export class LabelsController {
|
||||
|
||||
@Get('/chats/:chatId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@ApiOperation({ summary: 'Get labels for the chat' })
|
||||
getChatLabels(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@@ -34,6 +36,7 @@ export class LabelsController {
|
||||
|
||||
@Put('/chats/:chatId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@ApiOperation({ summary: 'Save labels for the chat' })
|
||||
putChatLabels(
|
||||
@SessionParam session: WhatsappSession,
|
||||
|
||||
@@ -7,7 +7,10 @@ import { PingResponse } from '@waha/structures/ping.dto';
|
||||
@ApiTags('🔍 Observability')
|
||||
export class PingController {
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Ping the server, perform no operations.' })
|
||||
@ApiOperation({
|
||||
summary: 'Ping the server',
|
||||
description: 'Check if the server is alive and responding to requests.',
|
||||
})
|
||||
ping(): PingResponse {
|
||||
return { message: 'pong' };
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
|
||||
import {
|
||||
SessionApiParam,
|
||||
SessionParam,
|
||||
@@ -67,6 +68,7 @@ export class PresenceController {
|
||||
|
||||
@Get(':chatId')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
|
||||
@@ -80,6 +82,7 @@ export class PresenceController {
|
||||
|
||||
@Post(':chatId/subscribe')
|
||||
@SessionApiParam
|
||||
@ChatIdApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Subscribe to presence events for the chat.',
|
||||
})
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import * as process from 'node:process';
|
||||
import * as v8 from 'node:v8';
|
||||
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
Post,
|
||||
Query,
|
||||
Res,
|
||||
StreamableFile,
|
||||
UseInterceptors,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { WhatsappConfigService } from '@waha/config.service';
|
||||
import { getApp } from '@waha/main';
|
||||
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
|
||||
import { WAHAEnvironment } from '@waha/structures/environment.dto';
|
||||
@@ -38,7 +44,7 @@ export class ServerController {
|
||||
}
|
||||
|
||||
@Get('environment')
|
||||
@ApiOperation({ summary: 'Return the server environment' })
|
||||
@ApiOperation({ summary: 'Get the server environment' })
|
||||
environment(
|
||||
@Query(new WAHAValidationPipe()) query: EnvironmentQuery,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
@@ -64,7 +70,7 @@ export class ServerController {
|
||||
}
|
||||
|
||||
@Get('status')
|
||||
@ApiOperation({ summary: 'The server status' })
|
||||
@ApiOperation({ summary: 'Get the server status' })
|
||||
async status(): Promise<ServerStatusResponse> {
|
||||
const now = Date.now();
|
||||
const uptime = Math.floor(process.uptime() * 1000);
|
||||
@@ -105,3 +111,34 @@ export class ServerController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/server/debug')
|
||||
@ApiTags('🔍 Observability')
|
||||
export class ServerDebugController {
|
||||
private logger: Logger;
|
||||
private readonly enabled: boolean;
|
||||
|
||||
constructor(protected config: WhatsappConfigService) {
|
||||
this.logger = new Logger('ServerDebugController');
|
||||
this.enabled = this.config.debugModeEnabled;
|
||||
}
|
||||
|
||||
@Get('heapsnapshot')
|
||||
@ApiOperation({
|
||||
summary: 'Return a heapsnapshot',
|
||||
description: "Return a heapsnapshot of the server's memory",
|
||||
})
|
||||
async heapsnapshot() {
|
||||
if (!this.enabled) {
|
||||
throw new NotFoundException('WAHA_DEBUG_MODE is disabled');
|
||||
}
|
||||
this.logger.log('Creating a heap snapshot...');
|
||||
const heap = v8.getHeapSnapshot();
|
||||
const fileName = `${Date.now()}.heapsnapshot`;
|
||||
return new StreamableFile(heap, {
|
||||
type: 'application/octet-stream',
|
||||
disposition: `attachment; filename=${fileName}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -249,9 +249,7 @@ class SessionsController {
|
||||
|
||||
return await this.withLock(name, async () => {
|
||||
const config = request.config;
|
||||
if (config) {
|
||||
await this.manager.upsert(name, config);
|
||||
}
|
||||
await this.manager.upsert(name, config);
|
||||
return await this.manager.start(name);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
SessionApiParam,
|
||||
@@ -31,7 +23,7 @@ class StatusController {
|
||||
|
||||
@Post('text')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send text status.' })
|
||||
@ApiOperation({ summary: 'Send text status' })
|
||||
sendTextStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: TextStatus,
|
||||
@@ -41,7 +33,7 @@ class StatusController {
|
||||
|
||||
@Post('image')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send image status.' })
|
||||
@ApiOperation({ summary: 'Send image status' })
|
||||
sendImageStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: ImageStatus,
|
||||
@@ -51,7 +43,7 @@ class StatusController {
|
||||
|
||||
@Post('voice')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send voice status.' })
|
||||
@ApiOperation({ summary: 'Send voice status' })
|
||||
sendVoiceStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VoiceStatus,
|
||||
@@ -61,7 +53,7 @@ class StatusController {
|
||||
|
||||
@Post('video')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Send video status.' })
|
||||
@ApiOperation({ summary: 'Send video status' })
|
||||
sendVideoStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: VideoStatus,
|
||||
@@ -71,7 +63,7 @@ class StatusController {
|
||||
|
||||
@Post('delete')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'DELETE sent status.' })
|
||||
@ApiOperation({ summary: 'DELETE sent status' })
|
||||
deleteStatus(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() status: DeleteStatusRequest,
|
||||
|
||||
@@ -14,7 +14,11 @@ import { VERSION } from '../version';
|
||||
@ApiTags('🔍 Observability')
|
||||
export class VersionController {
|
||||
@Get('')
|
||||
@ApiOperation({ summary: 'Get the version of the server', deprecated: true })
|
||||
@ApiOperation({
|
||||
summary: 'Get the server version ',
|
||||
deprecated: true,
|
||||
description: "Use 'GET /api/server/version' instead ",
|
||||
})
|
||||
get(): WAHAEnvironment {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@@ -136,4 +136,9 @@ export class WhatsappConfigService {
|
||||
3000,
|
||||
);
|
||||
}
|
||||
|
||||
get debugModeEnabled(): boolean {
|
||||
const value = this.configService.get('WAHA_DEBUG_MODE', 'false');
|
||||
return parseBool(value);
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,7 @@ export abstract class WhatsappSession {
|
||||
'--disable-offline-load-stale-cache',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-site-isolation-trials', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
'--disable-speech-api',
|
||||
'--disable-sync',
|
||||
'--disable-translate',
|
||||
@@ -178,6 +179,7 @@ export abstract class WhatsappSession {
|
||||
'--hide-scrollbars',
|
||||
'--ignore-certificate-errors',
|
||||
'--ignore-ssl-errors',
|
||||
'--in-process-gpu', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
'--metrics-recording-only',
|
||||
'--mute-audio',
|
||||
'--no-default-browser-check',
|
||||
@@ -186,6 +188,7 @@ export abstract class WhatsappSession {
|
||||
'--no-sandbox',
|
||||
'--no-zygote',
|
||||
'--password-store=basic',
|
||||
'--renderer-process-limit=2', // https://superuser.com/questions/654565/how-to-run-google-chrome-in-a-single-process
|
||||
'--safebrowsing-disable-auto-update',
|
||||
'--single-process',
|
||||
'--use-mock-keychain',
|
||||
|
||||
@@ -5,7 +5,10 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { ChannelsController } from '@waha/api/channels.controller';
|
||||
import { ServerController } from '@waha/api/server.controller';
|
||||
import {
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
} from '@waha/api/server.controller';
|
||||
import { WebsocketGatewayCore } from '@waha/core/api/websocket.gateway.core';
|
||||
import { MediaLocalStorageConfig } from '@waha/core/media/local/MediaLocalStorageConfig';
|
||||
import { BufferJsonReplacerInterceptor } from '@waha/nestjs/BufferJsonReplacerInterceptor';
|
||||
@@ -112,6 +115,7 @@ export const CONTROLLERS = [
|
||||
PingController,
|
||||
HealthController,
|
||||
ServerController,
|
||||
ServerDebugController,
|
||||
VersionController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
|
||||
@@ -284,6 +284,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async buildClient() {
|
||||
// @ts-ignore
|
||||
this.sock?.ev?.removeAllListeners();
|
||||
this.sock = await this.makeSocket();
|
||||
this.issueMessageUpdateOnEdits();
|
||||
this.issuePresenceUpdateOnMessageUpsert();
|
||||
@@ -645,11 +647,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('composing', request.chatId);
|
||||
const chatId = toJID(this.ensureSuffix(request.chatId));
|
||||
return this.sock.sendPresenceUpdate('composing', chatId);
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('paused', request.chatId);
|
||||
const chatId = toJID(this.ensureSuffix(request.chatId));
|
||||
return this.sock.sendPresenceUpdate('paused', chatId);
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
@@ -887,6 +891,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
`NOWEB engine doesn't support '${presence}' presence.`,
|
||||
);
|
||||
}
|
||||
if (chatId) {
|
||||
chatId = toJID(this.ensureSuffix(chatId));
|
||||
}
|
||||
await this.sock.sendPresenceUpdate(enginePresence, chatId);
|
||||
}
|
||||
|
||||
@@ -1312,7 +1319,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
const body = this.extractBody(quotedMessage);
|
||||
return {
|
||||
id: contextInfo.stanzaId,
|
||||
participant: contextInfo.participant,
|
||||
participant: toCusFormat(contextInfo.participant),
|
||||
body: body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { IMediaEngineProcessor } from '@waha/core/media/IMediaEngineProcessor';
|
||||
import * as Buffer from 'buffer';
|
||||
import { create, CreateConfig, Message, Whatsapp } from 'venom-bot';
|
||||
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextRequest,
|
||||
WANumberExistResult,
|
||||
} from '../../../structures/chatting.dto';
|
||||
import {
|
||||
WAHAEngine,
|
||||
WAHAEvents,
|
||||
WAHASessionStatus,
|
||||
} from '../../../structures/enums.dto';
|
||||
import { WAMessage } from '../../../structures/responses.dto';
|
||||
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
|
||||
import { NotImplementedByEngineError } from '../../exceptions';
|
||||
import { QR } from '../../QR';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
engine = WAHAEngine.VENOM;
|
||||
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
const venomOptions: CreateConfig = {
|
||||
headless: true,
|
||||
devtools: false,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: this.getBrowserArgsForPuppeteer(),
|
||||
autoClose: 60000,
|
||||
puppeteerOptions: {},
|
||||
};
|
||||
this.addProxyConfig(venomOptions);
|
||||
return create(this.name, this.getCatchQR(), undefined, venomOptions);
|
||||
}
|
||||
|
||||
protected addProxyConfig(venomOptions: CreateConfig) {
|
||||
if (this.proxyConfig?.server !== undefined) {
|
||||
venomOptions.addProxy = [this.proxyConfig?.server];
|
||||
}
|
||||
if (
|
||||
this.proxyConfig?.username !== undefined &&
|
||||
this.proxyConfig?.password !== undefined
|
||||
) {
|
||||
venomOptions.userProxy = this.proxyConfig?.username;
|
||||
venomOptions.userPass = this.proxyConfig?.password;
|
||||
}
|
||||
}
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg, urlCode);
|
||||
this.printQR(this.qr);
|
||||
this.status = WAHASessionStatus.SCAN_QR_CODE;
|
||||
this.logger.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.status = WAHASessionStatus.STARTING;
|
||||
try {
|
||||
this.whatsapp = await this.buildClient();
|
||||
} catch (error) {
|
||||
this.status = WAHASessionStatus.FAILED;
|
||||
this.logger.error(error);
|
||||
this.qr.save('');
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = WAHASessionStatus.WORKING;
|
||||
this.events.emit(WAHAInternalEvent.ENGINE_START);
|
||||
return this;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this.status = WAHASessionStatus.STOPPED;
|
||||
this.events.removeAllListeners();
|
||||
await this.whatsapp.close();
|
||||
}
|
||||
|
||||
subscribeEngineEvent(event: WAHAEvents | string, handler: (message) => void) {
|
||||
switch (event) {
|
||||
case WAHAEvents.MESSAGE:
|
||||
this.whatsapp.onMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ANY:
|
||||
this.whatsapp.onAnyMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
return true;
|
||||
case WAHAEvents.STATE_CHANGE:
|
||||
this.whatsapp.onStateChange(handler);
|
||||
return true;
|
||||
case WAHAEvents.MESSAGE_ACK:
|
||||
this.whatsapp.onAck(handler);
|
||||
return true;
|
||||
case WAHAEvents.GROUP_JOIN:
|
||||
this.whatsapp.onAddedToGroup(handler);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auth methods
|
||||
*/
|
||||
public getQR(): QR {
|
||||
return this.qr;
|
||||
}
|
||||
|
||||
getScreenshot(): Promise<Buffer> {
|
||||
if (this.status === WAHASessionStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WAHASessionStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WAHASessionStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot();
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
try {
|
||||
return await this.whatsapp.checkNumberStatus(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
} catch (error) {
|
||||
// Catch number doesn't exist error and return it as is
|
||||
if (error.status === 404 && !error.numberExists) {
|
||||
return error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to);
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(
|
||||
request.chatId,
|
||||
request.url,
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(
|
||||
request.chatId,
|
||||
String(request.latitude),
|
||||
String(request.longitude),
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.markMarkSeenMessage(chat.chatId);
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId, false);
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return;
|
||||
// The method is not available yet in new venom-bot
|
||||
// return this.whatsapp.stopTyping(chat.chatId);
|
||||
}
|
||||
|
||||
async getChatMessages(chatId: string, limit: number) {
|
||||
const messages = await this.whatsapp.getAllMessagesInChat(
|
||||
chatId,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const [count, message] of messages.entries()) {
|
||||
if (count > limit) {
|
||||
// Have enough in the list, stop processing
|
||||
break;
|
||||
}
|
||||
result.push(await this.processIncomingMessage(message));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
return this.getChatMessages(query.chatId, query.limit);
|
||||
}
|
||||
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message, this.name);
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IMediaEngineProcessor<Message> {
|
||||
constructor(public session: WhatsappSessionVenomCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
private async end() {
|
||||
try {
|
||||
this.whatsapp?.removeAllListeners();
|
||||
this.whatsapp?.pupBrowser?.removeAllListeners();
|
||||
this.whatsapp?.pupPage?.removeAllListeners();
|
||||
this.startDelayedJob.cancel();
|
||||
// It's possible that browser yet starting
|
||||
await waitUntil(
|
||||
|
||||
+23
-24
@@ -27,7 +27,6 @@ import { SessionManager } from './abc/manager.abc';
|
||||
import { SessionParams, WhatsappSession } from './abc/session.abc';
|
||||
import { EngineConfigService } from './config/EngineConfigService';
|
||||
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
|
||||
import { WhatsappSessionVenomCore } from './engines/venom/session.venom.core';
|
||||
import { WhatsappSessionWebJSCore } from './engines/webjs/session.webjs.core';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { getProxyConfig } from './helpers.proxy';
|
||||
@@ -43,6 +42,10 @@ export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
);
|
||||
}
|
||||
}
|
||||
enum DefaultSessionStatus {
|
||||
REMOVED = undefined,
|
||||
STOPPED = null,
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
@@ -51,7 +54,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
// session - exists and running (or failed or smth)
|
||||
// null - stopped
|
||||
// undefined - removed
|
||||
private session: WhatsappSession | undefined | null;
|
||||
private session: WhatsappSession | DefaultSessionStatus;
|
||||
private sessionConfig?: SessionConfig;
|
||||
DEFAULT = 'default';
|
||||
|
||||
@@ -66,7 +69,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
) {
|
||||
super();
|
||||
this.events = new EventEmitter();
|
||||
this.session = null;
|
||||
this.session = DefaultSessionStatus.STOPPED;
|
||||
this.sessionConfig = null;
|
||||
this.log.setContext(SessionManagerCore.name);
|
||||
const engineName = this.engineConfigService.getDefaultEngineName();
|
||||
@@ -86,8 +89,6 @@ export class SessionManagerCore extends SessionManager {
|
||||
protected getEngine(engine: WAHAEngine): typeof WhatsappSession {
|
||||
if (engine === WAHAEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WAHAEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore;
|
||||
} else if (engine === WAHAEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else {
|
||||
@@ -113,7 +114,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
//
|
||||
async exists(name: string): Promise<boolean> {
|
||||
this.onlyDefault(name);
|
||||
return this.session !== undefined;
|
||||
return this.session !== DefaultSessionStatus.REMOVED;
|
||||
}
|
||||
|
||||
isRunning(name: string): boolean {
|
||||
@@ -138,10 +139,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
new MediaNoopStorage(),
|
||||
this.config.mimetypes,
|
||||
);
|
||||
const logger = this.log.logger.child({
|
||||
session: name,
|
||||
sessionRunTimestamp: Date.now(),
|
||||
});
|
||||
const logger = this.log.logger.child({ session: name });
|
||||
logger.level = getPinoLogLevel(this.sessionConfig?.debug);
|
||||
const loggerBuilder: LoggerBuilder = logger;
|
||||
|
||||
@@ -199,7 +197,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
}
|
||||
}
|
||||
this.log.info(`Session has been stopped.`, { session: name });
|
||||
this.session = null;
|
||||
this.session = DefaultSessionStatus.STOPPED;
|
||||
await sleep(this.SESSION_STOP_TIMEOUT);
|
||||
}
|
||||
|
||||
@@ -210,7 +208,7 @@ export class SessionManagerCore extends SessionManager {
|
||||
|
||||
async delete(name: string): Promise<void> {
|
||||
this.onlyDefault(name);
|
||||
this.session = undefined;
|
||||
this.session = DefaultSessionStatus.REMOVED;
|
||||
this.sessionConfig = undefined;
|
||||
}
|
||||
|
||||
@@ -239,20 +237,20 @@ export class SessionManagerCore extends SessionManager {
|
||||
if (!this.session) {
|
||||
return undefined;
|
||||
}
|
||||
const sessions = { [this.DEFAULT]: this.session };
|
||||
const sessions = { [this.DEFAULT]: this.session as WhatsappSession };
|
||||
return getProxyConfig(this.config, sessions, this.DEFAULT);
|
||||
}
|
||||
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (session === undefined) {
|
||||
if (!session) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'.
|
||||
Please start it first by using POST /sessions/${name}/start request`,
|
||||
`We didn't find a session with name '${name}'.\n` +
|
||||
`Please start it first by using POST /sessions/${name}/start request`,
|
||||
);
|
||||
}
|
||||
return session;
|
||||
return session as WhatsappSession;
|
||||
}
|
||||
|
||||
async getSessions(all: boolean): Promise<SessionInfo[]> {
|
||||
@@ -270,12 +268,13 @@ export class SessionManagerCore extends SessionManager {
|
||||
return [];
|
||||
}
|
||||
|
||||
const me = this.session.getSessionMeInfo();
|
||||
const session = this.session as WhatsappSession;
|
||||
const me = session.getSessionMeInfo();
|
||||
// Get engine info
|
||||
let engineInfo = {};
|
||||
if (this.session) {
|
||||
if (session) {
|
||||
try {
|
||||
engineInfo = await promiseTimeout(10, this.session.getEngineInfo());
|
||||
engineInfo = await promiseTimeout(10, session.getEngineInfo());
|
||||
} catch (e) {
|
||||
this.log.warn(
|
||||
{ session: this.session },
|
||||
@@ -284,14 +283,14 @@ export class SessionManagerCore extends SessionManager {
|
||||
}
|
||||
}
|
||||
const engine = {
|
||||
engine: this.session?.engine,
|
||||
engine: session?.engine,
|
||||
...engineInfo,
|
||||
};
|
||||
return [
|
||||
{
|
||||
name: this.session.name,
|
||||
status: this.session.status,
|
||||
config: this.session.sessionConfig,
|
||||
name: session.name,
|
||||
status: session.status,
|
||||
config: session.sessionConfig,
|
||||
me: me,
|
||||
engine: engine,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,13 @@ export class LocalSessionConfigRepository extends ISessionConfigRepository {
|
||||
|
||||
async exists(sessionName: string): Promise<boolean> {
|
||||
const filepath = this.getFilePath(sessionName);
|
||||
return await fileExists(filepath);
|
||||
const exists = await fileExists(filepath);
|
||||
if (!exists) {
|
||||
// check directory exist for empty config sessions
|
||||
const folder = this.store.getSessionDirectory(sessionName);
|
||||
return await fileExists(folder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async get(sessionName: string): Promise<SessionConfig | null> {
|
||||
|
||||
@@ -98,5 +98,7 @@ async function bootstrap() {
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
logger.error(error, `Failed to start WAHA: ${error}`);
|
||||
// @ts-ignore
|
||||
logger.error(error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -7,3 +7,11 @@ export const ChatIdApiParam = ApiParam({
|
||||
description: 'Chat ID',
|
||||
example: '123456789@c.us',
|
||||
});
|
||||
|
||||
export const GroupIdApiParam = ApiParam({
|
||||
name: 'id',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'Group ID',
|
||||
example: '123123123@g.us',
|
||||
});
|
||||
@@ -12,7 +12,7 @@ export const SessionApiParam = ApiParam({
|
||||
schema: {
|
||||
default: 'default',
|
||||
},
|
||||
description: 'Session <code>name</code>',
|
||||
description: 'Session name',
|
||||
});
|
||||
/**
|
||||
* Session param
|
||||
|
||||
@@ -4,6 +4,7 @@ export enum QRCodeFormat {
|
||||
IMAGE = 'image',
|
||||
RAW = 'raw',
|
||||
}
|
||||
|
||||
export class QRCodeQuery {
|
||||
format: QRCodeFormat = QRCodeFormat.IMAGE;
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ export class SessionQuery {
|
||||
*/
|
||||
|
||||
export class SessionBaseRequest {
|
||||
session = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
@@ -45,6 +45,10 @@ export class Channel {
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Channel name',
|
||||
example: 'Channel Name',
|
||||
})
|
||||
name: string;
|
||||
|
||||
description?: string;
|
||||
|
||||
@@ -21,6 +21,10 @@ import { ChatIdProperty, ReplyToProperty } from './properties.dto';
|
||||
* Queries
|
||||
*/
|
||||
export class CheckNumberStatusQuery extends SessionQuery {
|
||||
@ApiProperty({
|
||||
description: 'The phone number to check',
|
||||
example: '1213213213',
|
||||
})
|
||||
@IsString()
|
||||
phone: string;
|
||||
}
|
||||
@@ -142,7 +146,8 @@ export class MessageContactVcardRequest extends ChatRequest {
|
||||
}
|
||||
|
||||
export class MessageTextRequest extends ChatRequest {
|
||||
text = 'Hi there!';
|
||||
text: string = 'Hi there!';
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
|
||||
@@ -151,14 +156,14 @@ export class MessageTextRequest extends ChatRequest {
|
||||
}
|
||||
|
||||
export class EditMessageRequest {
|
||||
text = 'Hello, world!';
|
||||
text: string = 'Hello, world!';
|
||||
|
||||
@ApiHideProperty()
|
||||
mentions?: string[];
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends MessageTextRequest {
|
||||
text = 'Reply text';
|
||||
text: string = 'Reply text';
|
||||
}
|
||||
|
||||
export class MessageLocationRequest extends ChatRequest {
|
||||
@@ -193,14 +198,14 @@ class FileRequest extends ChatRequest {
|
||||
}
|
||||
|
||||
export class MessageImageRequest extends FileRequest {
|
||||
caption: string;
|
||||
caption?: string;
|
||||
|
||||
@ReplyToProperty()
|
||||
reply_to?: string;
|
||||
}
|
||||
|
||||
export class MessageFileRequest extends FileRequest {
|
||||
caption: string;
|
||||
caption?: string;
|
||||
|
||||
@ReplyToProperty()
|
||||
reply_to?: string;
|
||||
@@ -230,7 +235,7 @@ export class MessageVideoRequest extends ChatRequest {
|
||||
})
|
||||
file: VideoRemoteFile | VideoBinaryFile;
|
||||
|
||||
caption: string = 'Just watch at this!';
|
||||
caption?: string = 'Just watch at this!';
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChatIdProperty } from '@waha/structures/properties.dto';
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
@@ -7,6 +8,7 @@ import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
*/
|
||||
|
||||
export class ContactQuery extends SessionQuery {
|
||||
@ChatIdProperty()
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
@@ -16,6 +18,7 @@ export class ContactQuery extends SessionQuery {
|
||||
*/
|
||||
|
||||
export class ContactRequest extends SessionBaseRequest {
|
||||
@ChatIdProperty()
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
@@ -32,7 +32,6 @@ export enum WAHASessionStatus {
|
||||
}
|
||||
|
||||
export enum WAHAEngine {
|
||||
VENOM = 'VENOM',
|
||||
WEBJS = 'WEBJS',
|
||||
NOWEB = 'NOWEB',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
@@ -5,6 +6,9 @@ import { IsArray, IsString } from 'class-validator';
|
||||
*/
|
||||
export class Participant {
|
||||
@IsString()
|
||||
@ApiProperty({
|
||||
example: '123456789@c.us',
|
||||
})
|
||||
id: string;
|
||||
}
|
||||
|
||||
@@ -29,6 +33,7 @@ export class DescriptionRequest {
|
||||
@IsString()
|
||||
description: string;
|
||||
}
|
||||
|
||||
export class SubjectRequest {
|
||||
@IsString()
|
||||
subject: string;
|
||||
|
||||
@@ -60,6 +60,7 @@ export class NowebStoreConfig {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Enable or disable the store for contacts, chats, and messages.',
|
||||
example: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
enabled: boolean = false;
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import {
|
||||
BinaryFile,
|
||||
RemoteFile,
|
||||
VideoBinaryFile,
|
||||
VideoRemoteFile,
|
||||
VoiceBinaryFile,
|
||||
VoiceRemoteFile,
|
||||
} from './files.dto';
|
||||
@@ -19,48 +21,48 @@ class StatusRequest {
|
||||
}
|
||||
|
||||
export class TextStatus extends StatusRequest {
|
||||
text = 'Have a look! https://github.com/';
|
||||
backgroundColor = '#38b42f';
|
||||
font = 1;
|
||||
text: string = 'Have a look! https://github.com/';
|
||||
backgroundColor: string = '#38b42f';
|
||||
font: number = 1;
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
@ApiExtraModels(RemoteFile, BinaryFile)
|
||||
export class ImageStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
file: RemoteFile | BinaryFile;
|
||||
|
||||
caption: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
|
||||
@ApiExtraModels(VoiceRemoteFile, VoiceBinaryFile)
|
||||
export class VoiceStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VoiceBinaryFile) },
|
||||
{ $ref: getSchemaPath(VoiceRemoteFile) },
|
||||
{ $ref: getSchemaPath(VoiceBinaryFile) },
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile;
|
||||
file: VoiceRemoteFile | VoiceBinaryFile;
|
||||
|
||||
backgroundColor = '#38b42f';
|
||||
backgroundColor: string = '#38b42f';
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
@ApiExtraModels(VideoRemoteFile, VideoBinaryFile)
|
||||
export class VideoStatus extends StatusRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
{ $ref: getSchemaPath(VideoRemoteFile) },
|
||||
{ $ref: getSchemaPath(VideoBinaryFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
file: VideoRemoteFile | VideoBinaryFile;
|
||||
|
||||
caption: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
export class DeleteStatusRequest extends StatusRequest {
|
||||
|
||||
@@ -32,8 +32,8 @@ export class SingleDelayedJobRunner {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.logger.debug(`Running job...`);
|
||||
fn().finally(() => {
|
||||
this.logger.debug(`Job finished`);
|
||||
this.timeout = null;
|
||||
this.logger.debug(`Job finished`);
|
||||
});
|
||||
}, this.timeoutMs);
|
||||
this.logger.info(`Job scheduled with timeout ${this.timeoutMs} ms`);
|
||||
@@ -42,6 +42,7 @@ export class SingleDelayedJobRunner {
|
||||
|
||||
cancel() {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
this.logger.info(`Job cancelled`);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ export class SinglePeriodicJobRunner {
|
||||
|
||||
stop() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
this.logger.info(`Job stopped`);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function getEngineName(): string {
|
||||
}
|
||||
|
||||
export const VERSION: WAHAEnvironment = {
|
||||
version: '2024.9.2',
|
||||
version: '2024.9.5',
|
||||
engine: getEngineName(),
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
|
||||
Reference in new issue
Block a user