Implementation of Whatsapp labels management

This commit is contained in:
Judson Cairo 2024-02-08 09:16:24 -03:00
parent 32026d1fd4
commit 23f1b4ac03
25 changed files with 610 additions and 1 deletions

View File

@ -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

View File

@ -84,6 +84,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

View File

@ -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

View File

@ -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

View File

@ -34,6 +34,7 @@ export type SaveData = {
MESSAGE_UPDATE: boolean;
CONTACTS: boolean;
CHATS: boolean;
LABELS: boolean;
};
export type StoreConf = {
@ -41,6 +42,7 @@ export type StoreConf = {
MESSAGE_UP: boolean;
CONTACTS: boolean;
CHATS: boolean;
LABELS: boolean;
};
export type CleanStoreConf = {
@ -103,6 +105,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;
@ -237,6 +241,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)
@ -259,6 +264,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: {
@ -323,6 +329,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',

View File

@ -127,6 +127,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

View File

@ -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,96 @@ 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
required:
- numberJid
- labelId
- remove
/settings/set/{instanceName}:
post:
tags:

View File

@ -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',
@ -897,6 +899,8 @@ export const webhookSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -977,6 +981,8 @@ export const websocketSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -1020,6 +1026,8 @@ export const rabbitmqSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -1063,6 +1071,8 @@ export const sqsSchema: JSONSchema7 = {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -1150,3 +1160,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'],
};

View File

@ -151,6 +151,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -201,6 +203,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -248,6 +252,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
@ -295,6 +301,8 @@ export class InstanceController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View 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);
}
}

View File

@ -38,6 +38,8 @@ export class RabbitmqController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@ -38,6 +38,8 @@ export class SqsController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@ -46,6 +46,8 @@ export class WebhookController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@ -38,6 +38,8 @@ export class WebsocketController {
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',

View File

@ -0,0 +1,121 @@
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 LabelDto {
id?: string;
name: string;
color: number;
predefinedId?: string;
}
export class HandleLabelDto {
number: string;
labelId: string;
action: 'add' | 'remove';
}
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;
website?: string[];
address?: 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;
}

View File

@ -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';

View 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;

View 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() };
}
}
}

View File

@ -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,
) {

View File

@ -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 };

View 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();
}

View File

@ -15,6 +15,7 @@ import {
ChamaaiModel,
// ChatModel,
ChatwootModel,
LabelModel,
// ContactModel,
// MessageModel,
// MessageUpModel,
@ -320,6 +321,7 @@ 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;
}
@ -340,6 +342,7 @@ export class WAMonitoringService {
await TypebotModel.deleteMany({ _id: instanceName });
await WebsocketModel.deleteMany({ _id: instanceName });
await SettingsModel.deleteMany({ _id: instanceName });
await LabelModel.deleteMany({ owner: instanceName });
return;
}

View File

@ -34,6 +34,8 @@ import makeWASocket, {
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 { exec, execSync } from 'child_process';
import { arrayUnique, isBase64, isURL } from 'class-validator';
@ -105,6 +107,7 @@ import {
GroupUpdateSettingDto,
} from '../dto/group.dto';
import { InstanceDto } from '../dto/instance.dto';
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
import {
ContactMessage,
MediaMessage,
@ -2147,6 +2150,53 @@ 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' }) => {
this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION');
this.sendDataWebhook(Events.LABELS_ASSOCIATION, {
instance: this.instance.name,
type: data.type,
jid: data.association.chatId,
labelId: data.association.labelId,
});
},
};
private eventHandler() {
this.logger.verbose('Initializing event handler');
this.client.ev.process(async (events) => {
@ -2282,6 +2332,19 @@ export class WAStartupService {
this.logger.verbose('Listening event: contacts.update');
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);
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;
}
}
});
@ -4005,4 +4068,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());
}
}
}

View File

@ -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 {

View File

@ -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';
@ -77,6 +80,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,
@ -93,6 +97,7 @@ export const repository = new RepositoryBroker(
proxyRepository,
chamaaiRepository,
authRepository,
labelRepository,
configService,
dbserver?.getClient(),
);
@ -160,5 +165,6 @@ 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);
logger.info('Module - ON');