From bf615efa96db4faf484768a1d9518f8a06f5dd46 Mon Sep 17 00:00:00 2001 From: allburov Date: Sun, 21 May 2023 18:09:25 +0700 Subject: [PATCH] [core] Apply pre-commit with prettier --- src/api/chatting.controller.ts | 246 +++++----- src/api/contacts.controller.ts | 112 ++--- src/api/exception.filter.ts | 70 +-- src/api/groups.controller.ts | 298 +++++++------ src/api/helpers.ts | 30 +- src/api/screenshot.controller.ts | 46 +- src/api/sessions.controller.ts | 45 +- src/api/version.controller.ts | 16 +- src/config.service.ts | 185 ++++---- src/core/QR.ts | 14 +- src/core/abc/manager.abc.ts | 36 +- src/core/abc/session.abc.ts | 404 ++++++++--------- src/core/abc/storage.abc.ts | 16 +- src/core/abc/webhooks.abc.ts | 4 +- src/core/app.module.core.ts | 99 ++-- src/core/exceptions.ts | 25 +- src/core/manager.core.ts | 276 ++++++------ src/core/session.noweb.core.ts | 192 ++++---- src/core/session.venom.core.ts | 473 +++++++++++--------- src/core/session.webjs.core.ts | 745 +++++++++++++++++-------------- src/core/storage.core.ts | 33 +- src/core/swagger.module.core.ts | 122 +++-- src/core/webhooks.core.ts | 142 +++--- src/helpers.ts | 14 +- src/main.ts | 78 ++-- src/structures/base.dto.ts | 10 +- src/structures/chatting.dto.ts | 267 +++++------ src/structures/contacts.dto.ts | 14 +- src/structures/enums.dto.ts | 28 +- src/structures/groups.dto.ts | 27 +- src/structures/responses.dto.ts | 215 ++++----- src/structures/sessions.dto.ts | 39 +- src/version.ts | 38 +- 33 files changed, 2276 insertions(+), 2083 deletions(-) diff --git a/src/api/chatting.controller.ts b/src/api/chatting.controller.ts index 1901f7c7..c0c1df39 100644 --- a/src/api/chatting.controller.ts +++ b/src/api/chatting.controller.ts @@ -1,141 +1,151 @@ -import {Body, Controller, Get, Post, Put, Query} from '@nestjs/common'; -import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger"; +import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; import { - ChatRequest, - CheckNumberStatusQuery, GetMessageQuery, - MessageContactVcardRequest, - MessageFileRequest, - MessageImageRequest, - MessageLinkPreviewRequest, - MessageLocationRequest, - MessageReactionRequest, - MessageReplyRequest, - MessageTextButtonsRequest, - MessageTextQuery, - MessageTextRequest, - MessageVoiceRequest, SendSeenRequest -} from "../structures/chatting.dto"; -import {WAMessage, WANumberExistResult} from "../structures/responses.dto"; -import {SessionManager} from "../core/abc/manager.abc"; + ChatRequest, + CheckNumberStatusQuery, + GetMessageQuery, + MessageContactVcardRequest, + MessageFileRequest, + MessageImageRequest, + MessageLinkPreviewRequest, + MessageLocationRequest, + MessageReactionRequest, + MessageReplyRequest, + MessageTextButtonsRequest, + MessageTextQuery, + MessageTextRequest, + MessageVoiceRequest, + SendSeenRequest, +} from '../structures/chatting.dto'; +import { WAMessage, WANumberExistResult } from '../structures/responses.dto'; +import { SessionManager } from '../core/abc/manager.abc'; @ApiSecurity('api_key') @Controller('api') @ApiTags('chatting') export class ChattingController { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - @Get('/checkNumberStatus') - @ApiOperation({summary: 'Check number status'}) - async checkNumberStatus(@Query() request: CheckNumberStatusQuery): Promise { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.checkNumberStatus(request) - } + @Get('/checkNumberStatus') + @ApiOperation({ summary: 'Check number status' }) + async checkNumberStatus( + @Query() request: CheckNumberStatusQuery, + ): Promise { + 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('/sendContactVcard') + sendContactVcard(@Body() request: MessageContactVcardRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.sendContactVCard(request); + } - @Get('/sendText') - @ApiOperation({summary: 'Send a text message'}) - sendTextGet( - @Query() query: MessageTextQuery, - ) { - const whatsapp = this.manager.getSession(query.session) - const msg = new MessageTextRequest() - msg.chatId = query.phone - msg.text = query.text - return whatsapp.sendText(msg) - } + @Get('/sendText') + @ApiOperation({ summary: 'Send a text message' }) + sendTextGet(@Query() query: MessageTextQuery) { + const whatsapp = this.manager.getSession(query.session); + const msg = new MessageTextRequest(); + msg.chatId = query.phone; + msg.text = query.text; + return whatsapp.sendText(msg); + } - @Post('/sendText') - @ApiOperation({summary: 'Send a text message'}) - sendText(@Body() request: MessageTextRequest): Promise { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.sendText(request) - } + @Post('/sendText') + @ApiOperation({ summary: 'Send a text message' }) + sendText(@Body() request: MessageTextRequest): Promise { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.sendText(request); + } - @Post('/sendTextButtons') - @ApiOperation({summary: 'Send a text message with buttons'}) - sendTextButtons(@Body() request: MessageTextButtonsRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.sendTextButtons(request) - } + @Post('/sendTextButtons') + @ApiOperation({ summary: 'Send a text message with buttons' }) + sendTextButtons(@Body() request: MessageTextButtonsRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.sendTextButtons(request); + } - @Post('/sendLocation') - sendLocation(@Body() request: MessageLocationRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.sendLocation(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('/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."}) - sendImage(@Body() request: MessageImageRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.sendImage(request) - } + @Post('/sendImage') + @ApiOperation({ + summary: + 'Send an image. 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); + return whatsapp.sendImage(request); + } - @Post('/sendFile') - @ApiOperation({summary: "Send a file. 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) - return whatsapp.sendFile(request) - } + @Post('/sendFile') + @ApiOperation({ + summary: + 'Send a file. 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); + return whatsapp.sendFile(request); + } - @Post('/sendVoice') - @ApiOperation({summary: "Send an voice message. 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) - return whatsapp.sendVoice(request) - } + @Post('/sendVoice') + @ApiOperation({ + summary: + 'Send an voice message. 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); + return whatsapp.sendVoice(request); + } - @Post('/reply') - @ApiOperation({summary: 'Reply to a text message'}) - reply(@Body() request: MessageReplyRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.reply(request) - } + @Post('/reply') + @ApiOperation({ summary: 'Reply to a text message' }) + reply(@Body() request: MessageReplyRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.reply(request); + } - @Post('/sendSeen') - sendSeen(@Body() chat: SendSeenRequest) { - const whatsapp = this.manager.getSession(chat.session) - return whatsapp.sendSeen(chat) - } + @Post('/sendSeen') + sendSeen(@Body() chat: SendSeenRequest) { + const whatsapp = this.manager.getSession(chat.session); + return whatsapp.sendSeen(chat); + } - @Post('/startTyping') - async startTyping(@Body() chat: ChatRequest) { - // It's infinitive action - const whatsapp = this.manager.getSession(chat.session) - await whatsapp.startTyping(chat) - return {result: true} - } + @Post('/startTyping') + async startTyping(@Body() chat: ChatRequest) { + // It's infinitive action + const whatsapp = this.manager.getSession(chat.session); + await whatsapp.startTyping(chat); + return { result: true }; + } - @Post('/stopTyping') - async stopTyping(@Body() chat: ChatRequest) { - const whatsapp = this.manager.getSession(chat.session) - await whatsapp.stopTyping(chat) - return {result: true} - } + @Post('/stopTyping') + async stopTyping(@Body() chat: ChatRequest) { + const whatsapp = this.manager.getSession(chat.session); + await whatsapp.stopTyping(chat); + return { result: true }; + } - @Put('/reaction') - @ApiOperation({summary: 'React to a message with an emoji'}) - setReaction(@Body() request: MessageReactionRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.setReaction(request) - } + @Put('/reaction') + @ApiOperation({ summary: 'React to a message with an emoji' }) + setReaction(@Body() request: MessageReactionRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.setReaction(request); + } - @Get('/messages') - @ApiOperation({summary: 'Get messages in a chat'}) - getMessages( @Query() query: GetMessageQuery ) { - const whatsapp = this.manager.getSession(query.session) - return whatsapp.getMessages(query) - } + @Get('/messages') + @ApiOperation({ summary: 'Get messages in a chat' }) + getMessages(@Query() query: GetMessageQuery) { + const whatsapp = this.manager.getSession(query.session); + return whatsapp.getMessages(query); + } } diff --git a/src/api/contacts.controller.ts b/src/api/contacts.controller.ts index fee9d7b0..ce9c96af 100644 --- a/src/api/contacts.controller.ts +++ b/src/api/contacts.controller.ts @@ -1,63 +1,73 @@ -import {Body, Controller, Get, Post, Query} from '@nestjs/common'; -import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger"; -import {SessionManager} from "../core/abc/manager.abc"; -import {ContactQuery, ContactRequest} from "../structures/contacts.dto"; -import {SessionQuery} from "../structures/base.dto"; -import {CheckNumberStatusQuery} from "../structures/chatting.dto"; -import {WANumberExistResult} from "../structures/responses.dto"; +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { SessionManager } from '../core/abc/manager.abc'; +import { ContactQuery, ContactRequest } from '../structures/contacts.dto'; +import { SessionQuery } from '../structures/base.dto'; +import { CheckNumberStatusQuery } from '../structures/chatting.dto'; +import { WANumberExistResult } from '../structures/responses.dto'; @ApiSecurity('api_key') @Controller('api/contacts') @ApiTags('contacts') export class ContactsController { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - @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.'}) - get(@Query() query: ContactQuery) { - const whatsapp = this.manager.getSession(query.session) - return whatsapp.getContact(query) - } + @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.', + }) + get(@Query() query: ContactQuery) { + const whatsapp = this.manager.getSession(query.session); + return whatsapp.getContact(query); + } - @Get('/all') - @ApiOperation({summary: 'Get all contacts'}) - getAll(@Query() query: SessionQuery) { - const whatsapp = this.manager.getSession(query.session) - return whatsapp.getContacts() - } + @Get('/all') + @ApiOperation({ summary: 'Get all contacts' }) + getAll(@Query() query: SessionQuery) { + const whatsapp = this.manager.getSession(query.session); + return whatsapp.getContacts(); + } - @Get('/check-exists') - @ApiOperation({summary: 'Check phone number is registered in WhatsApp.'}) - async checkExists(@Query() request: CheckNumberStatusQuery): Promise { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.checkNumberStatus(request) - } + @Get('/check-exists') + @ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' }) + async checkExists( + @Query() request: CheckNumberStatusQuery, + ): Promise { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.checkNumberStatus(request); + } - @Get('/about') - @ApiOperation({summary: 'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.'}) - getAbout(@Query() query: ContactQuery) { - const whatsapp = this.manager.getSession(query.session) - return whatsapp.getContactAbout(query) - } + @Get('/about') + @ApiOperation({ + summary: + 'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.', + }) + getAbout(@Query() query: ContactQuery) { + const whatsapp = this.manager.getSession(query.session); + return whatsapp.getContactAbout(query); + } - @Get('/profile-picture') - @ApiOperation({summary: 'Returns the contact\'s profile picture URL, if privacy settings allow it.'}) - getProfilePicture(@Query() query: ContactQuery) { - const whatsapp = this.manager.getSession(query.session) - return whatsapp.getContactProfilePicture(query) - } + @Get('/profile-picture') + @ApiOperation({ + summary: + "Returns the contact's profile picture URL, if privacy settings allow it.", + }) + getProfilePicture(@Query() query: ContactQuery) { + const whatsapp = this.manager.getSession(query.session); + return whatsapp.getContactProfilePicture(query); + } - @Post('/block') - @ApiOperation({summary:"Block contact"}) - block(@Body() request: ContactRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.blockContact(request) - } - @Post('/unblock') - @ApiOperation({summary:"Unblock contact"}) - unblock(@Body() request: ContactRequest) { - const whatsapp = this.manager.getSession(request.session) - return whatsapp.unblockContact(request) - } + @Post('/block') + @ApiOperation({ summary: 'Block contact' }) + block(@Body() request: ContactRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.blockContact(request); + } + @Post('/unblock') + @ApiOperation({ summary: 'Unblock contact' }) + unblock(@Body() request: ContactRequest) { + const whatsapp = this.manager.getSession(request.session); + return whatsapp.unblockContact(request); + } } diff --git a/src/api/exception.filter.ts b/src/api/exception.filter.ts index a986a9ce..209185f5 100644 --- a/src/api/exception.filter.ts +++ b/src/api/exception.filter.ts @@ -1,6 +1,12 @@ -import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common'; -import {Request, Response} from 'express'; -import {VERSION} from "../version"; +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { VERSION } from '../version'; /** * Serializer error to JSON. @@ -8,42 +14,36 @@ import {VERSION} from "../version"; * Credits: https://stackoverflow.com/a/72707578/6753144 */ export function serializeError(err: unknown) { - const properties = Object.getOwnPropertyNames(err) - // getOwnPropertyNames does not get 'name', like "ReferenceError" - properties.push('name') - return JSON.parse(JSON.stringify(err, properties)) + const properties = Object.getOwnPropertyNames(err); + // getOwnPropertyNames does not get 'name', like "ReferenceError" + properties.push('name'); + return JSON.parse(JSON.stringify(err, properties)); } @Catch() export class AllExceptionsFilter implements ExceptionFilter { - catch(exception: HttpException | Error, host: ArgumentsHost): void { - - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - const request = ctx.getRequest(); - - if (exception instanceof HttpException) { - response - .status(exception.getStatus()) - .json(exception.getResponse()); - return - } - - const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR - response - .status(httpStatus) - .json({ - statusCode: httpStatus, - timestamp: new Date().toISOString(), - exception: serializeError(exception), - request: { - path: request.url, - method: request.method, - body: request.body, - query: request.query, - }, - version: VERSION, - }); + catch(exception: HttpException | Error, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + if (exception instanceof HttpException) { + response.status(exception.getStatus()).json(exception.getResponse()); + return; } + + const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + response.status(httpStatus).json({ + statusCode: httpStatus, + timestamp: new Date().toISOString(), + exception: serializeError(exception), + request: { + path: request.url, + method: request.method, + body: request.body, + query: request.query, + }, + version: VERSION, + }); + } } diff --git a/src/api/groups.controller.ts b/src/api/groups.controller.ts index e27075e0..1a28d56d 100644 --- a/src/api/groups.controller.ts +++ b/src/api/groups.controller.ts @@ -1,165 +1,173 @@ -import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common'; -import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger"; -import {SessionManager} from "../core/abc/manager.abc"; -import {CreateGroupRequest, DescriptionRequest, ParticipantsRequest, SubjectRequest} from "../structures/groups.dto"; -import {WhatsappSession} from "../core/abc/session.abc"; -import {SessionApiParam, SessionParam} from "./helpers"; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, +} from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { SessionManager } from '../core/abc/manager.abc'; +import { + CreateGroupRequest, + DescriptionRequest, + ParticipantsRequest, + SubjectRequest, +} from '../structures/groups.dto'; +import { WhatsappSession } from '../core/abc/session.abc'; +import { SessionApiParam, SessionParam } from './helpers'; @ApiSecurity('api_key') @Controller('api/:session/groups') @ApiTags('groups') export class GroupsController { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - @Post('') - @SessionApiParam - @ApiOperation({summary: "Create a new group."}) - createGroup( - @SessionParam session: WhatsappSession, - @Body() request: CreateGroupRequest, - ) { - return session.createGroup(request) - } + @Post('') + @SessionApiParam + @ApiOperation({ summary: 'Create a new group.' }) + createGroup( + @SessionParam session: WhatsappSession, + @Body() request: CreateGroupRequest, + ) { + return session.createGroup(request); + } - @Get('') - @SessionApiParam - @ApiOperation({summary: "Get all groups."}) - getGroups( - @SessionParam session: WhatsappSession, - ) { - return session.getGroups() - } + @Get('') + @SessionApiParam + @ApiOperation({ summary: 'Get all groups.' }) + getGroups(@SessionParam session: WhatsappSession) { + return session.getGroups(); + } - @Get(':id') - @SessionApiParam - @ApiOperation({summary: "Get the group."}) - getGroup( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ) { - return session.getGroup(id) - } + @Get(':id') + @SessionApiParam + @ApiOperation({ summary: 'Get the group.' }) + getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) { + return session.getGroup(id); + } - @Delete(':id') - @SessionApiParam - @ApiOperation({summary: "Delete the group."}) - deleteGroup( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ) { - return session.deleteGroup(id) - } + @Delete(':id') + @SessionApiParam + @ApiOperation({ summary: 'Delete the group.' }) + deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) { + return session.deleteGroup(id); + } - @Post(':id/leave') - @SessionApiParam - @ApiOperation({summary: "Leave the group."}) - leaveGroup( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ) { - return session.leaveGroup(id) - } + @Post(':id/leave') + @SessionApiParam + @ApiOperation({ summary: 'Leave the group.' }) + leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) { + return session.leaveGroup(id); + } - @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" - }) - @SessionApiParam - setDescription( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: DescriptionRequest - ) { - return session.setDescription(id, request.description) - } + @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', + }) + @SessionApiParam + setDescription( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: DescriptionRequest, + ) { + return session.setDescription(id, request.description); + } - @Put(':id/subject') - @SessionApiParam - @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" - }) - setSubject( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: SubjectRequest - ) { - return session.setSubject(id, request.subject) - } + @Put(':id/subject') + @SessionApiParam + @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', + }) + setSubject( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: SubjectRequest, + ) { + return session.setSubject(id, request.subject); + } - @Get(':id/invite-code') - @SessionApiParam - @ApiOperation({summary: "Gets the invite code for a specific group."}) - getInviteCode( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ): Promise { - return session.getInviteCode(id) - } + @Get(':id/invite-code') + @SessionApiParam + @ApiOperation({ summary: 'Gets the invite code for a specific group.' }) + getInviteCode( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + ): Promise { + return session.getInviteCode(id); + } - @Post(':id/invite-code/revoke') - @SessionApiParam - @ApiOperation({summary: "Invalidates the current group invite code and generates a new one."}) - revokeInviteCode( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ): Promise { - return session.revokeInviteCode(id) - } + @Post(':id/invite-code/revoke') + @SessionApiParam + @ApiOperation({ + summary: + 'Invalidates the current group invite code and generates a new one.', + }) + revokeInviteCode( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + ): Promise { + return session.revokeInviteCode(id); + } - @Get(':id/participants/') - @SessionApiParam - @ApiOperation({summary: 'Get a list of participants by in the group.'}) - getParticipants( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - ) { - return session.getParticipants(id) - } + @Get(':id/participants/') + @SessionApiParam + @ApiOperation({ summary: 'Get a list of participants by in the group.' }) + getParticipants( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + ) { + return session.getParticipants(id); + } - @Post(':id/participants/add') - @SessionApiParam - @ApiOperation({summary: 'Adds a list of participants by ID to the group.'}) - addParticipants( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: ParticipantsRequest - ) { - return session.addParticipants(id, request) - } + @Post(':id/participants/add') + @SessionApiParam + @ApiOperation({ summary: 'Adds a list of participants by ID to the group.' }) + addParticipants( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: ParticipantsRequest, + ) { + return session.addParticipants(id, request); + } - @Post(':id/participants/remove') - @SessionApiParam - @ApiOperation({summary: 'Removes a list of participants by ID to the group.'}) - removeParticipants( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: ParticipantsRequest - ) { - return session.removeParticipants(id, request) - } + @Post(':id/participants/remove') + @SessionApiParam + @ApiOperation({ + summary: 'Removes a list of participants by ID to the group.', + }) + removeParticipants( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: ParticipantsRequest, + ) { + return session.removeParticipants(id, request); + } - @Post(':id/admin/promote') - @SessionApiParam - @ApiOperation({summary: 'Promote participants to admin users.'}) - promoteToAdmin( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: ParticipantsRequest - ) { - return session.promoteParticipantsToAdmin(id, request) - } + @Post(':id/admin/promote') + @SessionApiParam + @ApiOperation({ summary: 'Promote participants to admin users.' }) + promoteToAdmin( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: ParticipantsRequest, + ) { + return session.promoteParticipantsToAdmin(id, request); + } - @Post(':id/admin/demote') - @SessionApiParam - @ApiOperation({summary: 'Demotes participants by to regular users.'}) - demoteToAdmin( - @SessionParam session: WhatsappSession, - @Param('id') id: string, - @Body() request: ParticipantsRequest - ) { - return session.demoteParticipantsToUser(id, request) - } + @Post(':id/admin/demote') + @SessionApiParam + @ApiOperation({ summary: 'Demotes participants by to regular users.' }) + demoteToAdmin( + @SessionParam session: WhatsappSession, + @Param('id') id: string, + @Body() request: ParticipantsRequest, + ) { + return session.demoteParticipantsToUser(id, request); + } } diff --git a/src/api/helpers.ts b/src/api/helpers.ts index 367a5ae7..f3180929 100644 --- a/src/api/helpers.ts +++ b/src/api/helpers.ts @@ -1,7 +1,7 @@ -import { ApiParam } from "@nestjs/swagger"; -import { Injectable, Param, PipeTransform } from "@nestjs/common"; -import { SessionManager } from "../core/abc/manager.abc"; -import { WhatsappSession } from "../core/abc/session.abc"; +import { ApiParam } from '@nestjs/swagger'; +import { Injectable, Param, PipeTransform } from '@nestjs/common'; +import { SessionManager } from '../core/abc/manager.abc'; +import { WhatsappSession } from '../core/abc/session.abc'; /** * Get session name and return Whatsapp session back (if exists) @@ -10,27 +10,25 @@ import { WhatsappSession } from "../core/abc/session.abc"; */ @Injectable() export class SessionPipe implements PipeTransform { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - async transform(value: any) { - return this.manager.getSession(value) - } + async transform(value: any) { + return this.manager.getSession(value); + } } /** * Decorator for a method that uses SessionPipe above */ export const SessionApiParam = ApiParam({ - name: 'session', - required: true, - type: "string", - description: "WhatsApp session name", -}) + name: 'session', + required: true, + type: 'string', + description: 'WhatsApp session name', +}); /** * Session param @SessionParam session: WhatsappSession, */ -export const SessionParam = Param('session', SessionPipe) - +export const SessionParam = Param('session', SessionPipe); diff --git a/src/api/screenshot.controller.ts b/src/api/screenshot.controller.ts index 7aef0bc8..01c32480 100644 --- a/src/api/screenshot.controller.ts +++ b/src/api/screenshot.controller.ts @@ -1,34 +1,28 @@ -import {Controller, Get, Query, Res} from '@nestjs/common'; -import {ApiSecurity, ApiTags} from "@nestjs/swagger"; -import {Readable} from "stream"; -import {Response} from 'express'; -import {SessionManager} from "../core/abc/manager.abc"; -import {SessionQuery} from "../structures/base.dto"; - +import { Controller, Get, Query, Res } from '@nestjs/common'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { Readable } from 'stream'; +import { Response } from 'express'; +import { SessionManager } from '../core/abc/manager.abc'; +import { SessionQuery } from '../structures/base.dto'; @ApiSecurity('api_key') @Controller('api') @ApiTags('screenshot') export class ScreenshotController { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - @Get('/screenshot') - async screenshot( - @Res() res: Response, - @Query() sessionQuery: SessionQuery, - ) { - const whatsappService = this.manager.getSession(sessionQuery.session) - const buffer = await whatsappService.getScreenshot(); - const stream = new Readable(); - stream.push(buffer); - stream.push(null); + @Get('/screenshot') + async screenshot(@Res() res: Response, @Query() sessionQuery: SessionQuery) { + const whatsappService = this.manager.getSession(sessionQuery.session); + const buffer = await whatsappService.getScreenshot(); + const stream = new Readable(); + stream.push(buffer); + stream.push(null); - res.set({ - 'Content-Type': 'image/png', - 'Content-Length': buffer.length, - }); - stream.pipe(res) - } + res.set({ + 'Content-Type': 'image/png', + 'Content-Length': buffer.length, + }); + stream.pipe(res); + } } - diff --git a/src/api/sessions.controller.ts b/src/api/sessions.controller.ts index dc59da93..a0f091d9 100644 --- a/src/api/sessions.controller.ts +++ b/src/api/sessions.controller.ts @@ -1,55 +1,52 @@ -import { Body, Controller, Get, Post, Query } from "@nestjs/common"; -import { ApiOperation, ApiSecurity, ApiTags } from "@nestjs/swagger"; +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; import { SessionLogoutRequest, SessionDTO, SessionStartRequest, SessionStopRequest, - ListSessionsQuery -} from "../structures/sessions.dto"; -import { SessionManager } from "../core/abc/manager.abc"; -import { UnprocessableEntityException } from "@nestjs/common/exceptions/unprocessable-entity.exception"; -import { parseBool } from "../helpers"; + ListSessionsQuery, +} from '../structures/sessions.dto'; +import { SessionManager } from '../core/abc/manager.abc'; +import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception'; +import { parseBool } from '../helpers'; - -@ApiSecurity("api_key") -@Controller("api/sessions") -@ApiTags("sessions") +@ApiSecurity('api_key') +@Controller('api/sessions') +@ApiTags('sessions') export class SessionsController { - constructor(private manager: SessionManager) { - } + constructor(private manager: SessionManager) {} - @Post("/start/") + @Post('/start/') start(@Body() request: SessionStartRequest): SessionDTO { return this.manager.start(request); } - @Post("/stop/") - @ApiOperation({summary: 'Stop session'}) + @Post('/stop/') + @ApiOperation({ summary: 'Stop session' }) async stop(@Body() request: SessionStopRequest): Promise { await this.manager.stop(request); - if (request.logout){ + if (request.logout) { await this.manager.logout(request); } - return + return; } - @Post("/logout/") - @ApiOperation({summary: 'Logout from session.'}) + @Post('/logout/') + @ApiOperation({ summary: 'Logout from session.' }) clean(@Body() request: SessionLogoutRequest): Promise { const session = this.manager.getSession(request.name, false); if (session) { throw new UnprocessableEntityException( - `Can not clean running session, please stop it first. You can set 'clean' field to 'True' in stop request so it stops and cleans at the same time.` + `Can not clean running session, please stop it first. You can set 'clean' field to 'True' in stop request so it stops and cleans at the same time.`, ); } return this.manager.logout(request); } - @Get("/") + @Get('/') list(@Query() query: ListSessionsQuery): SessionDTO[] { - const all = parseBool(query.all) + const all = parseBool(query.all); return this.manager.getSessions(all); } } - diff --git a/src/api/version.controller.ts b/src/api/version.controller.ts index 44028ade..8b02ec8e 100644 --- a/src/api/version.controller.ts +++ b/src/api/version.controller.ts @@ -1,15 +1,13 @@ -import {Controller, Get} from '@nestjs/common'; -import {ApiSecurity, ApiTags} from "@nestjs/swagger"; -import {VERSION} from "../version"; - +import { Controller, Get } from '@nestjs/common'; +import { ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { VERSION } from '../version'; @ApiSecurity('api_key') @Controller('api/version') @ApiTags('other') export class VersionController { - @Get('') - async get() { - return VERSION - } + @Get('') + async get() { + return VERSION; + } } - diff --git a/src/config.service.ts b/src/config.service.ts index 39143806..5a6d47a7 100644 --- a/src/config.service.ts +++ b/src/config.service.ts @@ -1,104 +1,103 @@ -import { Injectable } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { WhatsappEngine } from "./structures/enums.dto"; -import { parseBool } from "./helpers"; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { WhatsappEngine } from './structures/enums.dto'; +import { parseBool } from './helpers'; @Injectable() export class WhatsappConfigService { - public files_uri = '/api/files' - public schema = "http" + public files_uri = '/api/files'; + public schema = 'http'; - constructor(private configService: ConfigService) { + constructor(private configService: ConfigService) {} + + get filesURL(): string { + return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`; + } + + get hostname(): string { + return this.configService.get('WHATSAPP_API_HOSTNAME', 'localhost'); + } + + get port(): string { + return this.configService.get('WHATSAPP_API_PORT', '3000'); + } + + get filesFolder(): string { + return this.configService.get( + 'WHATSAPP_FILES_FOLDER', + '/tmp/whatsapp-files', + ); + } + + get filesLifetime(): number { + return this.configService.get('WHATSAPP_FILES_LIFETIME', 180); + } + + get mimetypes(): string[] | null { + const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', ''); + return types ? types.split(',') : null; + } + + get startSessions(): string[] { + const value: string = this.configService.get('WHATSAPP_START_SESSION', ''); + if (!value) { + return []; } + return value.split(','); + } - get filesURL(): string { - return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/` + get shouldRestartAllSessions(): boolean { + const value: string = this.configService.get( + 'WHATSAPP_RESTART_ALL_SESSIONS', + 'false', + ); + return parseBool(value); + } + + getWebhookUrl(): string | undefined { + return this.get('WHATSAPP_HOOK_URL'); + } + + getWebhookEvents(): string[] { + const value = this.get('WHATSAPP_HOOK_EVENTS', ''); + return value ? value.split(',') : []; + } + + getDefaultEngineName(): WhatsappEngine { + const value = this.get('WHATSAPP_DEFAULT_ENGINE', WhatsappEngine.WEBJS); + if (value in WhatsappEngine) { + return WhatsappEngine[value]; } + console.log( + `Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`, + ); + } - get hostname(): string { - return this.configService.get( - 'WHATSAPP_API_HOSTNAME', - 'localhost', - ) + get(name: string, defaultValue = undefined): any { + return this.configService.get(name, defaultValue); + } + + getApiKey(): string | undefined { + return this.configService.get('WHATSAPP_API_KEY', ''); + } + + getSwaggerUsernamePassword(): [string, string] | undefined { + const user = this.configService.get('WHATSAPP_SWAGGER_USERNAME', undefined); + const password = this.configService.get( + 'WHATSAPP_SWAGGER_PASSWORD', + undefined, + ); + if (!user && !password) { + console.log( + 'Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD ' + + 'to enable swagger authentication.', + ); + return undefined; } + return [user, password]; + } - get port(): string { - return this.configService.get( - 'WHATSAPP_API_PORT', - '3000', - ) - } - - get filesFolder(): string { - return this.configService.get( - 'WHATSAPP_FILES_FOLDER', - '/tmp/whatsapp-files', - ) - } - - get filesLifetime(): number { - return this.configService.get( - 'WHATSAPP_FILES_LIFETIME', - 180, - ) - } - - get mimetypes(): string[] | null { - const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', "") - return types ? types.split(',') : null - } - - get startSessions(): string[] { - const value: string = this.configService.get('WHATSAPP_START_SESSION', "") - if (!value){ - return [] - } - return value.split(",") - } - - get shouldRestartAllSessions(): boolean { - const value: string = this.configService.get('WHATSAPP_RESTART_ALL_SESSIONS', "false") - return parseBool(value) - } - - getWebhookUrl(): string | undefined { - return this.get('WHATSAPP_HOOK_URL') - } - - getWebhookEvents(): string[] { - const value = this.get('WHATSAPP_HOOK_EVENTS', "") - return value ? value.split(',') : [] - } - - getDefaultEngineName(): WhatsappEngine { - const value = this.get("WHATSAPP_DEFAULT_ENGINE", WhatsappEngine.WEBJS) - if (value in WhatsappEngine) { - return WhatsappEngine[value] - } - console.log(`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`) - - } - - get(name: string, defaultValue = undefined): any { - return this.configService.get(name, defaultValue) - } - - getApiKey(): string | undefined { - return this.configService.get("WHATSAPP_API_KEY", "") - } - - getSwaggerUsernamePassword(): [string, string] | undefined { - const user = this.configService.get("WHATSAPP_SWAGGER_USERNAME", undefined) - const password = this.configService.get("WHATSAPP_SWAGGER_PASSWORD", undefined) - if (!user && !password) { - console.log("Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD " + - "to enable swagger authentication.") - return undefined - } - return [user, password] - } - - getSwaggerAdvancedConfigEnabled(): boolean { - return this.configService.get("WHATSAPP_SWAGGER_CONFIG_ADVANCED", false) - } + getSwaggerAdvancedConfigEnabled(): boolean { + return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false); + } } diff --git a/src/core/QR.ts b/src/core/QR.ts index 4bcaf020..ad2af7c9 100644 --- a/src/core/QR.ts +++ b/src/core/QR.ts @@ -1,11 +1,11 @@ export class QR { - private base64: string; + private base64: string; - save(base64) { - this.base64 = base64.replace(/^data:image\/png;base64,/, ''); - } + save(base64) { + this.base64 = base64.replace(/^data:image\/png;base64,/, ''); + } - get(): Buffer { - return Buffer.from(this.base64, "base64") - } + get(): Buffer { + return Buffer.from(this.base64, 'base64'); + } } diff --git a/src/core/abc/manager.abc.ts b/src/core/abc/manager.abc.ts index 1e050741..4f2293db 100644 --- a/src/core/abc/manager.abc.ts +++ b/src/core/abc/manager.abc.ts @@ -1,38 +1,36 @@ -import { OnApplicationShutdown } from "@nestjs/common"; +import { OnApplicationShutdown } from '@nestjs/common'; import { SessionLogoutRequest, SessionDTO, SessionStartRequest, - SessionStopRequest -} from "../../structures/sessions.dto"; -import { WhatsappSession } from "./session.abc"; -import { WhatsappEngine } from "../../structures/enums.dto"; -import { MediaStorage } from "./storage.abc"; -import { WebhookConductor } from "./webhooks.abc"; + SessionStopRequest, +} from '../../structures/sessions.dto'; +import { WhatsappSession } from './session.abc'; +import { WhatsappEngine } from '../../structures/enums.dto'; +import { MediaStorage } from './storage.abc'; +import { WebhookConductor } from './webhooks.abc'; export abstract class SessionManager implements OnApplicationShutdown { + protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession; - protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession + protected abstract get EngineClass(): typeof WhatsappSession; - protected abstract get EngineClass(): typeof WhatsappSession + protected abstract get WebhookConductorClass(): typeof WebhookConductor; - protected abstract get WebhookConductorClass(): typeof WebhookConductor + protected abstract get MediaStorageClass(): typeof MediaStorage; - protected abstract get MediaStorageClass(): typeof MediaStorage - - abstract onApplicationShutdown(signal ?: string) + abstract onApplicationShutdown(signal?: string); // // API Methods // - abstract start(request: SessionStartRequest): SessionDTO + abstract start(request: SessionStartRequest): SessionDTO; - abstract stop(request: SessionStopRequest): Promise + abstract stop(request: SessionStopRequest): Promise; - abstract logout(request: SessionLogoutRequest): Promise + abstract logout(request: SessionLogoutRequest): Promise; - abstract getSession(name: string, error?: boolean): WhatsappSession + abstract getSession(name: string, error?: boolean): WhatsappSession; - abstract getSessions(all: boolean): SessionDTO[] + abstract getSessions(all: boolean): SessionDTO[]; } - diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 9c8c696d..1ea951f2 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -1,264 +1,264 @@ -import { WAEvents, WhatsappEngine, WhatsappStatus } from "../../structures/enums.dto"; -import {ConsoleLogger} from "@nestjs/common"; import { - ChatRequest, - CheckNumberStatusQuery, GetMessageQuery, - MessageContactVcardRequest, - MessageFileRequest, - MessageImageRequest, - MessageLinkPreviewRequest, - MessageLocationRequest, - MessageReactionRequest, - MessageReplyRequest, - MessageTextButtonsRequest, - MessageTextRequest, - MessageVoiceRequest -} from "../../structures/chatting.dto"; -import { LocalSessionStorage, MediaStorage } from "./storage.abc"; -import {MessageId} from "whatsapp-web.js"; -import {ContactQuery, ContactRequest} from "../../structures/contacts.dto"; -import {NotImplementedByEngineError} from "../exceptions"; -import {CreateGroupRequest, ParticipantsRequest} from "../../structures/groups.dto"; -import * as fs from "fs"; - -const CHROME_PATH = "/usr/bin/google-chrome-stable" -const CHROMIUM_PATH = "/usr/bin/chromium" -import { EventEmitter } from 'events' + WAEvents, + WhatsappEngine, + WhatsappStatus, +} from '../../structures/enums.dto'; +import { ConsoleLogger } from '@nestjs/common'; +import { + ChatRequest, + CheckNumberStatusQuery, + GetMessageQuery, + MessageContactVcardRequest, + MessageFileRequest, + MessageImageRequest, + MessageLinkPreviewRequest, + MessageLocationRequest, + MessageReactionRequest, + MessageReplyRequest, + MessageTextButtonsRequest, + MessageTextRequest, + MessageVoiceRequest, +} from '../../structures/chatting.dto'; +import { LocalSessionStorage, MediaStorage } from './storage.abc'; +import { MessageId } from 'whatsapp-web.js'; +import { ContactQuery, ContactRequest } from '../../structures/contacts.dto'; +import { NotImplementedByEngineError } from '../exceptions'; +import { + CreateGroupRequest, + ParticipantsRequest, +} from '../../structures/groups.dto'; +import * as fs from 'fs'; +const CHROME_PATH = '/usr/bin/google-chrome-stable'; +const CHROMIUM_PATH = '/usr/bin/chromium'; +import { EventEmitter } from 'events'; export function getBrowserExecutablePath() { - if (fs.existsSync(CHROME_PATH)) { - return CHROME_PATH - } - return CHROMIUM_PATH + if (fs.existsSync(CHROME_PATH)) { + return CHROME_PATH; + } + return CHROMIUM_PATH; } export function ensureSuffix(phone) { - const suffix = "@c.us" - if (phone.includes("@")) { - return phone - } - return phone + suffix + const suffix = '@c.us'; + if (phone.includes('@')) { + return phone; + } + return phone + suffix; } export enum WAHAInternalEvent { - engine_start = "engine.start", + engine_start = 'engine.start', } export interface WhatsAppSessionConfig { - name: string, - storage: MediaStorage, - log: ConsoleLogger, - sessionStorage: LocalSessionStorage, + name: string; + storage: MediaStorage; + log: ConsoleLogger; + sessionStorage: LocalSessionStorage; } export abstract class WhatsappSession { - public status: WhatsappStatus; - public events: EventEmitter - public engine: WhatsappEngine + public status: WhatsappStatus; + public events: EventEmitter; + public engine: WhatsappEngine; - public name: string - protected storage: MediaStorage - protected log: ConsoleLogger - protected sessionStorage: LocalSessionStorage + public name: string; + protected storage: MediaStorage; + protected log: ConsoleLogger; + protected sessionStorage: LocalSessionStorage; + public constructor({ name, log, sessionStorage }: WhatsAppSessionConfig) { + this.name = name; + this.status = WhatsappStatus.STARTING; + this.log = log; + this.sessionStorage = sessionStorage; + this.events = new EventEmitter(); + } - public constructor({name, log, sessionStorage}: WhatsAppSessionConfig ) { - this.name = name - this.status = WhatsappStatus.STARTING - this.log = log - this.sessionStorage = sessionStorage - this.events = new EventEmitter() - } + getBrowserExecutablePath() { + return getBrowserExecutablePath(); + } - getBrowserExecutablePath() { - return getBrowserExecutablePath() - } + getBrowserArgsForPuppeteer() { + // Run optimized version of Chrome + // References: + // https://github.com/pedroslopez/whatsapp-web.js/issues/1420 + // https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/ + return [ + '--no-sandbox', + '--disable-client-side-phishing-detection', + '--disable-setuid-sandbox', + '--disable-component-update', + '--disable-default-apps', + '--disable-popup-blocking', + '--disable-offer-store-unmasked-wallet-cards', + '--disable-speech-api', + '--hide-scrollbars', + '--mute-audio', + '--disable-extensions', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-default-browser-check', + '--no-pings', + '--password-store=basic', + '--use-mock-keychain', + '--no-zygote', + '--single-process', + '--disable-gpu', + ]; + } - getBrowserArgsForPuppeteer() { - // Run optimized version of Chrome - // References: - // https://github.com/pedroslopez/whatsapp-web.js/issues/1420 - // https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/ - return [ - '--no-sandbox', - '--disable-client-side-phishing-detection', - '--disable-setuid-sandbox', - '--disable-component-update', - '--disable-default-apps', - '--disable-popup-blocking', - '--disable-offer-store-unmasked-wallet-cards', - '--disable-speech-api', - '--hide-scrollbars', - '--mute-audio', - '--disable-extensions', - '--disable-dev-shm-usage', - '--disable-accelerated-2d-canvas', - '--no-first-run', - '--no-default-browser-check', - '--no-pings', - '--password-store=basic', - '--use-mock-keychain', - '--no-zygote', - '--single-process', - '--disable-gpu' - ] - } + /** Start the session */ + abstract start(); + /** Stop the session */ + abstract stop(): void; - /** Start the session */ - abstract start() + /** Subscribe the handler to specific hook */ + abstract subscribe(hook: WAEvents | string, handler: (message) => void); - /** Stop the session */ - abstract stop(): void + /** + * START - Methods for API + */ + abstract getScreenshot(): Promise; - /** Subscribe the handler to specific hook */ - abstract subscribe(hook: WAEvents | string, handler: (message) => void) + abstract checkNumberStatus(request: CheckNumberStatusQuery); - /** - * START - Methods for API - */ - abstract getScreenshot(): Promise + abstract sendText(request: MessageTextRequest); - abstract checkNumberStatus(request: CheckNumberStatusQuery) + abstract sendContactVCard(request: MessageContactVcardRequest); - abstract sendText(request: MessageTextRequest) + abstract sendTextButtons(request: MessageTextButtonsRequest); - abstract sendContactVCard(request: MessageContactVcardRequest) + abstract sendLocation(request: MessageLocationRequest); - abstract sendTextButtons(request: MessageTextButtonsRequest) + abstract sendLinkPreview(request: MessageLinkPreviewRequest); - abstract sendLocation(request: MessageLocationRequest) + abstract sendImage(request: MessageImageRequest); - abstract sendLinkPreview(request: MessageLinkPreviewRequest) + abstract sendFile(request: MessageFileRequest); - abstract sendImage(request: MessageImageRequest) + abstract sendVoice(request: MessageVoiceRequest); - abstract sendFile(request: MessageFileRequest) + abstract reply(request: MessageReplyRequest); - abstract sendVoice(request: MessageVoiceRequest) + abstract sendSeen(chat: ChatRequest); - abstract reply(request: MessageReplyRequest) + abstract startTyping(chat: ChatRequest); - abstract sendSeen(chat: ChatRequest) + abstract stopTyping(chat: ChatRequest); - abstract startTyping(chat: ChatRequest) + abstract getMessages(query: GetMessageQuery); - abstract stopTyping(chat: ChatRequest) + abstract setReaction(request: MessageReactionRequest); - abstract getMessages(query: GetMessageQuery) + /** + * Contacts methods + */ + public getContact(query: ContactQuery) { + throw new NotImplementedByEngineError(); + } - abstract setReaction(request: MessageReactionRequest) + public getContacts() { + throw new NotImplementedByEngineError(); + } + public getContactAbout(query: ContactQuery) { + throw new NotImplementedByEngineError(); + } - /** - * Contacts methods - */ - public getContact(query: ContactQuery) { - throw new NotImplementedByEngineError() - } + public getContactProfilePicture(query: ContactQuery) { + throw new NotImplementedByEngineError(); + } - public getContacts() { - throw new NotImplementedByEngineError() - } + public blockContact(request: ContactRequest) { + throw new NotImplementedByEngineError(); + } - public getContactAbout(query: ContactQuery) { - throw new NotImplementedByEngineError() - } + public unblockContact(request: ContactRequest) { + throw new NotImplementedByEngineError(); + } - public getContactProfilePicture(query: ContactQuery) { - throw new NotImplementedByEngineError() - } + /** + * Group methods + */ + public createGroup(request: CreateGroupRequest) { + throw new NotImplementedByEngineError(); + } - public blockContact(request: ContactRequest) { - throw new NotImplementedByEngineError() - } + public getGroups() { + throw new NotImplementedByEngineError(); + } - public unblockContact(request: ContactRequest) { - throw new NotImplementedByEngineError() - } + public getGroup(id) { + throw new NotImplementedByEngineError(); + } - /** - * Group methods - */ - public createGroup(request: CreateGroupRequest) { - throw new NotImplementedByEngineError() - } + public deleteGroup(id) { + throw new NotImplementedByEngineError(); + } - public getGroups() { - throw new NotImplementedByEngineError() - } + public leaveGroup(id) { + throw new NotImplementedByEngineError(); + } - public getGroup(id) { - throw new NotImplementedByEngineError() - } + public setDescription(id, description) { + throw new NotImplementedByEngineError(); + } - public deleteGroup(id) { - throw new NotImplementedByEngineError() - } + public setSubject(id, description) { + throw new NotImplementedByEngineError(); + } - public leaveGroup(id) { - throw new NotImplementedByEngineError() - } + public getInviteCode(id): Promise { + throw new NotImplementedByEngineError(); + } - public setDescription(id, description) { - throw new NotImplementedByEngineError() - } + public revokeInviteCode(id): Promise { + throw new NotImplementedByEngineError(); + } - public setSubject(id, description) { - throw new NotImplementedByEngineError() - } + public getParticipants(id) { + throw new NotImplementedByEngineError(); + } - public getInviteCode(id): Promise { - throw new NotImplementedByEngineError() - } + public addParticipants(id, request: ParticipantsRequest) { + throw new NotImplementedByEngineError(); + } - public revokeInviteCode(id): Promise { - throw new NotImplementedByEngineError() - } + public removeParticipants(id, request: ParticipantsRequest) { + throw new NotImplementedByEngineError(); + } - public getParticipants(id) { - throw new NotImplementedByEngineError() - } + public promoteParticipantsToAdmin(id, request: ParticipantsRequest) { + throw new NotImplementedByEngineError(); + } - public addParticipants(id, request: ParticipantsRequest) { - throw new NotImplementedByEngineError() - } + public demoteParticipantsToUser(id, request: ParticipantsRequest) { + throw new NotImplementedByEngineError(); + } - public removeParticipants(id, request: ParticipantsRequest) { - throw new NotImplementedByEngineError() - } + /** + * END - Methods for API + */ - public promoteParticipantsToAdmin(id, request: ParticipantsRequest) { - throw new NotImplementedByEngineError() - } - - public demoteParticipantsToUser(id, request: ParticipantsRequest) { - throw new NotImplementedByEngineError() - } - - - /** - * END - Methods for API - */ - - /** - * Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet - * @param phone - */ - protected ensureSuffix(phone) { - return ensureSuffix(phone) - } - - protected deserializeId(messageId: string): MessageId { - const parts = messageId.split("_") - return { - "fromMe": parts[0] === "true", - "remote": parts[1], - "id": parts[2], - "_serialized": messageId - } - } + /** + * Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet + * @param phone + */ + protected ensureSuffix(phone) { + return ensureSuffix(phone); + } + protected deserializeId(messageId: string): MessageId { + const parts = messageId.split('_'); + return { + fromMe: parts[0] === 'true', + remote: parts[1], + id: parts[2], + _serialized: messageId, + }; + } } - - diff --git a/src/core/abc/storage.abc.ts b/src/core/abc/storage.abc.ts index f12dc314..10adcd83 100644 --- a/src/core/abc/storage.abc.ts +++ b/src/core/abc/storage.abc.ts @@ -1,19 +1,23 @@ export abstract class MediaStorage { - abstract save(messageId: string, mimetype: string, buffer: Buffer): Promise + abstract save( + messageId: string, + mimetype: string, + buffer: Buffer, + ): Promise; } export abstract class LocalSessionStorage { public sessionsFolder: string; protected constructor(public engine: string) { - this.engine = engine + this.engine = engine; } - abstract init() + abstract init(); - abstract getFolderPath(sessionName: string): string + abstract getFolderPath(sessionName: string): string; - abstract clean(sessionName: string) + abstract clean(sessionName: string); - abstract getAll(): string[] + abstract getAll(): string[]; } diff --git a/src/core/abc/webhooks.abc.ts b/src/core/abc/webhooks.abc.ts index 47cb9958..ea3d1db1 100644 --- a/src/core/abc/webhooks.abc.ts +++ b/src/core/abc/webhooks.abc.ts @@ -1,5 +1,5 @@ -import {WhatsappSession} from "./session.abc"; +import { WhatsappSession } from './session.abc'; export abstract class WebhookConductor { - abstract configure(session: WhatsappSession) + abstract configure(session: WhatsappSession); } diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts index 71eef11d..ba784cae 100644 --- a/src/core/app.module.core.ts +++ b/src/core/app.module.core.ts @@ -1,57 +1,56 @@ -import {ConsoleLogger, Module} from '@nestjs/common'; -import {ScreenshotController} from "../api/screenshot.controller"; -import {ConfigModule} from "@nestjs/config"; -import {WhatsappConfigService} from "../config.service"; -import {ServeStaticModule} from "@nestjs/serve-static"; -import {SessionsController} from "../api/sessions.controller"; -import {ChattingController} from "../api/chatting.controller"; -import {ContactsController} from "../api/contacts.controller"; -import {VersionController} from "../api/version.controller"; -import {PassportModule} from "@nestjs/passport"; -import {SessionManager} from "./abc/manager.abc"; -import {SessionManagerCore} from "./manager.core"; -import {GroupsController} from "../api/groups.controller"; +import { ConsoleLogger, Module } from '@nestjs/common'; +import { ScreenshotController } from '../api/screenshot.controller'; +import { ConfigModule } from '@nestjs/config'; +import { WhatsappConfigService } from '../config.service'; +import { ServeStaticModule } from '@nestjs/serve-static'; +import { SessionsController } from '../api/sessions.controller'; +import { ChattingController } from '../api/chatting.controller'; +import { ContactsController } from '../api/contacts.controller'; +import { VersionController } from '../api/version.controller'; +import { PassportModule } from '@nestjs/passport'; +import { SessionManager } from './abc/manager.abc'; +import { SessionManagerCore } from './manager.core'; +import { GroupsController } from '../api/groups.controller'; export const IMPORTS = [ - ConfigModule.forRoot({ - isGlobal: true, - }), - ServeStaticModule.forRootAsync({ - imports: [], - extraProviders: [WhatsappConfigService], - inject: [WhatsappConfigService], - useFactory: (config: WhatsappConfigService) => { - return [{ - rootPath: config.filesFolder, - serveRoot: config.files_uri, - }] + ConfigModule.forRoot({ + isGlobal: true, + }), + ServeStaticModule.forRootAsync({ + imports: [], + extraProviders: [WhatsappConfigService], + inject: [WhatsappConfigService], + useFactory: (config: WhatsappConfigService) => { + return [ + { + rootPath: config.filesFolder, + serveRoot: config.files_uri, }, - }), - PassportModule, -] -export const CONTROLLERS = [ - SessionsController, - ChattingController, - ContactsController, - GroupsController, - ScreenshotController, - VersionController, -] -const PROVIDERS = [ - { - provide: SessionManager, - useClass: SessionManagerCore, + ]; }, - WhatsappConfigService, - ConsoleLogger, -] - + }), + PassportModule, +]; +export const CONTROLLERS = [ + SessionsController, + ChattingController, + ContactsController, + GroupsController, + ScreenshotController, + VersionController, +]; +const PROVIDERS = [ + { + provide: SessionManager, + useClass: SessionManagerCore, + }, + WhatsappConfigService, + ConsoleLogger, +]; @Module({ - imports: IMPORTS, - controllers: CONTROLLERS, - providers: PROVIDERS, + imports: IMPORTS, + controllers: CONTROLLERS, + providers: PROVIDERS, }) -export class AppModuleCore { - -} +export class AppModuleCore {} diff --git a/src/core/exceptions.ts b/src/core/exceptions.ts index fddce6f9..2f1bc48a 100644 --- a/src/core/exceptions.ts +++ b/src/core/exceptions.ts @@ -1,20 +1,21 @@ -import {UnprocessableEntityException} from "@nestjs/common"; +import { UnprocessableEntityException } from '@nestjs/common'; -export const DOCS_URL = "https://waha.devlike.pro/" +export const DOCS_URL = 'https://waha.devlike.pro/'; export class NotImplementedByEngineError extends UnprocessableEntityException { - constructor(msg = "") { - let error = "The method is not implemented by the engine." - if (msg) { - error += ` ${msg}` - } - super(error); + constructor(msg = '') { + let error = 'The method is not implemented by the engine.'; + if (msg) { + error += ` ${msg}`; } + super(error); + } } - export class AvailableInPlusVersion extends UnprocessableEntityException { - constructor() { - super(`The feature is available only in Plus version. Check this out: ${DOCS_URL}`); - } + constructor() { + super( + `The feature is available only in Plus version. Check this out: ${DOCS_URL}`, + ); + } } diff --git a/src/core/manager.core.ts b/src/core/manager.core.ts index ed42d1a2..573a2c9b 100644 --- a/src/core/manager.core.ts +++ b/src/core/manager.core.ts @@ -1,144 +1,168 @@ -import {ConsoleLogger, Injectable, NotFoundException, UnprocessableEntityException} from "@nestjs/common"; -import {SessionManager} from "./abc/manager.abc"; -import { WAHAInternalEvent, WhatsappSession, WhatsAppSessionConfig } from "./abc/session.abc"; -import { WhatsappEngine, WhatsappStatus } from "../structures/enums.dto"; -import { SessionLogoutRequest, SessionDTO, SessionStartRequest, SessionStopRequest } from "../structures/sessions.dto"; -import {WhatsappConfigService} from "../config.service"; -import {WhatsappSessionVenomCore} from "./session.venom.core"; -import {WhatsappSessionWebJSCore} from "./session.webjs.core"; -import {DOCS_URL} from "./exceptions"; -import {WebhookConductorCore} from "./webhooks.core"; -import { MediaStorageCore, SessionStorageCore } from "./storage.core"; -import {WhatsappSessionNoWebCore} from "./session.noweb.core"; -import { LocalSessionStorage } from "./abc/storage.abc"; +import { + ConsoleLogger, + Injectable, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; +import { SessionManager } from './abc/manager.abc'; +import { + WAHAInternalEvent, + WhatsappSession, + WhatsAppSessionConfig, +} from './abc/session.abc'; +import { WhatsappEngine, WhatsappStatus } from '../structures/enums.dto'; +import { + SessionLogoutRequest, + SessionDTO, + SessionStartRequest, + SessionStopRequest, +} from '../structures/sessions.dto'; +import { WhatsappConfigService } from '../config.service'; +import { WhatsappSessionVenomCore } from './session.venom.core'; +import { WhatsappSessionWebJSCore } from './session.webjs.core'; +import { DOCS_URL } from './exceptions'; +import { WebhookConductorCore } from './webhooks.core'; +import { MediaStorageCore, SessionStorageCore } from './storage.core'; +import { WhatsappSessionNoWebCore } from './session.noweb.core'; +import { LocalSessionStorage } from './abc/storage.abc'; export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException { - constructor() { - super(`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`); - } + constructor() { + super( + `WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`, + ); + } } @Injectable() export class SessionManagerCore extends SessionManager { - private session: WhatsappSession; - DEFAULT = "default" + private session: WhatsappSession; + DEFAULT = 'default'; - // @ts-ignore - protected MediaStorageClass = MediaStorageCore - // @ts-ignore - protected WebhookConductorClass = WebhookConductorCore - protected readonly EngineClass: typeof WhatsappSession; - protected sessionStorage: LocalSessionStorage + // @ts-ignore + protected MediaStorageClass = MediaStorageCore; + // @ts-ignore + protected WebhookConductorClass = WebhookConductorCore; + protected readonly EngineClass: typeof WhatsappSession; + protected sessionStorage: LocalSessionStorage; + constructor( + private config: WhatsappConfigService, + private log: ConsoleLogger, + ) { + super(); - constructor( - private config: WhatsappConfigService, - private log: ConsoleLogger, - ) { - super() + this.log.setContext('SessionManager'); + this.session = undefined; + const engineName = this.config.getDefaultEngineName(); + this.EngineClass = this.getEngine(engineName); + this.sessionStorage = new SessionStorageCore(engineName.toLowerCase()); + this.sessionStorage.init(); - this.log.setContext('SessionManager') - this.session = undefined - const engineName = this.config.getDefaultEngineName() - this.EngineClass = this.getEngine(engineName) - this.sessionStorage = new SessionStorageCore(engineName.toLowerCase()) - this.sessionStorage.init() + this.startPredefinedSessions(); + } - this.startPredefinedSessions() + protected startPredefinedSessions() { + const startSessions = this.config.startSessions; + startSessions.forEach((sessionName) => { + this.start({ name: sessionName }); + }); + } + + protected getEngine(engine: WhatsappEngine): typeof WhatsappSession { + if (engine === WhatsappEngine.WEBJS) { + return WhatsappSessionWebJSCore; + } else if (engine === WhatsappEngine.VENOM) { + return WhatsappSessionVenomCore; + } else if (engine === WhatsappEngine.NOWEB) { + return WhatsappSessionNoWebCore; + } else { + throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`); } + } - protected startPredefinedSessions() { - const startSessions = this.config.startSessions; - startSessions.forEach((sessionName) => { - this.start({ name: sessionName }); - } + private onlyDefault(name: string) { + if (name !== this.DEFAULT) { + throw new OnlyDefaultSessionIsAllowed(); + } + } + + async onApplicationShutdown(signal?: string) { + if (!this.session) { + return; + } + await this.stop({ name: this.DEFAULT, logout: false }); + } + + // + // API Methods + // + start(request: SessionStartRequest): SessionDTO { + this.onlyDefault(request.name); + + const name = request.name; + this.log.log(`'${name}' - starting session...`); + const log = new ConsoleLogger(`WhatsappSession - ${name}`); + const storage = new this.MediaStorageClass(); + const webhookLog = new ConsoleLogger(`Webhook - ${name}`); + const webhook = new this.WebhookConductorClass( + webhookLog, + this.config.getWebhookUrl(), + this.config.getWebhookEvents(), + ); + + const sessionConfig: WhatsAppSessionConfig = { + name, + storage, + log, + sessionStorage: this.sessionStorage, + }; + // @ts-ignore + const session = new this.EngineClass(sessionConfig); + this.session = session; + + session.events.on(WAHAInternalEvent.engine_start, () => + webhook.configure(session), + ); + session.start(); + return { name: session.name, status: session.status }; + } + + async stop(request: SessionStopRequest): Promise { + this.onlyDefault(request.name); + + const name = request.name; + this.log.log(`Stopping ${name} session...`); + const session = this.getSession(name); + await session.stop(); + this.log.log(`"${name}" has been stopped.`); + this.session = undefined; + } + + async logout(request: SessionLogoutRequest) { + this.sessionStorage.clean(request.name); + } + + getSession(name: string, error = true): WhatsappSession { + this.onlyDefault(name); + const session = this.session; + if (!session) { + if (error) { + throw new NotFoundException( + `We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`, ); + } + return; } + return session; + } - protected getEngine(engine: WhatsappEngine): typeof WhatsappSession { - if (engine === WhatsappEngine.WEBJS) { - return WhatsappSessionWebJSCore - } else if (engine === WhatsappEngine.VENOM) { - return WhatsappSessionVenomCore - } else if (engine === WhatsappEngine.NOWEB) { - return WhatsappSessionNoWebCore - } else { - throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`) - } - - } - - private onlyDefault(name: string) { - if (name !== this.DEFAULT) { - throw new OnlyDefaultSessionIsAllowed() - } - } - - async onApplicationShutdown(signal?: string) { - if (!this.session){ - return - } - await this.stop({name: this.DEFAULT, logout: false}) - } - - // - // API Methods - // - start(request: SessionStartRequest): SessionDTO { - this.onlyDefault(request.name) - - const name = request.name - this.log.log(`'${name}' - starting session...`) - const log = new ConsoleLogger(`WhatsappSession - ${name}`) - const storage = new this.MediaStorageClass() - const webhookLog = new ConsoleLogger(`Webhook - ${name}`) - const webhook = new this.WebhookConductorClass( - webhookLog, - this.config.getWebhookUrl(), - this.config.getWebhookEvents() - ) - - const sessionConfig: WhatsAppSessionConfig = {name, storage, log, sessionStorage: this.sessionStorage} - // @ts-ignore - const session = new this.EngineClass(sessionConfig) - this.session = session - - session.events.on(WAHAInternalEvent.engine_start, () => webhook.configure(session)) - session.start() - return {name: session.name, status: session.status} - } - - async stop(request: SessionStopRequest): Promise { - this.onlyDefault(request.name) - - const name = request.name - this.log.log(`Stopping ${name} session...`) - const session = this.getSession(name) - await session.stop() - this.log.log(`"${name}" has been stopped.`) - this.session = undefined - } - - async logout(request: SessionLogoutRequest){ - this.sessionStorage.clean(request.name) - } - - getSession(name: string, error=true): WhatsappSession { - this.onlyDefault(name) - const session = this.session - if (!session) { - if (error){ - throw new NotFoundException( `We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`); - } - return - } - return session - } - - getSessions(all: boolean): SessionDTO[] { - if (!this.session){ - return all? [{name: this.DEFAULT, status: WhatsappStatus.STOPPED}] : [] - } - return [{name: this.session.name, status: this.session.status}] + getSessions(all: boolean): SessionDTO[] { + if (!this.session) { + return all + ? [{ name: this.DEFAULT, status: WhatsappStatus.STOPPED }] + : []; } + return [{ name: this.session.name, status: this.session.status }]; + } } diff --git a/src/core/session.noweb.core.ts b/src/core/session.noweb.core.ts index 51ba3d83..68b530b3 100644 --- a/src/core/session.noweb.core.ts +++ b/src/core/session.noweb.core.ts @@ -1,13 +1,22 @@ -import { SECOND, WAEvents, WhatsappEngine, WhatsappStatus } from "../structures/enums.dto"; +import { + SECOND, + WAEvents, + WhatsappEngine, + WhatsappStatus, +} from '../structures/enums.dto'; import makeWASocket, { DisconnectReason, isJidGroup, makeInMemoryStore, - useMultiFileAuthState -} from "@adiwajshing/baileys"; + useMultiFileAuthState, +} from '@adiwajshing/baileys'; -import { ensureSuffix, WAHAInternalEvent, WhatsappSession } from "./abc/session.abc"; -import { WAMessage, WANumberExistResult } from "../structures/responses.dto"; +import { + ensureSuffix, + WAHAInternalEvent, + WhatsappSession, +} from './abc/session.abc'; +import { WAMessage, WANumberExistResult } from '../structures/responses.dto'; import { Button, ChatRequest, @@ -23,34 +32,39 @@ import { MessageTextButtonsRequest, MessageTextRequest, MessageVoiceRequest, - SendSeenRequest -} from "../structures/chatting.dto"; -import { AvailableInPlusVersion, NotImplementedByEngineError } from "./exceptions"; -import { ContactQuery, ContactRequest } from "../structures/contacts.dto"; -import { CreateGroupRequest, ParticipantsRequest } from "../structures/groups.dto"; -import { QR } from "./QR"; -import { MediaStorage } from "./abc/storage.abc"; -import { ConsoleLogger, UnprocessableEntityException } from "@nestjs/common"; -import { Message } from "whatsapp-web.js"; -import * as fs from "fs"; -import * as path from "path"; -import * as os from "os"; + SendSeenRequest, +} from '../structures/chatting.dto'; +import { + AvailableInPlusVersion, + NotImplementedByEngineError, +} from './exceptions'; +import { ContactQuery, ContactRequest } from '../structures/contacts.dto'; +import { + CreateGroupRequest, + ParticipantsRequest, +} from '../structures/groups.dto'; +import { QR } from './QR'; +import { MediaStorage } from './abc/storage.abc'; +import { ConsoleLogger, UnprocessableEntityException } from '@nestjs/common'; +import { Message } from 'whatsapp-web.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; // eslint-disable-next-line @typescript-eslint/no-var-requires -const QRCode = require("qrcode"); +const QRCode = require('qrcode'); // eslint-disable-next-line @typescript-eslint/no-var-requires -const logger = require("pino")(); - +const logger = require('pino')(); export const BaileysEvents = { - CONNECTION_UPDATE: "connection.update", - CREDS_UPDATE: "creds.update", - MESSAGES_UPSERT: "messages.upsert", - GROUPS_UPSERT: "groups.upsert" + CONNECTION_UPDATE: 'connection.update', + CREDS_UPDATE: 'creds.update', + MESSAGES_UPSERT: 'messages.upsert', + GROUPS_UPSERT: 'groups.upsert', }; export class WhatsappSessionNoWebCore extends WhatsappSession { - engine = WhatsappEngine.NOWEB + engine = WhatsappEngine.NOWEB; sock: any; store: any; @@ -68,7 +82,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } protected getAuthFolder() { - const folder = this.sessionStorage.getFolderPath(this.name) + const folder = this.sessionStorage.getFolderPath(this.name); return fs.mkdtempSync(folder); } @@ -77,8 +91,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { const sock: any = makeWASocket({ auth: state, printQRInTerminal: true, - browser: ["Linux", "Chrome", "111.0.5563.64"], - logger: logger + browser: ['Linux', 'Chrome', '111.0.5563.64'], + logger: logger, }); sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds); return sock; @@ -96,14 +110,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { this.connectStore(); this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, async (update) => { const { connection, lastDisconnect, qr } = update; - if (connection === "connecting") { - this.qr.save(""); + if (connection === 'connecting') { + this.qr.save(''); this.status = WhatsappStatus.WORKING; return; - } else if (connection === "close") { - const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== DisconnectReason.loggedOut; - this.log.error("connection closed due to ", lastDisconnect.error, ", reconnecting ", shouldReconnect); - this.qr.save(""); + } else if (connection === 'close') { + const shouldReconnect = + lastDisconnect.error?.output?.statusCode !== + DisconnectReason.loggedOut; + this.log.error( + 'connection closed due to ', + lastDisconnect.error, + ', reconnecting ', + shouldReconnect, + ); + this.qr.save(''); this.status = WhatsappStatus.FAILED; // reconnect if not logged out if (shouldReconnect) { @@ -124,7 +145,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { stop() { this.sock.ws.removeAllListeners(); - this.log.log("socket connection terminated"); + this.log.log('socket connection terminated'); return; } @@ -133,18 +154,24 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { */ async getScreenshot(): Promise { if (this.status === WhatsappStatus.STARTING) { - throw new UnprocessableEntityException(`The session is starting, please try again after few seconds`); + throw new UnprocessableEntityException( + `The session is starting, please try again after few seconds`, + ); } else if (this.status === WhatsappStatus.SCAN_QR_CODE) { return Promise.resolve(this.qr.get()); } else if (this.status === WhatsappStatus.WORKING) { - throw new UnprocessableEntityException(`Can not get screenshot for non chrome based engine.`); + throw new UnprocessableEntityException( + `Can not get screenshot for non chrome based engine.`, + ); } else { throw new UnprocessableEntityException(`Unknown status - ${this.status}`); } } - async checkNumberStatus(request: CheckNumberStatusQuery): Promise { - const phone = request.phone.split("@")[0]; + async checkNumberStatus( + request: CheckNumberStatusQuery, + ): Promise { + const phone = request.phone.split('@')[0]; const [result] = await this.sock.onWhatsApp(phone); if (!result) { return { numberExists: false }; @@ -160,14 +187,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { sendTextButtons(request: MessageTextButtonsRequest) { const buttons = request.buttons.map((button: Button) => { return { - buttonId: button.id, buttonText: { displayText: button.text }, type: 1 + buttonId: button.id, + buttonText: { displayText: button.text }, + type: 1, }; }); const buttonMessage = { text: request.title, buttons: buttons, - headerType: 1 + headerType: 1, }; return this.sock.sendMessage(request.chatId, buttonMessage); @@ -180,7 +209,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { async reply(request: MessageReplyRequest) { const { id } = parseMessageId(request.reply_to); const message = await this.store.loadMessage(toJID(request.chatId), id); - return await this.sock.sendMessage(request.chatId, { text: request.text }, { quoted: message }); + return await this.sock.sendMessage( + request.chatId, + { text: request.text }, + { quoted: message }, + ); } sendImage(request: MessageImageRequest) { @@ -196,10 +229,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } sendLocation(request: MessageLocationRequest) { - return this.sock.sendMessage( - request.chatId, - { location: { degreesLatitude: request.latitude, degreesLongitude: request.longitude } } - ); + return this.sock.sendMessage(request.chatId, { + location: { + degreesLatitude: request.latitude, + degreesLongitude: request.longitude, + }, + }); } sendLinkPreview(request: MessageLinkPreviewRequest) { @@ -208,23 +243,22 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { return this.sock.sendMessage(chatId, { text: text }); } - async sendSeen(request: SendSeenRequest) { const key = parseMessageId(request.messageId); const data = { remoteJid: key.remoteJid, id: key.id, - participant: request.participant + participant: request.participant, }; return this.sock.readMessages([data]); } async startTyping(request: ChatRequest) { - return this.sock.sendPresenceUpdate("composing", request.chatId); + return this.sock.sendPresenceUpdate('composing', request.chatId); } async stopTyping(request: ChatRequest) { - return this.sock.sendPresenceUpdate("paused", request.chatId); + return this.sock.sendPresenceUpdate('paused', request.chatId); } async getMessages(query: GetMessageQuery) { @@ -236,13 +270,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { const reactionMessage = { react: { text: request.reaction, - key: key - } + key: key, + }, }; return this.sock.sendMessage(key.remoteJid, reactionMessage); } - /** * Contacts methods */ @@ -319,22 +352,22 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { public async addParticipants(id, request: ParticipantsRequest) { const participants = request.participants.map(getId); - return this.sock.groupParticipantsUpdate(id, participants, "add"); + return this.sock.groupParticipantsUpdate(id, participants, 'add'); } public async removeParticipants(id, request: ParticipantsRequest) { const participants = request.participants.map(getId); - return this.sock.groupParticipantsUpdate(id, participants, "remove"); + return this.sock.groupParticipantsUpdate(id, participants, 'remove'); } public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) { const participants = request.participants.map(getId); - return this.sock.groupParticipantsUpdate(id, participants, "promote"); + return this.sock.groupParticipantsUpdate(id, participants, 'promote'); } public async demoteParticipantsToUser(id, request: ParticipantsRequest) { const participants = request.participants.map(getId); - return this.sock.groupParticipantsUpdate(id, participants, "demote"); + return this.sock.groupParticipantsUpdate(id, participants, 'demote'); } /** @@ -343,19 +376,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { subscribe(event, handler) { if (event === WAEvents.MESSAGE) { - return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, - ({ messages }) => this.handleIncomingMessages(messages, handler, false) + return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) => + this.handleIncomingMessages(messages, handler, false), ); } else if (event === WAEvents.MESSAGE_ANY) { - return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, - ({ messages }) => this.handleIncomingMessages(messages, handler, true) + return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) => + this.handleIncomingMessages(messages, handler, true), ); } else if (event === WAEvents.STATE_CHANGE) { return this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler); } else if (event === WAEvents.GROUP_JOIN) { return this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler); } else { - throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`); + throw new NotImplementedByEngineError( + `Engine does not support webhook event: ${event}`, + ); } } @@ -386,12 +421,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } const from = message.key.remoteJid; const id = buildMessageId(message.key); - let body = message.message.conversation - if (!body){ - // Some of the messages have no conversation, but instead have text in extendedTextMessage - // https://github.com/devlikeapro/whatsapp-http-api/issues/90 - body = message.message.extendedTextMessage?.text - }return Promise.resolve({ + let body = message.message.conversation; + if (!body) { + // Some of the messages have no conversation, but instead have text in extendedTextMessage + // https://github.com/devlikeapro/whatsapp-http-api/issues/90 + body = message.message.extendedTextMessage?.text; + } + return Promise.resolve({ id: id, timestamp: message.messageTimestamp, from: toCusFormat(from), @@ -407,7 +443,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { ack: message.ack, location: message.location, vCards: message.vCards, - _data: message + _data: message, }); } @@ -417,10 +453,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } // @ts-ignore - message.mediaUrl = await this.storage.save(message.key.id, "", undefined); + message.mediaUrl = await this.storage.save(message.key.id, '', undefined); return message; } - } /** @@ -433,7 +468,7 @@ function toCusFormat(remoteJid) { if (!remoteJid) { return; } - const number = remoteJid.split("@")[0]; + const number = remoteJid.split('@')[0]; return ensureSuffix(number); } @@ -445,8 +480,8 @@ function toJID(chatId) { if (isJidGroup(chatId)) { return chatId; } - const number = chatId.split("@")[0]; - return number + "@s.whatsapp.net"; + const number = chatId.split('@')[0]; + return number + '@s.whatsapp.net'; } /** @@ -465,18 +500,19 @@ function buildMessageId({ id, remoteJid, fromMe }) { * {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false} */ function parseMessageId(messageId) { - const parts = messageId.split("_"); + const parts = messageId.split('_'); if (parts.length != 3) { - throw new Error("Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA"); + throw new Error( + 'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA', + ); } - const fromMe = parts[0] == "true"; + const fromMe = parts[0] == 'true'; const chatId = parts[1]; const remoteJid = toJID(chatId); const id = parts[2]; return { fromMe: fromMe, id: id, remoteJid: remoteJid }; } - function getId(object) { return object.id; } diff --git a/src/core/session.venom.core.ts b/src/core/session.venom.core.ts index 3804e1eb..50d22c30 100644 --- a/src/core/session.venom.core.ts +++ b/src/core/session.venom.core.ts @@ -1,239 +1,274 @@ -import {WAHAInternalEvent, WhatsappSession} from "./abc/session.abc"; +import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc'; import { - Button, - ChatRequest, - CheckNumberStatusQuery, - GetMessageQuery, - MessageContactVcardRequest, - MessageFileRequest, - MessageImageRequest, - MessageLinkPreviewRequest, - MessageLocationRequest, - MessageReactionRequest, - MessageReplyRequest, - MessageTextButtonsRequest, - MessageTextRequest -} from "../structures/chatting.dto"; -import {WAMessage, WANumberExistResult} from "../structures/responses.dto"; -import {create, Message, Whatsapp} from "venom-bot"; -import { WAEvents, WhatsappEngine, WhatsappStatus } from "../structures/enums.dto"; -import {NotImplementedByEngineError} from "./exceptions"; -import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception"; -import {QR} from "./QR"; + Button, + ChatRequest, + CheckNumberStatusQuery, + GetMessageQuery, + MessageContactVcardRequest, + MessageFileRequest, + MessageImageRequest, + MessageLinkPreviewRequest, + MessageLocationRequest, + MessageReactionRequest, + MessageReplyRequest, + MessageTextButtonsRequest, + MessageTextRequest, +} from '../structures/chatting.dto'; +import { WAMessage, WANumberExistResult } from '../structures/responses.dto'; +import { create, Message, Whatsapp } from 'venom-bot'; +import { + WAEvents, + WhatsappEngine, + WhatsappStatus, +} from '../structures/enums.dto'; +import { NotImplementedByEngineError } from './exceptions'; +import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception'; +import { QR } from './QR'; export class WhatsappSessionVenomCore extends WhatsappSession { - engine = WhatsappEngine.NOWEB + engine = WhatsappEngine.NOWEB; - whatsapp: Whatsapp; - private qr: QR + whatsapp: Whatsapp; + private qr: QR; - public constructor(config) { - super(config); - this.qr = new QR() + public constructor(config) { + super(config); + this.qr = new QR(); + } + + protected buildClient() { + return create(this.name, this.getCatchQR(), undefined, { + headless: true, + devtools: false, + useChrome: true, + debug: false, + logQR: true, + browserArgs: this.getBrowserArgsForPuppeteer(), + autoClose: 60000, + puppeteerOptions: {}, + multidevice: true, + }); + } + + protected getCatchQR() { + return (base64Qrimg, asciiQR, attempts, urlCode) => { + this.qr.save(base64Qrimg); + this.status = WhatsappStatus.SCAN_QR_CODE; + this.log.debug('Number of attempts to read the qrcode: ', attempts); + this.log.log('Terminal qrcode:'); + // Log QR image in console without this.log to make it pretty + console.log(asciiQR); + }; + } + + async start() { + try { + this.whatsapp = await this.buildClient(); + } catch (error) { + this.status = WhatsappStatus.FAILED; + this.log.error(error); + this.qr.save(''); + return; } - protected buildClient() { - return create(this.name, - this.getCatchQR(), - undefined, - { - headless: true, - devtools: false, - useChrome: true, - debug: false, - logQR: true, - browserArgs: this.getBrowserArgsForPuppeteer(), - autoClose: 60000, - puppeteerOptions: {}, - multidevice: true, - } - ) + this.status = WhatsappStatus.WORKING; + this.events.emit(WAHAInternalEvent.engine_start); + return this; + } + + stop() { + return this.whatsapp.close(); + } + + subscribe(event: WAEvents | string, handler: (message) => void) { + if (event === WAEvents.MESSAGE) { + return this.whatsapp.onMessage((message: Message) => + this.processIncomingMessage(message).then(handler), + ); + } else if (event === WAEvents.MESSAGE_ANY) { + return this.whatsapp.onAnyMessage((message: Message) => + this.processIncomingMessage(message).then(handler), + ); + } else if (event === WAEvents.STATE_CHANGE) { + return this.whatsapp.onStateChange(handler); + } else if (event === WAEvents.MESSAGE_ACK) { + return this.whatsapp.onAck(handler); + } else if (event === WAEvents.GROUP_JOIN) { + return this.whatsapp.onAddedToGroup(handler); + } else { + throw new NotImplementedByEngineError( + `Engine does not support webhook event: ${event}`, + ); } + } - protected getCatchQR() { - return (base64Qrimg, asciiQR, attempts, urlCode) => { - this.qr.save(base64Qrimg) - this.status = WhatsappStatus.SCAN_QR_CODE - this.log.debug('Number of attempts to read the qrcode: ', attempts); - this.log.log('Terminal qrcode:'); - // Log QR image in console without this.log to make it pretty - console.log(asciiQR); - }; + /** + * START - Methods for API + */ + getScreenshot(): Promise { + if (this.status === WhatsappStatus.STARTING) { + throw new UnprocessableEntityException( + `The session is starting, please try again after few seconds`, + ); + } else if (this.status === WhatsappStatus.SCAN_QR_CODE) { + return Promise.resolve(this.qr.get()); + } else if (this.status === WhatsappStatus.WORKING) { + return this.whatsapp.page.screenshot(); + } else { + throw new UnprocessableEntityException(`Unknown status - ${this.status}`); } + } - async start() { - try { - this.whatsapp = await this.buildClient() - } catch (error) { - this.status = WhatsappStatus.FAILED - this.log.error(error) - this.qr.save("") - return - } - - this.status = WhatsappStatus.WORKING - this.events.emit(WAHAInternalEvent.engine_start) - return this + async checkNumberStatus( + request: CheckNumberStatusQuery, + ): Promise { + 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; } + } + sendContactVCard(request: MessageContactVcardRequest) { + return this.whatsapp.sendContactVcard( + request.chatId, + request.contactsId, + request.name, + ); + } - stop() { - return this.whatsapp.close() + 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.sendSeen(chat.chatId); + } + + sendTextButtons(request: MessageTextButtonsRequest) { + const buttons = request.buttons.map((button: Button) => { + return { + buttonId: button.id, + buttonText: { + displayText: button.text, + }, + }; + }); + return this.whatsapp.sendButtons( + this.ensureSuffix(request.chatId), + request.title, + buttons, + request.footer, + ); + } + + startTyping(chat: ChatRequest) { + return this.whatsapp.startTyping(chat.chatId); + } + + stopTyping(chat: ChatRequest) { + return this.whatsapp.stopTyping(chat.chatId); + } + + async getMessages(query: GetMessageQuery) { + const messages = await this.whatsapp.getAllMessagesInChat( + query.chatId, + true, + false, + ); + // Go over messages, download media, and convert to right format. + const result = []; + for (const [count, message] of messages.entries()) { + if (count > query.limit) { + // Have enough in the list, stop processing + break; + } + result.push(await this.processIncomingMessage(message)); } + return result; + } - subscribe(event: WAEvents | string, handler: (message) => void) { - if (event === WAEvents.MESSAGE) { - return this.whatsapp.onMessage((message: Message) => this.processIncomingMessage(message).then(handler)) - } else if (event === WAEvents.MESSAGE_ANY) { - return this.whatsapp.onAnyMessage((message: Message) => this.processIncomingMessage(message).then(handler)) - } else if (event === WAEvents.STATE_CHANGE) { - return this.whatsapp.onStateChange(handler) - } else if (event === WAEvents.MESSAGE_ACK) { - return this.whatsapp.onAck(handler) - } else if (event === WAEvents.GROUP_JOIN) { - return this.whatsapp.onAddedToGroup(handler) - } else { - throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`) - } + setReaction(request: MessageReactionRequest) { + throw new NotImplementedByEngineError(); + } + + /** + * END - Methods for API + */ + + protected async downloadAndDecryptMedia(message: Message) { + if (!message.isMMS || !message.isMedia) { + return message; } + // @ts-ignore + message.mediaUrl = await this.storage.save(message.id, '', undefined); + return message; + } + private processIncomingMessage(message: Message) { + return this.downloadAndDecryptMedia(message).then(this.toWAMessage); + } - /** - * START - Methods for API - */ - getScreenshot(): Promise { - if (this.status === WhatsappStatus.STARTING) { - throw new UnprocessableEntityException(`The session is starting, please try again after few seconds`); - } else if (this.status === WhatsappStatus.SCAN_QR_CODE) { - return Promise.resolve(this.qr.get()) - } else if (this.status === WhatsappStatus.WORKING) { - return this.whatsapp.page.screenshot() - } else { - throw new UnprocessableEntityException(`Unknown status - ${this.status}`); - } - } - - async checkNumberStatus(request: CheckNumberStatusQuery): Promise { - 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 - } - } - - sendContactVCard(request: MessageContactVcardRequest) { - return this.whatsapp.sendContactVcard(request.chatId, request.contactsId, request.name) - } - - 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.sendSeen(chat.chatId) - } - - sendTextButtons(request: MessageTextButtonsRequest) { - const buttons = request.buttons.map((button: Button) => { - return { - buttonId: button.id, - buttonText: { - displayText: button.text - } - } - }) - return this.whatsapp.sendButtons(this.ensureSuffix(request.chatId), request.title, buttons, request.footer) - } - - startTyping(chat: ChatRequest) { - return this.whatsapp.startTyping(chat.chatId) - } - - stopTyping(chat: ChatRequest) { - return this.whatsapp.stopTyping(chat.chatId) - } - - async getMessages(query: GetMessageQuery) { - const messages = await this.whatsapp.getAllMessagesInChat(query.chatId, true, false) - // Go over messages, download media, and convert to right format. - const result = [] - for (const [count, message] of messages.entries()) { - if (count > query.limit) { - // Have enough in the list, stop processing - break - } - result.push(await this.processIncomingMessage(message)) - } - return result - } - - setReaction(request: MessageReactionRequest) { - throw new NotImplementedByEngineError() - } - - /** - * END - Methods for API - */ - - protected async downloadAndDecryptMedia(message: Message) { - if (!message.isMMS || !message.isMedia) { - return message - } - // @ts-ignore - message.mediaUrl = await this.storage.save(message.id, "", undefined) - return message - } - - private processIncomingMessage(message: Message) { - return this.downloadAndDecryptMedia(message).then(this.toWAMessage) - } - - protected toWAMessage(message: Message): Promise { - // @ts-ignore - return Promise.resolve({ - id: message.id, - timestamp: message.timestamp, - from: message.from, - fromMe: message.fromMe, - to: message.to, - body: message.body, - // @ts-ignore - hasMedia: Boolean(message.mediaUrl), - // @ts-ignore - mediaUrl: message.mediaUrl, - // @ts-ignore - ack: message.ack, - location: undefined, - vCards: undefined, - _data: message, - }) - } + protected toWAMessage(message: Message): Promise { + // @ts-ignore + return Promise.resolve({ + id: message.id, + timestamp: message.timestamp, + from: message.from, + fromMe: message.fromMe, + to: message.to, + body: message.body, + // @ts-ignore + hasMedia: Boolean(message.mediaUrl), + // @ts-ignore + mediaUrl: message.mediaUrl, + // @ts-ignore + ack: message.ack, + location: undefined, + vCards: undefined, + _data: message, + }); + } } diff --git a/src/core/session.webjs.core.ts b/src/core/session.webjs.core.ts index c1e5ae42..1edb301c 100644 --- a/src/core/session.webjs.core.ts +++ b/src/core/session.webjs.core.ts @@ -1,351 +1,416 @@ -import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception"; -import {Buttons, Chat, Client, Contact, Events, GroupChat, Location, Message} from "whatsapp-web.js"; -import {Message as MessageInstance} from "whatsapp-web.js/src/structures" -import { WAEvents, WhatsappEngine, WhatsappStatus } from "../structures/enums.dto"; -import {WAHAInternalEvent, WhatsappSession} from "./abc/session.abc"; -import {WAMessage, WANumberExistResult} from "../structures/responses.dto"; +import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception'; import { - Button, - ChatRequest, - CheckNumberStatusQuery, - GetMessageQuery, - MessageContactVcardRequest, - MessageFileRequest, - MessageImageRequest, - MessageLinkPreviewRequest, - MessageLocationRequest, - MessageReactionRequest, - MessageReplyRequest, - MessageTextButtonsRequest, - MessageTextRequest, - MessageVoiceRequest, SendSeenRequest -} from "../structures/chatting.dto"; -import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions"; -import {ContactQuery, ContactRequest} from "../structures/contacts.dto"; -import {CreateGroupRequest, ParticipantsRequest} from "../structures/groups.dto"; + Buttons, + Chat, + Client, + Contact, + Events, + GroupChat, + Location, + Message, +} from 'whatsapp-web.js'; +import { Message as MessageInstance } from 'whatsapp-web.js/src/structures'; +import { + WAEvents, + WhatsappEngine, + WhatsappStatus, +} from '../structures/enums.dto'; +import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc'; +import { WAMessage, WANumberExistResult } from '../structures/responses.dto'; +import { + Button, + ChatRequest, + CheckNumberStatusQuery, + GetMessageQuery, + MessageContactVcardRequest, + MessageFileRequest, + MessageImageRequest, + MessageLinkPreviewRequest, + MessageLocationRequest, + MessageReactionRequest, + MessageReplyRequest, + MessageTextButtonsRequest, + MessageTextRequest, + MessageVoiceRequest, + SendSeenRequest, +} from '../structures/chatting.dto'; +import { + AvailableInPlusVersion, + NotImplementedByEngineError, +} from './exceptions'; +import { ContactQuery, ContactRequest } from '../structures/contacts.dto'; +import { + CreateGroupRequest, + ParticipantsRequest, +} from '../structures/groups.dto'; // eslint-disable-next-line @typescript-eslint/no-var-requires const qrcode = require('qrcode-terminal'); - export class WhatsappSessionWebJSCore extends WhatsappSession { - engine = WhatsappEngine.NOWEB + engine = WhatsappEngine.NOWEB; - whatsapp: Client; + whatsapp: Client; - protected buildClient() { - return new Client({ - puppeteer: { - headless: true, - executablePath: this.getBrowserExecutablePath(), - args: this.getBrowserArgsForPuppeteer(), - } - }); - } - - async start() { - this.whatsapp = this.buildClient() - - this.whatsapp.initialize().catch(error => { - this.status = WhatsappStatus.FAILED - this.log.error(error) - return - }); - - // Connect events - this.whatsapp.on(Events.QR_RECEIVED, (qr) => { - qrcode.generate(qr, {small: true}); - this.status = WhatsappStatus.SCAN_QR_CODE - }); - - this.whatsapp.on(Events.AUTHENTICATED, () => { - this.status = WhatsappStatus.WORKING - this.log.log(`Session '${this.name}' has been authenticated!`) - }); - this.events.emit(WAHAInternalEvent.engine_start) - return this - } - - stop() { - return this.whatsapp.destroy() - } - - /** - * START - Methods for API - */ - async getScreenshot(): Promise { - if (this.status === WhatsappStatus.FAILED) { - throw new UnprocessableEntityException(`The session under FAILED status. Please try to restart it.`); - } - return await this.whatsapp.pupPage.screenshot() - } - - async checkNumberStatus(request: CheckNumberStatusQuery): Promise { - const exist = await this.whatsapp.isRegisteredUser(this.ensureSuffix(request.phone)) - return {numberExists: exist} - } - - sendText(request: MessageTextRequest) { - return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), request.text) - } - - sendTextButtons(request: MessageTextButtonsRequest) { - const buttons = request.buttons.map((button: Button) => { - return { - id: button.id, - body: button.text, - } - }) - const message = new Buttons("", buttons, request.title, request.footer) - return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), message) - } - - sendContactVCard(request: MessageContactVcardRequest) { - throw new NotImplementedByEngineError() - } - - reply(request: MessageReplyRequest) { - const options = { - quotedMessageId: request.reply_to, - }; - return this.whatsapp.sendMessage(request.chatId, request.text, options) - } - - sendImage(request: MessageImageRequest) { - throw new AvailableInPlusVersion() - } - - sendFile(request: MessageFileRequest) { - throw new AvailableInPlusVersion() - } - - sendVoice(request: MessageVoiceRequest) { - throw new AvailableInPlusVersion() - } - - async sendLocation(request: MessageLocationRequest) { - const location = new Location( - request.latitude, - request.longitude, - request.title, - ) - return this.whatsapp.sendMessage(request.chatId, location) - } - - sendLinkPreview(request: MessageLinkPreviewRequest) { - throw new NotImplementedByEngineError() - } - - - async sendSeen(request: SendSeenRequest) { - const chat: Chat = await this.whatsapp.getChatById(request.chatId) - await chat.sendSeen() - } - - async startTyping(request: ChatRequest) { - const chat: Chat = await this.whatsapp.getChatById(request.chatId) - await chat.sendStateTyping() - } - - async stopTyping(request: ChatRequest) { - const chat: Chat = await this.whatsapp.getChatById(request.chatId) - await chat.clearState() - } - - async getMessages(query: GetMessageQuery) { - const chat: Chat = await this.whatsapp.getChatById(this.ensureSuffix(query.chatId)) - const messages = await chat.fetchMessages({ - limit: query.limit, - }) - // Go over messages, download media, and convert to right format. - const result = [] - for (const message of messages) { - result.push(await this.processIncomingMessage(message)) - } - return result - } - - async setReaction(request: MessageReactionRequest) { - const messageId = this.deserializeId(request.messageId) - // Recreate instance to react on it - const message = new MessageInstance(this.whatsapp) - message.id = messageId - message._data = {id: messageId} - - return message.react(request.reaction) - } - - - /** - * Contacts methods - */ - getContact(query: ContactQuery) { - return this.whatsapp.getContactById(this.ensureSuffix(query.contactId)).then(this.toWAContact) - } - - getContacts() { - return this.whatsapp.getContacts().then(contacts => contacts.map(this.toWAContact)) - } - - public async getContactAbout(query: ContactQuery) { - const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId)) - return {"about": await contact.getAbout()} - } - - public async getContactProfilePicture(query: ContactQuery) { - const contact = await this.whatsapp.getContactById(this.ensureSuffix(query.contactId)) - return {"profilePictureURL": await contact.getProfilePicUrl()} - } - - public async blockContact(request: ContactRequest) { - const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId)) - await contact.block() - } - - public async unblockContact(request: ContactRequest) { - const contact = await this.whatsapp.getContactById(this.ensureSuffix(request.contactId)) - await contact.unblock() - } - - /** - * Group methods - */ - public createGroup(request: CreateGroupRequest) { - const participantIds = request.participants.map(participant => participant.id) - return this.whatsapp.createGroup(request.name, participantIds) - } - - public getGroups() { - return this.whatsapp.getChats().then(chats => chats.filter(chat => chat.isGroup)) - } - - public getGroup(id) { - return this.whatsapp.getChatById(id) - } - - public async deleteGroup(id) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.delete() - } - - public async leaveGroup(id) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.leave() - } - - public async setDescription(id, description) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.setDescription(description) - } - - public async setSubject(id, subject) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.setSubject(subject) - } - - public async getInviteCode(id): Promise { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.getInviteCode() - } - - public async revokeInviteCode(id): Promise { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - await groupChat.revokeInvite() - return groupChat.getInviteCode() - } - - public async getParticipants(id) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - return groupChat.participants - } - - public async addParticipants(id, request: ParticipantsRequest) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - const participantIds = request.participants.map(participant => participant.id) - return groupChat.addParticipants(participantIds) - } - - public async removeParticipants(id, request: ParticipantsRequest) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - const participantIds = request.participants.map(participant => participant.id) - return groupChat.removeParticipants(participantIds) - } - - public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - const participantIds = request.participants.map(participant => participant.id) - return groupChat.promoteParticipants(participantIds) - } - - public async demoteParticipantsToUser(id, request: ParticipantsRequest) { - const groupChat = await this.whatsapp.getChatById(id) as GroupChat - const participantIds = request.participants.map(participant => participant.id) - return groupChat.demoteParticipants(participantIds) - } - - /** - * END - Methods for API - */ - - subscribe(event, handler) { - if (event === WAEvents.MESSAGE) { - this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) => this.processIncomingMessage(message).then(handler)) - } else if (event === WAEvents.MESSAGE_ANY) { - this.whatsapp.on(Events.MESSAGE_CREATE, (message) => this.processIncomingMessage(message).then(handler)) - } else if (event === WAEvents.STATE_CHANGE) { - this.whatsapp.on(Events.STATE_CHANGED, handler) - } else if (event === WAEvents.MESSAGE_ACK) { - // We do not download media here - this.whatsapp.on(Events.MESSAGE_ACK, (message) => this.toWAMessage(message).then(handler)) - } else if (event === WAEvents.GROUP_JOIN) { - this.whatsapp.on(Events.GROUP_JOIN, handler) - } else if (event === WAEvents.GROUP_LEAVE) { - this.whatsapp.on(Events.GROUP_LEAVE, handler) - } else { - throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`) - } - } - - private processIncomingMessage(message: Message) { - return this.downloadMedia(message).then(this.toWAMessage) - } - - protected toWAMessage(message: Message): Promise { - // @ts-ignore - return Promise.resolve({ - id: message.id._serialized, - timestamp: message.timestamp, - from: message.from, - fromMe: message.fromMe, - to: message.to, - body: message.body, - // @ts-ignore - hasMedia: Boolean(message.mediaUrl), - // @ts-ignore - mediaUrl: message.mediaUrl, - // @ts-ignore - ack: message.ack, - location: message.location, - vCards: message.vCards, - _data: message.rawData, - }) - } - - protected toWAContact(contact: Contact) { - // @ts-ignore - contact.id = contact.id._serialized - return contact - } - - protected async downloadMedia(message: Message) { - if (!message.hasMedia) { - return message - } - - // @ts-ignore - message.mediaUrl = await this.storage.save(message.id._serialized, "", undefined) - return message + protected buildClient() { + return new Client({ + puppeteer: { + headless: true, + executablePath: this.getBrowserExecutablePath(), + args: this.getBrowserArgsForPuppeteer(), + }, + }); + } + + async start() { + this.whatsapp = this.buildClient(); + + this.whatsapp.initialize().catch((error) => { + this.status = WhatsappStatus.FAILED; + this.log.error(error); + return; + }); + + // Connect events + this.whatsapp.on(Events.QR_RECEIVED, (qr) => { + qrcode.generate(qr, { small: true }); + this.status = WhatsappStatus.SCAN_QR_CODE; + }); + + this.whatsapp.on(Events.AUTHENTICATED, () => { + this.status = WhatsappStatus.WORKING; + this.log.log(`Session '${this.name}' has been authenticated!`); + }); + this.events.emit(WAHAInternalEvent.engine_start); + return this; + } + + stop() { + return this.whatsapp.destroy(); + } + + /** + * START - Methods for API + */ + async getScreenshot(): Promise { + if (this.status === WhatsappStatus.FAILED) { + throw new UnprocessableEntityException( + `The session under FAILED status. Please try to restart it.`, + ); + } + return await this.whatsapp.pupPage.screenshot(); + } + + async checkNumberStatus( + request: CheckNumberStatusQuery, + ): Promise { + const exist = await this.whatsapp.isRegisteredUser( + this.ensureSuffix(request.phone), + ); + return { numberExists: exist }; + } + + sendText(request: MessageTextRequest) { + return this.whatsapp.sendMessage( + this.ensureSuffix(request.chatId), + request.text, + ); + } + + sendTextButtons(request: MessageTextButtonsRequest) { + const buttons = request.buttons.map((button: Button) => { + return { + id: button.id, + body: button.text, + }; + }); + const message = new Buttons('', buttons, request.title, request.footer); + return this.whatsapp.sendMessage( + this.ensureSuffix(request.chatId), + message, + ); + } + + sendContactVCard(request: MessageContactVcardRequest) { + throw new NotImplementedByEngineError(); + } + + reply(request: MessageReplyRequest) { + const options = { + quotedMessageId: request.reply_to, + }; + return this.whatsapp.sendMessage(request.chatId, request.text, options); + } + + sendImage(request: MessageImageRequest) { + throw new AvailableInPlusVersion(); + } + + sendFile(request: MessageFileRequest) { + throw new AvailableInPlusVersion(); + } + + sendVoice(request: MessageVoiceRequest) { + throw new AvailableInPlusVersion(); + } + + async sendLocation(request: MessageLocationRequest) { + const location = new Location( + request.latitude, + request.longitude, + request.title, + ); + return this.whatsapp.sendMessage(request.chatId, location); + } + + sendLinkPreview(request: MessageLinkPreviewRequest) { + throw new NotImplementedByEngineError(); + } + + async sendSeen(request: SendSeenRequest) { + const chat: Chat = await this.whatsapp.getChatById(request.chatId); + await chat.sendSeen(); + } + + async startTyping(request: ChatRequest) { + const chat: Chat = await this.whatsapp.getChatById(request.chatId); + await chat.sendStateTyping(); + } + + async stopTyping(request: ChatRequest) { + const chat: Chat = await this.whatsapp.getChatById(request.chatId); + await chat.clearState(); + } + + async getMessages(query: GetMessageQuery) { + const chat: Chat = await this.whatsapp.getChatById( + this.ensureSuffix(query.chatId), + ); + const messages = await chat.fetchMessages({ + limit: query.limit, + }); + // Go over messages, download media, and convert to right format. + const result = []; + for (const message of messages) { + result.push(await this.processIncomingMessage(message)); + } + return result; + } + + async setReaction(request: MessageReactionRequest) { + const messageId = this.deserializeId(request.messageId); + // Recreate instance to react on it + const message = new MessageInstance(this.whatsapp); + message.id = messageId; + message._data = { id: messageId }; + + return message.react(request.reaction); + } + + /** + * Contacts methods + */ + getContact(query: ContactQuery) { + return this.whatsapp + .getContactById(this.ensureSuffix(query.contactId)) + .then(this.toWAContact); + } + + getContacts() { + return this.whatsapp + .getContacts() + .then((contacts) => contacts.map(this.toWAContact)); + } + + public async getContactAbout(query: ContactQuery) { + const contact = await this.whatsapp.getContactById( + this.ensureSuffix(query.contactId), + ); + return { about: await contact.getAbout() }; + } + + public async getContactProfilePicture(query: ContactQuery) { + const contact = await this.whatsapp.getContactById( + this.ensureSuffix(query.contactId), + ); + return { profilePictureURL: await contact.getProfilePicUrl() }; + } + + public async blockContact(request: ContactRequest) { + const contact = await this.whatsapp.getContactById( + this.ensureSuffix(request.contactId), + ); + await contact.block(); + } + + public async unblockContact(request: ContactRequest) { + const contact = await this.whatsapp.getContactById( + this.ensureSuffix(request.contactId), + ); + await contact.unblock(); + } + + /** + * Group methods + */ + public createGroup(request: CreateGroupRequest) { + const participantIds = request.participants.map( + (participant) => participant.id, + ); + return this.whatsapp.createGroup(request.name, participantIds); + } + + public getGroups() { + return this.whatsapp + .getChats() + .then((chats) => chats.filter((chat) => chat.isGroup)); + } + + public getGroup(id) { + return this.whatsapp.getChatById(id); + } + + public async deleteGroup(id) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.delete(); + } + + public async leaveGroup(id) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.leave(); + } + + public async setDescription(id, description) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.setDescription(description); + } + + public async setSubject(id, subject) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.setSubject(subject); + } + + public async getInviteCode(id): Promise { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.getInviteCode(); + } + + public async revokeInviteCode(id): Promise { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + await groupChat.revokeInvite(); + return groupChat.getInviteCode(); + } + + public async getParticipants(id) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + return groupChat.participants; + } + + public async addParticipants(id, request: ParticipantsRequest) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + const participantIds = request.participants.map( + (participant) => participant.id, + ); + return groupChat.addParticipants(participantIds); + } + + public async removeParticipants(id, request: ParticipantsRequest) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + const participantIds = request.participants.map( + (participant) => participant.id, + ); + return groupChat.removeParticipants(participantIds); + } + + public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + const participantIds = request.participants.map( + (participant) => participant.id, + ); + return groupChat.promoteParticipants(participantIds); + } + + public async demoteParticipantsToUser(id, request: ParticipantsRequest) { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + const participantIds = request.participants.map( + (participant) => participant.id, + ); + return groupChat.demoteParticipants(participantIds); + } + + /** + * END - Methods for API + */ + + subscribe(event, handler) { + if (event === WAEvents.MESSAGE) { + this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) => + this.processIncomingMessage(message).then(handler), + ); + } else if (event === WAEvents.MESSAGE_ANY) { + this.whatsapp.on(Events.MESSAGE_CREATE, (message) => + this.processIncomingMessage(message).then(handler), + ); + } else if (event === WAEvents.STATE_CHANGE) { + this.whatsapp.on(Events.STATE_CHANGED, handler); + } else if (event === WAEvents.MESSAGE_ACK) { + // We do not download media here + this.whatsapp.on(Events.MESSAGE_ACK, (message) => + this.toWAMessage(message).then(handler), + ); + } else if (event === WAEvents.GROUP_JOIN) { + this.whatsapp.on(Events.GROUP_JOIN, handler); + } else if (event === WAEvents.GROUP_LEAVE) { + this.whatsapp.on(Events.GROUP_LEAVE, handler); + } else { + throw new NotImplementedByEngineError( + `Engine does not support webhook event: ${event}`, + ); + } + } + + private processIncomingMessage(message: Message) { + return this.downloadMedia(message).then(this.toWAMessage); + } + + protected toWAMessage(message: Message): Promise { + // @ts-ignore + return Promise.resolve({ + id: message.id._serialized, + timestamp: message.timestamp, + from: message.from, + fromMe: message.fromMe, + to: message.to, + body: message.body, + // @ts-ignore + hasMedia: Boolean(message.mediaUrl), + // @ts-ignore + mediaUrl: message.mediaUrl, + // @ts-ignore + ack: message.ack, + location: message.location, + vCards: message.vCards, + _data: message.rawData, + }); + } + + protected toWAContact(contact: Contact) { + // @ts-ignore + contact.id = contact.id._serialized; + return contact; + } + + protected async downloadMedia(message: Message) { + if (!message.hasMedia) { + return message; } + // @ts-ignore + message.mediaUrl = await this.storage.save( + message.id._serialized, + '', + undefined, + ); + return message; + } } - diff --git a/src/core/storage.core.ts b/src/core/storage.core.ts index 1eed4f84..cd70f6bd 100644 --- a/src/core/storage.core.ts +++ b/src/core/storage.core.ts @@ -1,12 +1,18 @@ -import { DOCS_URL } from "./exceptions"; -import { LocalSessionStorage, MediaStorage } from "./abc/storage.abc"; -import * as path from "path"; -import * as fs from "fs"; -import * as os from "os"; +import { DOCS_URL } from './exceptions'; +import { LocalSessionStorage, MediaStorage } from './abc/storage.abc'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; export class MediaStorageCore implements MediaStorage { - async save(messageId: string, mimetype: string, buffer: Buffer): Promise { - return Promise.resolve(`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`); + async save( + messageId: string, + mimetype: string, + buffer: Buffer, + ): Promise { + return Promise.resolve( + `Media attachment's available only in WAHA Plus version. ${DOCS_URL}`, + ); } } @@ -15,15 +21,15 @@ export class SessionStorageCore extends LocalSessionStorage { constructor(engine: string) { super(engine); - this.sessionsFolder = path.join(os.tmpdir(), 'waha-') + this.sessionsFolder = path.join(os.tmpdir(), 'waha-'); } - get engineFolder(){ + get engineFolder() { return path.join(this.sessionsFolder, this.engine); } init() { - fs.mkdirSync(this.engineFolder, {recursive: true}); + fs.mkdirSync(this.engineFolder, { recursive: true }); } getFolderPath(name: string): string { @@ -36,8 +42,9 @@ export class SessionStorageCore extends LocalSessionStorage { } getAll(): string[] { - return fs.readdirSync(this.engineFolder, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory()) - .map(dirent => dirent.name); + return fs + .readdirSync(this.engineFolder, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); } } diff --git a/src/core/swagger.module.core.ts b/src/core/swagger.module.core.ts index 61d4c4b2..8de7e1ac 100644 --- a/src/core/swagger.module.core.ts +++ b/src/core/swagger.module.core.ts @@ -1,72 +1,66 @@ -import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger"; -import {INestApplication} from "@nestjs/common"; -import {VERSION} from "../version"; -import { WhatsappConfigService } from "../config.service"; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { INestApplication } from '@nestjs/common'; +import { VERSION } from '../version'; +import { WhatsappConfigService } from '../config.service'; export class SwaggerModuleCore { - configure(app: INestApplication) { - this.setUpAuth(app) - const builder = new DocumentBuilder() + configure(app: INestApplication) { + this.setUpAuth(app); + const builder = new DocumentBuilder(); - builder - .setTitle('WAHA - WhatsApp HTTP API') - .setDescription('WhatsApp HTTP API that you can configure in a click!') - .setExternalDoc("Documentation", "https://waha.devlike.pro/") - .setVersion(VERSION.version) - .addTag('sessions', 'Control WhatsApp sessions') - .addTag('screenshot', 'Get screenshot of WhatsApp and show QR code') - .addTag('chatting', 'Chat methods') - .addTag( - 'contacts', - `Contacts methods.
+ builder + .setTitle('WAHA - WhatsApp HTTP API') + .setDescription('WhatsApp HTTP API that you can configure in a click!') + .setExternalDoc('Documentation', 'https://waha.devlike.pro/') + .setVersion(VERSION.version) + .addTag('sessions', 'Control WhatsApp sessions') + .addTag('screenshot', 'Get screenshot of WhatsApp and show QR code') + .addTag('chatting', 'Chat methods') + .addTag( + 'contacts', + `Contacts methods.
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.
- 'E.g: \`12312312310\` OR \`12312312310@c.us\`
` - ) - .addTag( - 'groups', - `Groups methods.
` - ) - .addTag('other', 'Other methods') - .addApiKey({ - type: 'apiKey', - description: 'Your secret api key', - name: 'X-Api-Key' - } - ) + 'E.g: \`12312312310\` OR \`12312312310@c.us\`
`, + ) + .addTag('groups', `Groups methods.
`) + .addTag('other', 'Other methods') + .addApiKey({ + type: 'apiKey', + description: 'Your secret api key', + name: 'X-Api-Key', + }); - const config = app.get(WhatsappConfigService) - if (config.getSwaggerAdvancedConfigEnabled()){ - builder.addServer( - "{protocol}://{host}:{port}/{baseUrl}", - "", - { - "protocol": { - "default": "http", - "enum": ["http", "https"], - "description": "The protocol used to access the server." - }, - "host": { - "default": config.hostname, - "description": "The hostname or IP address of the server." - }, - "port": { - "default": config.port, - "description": "The port number on which the server is listening for requests" - }, - "baseUrl": { - "default": "", - "description": "The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.", - } - } - ) - } - - const options = builder.build() - const document = SwaggerModule.createDocument(app, options); - SwaggerModule.setup('', app, document); + const config = app.get(WhatsappConfigService); + if (config.getSwaggerAdvancedConfigEnabled()) { + builder.addServer('{protocol}://{host}:{port}/{baseUrl}', '', { + protocol: { + default: 'http', + enum: ['http', 'https'], + description: 'The protocol used to access the server.', + }, + host: { + default: config.hostname, + description: 'The hostname or IP address of the server.', + }, + port: { + default: config.port, + description: + 'The port number on which the server is listening for requests', + }, + baseUrl: { + default: '', + description: + 'The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.', + }, + }); } - protected setUpAuth(app: INestApplication) { - return undefined - } + const options = builder.build(); + const document = SwaggerModule.createDocument(app, options); + SwaggerModule.setup('', app, document); + } + + protected setUpAuth(app: INestApplication) { + return undefined; + } } diff --git a/src/core/webhooks.core.ts b/src/core/webhooks.core.ts index 02b4a5fe..ed754e88 100644 --- a/src/core/webhooks.core.ts +++ b/src/core/webhooks.core.ts @@ -1,81 +1,83 @@ -import {WAHAInternalEvent, WhatsappSession} from "./abc/session.abc"; -import {ConsoleLogger} from "@nestjs/common"; -import {WAEvents} from "../structures/enums.dto"; -import {WAWebhook} from "../structures/responses.dto"; -import {NotImplementedByEngineError} from "./exceptions"; -import {WebhookConductor} from "./abc/webhooks.abc"; +import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc'; +import { ConsoleLogger } from '@nestjs/common'; +import { WAEvents } from '../structures/enums.dto'; +import { WAWebhook } from '../structures/responses.dto'; +import { NotImplementedByEngineError } from './exceptions'; +import { WebhookConductor } from './abc/webhooks.abc'; import request = require('request'); - export class WebhookConductorCore implements WebhookConductor { - constructor(protected log: ConsoleLogger, private readonly url, private readonly events: WAEvents[] | string[]) { - this.url = url - this.events = this.getSuitableEvents(events) - this.log = log + constructor( + protected log: ConsoleLogger, + private readonly url, + private readonly events: WAEvents[] | string[], + ) { + this.url = url; + this.events = this.getSuitableEvents(events); + this.log = log; + } + + private getSuitableEvents(events: WAEvents[] | string[]) { + const allEvents = Object.values(WAEvents); + + // Enable all events if * in the events + // @ts-ignore + if (events.includes('*')) { + return allEvents; } - private getSuitableEvents(events: WAEvents[] | string[]) { - const allEvents = Object.values(WAEvents) + // Get only known events, log and ignore others + const rightEvents = []; + for (const event of events) { + // @ts-ignore + if (!allEvents.includes(event)) { + this.log.error(`Unknown event for webhook: '${event}'`); + continue; + } + rightEvents.push(event); + } + return rightEvents; + } - // Enable all events if * in the events - // @ts-ignore - if (events.includes("*")) { - return allEvents + public configure(session: WhatsappSession) { + this.log.log('Configuring webhooks...'); + for (const event of this.events) { + try { + session.subscribe(event, (data: any) => + this.callWebhook(event, session, data, this.url), + ); + } catch (error) { + if (error instanceof NotImplementedByEngineError) { + this.log.error(error); + } else { + throw error; } - - // Get only known events, log and ignore others - const rightEvents = [] - for (const event of events) { - // @ts-ignore - if (!allEvents.includes(event)) { - this.log.error(`Unknown event for webhook: '${event}'`) - continue - } - rightEvents.push(event) - } - return rightEvents + } + this.log.log(`Event '${event}' is enabled for url: ${this.url}`); } + this.log.log('Webhooks were configured.'); + } - public configure(session: WhatsappSession) { - this.log.log('Configuring webhooks...') - for (const event of this.events) { - try { - session.subscribe(event, (data: any) => this.callWebhook(event, session, data, this.url)) - } catch (error) { - if (error instanceof NotImplementedByEngineError) { - this.log.error(error) - } else { - throw error - } - } - this.log.log(`Event '${event}' is enabled for url: ${this.url}`) - } - this.log.log('Webhooks were configured.') - } + public callWebhook(event, session: WhatsappSession, data: any, url) { + const json: WAWebhook = { + event: event, + session: session.name, + payload: data, + engine: session.engine, + }; + this.log.log(`Sending POST to ${url}...`); + this.log.debug(`POST DATA: ${JSON.stringify(json)}`); + this.post(json, url); + } - public callWebhook(event, session: WhatsappSession, data: any, url) { - const json: WAWebhook = { - event: event, - session: session.name, - payload: data, - engine: session.engine, - } - this.log.log(`Sending POST to ${url}...`) - this.log.debug(`POST DATA: ${JSON.stringify(json)}`) - this.post(json, url) - } - - protected post(json, url) { - request.post( - url, - {json: json}, - (error, res, body) => { - if (error) { - this.log.error(error) - return - } - this.log.log(`POST request was sent with status code: ${res.statusCode}`) - this.log.verbose(`Response: ${JSON.stringify(body)}`) - }) - } + protected post(json, url) { + request.post(url, { json: json }, (error, res, body) => { + if (error) { + this.log.error(error); + return; + } + this.log.log(`POST request was sent with status code: ${res.statusCode}`); + this.log.verbose(`Response: ${JSON.stringify(body)}`); + }); + } } diff --git a/src/helpers.ts b/src/helpers.ts index f4520eaf..da1b63df 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -2,13 +2,19 @@ export function parseBool(value: any): boolean { if (!value) { return false; } - if (value.toLowerCase() === "true" || value.toLowerCase() === "1") { + if (value.toLowerCase() === 'true' || value.toLowerCase() === '1') { return true; } - if (value.toLowerCase() === "false" || value.toLowerCase() === "0") { + if (value.toLowerCase() === 'false' || value.toLowerCase() === '0') { return false; } - console.error("parseBool got an unexpected value:", value, "(accepted values : \"true\", \"false\")"); - throw new Error("Error: parseBool got unexpected value - use \"true\" or \"false\" values"); + console.error( + 'parseBool got an unexpected value:', + value, + '(accepted values : "true", "false")', + ); + throw new Error( + 'Error: parseBool got unexpected value - use "true" or "false" values', + ); } diff --git a/src/main.ts b/src/main.ts index a7529b8a..f70b2b40 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,49 +1,53 @@ -import {NestFactory} from '@nestjs/core'; -import {WhatsappConfigService} from "./config.service"; -import {AllExceptionsFilter} from "./api/exception.filter"; -import {getWAHAVersion, VERSION, WAHAVersion} from "./version"; -import {AppModuleCore} from "./core/app.module.core"; -import {SwaggerModuleCore} from "./core/swagger.module.core"; -import {json, urlencoded} from "express"; +import { NestFactory } from '@nestjs/core'; +import { WhatsappConfigService } from './config.service'; +import { AllExceptionsFilter } from './api/exception.filter'; +import { getWAHAVersion, VERSION, WAHAVersion } from './version'; +import { AppModuleCore } from './core/app.module.core'; +import { SwaggerModuleCore } from './core/swagger.module.core'; +import { json, urlencoded } from 'express'; -async function loadModules(): Promise<[typeof AppModuleCore, typeof SwaggerModuleCore]> { - const version = getWAHAVersion() - console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`) +async function loadModules(): Promise< + [typeof AppModuleCore, typeof SwaggerModuleCore] +> { + const version = getWAHAVersion(); + console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`); - if (version === WAHAVersion.CORE) { - const {AppModuleCore} = await import("./core/app.module.core") - const {SwaggerModuleCore} = await import("./core/swagger.module.core") - return [AppModuleCore, SwaggerModuleCore] - } - // Ignore if it's core version - there's no plus module - // @ts-ignore - const {AppModulePlus} = await import("./plus/app.module.plus") - // @ts-ignore - const {SwaggerModulePlus} = await import("./plus/swagger.module.plus") - return [AppModulePlus, SwaggerModulePlus] + if (version === WAHAVersion.CORE) { + const { AppModuleCore } = await import('./core/app.module.core'); + const { SwaggerModuleCore } = await import('./core/swagger.module.core'); + return [AppModuleCore, SwaggerModuleCore]; + } + // Ignore if it's core version - there's no plus module + // @ts-ignore + const { AppModulePlus } = await import('./plus/app.module.plus'); + // @ts-ignore + const { SwaggerModulePlus } = await import('./plus/swagger.module.plus'); + return [AppModulePlus, SwaggerModulePlus]; } async function bootstrap() { - const [AppModule, SwaggerModule] = await loadModules() - const app = await NestFactory.create(AppModule, { - logger: process.env.DEBUG != undefined ? ['log', 'debug', 'error', 'verbose', 'warn'] : - ['log', 'error', 'warn'], - }); + const [AppModule, SwaggerModule] = await loadModules(); + const app = await NestFactory.create(AppModule, { + logger: + process.env.DEBUG != undefined + ? ['log', 'debug', 'error', 'verbose', 'warn'] + : ['log', 'error', 'warn'], + }); - app.enableShutdownHooks(); - app.useGlobalFilters(new AllExceptionsFilter()); + app.enableShutdownHooks(); + app.useGlobalFilters(new AllExceptionsFilter()); - // Allow to send big body - for images and attachments - app.use(json({limit: '50mb'})); - app.use(urlencoded({limit: '50mb', extended: false})); + // Allow to send big body - for images and attachments + app.use(json({ limit: '50mb' })); + app.use(urlencoded({ limit: '50mb', extended: false })); - // Configure swagger - const swagger = new SwaggerModule() - swagger.configure(app) + // Configure swagger + const swagger = new SwaggerModule(); + swagger.configure(app); - const config = app.get(WhatsappConfigService); - await app.listen(config.port); - console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`); + const config = app.get(WhatsappConfigService); + await app.listen(config.port); + console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`); } bootstrap(); diff --git a/src/structures/base.dto.ts b/src/structures/base.dto.ts index 5c906927..627c7912 100644 --- a/src/structures/base.dto.ts +++ b/src/structures/base.dto.ts @@ -1,13 +1,13 @@ -import {IsNotEmpty} from "class-validator"; +import { IsNotEmpty } from 'class-validator'; -export const WHATSAPP_DEFAULT_SESSION_NAME = "default" +export const WHATSAPP_DEFAULT_SESSION_NAME = 'default'; /** * Queries */ export class SessionQuery { - @IsNotEmpty() - session: string = WHATSAPP_DEFAULT_SESSION_NAME; + @IsNotEmpty() + session: string = WHATSAPP_DEFAULT_SESSION_NAME; } /** @@ -15,5 +15,5 @@ export class SessionQuery { */ export class SessionBaseRequest { - session = WHATSAPP_DEFAULT_SESSION_NAME + session = WHATSAPP_DEFAULT_SESSION_NAME; } diff --git a/src/structures/chatting.dto.ts b/src/structures/chatting.dto.ts index 895ac394..5054974c 100644 --- a/src/structures/chatting.dto.ts +++ b/src/structures/chatting.dto.ts @@ -1,218 +1,221 @@ -import {IsNotEmpty, IsString, IsOptional, IsNumber} from "class-validator"; -import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger"; -import {SessionBaseRequest, SessionQuery} from "./base.dto"; - +import { IsNotEmpty, IsString, IsOptional, IsNumber } from 'class-validator'; +import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'; +import { SessionBaseRequest, SessionQuery } from './base.dto'; /** * Queries */ export class CheckNumberStatusQuery extends SessionQuery { - @IsString() - phone: string + @IsString() + phone: string; } export class MessageTextQuery extends SessionQuery { - @IsString() - phone: string - @IsString() - text: string + @IsString() + phone: string; + @IsString() + text: string; } export class ChatQuery extends SessionQuery { - @ApiProperty({ - example: '11111111111@c.us', - }) - chatId: string + @ApiProperty({ + example: '11111111111@c.us', + }) + chatId: string; } export class GetMessageQuery extends ChatQuery { - @IsNumber() - limit: number + @IsNumber() + limit: number; } - /** * Requests */ export class ChatRequest extends SessionBaseRequest { - @ApiProperty({ - example: '11111111111@c.us', - }) - chatId: string + @ApiProperty({ + example: '11111111111@c.us', + }) + chatId: string; } export class SendSeenRequest extends ChatRequest { - @ApiProperty({ - example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA', - required: false, - description: "NOWEB engine only - it's important to mark ALL messages as seen" - }) - messageId?: string + @ApiProperty({ + example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA', + required: false, + description: + "NOWEB engine only - it's important to mark ALL messages as seen", + }) + messageId?: string; - @ApiProperty({ - example: '11111111111@c.us', - required: false, - description: "NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)" - }) - participant?: string + @ApiProperty({ + example: '11111111111@c.us', + required: false, + description: + 'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)', + }) + participant?: string; } export class MessageRequest extends SessionBaseRequest { - @ApiProperty({ - example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA", - }) - messageId: string + @ApiProperty({ + example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA', + }) + messageId: string; } export class MessageContactVcardRequest extends ChatRequest { - contactsId: string; - name: string + contactsId: string; + name: string; } export class MessageTextRequest extends ChatRequest { - text = "Hi there!" + text = 'Hi there!'; } export class Button { - id: string - text: string + id: string; + text: string; } export class MessageTextButtonsRequest extends ChatRequest { - title: string; - footer?: string - buttons?: Array