[core] Apply pre-commit with prettier

This commit is contained in:
allburov committed 2023-05-21 18:09:25 +07:00
1 parent 2af3940be7
commit bf615efa96
33 files changed
+2276 -2083

No files matched your search

+128 -118
View File
@@ -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<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.checkNumberStatus(request)
}
@Get('/checkNumberStatus')
@ApiOperation({ summary: 'Check number status' })
async checkNumberStatus(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Post('/sendContactVcard')
sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendContactVCard(request)
}
@Post('/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<WAMessage> {
const whatsapp = this.manager.getSession(request.session)
return whatsapp.sendText(request)
}
@Post('/sendText')
@ApiOperation({ summary: 'Send a text message' })
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
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);
}
}
+61 -51
View File
@@ -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<WANumberExistResult> {
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<WANumberExistResult> {
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);
}
}
+35 -35
View File
@@ -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<Response>();
const request = ctx.getRequest<Request>();
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<Response>();
const request = ctx.getRequest<Request>();
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,
});
}
}
+153 -145
View File
@@ -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<string> {
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<string> {
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<string> {
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<string> {
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);
}
}
+14 -16
View File
@@ -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<WhatsappSession> {
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);
+20 -26
View File
@@ -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);
}
}
+21 -24
View File
@@ -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<void> {
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<void> {
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);
}
}
+7 -9
View File
@@ -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;
}
}
+92 -93
View File
@@ -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<number>('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<number>(
'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);
}
}
+7 -7
View File
@@ -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');
}
}
+17 -19
View File
@@ -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<void>
abstract stop(request: SessionStopRequest): Promise<void>;
abstract logout(request: SessionLogoutRequest): Promise<void>
abstract logout(request: SessionLogoutRequest): Promise<void>;
abstract getSession(name: string, error?: boolean): WhatsappSession
abstract getSession(name: string, error?: boolean): WhatsappSession;
abstract getSessions(all: boolean): SessionDTO[]
abstract getSessions(all: boolean): SessionDTO[];
}
+202 -202
View File
@@ -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<Buffer | string>;
/** 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<Buffer | string>
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<string> {
throw new NotImplementedByEngineError();
}
public setDescription(id, description) {
throw new NotImplementedByEngineError()
}
public revokeInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError();
}
public setSubject(id, description) {
throw new NotImplementedByEngineError()
}
public getParticipants(id) {
throw new NotImplementedByEngineError();
}
public getInviteCode(id): Promise<string> {
throw new NotImplementedByEngineError()
}
public addParticipants(id, request: ParticipantsRequest) {
throw new NotImplementedByEngineError();
}
public revokeInviteCode(id): Promise<string> {
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,
};
}
}
+10 -6
View File
@@ -1,19 +1,23 @@
export abstract class MediaStorage {
abstract save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>
abstract save(
messageId: string,
mimetype: string,
buffer: Buffer,
): Promise<string>;
}
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[];
}
+2 -2
View File
@@ -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);
}
+49 -50
View File
@@ -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 {}
+13 -12
View File
@@ -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}`,
);
}
}
+150 -126
View File
@@ -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<void> {
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<void> {
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 }];
}
}
+114 -78
View File
@@ -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<Buffer | string> {
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<WANumberExistResult> {
const phone = request.phone.split("@")[0];
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
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;
}
+254 -219
View File
@@ -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<Buffer | string> {
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<WANumberExistResult> {
try {
return await this.whatsapp.checkNumberStatus(
this.ensureSuffix(request.phone),
);
} catch (error) {
// Catch number doesn't exist error and return it as is
if (error.status === 404 && !error.numberExists) {
return error;
}
throw error;
}
}
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<Buffer | string> {
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<WANumberExistResult> {
try {
return await this.whatsapp.checkNumberStatus(this.ensureSuffix(request.phone))
} catch (error) {
// Catch number doesn't exist error and return it as is
if (error.status === 404 && !error.numberExists) {
return error
}
throw error
}
}
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<WAMessage> {
// @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<WAMessage> {
// @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,
});
}
}
+405 -340
View File
@@ -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<Buffer | string> {
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<WANumberExistResult> {
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<string> {
const groupChat = await this.whatsapp.getChatById(id) as GroupChat
return groupChat.getInviteCode()
}
public async revokeInviteCode(id): Promise<string> {
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<WAMessage> {
// @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<Buffer | string> {
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<WANumberExistResult> {
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<string> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return groupChat.getInviteCode();
}
public async revokeInviteCode(id): Promise<string> {
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<WAMessage> {
// @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;
}
}
+20 -13
View File
@@ -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<string> {
return Promise.resolve(`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`);
async save(
messageId: string,
mimetype: string,
buffer: Buffer,
): Promise<string> {
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);
}
}
+58 -64
View File
@@ -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.<br>
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.<br>
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`
)
.addTag(
'groups',
`Groups methods.<br>`
)
.addTag('other', 'Other methods')
.addApiKey({
type: 'apiKey',
description: 'Your secret api key',
name: 'X-Api-Key'
}
)
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
)
.addTag('groups', `Groups methods.<br>`)
.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;
}
}
+72 -70
View File
@@ -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)}`);
});
}
}
+10 -4
View File
@@ -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',
);
}
+41 -37
View File
@@ -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();
+5 -5
View File
@@ -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;
}
+135 -132
View File
@@ -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<Button>;
title: string;
footer?: string;
buttons?: Array<Button>;
}
export class MessageReplyRequest extends ChatRequest {
text = "Reply text"
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
reply_to: string;
text = 'Reply text';
@ApiProperty({
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
reply_to: string;
}
export class MessageLocationRequest extends ChatRequest {
latitude: number;
longitude: number;
title: string;
latitude: number;
longitude: number;
title: string;
}
export class RemoteFile {
@ApiProperty({
description: "MIME type of the attachment.",
example: 'image/jpeg',
})
mimetype: string
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: "Document file name. Value can be null",
example: 'filename.jpg',
})
filename?: string | null
@ApiProperty({
description: 'Document file name. Value can be null',
example: 'filename.jpg',
})
filename?: string | null;
@ApiProperty({
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
})
url: string;
}
export class BinaryFile {
@ApiProperty({
description: "MIME type of the attachment.",
example: 'image/jpeg',
})
mimetype: string
@ApiProperty({
description: 'MIME type of the attachment.',
example: 'image/jpeg',
})
mimetype: string;
@ApiProperty({
description: "Document file name. Optional",
example: 'filename.jpeg',
})
filename?: string | null
@ApiProperty({
description: 'Document file name. Optional',
example: 'filename.jpeg',
})
filename?: string | null;
@ApiProperty({
description: "Base64-encoded data of the file",
example: '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',
})
data: string
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'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',
})
data: string;
}
@ApiExtraModels(BinaryFile, RemoteFile)
class FileRequest extends ChatRequest {
@ApiProperty({
oneOf: [
{$ref: getSchemaPath(BinaryFile)},
{$ref: getSchemaPath(RemoteFile)},
],
})
file: BinaryFile | RemoteFile
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(BinaryFile) },
{ $ref: getSchemaPath(RemoteFile) },
],
})
file: BinaryFile | RemoteFile;
}
export class MessageImageRequest extends FileRequest {
caption: string;
caption: string;
}
export class MessageFileRequest extends FileRequest {
caption: string;
caption: string;
}
export class VoiceBinaryFile {
@ApiProperty({
description: "MIME type of the attachment.",
})
mimetype = "audio/ogg; codecs=opus"
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: "Document file name. Optional",
})
filename = "voice-message.mp3"
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
description: "Base64-encoded data of the file",
example: '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 Line truncated
})
data: string
@ApiProperty({
description: 'Base64-encoded data of the file',
example:
'SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//NkxAAW0D4AVN4YAAYAKqTsSE2Z0IlkQMKANEGBUWH7a99t8eFgGQqfrfJxAsocfid8uizPyg535NQgTSs/z6xwJlJc/XKOE8uQtJsTP7ym4u/ky78nQzJw+t+4TvEiw/UftU4TymUVBiqj2qVLlF8X+bWGBLTBZi2+uOml4gA4HEVKHj2Fw89os529yuxD//NkxCcUAGI0FOJGKNWEHAhP3y5/+LZc5OU6X9RI+9M5IZc/fOaJf5y6cTjSBfLxrzjCnoSUCNUgnzokmHoBlzC6CGSrXGjEOPedHzBCkYcOBBpTTNe9/8tGfO5M4inBdS7PsvVusm91LKv4Ycftpwq4YATDG98/QyhKd3V7mZn7wZb2yrt3Y0fiGYpP4aUa//NkxFokCoIwNOpMRV7E1Kqm9vJ123c1F9bGVSKnasqqcezN/4ar26Uz3P8M/AgXq/+6ctsqX+fW6UmfCJ79o9g/CqpFrrpE78sS2V8ApIpklEE02r9II4IDbdNNnFC4dREiIgJFHHcOZjvE9lBMgBQUZzqdOmMDc97ku5pyFwNDph6+LaOyjKaRA6x4x2sc//NkxEwZkcZIVNpGCBophiL5tglY4Lj3qeQceTdr6ur/R11nlO/6KuHORu5SCMucIAApqsZ1I2CBCFxtWZ1tzE3pcvJD87eocViSFHDFuRXMs+q62dVX4O2GtELkoUrIfEbh7506+CEA0VUxhsBLvWABIfGtS8OCcALHMrWskk2pbXJuOEheYHvtaFW9arRY//NkxGgc+ZY8COJGVJsJX1dZg8q3QVzqEQCJapAB18VGV0Kcf+J8F8mNnEbyRRigCGBwCm4AQGjWWcBwIbSN5OhOjQgmU8f6ry3stZNsXzEDM1r44o0zCIuyJMgAejNzadpOkuIVTwXA/BS9ep5F8IvMiz9tM16cuR2SKoggy2MZQtg8BNAidnuNVpsFLpx5//NkxHcgEjJFdOGGtCyrFeW67L62epUITeB47MPJE/N7DOhVOlzQ3shzlhFM2gkw2MBQ0zxwHeTFmAKsdmTYnkf6VQlp4GCOOLPWCLF3sYHocCoKThOYp41Wo93UtOhKLnDyelr+f407RfQ0loWe5+a43SqraakgBnwG4POuQSELhVgoHwVB0OoeCgYj442U//NkxHkjKbZIyuZQTO/HTN86uNWKDHSjaBJ/RdqIU9N6adPSJcjBpAjd5ZQMRpirXYsZphSRhl2BiwAD9ZAHGGWBEzHAU3Ws11wH6d2fdF/sqG9P8pbEqpa2MNUcah+aizKGY8v4+WWNtiSTbbMmyf3K+U5Rez8xte6f9ZqzAIjDxVp4AEmjlm3KWLyoJw1r//NkxG8kSXpQMu5MWDLiYSGiEC1DUiwsLihhGVbL4DpupqbFV9eMCtpMfeuq2rdVeZVjIMHTE8nDqsWwcQxmk3BgCBxhAbhgGDBVBEP6XAIhRMwmTmYYwZ247Uc+cECwUeim6NFyz5cgE0SixFn+fd5Sb0kClO5up2jHtvXhqfSgK48ZRY+xgCaKhmJRI9lr//NkxGAhIT5UFO4MTFtqa6x7kuCp0aq5TakV0uFEJq1b3N9XYzKlRXJPaSjSxK16nqUZU4nVxqUFGJXeenWhgstGlJWAiyYfWYQCQYElQINEwKcFLFqIG1Zg8UvWrXmn1urARzCgoBTosSTkRcfBUVbsDXM62KmDEAkBJIYuNDHnGAAaYAyAwGwK6dYB7GPQ//NkxF4e4R5YXOMGiLWuWmL28y2xduEEPX16UzKUPFHrolYu8zXv879xhWqbVph6jGInGdqQwcFDlqOMrjUxIck9jAYAaslNTOm3mOyEoAq0pPsjKCa8QSZV7WmomOiijP1smCamch4MWEIfTcdiD/8MysS3MIj1h6NBhMoXeaAARIGjNLI4WOMDQOLS4+kR//NkxGUgaXZYVOGGlIXFBZoDP07hI3oN61pXNfrczNjykgKtFEu64tDaAKkkkkkkkiuSIFcvY9AuCM1psOiZD1e2RL5UFGSfVFOxKiSZcOB/rRbEIby4QS/l7HwSQ50PdnO5vF8uaYLg9T7o/0LGAVoE+AnnUhEqHqN+2EiZOousyjbkKCoGzIXFQGBGBcEg//NkxGY11Dq1vnpNhsAoeI90gZTMogfZUtHJiDaiLhMLEIFjgi/rrIXF+zNMlMwtIAFwQc+pTaGwtePD6+tl7j0comzEIh1EyQYLbHpcQ5SF7kZd1NIWQyNQKRC9JnDD6UFiJIGrIKBzkPl2Uq4cmamQz7+7tOxl7FtEH0h06g4OqqDJTBgSqEvrWRlEECjC//NkxBEhKYKUANPSnD4CG3gCwLkHoBMqTKUaUkraqCzctWV+m1bkb8u0ouTmawYa28AxqoQtKHeXrBv5cEpBP6IK/CcgM8Ey2hPrVRHqMT4cYXQFwbZAcIhcmTBOaMnhVz81zINiN///8sMPC3/7n/+qticPkhrRgq5w0OPcxplVBAMcSK16xyfBu6V5E4VO//NkxA8fQe7SVnsE3sqlEuhK9fjDZLxzi6SLBUEewl/scsTkeqdE9uFRUHGoaSs6r+2lvfUOiQ9kXdOx7B+1x2jPQvKC7RPl2PzZ7uzXe1f/5SkZoy2lzQ2FA6cWKCptRB0u3/6Vf+0SoKiglKox7AQEgJh8ISADd6fuBIAUaKk9cslsqjVbKmbz0PPY5Q1+//NkxBUfIaK01sPSlh5ZFGEiMvdwnWn2oMTLJTM2J8t0RhfRR2tLEUkrDGLOos4rOIYcCqAiKoiuom7r2xebLJIlTx44fjk7d8n83YRe957vMH/cwSKL6Ey973mSpMgGTlosv1ecaUut/hE0ILvvGRox6nk1AL2mPfUBUZaSvgWZbhpRdzWxrQ2qsfS4Qmi6//NkxBsgWWKwFsPMnLLjPbZm/+Hp/Fw44c4jYeLAJohxuQaL0ZST7Y3rG5bVb0kahY09mDWHbJTQ7lcxyAsgLkCImGpSeLFqjKWO+szt1WrMpSDqwjc80JbaLzItpImQmNBnf/t1/SBQZHiEsLNJUhlwbQoEgDWNNauCKdiupuITWqBhmGTVEDhJolUxTqPG//NkxBwc4Wq81nsMlh7aUSRnfU3mOSHKcXHggNr2WWu5xRtopJoCBLrRrL1WX2xrcpnD6LkCyUmR0yUWoJjCR77Sw9mBXULCT54Yu0Xrdb/hVS0I3/mTSw7/w6GyoBSMHjLXpqqgkAv1krkkRYNGdtnWV3uRCZjY3Di3rqbqSNhlzSWI00OhwAAcQvlyZaGo//NkxCsdK+rM9njE+zCBnEkZT8iKUmbJBEcUCMSNAxxiWZoqSN//tlV5mb7aKrPeulvuyohyLP////rv03ns6IrqtNN6p3LrKhFRn0akUwsm8ZkkyhudZqIlO/a1dASkgCFg25A5g4wH3DYgeONWRWR5F0rSuv35UUCWGdFkme7Wioowdlp+tKTOglxQIdDs//NkxDkcgZ7U/lmEllYr/sU44cWEhi+sCpF31D0rAKFKjD0tGAkIsfWlkkkieDodcZLB37qXxcNOSQIHaGhVNV6qqhR1SlSSUm1lRiYWROH6A2xUkPSoFND1ytKPu+bBqK93TKimC1i0iELlCdh2brUzdCKYEJBmO22uLhva2uLZ3EQvGqNqW2+KiYpnIZqf//NkxEodEoLEXmIE3uFdKU1ft6UWWu/NtOV2MFJBARFv915Fwi//ijKZ5QPtahDwbPvMOOPta9UJJad1sibIEcAnXUzOin78wDRaiSuCjubzcE4XF3jy82iLCgZdHNdXe0NttCoCkSCqQUsqtTX1rrQ0VUezN/9/F8WRyxaMdBFmkPGnrCA4XUpoG/xKGlVK//NkxFgdUa6w3nmQkoUGufspBpZEMpRb92LyRGYOuPLBUNETxFC+eZYI1QBiWo7bv9cbC4RBNgU2ZOzJlF2I6hncW0XFxQq5joOD4qV1k0mFqK+c0awzlTSR0s1t3Fwl6uZIKmCg7bWIdsjgSDKRpnRGr8vcxrIbKzUVl7316SOkxjs9CJen///Npyf///f///NkxGUc4869vkoEv/MjM0zQT+PLHf03+FAppQiO26Si0wExQTgQZNKKhYVXL/KdqhblIpuWqJEkZJao2zpmnQJzwo/KzF2SsevxgrWgg8ogPCHJC+ZGYI2ADMGCNweE3kigIuDQFGDEH4qBwcUYUz+bXbe4AMoPu/7s24QUHaPJvozFL7kkmPLhgUeLCPpU//NkxHQdQYKAPsmGkBUB40UDw1eB2YYqrAq9/o+y1xKjtHtsXBr0nSf2Vwmm+QRJlXz1m1IVzyOFGEgDDGZVaz3eRW2U4RFBamZ93qsjBtD5znsr2aIZnVX2RKpDvajaEnvzn37TvfX9f/faT/t///8i6rX3INPKHWGrJkprEf6xqTGg6UAvtPBqpnPTuNw1//NkxIIcA758FspEWAuYStUOFnAwRayKry+iKyESNAqfXOu5qY5aIXsruZdM/Z6b1jIKxB4uh2+ai4eSFYcK4fVqaeiZnNyp575zQoX5/c34fn8L/0fInJ48l+F09KZX4/8eN+rndzx47njOiPmj3Nd6SOQRWBYTMnDxEcIuFllGECkpxosYIVIcSiVJ51w+//NkxJUf9DqIFMGGMBolGBvMSYphcBJDXSmhMFFUsV27MeGBQHxCBzaaMCtwETeeCALA0JwkK1nBt2pRB7HrBckAWrpF4kJn0LYWQwa0URuPUN5l7LLlrWrvMhMuPBVwCCIFDLUqLslhZQTOEZoFjKZSQq07QHBQoX0DkWRTrgWmFVJSeW5NkVpKKXrclWbD//NkxJgcYPqSNoGGDBXTKSmTqRYo2Geff0vxs/Ve9dGEwGFWSYiWdaxBoqdDLZ+WPRaFSICcLmgghZVzUDgKz1IZYOfD3YlyEq1PNCo0XEoCUwGh0Ep0TLyRYDDhRQUDKSXe Line truncated
})
data: string;
}
export class VoiceRemoteFile {
@ApiProperty({
description: "MIME type of the attachment.",
})
mimetype = "audio/ogg; codecs=opus"
@ApiProperty({
description: 'MIME type of the attachment.',
})
mimetype = 'audio/ogg; codecs=opus';
@ApiProperty({
description: "Document file name. Optional",
})
filename = "voice-message.mp3"
@ApiProperty({
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string
@ApiProperty({
description: 'Document file name. Optional',
})
filename = 'voice-message.mp3';
@ApiProperty({
example:
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
})
url: string;
}
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
export class MessageVoiceRequest extends ChatRequest {
@ApiProperty({
oneOf: [
{$ref: getSchemaPath(VoiceBinaryFile)},
{$ref: getSchemaPath(VoiceRemoteFile)},
],
})
file: VoiceBinaryFile | VoiceRemoteFile
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(VoiceBinaryFile) },
{ $ref: getSchemaPath(VoiceRemoteFile) },
],
})
file: VoiceBinaryFile | VoiceRemoteFile;
}
export class MessageLinkPreviewRequest extends ChatRequest {
url: string;
title: string;
url: string;
title: string;
}
export class MessageReactionRequest extends MessageRequest {
@ApiProperty({
description: "Emoji to react with. Send an empty string to remove the reaction",
example: '👍',
})
reaction: string
@ApiProperty({
description:
'Emoji to react with. Send an empty string to remove the reaction',
example: '👍',
})
reaction: string;
}
+6 -8
View File
@@ -1,22 +1,20 @@
import {IsString} from "class-validator";
import {SessionBaseRequest, SessionQuery} from "./base.dto";
import { IsString } from 'class-validator';
import { SessionBaseRequest, SessionQuery } from './base.dto';
/**
* Queries
*/
export class ContactQuery extends SessionQuery {
@IsString()
contactId: string
@IsString()
contactId: string;
}
/**
* Requests
*/
export class ContactRequest extends SessionBaseRequest {
@IsString()
contactId: string
@IsString()
contactId: string;
}
+14 -14
View File
@@ -1,24 +1,24 @@
export const SECOND = 1000;
export enum WAEvents {
MESSAGE = "message",
MESSAGE_ANY = "message.any",
MESSAGE_ACK = "message.ack",
STATE_CHANGE = "state.change",
GROUP_JOIN = "group.join",
GROUP_LEAVE = "group.leave",
MESSAGE = 'message',
MESSAGE_ANY = 'message.any',
MESSAGE_ACK = 'message.ack',
STATE_CHANGE = 'state.change',
GROUP_JOIN = 'group.join',
GROUP_LEAVE = 'group.leave',
}
export enum WhatsappStatus {
STOPPED = "STOPPED",
STARTING = "STARTING",
SCAN_QR_CODE = "SCAN_QR_CODE",
WORKING = "WORKING",
FAILED = "FAILED",
STOPPED = 'STOPPED',
STARTING = 'STARTING',
SCAN_QR_CODE = 'SCAN_QR_CODE',
WORKING = 'WORKING',
FAILED = 'FAILED',
}
export enum WhatsappEngine {
VENOM = "VENOM",
WEBJS = "WEBJS",
NOWEB = "NOWEB",
VENOM = 'VENOM',
WEBJS = 'WEBJS',
NOWEB = 'NOWEB',
}
+13 -14
View File
@@ -1,40 +1,39 @@
import {IsArray, IsString} from "class-validator";
import { IsArray, IsString } from 'class-validator';
/**
* Structures
*/
export class Participant {
@IsString()
id: string
@IsString()
id: string;
}
/**
* Queries
*/
/**
* Requests
*/
export class ParticipantsRequest {
@IsArray()
participants: Array<Participant>
@IsArray()
participants: Array<Participant>;
}
export class DescriptionRequest {
@IsString()
description: string
@IsString()
description: string;
}
export class SubjectRequest {
@IsString()
subject: string
@IsString()
subject: string;
}
export class CreateGroupRequest {
@IsString()
name: string
@IsString()
name: string;
@IsArray()
participants: Array<Participant>
@IsArray()
participants: Array<Participant>;
}
+109 -106
View File
@@ -1,145 +1,148 @@
import {ApiProperty} from "@nestjs/swagger";
import { WAEvents, WhatsappEngine } from "./enums.dto";
import { ApiProperty } from '@nestjs/swagger';
import { WAEvents, WhatsappEngine } from './enums.dto';
export class WANumberExistResult {
numberExists: boolean
numberExists: boolean;
}
export class WAGroupNotification {
@ApiProperty({
description: "ID that represents the groupNotification",
})
id: any
@ApiProperty({
description: 'ID that represents the groupNotification',
})
id: any;
@ApiProperty({
description: "Unix timestamp for when the groupNotification was created",
})
timestamp: number
@ApiProperty({
description: 'Unix timestamp for when the groupNotification was created',
})
timestamp: number;
@ApiProperty({
description: "ID for the Chat that this groupNotification was sent for",
})
chatId: string
@ApiProperty({
description: 'ID for the Chat that this groupNotification was sent for',
})
chatId: string;
@ApiProperty({
description: 'ContactId for the user that produced the GroupNotification',
})
author: string;
@ApiProperty({
description: "ContactId for the user that produced the GroupNotification",
})
author: string
@ApiProperty({
description: 'Extra content',
})
body: string;
@ApiProperty({
description: "Extra content",
})
body: string
@ApiProperty({
description: "Contact IDs for the users that were affected by this GroupNotification",
})
recipientIds: string[]
@ApiProperty({
description:
'Contact IDs for the users that were affected by this GroupNotification',
})
recipientIds: string[];
}
export enum WAMessageAck {
ACK_ERROR = -1,
ACK_PENDING = 0,
ACK_SERVER = 1,
ACK_DEVICE = 2,
ACK_READ = 3,
ACK_PLAYED = 4,
ACK_ERROR = -1,
ACK_PENDING = 0,
ACK_SERVER = 1,
ACK_DEVICE = 2,
ACK_READ = 3,
ACK_PLAYED = 4,
}
export class WALocation {
description?: string | null
latitude: string
longitude: string
description?: string | null;
latitude: string;
longitude: string;
}
export class WAMessage {
@ApiProperty({
description: "Message ID",
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA"
})
id: string
@ApiProperty({
description: 'Message ID',
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
})
id: string;
/** */
@ApiProperty({
description: "Unix timestamp for when the message was created",
example: 1666943582
})
timestamp: number
/** */
@ApiProperty({
description: 'Unix timestamp for when the message was created',
example: 1666943582,
})
timestamp: number;
@ApiProperty({
description: "ID for the Chat that this message was sent to, except if the message was sent by the current user "
})
from: string
@ApiProperty({
description:
'ID for the Chat that this message was sent to, except if the message was sent by the current user ',
})
from: string;
@ApiProperty({
description: "Indicates if the message was sent by the current user"
})
fromMe: boolean
@ApiProperty({
description: 'Indicates if the message was sent by the current user',
})
fromMe: boolean;
@ApiProperty({
description: `
@ApiProperty({
description: `
* ID for who this message is for.
* If the message is sent by the current user, it will be the Chat to which the message is being sent.
* If the message is sent by another user, it will be the ID for the current user.
`
})
to: string
`,
})
to: string;
@ApiProperty({
description: "For groups - participant who sent the message"
})
participant: string
@ApiProperty({
description: 'For groups - participant who sent the message',
})
participant: string;
@ApiProperty({
description: "Message content"
})
body: string
@ApiProperty({
description: 'Message content',
})
body: string;
@ApiProperty({
description: "Indicates if the message has media available for download"
})
hasMedia: boolean
@ApiProperty({
description: 'Indicates if the message has media available for download',
})
hasMedia: boolean;
@ApiProperty({
description: "The URL for the media in the message if any",
example: "http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga"
})
mediaUrl: string
@ApiProperty({
description: 'The URL for the media in the message if any',
example:
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
})
mediaUrl: string;
@ApiProperty({
description: "ACK status for the message"
})
ack: WAMessageAck
@ApiProperty({
description: 'ACK status for the message',
})
ack: WAMessageAck;
@ApiProperty({
description: "If the message was sent to a group, this field will contain the user that sent the message."
})
author?: string;
@ApiProperty({
description:
'If the message was sent to a group, this field will contain the user that sent the message.',
})
author?: string;
@ApiProperty({
description: "Location information contained in the message, if the message is type \"location\""
})
location?: WALocation
@ApiProperty({
description:
'Location information contained in the message, if the message is type "location"',
})
location?: WALocation;
@ApiProperty({
description: "List of vCards contained in the message."
})
vCards?: string[]
@ApiProperty({
description: 'List of vCards contained in the message.',
})
vCards?: string[];
/** Returns message in a raw format */
@ApiProperty({
description: "Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend."
})
_data?: any
/** Returns message in a raw format */
@ApiProperty({
description:
'Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend.',
})
_data?: any;
}
export class WAWebhook {
event: WAEvents
session: string
engine: WhatsappEngine
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object
event: WAEvents;
session: string;
engine: WhatsappEngine;
// eslint-disable-next-line @typescript-eslint/ban-types
payload: WAMessage | WAGroupNotification | object;
}
+20 -19
View File
@@ -1,39 +1,40 @@
import {WhatsappStatus} from "./enums.dto";
import { ApiProperty } from "@nestjs/swagger";
import { WhatsappStatus } from './enums.dto';
import { ApiProperty } from '@nestjs/swagger';
/**
* Queries
*/
export class ListSessionsQuery {
@ApiProperty({
example: false,
required: false,
description: "Return all sessions, including those that are in the STOPPED state."
})
all: boolean
@ApiProperty({
example: false,
required: false,
description:
'Return all sessions, including those that are in the STOPPED state.',
})
all: boolean;
}
/**
* Requests
*/
export class SessionStartRequest {
name = "default"
name = 'default';
}
export class SessionStopRequest {
name = "default"
@ApiProperty({
example: false,
required: false,
description: "Stop and logout from the session."
})
logout = false
name = 'default';
@ApiProperty({
example: false,
required: false,
description: 'Stop and logout from the session.',
})
logout = false;
}
export class SessionLogoutRequest {
name = "default"
name = 'default';
}
export class SessionDTO {
name = "default"
status: WhatsappStatus
name = 'default';
status: WhatsappStatus;
}
+19 -19
View File
@@ -1,34 +1,34 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
import {getBrowserExecutablePath} from "./core/abc/session.abc";
import { getBrowserExecutablePath } from './core/abc/session.abc';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs')
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
export enum WAHAVersion {
PLUS = "PLUS",
CORE = "CORE",
PLUS = 'PLUS',
CORE = 'CORE',
}
export function getWAHAVersion(): WAHAVersion {
// force core version if env variables set
const waha_version = process.env.WAHA_VERSION;
if (waha_version && waha_version === WAHAVersion.CORE) {
return WAHAVersion.CORE
}
// force core version if env variables set
const waha_version = process.env.WAHA_VERSION;
if (waha_version && waha_version === WAHAVersion.CORE) {
return WAHAVersion.CORE;
}
// Check the plus directory exists
const plusExists = fs.existsSync(`${__dirname}/plus`)
if (plusExists) {
return WAHAVersion.PLUS
}
// Check the plus directory exists
const plusExists = fs.existsSync(`${__dirname}/plus`);
if (plusExists) {
return WAHAVersion.PLUS;
}
return WAHAVersion.CORE
return WAHAVersion.CORE;
}
export const VERSION = {
version: "2023.5.20",
tier: getWAHAVersion(),
browser: getBrowserExecutablePath()
}
version: '2023.5.20',
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
};