mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 03:42:23 -06:00
fix: reorganization of files and folders
This commit is contained in:
13
src/api/abstract/abstract.cache.ts
Normal file
13
src/api/abstract/abstract.cache.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface ICache {
|
||||
get(key: string): Promise<any>;
|
||||
|
||||
set(key: string, value: any, ttl?: number): void;
|
||||
|
||||
has(key: string): Promise<boolean>;
|
||||
|
||||
keys(appendCriteria?: string): Promise<string[]>;
|
||||
|
||||
delete(key: string | string[]): Promise<number>;
|
||||
|
||||
deleteAll(appendCriteria?: string): Promise<number>;
|
||||
}
|
||||
67
src/api/abstract/abstract.repository.ts
Normal file
67
src/api/abstract/abstract.repository.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, Database } from '../../config/env.config';
|
||||
import { ROOT_DIR } from '../../config/path.config';
|
||||
|
||||
export type IInsert = { insertCount: number };
|
||||
|
||||
export interface IRepository {
|
||||
insert(data: any, instanceName: string, saveDb?: boolean): Promise<IInsert>;
|
||||
update(data: any, instanceName: string, saveDb?: boolean): Promise<IInsert>;
|
||||
find(query: any): Promise<any>;
|
||||
delete(query: any, force?: boolean): Promise<any>;
|
||||
|
||||
dbSettings: Database;
|
||||
readonly storePath: string;
|
||||
}
|
||||
|
||||
type WriteStore<U> = {
|
||||
path: string;
|
||||
fileName: string;
|
||||
data: U;
|
||||
};
|
||||
|
||||
export abstract class Repository implements IRepository {
|
||||
constructor(configService: ConfigService) {
|
||||
this.dbSettings = configService.get<Database>('DATABASE');
|
||||
}
|
||||
|
||||
dbSettings: Database;
|
||||
readonly storePath = join(ROOT_DIR, 'store');
|
||||
|
||||
public writeStore = <T = any>(create: WriteStore<T>) => {
|
||||
if (!existsSync(create.path)) {
|
||||
mkdirSync(create.path, { recursive: true });
|
||||
}
|
||||
try {
|
||||
writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
return { message: 'create - success' };
|
||||
} finally {
|
||||
create.data = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
public insert(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
public update(data: any, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
public find(query: any): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
delete(query: any, force?: boolean): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
227
src/api/abstract/abstract.router.ts
Normal file
227
src/api/abstract/abstract.router.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import 'express-async-errors';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { validate } from 'jsonschema';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { GetParticipant, GroupInvite } from '../dto/group.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
|
||||
type DataValidate<T> = {
|
||||
request: Request;
|
||||
schema: JSONSchema7;
|
||||
ClassRef: any;
|
||||
execute: (instance: InstanceDto, data: T) => Promise<any>;
|
||||
};
|
||||
|
||||
const logger = new Logger('Validate');
|
||||
|
||||
export abstract class RouterBroker {
|
||||
constructor() {}
|
||||
public routerPath(path: string, param = true) {
|
||||
let route = '/' + path;
|
||||
param ? (route += '/:instanceName') : null;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
public async dataValidate<T>(args: DataValidate<T>) {
|
||||
const { request, schema, ClassRef, execute } = args;
|
||||
|
||||
const ref = new ClassRef();
|
||||
const body = request.body;
|
||||
const instance = request.params as unknown as InstanceDto;
|
||||
|
||||
if (request?.query && Object.keys(request.query).length > 0) {
|
||||
Object.assign(instance, request.query);
|
||||
}
|
||||
|
||||
if (request.originalUrl.includes('/instance/create')) {
|
||||
Object.assign(instance, body);
|
||||
}
|
||||
|
||||
Object.assign(ref, body);
|
||||
|
||||
const v = schema ? validate(ref, schema) : { valid: true, errors: [] };
|
||||
|
||||
if (!v.valid) {
|
||||
const message: any[] = v.errors.map(({ stack, schema }) => {
|
||||
let message: string;
|
||||
if (schema['description']) {
|
||||
message = schema['description'];
|
||||
} else {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return message;
|
||||
});
|
||||
logger.error(message);
|
||||
throw new BadRequestException(message);
|
||||
}
|
||||
|
||||
return await execute(instance, ref);
|
||||
}
|
||||
|
||||
public async groupNoValidate<T>(args: DataValidate<T>) {
|
||||
const { request, ClassRef, schema, execute } = args;
|
||||
|
||||
const instance = request.params as unknown as InstanceDto;
|
||||
|
||||
const ref = new ClassRef();
|
||||
|
||||
Object.assign(ref, request.body);
|
||||
|
||||
const v = validate(ref, schema);
|
||||
|
||||
if (!v.valid) {
|
||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||
let message: string;
|
||||
if (schema['description']) {
|
||||
message = schema['description'];
|
||||
} else {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return {
|
||||
property: property.replace('instance.', ''),
|
||||
message,
|
||||
};
|
||||
});
|
||||
logger.error([...message]);
|
||||
throw new BadRequestException(...message);
|
||||
}
|
||||
|
||||
return await execute(instance, ref);
|
||||
}
|
||||
|
||||
public async groupValidate<T>(args: DataValidate<T>) {
|
||||
const { request, ClassRef, schema, execute } = args;
|
||||
|
||||
const instance = request.params as unknown as InstanceDto;
|
||||
const body = request.body;
|
||||
|
||||
let groupJid = body?.groupJid;
|
||||
|
||||
if (!groupJid) {
|
||||
if (request.query?.groupJid) {
|
||||
groupJid = request.query.groupJid;
|
||||
} else {
|
||||
throw new BadRequestException('The group id needs to be informed in the query', 'ex: "groupJid=120362@g.us"');
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupJid.endsWith('@g.us')) {
|
||||
groupJid = groupJid + '@g.us';
|
||||
}
|
||||
|
||||
Object.assign(body, {
|
||||
groupJid: groupJid,
|
||||
});
|
||||
|
||||
const ref = new ClassRef();
|
||||
|
||||
Object.assign(ref, body);
|
||||
|
||||
const v = validate(ref, schema);
|
||||
|
||||
if (!v.valid) {
|
||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||
let message: string;
|
||||
if (schema['description']) {
|
||||
message = schema['description'];
|
||||
} else {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return {
|
||||
property: property.replace('instance.', ''),
|
||||
message,
|
||||
};
|
||||
});
|
||||
logger.error([...message]);
|
||||
throw new BadRequestException(...message);
|
||||
}
|
||||
|
||||
return await execute(instance, ref);
|
||||
}
|
||||
|
||||
public async inviteCodeValidate<T>(args: DataValidate<T>) {
|
||||
const { request, ClassRef, schema, execute } = args;
|
||||
|
||||
const inviteCode = request.query as unknown as GroupInvite;
|
||||
|
||||
if (!inviteCode?.inviteCode) {
|
||||
throw new BadRequestException(
|
||||
'The group invite code id needs to be informed in the query',
|
||||
'ex: "inviteCode=F1EX5QZxO181L3TMVP31gY" (Obtained from group join link)',
|
||||
);
|
||||
}
|
||||
|
||||
const instance = request.params as unknown as InstanceDto;
|
||||
const body = request.body;
|
||||
|
||||
const ref = new ClassRef();
|
||||
|
||||
Object.assign(body, inviteCode);
|
||||
Object.assign(ref, body);
|
||||
|
||||
const v = validate(ref, schema);
|
||||
|
||||
if (!v.valid) {
|
||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||
let message: string;
|
||||
if (schema['description']) {
|
||||
message = schema['description'];
|
||||
} else {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return {
|
||||
property: property.replace('instance.', ''),
|
||||
message,
|
||||
};
|
||||
});
|
||||
logger.error([...message]);
|
||||
throw new BadRequestException(...message);
|
||||
}
|
||||
|
||||
return await execute(instance, ref);
|
||||
}
|
||||
|
||||
public async getParticipantsValidate<T>(args: DataValidate<T>) {
|
||||
const { request, ClassRef, schema, execute } = args;
|
||||
|
||||
const getParticipants = request.query as unknown as GetParticipant;
|
||||
|
||||
if (!getParticipants?.getParticipants) {
|
||||
throw new BadRequestException('The getParticipants needs to be informed in the query');
|
||||
}
|
||||
|
||||
const instance = request.params as unknown as InstanceDto;
|
||||
const body = request.body;
|
||||
|
||||
const ref = new ClassRef();
|
||||
|
||||
Object.assign(body, getParticipants);
|
||||
Object.assign(ref, body);
|
||||
|
||||
const v = validate(ref, schema);
|
||||
|
||||
if (!v.valid) {
|
||||
const message: any[] = v.errors.map(({ property, stack, schema }) => {
|
||||
let message: string;
|
||||
if (schema['description']) {
|
||||
message = schema['description'];
|
||||
} else {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return {
|
||||
property: property.replace('instance.', ''),
|
||||
message,
|
||||
};
|
||||
});
|
||||
logger.error([...message]);
|
||||
throw new BadRequestException(...message);
|
||||
}
|
||||
|
||||
return await execute(instance, ref);
|
||||
}
|
||||
}
|
||||
29
src/api/controllers/chamaai.controller.ts
Normal file
29
src/api/controllers/chamaai.controller.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ChamaaiService } from '../services/chamaai.service';
|
||||
|
||||
const logger = new Logger('ChamaaiController');
|
||||
|
||||
export class ChamaaiController {
|
||||
constructor(private readonly chamaaiService: ChamaaiService) {}
|
||||
|
||||
public async createChamaai(instance: InstanceDto, data: ChamaaiDto) {
|
||||
logger.verbose('requested createChamaai from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('chamaai disabled');
|
||||
data.url = '';
|
||||
data.token = '';
|
||||
data.waNumber = '';
|
||||
data.answerByAudio = false;
|
||||
}
|
||||
|
||||
return this.chamaaiService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findChamaai(instance: InstanceDto) {
|
||||
logger.verbose('requested findChamaai from ' + instance.instanceName + ' instance');
|
||||
return this.chamaaiService.find(instance);
|
||||
}
|
||||
}
|
||||
132
src/api/controllers/chat.controller.ts
Normal file
132
src/api/controllers/chat.controller.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
PrivacySettingDto,
|
||||
ProfileNameDto,
|
||||
ProfilePictureDto,
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ContactQuery } from '../repository/contact.repository';
|
||||
import { MessageQuery } from '../repository/message.repository';
|
||||
import { MessageUpQuery } from '../repository/messageUp.repository';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('ChatController');
|
||||
|
||||
export class ChatController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {
|
||||
logger.verbose('requested whatsappNumber from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].whatsappNumber(data);
|
||||
}
|
||||
|
||||
public async readMessage({ instanceName }: InstanceDto, data: ReadMessageDto) {
|
||||
logger.verbose('requested readMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].markMessageAsRead(data);
|
||||
}
|
||||
|
||||
public async archiveChat({ instanceName }: InstanceDto, data: ArchiveChatDto) {
|
||||
logger.verbose('requested archiveChat from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].archiveChat(data);
|
||||
}
|
||||
|
||||
public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) {
|
||||
logger.verbose('requested deleteMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].deleteMessage(data);
|
||||
}
|
||||
|
||||
public async fetchProfilePicture({ instanceName }: InstanceDto, data: NumberDto) {
|
||||
logger.verbose('requested fetchProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].profilePicture(data.number);
|
||||
}
|
||||
|
||||
public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) {
|
||||
logger.verbose('requested fetchProfile from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchProfile(instanceName, data.number);
|
||||
}
|
||||
|
||||
public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) {
|
||||
logger.verbose('requested fetchContacts from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchContacts(query);
|
||||
}
|
||||
|
||||
public async getBase64FromMediaMessage({ instanceName }: InstanceDto, data: getBase64FromMediaMessageDto) {
|
||||
logger.verbose('requested getBase64FromMediaMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data);
|
||||
}
|
||||
|
||||
public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) {
|
||||
logger.verbose('requested fetchMessages from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchMessages(query);
|
||||
}
|
||||
|
||||
public async fetchStatusMessage({ instanceName }: InstanceDto, query: MessageUpQuery) {
|
||||
logger.verbose('requested fetchStatusMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchStatusMessage(query);
|
||||
}
|
||||
|
||||
public async fetchChats({ instanceName }: InstanceDto) {
|
||||
logger.verbose('requested fetchChats from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchChats();
|
||||
}
|
||||
|
||||
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
|
||||
logger.verbose('requested sendPresence from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
|
||||
}
|
||||
|
||||
public async fetchPrivacySettings({ instanceName }: InstanceDto) {
|
||||
logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings();
|
||||
}
|
||||
|
||||
public async updatePrivacySettings({ instanceName }: InstanceDto, data: PrivacySettingDto) {
|
||||
logger.verbose('requested updatePrivacySettings from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data);
|
||||
}
|
||||
|
||||
public async fetchBusinessProfile({ instanceName }: InstanceDto, data: ProfilePictureDto) {
|
||||
logger.verbose('requested fetchBusinessProfile from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(data.number);
|
||||
}
|
||||
|
||||
public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) {
|
||||
logger.verbose('requested updateProfileName from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateProfileName(data.name);
|
||||
}
|
||||
|
||||
public async updateProfileStatus({ instanceName }: InstanceDto, data: ProfileStatusDto) {
|
||||
logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateProfileStatus(data.status);
|
||||
}
|
||||
|
||||
public async updateProfilePicture({ instanceName }: InstanceDto, data: ProfilePictureDto) {
|
||||
logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateProfilePicture(data.picture);
|
||||
}
|
||||
|
||||
public async removeProfilePicture({ instanceName }: InstanceDto) {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
|
||||
}
|
||||
|
||||
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
|
||||
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
|
||||
}
|
||||
|
||||
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
|
||||
logger.verbose('requested blockUser from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].blockUser(data);
|
||||
}
|
||||
}
|
||||
103
src/api/controllers/group.controller.ts
Normal file
103
src/api/controllers/group.controller.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
GroupInvite,
|
||||
GroupJid,
|
||||
GroupPictureDto,
|
||||
GroupSendInvite,
|
||||
GroupSubjectDto,
|
||||
GroupToggleEphemeralDto,
|
||||
GroupUpdateParticipantDto,
|
||||
GroupUpdateSettingDto,
|
||||
} from '../dto/group.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('ChatController');
|
||||
|
||||
export class GroupController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createGroup(instance: InstanceDto, create: CreateGroupDto) {
|
||||
logger.verbose('requested createGroup from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].createGroup(create);
|
||||
}
|
||||
|
||||
public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) {
|
||||
logger.verbose('requested updateGroupPicture from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(update);
|
||||
}
|
||||
|
||||
public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) {
|
||||
logger.verbose('requested updateGroupSubject from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(update);
|
||||
}
|
||||
|
||||
public async updateGroupDescription(instance: InstanceDto, update: GroupDescriptionDto) {
|
||||
logger.verbose('requested updateGroupDescription from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(update);
|
||||
}
|
||||
|
||||
public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested findGroupInfo from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid);
|
||||
}
|
||||
|
||||
public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) {
|
||||
logger.verbose('requested fetchAllGroups from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(getPaticipants);
|
||||
}
|
||||
|
||||
public async inviteCode(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested inviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid);
|
||||
}
|
||||
|
||||
public async inviteInfo(instance: InstanceDto, inviteCode: GroupInvite) {
|
||||
logger.verbose('requested inviteInfo from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
|
||||
}
|
||||
|
||||
public async sendInvite(instance: InstanceDto, data: GroupSendInvite) {
|
||||
logger.verbose('requested sendInvite from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
|
||||
}
|
||||
|
||||
public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
|
||||
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);
|
||||
}
|
||||
|
||||
public async findParticipants(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested findParticipants from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].findParticipants(groupJid);
|
||||
}
|
||||
|
||||
public async updateGParticipate(instance: InstanceDto, update: GroupUpdateParticipantDto) {
|
||||
logger.verbose('requested updateGParticipate from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(update);
|
||||
}
|
||||
|
||||
public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) {
|
||||
logger.verbose('requested updateGSetting from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update);
|
||||
}
|
||||
|
||||
public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) {
|
||||
logger.verbose('requested toggleEphemeral from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update);
|
||||
}
|
||||
|
||||
public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested leaveGroup from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid);
|
||||
}
|
||||
}
|
||||
751
src/api/controllers/instance.controller.ts
Normal file
751
src/api/controllers/instance.controller.ts
Normal file
@@ -0,0 +1,751 @@
|
||||
import { delay } from '@whiskeysockets/baileys';
|
||||
import { isURL } from 'class-validator';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
|
||||
import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service';
|
||||
import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service';
|
||||
import { SqsService } from '../integrations/sqs/services/sqs.service';
|
||||
import { TypebotService } from '../integrations/typebot/services/typebot.service';
|
||||
import { WebsocketService } from '../integrations/websocket/services/websocket.service';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { AuthService, OldToken } from '../services/auth.service';
|
||||
import { CacheService } from '../services/cache.service';
|
||||
import { IntegrationService } from '../services/integration.service';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
import { BaileysStartupService } from '../services/whatsapp.baileys.service';
|
||||
import { BusinessStartupService } from '../services/whatsapp.business.service';
|
||||
import { Events, Integration, wa } from '../types/wa.types';
|
||||
import { ProxyController } from './proxy.controller';
|
||||
|
||||
export class InstanceController {
|
||||
constructor(
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly authService: AuthService,
|
||||
private readonly webhookService: WebhookService,
|
||||
private readonly chatwootService: ChatwootService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly websocketService: WebsocketService,
|
||||
private readonly rabbitmqService: RabbitmqService,
|
||||
private readonly sqsService: SqsService,
|
||||
private readonly typebotService: TypebotService,
|
||||
private readonly integrationService: IntegrationService,
|
||||
private readonly proxyService: ProxyController,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {}
|
||||
|
||||
private readonly logger = new Logger(InstanceController.name);
|
||||
|
||||
public async createInstance({
|
||||
instanceName,
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events,
|
||||
qrcode,
|
||||
number,
|
||||
mobile,
|
||||
integration,
|
||||
token,
|
||||
chatwoot_account_id,
|
||||
chatwoot_token,
|
||||
chatwoot_url,
|
||||
chatwoot_sign_msg,
|
||||
chatwoot_reopen_conversation,
|
||||
chatwoot_conversation_pending,
|
||||
chatwoot_import_contacts,
|
||||
chatwoot_import_messages,
|
||||
chatwoot_days_limit_import_messages,
|
||||
reject_call,
|
||||
msg_call,
|
||||
groups_ignore,
|
||||
always_online,
|
||||
read_messages,
|
||||
read_status,
|
||||
sync_full_history,
|
||||
websocket_enabled,
|
||||
websocket_events,
|
||||
rabbitmq_enabled,
|
||||
rabbitmq_events,
|
||||
sqs_enabled,
|
||||
sqs_events,
|
||||
typebot_url,
|
||||
typebot,
|
||||
typebot_expire,
|
||||
typebot_keyword_finish,
|
||||
typebot_delay_message,
|
||||
typebot_unknown_message,
|
||||
typebot_listening_from_me,
|
||||
proxy,
|
||||
}: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||
|
||||
this.logger.verbose('checking duplicate token');
|
||||
await this.authService.checkDuplicateToken(token);
|
||||
|
||||
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
this.logger.verbose('creating instance');
|
||||
let instance: BaileysStartupService | BusinessStartupService;
|
||||
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||
instance = new BusinessStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
} else {
|
||||
instance = new BaileysStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
}
|
||||
|
||||
await this.waMonitor.saveInstance({ integration, instanceName, token, number, mobile });
|
||||
|
||||
instance.instanceName = instanceName;
|
||||
|
||||
const instanceId = v4();
|
||||
|
||||
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
|
||||
instanceName,
|
||||
instanceId: instanceId,
|
||||
});
|
||||
|
||||
this.logger.verbose('instance: ' + instance.instanceName + ' created');
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName] = instance;
|
||||
this.waMonitor.delInstanceTime(instance.instanceName);
|
||||
|
||||
this.logger.verbose('generating hash');
|
||||
const hash = await this.authService.generateHash(
|
||||
{
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
},
|
||||
token,
|
||||
);
|
||||
|
||||
this.logger.verbose('hash: ' + hash + ' generated');
|
||||
|
||||
let webhookEvents: string[];
|
||||
|
||||
if (webhook) {
|
||||
if (!isURL(webhook, { require_tld: false })) {
|
||||
throw new BadRequestException('Invalid "url" property in webhook');
|
||||
}
|
||||
|
||||
this.logger.verbose('creating webhook');
|
||||
try {
|
||||
let newEvents: string[] = [];
|
||||
if (events.length === 0) {
|
||||
newEvents = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = events;
|
||||
}
|
||||
this.webhookService.create(instance, {
|
||||
enabled: true,
|
||||
url: webhook,
|
||||
events: newEvents,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
});
|
||||
|
||||
webhookEvents = (await this.webhookService.find(instance)).events;
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let websocketEvents: string[];
|
||||
|
||||
if (websocket_enabled) {
|
||||
this.logger.verbose('creating websocket');
|
||||
try {
|
||||
let newEvents: string[] = [];
|
||||
if (websocket_events.length === 0) {
|
||||
newEvents = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = websocket_events;
|
||||
}
|
||||
this.websocketService.create(instance, {
|
||||
enabled: true,
|
||||
events: newEvents,
|
||||
});
|
||||
|
||||
websocketEvents = (await this.websocketService.find(instance)).events;
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let rabbitmqEvents: string[];
|
||||
|
||||
if (rabbitmq_enabled) {
|
||||
this.logger.verbose('creating rabbitmq');
|
||||
try {
|
||||
let newEvents: string[] = [];
|
||||
if (rabbitmq_events.length === 0) {
|
||||
newEvents = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = rabbitmq_events;
|
||||
}
|
||||
this.rabbitmqService.create(instance, {
|
||||
enabled: true,
|
||||
events: newEvents,
|
||||
});
|
||||
|
||||
rabbitmqEvents = (await this.rabbitmqService.find(instance)).events;
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let sqsEvents: string[];
|
||||
|
||||
if (sqs_enabled) {
|
||||
this.logger.verbose('creating sqs');
|
||||
try {
|
||||
let newEvents: string[] = [];
|
||||
if (sqs_events.length === 0) {
|
||||
newEvents = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
} else {
|
||||
newEvents = sqs_events;
|
||||
}
|
||||
this.sqsService.create(instance, {
|
||||
enabled: true,
|
||||
events: newEvents,
|
||||
});
|
||||
|
||||
sqsEvents = (await this.sqsService.find(instance)).events;
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (proxy) {
|
||||
const testProxy = await this.proxyService.testProxy(proxy);
|
||||
if (!testProxy) {
|
||||
throw new BadRequestException('Invalid proxy');
|
||||
}
|
||||
|
||||
await this.proxyService.createProxy(instance, {
|
||||
enabled: true,
|
||||
proxy,
|
||||
});
|
||||
}
|
||||
|
||||
if (typebot_url) {
|
||||
try {
|
||||
if (!isURL(typebot_url, { require_tld: false })) {
|
||||
throw new BadRequestException('Invalid "url" property in typebot_url');
|
||||
}
|
||||
|
||||
this.logger.verbose('creating typebot');
|
||||
|
||||
this.typebotService.create(instance, {
|
||||
enabled: true,
|
||||
url: typebot_url,
|
||||
typebot: typebot,
|
||||
expire: typebot_expire,
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('creating settings');
|
||||
const settings: wa.LocalSettings = {
|
||||
reject_call: reject_call || false,
|
||||
msg_call: msg_call || '',
|
||||
groups_ignore: groups_ignore || true,
|
||||
always_online: always_online || false,
|
||||
read_messages: read_messages || false,
|
||||
read_status: read_status || false,
|
||||
sync_full_history: sync_full_history ?? false,
|
||||
};
|
||||
|
||||
this.logger.verbose('settings: ' + JSON.stringify(settings));
|
||||
|
||||
this.settingsService.create(instance, settings);
|
||||
|
||||
let webhook_wa_business = null,
|
||||
access_token_wa_business = '';
|
||||
|
||||
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||
if (!number) {
|
||||
throw new BadRequestException('number is required');
|
||||
}
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
|
||||
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
|
||||
}
|
||||
|
||||
this.integrationService.create(instance, {
|
||||
integration,
|
||||
number,
|
||||
token,
|
||||
});
|
||||
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
|
||||
let getQrcode: wa.QrCode;
|
||||
|
||||
if (qrcode) {
|
||||
this.logger.verbose('creating qrcode');
|
||||
await instance.connectToWhatsapp(number, mobile);
|
||||
await delay(5000);
|
||||
getQrcode = instance.qrCode;
|
||||
}
|
||||
|
||||
const result = {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
webhook: {
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events: webhookEvents,
|
||||
},
|
||||
websocket: {
|
||||
enabled: websocket_enabled,
|
||||
events: websocketEvents,
|
||||
},
|
||||
rabbitmq: {
|
||||
enabled: rabbitmq_enabled,
|
||||
events: rabbitmqEvents,
|
||||
},
|
||||
sqs: {
|
||||
enabled: sqs_enabled,
|
||||
events: sqsEvents,
|
||||
},
|
||||
typebot: {
|
||||
enabled: typebot_url ? true : false,
|
||||
url: typebot_url,
|
||||
typebot,
|
||||
expire: typebot_expire,
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
},
|
||||
settings,
|
||||
qrcode: getQrcode,
|
||||
};
|
||||
|
||||
this.logger.verbose('instance created');
|
||||
this.logger.verbose(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!chatwoot_account_id) {
|
||||
throw new BadRequestException('account_id is required');
|
||||
}
|
||||
|
||||
if (!chatwoot_token) {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
if (!chatwoot_url) {
|
||||
throw new BadRequestException('url is required');
|
||||
}
|
||||
|
||||
if (!isURL(chatwoot_url, { require_tld: false })) {
|
||||
throw new BadRequestException('Invalid "url" property in chatwoot');
|
||||
}
|
||||
|
||||
if (chatwoot_sign_msg !== true && chatwoot_sign_msg !== false) {
|
||||
throw new BadRequestException('sign_msg is required');
|
||||
}
|
||||
|
||||
if (chatwoot_reopen_conversation !== true && chatwoot_reopen_conversation !== false) {
|
||||
throw new BadRequestException('reopen_conversation is required');
|
||||
}
|
||||
|
||||
if (chatwoot_conversation_pending !== true && chatwoot_conversation_pending !== false) {
|
||||
throw new BadRequestException('conversation_pending is required');
|
||||
}
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
try {
|
||||
this.chatwootService.create(instance, {
|
||||
enabled: true,
|
||||
account_id: chatwoot_account_id,
|
||||
token: chatwoot_token,
|
||||
url: chatwoot_url,
|
||||
sign_msg: chatwoot_sign_msg || false,
|
||||
name_inbox: instance.instanceName.split('-cwId-')[0],
|
||||
number,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
|
||||
auto_create: true,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
|
||||
return {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
webhook: {
|
||||
webhook,
|
||||
webhook_by_events,
|
||||
webhook_base64,
|
||||
events: webhookEvents,
|
||||
},
|
||||
websocket: {
|
||||
enabled: websocket_enabled,
|
||||
events: websocketEvents,
|
||||
},
|
||||
rabbitmq: {
|
||||
enabled: rabbitmq_enabled,
|
||||
events: rabbitmqEvents,
|
||||
},
|
||||
sqs: {
|
||||
enabled: sqs_enabled,
|
||||
events: sqsEvents,
|
||||
},
|
||||
typebot: {
|
||||
enabled: typebot_url ? true : false,
|
||||
url: typebot_url,
|
||||
typebot,
|
||||
expire: typebot_expire,
|
||||
keyword_finish: typebot_keyword_finish,
|
||||
delay_message: typebot_delay_message,
|
||||
unknown_message: typebot_unknown_message,
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
},
|
||||
settings,
|
||||
chatwoot: {
|
||||
enabled: true,
|
||||
account_id: chatwoot_account_id,
|
||||
token: chatwoot_token,
|
||||
url: chatwoot_url,
|
||||
sign_msg: chatwoot_sign_msg || false,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
|
||||
number,
|
||||
name_inbox: instance.instanceName,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error.message[0]);
|
||||
throw new BadRequestException(error.message[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public async connectToWhatsapp({ instanceName, number = null, mobile = null }: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance');
|
||||
|
||||
const instance = this.waMonitor.waInstances[instanceName];
|
||||
const state = instance?.connectionStatus?.state;
|
||||
|
||||
this.logger.verbose('state: ' + state);
|
||||
|
||||
if (!state) {
|
||||
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
|
||||
}
|
||||
|
||||
if (state == 'open') {
|
||||
return await this.connectionState({ instanceName });
|
||||
}
|
||||
|
||||
if (state == 'connecting') {
|
||||
return instance.qrCode;
|
||||
}
|
||||
|
||||
if (state == 'close') {
|
||||
this.logger.verbose('connecting');
|
||||
await instance.connectToWhatsapp(number, mobile);
|
||||
|
||||
await delay(5000);
|
||||
return instance.qrCode;
|
||||
}
|
||||
|
||||
return {
|
||||
instance: {
|
||||
instanceName: instanceName,
|
||||
status: state,
|
||||
},
|
||||
qrcode: instance?.qrCode,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async restartInstance({ instanceName }: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
|
||||
|
||||
const instance = this.waMonitor.waInstances[instanceName];
|
||||
const state = instance?.connectionStatus?.state;
|
||||
|
||||
switch (state) {
|
||||
case 'open':
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
instance.clearCacheChatwoot();
|
||||
await instance.reloadConnection();
|
||||
await delay(2000);
|
||||
|
||||
return await this.connectionState({ instanceName });
|
||||
default:
|
||||
return await this.connectionState({ instanceName });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async registerMobileCode({ instanceName }: InstanceDto, { mobileCode }: any) {
|
||||
try {
|
||||
this.logger.verbose('requested registerMobileCode from ' + instanceName + ' instance');
|
||||
|
||||
const instance = this.waMonitor.waInstances[instanceName];
|
||||
|
||||
console.log('mobileCode', mobileCode);
|
||||
await instance.receiveMobileCode(mobileCode);
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Mobile code registered' } };
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async connectionState({ instanceName }: InstanceDto) {
|
||||
this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
|
||||
return {
|
||||
instance: {
|
||||
instanceName: instanceName,
|
||||
state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto) {
|
||||
if (instanceName) {
|
||||
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
|
||||
this.logger.verbose('instanceName: ' + instanceName);
|
||||
return this.waMonitor.instanceInfo(instanceName);
|
||||
} else if (instanceId || number) {
|
||||
return this.waMonitor.instanceInfoById(instanceId, number);
|
||||
}
|
||||
|
||||
this.logger.verbose('requested fetchInstances (all instances)');
|
||||
return this.waMonitor.instanceInfo();
|
||||
}
|
||||
|
||||
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
||||
this.logger.verbose('requested sendPresence from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].setPresence(data);
|
||||
}
|
||||
|
||||
public async logout({ instanceName }: InstanceDto) {
|
||||
this.logger.verbose('requested logout from ' + instanceName + ' instance');
|
||||
const { instance } = await this.connectionState({ instanceName });
|
||||
|
||||
if (instance.state === 'close') {
|
||||
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
|
||||
}
|
||||
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
||||
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteInstance({ instanceName }: InstanceDto) {
|
||||
this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance');
|
||||
const { instance } = await this.connectionState({ instanceName });
|
||||
|
||||
if (instance.state === 'open') {
|
||||
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
|
||||
}
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
|
||||
if (instance.state === 'connecting') {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
|
||||
await this.logout({ instanceName });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting instance: ' + instanceName);
|
||||
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
delete this.waMonitor.waInstances[instanceName];
|
||||
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
||||
} catch (error) {
|
||||
throw new BadRequestException(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public async refreshToken(_: InstanceDto, oldToken: OldToken) {
|
||||
this.logger.verbose('requested refreshToken');
|
||||
return await this.authService.refreshToken(oldToken);
|
||||
}
|
||||
}
|
||||
20
src/api/controllers/label.controller.ts
Normal file
20
src/api/controllers/label.controller.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { HandleLabelDto } from '../dto/label.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('LabelController');
|
||||
|
||||
export class LabelController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async fetchLabels({ instanceName }: InstanceDto) {
|
||||
logger.verbose('requested fetchLabels from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchLabels();
|
||||
}
|
||||
|
||||
public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) {
|
||||
logger.verbose('requested chat label change from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].handleLabel(data);
|
||||
}
|
||||
}
|
||||
72
src/api/controllers/proxy.controller.ts
Normal file
72
src/api/controllers/proxy.controller.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException, NotFoundException } from '../../exceptions';
|
||||
import { makeProxyAgent } from '../../utils/makeProxyAgent';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ProxyDto } from '../dto/proxy.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
|
||||
const logger = new Logger('ProxyController');
|
||||
|
||||
export class ProxyController {
|
||||
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createProxy(instance: InstanceDto, data: ProxyDto) {
|
||||
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||
}
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('proxy disabled');
|
||||
data.proxy = null;
|
||||
}
|
||||
|
||||
if (data.proxy) {
|
||||
const testProxy = await this.testProxy(data.proxy);
|
||||
if (!testProxy) {
|
||||
throw new BadRequestException('Invalid proxy');
|
||||
}
|
||||
logger.verbose('proxy enabled');
|
||||
}
|
||||
|
||||
return this.proxyService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findProxy(instance: InstanceDto) {
|
||||
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||
}
|
||||
|
||||
return this.proxyService.find(instance);
|
||||
}
|
||||
|
||||
public async testProxy(proxy: ProxyDto['proxy']) {
|
||||
logger.verbose('requested testProxy');
|
||||
try {
|
||||
const serverIp = await axios.get('https://icanhazip.com/');
|
||||
const response = await axios.get('https://icanhazip.com/', {
|
||||
httpsAgent: makeProxyAgent(proxy),
|
||||
});
|
||||
|
||||
logger.verbose('[testProxy] from IP: ' + response?.data + ' To IP: ' + serverIp?.data);
|
||||
return response?.data !== serverIp?.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
logger.error('testProxy error: ' + error.response.data);
|
||||
} else if (axios.isAxiosError(error)) {
|
||||
logger.error('testProxy error: ');
|
||||
logger.verbose(error.cause ?? error.message);
|
||||
} else {
|
||||
logger.error('testProxy error: ');
|
||||
logger.verbose(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/api/controllers/sendMessage.controller.ts
Normal file
117
src/api/controllers/sendMessage.controller.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { isBase64, isURL } from 'class-validator';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import {
|
||||
SendAudioDto,
|
||||
SendButtonDto,
|
||||
SendContactDto,
|
||||
SendListDto,
|
||||
SendLocationDto,
|
||||
SendMediaDto,
|
||||
SendPollDto,
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('MessageRouter');
|
||||
|
||||
export class SendMessageController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async sendText({ instanceName }: InstanceDto, data: SendTextDto) {
|
||||
logger.verbose('requested sendText from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].textMessage(data);
|
||||
}
|
||||
|
||||
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
|
||||
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
|
||||
}
|
||||
|
||||
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
|
||||
logger.verbose('requested sendMedia from ' + instanceName + ' instance');
|
||||
|
||||
if (
|
||||
isBase64(data?.mediaMessage?.media) &&
|
||||
!data?.mediaMessage?.fileName &&
|
||||
data?.mediaMessage?.mediatype === 'document'
|
||||
) {
|
||||
throw new BadRequestException('For base64 the file name must be informed.');
|
||||
}
|
||||
|
||||
logger.verbose('isURL: ' + isURL(data?.mediaMessage?.media) + ', isBase64: ' + isBase64(data?.mediaMessage?.media));
|
||||
if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) {
|
||||
return await this.waMonitor.waInstances[instanceName].mediaMessage(data);
|
||||
}
|
||||
throw new BadRequestException('Owned media must be a url or base64');
|
||||
}
|
||||
|
||||
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) {
|
||||
logger.verbose('requested sendSticker from ' + instanceName + ' instance');
|
||||
|
||||
logger.verbose(
|
||||
'isURL: ' + isURL(data?.stickerMessage?.image) + ', isBase64: ' + isBase64(data?.stickerMessage?.image),
|
||||
);
|
||||
if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) {
|
||||
return await this.waMonitor.waInstances[instanceName].mediaSticker(data);
|
||||
}
|
||||
throw new BadRequestException('Owned media must be a url or base64');
|
||||
}
|
||||
|
||||
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) {
|
||||
logger.verbose('requested sendWhatsAppAudio from ' + instanceName + ' instance');
|
||||
|
||||
logger.verbose('isURL: ' + isURL(data?.audioMessage?.audio) + ', isBase64: ' + isBase64(data?.audioMessage?.audio));
|
||||
if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) {
|
||||
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data);
|
||||
}
|
||||
throw new BadRequestException('Owned media must be a url or base64');
|
||||
}
|
||||
|
||||
public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) {
|
||||
logger.verbose('requested sendButtons from ' + instanceName + ' instance');
|
||||
if (isBase64(data.buttonMessage.mediaMessage?.media) && !data.buttonMessage.mediaMessage?.fileName) {
|
||||
throw new BadRequestException('For bse64 the file name must be informed.');
|
||||
}
|
||||
return await this.waMonitor.waInstances[instanceName].buttonMessage(data);
|
||||
}
|
||||
|
||||
public async sendLocation({ instanceName }: InstanceDto, data: SendLocationDto) {
|
||||
logger.verbose('requested sendLocation from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].locationMessage(data);
|
||||
}
|
||||
|
||||
public async sendList({ instanceName }: InstanceDto, data: SendListDto) {
|
||||
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].listMessage(data);
|
||||
}
|
||||
|
||||
public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) {
|
||||
logger.verbose('requested sendContact from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].contactMessage(data);
|
||||
}
|
||||
|
||||
public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) {
|
||||
logger.verbose('requested sendReaction from ' + instanceName + ' instance');
|
||||
if (!data.reactionMessage.reaction.match(/[^()\w\sà-ú"-+]+/)) {
|
||||
throw new BadRequestException('"reaction" must be an emoji');
|
||||
}
|
||||
return await this.waMonitor.waInstances[instanceName].reactionMessage(data);
|
||||
}
|
||||
|
||||
public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) {
|
||||
logger.verbose('requested sendPoll from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].pollMessage(data);
|
||||
}
|
||||
|
||||
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) {
|
||||
logger.verbose('requested sendStatus from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].statusMessage(data);
|
||||
}
|
||||
}
|
||||
22
src/api/controllers/settings.controller.ts
Normal file
22
src/api/controllers/settings.controller.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
|
||||
const logger = new Logger('SettingsController');
|
||||
|
||||
export class SettingsController {
|
||||
constructor(private readonly settingsService: SettingsService) {}
|
||||
|
||||
public async createSettings(instance: InstanceDto, data: SettingsDto) {
|
||||
logger.verbose('requested createSettings from ' + instance.instanceName + ' instance');
|
||||
|
||||
return this.settingsService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findSettings(instance: InstanceDto) {
|
||||
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance');
|
||||
const settings = this.settingsService.find(instance);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
72
src/api/controllers/webhook.controller.ts
Normal file
72
src/api/controllers/webhook.controller.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { isURL } from 'class-validator';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
|
||||
const logger = new Logger('WebhookController');
|
||||
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
|
||||
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!isURL(data.url, { require_tld: false })) {
|
||||
throw new BadRequestException('Invalid "url" property');
|
||||
}
|
||||
|
||||
data.enabled = data.enabled ?? true;
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('webhook disabled');
|
||||
data.url = '';
|
||||
data.events = [];
|
||||
} else if (data.events.length === 0) {
|
||||
logger.verbose('webhook events empty');
|
||||
data.events = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
return this.webhookService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findWebhook(instance: InstanceDto) {
|
||||
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
|
||||
return this.webhookService.find(instance);
|
||||
}
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
|
||||
}
|
||||
}
|
||||
7
src/api/dto/chamaai.dto.ts
Normal file
7
src/api/dto/chamaai.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export class ChamaaiDto {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
token: string;
|
||||
waNumber: string;
|
||||
answerByAudio: boolean;
|
||||
}
|
||||
122
src/api/dto/chat.dto.ts
Normal file
122
src/api/dto/chat.dto.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
|
||||
|
||||
export class OnWhatsAppDto {
|
||||
constructor(
|
||||
public readonly jid: string,
|
||||
public readonly exists: boolean,
|
||||
public readonly number: string,
|
||||
public readonly name?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class getBase64FromMediaMessageDto {
|
||||
message: proto.WebMessageInfo;
|
||||
convertToMp4?: boolean;
|
||||
}
|
||||
|
||||
export class WhatsAppNumberDto {
|
||||
numbers: string[];
|
||||
}
|
||||
|
||||
export class NumberDto {
|
||||
number: string;
|
||||
}
|
||||
|
||||
export class NumberBusiness {
|
||||
wid?: string;
|
||||
jid?: string;
|
||||
exists?: boolean;
|
||||
isBusiness: boolean;
|
||||
name?: string;
|
||||
message?: string;
|
||||
description?: string;
|
||||
email?: string;
|
||||
websites?: string[];
|
||||
website?: string[];
|
||||
address?: string;
|
||||
about?: string;
|
||||
vertical?: string;
|
||||
profilehandle?: string;
|
||||
}
|
||||
|
||||
export class ProfileNameDto {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ProfileStatusDto {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export class ProfilePictureDto {
|
||||
number?: string;
|
||||
// url or base64
|
||||
picture?: string;
|
||||
}
|
||||
|
||||
class Key {
|
||||
id: string;
|
||||
fromMe: boolean;
|
||||
remoteJid: string;
|
||||
}
|
||||
export class ReadMessageDto {
|
||||
read_messages: Key[];
|
||||
}
|
||||
|
||||
export class LastMessage {
|
||||
key: Key;
|
||||
messageTimestamp?: number;
|
||||
}
|
||||
|
||||
export class ArchiveChatDto {
|
||||
lastMessage?: LastMessage;
|
||||
chat?: string;
|
||||
archive: boolean;
|
||||
}
|
||||
|
||||
class PrivacySetting {
|
||||
readreceipts: WAReadReceiptsValue;
|
||||
profile: WAPrivacyValue;
|
||||
status: WAPrivacyValue;
|
||||
online: WAPrivacyOnlineValue;
|
||||
last: WAPrivacyValue;
|
||||
groupadd: WAPrivacyValue;
|
||||
}
|
||||
|
||||
export class PrivacySettingDto {
|
||||
privacySettings: PrivacySetting;
|
||||
}
|
||||
|
||||
export class DeleteMessage {
|
||||
id: string;
|
||||
fromMe: boolean;
|
||||
remoteJid: string;
|
||||
participant?: string;
|
||||
}
|
||||
export class Options {
|
||||
delay?: number;
|
||||
presence?: WAPresence;
|
||||
}
|
||||
class OptionsMessage {
|
||||
options: Options;
|
||||
}
|
||||
export class Metadata extends OptionsMessage {
|
||||
number: string;
|
||||
}
|
||||
|
||||
export class SendPresenceDto extends Metadata {
|
||||
options: {
|
||||
presence: WAPresence;
|
||||
delay: number;
|
||||
};
|
||||
}
|
||||
|
||||
export class UpdateMessageDto extends Metadata {
|
||||
number: string;
|
||||
key: proto.IMessageKey;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class BlockUserDto {
|
||||
number: string;
|
||||
status: 'block' | 'unblock';
|
||||
}
|
||||
56
src/api/dto/group.dto.ts
Normal file
56
src/api/dto/group.dto.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export class CreateGroupDto {
|
||||
subject: string;
|
||||
participants: string[];
|
||||
description?: string;
|
||||
promoteParticipants?: boolean;
|
||||
}
|
||||
|
||||
export class GroupPictureDto {
|
||||
groupJid: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export class GroupSubjectDto {
|
||||
groupJid: string;
|
||||
subject: string;
|
||||
}
|
||||
|
||||
export class GroupDescriptionDto {
|
||||
groupJid: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export class GroupJid {
|
||||
groupJid: string;
|
||||
}
|
||||
|
||||
export class GetParticipant {
|
||||
getParticipants: string;
|
||||
}
|
||||
|
||||
export class GroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class AcceptGroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class GroupSendInvite {
|
||||
groupJid: string;
|
||||
description: string;
|
||||
numbers: string[];
|
||||
}
|
||||
|
||||
export class GroupUpdateParticipantDto extends GroupJid {
|
||||
action: 'add' | 'remove' | 'promote' | 'demote';
|
||||
participants: string[];
|
||||
}
|
||||
|
||||
export class GroupUpdateSettingDto extends GroupJid {
|
||||
action: 'announcement' | 'not_announcement' | 'unlocked' | 'locked';
|
||||
}
|
||||
|
||||
export class GroupToggleEphemeralDto extends GroupJid {
|
||||
expiration: 0 | 86400 | 604800 | 7776000;
|
||||
}
|
||||
51
src/api/dto/instance.dto.ts
Normal file
51
src/api/dto/instance.dto.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { WAPresence } from '@whiskeysockets/baileys';
|
||||
|
||||
import { ProxyDto } from './proxy.dto';
|
||||
|
||||
export class InstanceDto {
|
||||
instanceName: string;
|
||||
instanceId?: string;
|
||||
qrcode?: boolean;
|
||||
number?: string;
|
||||
mobile?: boolean;
|
||||
integration?: string;
|
||||
token?: string;
|
||||
webhook?: string;
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
events?: string[];
|
||||
reject_call?: boolean;
|
||||
msg_call?: string;
|
||||
groups_ignore?: boolean;
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
chatwoot_account_id?: string;
|
||||
chatwoot_token?: string;
|
||||
chatwoot_url?: string;
|
||||
chatwoot_sign_msg?: boolean;
|
||||
chatwoot_reopen_conversation?: boolean;
|
||||
chatwoot_conversation_pending?: boolean;
|
||||
chatwoot_import_contacts?: boolean;
|
||||
chatwoot_import_messages?: boolean;
|
||||
chatwoot_days_limit_import_messages?: number;
|
||||
websocket_enabled?: boolean;
|
||||
websocket_events?: string[];
|
||||
rabbitmq_enabled?: boolean;
|
||||
rabbitmq_events?: string[];
|
||||
sqs_enabled?: boolean;
|
||||
sqs_events?: string[];
|
||||
typebot_url?: string;
|
||||
typebot?: string;
|
||||
typebot_expire?: number;
|
||||
typebot_keyword_finish?: string;
|
||||
typebot_delay_message?: number;
|
||||
typebot_unknown_message?: string;
|
||||
typebot_listening_from_me?: boolean;
|
||||
proxy?: ProxyDto['proxy'];
|
||||
}
|
||||
|
||||
export class SetPresenceDto {
|
||||
presence: WAPresence;
|
||||
}
|
||||
5
src/api/dto/integration.dto.ts
Normal file
5
src/api/dto/integration.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class IntegrationDto {
|
||||
integration: string;
|
||||
number: string;
|
||||
token: string;
|
||||
}
|
||||
12
src/api/dto/label.dto.ts
Normal file
12
src/api/dto/label.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export class LabelDto {
|
||||
id?: string;
|
||||
name: string;
|
||||
color: number;
|
||||
predefinedId?: string;
|
||||
}
|
||||
|
||||
export class HandleLabelDto {
|
||||
number: string;
|
||||
labelId: string;
|
||||
action: 'add' | 'remove';
|
||||
}
|
||||
12
src/api/dto/proxy.dto.ts
Normal file
12
src/api/dto/proxy.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
class Proxy {
|
||||
host: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyDto {
|
||||
enabled: boolean;
|
||||
proxy: Proxy;
|
||||
}
|
||||
165
src/api/dto/sendMessage.dto.ts
Normal file
165
src/api/dto/sendMessage.dto.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { proto, WAPresence } from '@whiskeysockets/baileys';
|
||||
|
||||
export class Quoted {
|
||||
key: proto.IMessageKey;
|
||||
message: proto.IMessage;
|
||||
}
|
||||
|
||||
export class Mentions {
|
||||
everyOne: boolean;
|
||||
mentioned: string[];
|
||||
}
|
||||
|
||||
export class Options {
|
||||
delay?: number;
|
||||
presence?: WAPresence;
|
||||
quoted?: Quoted;
|
||||
mentions?: Mentions;
|
||||
linkPreview?: boolean;
|
||||
encoding?: boolean;
|
||||
}
|
||||
class OptionsMessage {
|
||||
options: Options;
|
||||
}
|
||||
|
||||
export class Metadata extends OptionsMessage {
|
||||
number: string;
|
||||
}
|
||||
|
||||
class TextMessage {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class StatusMessage {
|
||||
type: string;
|
||||
content: string;
|
||||
statusJidList?: string[];
|
||||
allContacts?: boolean;
|
||||
caption?: string;
|
||||
backgroundColor?: string;
|
||||
font?: number;
|
||||
}
|
||||
|
||||
class PollMessage {
|
||||
name: string;
|
||||
selectableCount: number;
|
||||
values: string[];
|
||||
messageSecret?: Uint8Array;
|
||||
}
|
||||
|
||||
export class SendTextDto extends Metadata {
|
||||
textMessage: TextMessage;
|
||||
}
|
||||
export class SendPresence extends Metadata {
|
||||
textMessage: TextMessage;
|
||||
}
|
||||
|
||||
export class SendStatusDto extends Metadata {
|
||||
statusMessage: StatusMessage;
|
||||
}
|
||||
|
||||
export class SendPollDto extends Metadata {
|
||||
pollMessage: PollMessage;
|
||||
}
|
||||
|
||||
export type MediaType = 'image' | 'document' | 'video' | 'audio';
|
||||
export class MediaMessage {
|
||||
mediatype: MediaType;
|
||||
mimetype?: string;
|
||||
caption?: string;
|
||||
// for document
|
||||
fileName?: string;
|
||||
// url or base64
|
||||
media: string;
|
||||
}
|
||||
export class SendMediaDto extends Metadata {
|
||||
mediaMessage: MediaMessage;
|
||||
}
|
||||
class Sticker {
|
||||
image: string;
|
||||
}
|
||||
export class SendStickerDto extends Metadata {
|
||||
stickerMessage: Sticker;
|
||||
}
|
||||
|
||||
class Audio {
|
||||
audio: string;
|
||||
}
|
||||
export class SendAudioDto extends Metadata {
|
||||
audioMessage: Audio;
|
||||
}
|
||||
|
||||
class Button {
|
||||
buttonText: string;
|
||||
buttonId: string;
|
||||
}
|
||||
class ButtonMessage {
|
||||
title: string;
|
||||
description: string;
|
||||
footerText?: string;
|
||||
buttons: Button[];
|
||||
mediaMessage?: MediaMessage;
|
||||
}
|
||||
export class SendButtonDto extends Metadata {
|
||||
buttonMessage: ButtonMessage;
|
||||
}
|
||||
|
||||
class LocationMessage {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name?: string;
|
||||
address?: string;
|
||||
}
|
||||
export class SendLocationDto extends Metadata {
|
||||
locationMessage: LocationMessage;
|
||||
}
|
||||
|
||||
class Row {
|
||||
title: string;
|
||||
description: string;
|
||||
rowId: string;
|
||||
}
|
||||
class Section {
|
||||
title: string;
|
||||
rows: Row[];
|
||||
}
|
||||
class ListMessage {
|
||||
title: string;
|
||||
description: string;
|
||||
footerText?: string;
|
||||
buttonText: string;
|
||||
sections: Section[];
|
||||
}
|
||||
export class SendListDto extends Metadata {
|
||||
listMessage: ListMessage;
|
||||
}
|
||||
|
||||
export class ContactMessage {
|
||||
fullName: string;
|
||||
wuid: string;
|
||||
phoneNumber: string;
|
||||
organization?: string;
|
||||
email?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class TemplateMessage {
|
||||
name: string;
|
||||
language: string;
|
||||
components: any;
|
||||
}
|
||||
|
||||
export class SendTemplateDto extends Metadata {
|
||||
templateMessage: TemplateMessage;
|
||||
}
|
||||
export class SendContactDto extends Metadata {
|
||||
contactMessage: ContactMessage[];
|
||||
}
|
||||
|
||||
class ReactionMessage {
|
||||
key: proto.IMessageKey;
|
||||
reaction: string;
|
||||
}
|
||||
export class SendReactionDto {
|
||||
reactionMessage: ReactionMessage;
|
||||
}
|
||||
9
src/api/dto/settings.dto.ts
Normal file
9
src/api/dto/settings.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class SettingsDto {
|
||||
reject_call?: boolean;
|
||||
msg_call?: string;
|
||||
groups_ignore?: boolean;
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
7
src/api/dto/webhook.dto.ts
Normal file
7
src/api/dto/webhook.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export class WebhookDto {
|
||||
enabled?: boolean;
|
||||
url?: string;
|
||||
events?: string[];
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
}
|
||||
83
src/api/guards/auth.guard.ts
Normal file
83
src/api/guards/auth.guard.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { isJWT } from 'class-validator';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { name } from '../../../package.json';
|
||||
import { Auth, configService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ForbiddenException, UnauthorizedException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { repository } from '../server.module';
|
||||
import { JwtPayload } from '../services/auth.service';
|
||||
|
||||
const logger = new Logger('GUARD');
|
||||
|
||||
async function jwtGuard(req: Request, res: Response, next: NextFunction) {
|
||||
const key = req.get('apikey');
|
||||
|
||||
if (key && configService.get<Auth>('AUTHENTICATION').API_KEY.KEY !== key) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
if (configService.get<Auth>('AUTHENTICATION').API_KEY.KEY === key) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
|
||||
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
|
||||
}
|
||||
|
||||
const jwtOpts = configService.get<Auth>('AUTHENTICATION').JWT;
|
||||
try {
|
||||
const [bearer, token] = req.get('authorization').split(' ');
|
||||
|
||||
if (bearer.toLowerCase() !== 'bearer') {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
if (!isJWT(token)) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const param = req.params as unknown as InstanceDto;
|
||||
const decode = jwt.verify(token, jwtOpts.SECRET, {
|
||||
ignoreExpiration: jwtOpts.EXPIRIN_IN === 0,
|
||||
}) as JwtPayload;
|
||||
|
||||
if (param.instanceName !== decode.instanceName || name !== decode.apiName) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
async function apikey(req: Request, _: Response, next: NextFunction) {
|
||||
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
|
||||
const key = req.get('apikey');
|
||||
|
||||
if (env.KEY === key) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
|
||||
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
|
||||
}
|
||||
|
||||
try {
|
||||
const param = req.params as unknown as InstanceDto;
|
||||
const instanceKey = await repository.auth.find(param.instanceName);
|
||||
if (instanceKey.apikey === key) {
|
||||
return next();
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
export const authGuard = { jwt: jwtGuard, apikey };
|
||||
74
src/api/guards/instance.guard.ts
Normal file
74
src/api/guards/instance.guard.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { configService, Database, Redis } from '../../config/env.config';
|
||||
import { INSTANCE_DIR } from '../../config/path.config';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
} from '../../exceptions';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { cache, waMonitor } from '../server.module';
|
||||
|
||||
async function getInstance(instanceName: string) {
|
||||
try {
|
||||
const db = configService.get<Database>('DATABASE');
|
||||
const redisConf = configService.get<Redis>('REDIS');
|
||||
|
||||
const exists = !!waMonitor.waInstances[instanceName];
|
||||
|
||||
if (redisConf.ENABLED) {
|
||||
const keyExists = await cache.keyExists();
|
||||
return exists || keyExists;
|
||||
}
|
||||
|
||||
if (db.ENABLED) {
|
||||
const collection = dbserver
|
||||
.getClient()
|
||||
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
|
||||
.collection(instanceName);
|
||||
return exists || (await collection.find({}).toArray()).length > 0;
|
||||
}
|
||||
|
||||
return exists || existsSync(join(INSTANCE_DIR, instanceName));
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(error?.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
|
||||
if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const param = req.params as unknown as InstanceDto;
|
||||
if (!param?.instanceName) {
|
||||
throw new BadRequestException('"instanceName" not provided.');
|
||||
}
|
||||
|
||||
if (!(await getInstance(param.instanceName))) {
|
||||
throw new NotFoundException(`The "${param.instanceName}" instance does not exist`);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {
|
||||
if (req.originalUrl.includes('/instance/create')) {
|
||||
const instance = req.body as InstanceDto;
|
||||
if (await getInstance(instance.instanceName)) {
|
||||
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
|
||||
}
|
||||
|
||||
if (waMonitor.waInstances[instance.instanceName]) {
|
||||
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
|
||||
delete waMonitor.waInstances[instance.instanceName];
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
22
src/api/integrations/chatwoot/cache/cacheengine.ts
vendored
Normal file
22
src/api/integrations/chatwoot/cache/cacheengine.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CacheConf, ConfigService } from '../../../../config/env.config';
|
||||
import { ICache } from '../../../abstract/abstract.cache';
|
||||
import { LocalCache } from './localcache';
|
||||
import { RedisCache } from './rediscache';
|
||||
|
||||
export class CacheEngine {
|
||||
private engine: ICache;
|
||||
|
||||
constructor(private readonly configService: ConfigService, module: string) {
|
||||
const cacheConf = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
|
||||
this.engine = new RedisCache(configService, module);
|
||||
} else if (cacheConf?.LOCAL?.ENABLED) {
|
||||
this.engine = new LocalCache(configService, module);
|
||||
}
|
||||
}
|
||||
|
||||
public getEngine() {
|
||||
return this.engine;
|
||||
}
|
||||
}
|
||||
48
src/api/integrations/chatwoot/cache/localcache.ts
vendored
Normal file
48
src/api/integrations/chatwoot/cache/localcache.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
import { CacheConf, CacheConfLocal, ConfigService } from '../../../../config/env.config';
|
||||
import { ICache } from '../../../abstract/abstract.cache';
|
||||
|
||||
export class LocalCache implements ICache {
|
||||
private conf: CacheConfLocal;
|
||||
static localCache = new NodeCache();
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
return LocalCache.localCache.get(this.buildKey(key));
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
return LocalCache.localCache.set(this.buildKey(key), value, ttl || this.conf.TTL);
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
return LocalCache.localCache.has(this.buildKey(key));
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
return LocalCache.localCache.del(this.buildKey(key));
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return LocalCache.localCache.del(keys);
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
const filter = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}`;
|
||||
|
||||
return LocalCache.localCache.keys().filter((key) => key.substring(0, filter.length) === filter);
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.module}:${key}`;
|
||||
}
|
||||
}
|
||||
59
src/api/integrations/chatwoot/cache/rediscache.client.ts
vendored
Normal file
59
src/api/integrations/chatwoot/cache/rediscache.client.ts
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createClient, RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, configService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
|
||||
class Redis {
|
||||
private logger = new Logger(Redis.name);
|
||||
private client: RedisClientType = null;
|
||||
private conf: CacheConfRedis;
|
||||
private connected = false;
|
||||
|
||||
constructor() {
|
||||
this.conf = configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
}
|
||||
|
||||
getConnection(): RedisClientType {
|
||||
if (this.connected) {
|
||||
return this.client;
|
||||
} else {
|
||||
this.client = createClient({
|
||||
url: this.conf.URI,
|
||||
});
|
||||
|
||||
this.client.on('connect', () => {
|
||||
this.logger.verbose('redis connecting');
|
||||
});
|
||||
|
||||
this.client.on('ready', () => {
|
||||
this.logger.verbose('redis ready');
|
||||
this.connected = true;
|
||||
});
|
||||
|
||||
this.client.on('error', () => {
|
||||
this.logger.error('redis disconnected');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
this.client.on('end', () => {
|
||||
this.logger.verbose('redis connection ended');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.verbose('connecting new redis client');
|
||||
this.client.connect();
|
||||
this.connected = true;
|
||||
this.logger.verbose('connected to new redis client');
|
||||
} catch (e) {
|
||||
this.connected = false;
|
||||
this.logger.error('redis connect exception caught: ' + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const redisClient = new Redis();
|
||||
83
src/api/integrations/chatwoot/cache/rediscache.ts
vendored
Normal file
83
src/api/integrations/chatwoot/cache/rediscache.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
import { RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { ICache } from '../../../abstract/abstract.cache';
|
||||
import { redisClient } from './rediscache.client';
|
||||
|
||||
export class RedisCache implements ICache {
|
||||
private readonly logger = new Logger(RedisCache.name);
|
||||
private client: RedisClientType;
|
||||
private conf: CacheConfRedis;
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
this.client = redisClient.getConnection();
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
try {
|
||||
return JSON.parse(await this.client.get(this.buildKey(key)));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
try {
|
||||
await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
try {
|
||||
return (await this.client.exists(this.buildKey(key))) > 0;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
try {
|
||||
return await this.client.del(this.buildKey(key));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
try {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await this.client.del(keys);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
try {
|
||||
const match = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}*`;
|
||||
const keys = [];
|
||||
for await (const key of this.client.scanIterator({
|
||||
MATCH: match,
|
||||
COUNT: 100,
|
||||
})) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
return [...new Set(keys)];
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.conf?.PREFIX_KEY}:${this.module}:${key}`;
|
||||
}
|
||||
}
|
||||
108
src/api/integrations/chatwoot/controllers/chatwoot.controller.ts
Normal file
108
src/api/integrations/chatwoot/controllers/chatwoot.controller.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { isURL } from 'class-validator';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { BadRequestException } from '../../../../exceptions';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../../../repository/repository.manager';
|
||||
import { waMonitor } from '../../../server.module';
|
||||
import { CacheService } from '../../../services/cache.service';
|
||||
import { CacheEngine } from '../cache/cacheengine';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
|
||||
const logger = new Logger('ChatwootController');
|
||||
|
||||
export class ChatwootController {
|
||||
constructor(
|
||||
private readonly chatwootService: ChatwootService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
) {}
|
||||
|
||||
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
|
||||
logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (data.enabled) {
|
||||
if (!isURL(data.url, { require_tld: false })) {
|
||||
throw new BadRequestException('url is not valid');
|
||||
}
|
||||
|
||||
if (!data.account_id) {
|
||||
throw new BadRequestException('account_id is required');
|
||||
}
|
||||
|
||||
if (!data.token) {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
if (data.sign_msg !== true && data.sign_msg !== false) {
|
||||
throw new BadRequestException('sign_msg is required');
|
||||
}
|
||||
if (data.sign_msg === false) data.sign_delimiter = null;
|
||||
}
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('chatwoot disabled');
|
||||
data.account_id = '';
|
||||
data.token = '';
|
||||
data.url = '';
|
||||
data.sign_msg = false;
|
||||
data.sign_delimiter = null;
|
||||
data.reopen_conversation = false;
|
||||
data.conversation_pending = false;
|
||||
data.import_contacts = false;
|
||||
data.import_messages = false;
|
||||
data.days_limit_import_messages = 0;
|
||||
data.auto_create = false;
|
||||
}
|
||||
|
||||
data.name_inbox = instance.instanceName;
|
||||
|
||||
const result = await this.chatwootService.create(instance, data);
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
const response = {
|
||||
...result,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async findChatwoot(instance: InstanceDto) {
|
||||
logger.verbose('requested findChatwoot from ' + instance.instanceName + ' instance');
|
||||
const result = await this.chatwootService.find(instance);
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
if (Object.keys(result || {}).length === 0) {
|
||||
return {
|
||||
enabled: false,
|
||||
url: '',
|
||||
account_id: '',
|
||||
token: '',
|
||||
sign_msg: false,
|
||||
name_inbox: '',
|
||||
webhook_url: '',
|
||||
};
|
||||
}
|
||||
|
||||
const response = {
|
||||
...result,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
|
||||
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
|
||||
|
||||
return chatwootService.receiveWebhook(instance, data);
|
||||
}
|
||||
}
|
||||
16
src/api/integrations/chatwoot/dto/chatwoot.dto.ts
Normal file
16
src/api/integrations/chatwoot/dto/chatwoot.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export class ChatwootDto {
|
||||
enabled?: boolean;
|
||||
account_id?: string;
|
||||
token?: string;
|
||||
url?: string;
|
||||
name_inbox?: string;
|
||||
sign_msg?: boolean;
|
||||
sign_delimiter?: string;
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
auto_create?: boolean;
|
||||
}
|
||||
49
src/api/integrations/chatwoot/libs/postgres.client.ts
Normal file
49
src/api/integrations/chatwoot/libs/postgres.client.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import postgresql from 'pg';
|
||||
|
||||
import { Chatwoot, configService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
|
||||
const { Pool } = postgresql;
|
||||
|
||||
class Postgres {
|
||||
private logger = new Logger(Postgres.name);
|
||||
private pool;
|
||||
private connected = false;
|
||||
|
||||
getConnection(connectionString: string) {
|
||||
if (this.connected) {
|
||||
return this.pool;
|
||||
} else {
|
||||
this.pool = new Pool({
|
||||
connectionString,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
this.pool.on('error', () => {
|
||||
this.logger.error('postgres disconnected');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.verbose('connecting new postgres');
|
||||
this.connected = true;
|
||||
} catch (e) {
|
||||
this.connected = false;
|
||||
this.logger.error('postgres connect exception caught: ' + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.pool;
|
||||
}
|
||||
}
|
||||
|
||||
getChatwootConnection() {
|
||||
const uri = configService.get<Chatwoot>('CHATWOOT').IMPORT.DATABASE.CONNECTION.URI;
|
||||
|
||||
return this.getConnection(uri);
|
||||
}
|
||||
}
|
||||
|
||||
export const postgresClient = new Postgres();
|
||||
40
src/api/integrations/chatwoot/models/chatwoot.model.ts
Normal file
40
src/api/integrations/chatwoot/models/chatwoot.model.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../../../libs/db.connect';
|
||||
|
||||
export class ChatwootRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
account_id?: string;
|
||||
token?: string;
|
||||
url?: string;
|
||||
name_inbox?: string;
|
||||
sign_msg?: boolean;
|
||||
sign_delimiter?: string;
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
}
|
||||
|
||||
const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
account_id: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
url: { type: String, required: true },
|
||||
name_inbox: { type: String, required: true },
|
||||
sign_msg: { type: Boolean, required: true },
|
||||
sign_delimiter: { type: String, required: false },
|
||||
number: { type: String, required: true },
|
||||
reopen_conversation: { type: Boolean, required: true },
|
||||
conversation_pending: { type: Boolean, required: true },
|
||||
import_contacts: { type: Boolean, required: true },
|
||||
import_messages: { type: Boolean, required: true },
|
||||
days_limit_import_messages: { type: Number, required: true },
|
||||
});
|
||||
|
||||
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');
|
||||
export type IChatwootModel = typeof ChatwootModel;
|
||||
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||
import { ChatwootRaw, IChatwootModel } from '../../../models';
|
||||
|
||||
export class ChatwootRepository extends Repository {
|
||||
constructor(private readonly chatwootModel: IChatwootModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ChatwootRepository');
|
||||
|
||||
public async create(data: ChatwootRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating chatwoot');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving chatwoot to db');
|
||||
const insert = await this.chatwootModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving chatwoot to store');
|
||||
|
||||
this.writeStore<ChatwootRaw>({
|
||||
path: join(this.storePath, 'chatwoot'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('chatwoot saved to store in path: ' + join(this.storePath, 'chatwoot') + '/' + instance);
|
||||
|
||||
this.logger.verbose('chatwoot created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<ChatwootRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding chatwoot');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding chatwoot in db');
|
||||
return await this.chatwootModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding chatwoot in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'chatwoot', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as ChatwootRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/api/integrations/chatwoot/routes/chatwoot.router.ts
Normal file
68
src/api/integrations/chatwoot/routes/chatwoot.router.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { chatwootSchema, instanceNameSchema } from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routers/index.router';
|
||||
import { chatwootController } from '../../../server.module';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
|
||||
const logger = new Logger('ChatwootRouter');
|
||||
|
||||
export class ChatwootRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setChatwoot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<ChatwootDto>({
|
||||
request: req,
|
||||
schema: chatwootSchema,
|
||||
ClassRef: ChatwootDto,
|
||||
execute: (instance, data) => chatwootController.createChatwoot(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findChatwoot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => chatwootController.findChatwoot(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('webhook'), async (req, res) => {
|
||||
logger.verbose('request received in findChatwoot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => chatwootController.receiveWebhook(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
2352
src/api/integrations/chatwoot/services/chatwoot.service.ts
Normal file
2352
src/api/integrations/chatwoot/services/chatwoot.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
472
src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts
Normal file
472
src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts
Normal file
@@ -0,0 +1,472 @@
|
||||
import { inbox } from '@figuro/chatwoot-sdk';
|
||||
import { proto } from '@whiskeysockets/baileys';
|
||||
|
||||
import { InstanceDto } from '../../../../api/dto/instance.dto';
|
||||
import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../../api/models';
|
||||
import { Chatwoot, configService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { postgresClient } from '../libs/postgres.client';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
|
||||
type ChatwootUser = {
|
||||
user_type: string;
|
||||
user_id: number;
|
||||
};
|
||||
|
||||
type FksChatwoot = {
|
||||
phone_number: string;
|
||||
contact_id: string;
|
||||
conversation_id: string;
|
||||
};
|
||||
|
||||
type firstLastTimestamp = {
|
||||
first: number;
|
||||
last: number;
|
||||
};
|
||||
|
||||
type IWebMessageInfo = Omit<proto.IWebMessageInfo, 'key'> & Partial<Pick<proto.IWebMessageInfo, 'key'>>;
|
||||
|
||||
class ChatwootImport {
|
||||
private logger = new Logger(ChatwootImport.name);
|
||||
private repositoryMessagesCache = new Map<string, Set<string>>();
|
||||
private historyMessages = new Map<string, MessageRaw[]>();
|
||||
private historyContacts = new Map<string, ContactRaw[]>();
|
||||
|
||||
public getRepositoryMessagesCache(instance: InstanceDto) {
|
||||
return this.repositoryMessagesCache.has(instance.instanceName)
|
||||
? this.repositoryMessagesCache.get(instance.instanceName)
|
||||
: null;
|
||||
}
|
||||
|
||||
public setRepositoryMessagesCache(instance: InstanceDto, repositoryMessagesCache: Set<string>) {
|
||||
this.repositoryMessagesCache.set(instance.instanceName, repositoryMessagesCache);
|
||||
}
|
||||
|
||||
public deleteRepositoryMessagesCache(instance: InstanceDto) {
|
||||
this.repositoryMessagesCache.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public addHistoryMessages(instance: InstanceDto, messagesRaw: MessageRaw[]) {
|
||||
const actualValue = this.historyMessages.has(instance.instanceName)
|
||||
? this.historyMessages.get(instance.instanceName)
|
||||
: [];
|
||||
this.historyMessages.set(instance.instanceName, actualValue.concat(messagesRaw));
|
||||
}
|
||||
|
||||
public addHistoryContacts(instance: InstanceDto, contactsRaw: ContactRaw[]) {
|
||||
const actualValue = this.historyContacts.has(instance.instanceName)
|
||||
? this.historyContacts.get(instance.instanceName)
|
||||
: [];
|
||||
this.historyContacts.set(instance.instanceName, actualValue.concat(contactsRaw));
|
||||
}
|
||||
|
||||
public deleteHistoryMessages(instance: InstanceDto) {
|
||||
this.historyMessages.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public deleteHistoryContacts(instance: InstanceDto) {
|
||||
this.historyContacts.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public clearAll(instance: InstanceDto) {
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteHistoryContacts(instance);
|
||||
}
|
||||
|
||||
public getHistoryMessagesLenght(instance: InstanceDto) {
|
||||
return this.historyMessages.get(instance.instanceName)?.length ?? 0;
|
||||
}
|
||||
|
||||
public async importHistoryContacts(instance: InstanceDto, provider: ChatwootRaw) {
|
||||
try {
|
||||
if (this.getHistoryMessagesLenght(instance) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
let totalContactsImported = 0;
|
||||
|
||||
const contacts = this.historyContacts.get(instance.instanceName) || [];
|
||||
if (contacts.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let contactsChunk: ContactRaw[] = this.sliceIntoChunks(contacts, 3000);
|
||||
while (contactsChunk.length > 0) {
|
||||
// inserting contacts in chatwoot db
|
||||
let sqlInsert = `INSERT INTO contacts
|
||||
(name, phone_number, account_id, identifier, created_at, updated_at) VALUES `;
|
||||
const bindInsert = [provider.account_id];
|
||||
|
||||
for (const contact of contactsChunk) {
|
||||
bindInsert.push(contact.pushName);
|
||||
const bindName = `$${bindInsert.length}`;
|
||||
|
||||
bindInsert.push(`+${contact.id.split('@')[0]}`);
|
||||
const bindPhoneNumber = `$${bindInsert.length}`;
|
||||
|
||||
bindInsert.push(contact.id);
|
||||
const bindIdentifier = `$${bindInsert.length}`;
|
||||
|
||||
sqlInsert += `(${bindName}, ${bindPhoneNumber}, $1, ${bindIdentifier}, NOW(), NOW()),`;
|
||||
}
|
||||
if (sqlInsert.slice(-1) === ',') {
|
||||
sqlInsert = sqlInsert.slice(0, -1);
|
||||
}
|
||||
sqlInsert += ` ON CONFLICT (identifier, account_id)
|
||||
DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
phone_number = EXCLUDED.phone_number,
|
||||
identifier = EXCLUDED.identifier`;
|
||||
|
||||
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
|
||||
contactsChunk = this.sliceIntoChunks(contacts, 3000);
|
||||
}
|
||||
|
||||
this.deleteHistoryContacts(instance);
|
||||
|
||||
return totalContactsImported;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on import history contacts: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async importHistoryMessages(
|
||||
instance: InstanceDto,
|
||||
chatwootService: ChatwootService,
|
||||
inbox: inbox,
|
||||
provider: ChatwootRaw,
|
||||
) {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const chatwootUser = await this.getChatwootUser(provider);
|
||||
if (!chatwootUser) {
|
||||
throw new Error('User not found to import messages.');
|
||||
}
|
||||
|
||||
let totalMessagesImported = 0;
|
||||
|
||||
const messagesOrdered = this.historyMessages.get(instance.instanceName) || [];
|
||||
if (messagesOrdered.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ordering messages by number and timestamp asc
|
||||
messagesOrdered.sort((a, b) => {
|
||||
return (
|
||||
parseInt(a.key.remoteJid) - parseInt(b.key.remoteJid) ||
|
||||
(a.messageTimestamp as number) - (b.messageTimestamp as number)
|
||||
);
|
||||
});
|
||||
|
||||
const allMessagesMappedByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesOrdered);
|
||||
// Map structure: +552199999999 => { first message timestamp from number, last message timestamp from number}
|
||||
const phoneNumbersWithTimestamp = new Map<string, firstLastTimestamp>();
|
||||
allMessagesMappedByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||
phoneNumbersWithTimestamp.set(phoneNumber, {
|
||||
first: messages[0]?.messageTimestamp as number,
|
||||
last: messages[messages.length - 1]?.messageTimestamp as number,
|
||||
});
|
||||
});
|
||||
|
||||
// processing messages in batch
|
||||
const batchSize = 4000;
|
||||
let messagesChunk: MessageRaw[] = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||
while (messagesChunk.length > 0) {
|
||||
// Map structure: +552199999999 => MessageRaw[]
|
||||
const messagesByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesChunk);
|
||||
|
||||
if (messagesByPhoneNumber.size > 0) {
|
||||
const fksByNumber = await this.selectOrCreateFksFromChatwoot(
|
||||
provider,
|
||||
inbox,
|
||||
phoneNumbersWithTimestamp,
|
||||
messagesByPhoneNumber,
|
||||
);
|
||||
|
||||
// inserting messages in chatwoot db
|
||||
let sqlInsertMsg = `INSERT INTO messages
|
||||
(content, account_id, inbox_id, conversation_id, message_type, private, content_type,
|
||||
sender_type, sender_id, created_at, updated_at) VALUES `;
|
||||
const bindInsertMsg = [provider.account_id, inbox.id];
|
||||
|
||||
messagesByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||
const fksChatwoot = fksByNumber.get(phoneNumber);
|
||||
|
||||
messages.forEach((message) => {
|
||||
if (!message.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fksChatwoot?.conversation_id || !fksChatwoot?.contact_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentMessage = this.getContentMessage(chatwootService, message);
|
||||
if (!contentMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
bindInsertMsg.push(contentMessage);
|
||||
const bindContent = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(fksChatwoot.conversation_id);
|
||||
const bindConversationId = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? '1' : '0');
|
||||
const bindMessageType = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_type : 'Contact');
|
||||
const bindSenderType = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_id : fksChatwoot.contact_id);
|
||||
const bindSenderId = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.messageTimestamp as number);
|
||||
const bindmessageTimestamp = `$${bindInsertMsg.length}`;
|
||||
|
||||
sqlInsertMsg += `(${bindContent}, $1, $2, ${bindConversationId}, ${bindMessageType}, FALSE, 0,
|
||||
${bindSenderType},${bindSenderId}, to_timestamp(${bindmessageTimestamp}), to_timestamp(${bindmessageTimestamp})),`;
|
||||
});
|
||||
});
|
||||
if (bindInsertMsg.length > 2) {
|
||||
if (sqlInsertMsg.slice(-1) === ',') {
|
||||
sqlInsertMsg = sqlInsertMsg.slice(0, -1);
|
||||
}
|
||||
totalMessagesImported += (await pgClient.query(sqlInsertMsg, bindInsertMsg))?.rowCount ?? 0;
|
||||
}
|
||||
}
|
||||
messagesChunk = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||
}
|
||||
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
|
||||
this.importHistoryContacts(instance, provider);
|
||||
|
||||
return totalMessagesImported;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on import history messages: ${error.toString()}`);
|
||||
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
}
|
||||
}
|
||||
|
||||
public async selectOrCreateFksFromChatwoot(
|
||||
provider: ChatwootRaw,
|
||||
inbox: inbox,
|
||||
phoneNumbersWithTimestamp: Map<string, firstLastTimestamp>,
|
||||
messagesByPhoneNumber: Map<string, MessageRaw[]>,
|
||||
): Promise<Map<string, FksChatwoot>> {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const bindValues = [provider.account_id, inbox.id];
|
||||
const phoneNumberBind = Array.from(messagesByPhoneNumber.keys())
|
||||
.map((phoneNumber) => {
|
||||
const phoneNumberTimestamp = phoneNumbersWithTimestamp.get(phoneNumber);
|
||||
|
||||
if (phoneNumberTimestamp) {
|
||||
bindValues.push(phoneNumber);
|
||||
let bindStr = `($${bindValues.length},`;
|
||||
|
||||
bindValues.push(phoneNumberTimestamp.first);
|
||||
bindStr += `$${bindValues.length},`;
|
||||
|
||||
bindValues.push(phoneNumberTimestamp.last);
|
||||
return `${bindStr}$${bindValues.length})`;
|
||||
}
|
||||
})
|
||||
.join(',');
|
||||
|
||||
// select (or insert when necessary) data from tables contacts, contact_inboxes, conversations from chatwoot db
|
||||
const sqlFromChatwoot = `WITH
|
||||
phone_number AS (
|
||||
SELECT phone_number, created_at::INTEGER, last_activity_at::INTEGER FROM (
|
||||
VALUES
|
||||
${phoneNumberBind}
|
||||
) as t (phone_number, created_at, last_activity_at)
|
||||
),
|
||||
|
||||
only_new_phone_number AS (
|
||||
SELECT * FROM phone_number
|
||||
WHERE phone_number NOT IN (
|
||||
SELECT phone_number
|
||||
FROM contacts
|
||||
JOIN contact_inboxes ci ON ci.contact_id = contacts.id AND ci.inbox_id = $2
|
||||
JOIN conversations con ON con.contact_inbox_id = ci.id
|
||||
AND con.account_id = $1
|
||||
AND con.inbox_id = $2
|
||||
AND con.contact_id = contacts.id
|
||||
WHERE contacts.account_id = $1
|
||||
)
|
||||
),
|
||||
|
||||
new_contact AS (
|
||||
INSERT INTO contacts (name, phone_number, account_id, identifier, created_at, updated_at)
|
||||
SELECT REPLACE(p.phone_number, '+', ''), p.phone_number, $1, CONCAT(REPLACE(p.phone_number, '+', ''),
|
||||
'@s.whatsapp.net'), to_timestamp(p.created_at), to_timestamp(p.last_activity_at)
|
||||
FROM only_new_phone_number AS p
|
||||
ON CONFLICT(identifier, account_id) DO UPDATE SET updated_at = EXCLUDED.updated_at
|
||||
RETURNING id, phone_number, created_at, updated_at
|
||||
),
|
||||
|
||||
new_contact_inbox AS (
|
||||
INSERT INTO contact_inboxes (contact_id, inbox_id, source_id, created_at, updated_at)
|
||||
SELECT new_contact.id, $2, gen_random_uuid(), new_contact.created_at, new_contact.updated_at
|
||||
FROM new_contact
|
||||
RETURNING id, contact_id, created_at, updated_at
|
||||
),
|
||||
|
||||
new_conversation AS (
|
||||
INSERT INTO conversations (account_id, inbox_id, status, contact_id,
|
||||
contact_inbox_id, uuid, last_activity_at, created_at, updated_at)
|
||||
SELECT $1, $2, 0, new_contact_inbox.contact_id, new_contact_inbox.id, gen_random_uuid(),
|
||||
new_contact_inbox.updated_at, new_contact_inbox.created_at, new_contact_inbox.updated_at
|
||||
FROM new_contact_inbox
|
||||
RETURNING id, contact_id
|
||||
)
|
||||
|
||||
SELECT new_contact.phone_number, new_conversation.contact_id, new_conversation.id AS conversation_id
|
||||
FROM new_conversation
|
||||
JOIN new_contact ON new_conversation.contact_id = new_contact.id
|
||||
|
||||
UNION
|
||||
|
||||
SELECT p.phone_number, c.id contact_id, con.id conversation_id
|
||||
FROM phone_number p
|
||||
JOIN contacts c ON c.phone_number = p.phone_number
|
||||
JOIN contact_inboxes ci ON ci.contact_id = c.id AND ci.inbox_id = $2
|
||||
JOIN conversations con ON con.contact_inbox_id = ci.id AND con.account_id = $1
|
||||
AND con.inbox_id = $2 AND con.contact_id = c.id`;
|
||||
|
||||
const fksFromChatwoot = await pgClient.query(sqlFromChatwoot, bindValues);
|
||||
|
||||
return new Map(fksFromChatwoot.rows.map((item: FksChatwoot) => [item.phone_number, item]));
|
||||
}
|
||||
|
||||
public async getChatwootUser(provider: ChatwootRaw): Promise<ChatwootUser> {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const sqlUser = `SELECT owner_type AS user_type, owner_id AS user_id
|
||||
FROM access_tokens
|
||||
WHERE token = $1`;
|
||||
|
||||
return (await pgClient.query(sqlUser, [provider.token]))?.rows[0] || false;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on getChatwootUser: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public createMessagesMapByPhoneNumber(messages: MessageRaw[]): Map<string, MessageRaw[]> {
|
||||
return messages.reduce((acc: Map<string, MessageRaw[]>, message: MessageRaw) => {
|
||||
if (!this.isIgnorePhoneNumber(message?.key?.remoteJid)) {
|
||||
const phoneNumber = message?.key?.remoteJid?.split('@')[0];
|
||||
if (phoneNumber) {
|
||||
const phoneNumberPlus = `+${phoneNumber}`;
|
||||
const messages = acc.has(phoneNumberPlus) ? acc.get(phoneNumberPlus) : [];
|
||||
messages.push(message);
|
||||
acc.set(phoneNumberPlus, messages);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
public async getContactsOrderByRecentConversations(
|
||||
inbox: inbox,
|
||||
provider: ChatwootRaw,
|
||||
limit = 50,
|
||||
): Promise<{ id: number; phone_number: string; identifier: string }[]> {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const sql = `SELECT contacts.id, contacts.identifier, contacts.phone_number
|
||||
FROM conversations
|
||||
JOIN contacts ON contacts.id = conversations.contact_id
|
||||
WHERE conversations.account_id = $1
|
||||
AND inbox_id = $2
|
||||
ORDER BY conversations.last_activity_at DESC
|
||||
LIMIT $3`;
|
||||
|
||||
return (await pgClient.query(sql, [provider.account_id, inbox.id, limit]))?.rows;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on get recent conversations: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getContentMessage(chatwootService: ChatwootService, msg: IWebMessageInfo) {
|
||||
const contentMessage = chatwootService.getConversationMessage(msg.message);
|
||||
if (contentMessage) {
|
||||
return contentMessage;
|
||||
}
|
||||
|
||||
if (!configService.get<Chatwoot>('CHATWOOT').IMPORT.PLACEHOLDER_MEDIA_MESSAGE) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const types = {
|
||||
documentMessage: msg.message.documentMessage,
|
||||
documentWithCaptionMessage: msg.message.documentWithCaptionMessage?.message?.documentMessage,
|
||||
imageMessage: msg.message.imageMessage,
|
||||
videoMessage: msg.message.videoMessage,
|
||||
audioMessage: msg.message.audioMessage,
|
||||
stickerMessage: msg.message.stickerMessage,
|
||||
templateMessage: msg.message.templateMessage?.hydratedTemplate?.hydratedContentText,
|
||||
};
|
||||
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||
|
||||
switch (typeKey) {
|
||||
case 'documentMessage':
|
||||
return `_<File: ${msg.message.documentMessage.fileName}${
|
||||
msg.message.documentMessage.caption ? ` ${msg.message.documentMessage.caption}` : ''
|
||||
}>_`;
|
||||
|
||||
case 'documentWithCaptionMessage':
|
||||
return `_<File: ${msg.message.documentWithCaptionMessage.message.documentMessage.fileName}${
|
||||
msg.message.documentWithCaptionMessage.message.documentMessage.caption
|
||||
? ` ${msg.message.documentWithCaptionMessage.message.documentMessage.caption}`
|
||||
: ''
|
||||
}>_`;
|
||||
|
||||
case 'templateMessage':
|
||||
return msg.message.templateMessage.hydratedTemplate.hydratedTitleText
|
||||
? `*${msg.message.templateMessage.hydratedTemplate.hydratedTitleText}*\\n`
|
||||
: '' + msg.message.templateMessage.hydratedTemplate.hydratedContentText;
|
||||
|
||||
case 'imageMessage':
|
||||
return '_<Image Message>_';
|
||||
|
||||
case 'videoMessage':
|
||||
return '_<Video Message>_';
|
||||
|
||||
case 'audioMessage':
|
||||
return '_<Audio Message>_';
|
||||
|
||||
case 'stickerMessage':
|
||||
return '_<Sticker Message>_';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public sliceIntoChunks(arr: any[], chunkSize: number) {
|
||||
return arr.splice(0, chunkSize);
|
||||
}
|
||||
|
||||
public isGroup(remoteJid: string) {
|
||||
return remoteJid.includes('@g.us');
|
||||
}
|
||||
|
||||
public isIgnorePhoneNumber(remoteJid: string) {
|
||||
return this.isGroup(remoteJid) || remoteJid === 'status@broadcast' || remoteJid === '0@s.whatsapp.net';
|
||||
}
|
||||
}
|
||||
|
||||
export const chatwootImport = new ChatwootImport();
|
||||
42
src/api/integrations/chatwoot/validate/chatwoot.schema.ts
Normal file
42
src/api/integrations/chatwoot/validate/chatwoot.schema.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const chatwootSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
account_id: { type: 'string' },
|
||||
token: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
sign_msg: { type: 'boolean', enum: [true, false] },
|
||||
sign_delimiter: { type: ['string', 'null'] },
|
||||
reopen_conversation: { type: 'boolean', enum: [true, false] },
|
||||
conversation_pending: { type: 'boolean', enum: [true, false] },
|
||||
auto_create: { type: 'boolean', enum: [true, false] },
|
||||
import_contacts: { type: 'boolean', enum: [true, false] },
|
||||
import_messages: { type: 'boolean', enum: [true, false] },
|
||||
days_limit_import_messages: { type: 'number' },
|
||||
},
|
||||
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
|
||||
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||
|
||||
const logger = new Logger('RabbitmqController');
|
||||
|
||||
export class RabbitmqController {
|
||||
constructor(private readonly rabbitmqService: RabbitmqService) {}
|
||||
|
||||
public async createRabbitmq(instance: InstanceDto, data: RabbitmqDto) {
|
||||
logger.verbose('requested createRabbitmq from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('rabbitmq disabled');
|
||||
data.events = [];
|
||||
}
|
||||
|
||||
if (data.events.length === 0) {
|
||||
logger.verbose('rabbitmq events empty');
|
||||
data.events = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
return this.rabbitmqService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findRabbitmq(instance: InstanceDto) {
|
||||
logger.verbose('requested findRabbitmq from ' + instance.instanceName + ' instance');
|
||||
return this.rabbitmqService.find(instance);
|
||||
}
|
||||
}
|
||||
4
src/api/integrations/rabbitmq/dto/rabbitmq.dto.ts
Normal file
4
src/api/integrations/rabbitmq/dto/rabbitmq.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class RabbitmqDto {
|
||||
enabled: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
100
src/api/integrations/rabbitmq/libs/amqp.server.ts
Normal file
100
src/api/integrations/rabbitmq/libs/amqp.server.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as amqp from 'amqplib/callback_api';
|
||||
|
||||
import { configService, Rabbitmq } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
|
||||
const logger = new Logger('AMQP');
|
||||
|
||||
let amqpChannel: amqp.Channel | null = null;
|
||||
|
||||
export const initAMQP = () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
|
||||
amqp.connect(uri, (error, connection) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
connection.createChannel((channelError, channel) => {
|
||||
if (channelError) {
|
||||
reject(channelError);
|
||||
return;
|
||||
}
|
||||
|
||||
const exchangeName = 'evolution_exchange';
|
||||
|
||||
channel.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
});
|
||||
|
||||
amqpChannel = channel;
|
||||
|
||||
logger.info('AMQP initialized');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const getAMQP = (): amqp.Channel | null => {
|
||||
return amqpChannel;
|
||||
};
|
||||
|
||||
export const initQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||
});
|
||||
|
||||
queues.forEach((event) => {
|
||||
const amqp = getAMQP();
|
||||
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
});
|
||||
|
||||
const queueName = `${instanceName}.${event}`;
|
||||
|
||||
amqp.assertQueue(queueName, {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
arguments: {
|
||||
'x-queue-type': 'quorum',
|
||||
},
|
||||
});
|
||||
|
||||
amqp.bindQueue(queueName, exchangeName, event);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const channel = getAMQP();
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||
});
|
||||
|
||||
const exchangeName = instanceName ?? 'evolution_exchange';
|
||||
|
||||
queues.forEach((event) => {
|
||||
const amqp = getAMQP();
|
||||
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
});
|
||||
|
||||
const queueName = `${instanceName}.${event}`;
|
||||
|
||||
amqp.deleteQueue(queueName);
|
||||
});
|
||||
|
||||
channel.deleteExchange(exchangeName);
|
||||
};
|
||||
18
src/api/integrations/rabbitmq/models/rabbitmq.model.ts
Normal file
18
src/api/integrations/rabbitmq/models/rabbitmq.model.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../../../libs/db.connect';
|
||||
|
||||
export class RabbitmqRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
|
||||
const rabbitmqSchema = new Schema<RabbitmqRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
events: { type: [String], required: true },
|
||||
});
|
||||
|
||||
export const RabbitmqModel = dbserver?.model(RabbitmqRaw.name, rabbitmqSchema, 'rabbitmq');
|
||||
export type IRabbitmqModel = typeof RabbitmqModel;
|
||||
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||
import { IRabbitmqModel, RabbitmqRaw } from '../../../models';
|
||||
|
||||
export class RabbitmqRepository extends Repository {
|
||||
constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('RabbitmqRepository');
|
||||
|
||||
public async create(data: RabbitmqRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating rabbitmq');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving rabbitmq to db');
|
||||
const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving rabbitmq to store');
|
||||
|
||||
this.writeStore<RabbitmqRaw>({
|
||||
path: join(this.storePath, 'rabbitmq'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance);
|
||||
|
||||
this.logger.verbose('rabbitmq created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<RabbitmqRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding rabbitmq');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding rabbitmq in db');
|
||||
return await this.rabbitmqModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding rabbitmq in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as RabbitmqRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/api/integrations/rabbitmq/routes/rabbitmq.router.ts
Normal file
52
src/api/integrations/rabbitmq/routes/rabbitmq.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { instanceNameSchema, rabbitmqSchema } from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routers/index.router';
|
||||
import { rabbitmqController } from '../../../server.module';
|
||||
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||
|
||||
const logger = new Logger('RabbitmqRouter');
|
||||
|
||||
export class RabbitmqRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setRabbitmq');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<RabbitmqDto>({
|
||||
request: req,
|
||||
schema: rabbitmqSchema,
|
||||
ClassRef: RabbitmqDto,
|
||||
execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findRabbitmq');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => rabbitmqController.findRabbitmq(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
35
src/api/integrations/rabbitmq/services/rabbitmq.service.ts
Normal file
35
src/api/integrations/rabbitmq/services/rabbitmq.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { RabbitmqRaw } from '../../../models';
|
||||
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||
import { RabbitmqDto } from '../dto/rabbitmq.dto';
|
||||
import { initQueues } from '../libs/amqp.server';
|
||||
|
||||
export class RabbitmqService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
private readonly logger = new Logger(RabbitmqService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: RabbitmqDto) {
|
||||
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
|
||||
|
||||
initQueues(instance.instanceName, data.events);
|
||||
return { rabbitmq: { ...instance, rabbitmq: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<RabbitmqRaw> {
|
||||
try {
|
||||
this.logger.verbose('find rabbitmq: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Rabbitmq not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, events: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts
Normal file
66
src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const rabbitmqSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
events: {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['enabled'],
|
||||
...isNotEmpty('enabled'),
|
||||
};
|
||||
58
src/api/integrations/sqs/controllers/sqs.controller.ts
Normal file
58
src/api/integrations/sqs/controllers/sqs.controller.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { SqsDto } from '../dto/sqs.dto';
|
||||
import { SqsService } from '../services/sqs.service';
|
||||
|
||||
const logger = new Logger('SqsController');
|
||||
|
||||
export class SqsController {
|
||||
constructor(private readonly sqsService: SqsService) {}
|
||||
|
||||
public async createSqs(instance: InstanceDto, data: SqsDto) {
|
||||
logger.verbose('requested createSqs from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('sqs disabled');
|
||||
data.events = [];
|
||||
}
|
||||
|
||||
if (data.events.length === 0) {
|
||||
logger.verbose('sqs events empty');
|
||||
data.events = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
return this.sqsService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findSqs(instance: InstanceDto) {
|
||||
logger.verbose('requested findSqs from ' + instance.instanceName + ' instance');
|
||||
return this.sqsService.find(instance);
|
||||
}
|
||||
}
|
||||
4
src/api/integrations/sqs/dto/sqs.dto.ts
Normal file
4
src/api/integrations/sqs/dto/sqs.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class SqsDto {
|
||||
enabled: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
97
src/api/integrations/sqs/libs/sqs.server.ts
Normal file
97
src/api/integrations/sqs/libs/sqs.server.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { SQS } from 'aws-sdk';
|
||||
|
||||
import { configService, Sqs } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
|
||||
const logger = new Logger('SQS');
|
||||
|
||||
let sqs: SQS;
|
||||
|
||||
export const initSQS = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const awsConfig = configService.get<Sqs>('SQS');
|
||||
sqs = new SQS({
|
||||
accessKeyId: awsConfig.ACCESS_KEY_ID,
|
||||
secretAccessKey: awsConfig.SECRET_ACCESS_KEY,
|
||||
region: awsConfig.REGION,
|
||||
});
|
||||
|
||||
logger.info('SQS initialized');
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
export const getSQS = (): SQS => {
|
||||
return sqs;
|
||||
};
|
||||
|
||||
export const initQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||
});
|
||||
|
||||
const sqs = getSQS();
|
||||
|
||||
queues.forEach((event) => {
|
||||
const queueName = `${instanceName}_${event}.fifo`;
|
||||
|
||||
sqs.createQueue(
|
||||
{
|
||||
QueueName: queueName,
|
||||
Attributes: {
|
||||
FifoQueue: 'true',
|
||||
},
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
logger.error(`Error creating queue ${queueName}: ${err.message}`);
|
||||
} else {
|
||||
logger.info(`Queue ${queueName} created: ${data.QueueUrl}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const removeQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
|
||||
const sqs = getSQS();
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '_').toLowerCase()}`;
|
||||
});
|
||||
|
||||
queues.forEach((event) => {
|
||||
const queueName = `${instanceName}_${event}.fifo`;
|
||||
|
||||
sqs.getQueueUrl(
|
||||
{
|
||||
QueueName: queueName,
|
||||
},
|
||||
(err, data) => {
|
||||
if (err) {
|
||||
logger.error(`Error getting queue URL for ${queueName}: ${err.message}`);
|
||||
} else {
|
||||
const queueUrl = data.QueueUrl;
|
||||
|
||||
sqs.deleteQueue(
|
||||
{
|
||||
QueueUrl: queueUrl,
|
||||
},
|
||||
(deleteErr) => {
|
||||
if (deleteErr) {
|
||||
logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`);
|
||||
} else {
|
||||
logger.info(`Queue ${queueName} deleted`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
18
src/api/integrations/sqs/models/sqs.model.ts
Normal file
18
src/api/integrations/sqs/models/sqs.model.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../../../libs/db.connect';
|
||||
|
||||
export class SqsRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
|
||||
const sqsSchema = new Schema<SqsRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
events: { type: [String], required: true },
|
||||
});
|
||||
|
||||
export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs');
|
||||
export type ISqsModel = typeof SqsModel;
|
||||
62
src/api/integrations/sqs/repository/sqs.repository.ts
Normal file
62
src/api/integrations/sqs/repository/sqs.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||
import { ISqsModel, SqsRaw } from '../../../models';
|
||||
|
||||
export class SqsRepository extends Repository {
|
||||
constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('SqsRepository');
|
||||
|
||||
public async create(data: SqsRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating sqs');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving sqs to db');
|
||||
const insert = await this.sqsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('sqs saved to db: ' + insert.modifiedCount + ' sqs');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving sqs to store');
|
||||
|
||||
this.writeStore<SqsRaw>({
|
||||
path: join(this.storePath, 'sqs'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('sqs saved to store in path: ' + join(this.storePath, 'sqs') + '/' + instance);
|
||||
|
||||
this.logger.verbose('sqs created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<SqsRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding sqs');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding sqs in db');
|
||||
return await this.sqsModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding sqs in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'sqs', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as SqsRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/api/integrations/sqs/routes/sqs.router.ts
Normal file
52
src/api/integrations/sqs/routes/sqs.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { instanceNameSchema, sqsSchema } from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routers/index.router';
|
||||
import { sqsController } from '../../../server.module';
|
||||
import { SqsDto } from '../dto/sqs.dto';
|
||||
|
||||
const logger = new Logger('SqsRouter');
|
||||
|
||||
export class SqsRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setSqs');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SqsDto>({
|
||||
request: req,
|
||||
schema: sqsSchema,
|
||||
ClassRef: SqsDto,
|
||||
execute: (instance, data) => sqsController.createSqs(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findSqs');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => sqsController.findSqs(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
35
src/api/integrations/sqs/services/sqs.service.ts
Normal file
35
src/api/integrations/sqs/services/sqs.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { SqsRaw } from '../../../models';
|
||||
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||
import { SqsDto } from '../dto/sqs.dto';
|
||||
import { initQueues } from '../libs/sqs.server';
|
||||
|
||||
export class SqsService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
private readonly logger = new Logger(SqsService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: SqsDto) {
|
||||
this.logger.verbose('create sqs: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setSqs(data);
|
||||
|
||||
initQueues(instance.instanceName, data.events);
|
||||
return { sqs: { ...instance, sqs: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<SqsRaw> {
|
||||
try {
|
||||
this.logger.verbose('find sqs: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findSqs();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Sqs not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, events: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/api/integrations/sqs/validate/sqs.schema.ts
Normal file
66
src/api/integrations/sqs/validate/sqs.schema.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const sqsSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
events: {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['enabled'],
|
||||
...isNotEmpty('enabled'),
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { TypebotDto } from '../dto/typebot.dto';
|
||||
import { TypebotService } from '../services/typebot.service';
|
||||
|
||||
const logger = new Logger('TypebotController');
|
||||
|
||||
export class TypebotController {
|
||||
constructor(private readonly typebotService: TypebotService) {}
|
||||
|
||||
public async createTypebot(instance: InstanceDto, data: TypebotDto) {
|
||||
logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('typebot disabled');
|
||||
data.url = '';
|
||||
data.typebot = '';
|
||||
data.expire = 0;
|
||||
data.sessions = [];
|
||||
} else {
|
||||
const saveData = await this.typebotService.find(instance);
|
||||
|
||||
if (saveData.enabled) {
|
||||
logger.verbose('typebot enabled');
|
||||
data.sessions = saveData.sessions;
|
||||
}
|
||||
}
|
||||
|
||||
return this.typebotService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findTypebot(instance: InstanceDto) {
|
||||
logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance');
|
||||
return this.typebotService.find(instance);
|
||||
}
|
||||
|
||||
public async changeStatus(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance');
|
||||
return this.typebotService.changeStatus(instance, data);
|
||||
}
|
||||
|
||||
public async startTypebot(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested startTypebot from ' + instance.instanceName + ' instance');
|
||||
return this.typebotService.startTypebot(instance, data);
|
||||
}
|
||||
}
|
||||
27
src/api/integrations/typebot/dto/typebot.dto.ts
Normal file
27
src/api/integrations/typebot/dto/typebot.dto.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export class Session {
|
||||
remoteJid?: string;
|
||||
sessionId?: string;
|
||||
status?: string;
|
||||
createdAt?: number;
|
||||
updateAt?: number;
|
||||
prefilledVariables?: PrefilledVariables;
|
||||
}
|
||||
|
||||
export class PrefilledVariables {
|
||||
remoteJid?: string;
|
||||
pushName?: string;
|
||||
messageType?: string;
|
||||
additionalData?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export class TypebotDto {
|
||||
enabled?: boolean;
|
||||
url: string;
|
||||
typebot?: string;
|
||||
expire?: number;
|
||||
keyword_finish?: string;
|
||||
delay_message?: number;
|
||||
unknown_message?: string;
|
||||
listening_from_me?: boolean;
|
||||
sessions?: Session[];
|
||||
}
|
||||
58
src/api/integrations/typebot/models/typebot.model.ts
Normal file
58
src/api/integrations/typebot/models/typebot.model.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../../../libs/db.connect';
|
||||
|
||||
class Session {
|
||||
remoteJid?: string;
|
||||
sessionId?: string;
|
||||
status?: string;
|
||||
createdAt?: number;
|
||||
updateAt?: number;
|
||||
prefilledVariables?: {
|
||||
remoteJid?: string;
|
||||
pushName?: string;
|
||||
additionalData?: { [key: string]: any };
|
||||
};
|
||||
}
|
||||
|
||||
export class TypebotRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
url: string;
|
||||
typebot?: string;
|
||||
expire?: number;
|
||||
keyword_finish?: string;
|
||||
delay_message?: number;
|
||||
unknown_message?: string;
|
||||
listening_from_me?: boolean;
|
||||
sessions?: Session[];
|
||||
}
|
||||
|
||||
const typebotSchema = new Schema<TypebotRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
url: { type: String, required: true },
|
||||
typebot: { type: String, required: true },
|
||||
expire: { type: Number, required: true },
|
||||
keyword_finish: { type: String, required: true },
|
||||
delay_message: { type: Number, required: true },
|
||||
unknown_message: { type: String, required: true },
|
||||
listening_from_me: { type: Boolean, required: true },
|
||||
sessions: [
|
||||
{
|
||||
remoteJid: { type: String, required: true },
|
||||
sessionId: { type: String, required: true },
|
||||
status: { type: String, required: true },
|
||||
createdAt: { type: Number, required: true },
|
||||
updateAt: { type: Number, required: true },
|
||||
prefilledVariables: {
|
||||
remoteJid: { type: String, required: false },
|
||||
pushName: { type: String, required: false },
|
||||
additionalData: { type: Schema.Types.Mixed, required: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot');
|
||||
export type ITypebotModel = typeof TypebotModel;
|
||||
@@ -0,0 +1,68 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||
import { ITypebotModel, TypebotRaw } from '../../../models';
|
||||
|
||||
export class TypebotRepository extends Repository {
|
||||
constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('TypebotRepository');
|
||||
|
||||
public async create(data: TypebotRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating typebot');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving typebot to db');
|
||||
const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving typebot to store');
|
||||
|
||||
this.writeStore<TypebotRaw>({
|
||||
path: join(this.storePath, 'typebot'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance);
|
||||
|
||||
this.logger.verbose('typebot created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<TypebotRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding typebot');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding typebot in db');
|
||||
return await this.typebotModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding typebot in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'typebot', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as TypebotRaw;
|
||||
} catch (error) {
|
||||
return {
|
||||
enabled: false,
|
||||
url: '',
|
||||
typebot: '',
|
||||
expire: 0,
|
||||
sessions: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/api/integrations/typebot/routes/typebot.router.ts
Normal file
89
src/api/integrations/typebot/routes/typebot.router.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import {
|
||||
instanceNameSchema,
|
||||
typebotSchema,
|
||||
typebotStartSchema,
|
||||
typebotStatusSchema,
|
||||
} from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routers/index.router';
|
||||
import { typebotController } from '../../../server.module';
|
||||
import { TypebotDto } from '../dto/typebot.dto';
|
||||
|
||||
const logger = new Logger('TypebotRouter');
|
||||
|
||||
export class TypebotRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setTypebot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<TypebotDto>({
|
||||
request: req,
|
||||
schema: typebotSchema,
|
||||
ClassRef: TypebotDto,
|
||||
execute: (instance, data) => typebotController.createTypebot(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findTypebot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => typebotController.findTypebot(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in changeStatusTypebot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: typebotStatusSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => typebotController.changeStatus(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('start'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in startTypebot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: typebotStartSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => typebotController.startTypebot(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
966
src/api/integrations/typebot/services/typebot.service.ts
Normal file
966
src/api/integrations/typebot/services/typebot.service.ts
Normal file
@@ -0,0 +1,966 @@
|
||||
import axios from 'axios';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
|
||||
import { ConfigService, Typebot } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { MessageRaw } from '../../../models';
|
||||
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||
import { Events } from '../../../types/wa.types';
|
||||
import { Session, TypebotDto } from '../dto/typebot.dto';
|
||||
|
||||
export class TypebotService {
|
||||
constructor(
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
this.eventEmitter.on('typebot:end', async (data) => {
|
||||
const keep_open = this.configService.get<Typebot>('TYPEBOT').KEEP_OPEN;
|
||||
if (keep_open) return;
|
||||
|
||||
await this.clearSessions(data.instance, data.remoteJid);
|
||||
});
|
||||
}
|
||||
|
||||
private readonly logger = new Logger(TypebotService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: TypebotDto) {
|
||||
this.logger.verbose('create typebot: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setTypebot(data);
|
||||
|
||||
return { typebot: { ...instance, typebot: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<TypebotDto> {
|
||||
try {
|
||||
this.logger.verbose('find typebot: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Typebot not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] };
|
||||
}
|
||||
}
|
||||
|
||||
public async changeStatus(instance: InstanceDto, data: any) {
|
||||
const remoteJid = data.remoteJid;
|
||||
const status = data.status;
|
||||
|
||||
const findData = await this.find(instance);
|
||||
|
||||
const session = findData.sessions.find((session) => session.remoteJid === remoteJid);
|
||||
|
||||
if (session) {
|
||||
if (status === 'closed') {
|
||||
const found_session: Session[] = findData.sessions.splice(findData.sessions.indexOf(session), 1);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findData.enabled,
|
||||
url: findData.url,
|
||||
typebot: findData.typebot,
|
||||
expire: findData.expire,
|
||||
keyword_finish: findData.keyword_finish,
|
||||
delay_message: findData.delay_message,
|
||||
unknown_message: findData.unknown_message,
|
||||
listening_from_me: findData.listening_from_me,
|
||||
sessions: found_session,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return { typebot: { ...instance, typebot: typebotData } };
|
||||
}
|
||||
|
||||
findData.sessions.map((session) => {
|
||||
if (session.remoteJid === remoteJid) {
|
||||
session.status = status;
|
||||
}
|
||||
});
|
||||
} else if (status === 'paused') {
|
||||
const session: Session = {
|
||||
remoteJid: remoteJid,
|
||||
sessionId: Math.floor(Math.random() * 10000000000).toString(),
|
||||
status: status,
|
||||
createdAt: Date.now(),
|
||||
updateAt: Date.now(),
|
||||
prefilledVariables: {
|
||||
remoteJid: remoteJid,
|
||||
pushName: '',
|
||||
additionalData: {},
|
||||
},
|
||||
};
|
||||
findData.sessions.push(session);
|
||||
}
|
||||
|
||||
const typebotData = {
|
||||
enabled: findData.enabled,
|
||||
url: findData.url,
|
||||
typebot: findData.typebot,
|
||||
expire: findData.expire,
|
||||
keyword_finish: findData.keyword_finish,
|
||||
delay_message: findData.delay_message,
|
||||
unknown_message: findData.unknown_message,
|
||||
listening_from_me: findData.listening_from_me,
|
||||
sessions: findData.sessions.splice(findData.sessions.indexOf(session), 1),
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, {
|
||||
remoteJid: remoteJid,
|
||||
status: status,
|
||||
url: findData.url,
|
||||
typebot: findData.typebot,
|
||||
session,
|
||||
});
|
||||
|
||||
return { typebot: { ...instance, typebot: typebotData } };
|
||||
}
|
||||
|
||||
public async clearSessions(instance: InstanceDto, remoteJid: string) {
|
||||
const findTypebot = await this.find(instance);
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
|
||||
const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid);
|
||||
|
||||
if (sessionWithRemoteJid.length > 0) {
|
||||
sessionWithRemoteJid.forEach((session) => {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: findTypebot.url,
|
||||
typebot: findTypebot.typebot,
|
||||
expire: findTypebot.expire,
|
||||
keyword_finish: findTypebot.keyword_finish,
|
||||
delay_message: findTypebot.delay_message,
|
||||
unknown_message: findTypebot.unknown_message,
|
||||
listening_from_me: findTypebot.listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
public async startTypebot(instance: InstanceDto, data: any) {
|
||||
if (data.remoteJid === 'status@broadcast') return;
|
||||
|
||||
const remoteJid = data.remoteJid;
|
||||
const url = data.url;
|
||||
const typebot = data.typebot;
|
||||
const startSession = data.startSession;
|
||||
const variables = data.variables;
|
||||
const findTypebot = await this.find(instance);
|
||||
const expire = findTypebot.expire;
|
||||
const keyword_finish = findTypebot.keyword_finish;
|
||||
const delay_message = findTypebot.delay_message;
|
||||
const unknown_message = findTypebot.unknown_message;
|
||||
const listening_from_me = findTypebot.listening_from_me;
|
||||
|
||||
const prefilledVariables = {
|
||||
remoteJid: remoteJid,
|
||||
instanceName: instance.instanceName,
|
||||
};
|
||||
|
||||
if (variables?.length) {
|
||||
variables.forEach((variable: { name: string | number; value: string }) => {
|
||||
prefilledVariables[variable.name] = variable.value;
|
||||
});
|
||||
}
|
||||
|
||||
if (startSession) {
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const response = await this.createNewSession(instance, {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
remoteJid: remoteJid,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: newSessions,
|
||||
prefilledVariables: prefilledVariables,
|
||||
});
|
||||
|
||||
if (response.sessionId) {
|
||||
await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
|
||||
remoteJid: remoteJid,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
sessionId: `${response.sessionId}`,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Session ID not found in response');
|
||||
}
|
||||
} else {
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let url: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
|
||||
|
||||
reqData = {
|
||||
prefilledVariables: prefilledVariables,
|
||||
};
|
||||
} else {
|
||||
url = `${data.url}/api/v1/sendMessage`;
|
||||
|
||||
reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
const request = await axios.post(url, reqData);
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
|
||||
remoteJid: remoteJid,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
variables: variables,
|
||||
sessionId: id,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
typebot: {
|
||||
...instance,
|
||||
typebot: {
|
||||
url: url,
|
||||
remoteJid: remoteJid,
|
||||
typebot: typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private getTypeMessage(msg: any) {
|
||||
this.logger.verbose('get type message');
|
||||
const types = {
|
||||
conversation: msg.conversation,
|
||||
extendedTextMessage: msg.extendedTextMessage?.text,
|
||||
audioMessage: msg.audioMessage?.url,
|
||||
imageMessage: msg.imageMessage?.url,
|
||||
videoMessage: msg.videoMessage?.url,
|
||||
documentMessage: msg.documentMessage?.fileName,
|
||||
contactMessage: msg.contactMessage?.displayName,
|
||||
locationMessage: msg.locationMessage?.degreesLatitude,
|
||||
viewOnceMessageV2:
|
||||
msg.viewOnceMessageV2?.message?.imageMessage?.url ||
|
||||
msg.viewOnceMessageV2?.message?.videoMessage?.url ||
|
||||
msg.viewOnceMessageV2?.message?.audioMessage?.url,
|
||||
listResponseMessage: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
};
|
||||
|
||||
const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown';
|
||||
|
||||
this.logger.verbose('Type message: ' + JSON.stringify(types));
|
||||
return { ...types, messageType };
|
||||
}
|
||||
|
||||
private getMessageContent(types: any) {
|
||||
this.logger.verbose('get message content');
|
||||
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||
|
||||
const result = typeKey ? types[typeKey] : undefined;
|
||||
|
||||
this.logger.verbose('message content: ' + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getConversationMessage(msg: any) {
|
||||
this.logger.verbose('get conversation message');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const messageContent = this.getMessageContent(types);
|
||||
|
||||
this.logger.verbose('conversation message: ' + messageContent);
|
||||
|
||||
return messageContent;
|
||||
}
|
||||
|
||||
private getAudioMessageContent(msg: any) {
|
||||
this.logger.verbose('get audio message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const audioContent = types.audioMessage;
|
||||
|
||||
this.logger.verbose('audio message URL: ' + audioContent);
|
||||
|
||||
return audioContent;
|
||||
}
|
||||
|
||||
private getImageMessageContent(msg: any) {
|
||||
this.logger.verbose('get image message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const imageContent = types.imageMessage;
|
||||
|
||||
this.logger.verbose('image message URL: ' + imageContent);
|
||||
|
||||
return imageContent;
|
||||
}
|
||||
|
||||
private getVideoMessageContent(msg: any) {
|
||||
this.logger.verbose('get video message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const videoContent = types.videoMessage;
|
||||
|
||||
this.logger.verbose('video message URL: ' + videoContent);
|
||||
|
||||
return videoContent;
|
||||
}
|
||||
|
||||
private getDocumentMessageContent(msg: any) {
|
||||
this.logger.verbose('get document message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const documentContent = types.documentMessage;
|
||||
|
||||
this.logger.verbose('document message fileName: ' + documentContent);
|
||||
|
||||
return documentContent;
|
||||
}
|
||||
|
||||
private getContactMessageContent(msg: any) {
|
||||
this.logger.verbose('get contact message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const contactContent = types.contactMessage;
|
||||
|
||||
this.logger.verbose('contact message displayName: ' + contactContent);
|
||||
|
||||
return contactContent;
|
||||
}
|
||||
|
||||
private getLocationMessageContent(msg: any) {
|
||||
this.logger.verbose('get location message content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const locationContent = types.locationMessage;
|
||||
|
||||
this.logger.verbose('location message degreesLatitude: ' + locationContent);
|
||||
|
||||
return locationContent;
|
||||
}
|
||||
|
||||
private getViewOnceMessageV2Content(msg: any) {
|
||||
this.logger.verbose('get viewOnceMessageV2 content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const viewOnceContent = types.viewOnceMessageV2;
|
||||
|
||||
this.logger.verbose('viewOnceMessageV2 URL: ' + viewOnceContent);
|
||||
|
||||
return viewOnceContent;
|
||||
}
|
||||
|
||||
private getListResponseMessageContent(msg: any) {
|
||||
this.logger.verbose('get listResponseMessage content');
|
||||
|
||||
const types = this.getTypeMessage(msg);
|
||||
|
||||
const listResponseContent = types.listResponseMessage || types.responseRowId;
|
||||
|
||||
this.logger.verbose('listResponseMessage selectedRowId: ' + listResponseContent);
|
||||
|
||||
return listResponseContent;
|
||||
}
|
||||
public async createNewSession(instance: InstanceDto, data: any) {
|
||||
if (data.remoteJid === 'status@broadcast') return;
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let url: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
|
||||
|
||||
reqData = {
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || data.prefilledVariables?.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
url = `${data.url}/api/v1/sendMessage`;
|
||||
|
||||
reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || data.prefilledVariables?.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const request = await axios.post(url, reqData);
|
||||
|
||||
if (request?.data?.sessionId) {
|
||||
data.sessions.push({
|
||||
remoteJid: data.remoteJid,
|
||||
sessionId: `${id}-${request.data.sessionId}`,
|
||||
status: 'opened',
|
||||
createdAt: Date.now(),
|
||||
updateAt: Date.now(),
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
enabled: data.enabled,
|
||||
url: data.url,
|
||||
typebot: data.typebot,
|
||||
expire: data.expire,
|
||||
keyword_finish: data.keyword_finish,
|
||||
delay_message: data.delay_message,
|
||||
unknown_message: data.unknown_message,
|
||||
listening_from_me: data.listening_from_me,
|
||||
sessions: data.sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
}
|
||||
return request.data;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public async sendWAMessage(
|
||||
instance: InstanceDto,
|
||||
remoteJid: string,
|
||||
messages: any[],
|
||||
input: any[],
|
||||
clientSideActions: any[],
|
||||
) {
|
||||
processMessages(
|
||||
this.waMonitor.waInstances[instance.instanceName],
|
||||
messages,
|
||||
input,
|
||||
clientSideActions,
|
||||
this.eventEmitter,
|
||||
applyFormatting,
|
||||
).catch((err) => {
|
||||
console.error('Erro ao processar mensagens:', err);
|
||||
});
|
||||
|
||||
function findItemAndGetSecondsToWait(array, targetId) {
|
||||
if (!array) return null;
|
||||
|
||||
for (const item of array) {
|
||||
if (item.lastBubbleBlockId === targetId) {
|
||||
return item.wait?.secondsToWaitFor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyFormatting(element) {
|
||||
let text = '';
|
||||
|
||||
if (element.text) {
|
||||
text += element.text;
|
||||
}
|
||||
|
||||
if (
|
||||
element.children &&
|
||||
(element.type === 'p' ||
|
||||
element.type === 'a' ||
|
||||
element.type === 'inline-variable' ||
|
||||
element.type === 'variable')
|
||||
) {
|
||||
for (const child of element.children) {
|
||||
text += applyFormatting(child);
|
||||
}
|
||||
}
|
||||
|
||||
let formats = '';
|
||||
|
||||
if (element.bold) {
|
||||
formats += '*';
|
||||
}
|
||||
|
||||
if (element.italic) {
|
||||
formats += '_';
|
||||
}
|
||||
|
||||
if (element.underline) {
|
||||
formats += '~';
|
||||
}
|
||||
|
||||
let formattedText = `${formats}${text}${formats.split('').reverse().join('')}`;
|
||||
|
||||
if (element.url) {
|
||||
formattedText = element.children[0]?.text ? `[${formattedText}]\n(${element.url})` : `${element.url}`;
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter, applyFormatting) {
|
||||
for (const message of messages) {
|
||||
if (message.type === 'text') {
|
||||
let formattedText = '';
|
||||
|
||||
for (const richText of message.content.richText) {
|
||||
for (const element of richText.children) {
|
||||
formattedText += applyFormatting(element);
|
||||
}
|
||||
formattedText += '\n';
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.type === 'image') {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
mediatype: 'image',
|
||||
media: message.content.url,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.type === 'video') {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
mediatype: 'video',
|
||||
media: message.content.url,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.type === 'audio') {
|
||||
await instance.audioWhatsapp({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'recording',
|
||||
encoding: true,
|
||||
},
|
||||
audioMessage: {
|
||||
audio: message.content.url,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
|
||||
if (wait) {
|
||||
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
if (input.type === 'choice input') {
|
||||
let formattedText = '';
|
||||
|
||||
const items = input.items;
|
||||
|
||||
for (const item of items) {
|
||||
formattedText += `▶️ ${item.content}\n`;
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eventEmitter.emit('typebot:end', {
|
||||
instance: instance,
|
||||
remoteJid: remoteJid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
|
||||
const findTypebot = await this.find(instance);
|
||||
const url = findTypebot.url;
|
||||
const typebot = findTypebot.typebot;
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
const expire = findTypebot.expire;
|
||||
const keyword_finish = findTypebot.keyword_finish;
|
||||
const delay_message = findTypebot.delay_message;
|
||||
const unknown_message = findTypebot.unknown_message;
|
||||
const listening_from_me = findTypebot.listening_from_me;
|
||||
const messageType = this.getTypeMessage(msg.message).messageType;
|
||||
|
||||
const session = sessions.find((session) => session.remoteJid === remoteJid);
|
||||
|
||||
try {
|
||||
if (session && expire && expire > 0) {
|
||||
const now = Date.now();
|
||||
|
||||
const diff = now - session.updateAt;
|
||||
|
||||
const diffInMinutes = Math.floor(diff / 1000 / 60);
|
||||
|
||||
if (diffInMinutes > expire) {
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const data = await this.createNewSession(instance, {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: newSessions,
|
||||
remoteJid: remoteJid,
|
||||
pushName: msg.pushName,
|
||||
});
|
||||
|
||||
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
|
||||
|
||||
if (data.messages.length === 0) {
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
|
||||
if (!content) {
|
||||
if (unknown_message) {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: unknown_message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let urlTypebot: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
reqData = {
|
||||
message: content,
|
||||
};
|
||||
} else {
|
||||
urlTypebot = `${url}/api/v1/sendMessage`;
|
||||
reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
};
|
||||
}
|
||||
|
||||
const request = await axios.post(urlTypebot, reqData);
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (session && session.status !== 'opened') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
const data = await this.createNewSession(instance, {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: sessions,
|
||||
remoteJid: remoteJid,
|
||||
pushName: msg.pushName,
|
||||
prefilledVariables: {
|
||||
messageType: messageType,
|
||||
},
|
||||
});
|
||||
|
||||
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
|
||||
|
||||
if (data.messages.length === 0) {
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
|
||||
if (!content) {
|
||||
if (unknown_message) {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: unknown_message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let request: any;
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let urlTypebot: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
reqData = {
|
||||
message: content,
|
||||
};
|
||||
} else {
|
||||
urlTypebot = `${url}/api/v1/sendMessage`;
|
||||
reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
};
|
||||
}
|
||||
request = await axios.post(urlTypebot, reqData);
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sessions.map((session) => {
|
||||
if (session.remoteJid === remoteJid) {
|
||||
session.updateAt = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
const content = this.getConversationMessage(msg.message);
|
||||
|
||||
if (!content) {
|
||||
if (unknown_message) {
|
||||
this.waMonitor.waInstances[instance.instanceName].textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: unknown_message,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: url,
|
||||
typebot: typebot,
|
||||
expire: expire,
|
||||
keyword_finish: keyword_finish,
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let urlTypebot: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
urlTypebot = `${url}/api/v1/sessions/${session.sessionId.split('-')[1]}/continueChat`;
|
||||
reqData = {
|
||||
message: content,
|
||||
};
|
||||
} else {
|
||||
urlTypebot = `${url}/api/v1/sendMessage`;
|
||||
reqData = {
|
||||
message: content,
|
||||
sessionId: session.sessionId.split('-')[1],
|
||||
};
|
||||
}
|
||||
const request = await axios.post(urlTypebot, reqData);
|
||||
|
||||
await this.sendWAMessage(
|
||||
instance,
|
||||
remoteJid,
|
||||
request.data.messages,
|
||||
request.data.input,
|
||||
request.data.clientSideActions,
|
||||
);
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/api/integrations/typebot/validate/typebot.schema.ts
Normal file
60
src/api/integrations/typebot/validate/typebot.schema.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const typebotSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
url: { type: 'string' },
|
||||
typebot: { type: 'string' },
|
||||
expire: { type: 'integer' },
|
||||
delay_message: { type: 'integer' },
|
||||
unknown_message: { type: 'string' },
|
||||
listening_from_me: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'],
|
||||
...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'),
|
||||
};
|
||||
|
||||
export const typebotStatusSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteJid: { type: 'string' },
|
||||
status: { type: 'string', enum: ['opened', 'closed', 'paused'] },
|
||||
},
|
||||
required: ['remoteJid', 'status'],
|
||||
...isNotEmpty('remoteJid', 'status'),
|
||||
};
|
||||
|
||||
export const typebotStartSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteJid: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
typebot: { type: 'string' },
|
||||
},
|
||||
required: ['remoteJid', 'url', 'typebot'],
|
||||
...isNotEmpty('remoteJid', 'url', 'typebot'),
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { WebsocketDto } from '../dto/websocket.dto';
|
||||
import { WebsocketService } from '../services/websocket.service';
|
||||
|
||||
const logger = new Logger('WebsocketController');
|
||||
|
||||
export class WebsocketController {
|
||||
constructor(private readonly websocketService: WebsocketService) {}
|
||||
|
||||
public async createWebsocket(instance: InstanceDto, data: WebsocketDto) {
|
||||
logger.verbose('requested createWebsocket from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('websocket disabled');
|
||||
data.events = [];
|
||||
}
|
||||
|
||||
if (data.events.length === 0) {
|
||||
logger.verbose('websocket events empty');
|
||||
data.events = [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
];
|
||||
}
|
||||
|
||||
return this.websocketService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findWebsocket(instance: InstanceDto) {
|
||||
logger.verbose('requested findWebsocket from ' + instance.instanceName + ' instance');
|
||||
return this.websocketService.find(instance);
|
||||
}
|
||||
}
|
||||
4
src/api/integrations/websocket/dto/websocket.dto.ts
Normal file
4
src/api/integrations/websocket/dto/websocket.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class WebsocketDto {
|
||||
enabled: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
44
src/api/integrations/websocket/libs/socket.server.ts
Normal file
44
src/api/integrations/websocket/libs/socket.server.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Server } from 'http';
|
||||
import { Server as SocketIO } from 'socket.io';
|
||||
|
||||
import { configService, Cors, Websocket } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
|
||||
const logger = new Logger('Socket');
|
||||
|
||||
let io: SocketIO;
|
||||
|
||||
const cors = configService.get<Cors>('CORS').ORIGIN;
|
||||
|
||||
export const initIO = (httpServer: Server) => {
|
||||
if (configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
|
||||
io = new SocketIO(httpServer, {
|
||||
cors: {
|
||||
origin: cors,
|
||||
},
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
logger.info('User connected');
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
logger.info('User disconnected');
|
||||
});
|
||||
});
|
||||
|
||||
logger.info('Socket.io initialized');
|
||||
return io;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getIO = (): SocketIO => {
|
||||
logger.verbose('Getting Socket.io');
|
||||
|
||||
if (!io) {
|
||||
logger.error('Socket.io not initialized');
|
||||
throw new Error('Socket.io not initialized');
|
||||
}
|
||||
|
||||
return io;
|
||||
};
|
||||
18
src/api/integrations/websocket/models/websocket.model.ts
Normal file
18
src/api/integrations/websocket/models/websocket.model.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../../../libs/db.connect';
|
||||
|
||||
export class WebsocketRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
}
|
||||
|
||||
const websocketSchema = new Schema<WebsocketRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
events: { type: [String], required: true },
|
||||
});
|
||||
|
||||
export const WebsocketModel = dbserver?.model(WebsocketRaw.name, websocketSchema, 'websocket');
|
||||
export type IWebsocketModel = typeof WebsocketModel;
|
||||
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../../../config/env.config';
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { IInsert, Repository } from '../../../abstract/abstract.repository';
|
||||
import { IWebsocketModel, WebsocketRaw } from '../../../models';
|
||||
|
||||
export class WebsocketRepository extends Repository {
|
||||
constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('WebsocketRepository');
|
||||
|
||||
public async create(data: WebsocketRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating websocket');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving websocket to db');
|
||||
const insert = await this.websocketModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('websocket saved to db: ' + insert.modifiedCount + ' websocket');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving websocket to store');
|
||||
|
||||
this.writeStore<WebsocketRaw>({
|
||||
path: join(this.storePath, 'websocket'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('websocket saved to store in path: ' + join(this.storePath, 'websocket') + '/' + instance);
|
||||
|
||||
this.logger.verbose('websocket created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<WebsocketRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding websocket');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding websocket in db');
|
||||
return await this.websocketModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding websocket in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'websocket', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as WebsocketRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/api/integrations/websocket/routes/websocket.router.ts
Normal file
52
src/api/integrations/websocket/routes/websocket.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { instanceNameSchema, websocketSchema } from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routers/index.router';
|
||||
import { websocketController } from '../../../server.module';
|
||||
import { WebsocketDto } from '../dto/websocket.dto';
|
||||
|
||||
const logger = new Logger('WebsocketRouter');
|
||||
|
||||
export class WebsocketRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setWebsocket');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<WebsocketDto>({
|
||||
request: req,
|
||||
schema: websocketSchema,
|
||||
ClassRef: WebsocketDto,
|
||||
execute: (instance, data) => websocketController.createWebsocket(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findWebsocket');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => websocketController.findWebsocket(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
33
src/api/integrations/websocket/services/websocket.service.ts
Normal file
33
src/api/integrations/websocket/services/websocket.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Logger } from '../../../../config/logger.config';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { WebsocketRaw } from '../../../models';
|
||||
import { WAMonitoringService } from '../../../services/monitor.service';
|
||||
import { WebsocketDto } from '../dto/websocket.dto';
|
||||
|
||||
export class WebsocketService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
private readonly logger = new Logger(WebsocketService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: WebsocketDto) {
|
||||
this.logger.verbose('create websocket: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setWebsocket(data);
|
||||
|
||||
return { websocket: { ...instance, websocket: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<WebsocketRaw> {
|
||||
try {
|
||||
this.logger.verbose('find websocket: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Websocket not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, events: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/api/integrations/websocket/validate/websocket.schema.ts
Normal file
66
src/api/integrations/websocket/validate/websocket.schema.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const websocketSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
events: {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'APPLICATION_STARTUP',
|
||||
'QRCODE_UPDATED',
|
||||
'MESSAGES_SET',
|
||||
'MESSAGES_UPSERT',
|
||||
'MESSAGES_UPDATE',
|
||||
'MESSAGES_DELETE',
|
||||
'SEND_MESSAGE',
|
||||
'CONTACTS_SET',
|
||||
'CONTACTS_UPSERT',
|
||||
'CONTACTS_UPDATE',
|
||||
'PRESENCE_UPDATE',
|
||||
'CHATS_SET',
|
||||
'CHATS_UPSERT',
|
||||
'CHATS_UPDATE',
|
||||
'CHATS_DELETE',
|
||||
'GROUPS_UPSERT',
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
'TYPEBOT_CHANGE_STATUS',
|
||||
'CHAMA_AI_ACTION',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['enabled'],
|
||||
...isNotEmpty('enabled'),
|
||||
};
|
||||
20
src/api/models/auth.model.ts
Normal file
20
src/api/models/auth.model.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class AuthRaw {
|
||||
_id?: string;
|
||||
jwt?: string;
|
||||
apikey?: string;
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
const authSchema = new Schema<AuthRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
jwt: { type: String, minlength: 1 },
|
||||
apikey: { type: String, minlength: 1 },
|
||||
instanceId: { type: String, minlength: 1 },
|
||||
});
|
||||
|
||||
export const AuthModel = dbserver?.model(AuthRaw.name, authSchema, 'authentication');
|
||||
export type IAuthModel = typeof AuthModel;
|
||||
24
src/api/models/chamaai.model.ts
Normal file
24
src/api/models/chamaai.model.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class ChamaaiRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
url?: string;
|
||||
token?: string;
|
||||
waNumber?: string;
|
||||
answerByAudio?: boolean;
|
||||
}
|
||||
|
||||
const chamaaiSchema = new Schema<ChamaaiRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
url: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
waNumber: { type: String, required: true },
|
||||
answerByAudio: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const ChamaaiModel = dbserver?.model(ChamaaiRaw.name, chamaaiSchema, 'chamaai');
|
||||
export type IChamaaiModel = typeof ChamaaiModel;
|
||||
26
src/api/models/chat.model.ts
Normal file
26
src/api/models/chat.model.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class ChatRaw {
|
||||
_id?: string;
|
||||
id?: string;
|
||||
owner: string;
|
||||
lastMsgTimestamp?: number;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
type ChatRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ChatRawSelect = ChatRawBoolean<ChatRaw>;
|
||||
|
||||
const chatSchema = new Schema<ChatRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
labels: { type: [String], default: [] },
|
||||
});
|
||||
|
||||
export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats');
|
||||
export type IChatModel = typeof ChatModel;
|
||||
27
src/api/models/contact.model.ts
Normal file
27
src/api/models/contact.model.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class ContactRaw {
|
||||
_id?: string;
|
||||
pushName?: string;
|
||||
id?: string;
|
||||
profilePictureUrl?: string;
|
||||
owner: string;
|
||||
}
|
||||
|
||||
type ContactRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ContactRawSelect = ContactRawBoolean<ContactRaw>;
|
||||
|
||||
const contactSchema = new Schema<ContactRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
pushName: { type: String, minlength: 1 },
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
profilePictureUrl: { type: String, minlength: 1 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
});
|
||||
|
||||
export const ContactModel = dbserver?.model(ContactRaw.name, contactSchema, 'contacts');
|
||||
export type IContactModel = typeof ContactModel;
|
||||
15
src/api/models/index.ts
Normal file
15
src/api/models/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export * from '../integrations/chatwoot/models/chatwoot.model';
|
||||
export * from '../integrations/rabbitmq/models/rabbitmq.model';
|
||||
export * from '../integrations/sqs/models/sqs.model';
|
||||
export * from '../integrations/typebot/models/typebot.model';
|
||||
export * from '../integrations/websocket/models/websocket.model';
|
||||
export * from './auth.model';
|
||||
export * from './chamaai.model';
|
||||
export * from './chat.model';
|
||||
export * from './contact.model';
|
||||
export * from './integration.model';
|
||||
export * from './label.model';
|
||||
export * from './message.model';
|
||||
export * from './proxy.model';
|
||||
export * from './settings.model';
|
||||
export * from './webhook.model';
|
||||
20
src/api/models/integration.model.ts
Normal file
20
src/api/models/integration.model.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class IntegrationRaw {
|
||||
_id?: string;
|
||||
integration?: string;
|
||||
number?: string;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
const sqsSchema = new Schema<IntegrationRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
integration: { type: String, required: true },
|
||||
number: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
});
|
||||
|
||||
export const IntegrationModel = dbserver?.model(IntegrationRaw.name, sqsSchema, 'integration');
|
||||
export type IntegrationModel = typeof IntegrationModel;
|
||||
29
src/api/models/label.model.ts
Normal file
29
src/api/models/label.model.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class LabelRaw {
|
||||
_id?: string;
|
||||
id?: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
color: number;
|
||||
predefinedId?: string;
|
||||
}
|
||||
|
||||
type LabelRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type LabelRawSelect = LabelRawBoolean<LabelRaw>;
|
||||
|
||||
const labelSchema = new Schema<LabelRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
name: { type: String, required: true, minlength: 1 },
|
||||
color: { type: Number, required: true, min: 0, max: 19 },
|
||||
predefinedId: { type: String },
|
||||
});
|
||||
|
||||
export const LabelModel = dbserver?.model(LabelRaw.name, labelSchema, 'labels');
|
||||
export type ILabelModel = typeof LabelModel;
|
||||
101
src/api/models/message.model.ts
Normal file
101
src/api/models/message.model.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { wa } from '../types/wa.types';
|
||||
|
||||
class Key {
|
||||
id?: string;
|
||||
remoteJid?: string;
|
||||
fromMe?: boolean;
|
||||
participant?: string;
|
||||
}
|
||||
|
||||
class ChatwootMessage {
|
||||
messageId?: number;
|
||||
inboxId?: number;
|
||||
conversationId?: number;
|
||||
contactInbox?: { sourceId: string };
|
||||
isRead?: boolean;
|
||||
}
|
||||
|
||||
export class MessageRaw {
|
||||
_id?: string;
|
||||
key?: Key;
|
||||
pushName?: string;
|
||||
participant?: string;
|
||||
message?: object;
|
||||
messageType?: string;
|
||||
messageTimestamp?: number | Long.Long;
|
||||
owner: string;
|
||||
source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
chatwoot?: ChatwootMessage;
|
||||
contextInfo?: any;
|
||||
}
|
||||
|
||||
type MessageRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type MessageRawSelect = Omit<Omit<MessageRawBoolean<MessageRaw>, 'key'>, 'chatwoot'> & {
|
||||
key?: MessageRawBoolean<Key>;
|
||||
chatwoot?: MessageRawBoolean<ChatwootMessage>;
|
||||
};
|
||||
|
||||
const messageSchema = new Schema<MessageRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
key: {
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
remoteJid: { type: String, required: true, minlength: 1 },
|
||||
fromMe: { type: Boolean, required: true },
|
||||
participant: { type: String, minlength: 1 },
|
||||
},
|
||||
pushName: { type: String },
|
||||
participant: { type: String },
|
||||
messageType: { type: String },
|
||||
message: { type: Object },
|
||||
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios', 'unknown', 'desktop'] },
|
||||
messageTimestamp: { type: Number, required: true },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
chatwoot: {
|
||||
messageId: { type: Number },
|
||||
inboxId: { type: Number },
|
||||
conversationId: { type: Number },
|
||||
contactInbox: { type: Object },
|
||||
isRead: { type: Boolean },
|
||||
},
|
||||
});
|
||||
|
||||
messageSchema.index({ 'chatwoot.messageId': 1, owner: 1 });
|
||||
messageSchema.index({ 'key.id': 1 });
|
||||
messageSchema.index({ 'key.id': 1, owner: 1 });
|
||||
messageSchema.index({ owner: 1 });
|
||||
|
||||
export const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages');
|
||||
export type IMessageModel = typeof MessageModel;
|
||||
|
||||
export class MessageUpdateRaw {
|
||||
_id?: string;
|
||||
remoteJid?: string;
|
||||
id?: string;
|
||||
fromMe?: boolean;
|
||||
participant?: string;
|
||||
datetime?: number;
|
||||
status?: wa.StatusMessage;
|
||||
owner: string;
|
||||
pollUpdates?: any;
|
||||
}
|
||||
|
||||
const messageUpdateSchema = new Schema<MessageUpdateRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
remoteJid: { type: String, required: true, min: 1 },
|
||||
id: { type: String, required: true, min: 1 },
|
||||
fromMe: { type: Boolean, required: true },
|
||||
participant: { type: String, min: 1 },
|
||||
datetime: { type: Number, required: true, min: 1 },
|
||||
status: { type: String, required: true },
|
||||
owner: { type: String, required: true, min: 1 },
|
||||
});
|
||||
|
||||
export const MessageUpModel = dbserver?.model(MessageUpdateRaw.name, messageUpdateSchema, 'messageUpdate');
|
||||
export type IMessageUpModel = typeof MessageUpModel;
|
||||
32
src/api/models/proxy.model.ts
Normal file
32
src/api/models/proxy.model.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
class Proxy {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
proxy?: Proxy;
|
||||
}
|
||||
|
||||
const proxySchema = new Schema<ProxyRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
proxy: {
|
||||
host: { type: String, required: true },
|
||||
port: { type: String, required: true },
|
||||
protocol: { type: String, required: true },
|
||||
username: { type: String, required: false },
|
||||
password: { type: String, required: false },
|
||||
},
|
||||
});
|
||||
|
||||
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');
|
||||
export type IProxyModel = typeof ProxyModel;
|
||||
28
src/api/models/settings.model.ts
Normal file
28
src/api/models/settings.model.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class SettingsRaw {
|
||||
_id?: string;
|
||||
reject_call?: boolean;
|
||||
msg_call?: string;
|
||||
groups_ignore?: boolean;
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
|
||||
const settingsSchema = new Schema<SettingsRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
reject_call: { type: Boolean, required: true },
|
||||
msg_call: { type: String, required: true },
|
||||
groups_ignore: { type: Boolean, required: true },
|
||||
always_online: { type: Boolean, required: true },
|
||||
read_messages: { type: Boolean, required: true },
|
||||
read_status: { type: Boolean, required: true },
|
||||
sync_full_history: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings');
|
||||
export type ISettingsModel = typeof SettingsModel;
|
||||
24
src/api/models/webhook.model.ts
Normal file
24
src/api/models/webhook.model.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class WebhookRaw {
|
||||
_id?: string;
|
||||
url?: string;
|
||||
enabled?: boolean;
|
||||
events?: string[];
|
||||
webhook_by_events?: boolean;
|
||||
webhook_base64?: boolean;
|
||||
}
|
||||
|
||||
const webhookSchema = new Schema<WebhookRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
url: { type: String, required: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
events: { type: [String], required: true },
|
||||
webhook_by_events: { type: Boolean, required: true },
|
||||
webhook_base64: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');
|
||||
export type IWebhookModel = typeof WebhookModel;
|
||||
135
src/api/repository/auth.repository.ts
Normal file
135
src/api/repository/auth.repository.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { AUTH_DIR } from '../../config/path.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { AuthRaw, IAuthModel, IntegrationModel } from '../models';
|
||||
|
||||
export class AuthRepository extends Repository {
|
||||
constructor(
|
||||
private readonly authModel: IAuthModel,
|
||||
private readonly integrationModel: IntegrationModel,
|
||||
readonly configService: ConfigService,
|
||||
) {
|
||||
super(configService);
|
||||
this.auth = configService.get<Auth>('AUTHENTICATION');
|
||||
}
|
||||
|
||||
private readonly auth: Auth;
|
||||
private readonly logger = new Logger('AuthRepository');
|
||||
|
||||
public async create(data: AuthRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating auth');
|
||||
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving auth to db');
|
||||
const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('auth saved to db: ' + insert.modifiedCount + ' auth');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving auth to store');
|
||||
|
||||
this.writeStore<AuthRaw>({
|
||||
path: join(AUTH_DIR, this.auth.TYPE),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
this.logger.verbose('auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance);
|
||||
|
||||
this.logger.verbose('auth created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return { error } as any;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<AuthRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding auth');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding auth in db');
|
||||
return await this.authModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding auth in store');
|
||||
|
||||
return JSON.parse(
|
||||
readFileSync(join(AUTH_DIR, this.auth.TYPE, instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as AuthRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
public async list(): Promise<AuthRaw[]> {
|
||||
try {
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('listing auth in db');
|
||||
return await this.authModel.find();
|
||||
}
|
||||
|
||||
this.logger.verbose('listing auth in store');
|
||||
|
||||
const auths: AuthRaw[] = [];
|
||||
const openDir = opendirSync(join(AUTH_DIR, this.auth.TYPE), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
auths.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(AUTH_DIR, this.auth.TYPE, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async findInstanceNameById(instanceId: string): Promise<string | null> {
|
||||
try {
|
||||
this.logger.verbose('finding auth by instanceId');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding auth in db');
|
||||
const response = await this.authModel.findOne({ instanceId });
|
||||
|
||||
return response._id;
|
||||
}
|
||||
|
||||
this.logger.verbose('finding auth in store is not supported');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async findInstanceNameByNumber(number: string): Promise<string | null> {
|
||||
try {
|
||||
this.logger.verbose('finding auth by number');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding auth in db');
|
||||
const instance = await this.integrationModel.findOne({ number });
|
||||
|
||||
const response = await this.authModel.findOne({ _id: instance._id });
|
||||
|
||||
return response._id;
|
||||
}
|
||||
|
||||
this.logger.verbose('finding auth in store is not supported');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/api/repository/chamaai.repository.ts
Normal file
62
src/api/repository/chamaai.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ChamaaiRaw, IChamaaiModel } from '../models';
|
||||
|
||||
export class ChamaaiRepository extends Repository {
|
||||
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ChamaaiRepository');
|
||||
|
||||
public async create(data: ChamaaiRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating chamaai');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving chamaai to db');
|
||||
const insert = await this.chamaaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('chamaai saved to db: ' + insert.modifiedCount + ' chamaai');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving chamaai to store');
|
||||
|
||||
this.writeStore<ChamaaiRaw>({
|
||||
path: join(this.storePath, 'chamaai'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('chamaai saved to store in path: ' + join(this.storePath, 'chamaai') + '/' + instance);
|
||||
|
||||
this.logger.verbose('chamaai created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<ChamaaiRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding chamaai');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding chamaai in db');
|
||||
return await this.chamaaiModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding chamaai in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'chamaai', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as ChamaaiRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/api/repository/chat.repository.ts
Normal file
177
src/api/repository/chat.repository.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ChatRaw, ChatRawSelect, IChatModel } from '../models';
|
||||
|
||||
export class ChatQuery {
|
||||
select?: ChatRawSelect;
|
||||
where: ChatRaw;
|
||||
}
|
||||
|
||||
export class ChatRepository extends Repository {
|
||||
constructor(private readonly chatModel: IChatModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ChatRepository');
|
||||
|
||||
public async insert(data: ChatRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting chats');
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no chats to insert');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.verbose('saving chats to store');
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving chats to db');
|
||||
const insert = await this.chatModel.insertMany([...data]);
|
||||
|
||||
this.logger.verbose('chats saved to db: ' + insert.length + ' chats');
|
||||
return { insertCount: insert.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving chats to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.CHATS) {
|
||||
this.logger.verbose('saving chats to store');
|
||||
data.forEach((chat) => {
|
||||
this.writeStore<ChatRaw>({
|
||||
path: join(this.storePath, 'chats', instanceName),
|
||||
fileName: chat.id,
|
||||
data: chat,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'chats saved to store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('chats saved to store');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('chats not saved to store');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: ChatQuery): Promise<ChatRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding chats');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding chats in db');
|
||||
return await this.chatModel.find({ owner: query.where.owner }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding chats in store');
|
||||
|
||||
const chats: ChatRaw[] = [];
|
||||
const openDir = opendirSync(join(this.storePath, 'chats', query.where.owner));
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
chats.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'chats', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('chats found in store: ' + chats.length + ' chats');
|
||||
return chats;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: ChatQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting chats');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting chats in db');
|
||||
return await this.chatModel.deleteOne({ ...query.where });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting chats in store');
|
||||
rmSync(join(this.storePath, 'chats', query.where.owner, query.where.id + '.josn'), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return { deleted: { chatId: query.where.id } };
|
||||
} catch (error) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
public async update(data: ChatRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('updating chats');
|
||||
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no chats to update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('updating chats in db');
|
||||
|
||||
const chats = data.map((chat) => {
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { id: chat.id },
|
||||
update: { ...chat },
|
||||
upsert: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { nModified } = await this.chatModel.bulkWrite(chats);
|
||||
|
||||
this.logger.verbose('chats updated in db: ' + nModified + ' chats');
|
||||
return { insertCount: nModified };
|
||||
}
|
||||
|
||||
this.logger.verbose('updating chats in store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.CONTACTS) {
|
||||
this.logger.verbose('updating chats in store');
|
||||
data.forEach((chat) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'chats', instanceName),
|
||||
fileName: chat.id,
|
||||
data: chat,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'chats updated in store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('chats updated in store: ' + data.length + ' chats');
|
||||
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('chats not updated');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
227
src/api/repository/contact.repository.ts
Normal file
227
src/api/repository/contact.repository.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ContactRaw, ContactRawSelect, IContactModel } from '../models';
|
||||
|
||||
export class ContactQuery {
|
||||
select?: ContactRawSelect;
|
||||
where: ContactRaw;
|
||||
}
|
||||
|
||||
export class ContactQueryMany {
|
||||
owner: ContactRaw['owner'];
|
||||
ids: ContactRaw['id'][];
|
||||
}
|
||||
|
||||
export class ContactRepository extends Repository {
|
||||
constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ContactRepository');
|
||||
|
||||
public async insert(data: ContactRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting contacts');
|
||||
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no contacts to insert');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving contacts to db');
|
||||
|
||||
const insert = await this.contactModel.insertMany([...data]);
|
||||
|
||||
this.logger.verbose('contacts saved to db: ' + insert.length + ' contacts');
|
||||
return { insertCount: insert.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving contacts to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.CONTACTS) {
|
||||
this.logger.verbose('saving contacts to store');
|
||||
data.forEach((contact) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'contacts', instanceName),
|
||||
fileName: contact.id,
|
||||
data: contact,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'contacts saved to store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('contacts saved to store: ' + data.length + ' contacts');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('contacts not saved');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async update(data: ContactRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('updating contacts');
|
||||
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no contacts to update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('updating contacts in db');
|
||||
|
||||
const contacts = data.map((contact) => {
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { id: contact.id },
|
||||
update: { ...contact },
|
||||
upsert: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { nModified } = await this.contactModel.bulkWrite(contacts);
|
||||
|
||||
this.logger.verbose('contacts updated in db: ' + nModified + ' contacts');
|
||||
return { insertCount: nModified };
|
||||
}
|
||||
|
||||
this.logger.verbose('updating contacts in store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.CONTACTS) {
|
||||
this.logger.verbose('updating contacts in store');
|
||||
data.forEach((contact) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'contacts', instanceName),
|
||||
fileName: contact.id,
|
||||
data: contact,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'contacts updated in store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('contacts updated in store: ' + data.length + ' contacts');
|
||||
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('contacts not updated');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: ContactQuery): Promise<ContactRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding contacts');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding contacts in db');
|
||||
return await this.contactModel.find({ ...query.where }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding contacts in store');
|
||||
const contacts: ContactRaw[] = [];
|
||||
if (query?.where?.id) {
|
||||
this.logger.verbose('finding contacts in store by id');
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.where.owner, query.where.id + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.logger.verbose('finding contacts in store by owner');
|
||||
|
||||
const openDir = opendirSync(join(this.storePath, 'contacts', query.where.owner), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts');
|
||||
return contacts;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async findManyById(query: ContactQueryMany): Promise<ContactRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding contacts');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding contacts in db');
|
||||
return await this.contactModel.find({
|
||||
owner: query.owner,
|
||||
id: { $in: query.ids },
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding contacts in store');
|
||||
const contacts: ContactRaw[] = [];
|
||||
if (query.ids.length > 0) {
|
||||
this.logger.verbose('finding contacts in store by id');
|
||||
query.ids.forEach((id) => {
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.owner, id + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.logger.verbose('finding contacts in store by owner');
|
||||
|
||||
const openDir = opendirSync(join(this.storePath, 'contacts', query.owner), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts');
|
||||
return contacts;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/api/repository/integration.repository.ts
Normal file
64
src/api/repository/integration.repository.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IntegrationModel, IntegrationRaw } from '../models';
|
||||
|
||||
export class IntegrationRepository extends Repository {
|
||||
constructor(private readonly integrationModel: IntegrationModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('IntegrationRepository');
|
||||
|
||||
public async create(data: IntegrationRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating integration');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving integration to db');
|
||||
const insert = await this.integrationModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('integration saved to db: ' + insert.modifiedCount + ' integration');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving integration to store');
|
||||
|
||||
this.writeStore<IntegrationRaw>({
|
||||
path: join(this.storePath, 'integration'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose(
|
||||
'integration saved to store in path: ' + join(this.storePath, 'integration') + '/' + instance,
|
||||
);
|
||||
|
||||
this.logger.verbose('integration created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<IntegrationRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding integration');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding integration in db');
|
||||
return await this.integrationModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding integration in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'integration', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as IntegrationRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/api/repository/label.repository.ts
Normal file
111
src/api/repository/label.repository.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ILabelModel, LabelRaw, LabelRawSelect } from '../models';
|
||||
|
||||
export class LabelQuery {
|
||||
select?: LabelRawSelect;
|
||||
where: Partial<LabelRaw>;
|
||||
}
|
||||
|
||||
export class LabelRepository extends Repository {
|
||||
constructor(private readonly labelModel: ILabelModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('LabelRepository');
|
||||
|
||||
public async insert(data: LabelRaw, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting labels');
|
||||
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving labels to db');
|
||||
const insert = await this.labelModel.findOneAndUpdate({ id: data.id }, data, { upsert: true });
|
||||
|
||||
this.logger.verbose(`label ${data.name} saved to db`);
|
||||
return { insertCount: Number(!!insert._id) };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving label to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.LABELS) {
|
||||
this.logger.verbose('saving label to store');
|
||||
this.writeStore<LabelRaw>({
|
||||
path: join(this.storePath, 'labels', instanceName),
|
||||
fileName: data.id,
|
||||
data,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'labels saved to store in path: ' + join(this.storePath, 'labels', instanceName) + '/' + data.id,
|
||||
);
|
||||
|
||||
this.logger.verbose(`label ${data.name} saved to store`);
|
||||
return { insertCount: 1 };
|
||||
}
|
||||
|
||||
this.logger.verbose('labels not saved to store');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: LabelQuery): Promise<LabelRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding labels');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding labels in db');
|
||||
return await this.labelModel.find({ owner: query.where.owner }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding labels in store');
|
||||
|
||||
const labels: LabelRaw[] = [];
|
||||
const openDir = opendirSync(join(this.storePath, 'labels', query.where.owner));
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
labels.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'labels', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('labels found in store: ' + labels.length + ' labels');
|
||||
return labels;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: LabelQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting labels');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting labels in db');
|
||||
return await this.labelModel.deleteOne({ ...query.where });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting labels in store');
|
||||
rmSync(join(this.storePath, 'labels', query.where.owner, query.where.id + '.josn'), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return { deleted: { labelId: query.where.id } };
|
||||
} catch (error) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
}
|
||||
240
src/api/repository/message.repository.ts
Normal file
240
src/api/repository/message.repository.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IMessageModel, MessageRaw, MessageRawSelect } from '../models';
|
||||
|
||||
export class MessageQuery {
|
||||
select?: MessageRawSelect;
|
||||
where: MessageRaw;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export class MessageRepository extends Repository {
|
||||
constructor(private readonly messageModel: IMessageModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('MessageRepository');
|
||||
|
||||
public buildQuery(query: MessageQuery): MessageQuery {
|
||||
for (const [o, p] of Object.entries(query?.where || {})) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.where[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.where[o];
|
||||
}
|
||||
}
|
||||
|
||||
for (const [o, p] of Object.entries(query?.select || {})) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.select[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.select[o];
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting messages');
|
||||
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
this.logger.verbose('no messages to insert');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving messages to db');
|
||||
const cleanedData = data.map((obj) => {
|
||||
const cleanedObj = { ...obj };
|
||||
if ('extendedTextMessage' in obj.message) {
|
||||
const extendedTextMessage = obj.message.extendedTextMessage as {
|
||||
contextInfo?: {
|
||||
mentionedJid?: any;
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof extendedTextMessage === 'object' && extendedTextMessage !== null) {
|
||||
if ('contextInfo' in extendedTextMessage) {
|
||||
delete extendedTextMessage.contextInfo?.mentionedJid;
|
||||
extendedTextMessage.contextInfo = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return cleanedObj;
|
||||
});
|
||||
|
||||
const insert = await this.messageModel.insertMany([...cleanedData]);
|
||||
|
||||
this.logger.verbose('messages saved to db: ' + insert.length + ' messages');
|
||||
return { insertCount: insert.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving messages to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.MESSAGES) {
|
||||
this.logger.verbose('saving messages to store');
|
||||
|
||||
data.forEach((message) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'messages', instanceName),
|
||||
fileName: message.key.id,
|
||||
data: message,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'messages saved to store in path: ' + join(this.storePath, 'messages', instanceName) + '/' + message.key.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('messages saved to store: ' + data.length + ' messages');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('messages not saved to store');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
console.log('ERROR: ', error);
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: MessageQuery) {
|
||||
try {
|
||||
this.logger.verbose('finding messages');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding messages in db');
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel
|
||||
.find({ ...query.where })
|
||||
.select(query.select || {})
|
||||
.sort({ messageTimestamp: -1 })
|
||||
.limit(query?.limit ?? 0);
|
||||
}
|
||||
|
||||
this.logger.verbose('finding messages in store');
|
||||
const messages: MessageRaw[] = [];
|
||||
if (query?.where?.key?.id) {
|
||||
this.logger.verbose('finding messages in store by id');
|
||||
messages.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.logger.verbose('finding messages in store by owner');
|
||||
const openDir = opendirSync(join(this.storePath, 'messages', query.where.owner), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
messages.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'messages', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('messages found in store: ' + messages.length + ' messages');
|
||||
return messages
|
||||
.sort((x, y) => {
|
||||
return (y.messageTimestamp as number) - (x.messageTimestamp as number);
|
||||
})
|
||||
.splice(0, query?.limit ?? messages.length);
|
||||
} catch (error) {
|
||||
this.logger.error(`error on message find: ${error.toString()}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async update(data: MessageRaw[], instanceName: string, saveDb?: boolean): Promise<IInsert> {
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('updating messages in db');
|
||||
|
||||
const messages = data.map((message) => {
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { 'key.id': message.key.id },
|
||||
update: { ...message },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { nModified } = await this.messageModel.bulkWrite(messages);
|
||||
|
||||
this.logger.verbose('messages updated in db: ' + nModified + ' messages');
|
||||
return { insertCount: nModified };
|
||||
}
|
||||
|
||||
this.logger.verbose('updating messages in store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.MESSAGES) {
|
||||
this.logger.verbose('updating messages in store');
|
||||
data.forEach((message) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'messages', instanceName),
|
||||
fileName: message.key.id,
|
||||
data: message,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'messages updated in store in path: ' +
|
||||
join(this.storePath, 'messages', instanceName) +
|
||||
'/' +
|
||||
message.key.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('messages updated in store: ' + data.length + ' messages');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('messages not updated');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: MessageQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting message');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting message in db');
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel.deleteOne({ ...query.where });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting message in store');
|
||||
rmSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return { deleted: { messageId: query.where.key.id } };
|
||||
} catch (error) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/api/repository/messageUp.repository.ts
Normal file
120
src/api/repository/messageUp.repository.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IMessageUpModel, MessageUpdateRaw } from '../models';
|
||||
|
||||
export class MessageUpQuery {
|
||||
where: MessageUpdateRaw;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export class MessageUpRepository extends Repository {
|
||||
constructor(private readonly messageUpModel: IMessageUpModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('MessageUpRepository');
|
||||
|
||||
public async insert(data: MessageUpdateRaw[], instanceName: string, saveDb?: boolean): Promise<IInsert> {
|
||||
this.logger.verbose('inserting message up');
|
||||
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no message up to insert');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving message up to db');
|
||||
const insert = await this.messageUpModel.insertMany([...data]);
|
||||
|
||||
this.logger.verbose('message up saved to db: ' + insert.length + ' message up');
|
||||
return { insertCount: insert.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving message up to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.MESSAGE_UP) {
|
||||
this.logger.verbose('saving message up to store');
|
||||
data.forEach((update) => {
|
||||
this.writeStore<MessageUpdateRaw>({
|
||||
path: join(this.storePath, 'message-up', instanceName),
|
||||
fileName: update.id,
|
||||
data: update,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'message up saved to store in path: ' + join(this.storePath, 'message-up', instanceName) + '/' + update.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('message up saved to store: ' + data.length + ' message up');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('message up not saved to store');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: MessageUpQuery) {
|
||||
try {
|
||||
this.logger.verbose('finding message up');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding message up in db');
|
||||
return await this.messageUpModel
|
||||
.find({ ...query.where })
|
||||
.sort({ datetime: -1 })
|
||||
.limit(query?.limit ?? 0);
|
||||
}
|
||||
|
||||
this.logger.verbose('finding message up in store');
|
||||
|
||||
const messageUpdate: MessageUpdateRaw[] = [];
|
||||
if (query?.where?.id) {
|
||||
this.logger.verbose('finding message up in store by id');
|
||||
|
||||
messageUpdate.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'message-up', query.where.owner, query.where.id + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.logger.verbose('finding message up in store by owner');
|
||||
|
||||
const openDir = opendirSync(join(this.storePath, 'message-up', query.where.owner), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
messageUpdate.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'message-up', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('message up found in store: ' + messageUpdate.length + ' message up');
|
||||
return messageUpdate
|
||||
.sort((x, y) => {
|
||||
return y.datetime - x.datetime;
|
||||
})
|
||||
.splice(0, query?.limit ?? messageUpdate.length);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/api/repository/proxy.repository.ts
Normal file
62
src/api/repository/proxy.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IProxyModel, ProxyRaw } from '../models';
|
||||
|
||||
export class ProxyRepository extends Repository {
|
||||
constructor(private readonly proxyModel: IProxyModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('ProxyRepository');
|
||||
|
||||
public async create(data: ProxyRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating proxy');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving proxy to db');
|
||||
const insert = await this.proxyModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('proxy saved to db: ' + insert.modifiedCount + ' proxy');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving proxy to store');
|
||||
|
||||
this.writeStore<ProxyRaw>({
|
||||
path: join(this.storePath, 'proxy'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('proxy saved to store in path: ' + join(this.storePath, 'proxy') + '/' + instance);
|
||||
|
||||
this.logger.verbose('proxy created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<ProxyRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding proxy');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding proxy in db');
|
||||
return await this.proxyModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding proxy in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'proxy', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as ProxyRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
168
src/api/repository/repository.manager.ts
Normal file
168
src/api/repository/repository.manager.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import fs from 'fs';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService, Database } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ChatwootRepository } from '../integrations/chatwoot/repository/chatwoot.repository';
|
||||
import { RabbitmqRepository } from '../integrations/rabbitmq/repository/rabbitmq.repository';
|
||||
import { SqsRepository } from '../integrations/sqs/repository/sqs.repository';
|
||||
import { TypebotRepository } from '../integrations/typebot/repository/typebot.repository';
|
||||
import { WebsocketRepository } from '../integrations/websocket/repository/websocket.repository';
|
||||
import { AuthRepository } from './auth.repository';
|
||||
import { ChamaaiRepository } from './chamaai.repository';
|
||||
import { ChatRepository } from './chat.repository';
|
||||
import { ContactRepository } from './contact.repository';
|
||||
import { IntegrationRepository } from './integration.repository';
|
||||
import { LabelRepository } from './label.repository';
|
||||
import { MessageRepository } from './message.repository';
|
||||
import { MessageUpRepository } from './messageUp.repository';
|
||||
import { ProxyRepository } from './proxy.repository';
|
||||
import { SettingsRepository } from './settings.repository';
|
||||
import { WebhookRepository } from './webhook.repository';
|
||||
export class RepositoryBroker {
|
||||
constructor(
|
||||
public readonly message: MessageRepository,
|
||||
public readonly chat: ChatRepository,
|
||||
public readonly contact: ContactRepository,
|
||||
public readonly messageUpdate: MessageUpRepository,
|
||||
public readonly webhook: WebhookRepository,
|
||||
public readonly chatwoot: ChatwootRepository,
|
||||
public readonly settings: SettingsRepository,
|
||||
public readonly websocket: WebsocketRepository,
|
||||
public readonly rabbitmq: RabbitmqRepository,
|
||||
public readonly sqs: SqsRepository,
|
||||
public readonly typebot: TypebotRepository,
|
||||
public readonly proxy: ProxyRepository,
|
||||
public readonly chamaai: ChamaaiRepository,
|
||||
public readonly integration: IntegrationRepository,
|
||||
public readonly auth: AuthRepository,
|
||||
public readonly labels: LabelRepository,
|
||||
private configService: ConfigService,
|
||||
dbServer?: MongoClient,
|
||||
) {
|
||||
this.dbClient = dbServer;
|
||||
this.__init_repo_without_db__();
|
||||
}
|
||||
|
||||
private dbClient?: MongoClient;
|
||||
private readonly logger = new Logger('RepositoryBroker');
|
||||
|
||||
public get dbServer() {
|
||||
return this.dbClient;
|
||||
}
|
||||
|
||||
private __init_repo_without_db__() {
|
||||
this.logger.verbose('initializing repository without db');
|
||||
if (!this.configService.get<Database>('DATABASE').ENABLED) {
|
||||
const storePath = join(process.cwd(), 'store');
|
||||
|
||||
this.logger.verbose('creating store path: ' + storePath);
|
||||
try {
|
||||
const authDir = join(storePath, 'auth', this.configService.get<Auth>('AUTHENTICATION').TYPE);
|
||||
const chatsDir = join(storePath, 'chats');
|
||||
const contactsDir = join(storePath, 'contacts');
|
||||
const messagesDir = join(storePath, 'messages');
|
||||
const messageUpDir = join(storePath, 'message-up');
|
||||
const webhookDir = join(storePath, 'webhook');
|
||||
const chatwootDir = join(storePath, 'chatwoot');
|
||||
const settingsDir = join(storePath, 'settings');
|
||||
const websocketDir = join(storePath, 'websocket');
|
||||
const rabbitmqDir = join(storePath, 'rabbitmq');
|
||||
const sqsDir = join(storePath, 'sqs');
|
||||
const typebotDir = join(storePath, 'typebot');
|
||||
const proxyDir = join(storePath, 'proxy');
|
||||
const chamaaiDir = join(storePath, 'chamaai');
|
||||
const integrationDir = join(storePath, 'integration');
|
||||
const tempDir = join(storePath, 'temp');
|
||||
|
||||
if (!fs.existsSync(authDir)) {
|
||||
this.logger.verbose('creating auth dir: ' + authDir);
|
||||
fs.mkdirSync(authDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(chatsDir)) {
|
||||
this.logger.verbose('creating chats dir: ' + chatsDir);
|
||||
fs.mkdirSync(chatsDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(contactsDir)) {
|
||||
this.logger.verbose('creating contacts dir: ' + contactsDir);
|
||||
fs.mkdirSync(contactsDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(messagesDir)) {
|
||||
this.logger.verbose('creating messages dir: ' + messagesDir);
|
||||
fs.mkdirSync(messagesDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(messageUpDir)) {
|
||||
this.logger.verbose('creating message-up dir: ' + messageUpDir);
|
||||
fs.mkdirSync(messageUpDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(webhookDir)) {
|
||||
this.logger.verbose('creating webhook dir: ' + webhookDir);
|
||||
fs.mkdirSync(webhookDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(chatwootDir)) {
|
||||
this.logger.verbose('creating chatwoot dir: ' + chatwootDir);
|
||||
fs.mkdirSync(chatwootDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(settingsDir)) {
|
||||
this.logger.verbose('creating settings dir: ' + settingsDir);
|
||||
fs.mkdirSync(settingsDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(websocketDir)) {
|
||||
this.logger.verbose('creating websocket dir: ' + websocketDir);
|
||||
fs.mkdirSync(websocketDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(rabbitmqDir)) {
|
||||
this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir);
|
||||
fs.mkdirSync(rabbitmqDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(sqsDir)) {
|
||||
this.logger.verbose('creating sqs dir: ' + sqsDir);
|
||||
fs.mkdirSync(sqsDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(typebotDir)) {
|
||||
this.logger.verbose('creating typebot dir: ' + typebotDir);
|
||||
fs.mkdirSync(typebotDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(proxyDir)) {
|
||||
this.logger.verbose('creating proxy dir: ' + proxyDir);
|
||||
fs.mkdirSync(proxyDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(chamaaiDir)) {
|
||||
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
|
||||
fs.mkdirSync(chamaaiDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(integrationDir)) {
|
||||
this.logger.verbose('creating integration dir: ' + integrationDir);
|
||||
fs.mkdirSync(integrationDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
this.logger.verbose('creating temp dir: ' + tempDir);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const storePath = join(process.cwd(), 'store');
|
||||
|
||||
this.logger.verbose('creating store path: ' + storePath);
|
||||
|
||||
const tempDir = join(storePath, 'temp');
|
||||
const chatwootDir = join(storePath, 'chatwoot');
|
||||
|
||||
if (!fs.existsSync(chatwootDir)) {
|
||||
this.logger.verbose('creating chatwoot dir: ' + chatwootDir);
|
||||
fs.mkdirSync(chatwootDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
this.logger.verbose('creating temp dir: ' + tempDir);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/api/repository/settings.repository.ts
Normal file
62
src/api/repository/settings.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ISettingsModel, SettingsRaw } from '../models';
|
||||
|
||||
export class SettingsRepository extends Repository {
|
||||
constructor(private readonly settingsModel: ISettingsModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('SettingsRepository');
|
||||
|
||||
public async create(data: SettingsRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating settings');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving settings to db');
|
||||
const insert = await this.settingsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('settings saved to db: ' + insert.modifiedCount + ' settings');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving settings to store');
|
||||
|
||||
this.writeStore<SettingsRaw>({
|
||||
path: join(this.storePath, 'settings'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('settings saved to store in path: ' + join(this.storePath, 'settings') + '/' + instance);
|
||||
|
||||
this.logger.verbose('settings created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<SettingsRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding settings');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding settings in db');
|
||||
return await this.settingsModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding settings in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'settings', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as SettingsRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/api/repository/webhook.repository.ts
Normal file
62
src/api/repository/webhook.repository.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IWebhookModel, WebhookRaw } from '../models';
|
||||
|
||||
export class WebhookRepository extends Repository {
|
||||
constructor(private readonly webhookModel: IWebhookModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('WebhookRepository');
|
||||
|
||||
public async create(data: WebhookRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating webhook');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving webhook to db');
|
||||
const insert = await this.webhookModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('webhook saved to db: ' + insert.modifiedCount + ' webhook');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving webhook to store');
|
||||
|
||||
this.writeStore<WebhookRaw>({
|
||||
path: join(this.storePath, 'webhook'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose('webhook saved to store in path: ' + join(this.storePath, 'webhook') + '/' + instance);
|
||||
|
||||
this.logger.verbose('webhook created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<WebhookRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding webhook');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding webhook in db');
|
||||
return await this.webhookModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding webhook in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'webhook', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as WebhookRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/api/routers/chamaai.router.ts
Normal file
52
src/api/routers/chamaai.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { chamaaiSchema, instanceNameSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { ChamaaiDto } from '../dto/chamaai.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { chamaaiController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('ChamaaiRouter');
|
||||
|
||||
export class ChamaaiRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setChamaai');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<ChamaaiDto>({
|
||||
request: req,
|
||||
schema: chamaaiSchema,
|
||||
ClassRef: ChamaaiDto,
|
||||
execute: (instance, data) => chamaaiController.createChamaai(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findChamaai');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => chamaaiController.findChamaai(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
410
src/api/routers/chat.router.ts
Normal file
410
src/api/routers/chat.router.ts
Normal file
@@ -0,0 +1,410 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
archiveChatSchema,
|
||||
blockUserSchema,
|
||||
contactValidateSchema,
|
||||
deleteMessageSchema,
|
||||
messageUpSchema,
|
||||
messageValidateSchema,
|
||||
presenceSchema,
|
||||
privacySettingsSchema,
|
||||
profileNameSchema,
|
||||
profilePictureSchema,
|
||||
profileSchema,
|
||||
profileStatusSchema,
|
||||
readMessageSchema,
|
||||
updateMessageSchema,
|
||||
whatsappNumberSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
PrivacySettingDto,
|
||||
ProfileNameDto,
|
||||
ProfilePictureDto,
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ContactQuery } from '../repository/contact.repository';
|
||||
import { MessageQuery } from '../repository/message.repository';
|
||||
import { MessageUpQuery } from '../repository/messageUp.repository';
|
||||
import { chatController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('ChatRouter');
|
||||
|
||||
export class ChatRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in whatsappNumbers');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<WhatsAppNumberDto>({
|
||||
request: req,
|
||||
schema: whatsappNumberSchema,
|
||||
ClassRef: WhatsAppNumberDto,
|
||||
execute: (instance, data) => chatController.whatsappNumber(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in markMessageAsRead');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ReadMessageDto>({
|
||||
request: req,
|
||||
schema: readMessageSchema,
|
||||
ClassRef: ReadMessageDto,
|
||||
execute: (instance, data) => chatController.readMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('archiveChat'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in archiveChat');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ArchiveChatDto>({
|
||||
request: req,
|
||||
schema: archiveChatSchema,
|
||||
ClassRef: ArchiveChatDto,
|
||||
execute: (instance, data) => chatController.archiveChat(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in deleteMessageForEveryone');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<DeleteMessage>({
|
||||
request: req,
|
||||
schema: deleteMessageSchema,
|
||||
ClassRef: DeleteMessage,
|
||||
execute: (instance, data) => chatController.deleteMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('fetchProfilePictureUrl'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchProfilePictureUrl');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<NumberDto>({
|
||||
request: req,
|
||||
schema: profilePictureSchema,
|
||||
ClassRef: NumberDto,
|
||||
execute: (instance, data) => chatController.fetchProfilePicture(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('fetchProfile'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchProfile');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<NumberDto>({
|
||||
request: req,
|
||||
schema: profileSchema,
|
||||
ClassRef: NumberDto,
|
||||
execute: (instance, data) => chatController.fetchProfile(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('findContacts'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findContacts');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ContactQuery>({
|
||||
request: req,
|
||||
schema: contactValidateSchema,
|
||||
ClassRef: ContactQuery,
|
||||
execute: (instance, data) => chatController.fetchContacts(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in getBase64FromMediaMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<getBase64FromMediaMessageDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: getBase64FromMediaMessageDto,
|
||||
execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('findMessages'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findMessages');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<MessageQuery>({
|
||||
request: req,
|
||||
schema: messageValidateSchema,
|
||||
ClassRef: MessageQuery,
|
||||
execute: (instance, data) => chatController.fetchMessages(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('findStatusMessage'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findStatusMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<MessageUpQuery>({
|
||||
request: req,
|
||||
schema: messageUpSchema,
|
||||
ClassRef: MessageUpQuery,
|
||||
execute: (instance, data) => chatController.fetchStatusMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('findChats'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findChats');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => chatController.fetchChats(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendPresence'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendPresence');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<null>({
|
||||
request: req,
|
||||
schema: presenceSchema,
|
||||
ClassRef: SendPresenceDto,
|
||||
execute: (instance, data) => chatController.sendPresence(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
// Profile routes
|
||||
.get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchPrivacySettings');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => chatController.fetchPrivacySettings(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updatePrivacySettings');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<PrivacySettingDto>({
|
||||
request: req,
|
||||
schema: privacySettingsSchema,
|
||||
ClassRef: PrivacySettingDto,
|
||||
execute: (instance, data) => chatController.updatePrivacySettings(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchBusinessProfile');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ProfilePictureDto>({
|
||||
request: req,
|
||||
schema: profilePictureSchema,
|
||||
ClassRef: ProfilePictureDto,
|
||||
execute: (instance, data) => chatController.fetchBusinessProfile(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('updateProfileName'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateProfileName');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ProfileNameDto>({
|
||||
request: req,
|
||||
schema: profileNameSchema,
|
||||
ClassRef: ProfileNameDto,
|
||||
execute: (instance, data) => chatController.updateProfileName(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('updateProfileStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateProfileStatus');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ProfileStatusDto>({
|
||||
request: req,
|
||||
schema: profileStatusSchema,
|
||||
ClassRef: ProfileStatusDto,
|
||||
execute: (instance, data) => chatController.updateProfileStatus(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateProfilePicture'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateProfilePicture');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ProfilePictureDto>({
|
||||
request: req,
|
||||
schema: profilePictureSchema,
|
||||
ClassRef: ProfilePictureDto,
|
||||
execute: (instance, data) => chatController.updateProfilePicture(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.delete(this.routerPath('removeProfilePicture'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in removeProfilePicture');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<ProfilePictureDto>({
|
||||
request: req,
|
||||
schema: profilePictureSchema,
|
||||
ClassRef: ProfilePictureDto,
|
||||
execute: (instance) => chatController.removeProfilePicture(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateMessage'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<UpdateMessageDto>({
|
||||
request: req,
|
||||
schema: updateMessageSchema,
|
||||
ClassRef: UpdateMessageDto,
|
||||
execute: (instance, data) => chatController.updateMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateBlockStatus');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<BlockUserDto>({
|
||||
request: req,
|
||||
schema: blockUserSchema,
|
||||
ClassRef: BlockUserDto,
|
||||
execute: (instance, data) => chatController.blockUser(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
302
src/api/routers/group.router.ts
Normal file
302
src/api/routers/group.router.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInviteSchema,
|
||||
createGroupSchema,
|
||||
getParticipantsSchema,
|
||||
groupInviteSchema,
|
||||
groupJidSchema,
|
||||
groupSendInviteSchema,
|
||||
toggleEphemeralSchema,
|
||||
updateGroupDescriptionSchema,
|
||||
updateGroupPictureSchema,
|
||||
updateGroupSubjectSchema,
|
||||
updateParticipantsSchema,
|
||||
updateSettingsSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
GroupInvite,
|
||||
GroupJid,
|
||||
GroupPictureDto,
|
||||
GroupSendInvite,
|
||||
GroupSubjectDto,
|
||||
GroupToggleEphemeralDto,
|
||||
GroupUpdateParticipantDto,
|
||||
GroupUpdateSettingDto,
|
||||
} from '../dto/group.dto';
|
||||
import { groupController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('GroupRouter');
|
||||
|
||||
export class GroupRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('create'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in createGroup');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<CreateGroupDto>({
|
||||
request: req,
|
||||
schema: createGroupSchema,
|
||||
ClassRef: CreateGroupDto,
|
||||
execute: (instance, data) => groupController.createGroup(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateGroupSubject');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.groupValidate<GroupSubjectDto>({
|
||||
request: req,
|
||||
schema: updateGroupSubjectSchema,
|
||||
ClassRef: GroupSubjectDto,
|
||||
execute: (instance, data) => groupController.updateGroupSubject(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateGroupPicture');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupPictureDto>({
|
||||
request: req,
|
||||
schema: updateGroupPictureSchema,
|
||||
ClassRef: GroupPictureDto,
|
||||
execute: (instance, data) => groupController.updateGroupPicture(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateGroupDescription');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupDescriptionDto>({
|
||||
request: req,
|
||||
schema: updateGroupDescriptionSchema,
|
||||
ClassRef: GroupDescriptionDto,
|
||||
execute: (instance, data) => groupController.updateGroupDescription(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('findGroupInfos'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findGroupInfos');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
schema: groupJidSchema,
|
||||
ClassRef: GroupJid,
|
||||
execute: (instance, data) => groupController.findGroupInfo(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('fetchAllGroups'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchAllGroups');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.getParticipantsValidate<GetParticipant>({
|
||||
request: req,
|
||||
schema: getParticipantsSchema,
|
||||
ClassRef: GetParticipant,
|
||||
execute: (instance, data) => groupController.fetchAllGroups(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('participants'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in participants');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
schema: groupJidSchema,
|
||||
ClassRef: GroupJid,
|
||||
execute: (instance, data) => groupController.findParticipants(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('inviteCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in inviteCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
schema: groupJidSchema,
|
||||
ClassRef: GroupJid,
|
||||
execute: (instance, data) => groupController.inviteCode(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('inviteInfo'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in inviteInfo');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.inviteCodeValidate<GroupInvite>({
|
||||
request: req,
|
||||
schema: groupInviteSchema,
|
||||
ClassRef: GroupInvite,
|
||||
execute: (instance, data) => groupController.inviteInfo(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('acceptInviteCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in acceptInviteCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.inviteCodeValidate<AcceptGroupInvite>({
|
||||
request: req,
|
||||
schema: AcceptGroupInviteSchema,
|
||||
ClassRef: AcceptGroupInvite,
|
||||
execute: (instance, data) => groupController.acceptInviteCode(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendInvite');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupNoValidate<GroupSendInvite>({
|
||||
request: req,
|
||||
schema: groupSendInviteSchema,
|
||||
ClassRef: GroupSendInvite,
|
||||
execute: (instance, data) => groupController.sendInvite(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in revokeInviteCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
schema: groupJidSchema,
|
||||
ClassRef: GroupJid,
|
||||
execute: (instance, data) => groupController.revokeInviteCode(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateParticipant'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateParticipant');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupUpdateParticipantDto>({
|
||||
request: req,
|
||||
schema: updateParticipantsSchema,
|
||||
ClassRef: GroupUpdateParticipantDto,
|
||||
execute: (instance, data) => groupController.updateGParticipate(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateSetting'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateSetting');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupUpdateSettingDto>({
|
||||
request: req,
|
||||
schema: updateSettingsSchema,
|
||||
ClassRef: GroupUpdateSettingDto,
|
||||
execute: (instance, data) => groupController.updateGSetting(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in toggleEphemeral');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupToggleEphemeralDto>({
|
||||
request: req,
|
||||
schema: toggleEphemeralSchema,
|
||||
ClassRef: GroupToggleEphemeralDto,
|
||||
execute: (instance, data) => groupController.toggleEphemeral(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in leaveGroup');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.groupValidate<GroupJid>({
|
||||
request: req,
|
||||
schema: {},
|
||||
ClassRef: GroupJid,
|
||||
execute: (instance, data) => groupController.leaveGroup(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
68
src/api/routers/index.router.ts
Normal file
68
src/api/routers/index.router.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Router } from 'express';
|
||||
import fs from 'fs';
|
||||
|
||||
import { Auth, configService } from '../../config/env.config';
|
||||
import { authGuard } from '../guards/auth.guard';
|
||||
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
|
||||
import { ChatwootRouter } from '../integrations/chatwoot/routes/chatwoot.router';
|
||||
import { RabbitmqRouter } from '../integrations/rabbitmq/routes/rabbitmq.router';
|
||||
import { SqsRouter } from '../integrations/sqs/routes/sqs.router';
|
||||
import { TypebotRouter } from '../integrations/typebot/routes/typebot.router';
|
||||
import { WebsocketRouter } from '../integrations/websocket/routes/websocket.router';
|
||||
import { ChamaaiRouter } from './chamaai.router';
|
||||
import { ChatRouter } from './chat.router';
|
||||
import { GroupRouter } from './group.router';
|
||||
import { InstanceRouter } from './instance.router';
|
||||
import { LabelRouter } from './label.router';
|
||||
import { ProxyRouter } from './proxy.router';
|
||||
import { MessageRouter } from './sendMessage.router';
|
||||
import { SettingsRouter } from './settings.router';
|
||||
import { ViewsRouter } from './view.router';
|
||||
import { WebhookRouter } from './webhook.router';
|
||||
|
||||
enum HttpStatus {
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
NOT_FOUND = 404,
|
||||
FORBIDDEN = 403,
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
}
|
||||
|
||||
const router = Router();
|
||||
const authType = configService.get<Auth>('AUTHENTICATION').TYPE;
|
||||
const serverConfig = configService.get('SERVER');
|
||||
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]];
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
|
||||
if (!serverConfig.DISABLE_MANAGER) router.use('/manager', new ViewsRouter().router);
|
||||
|
||||
router
|
||||
.get('/', (req, res) => {
|
||||
res.status(HttpStatus.OK).json({
|
||||
status: HttpStatus.OK,
|
||||
message: 'Welcome to the Evolution API, it is working!',
|
||||
version: packageJson.version,
|
||||
swagger: !serverConfig.DISABLE_DOCS ? `${req.protocol}://${req.get('host')}/docs` : undefined,
|
||||
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
|
||||
documentation: `https://doc.evolution-api.com`,
|
||||
});
|
||||
})
|
||||
.use('/instance', new InstanceRouter(configService, ...guards).router)
|
||||
.use('/message', new MessageRouter(...guards).router)
|
||||
.use('/chat', new ChatRouter(...guards).router)
|
||||
.use('/group', new GroupRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(configService, ...guards).router)
|
||||
.use('/chatwoot', new ChatwootRouter(...guards).router)
|
||||
.use('/settings', new SettingsRouter(...guards).router)
|
||||
.use('/websocket', new WebsocketRouter(...guards).router)
|
||||
.use('/rabbitmq', new RabbitmqRouter(...guards).router)
|
||||
.use('/sqs', new SqsRouter(...guards).router)
|
||||
.use('/typebot', new TypebotRouter(...guards).router)
|
||||
.use('/proxy', new ProxyRouter(...guards).router)
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router)
|
||||
.use('/label', new LabelRouter(...guards).router);
|
||||
|
||||
export { HttpStatus, router };
|
||||
209
src/api/routers/instance.router.ts
Normal file
209
src/api/routers/instance.router.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Auth, ConfigService, Database } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { instanceNameSchema, oldTokenSchema, presenceOnlySchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
|
||||
import { instanceController } from '../server.module';
|
||||
import { OldToken } from '../services/auth.service';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('InstanceRouter');
|
||||
|
||||
export class InstanceRouter extends RouterBroker {
|
||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
||||
super();
|
||||
const auth = configService.get<Auth>('AUTHENTICATION');
|
||||
this.router
|
||||
.post('/create', ...guards, async (req, res) => {
|
||||
logger.verbose('request received in createInstance');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.createInstance(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.put(this.routerPath('restart'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in restartInstance');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.restartInstance(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('registerMobileCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in registerMobileCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<null>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: SetPresenceDto,
|
||||
execute: (instance, data) => instanceController.registerMobileCode(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('connect'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in connectInstance');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.connectToWhatsapp(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('connectionState'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in connectionState');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.connectionState(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('fetchInstances', false), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in fetchInstances');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.fetchInstances(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('setPresence'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setPresence');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<null>({
|
||||
request: req,
|
||||
schema: presenceOnlySchema,
|
||||
ClassRef: SetPresenceDto,
|
||||
execute: (instance, data) => instanceController.setPresence(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.delete(this.routerPath('logout'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in logoutInstances');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.logout(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.delete(this.routerPath('delete'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in deleteInstances');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => instanceController.deleteInstance(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
|
||||
if (auth.TYPE === 'jwt') {
|
||||
this.router.put('/refreshToken', async (req, res) => {
|
||||
logger.verbose('request received in refreshToken');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<OldToken>({
|
||||
request: req,
|
||||
schema: oldTokenSchema,
|
||||
ClassRef: OldToken,
|
||||
execute: (_, data) => instanceController.refreshToken(_, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
this.router.delete('/deleteDatabase', async (req, res) => {
|
||||
logger.verbose('request received in deleteDatabase');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const db = this.configService.get<Database>('DATABASE');
|
||||
if (db.ENABLED) {
|
||||
try {
|
||||
await dbserver.dropDatabase();
|
||||
return res
|
||||
.status(HttpStatus.CREATED)
|
||||
.json({ status: 'SUCCESS', error: false, response: { message: 'database deleted' } });
|
||||
} catch (error) {
|
||||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: 'Database is not enabled' });
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
53
src/api/routers/label.router.ts
Normal file
53
src/api/routers/label.router.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { handleLabelSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
|
||||
import { labelController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('LabelRouter');
|
||||
|
||||
export class LabelRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.get(this.routerPath('findLabels'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findLabels');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<LabelDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: LabelDto,
|
||||
execute: (instance) => labelController.fetchLabels(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('handleLabel'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in handleLabel');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<HandleLabelDto>({
|
||||
request: req,
|
||||
schema: handleLabelSchema,
|
||||
ClassRef: HandleLabelDto,
|
||||
execute: (instance, data) => labelController.handleLabel(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
52
src/api/routers/proxy.router.ts
Normal file
52
src/api/routers/proxy.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { instanceNameSchema, proxySchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ProxyDto } from '../dto/proxy.dto';
|
||||
import { proxyController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('ProxyRouter');
|
||||
|
||||
export class ProxyRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setProxy');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<ProxyDto>({
|
||||
request: req,
|
||||
schema: proxySchema,
|
||||
ClassRef: ProxyDto,
|
||||
execute: (instance, data) => proxyController.createProxy(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findProxy');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => proxyController.findProxy(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
237
src/api/routers/sendMessage.router.ts
Normal file
237
src/api/routers/sendMessage.router.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
audioMessageSchema,
|
||||
buttonMessageSchema,
|
||||
contactMessageSchema,
|
||||
listMessageSchema,
|
||||
locationMessageSchema,
|
||||
mediaMessageSchema,
|
||||
pollMessageSchema,
|
||||
reactionMessageSchema,
|
||||
statusMessageSchema,
|
||||
stickerMessageSchema,
|
||||
templateMessageSchema,
|
||||
textMessageSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
SendAudioDto,
|
||||
SendButtonDto,
|
||||
SendContactDto,
|
||||
SendListDto,
|
||||
SendLocationDto,
|
||||
SendMediaDto,
|
||||
SendPollDto,
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { sendMessageController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('MessageRouter');
|
||||
|
||||
export class MessageRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('sendText'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendText');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendTextDto>({
|
||||
request: req,
|
||||
schema: textMessageSchema,
|
||||
ClassRef: SendTextDto,
|
||||
execute: (instance, data) => sendMessageController.sendText(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendMedia'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendMedia');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendMediaDto>({
|
||||
request: req,
|
||||
schema: mediaMessageSchema,
|
||||
ClassRef: SendMediaDto,
|
||||
execute: (instance, data) => sendMessageController.sendMedia(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendWhatsAppAudio'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendWhatsAppAudio');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendAudioDto>({
|
||||
request: req,
|
||||
schema: audioMessageSchema,
|
||||
ClassRef: SendMediaDto,
|
||||
execute: (instance, data) => sendMessageController.sendWhatsAppAudio(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendTemplate'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendTemplate');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendTemplateDto>({
|
||||
request: req,
|
||||
schema: templateMessageSchema,
|
||||
ClassRef: SendTemplateDto,
|
||||
execute: (instance, data) => sendMessageController.sendTemplate(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendButtons'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendButtons');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendButtonDto>({
|
||||
request: req,
|
||||
schema: buttonMessageSchema,
|
||||
ClassRef: SendButtonDto,
|
||||
execute: (instance, data) => sendMessageController.sendButtons(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendLocation'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendLocation');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendLocationDto>({
|
||||
request: req,
|
||||
schema: locationMessageSchema,
|
||||
ClassRef: SendLocationDto,
|
||||
execute: (instance, data) => sendMessageController.sendLocation(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendList'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendList');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendListDto>({
|
||||
request: req,
|
||||
schema: listMessageSchema,
|
||||
ClassRef: SendListDto,
|
||||
execute: (instance, data) => sendMessageController.sendList(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendContact'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendContact');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendContactDto>({
|
||||
request: req,
|
||||
schema: contactMessageSchema,
|
||||
ClassRef: SendContactDto,
|
||||
execute: (instance, data) => sendMessageController.sendContact(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendReaction'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendReaction');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendReactionDto>({
|
||||
request: req,
|
||||
schema: reactionMessageSchema,
|
||||
ClassRef: SendReactionDto,
|
||||
execute: (instance, data) => sendMessageController.sendReaction(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendPoll'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendPoll');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendPollDto>({
|
||||
request: req,
|
||||
schema: pollMessageSchema,
|
||||
ClassRef: SendPollDto,
|
||||
execute: (instance, data) => sendMessageController.sendPoll(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendStatus');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendStatusDto>({
|
||||
request: req,
|
||||
schema: statusMessageSchema,
|
||||
ClassRef: SendStatusDto,
|
||||
execute: (instance, data) => sendMessageController.sendStatus(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendSticker'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendSticker');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendStickerDto>({
|
||||
request: req,
|
||||
schema: stickerMessageSchema,
|
||||
ClassRef: SendStickerDto,
|
||||
execute: (instance, data) => sendMessageController.sendSticker(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
52
src/api/routers/settings.router.ts
Normal file
52
src/api/routers/settings.router.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
import { settingsController } from '../server.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('SettingsRouter');
|
||||
|
||||
export class SettingsRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setSettings');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SettingsDto>({
|
||||
request: req,
|
||||
schema: settingsSchema,
|
||||
ClassRef: SettingsDto,
|
||||
execute: (instance, data) => settingsController.createSettings(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findSettings');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => settingsController.findSettings(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user