mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-27 02:48:39 -06:00
Merge branch 'develop' into main
This commit is contained in:
commit
f5d49c54e1
@ -7,6 +7,7 @@
|
||||
* Join in Group by Invite Code
|
||||
* Read messages from whatsapp in chatwoot
|
||||
* Add support to use use redis in cacheservice
|
||||
* Add support for labels
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -93,6 +93,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
WEBHOOK_EVENTS_CALL=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
@ -73,6 +73,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
||||
|
@ -98,6 +98,8 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
ENV WEBHOOK_EVENTS_CALL=true
|
||||
|
||||
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
@ -36,6 +36,7 @@ export type SaveData = {
|
||||
MESSAGE_UPDATE: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type StoreConf = {
|
||||
@ -43,6 +44,7 @@ export type StoreConf = {
|
||||
MESSAGE_UP: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type CleanStoreConf = {
|
||||
@ -106,6 +108,8 @@ export type EventsWebhook = {
|
||||
CHATS_DELETE: boolean;
|
||||
CHATS_UPSERT: boolean;
|
||||
CONNECTION_UPDATE: boolean;
|
||||
LABELS_EDIT: boolean;
|
||||
LABELS_ASSOCIATION: boolean;
|
||||
GROUPS_UPSERT: boolean;
|
||||
GROUP_UPDATE: boolean;
|
||||
GROUP_PARTICIPANTS_UPDATE: boolean;
|
||||
@ -242,6 +246,7 @@ export class ConfigService {
|
||||
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
|
||||
CONTACTS: process.env?.STORE_CONTACTS === 'true',
|
||||
CHATS: process.env?.STORE_CHATS === 'true',
|
||||
LABELS: process.env?.STORE_LABELS === 'true',
|
||||
},
|
||||
CLEAN_STORE: {
|
||||
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
|
||||
@ -265,6 +270,7 @@ export class ConfigService {
|
||||
MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true',
|
||||
CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true',
|
||||
LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true',
|
||||
},
|
||||
},
|
||||
REDIS: {
|
||||
@ -331,6 +337,8 @@ export class ConfigService {
|
||||
CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true',
|
||||
CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true',
|
||||
CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true',
|
||||
LABELS_EDIT: process.env?.WEBHOOK_EVENTS_LABELS_EDIT === 'true',
|
||||
LABELS_ASSOCIATION: process.env?.WEBHOOK_EVENTS_LABELS_ASSOCIATION === 'true',
|
||||
GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true',
|
||||
GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true',
|
||||
GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
|
||||
|
@ -136,6 +136,8 @@ WEBHOOK:
|
||||
GROUP_UPDATE: true
|
||||
GROUP_PARTICIPANTS_UPDATE: true
|
||||
CONNECTION_UPDATE: true
|
||||
LABELS_EDIT: true
|
||||
LABELS_ASSOCIATION: true
|
||||
CALL: true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
NEW_JWT_TOKEN: false
|
||||
|
@ -51,6 +51,7 @@ tags:
|
||||
- name: Send Message Controller
|
||||
- name: Chat Controller
|
||||
- name: Group Controller
|
||||
- name: Label Controller
|
||||
- name: Profile Settings
|
||||
- name: JWT
|
||||
- name: Settings
|
||||
@ -1856,6 +1857,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -1932,6 +1935,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -2008,6 +2013,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -2046,6 +2053,97 @@ paths:
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/label/findLabels/{instanceName}:
|
||||
get:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: List all labels for an instance.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
color:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
predefinedId:
|
||||
type: string
|
||||
required:
|
||||
- color
|
||||
- name
|
||||
- id
|
||||
/label/handleLabel/{instanceName}:
|
||||
put:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: Change the label (add or remove) for an specific chat.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
action:
|
||||
type: string
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
required:
|
||||
- number
|
||||
- labelId
|
||||
- action
|
||||
example:
|
||||
number: '553499999999'
|
||||
labelId: '1'
|
||||
action: add
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
numberJid:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
remove:
|
||||
type: boolean
|
||||
add:
|
||||
type: boolean
|
||||
required:
|
||||
- numberJid
|
||||
- labelId
|
||||
|
||||
/settings/set/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
|
@ -4,8 +4,6 @@ import path from 'path';
|
||||
|
||||
import { ConfigService, Language } from '../config/env.config';
|
||||
|
||||
// export class i18n {
|
||||
// constructor(private readonly configService: ConfigService) {
|
||||
const languages = ['en', 'pt-BR'];
|
||||
const translationsPath = path.join(__dirname, 'translations');
|
||||
const configService: ConfigService = new ConfigService();
|
||||
@ -31,6 +29,4 @@ i18next.init({
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
// }
|
||||
// }
|
||||
export default i18next;
|
||||
|
@ -53,6 +53,8 @@ export const instanceNameSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -938,6 +940,8 @@ export const webhookSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1018,6 +1022,8 @@ export const websocketSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1061,6 +1067,8 @@ export const rabbitmqSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1104,6 +1112,8 @@ export const sqsSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1191,3 +1201,14 @@ export const chamaaiSchema: JSONSchema7 = {
|
||||
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
|
||||
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
|
||||
};
|
||||
|
||||
export const handleLabelSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
labelId: { type: 'string' },
|
||||
action: { type: 'string', enum: ['add', 'remove'] },
|
||||
},
|
||||
required: ['number', 'labelId', 'action'],
|
||||
};
|
||||
|
@ -21,7 +21,6 @@ const logger = new Logger('Validate');
|
||||
export abstract class RouterBroker {
|
||||
constructor() {}
|
||||
public routerPath(path: string, param = true) {
|
||||
// const route = param ? '/:instanceName/' + path : '/' + path;
|
||||
let route = '/' + path;
|
||||
param ? (route += '/:instanceName') : null;
|
||||
|
||||
@ -56,10 +55,6 @@ export abstract class RouterBroker {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return message;
|
||||
// return {
|
||||
// property: property.replace('instance.', ''),
|
||||
// message,
|
||||
// };
|
||||
});
|
||||
logger.error(message);
|
||||
throw new BadRequestException(message);
|
||||
|
@ -155,6 +155,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -205,6 +207,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -252,6 +256,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -299,6 +305,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
20
src/whatsapp/controllers/label.controller.ts
Normal file
20
src/whatsapp/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);
|
||||
}
|
||||
}
|
@ -38,6 +38,8 @@ export class RabbitmqController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -1,7 +1,4 @@
|
||||
// import { isURL } from 'class-validator';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
// import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
|
@ -38,6 +38,8 @@ export class SqsController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -54,6 +54,8 @@ export class WebhookController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -38,6 +38,8 @@ export class WebsocketController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
12
src/whatsapp/dto/label.dto.ts
Normal file
12
src/whatsapp/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';
|
||||
}
|
@ -7,6 +7,7 @@ export class ChatRaw {
|
||||
id?: string;
|
||||
owner: string;
|
||||
lastMsgTimestamp?: number;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
type ChatRawBoolean<T> = {
|
||||
@ -18,6 +19,7 @@ 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');
|
||||
|
@ -3,6 +3,7 @@ export * from './chamaai.model';
|
||||
export * from './chat.model';
|
||||
export * from './chatwoot.model';
|
||||
export * from './contact.model';
|
||||
export * from './label.model';
|
||||
export * from './message.model';
|
||||
export * from './proxy.model';
|
||||
export * from './rabbitmq.model';
|
||||
|
29
src/whatsapp/models/label.model.ts
Normal file
29
src/whatsapp/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;
|
@ -1,4 +1,4 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService } from '../../config/env.config';
|
||||
@ -64,6 +64,37 @@ export class AuthRepository extends Repository {
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
@ -115,4 +115,63 @@ export class ChatRepository extends Repository {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ export class ContactQuery {
|
||||
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);
|
||||
@ -169,4 +174,54 @@ export class ContactRepository extends Repository {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
src/whatsapp/repository/label.repository.ts
Normal file
111
src/whatsapp/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() };
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import { ChamaaiRepository } from './chamaai.repository';
|
||||
import { ChatRepository } from './chat.repository';
|
||||
import { ChatwootRepository } from './chatwoot.repository';
|
||||
import { ContactRepository } from './contact.repository';
|
||||
import { LabelRepository } from './label.repository';
|
||||
import { MessageRepository } from './message.repository';
|
||||
import { MessageUpRepository } from './messageUp.repository';
|
||||
import { ProxyRepository } from './proxy.repository';
|
||||
@ -34,6 +35,7 @@ export class RepositoryBroker {
|
||||
public readonly proxy: ProxyRepository,
|
||||
public readonly chamaai: ChamaaiRepository,
|
||||
public readonly auth: AuthRepository,
|
||||
public readonly labels: LabelRepository,
|
||||
private configService: ConfigService,
|
||||
dbServer?: MongoClient,
|
||||
) {
|
||||
|
@ -62,7 +62,7 @@ export class ChatRouter extends RouterBroker {
|
||||
execute: (instance, data) => chatController.whatsappNumber(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in markMessageAsRead');
|
||||
|
@ -5,7 +5,6 @@ import { chatwootSchema, instanceNameSchema } from '../../validate/validate.sche
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
// import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { chatwootController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { ChatRouter } from './chat.router';
|
||||
import { ChatwootRouter } from './chatwoot.router';
|
||||
import { GroupRouter } from './group.router';
|
||||
import { InstanceRouter } from './instance.router';
|
||||
import { LabelRouter } from './label.router';
|
||||
import { ProxyRouter } from './proxy.router';
|
||||
import { RabbitmqRouter } from './rabbitmq.router';
|
||||
import { MessageRouter } from './sendMessage.router';
|
||||
@ -61,6 +62,7 @@ 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('/chamaai', new ChamaaiRouter(...guards).router)
|
||||
.use('/label', new LabelRouter(...guards).router);
|
||||
|
||||
export { HttpStatus, router };
|
||||
|
53
src/whatsapp/routers/label.router.ts
Normal file
53
src/whatsapp/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 '../whatsapp.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();
|
||||
}
|
@ -5,7 +5,6 @@ import { instanceNameSchema, settingsSchema } from '../../validate/validate.sche
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
// import { SettingsService } from '../services/settings.service';
|
||||
import { settingsController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
|
@ -50,11 +50,6 @@ export class ChatwootService {
|
||||
this.cache.set(cacheKey, provider);
|
||||
|
||||
return provider;
|
||||
// try {
|
||||
// } catch (error) {
|
||||
// this.logger.error('provider not found');
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
private async clientCw(instance: InstanceDto) {
|
||||
@ -388,10 +383,6 @@ export class ChatwootService {
|
||||
q: query,
|
||||
});
|
||||
} else {
|
||||
// contact = await client.contacts.filter({
|
||||
// accountId: this.provider.account_id,
|
||||
// payload: this.getFilterPayload(query),
|
||||
// });
|
||||
// hotfix for: https://github.com/EvolutionAPI/evolution-api/pull/382. waiting fix: https://github.com/figurolatam/chatwoot-sdk/pull/7
|
||||
contact = await chatwootRequest(this.getClientCwConfig(), {
|
||||
method: 'POST',
|
||||
@ -1193,7 +1184,6 @@ export class ChatwootService {
|
||||
if (state !== 'open') {
|
||||
if (state === 'close') {
|
||||
this.logger.verbose('request cleaning up instance: ' + instance.instanceName);
|
||||
// await this.waMonitor.cleaningUp(instance.instanceName);
|
||||
}
|
||||
this.logger.verbose('connect to whatsapp');
|
||||
const number = command.split(':')[1];
|
||||
@ -1204,6 +1194,12 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
if (command === 'clearcache') {
|
||||
this.logger.verbose('command clearcache found');
|
||||
waInstance.clearCacheChatwoot();
|
||||
await this.createBotMessage(instance, `✅ ${body.inbox.name} instance cache cleared.`, 'incoming');
|
||||
}
|
||||
|
||||
if (command === 'status') {
|
||||
this.logger.verbose('command status found');
|
||||
|
||||
@ -2141,7 +2137,7 @@ export class ChatwootService {
|
||||
this.logger.verbose('qrcode success');
|
||||
const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64');
|
||||
|
||||
const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`;
|
||||
const fileName = `${path.join(waInstance?.storePath, 'temp', `${instance.instanceName}.png`)}`;
|
||||
|
||||
this.logger.verbose('temp file name: ' + fileName);
|
||||
|
||||
|
@ -2,22 +2,20 @@ import { execSync } from 'child_process';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { opendirSync, readdirSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { Db } from 'mongodb';
|
||||
import { Collection } from 'mongoose';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
|
||||
import { NotFoundException } from '../../exceptions';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import {
|
||||
AuthModel,
|
||||
ChamaaiModel,
|
||||
// ChatModel,
|
||||
ChatwootModel,
|
||||
// ContactModel,
|
||||
// MessageModel,
|
||||
// MessageUpModel,
|
||||
ContactModel,
|
||||
LabelModel,
|
||||
ProxyModel,
|
||||
RabbitmqModel,
|
||||
SettingsModel,
|
||||
@ -42,7 +40,6 @@ export class WAMonitoringService {
|
||||
|
||||
this.removeInstance();
|
||||
this.noConnection();
|
||||
// this.delInstanceFiles();
|
||||
|
||||
Object.assign(this.db, configService.get<Database>('DATABASE'));
|
||||
Object.assign(this.redis, configService.get<Redis>('REDIS'));
|
||||
@ -57,8 +54,6 @@ export class WAMonitoringService {
|
||||
|
||||
private dbInstance: Db;
|
||||
|
||||
private dbStore = dbserver;
|
||||
|
||||
private readonly logger = new Logger(WAMonitoringService.name);
|
||||
public readonly waInstances: Record<string, WAStartupService> = {};
|
||||
|
||||
@ -321,17 +316,13 @@ export class WAMonitoringService {
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'labels', instanceName + '*')}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('cleaning store database instance: ' + instanceName);
|
||||
|
||||
// await ChatModel.deleteMany({ owner: instanceName });
|
||||
// await ContactModel.deleteMany({ owner: instanceName });
|
||||
// await MessageUpModel.deleteMany({ owner: instanceName });
|
||||
// await MessageModel.deleteMany({ owner: instanceName });
|
||||
|
||||
await AuthModel.deleteMany({ _id: instanceName });
|
||||
await WebhookModel.deleteMany({ _id: instanceName });
|
||||
await ChatwootModel.deleteMany({ _id: instanceName });
|
||||
@ -341,6 +332,8 @@ export class WAMonitoringService {
|
||||
await TypebotModel.deleteMany({ _id: instanceName });
|
||||
await WebsocketModel.deleteMany({ _id: instanceName });
|
||||
await SettingsModel.deleteMany({ _id: instanceName });
|
||||
await LabelModel.deleteMany({ owner: instanceName });
|
||||
await ContactModel.deleteMany({ owner: instanceName });
|
||||
|
||||
return;
|
||||
}
|
||||
@ -395,7 +388,7 @@ export class WAMonitoringService {
|
||||
this.logger.verbose('Database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
|
||||
await this.deleteTempInstances(collections);
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('Reading collections and setting instances');
|
||||
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
|
||||
@ -507,4 +500,21 @@ export class WAMonitoringService {
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteTempInstances(collections: Collection<Document>[]) {
|
||||
this.logger.verbose('Cleaning up temp instances');
|
||||
const auths = await this.repository.auth.list();
|
||||
if (auths.length === 0) {
|
||||
this.logger.verbose('No temp instances found');
|
||||
return;
|
||||
}
|
||||
let tempInstances = 0;
|
||||
auths.forEach((auth) => {
|
||||
if (collections.find((coll) => coll.namespace.replace(/^[\w-]+\./, '') === auth._id)) {
|
||||
return;
|
||||
}
|
||||
tempInstances++;
|
||||
this.eventEmitter.emit('remove.instance', auth._id, 'inner');
|
||||
});
|
||||
this.logger.verbose('Temp instances removed: ' + tempInstances);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,41 @@
|
||||
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
|
||||
import { Boom } from '@hapi/boom';
|
||||
import makeWASocket, {
|
||||
AnyMessageContent,
|
||||
BufferedEventData,
|
||||
BufferJSON,
|
||||
CacheStore,
|
||||
Chat,
|
||||
ConnectionState,
|
||||
Contact,
|
||||
delay,
|
||||
DisconnectReason,
|
||||
downloadMediaMessage,
|
||||
fetchLatestBaileysVersion,
|
||||
generateWAMessageFromContent,
|
||||
getAggregateVotesInPollMessage,
|
||||
getContentType,
|
||||
getDevice,
|
||||
GroupMetadata,
|
||||
isJidBroadcast,
|
||||
isJidGroup,
|
||||
isJidUser,
|
||||
makeCacheableSignalKeyStore,
|
||||
MessageUpsertType,
|
||||
MiscMessageGenerationOptions,
|
||||
ParticipantAction,
|
||||
prepareWAMessageMedia,
|
||||
proto,
|
||||
useMultiFileAuthState,
|
||||
UserFacingSocketConfig,
|
||||
WABrowserDescription,
|
||||
WAMediaUpload,
|
||||
WAMessage,
|
||||
WAMessageUpdate,
|
||||
WASocket,
|
||||
} from '@whiskeysockets/baileys';
|
||||
import { Label } from '@whiskeysockets/baileys/lib/Types/Label';
|
||||
import { LabelAssociation } from '@whiskeysockets/baileys/lib/Types/LabelAssociation';
|
||||
import axios from 'axios';
|
||||
import { execSync } from 'child_process';
|
||||
import { isURL } from 'class-validator
|
||||
@ -64,6 +102,7 @@ import {
|
||||
GroupUpdateSettingDto,
|
||||
} from '../dto/group.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
|
||||
import {
|
||||
ContactMessage,
|
||||
MediaMessage,
|
||||
@ -1452,7 +1491,7 @@ export class WAStartupService {
|
||||
);
|
||||
|
||||
this.logger.verbose('Verifying if contacts exists in database to insert');
|
||||
const contactsRaw: ContactRaw[] = [];
|
||||
let contactsRaw: ContactRaw[] = [];
|
||||
|
||||
for (const contact of contacts) {
|
||||
if (contactsRepository.has(contact.id)) {
|
||||
@ -1462,7 +1501,7 @@ export class WAStartupService {
|
||||
contactsRaw.push({
|
||||
id: contact.id,
|
||||
pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0],
|
||||
profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl,
|
||||
profilePictureUrl: null,
|
||||
owner: this.instance.name,
|
||||
});
|
||||
}
|
||||
@ -1477,6 +1516,23 @@ export class WAStartupService {
|
||||
this.chatwootService.addHistoryContacts({ instanceName: this.instance.name }, contactsRaw);
|
||||
chatwootImport.importHistoryContacts({ instanceName: this.instance.name }, this.localChatwoot);
|
||||
}
|
||||
|
||||
// Update profile pictures
|
||||
contactsRaw = [];
|
||||
for await (const contact of contacts) {
|
||||
contactsRaw.push({
|
||||
id: contact.id,
|
||||
pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0],
|
||||
profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl,
|
||||
owner: this.instance.name,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE');
|
||||
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw);
|
||||
|
||||
this.logger.verbose('Updating contacts in database');
|
||||
this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
@ -1942,10 +1998,86 @@ export class WAStartupService {
|
||||
},
|
||||
};
|
||||
|
||||
private readonly labelHandle = {
|
||||
[Events.LABELS_EDIT]: async (label: Label, database: Database) => {
|
||||
this.logger.verbose('Event received: labels.edit');
|
||||
this.logger.verbose('Finding labels in database');
|
||||
const labelsRepository = await this.repository.labels.find({
|
||||
where: { owner: this.instance.name },
|
||||
});
|
||||
|
||||
const savedLabel = labelsRepository.find((l) => l.id === label.id);
|
||||
if (label.deleted && savedLabel) {
|
||||
this.logger.verbose('Sending data to webhook in event LABELS_EDIT');
|
||||
await this.repository.labels.delete({
|
||||
where: { owner: this.instance.name, id: label.id },
|
||||
});
|
||||
this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name });
|
||||
return;
|
||||
}
|
||||
|
||||
const labelName = label.name.replace(/[^\x20-\x7E]/g, '');
|
||||
if (!savedLabel || savedLabel.color !== label.color || savedLabel.name !== labelName) {
|
||||
this.logger.verbose('Sending data to webhook in event LABELS_EDIT');
|
||||
await this.repository.labels.insert(
|
||||
{
|
||||
color: label.color,
|
||||
name: labelName,
|
||||
owner: this.instance.name,
|
||||
id: label.id,
|
||||
predefinedId: label.predefinedId,
|
||||
},
|
||||
this.instance.name,
|
||||
database.SAVE_DATA.LABELS,
|
||||
);
|
||||
this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name });
|
||||
}
|
||||
},
|
||||
|
||||
[Events.LABELS_ASSOCIATION]: async (
|
||||
data: { association: LabelAssociation; type: 'remove' | 'add' },
|
||||
database: Database,
|
||||
) => {
|
||||
this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION');
|
||||
|
||||
// Atualiza labels nos chats
|
||||
if (database.SAVE_DATA.CHATS) {
|
||||
const chats = await this.repository.chat.find({
|
||||
where: {
|
||||
owner: this.instance.name,
|
||||
},
|
||||
});
|
||||
const chat = chats.find((c) => c.id === data.association.chatId);
|
||||
if (chat) {
|
||||
let labels = [...chat.labels];
|
||||
if (data.type === 'remove') {
|
||||
labels = labels.filter((label) => label !== data.association.labelId);
|
||||
} else if (data.type === 'add') {
|
||||
labels = [...labels, data.association.labelId];
|
||||
}
|
||||
await this.repository.chat.update(
|
||||
[{ id: chat.id, owner: this.instance.name, labels }],
|
||||
this.instance.name,
|
||||
database.SAVE_DATA.CHATS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Envia dados para o webhook
|
||||
this.sendDataWebhook(Events.LABELS_ASSOCIATION, {
|
||||
instance: this.instance.name,
|
||||
type: data.type,
|
||||
chatId: data.association.chatId,
|
||||
labelId: data.association.labelId,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
private eventHandler() {
|
||||
this.logger.verbose('Initializing event handler');
|
||||
this.client.ev.process(async (events) => {
|
||||
if (!this.endSession) {
|
||||
this.logger.verbose(`Event received: ${Object.keys(events).join(', ')}`);
|
||||
const database = this.configService.get<Database>('DATABASE');
|
||||
const settings = await this.findSettings();
|
||||
|
||||
@ -2078,6 +2210,20 @@ export class WAStartupService {
|
||||
const payload = events['contacts.update'];
|
||||
this.contactHandle['contacts.update'](payload, database);
|
||||
}
|
||||
|
||||
if (events[Events.LABELS_ASSOCIATION]) {
|
||||
this.logger.verbose('Listening event: labels.association');
|
||||
const payload = events[Events.LABELS_ASSOCIATION];
|
||||
this.labelHandle[Events.LABELS_ASSOCIATION](payload, database);
|
||||
return;
|
||||
}
|
||||
|
||||
if (events[Events.LABELS_EDIT]) {
|
||||
this.logger.verbose('Listening event: labels.edit');
|
||||
const payload = events[Events.LABELS_EDIT];
|
||||
this.labelHandle[Events.LABELS_EDIT](payload, database);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -3107,6 +3253,10 @@ export class WAStartupService {
|
||||
onWhatsapp.push(...groups);
|
||||
|
||||
// USERS
|
||||
const contacts: ContactRaw[] = await this.repository.contact.findManyById({
|
||||
owner: this.instance.name,
|
||||
ids: jids.users.map(({ jid }) => (jid.startsWith('+') ? jid.substring(1) : jid)),
|
||||
});
|
||||
const verify = await this.client.onWhatsApp(
|
||||
...jids.users.map(({ jid }) => (!jid.startsWith('+') ? `+${jid}` : jid)),
|
||||
);
|
||||
@ -3116,18 +3266,6 @@ export class WAStartupService {
|
||||
const isBrWithDigit = user.jid.startsWith('55') && user.jid.slice(4, 5) === '9' && user.jid.length === 28;
|
||||
const jid = isBrWithDigit ? user.jid.slice(0, 4) + user.jid.slice(5) : user.jid;
|
||||
|
||||
const query: ContactQuery = {
|
||||
where: {
|
||||
owner: this.instance.name,
|
||||
id: user.jid.startsWith('+') ? user.jid.substring(1) : user.jid,
|
||||
},
|
||||
};
|
||||
const contacts: ContactRaw[] = await this.repository.contact.find(query);
|
||||
let firstContactFound;
|
||||
if (contacts.length > 0) {
|
||||
firstContactFound = contacts[0].pushName;
|
||||
}
|
||||
|
||||
const numberVerified = verify.find((v) => {
|
||||
const mainJidSimilarity = levenshtein.get(user.jid, v.jid) / Math.max(user.jid.length, v.jid.length);
|
||||
const jidSimilarity = levenshtein.get(jid, v.jid) / Math.max(jid.length, v.jid.length);
|
||||
@ -3136,7 +3274,7 @@ export class WAStartupService {
|
||||
return {
|
||||
exists: !!numberVerified?.exists,
|
||||
jid: numberVerified?.jid || user.jid,
|
||||
name: firstContactFound,
|
||||
name: contacts.find((c) => c.id === jid)?.pushName,
|
||||
number: user.number,
|
||||
};
|
||||
}),
|
||||
@ -3808,4 +3946,47 @@ export class WAStartupService {
|
||||
throw new BadRequestException('Unable to leave the group', error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchLabels(): Promise<LabelDto[]> {
|
||||
this.logger.verbose('Fetching labels');
|
||||
const labels = await this.repository.labels.find({
|
||||
where: {
|
||||
owner: this.instance.name,
|
||||
},
|
||||
});
|
||||
|
||||
return labels.map((label) => ({
|
||||
color: label.color,
|
||||
name: label.name,
|
||||
id: label.id,
|
||||
predefinedId: label.predefinedId,
|
||||
}));
|
||||
}
|
||||
|
||||
public async handleLabel(data: HandleLabelDto) {
|
||||
this.logger.verbose('Adding label');
|
||||
const whatsappContact = await this.whatsappNumber({ numbers: [data.number] });
|
||||
if (whatsappContact.length === 0) {
|
||||
throw new NotFoundException('Number not found');
|
||||
}
|
||||
const contact = whatsappContact[0];
|
||||
if (!contact.exists) {
|
||||
throw new NotFoundException('Number is not on WhatsApp');
|
||||
}
|
||||
|
||||
try {
|
||||
if (data.action === 'add') {
|
||||
await this.client.addChatLabel(contact.jid, data.labelId);
|
||||
|
||||
return { numberJid: contact.jid, labelId: data.labelId, add: true };
|
||||
}
|
||||
if (data.action === 'remove') {
|
||||
await this.client.removeChatLabel(contact.jid, data.labelId);
|
||||
|
||||
return { numberJid: contact.jid, labelId: data.labelId, remove: true };
|
||||
}
|
||||
} catch (error) {
|
||||
throw new BadRequestException(`Unable to ${data.action} label to chat`, error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ export enum Events {
|
||||
TYPEBOT_START = 'typebot.start',
|
||||
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
|
||||
CHAMA_AI_ACTION = 'chama-ai.action',
|
||||
LABELS_EDIT = 'labels.edit',
|
||||
LABELS_ASSOCIATION = 'labels.association',
|
||||
CREDS_UPDATE = 'creds.update',
|
||||
MESSAGING_HISTORY_SET = 'messaging-history.set',
|
||||
}
|
||||
|
||||
export declare namespace wa {
|
||||
|
@ -9,6 +9,7 @@ import { ChatController } from './controllers/chat.controller';
|
||||
import { ChatwootController } from './controllers/chatwoot.controller';
|
||||
import { GroupController } from './controllers/group.controller';
|
||||
import { InstanceController } from './controllers/instance.controller';
|
||||
import { LabelController } from './controllers/label.controller';
|
||||
import { ProxyController } from './controllers/proxy.controller';
|
||||
import { RabbitmqController } from './controllers/rabbitmq.controller';
|
||||
import { SendMessageController } from './controllers/sendMessage.controller';
|
||||
@ -33,11 +34,13 @@ import {
|
||||
WebhookModel,
|
||||
WebsocketModel,
|
||||
} from './models';
|
||||
import { LabelModel } from './models/label.model';
|
||||
import { AuthRepository } from './repository/auth.repository';
|
||||
import { ChamaaiRepository } from './repository/chamaai.repository';
|
||||
import { ChatRepository } from './repository/chat.repository';
|
||||
import { ChatwootRepository } from './repository/chatwoot.repository';
|
||||
import { ContactRepository } from './repository/contact.repository';
|
||||
import { LabelRepository } from './repository/label.repository';
|
||||
import { MessageRepository } from './repository/message.repository';
|
||||
import { MessageUpRepository } from './repository/messageUp.repository';
|
||||
import { ProxyRepository } from './repository/proxy.repository';
|
||||
@ -82,6 +85,7 @@ const sqsRepository = new SqsRepository(SqsModel, configService);
|
||||
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
|
||||
const settingsRepository = new SettingsRepository(SettingsModel, configService);
|
||||
const authRepository = new AuthRepository(AuthModel, configService);
|
||||
const labelRepository = new LabelRepository(LabelModel, configService);
|
||||
|
||||
export const repository = new RepositoryBroker(
|
||||
messageRepository,
|
||||
@ -98,6 +102,7 @@ export const repository = new RepositoryBroker(
|
||||
proxyRepository,
|
||||
chamaaiRepository,
|
||||
authRepository,
|
||||
labelRepository,
|
||||
configService,
|
||||
dbserver?.getClient(),
|
||||
);
|
||||
@ -165,6 +170,7 @@ export const instanceController = new InstanceController(
|
||||
export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
export const groupController = new GroupController(waMonitor);
|
||||
export const labelController = new LabelController(waMonitor);
|
||||
|
||||
|
||||
export const WAStartupClass: {
|
||||
|
Loading…
Reference in New Issue
Block a user