diff --git a/src/api/controllers/sendMessage.controller.ts b/src/api/controllers/sendMessage.controller.ts index 52599724..8c005094 100644 --- a/src/api/controllers/sendMessage.controller.ts +++ b/src/api/controllers/sendMessage.controller.ts @@ -21,47 +21,40 @@ import { WAMonitoringService } from '../services/monitor.service'; export class SendMessageController { constructor(private readonly waMonitor: WAMonitoringService) {} - public async sendText({ instanceName }: InstanceDto, data: SendTextDto) { - return await this.waMonitor.waInstances[instanceName].textMessage(data); - } - public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) { return await this.waMonitor.waInstances[instanceName].templateMessage(data); } + public async sendText({ instanceName }: InstanceDto, data: SendTextDto) { + return await this.waMonitor.waInstances[instanceName].textMessage(data); + } + public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) { - if ( - isBase64(data?.mediaMessage?.media) && - !data?.mediaMessage?.fileName && - data?.mediaMessage?.mediatype === 'document' - ) { + if (isBase64(data?.media) && !data?.fileName && data?.mediatype === 'document') { throw new BadRequestException('For base64 the file name must be informed.'); } - if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) { + if (isURL(data?.media) || isBase64(data?.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) { - if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) { + if (isURL(data.sticker) || isBase64(data.sticker)) { 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) { - if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) { + if (isURL(data.audio) || isBase64(data.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) { - 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); } @@ -78,7 +71,7 @@ export class SendMessageController { } public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) { - if (!data.reactionMessage.reaction.match(/[^()\w\sà-ú"-+]+/)) { + if (!data.reaction.match(/[^()\w\sà-ú"-+]+/)) { throw new BadRequestException('"reaction" must be an emoji'); } return await this.waMonitor.waInstances[instanceName].reactionMessage(data); diff --git a/src/api/controllers/webhook.controller.ts b/src/api/controllers/webhook.controller.ts index 721c3ec7..889b216f 100644 --- a/src/api/controllers/webhook.controller.ts +++ b/src/api/controllers/webhook.controller.ts @@ -1,6 +1,7 @@ import { isURL } from 'class-validator'; import { BadRequestException } from '../../exceptions'; +import { Events } from '../../validate/validate.schema'; import { InstanceDto } from '../dto/instance.dto'; import { WebhookDto } from '../dto/webhook.dto'; import { WAMonitoringService } from '../services/monitor.service'; @@ -20,32 +21,7 @@ export class WebhookController { data.url = ''; data.events = []; } else if (data.events.length === 0) { - 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', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - ]; + data.events = Events; } return this.webhookService.create(instance, data); diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 3ed55d52..6865faf2 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -109,10 +109,8 @@ export class Metadata extends OptionsMessage { } export class SendPresenceDto extends Metadata { - options: { - presence: WAPresence; - delay: number; - }; + presence: WAPresence; + delay: number; } export class UpdateMessageDto extends Metadata { diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index 7bb33074..1a6b4db5 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -18,16 +18,15 @@ export class Options { linkPreview?: boolean; encoding?: boolean; } -class OptionsMessage { - options: Options; -} -export class Metadata extends OptionsMessage { - number: string; -} - -class TextMessage { - text: string; +export class MediaMessage { + mediatype: MediaType; + mimetype?: string; + caption?: string; + // for document + fileName?: string; + // url or base64 + media: string; } export class StatusMessage { @@ -40,30 +39,43 @@ export class StatusMessage { font?: number; } -class PollMessage { +export class Metadata { + number: string; + delay?: number; + quoted?: Quoted; + linkPreview?: boolean; + everyOne: boolean; + mentioned: string[]; + encoding?: boolean; +} + +export class SendTextDto extends Metadata { + text: string; +} +export class SendPresence extends Metadata { + text: string; +} + +export class SendStatusDto extends Metadata { + type: string; + content: string; + statusJidList?: string[]; + allContacts?: boolean; + caption?: string; + backgroundColor?: string; + font?: number; +} + +export class SendPollDto extends Metadata { 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 { + +export class SendMediaDto extends Metadata { mediatype: MediaType; mimetype?: string; caption?: string; @@ -72,21 +84,13 @@ export class MediaMessage { // url or base64 media: string; } -export class SendMediaDto extends Metadata { - mediaMessage: MediaMessage; -} -class Sticker { - image: string; -} + export class SendStickerDto extends Metadata { - stickerMessage: Sticker; + sticker: string; } -class Audio { - audio: string; -} export class SendAudioDto extends Metadata { - audioMessage: Audio; + audio: string; } class Button { @@ -98,21 +102,17 @@ class ButtonMessage { description: string; footerText?: string; buttons: Button[]; - mediaMessage?: MediaMessage; } export class SendButtonDto extends Metadata { buttonMessage: ButtonMessage; } -class LocationMessage { +export class SendLocationDto extends Metadata { latitude: number; longitude: number; name?: string; address?: string; } -export class SendLocationDto extends Metadata { - locationMessage: LocationMessage; -} class Row { title: string; @@ -123,16 +123,13 @@ class Section { title: string; rows: Row[]; } -class ListMessage { +export class SendListDto extends Metadata { title: string; - description: string; + description?: string; footerText?: string; buttonText: string; sections: Section[]; } -export class SendListDto extends Metadata { - listMessage: ListMessage; -} export class ContactMessage { fullName: string; @@ -143,23 +140,16 @@ export class ContactMessage { url?: string; } -export class TemplateMessage { +export class SendTemplateDto extends Metadata { name: string; language: string; components: any; } - -export class SendTemplateDto extends Metadata { - templateMessage: TemplateMessage; -} export class SendContactDto extends Metadata { - contactMessage: ContactMessage[]; + contact: ContactMessage[]; } -class ReactionMessage { +export class SendReactionDto { key: proto.IMessageKey; reaction: string; } -export class SendReactionDto { - reactionMessage: ReactionMessage; -} diff --git a/src/api/integrations/chatwoot/validate/chatwoot.schema.ts b/src/api/integrations/chatwoot/validate/chatwoot.schema.ts index 05995a2c..21135486 100644 --- a/src/api/integrations/chatwoot/validate/chatwoot.schema.ts +++ b/src/api/integrations/chatwoot/validate/chatwoot.schema.ts @@ -25,20 +25,20 @@ export const chatwootSchema: JSONSchema7 = { type: 'object', properties: { enabled: { type: 'boolean', enum: [true, false] }, - account_id: { type: 'string' }, + accountId: { type: 'string' }, token: { type: 'string' }, url: { type: 'string' }, - sign_msg: { type: 'boolean', enum: [true, false] }, - sign_delimiter: { type: ['string', 'null'] }, - name_inbox: { type: ['string', 'null'] }, - reopen_conversation: { type: 'boolean', enum: [true, false] }, + signMsg: { type: 'boolean', enum: [true, false] }, + signDelimiter: { type: ['string', 'null'] }, + nameInbox: { type: ['string', 'null'] }, + reopenConversation: { 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] }, - merge_brazil_contacts: { type: 'boolean', enum: [true, false] }, - import_messages: { type: 'boolean', enum: [true, false] }, - days_limit_import_messages: { type: 'number' }, + autoCreate: { type: 'boolean', enum: [true, false] }, + importContacts: { type: 'boolean', enum: [true, false] }, + mergeBrazilContacts: { type: 'boolean', enum: [true, false] }, + importMessages: { type: 'boolean', enum: [true, false] }, + daysLimitImportMessages: { 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'), + required: ['enabled', 'accountId', 'token', 'url', 'signMsg', 'reopenConversation', 'conversation_pending'], + ...isNotEmpty('enabled', 'accountId', 'token', 'url', 'signMsg', 'reopenConversation', 'conversation_pending'), }; diff --git a/src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts b/src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts index b2450286..c3bed060 100644 --- a/src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts +++ b/src/api/integrations/rabbitmq/validate/rabbitmq.schema.ts @@ -1,6 +1,8 @@ import { JSONSchema7 } from 'json-schema'; import { v4 } from 'uuid'; +import { Events } from '../../../../validate/validate.schema'; + const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { const properties = {}; propertyNames.forEach( @@ -30,32 +32,7 @@ export const rabbitmqSchema: JSONSchema7 = { 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', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - ], + enum: Events, }, }, }, diff --git a/src/api/integrations/sqs/validate/sqs.schema.ts b/src/api/integrations/sqs/validate/sqs.schema.ts index 254ae00f..ccd90c6b 100644 --- a/src/api/integrations/sqs/validate/sqs.schema.ts +++ b/src/api/integrations/sqs/validate/sqs.schema.ts @@ -1,6 +1,8 @@ import { JSONSchema7 } from 'json-schema'; import { v4 } from 'uuid'; +import { Events } from '../../../../validate/validate.schema'; + const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { const properties = {}; propertyNames.forEach( @@ -30,32 +32,7 @@ export const sqsSchema: JSONSchema7 = { 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', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - ], + enum: Events, }, }, }, diff --git a/src/api/integrations/typebot/validate/typebot.schema.ts b/src/api/integrations/typebot/validate/typebot.schema.ts index fde9d973..6bd26106 100644 --- a/src/api/integrations/typebot/validate/typebot.schema.ts +++ b/src/api/integrations/typebot/validate/typebot.schema.ts @@ -28,12 +28,12 @@ export const typebotSchema: JSONSchema7 = { 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] }, + delayMessage: { type: 'integer' }, + unknownMessage: { type: 'string' }, + listeningFromMe: { 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'), + required: ['enabled', 'url', 'typebot', 'expire', 'delayMessage', 'unknownMessage', 'listeningFromMe'], + ...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delayMessage', 'unknownMessage', 'listeningFromMe'), }; export const typebotStatusSchema: JSONSchema7 = { diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index a70f9d49..5cec98f0 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -55,7 +55,7 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { + .post(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, schema: readMessageSchema, @@ -65,7 +65,7 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('archiveChat'), ...guards, async (req, res) => { + .post(this.routerPath('archiveChat'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, schema: archiveChatSchema, @@ -75,7 +75,7 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('markChatUnread'), ...guards, async (req, res) => { + .post(this.routerPath('markChatUnread'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, schema: markChatUnreadSchema, @@ -105,16 +105,46 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, - schema: profileSchema, - ClassRef: NumberDto, - execute: (instance, data) => chatController.fetchProfile(instance, data), + schema: null, + ClassRef: getBase64FromMediaMessageDto, + execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('updateMessage'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: updateMessageSchema, + ClassRef: UpdateMessageDto, + execute: (instance, data) => chatController.updateMessage(instance, data), }); return res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('sendPresence'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: presenceSchema, + ClassRef: SendPresenceDto, + execute: (instance, data) => chatController.sendPresence(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: blockUserSchema, + ClassRef: BlockUserDto, + execute: (instance, data) => chatController.blockUser(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) .post(this.routerPath('findContacts'), ...guards, async (req, res) => { const response = await this.dataValidate>({ request: req, @@ -125,16 +155,6 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - 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) => { const response = await this.dataValidate>({ request: req, @@ -165,37 +185,7 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .post(this.routerPath('sendPresence'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - 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) => { - const response = await this.dataValidate({ - 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) => { - const response = await this.dataValidate({ - 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) => { const response = await this.dataValidate({ request: req, @@ -206,6 +196,17 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: profileSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchProfile(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('updateProfileName'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, @@ -246,22 +247,22 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .put(this.routerPath('updateMessage'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, - schema: updateMessageSchema, - ClassRef: UpdateMessageDto, - execute: (instance, data) => chatController.updateMessage(instance, data), + schema: null, + ClassRef: InstanceDto, + execute: (instance) => chatController.fetchPrivacySettings(instance), }); return res.status(HttpStatus.OK).json(response); }) - .put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .post(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, - schema: blockUserSchema, - ClassRef: BlockUserDto, - execute: (instance, data) => chatController.blockUser(instance, data), + schema: privacySettingsSchema, + ClassRef: PrivacySettingDto, + execute: (instance, data) => chatController.updatePrivacySettings(instance, data), }); return res.status(HttpStatus.CREATED).json(response); diff --git a/src/api/routes/group.router.ts b/src/api/routes/group.router.ts index 845c4e42..973d19c1 100644 --- a/src/api/routes/group.router.ts +++ b/src/api/routes/group.router.ts @@ -46,7 +46,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => { + .post(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: updateGroupSubjectSchema, @@ -56,7 +56,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => { + .post(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: updateGroupPictureSchema, @@ -66,7 +66,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => { + .post(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: updateGroupDescriptionSchema, @@ -146,7 +146,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) - .put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => { + .post(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: groupJidSchema, @@ -156,7 +156,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('updateParticipant'), ...guards, async (req, res) => { + .post(this.routerPath('updateParticipant'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: updateParticipantsSchema, @@ -166,7 +166,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('updateSetting'), ...guards, async (req, res) => { + .post(this.routerPath('updateSetting'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: updateSettingsSchema, @@ -176,7 +176,7 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) - .put(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => { + .post(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, schema: toggleEphemeralSchema, diff --git a/src/api/routes/label.router.ts b/src/api/routes/label.router.ts index 961276bb..e9dbdb5f 100644 --- a/src/api/routes/label.router.ts +++ b/src/api/routes/label.router.ts @@ -20,7 +20,7 @@ export class LabelRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) - .put(this.routerPath('handleLabel'), ...guards, async (req, res) => { + .post(this.routerPath('handleLabel'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, schema: handleLabelSchema, diff --git a/src/api/routes/sendMessage.router.ts b/src/api/routes/sendMessage.router.ts index a5eebb46..a50bcc92 100644 --- a/src/api/routes/sendMessage.router.ts +++ b/src/api/routes/sendMessage.router.ts @@ -2,7 +2,6 @@ import { RequestHandler, Router } from 'express'; import { audioMessageSchema, - buttonMessageSchema, contactMessageSchema, listMessageSchema, locationMessageSchema, @@ -17,7 +16,6 @@ import { import { RouterBroker } from '../abstract/abstract.router'; import { SendAudioDto, - SendButtonDto, SendContactDto, SendListDto, SendLocationDto, @@ -36,6 +34,16 @@ export class MessageRouter extends RouterBroker { constructor(...guards: RequestHandler[]) { super(); this.router + .post(this.routerPath('sendTemplate'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: templateMessageSchema, + ClassRef: SendTemplateDto, + execute: (instance, data) => sendMessageController.sendTemplate(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) .post(this.routerPath('sendText'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, @@ -66,22 +74,22 @@ export class MessageRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .post(this.routerPath('sendTemplate'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .post(this.routerPath('sendStatus'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, - schema: templateMessageSchema, - ClassRef: SendTemplateDto, - execute: (instance, data) => sendMessageController.sendTemplate(instance, data), + schema: statusMessageSchema, + ClassRef: SendStatusDto, + execute: (instance, data) => sendMessageController.sendStatus(instance, data), }); return res.status(HttpStatus.CREATED).json(response); }) - .post(this.routerPath('sendButtons'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .post(this.routerPath('sendSticker'), ...guards, async (req, res) => { + const response = await this.dataValidate({ request: req, - schema: buttonMessageSchema, - ClassRef: SendButtonDto, - execute: (instance, data) => sendMessageController.sendButtons(instance, data), + schema: stickerMessageSchema, + ClassRef: SendStickerDto, + execute: (instance, data) => sendMessageController.sendSticker(instance, data), }); return res.status(HttpStatus.CREATED).json(response); @@ -96,16 +104,6 @@ export class MessageRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .post(this.routerPath('sendList'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - 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) => { const response = await this.dataValidate({ request: req, @@ -136,26 +134,26 @@ export class MessageRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .post(this.routerPath('sendStatus'), ...guards, async (req, res) => { - const response = await this.dataValidate({ + .post(this.routerPath('sendList'), ...guards, async (req, res) => { + const response = await this.dataValidate({ 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) => { - const response = await this.dataValidate({ - request: req, - schema: stickerMessageSchema, - ClassRef: SendStickerDto, - execute: (instance, data) => sendMessageController.sendSticker(instance, data), + schema: listMessageSchema, + ClassRef: SendListDto, + execute: (instance, data) => sendMessageController.sendList(instance, data), }); return res.status(HttpStatus.CREATED).json(response); }); + // .post(this.routerPath('sendButtons'), ...guards, async (req, res) => { + // const response = await this.dataValidate({ + // request: req, + // schema: buttonMessageSchema, + // ClassRef: SendButtonDto, + // execute: (instance, data) => sendMessageController.sendButtons(instance, data), + // }); + + // return res.status(HttpStatus.CREATED).json(response); + // }) } public readonly router = Router(); diff --git a/src/api/routes/settings.router.ts b/src/api/routes/settings.router.ts index 211fdc05..ba505cea 100644 --- a/src/api/routes/settings.router.ts +++ b/src/api/routes/settings.router.ts @@ -1,6 +1,6 @@ import { RequestHandler, Router } from 'express'; -import { instanceSchema, settingsSchema } from '../../validate/validate.schema'; +import { settingsSchema } from '../../validate/validate.schema'; import { RouterBroker } from '../abstract/abstract.router'; import { InstanceDto } from '../dto/instance.dto'; import { SettingsDto } from '../dto/settings.dto'; @@ -24,7 +24,7 @@ export class SettingsRouter extends RouterBroker { .get(this.routerPath('find'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, - schema: instanceSchema, + schema: null, ClassRef: InstanceDto, execute: (instance) => settingsController.findSettings(instance), }); diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index e138cd1f..531e9186 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -1921,12 +1921,12 @@ export class BaileysStartupService extends ChannelStartupService { const sender = isWA.jid; - if (data?.options?.delay && data?.options?.delay > 20000) { - let remainingDelay = data?.options.delay; + if (data?.delay && data?.delay > 20000) { + let remainingDelay = data?.delay; while (remainingDelay > 20000) { await this.client.presenceSubscribe(sender); - await this.client.sendPresenceUpdate((data?.options?.presence as WAPresence) ?? 'composing', sender); + await this.client.sendPresenceUpdate((data?.presence as WAPresence) ?? 'composing', sender); await delay(20000); @@ -1937,7 +1937,7 @@ export class BaileysStartupService extends ChannelStartupService { if (remainingDelay > 0) { await this.client.presenceSubscribe(sender); - await this.client.sendPresenceUpdate((data?.options?.presence as WAPresence) ?? 'composing', sender); + await this.client.sendPresenceUpdate((data?.presence as WAPresence) ?? 'composing', sender); await delay(remainingDelay); @@ -1946,9 +1946,9 @@ export class BaileysStartupService extends ChannelStartupService { } else { await this.client.presenceSubscribe(sender); - await this.client.sendPresenceUpdate((data?.options?.presence as WAPresence) ?? 'composing', sender); + await this.client.sendPresenceUpdate((data?.presence as WAPresence) ?? 'composing', sender); - await delay(data?.options?.delay); + await delay(data?.delay); await this.client.sendPresenceUpdate('paused', sender); } @@ -1973,9 +1973,18 @@ export class BaileysStartupService extends ChannelStartupService { return await this.sendMessageWithTyping( data.number, { - conversation: data.textMessage.text, + conversation: data.text, + }, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + linkPreview: data?.linkPreview, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, + }, }, - data?.options, isChatwoot, ); } @@ -1985,12 +1994,21 @@ export class BaileysStartupService extends ChannelStartupService { data.number, { poll: { - name: data.pollMessage.name, - selectableCount: data.pollMessage.selectableCount, - values: data.pollMessage.values, + name: data.name, + selectableCount: data.selectableCount, + values: data.values, + }, + }, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + linkPreview: data?.linkPreview, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, }, }, - data?.options, ); } @@ -2093,7 +2111,7 @@ export class BaileysStartupService extends ChannelStartupService { } public async statusMessage(data: SendStatusDto) { - const status = await this.formatStatusMessage(data.statusMessage); + const status = await this.formatStatusMessage(data); return await this.sendMessageWithTyping('status@broadcast', { status, @@ -2233,13 +2251,21 @@ export class BaileysStartupService extends ChannelStartupService { } public async mediaSticker(data: SendStickerDto) { - const convert = await this.convertToWebP(data.stickerMessage.image, data.number); + const convert = await this.convertToWebP(data.sticker, data.number); const result = await this.sendMessageWithTyping( data.number, { sticker: { url: convert }, }, - data?.options, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, + }, + }, ); fs.unlinkSync(convert); @@ -2248,9 +2274,22 @@ export class BaileysStartupService extends ChannelStartupService { } public async mediaMessage(data: SendMediaDto, isChatwoot = false) { - const generate = await this.prepareMediaMessage(data.mediaMessage); + const generate = await this.prepareMediaMessage(data); - return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot); + return await this.sendMessageWithTyping( + data.number, + { ...generate.message }, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, + }, + }, + isChatwoot, + ); } public async processAudio(audio: string, number: string) { @@ -2290,12 +2329,12 @@ export class BaileysStartupService extends ChannelStartupService { } public async audioWhatsapp(data: SendAudioDto, isChatwoot = false) { - if (!data.options?.encoding && data.options?.encoding !== false) { - data.options.encoding = true; + if (!data?.encoding && data?.encoding !== false) { + data.encoding = true; } - if (data.options?.encoding) { - const convert = await this.processAudio(data.audioMessage.audio, data.number); + if (data?.encoding) { + const convert = await this.processAudio(data.audio, data.number); if (typeof convert === 'string') { const audio = fs.readFileSync(convert).toString('base64'); const result = this.sendMessageWithTyping( @@ -2305,7 +2344,7 @@ export class BaileysStartupService extends ChannelStartupService { ptt: true, mimetype: 'audio/mp4', }, - { presence: 'recording', delay: data?.options?.delay }, + { presence: 'recording', delay: data?.delay }, isChatwoot, ); @@ -2320,13 +2359,11 @@ export class BaileysStartupService extends ChannelStartupService { return await this.sendMessageWithTyping( data.number, { - audio: isURL(data.audioMessage.audio) - ? { url: data.audioMessage.audio } - : Buffer.from(data.audioMessage.audio, 'base64'), + audio: isURL(data.audio) ? { url: data.audio } : Buffer.from(data.audio, 'base64'), ptt: true, mimetype: 'audio/ogg; codecs=opus', }, - { presence: 'recording', delay: data?.options?.delay }, + { presence: 'recording', delay: data?.delay }, isChatwoot, ); } @@ -2340,13 +2377,21 @@ export class BaileysStartupService extends ChannelStartupService { data.number, { locationMessage: { - degreesLatitude: data.locationMessage.latitude, - degreesLongitude: data.locationMessage.longitude, - name: data.locationMessage?.name, - address: data.locationMessage?.address, + degreesLatitude: data.latitude, + degreesLongitude: data.longitude, + name: data?.name, + address: data?.address, + }, + }, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, }, }, - data?.options, ); } @@ -2355,15 +2400,23 @@ export class BaileysStartupService extends ChannelStartupService { data.number, { listMessage: { - title: data.listMessage.title, - description: data.listMessage.description, - buttonText: data.listMessage?.buttonText, - footerText: data.listMessage?.footerText, - sections: data.listMessage.sections, + title: data.title, + description: data?.description, + buttonText: data?.buttonText, + footerText: data?.footerText, + sections: data.sections, listType: 2, }, }, - data?.options, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + mentions: { + everyOne: data?.everyOne, + mentioned: data?.mentioned, + }, + }, ); } @@ -2394,15 +2447,15 @@ export class BaileysStartupService extends ChannelStartupService { return result; }; - if (data.contactMessage.length === 1) { + if (data.contact.length === 1) { message.contactMessage = { - displayName: data.contactMessage[0].fullName, - vcard: vcard(data.contactMessage[0]), + displayName: data.contact[0].fullName, + vcard: vcard(data.contact[0]), }; } else { message.contactsArrayMessage = { - displayName: `${data.contactMessage.length} contacts`, - contacts: data.contactMessage.map((contact) => { + displayName: `${data.contact.length} contacts`, + contacts: data.contact.map((contact) => { return { displayName: contact.fullName, vcard: vcard(contact), @@ -2411,14 +2464,14 @@ export class BaileysStartupService extends ChannelStartupService { }; } - return await this.sendMessageWithTyping(data.number, { ...message }, data?.options); + return await this.sendMessageWithTyping(data.number, { ...message }, {}); } public async reactionMessage(data: SendReactionDto) { - return await this.sendMessageWithTyping(data.reactionMessage.key.remoteJid, { + return await this.sendMessageWithTyping(data.key.remoteJid, { reactionMessage: { - key: data.reactionMessage.key, - text: data.reactionMessage.reaction, + key: data.key, + text: data.reaction, }, }); } diff --git a/src/api/services/channels/whatsapp.business.service.ts b/src/api/services/channels/whatsapp.business.service.ts index 274a8a94..00c8c544 100644 --- a/src/api/services/channels/whatsapp.business.service.ts +++ b/src/api/services/channels/whatsapp.business.service.ts @@ -1047,9 +1047,9 @@ export class BusinessStartupService extends ChannelStartupService { data.number, { template: { - name: data.templateMessage.name, - language: data.templateMessage.language, - components: data.templateMessage.components, + name: data.name, + language: data.language, + components: data.components, }, }, data?.options, diff --git a/src/validate/chat.schema.ts b/src/validate/chat.schema.ts new file mode 100644 index 00000000..7ae927a3 --- /dev/null +++ b/src/validate/chat.schema.ts @@ -0,0 +1,300 @@ +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +const numberDefinition: JSONSchema7Definition = { + type: 'string', + description: 'Invalid format', +}; + +export const whatsappNumberSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + numbers: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + description: '"numbers" must be an array of numeric strings', + }, + }, + }, +}; + +export const readMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + read_messages: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + properties: { + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + remoteJid: { type: 'string' }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + }, + }, + required: ['read_messages'], +}; + +export const archiveChatSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chat: { type: 'string' }, + lastMessage: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + messageTimestamp: { type: 'integer', minLength: 1 }, + }, + required: ['key'], + ...isNotEmpty('messageTimestamp'), + }, + archive: { type: 'boolean', enum: [true, false] }, + }, + required: ['archive'], +}; + +export const markChatUnreadSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chat: { type: 'string' }, + lastMessage: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + messageTimestamp: { type: 'integer', minLength: 1 }, + }, + required: ['key'], + ...isNotEmpty('messageTimestamp'), + }, + }, + required: ['lastMessage'], +}; + +export const deleteMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + remoteJid: { type: 'string' }, + participant: { type: 'string' }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid', 'participant'), +}; + +export const profilePictureSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { type: 'string' }, + picture: { type: 'string' }, + }, +}; + +export const updateMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { type: 'string' }, + text: { type: 'string' }, + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + }, + ...isNotEmpty('number', 'text', 'key'), +}; + +export const presenceSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + delay: { type: 'number' }, + presence: { + type: 'string', + enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], + }, + }, + required: ['number', 'presence', 'delay'], +}; + +export const blockUserSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { type: 'string' }, + status: { type: 'string', enum: ['block', 'unblock'] }, + }, + required: ['number', 'status'], + ...isNotEmpty('number', 'status'), +}; + +export const contactValidateSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string', minLength: 1 }, + pushName: { type: 'string', minLength: 1 }, + id: { type: 'string', minLength: 1 }, + }, + ...isNotEmpty('_id', 'id', 'pushName'), + }, + }, +}; + +export const messageValidateSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string', minLength: 1 }, + key: { + type: 'object', + if: { + propertyNames: { + enum: ['fromMe', 'remoteJid', 'id'], + }, + }, + then: { + properties: { + remoteJid: { + type: 'string', + minLength: 1, + description: 'The property cannot be empty', + }, + id: { + type: 'string', + minLength: 1, + description: 'The property cannot be empty', + }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + }, + }, + message: { type: 'object' }, + }, + ...isNotEmpty('_id'), + }, + limit: { type: 'integer' }, + }, +}; + +export const messageUpSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string' }, + remoteJid: { type: 'string' }, + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + participant: { type: 'string' }, + status: { + type: 'string', + enum: ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK', 'READ', 'PLAYED'], + }, + }, + ...isNotEmpty('_id', 'remoteJid', 'id', 'status'), + }, + limit: { type: 'integer' }, + }, +}; + +export const privacySettingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + readreceipts: { type: 'string', enum: ['all', 'none'] }, + profile: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + status: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + online: { type: 'string', enum: ['all', 'match_last_seen'] }, + last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] }, + groupadd: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + }, + required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'], + ...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'), +}; + +export const profileNameSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + name: { type: 'string' }, + }, + ...isNotEmpty('name'), +}; + +export const profileStatusSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + status: { type: 'string' }, + }, + ...isNotEmpty('status'), +}; + +export const profileSchema: JSONSchema7 = { + type: 'object', + properties: { + wuid: { type: 'string' }, + name: { type: 'string' }, + picture: { type: 'string' }, + status: { type: 'string' }, + isBusiness: { type: 'boolean' }, + }, +}; diff --git a/src/validate/group.schema.ts b/src/validate/group.schema.ts new file mode 100644 index 00000000..2e8f35f0 --- /dev/null +++ b/src/validate/group.schema.ts @@ -0,0 +1,176 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +export const createGroupSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + subject: { type: 'string' }, + description: { type: 'string' }, + profilePicture: { type: 'string' }, + promoteParticipants: { type: 'boolean', enum: [true, false] }, + participants: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"participants" must be an array of numeric strings', + }, + }, + }, + required: ['subject', 'participants'], + ...isNotEmpty('subject', 'description', 'profilePicture'), +}; + +export const groupJidSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string', pattern: '^[\\d-]+@g.us$' }, + }, + required: ['groupJid'], + ...isNotEmpty('groupJid'), +}; + +export const getParticipantsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + getParticipants: { type: 'string', enum: ['true', 'false'] }, + }, + required: ['getParticipants'], + ...isNotEmpty('getParticipants'), +}; + +export const groupSendInviteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + description: { type: 'string' }, + numbers: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"numbers" must be an array of numeric strings', + }, + }, + }, + required: ['groupJid', 'description', 'numbers'], + ...isNotEmpty('groupJid', 'description', 'numbers'), +}; + +export const groupInviteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, + }, + required: ['inviteCode'], + ...isNotEmpty('inviteCode'), +}; + +export const AcceptGroupInviteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, + }, + required: ['inviteCode'], + ...isNotEmpty('inviteCode'), +}; + +export const updateParticipantsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + action: { + type: 'string', + enum: ['add', 'remove', 'promote', 'demote'], + }, + participants: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"participants" must be an array of numeric strings', + }, + }, + }, + required: ['groupJid', 'action', 'participants'], + ...isNotEmpty('groupJid', 'action'), +}; + +export const updateSettingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + action: { + type: 'string', + enum: ['announcement', 'not_announcement', 'locked', 'unlocked'], + }, + }, + required: ['groupJid', 'action'], + ...isNotEmpty('groupJid', 'action'), +}; + +export const toggleEphemeralSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + expiration: { + type: 'number', + enum: [0, 86400, 604800, 7776000], + }, + }, + required: ['groupJid', 'expiration'], + ...isNotEmpty('groupJid', 'expiration'), +}; + +export const updateGroupPictureSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + image: { type: 'string' }, + }, + required: ['groupJid', 'image'], + ...isNotEmpty('groupJid', 'image'), +}; + +export const updateGroupSubjectSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + subject: { type: 'string' }, + }, + required: ['groupJid', 'subject'], + ...isNotEmpty('groupJid', 'subject'), +}; + +export const updateGroupDescriptionSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + description: { type: 'string' }, + }, + required: ['groupJid', 'description'], + ...isNotEmpty('groupJid', 'description'), +}; diff --git a/src/validate/instance.schema.ts b/src/validate/instance.schema.ts new file mode 100644 index 00000000..7422e270 --- /dev/null +++ b/src/validate/instance.schema.ts @@ -0,0 +1,100 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { Integration } from '../api/types/wa.types'; +import { Events, isNotEmpty } from './validate.schema'; + +export const instanceSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + // Instance + instanceName: { type: 'string' }, + token: { type: 'string' }, + number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, + qrcode: { type: 'boolean' }, + Integration: { + type: 'string', + enum: Object.values(Integration), + }, + // Settings + rejectCall: { type: 'boolean' }, + msgCall: { type: 'string' }, + groupsIgnore: { type: 'boolean' }, + alwaysOnline: { type: 'boolean' }, + readMessages: { type: 'boolean' }, + readStatus: { type: 'boolean' }, + syncFullHistory: { type: 'boolean' }, + // Proxy + proxyHost: { type: 'string' }, + proxyPort: { type: 'string' }, + proxyProtocol: { type: 'string' }, + proxyUsername: { type: 'string' }, + proxyPassword: { type: 'string' }, + // Webhook + webhookUrl: { type: 'string' }, + webhookByEvents: { type: 'boolean' }, + webhookBase64: { type: 'boolean' }, + webhookEvents: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: Events, + }, + }, + // RabbitMQ + rabbitmqEnabled: { type: 'boolean' }, + rabbitmqEvents: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: Events, + }, + }, + // SQS + sqsEnabled: { type: 'boolean' }, + sqsEvents: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: Events, + }, + }, + // Chatwoot + chatwootAccountId: { type: 'string' }, + chatwootToken: { type: 'string' }, + chatwootUrl: { type: 'string' }, + chatwootSignMsg: { type: 'boolean' }, + chatwootReopenConversation: { type: 'boolean' }, + chatwootConversationPending: { type: 'boolean' }, + chatwootImportContacts: { type: 'boolean' }, + chatwootNameInbox: { type: 'string' }, + chatwootMergeBrazilContacts: { type: 'boolean' }, + chatwootImportMessages: { type: 'boolean' }, + chatwootDaysLimitImportMessages: { type: 'number' }, + // Typebot + typebotUrl: { type: 'string' }, + typebot: { type: 'boolean' }, + typebotExpire: { type: 'number' }, + typebotKeywordFinish: { type: 'string' }, + typebotDelayMessage: { type: 'number' }, + typebotUnknownMessage: { type: 'string' }, + typebotListeningFromMe: { type: 'boolean' }, + }, + ...isNotEmpty('instanceName'), +}; + +export const presenceOnlySchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + presence: { + type: 'string', + enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], + }, + }, + required: ['presence'], +}; diff --git a/src/validate/label.schema.ts b/src/validate/label.schema.ts new file mode 100644 index 00000000..c7bfae37 --- /dev/null +++ b/src/validate/label.schema.ts @@ -0,0 +1,21 @@ +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +const numberDefinition: JSONSchema7Definition = { + type: 'string', + description: 'Invalid format', +}; + +export const handleLabelSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + labelId: { type: 'string' }, + action: { type: 'string', enum: ['add', 'remove'] }, + }, + required: ['number', 'labelId', 'action'], + ...isNotEmpty('number', 'labelId', 'action'), +}; diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts new file mode 100644 index 00000000..0e2de95c --- /dev/null +++ b/src/validate/message.schema.ts @@ -0,0 +1,343 @@ +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +const numberDefinition: JSONSchema7Definition = { + type: 'string', + description: 'Invalid format', +}; + +export const templateMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + name: { type: 'string' }, + language: { type: 'string' }, + components: { type: 'array' }, + }, + required: ['name', 'language'], +}; + +const quotedOptionsSchema: JSONSchema7 = { + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id'], + ...isNotEmpty('id'), + }, + message: { type: 'object' }, + }, +}; + +export const textMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + text: { type: 'string' }, + linkPreview: { type: 'boolean' }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'text'], +}; + +export const mediaMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + mediatype: { type: 'string', enum: ['image', 'document', 'video', 'audio'] }, + mimetype: { type: 'string' }, + media: { type: 'string' }, + fileName: { type: 'string' }, + caption: { type: 'string' }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'mediatype', 'media'], +}; + +export const audioMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + audio: { type: 'string' }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'audio'], +}; + +export const statusMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + type: { type: 'string', enum: ['text', 'image', 'audio', 'video'] }, + content: { type: 'string' }, + caption: { type: 'string' }, + backgroundColor: { type: 'string' }, + font: { type: 'integer', minimum: 0, maximum: 5 }, + statusJidList: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"statusJidList" must be an array of numeric strings', + }, + }, + allContacts: { type: 'boolean', enum: [true, false] }, + }, + required: ['type', 'content'], +}; + +export const stickerMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + sticker: { type: 'string' }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'sticker'], +}; + +export const locationMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + latitude: { type: 'number' }, + longitude: { type: 'number' }, + name: { type: 'string' }, + address: { type: 'string' }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'latitude', 'longitude', 'name', 'address'], +}; + +export const contactMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + contact: { + type: 'array', + items: { + type: 'object', + properties: { + fullName: { type: 'string' }, + wuid: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"wuid" must be a numeric string', + }, + phoneNumber: { type: 'string', minLength: 10 }, + organization: { type: 'string' }, + email: { type: 'string' }, + url: { type: 'string' }, + }, + required: ['fullName', 'phoneNumber'], + ...isNotEmpty('fullName'), + }, + minItems: 1, + uniqueItems: true, + }, + }, + required: ['number', 'contact'], +}; + +export const reactionMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'remoteJid', 'fromMe'], + ...isNotEmpty('id', 'remoteJid'), + }, + reaction: { type: 'string' }, + }, + required: ['key', 'reaction'], +}; + +export const pollMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + name: { type: 'string' }, + selectableCount: { type: 'integer', minimum: 0, maximum: 10 }, + values: { + type: 'array', + minItems: 2, + maxItems: 10, + uniqueItems: true, + items: { + type: 'string', + }, + }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'name', 'selectableCount', 'values'], +}; + +export const listMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + title: { type: 'string' }, + description: { type: 'string' }, + footerText: { type: 'string' }, + buttonText: { type: 'string' }, + sections: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'object', + properties: { + title: { type: 'string' }, + rows: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' }, + rowId: { type: 'string' }, + }, + required: ['title', 'rowId'], + ...isNotEmpty('title', 'description', 'rowId'), + }, + }, + }, + required: ['title', 'rows'], + ...isNotEmpty('title'), + }, + }, + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + quoted: { ...quotedOptionsSchema }, + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, + required: ['number', 'title', 'footerText', 'buttonText', 'sections'], +}; diff --git a/src/validate/proxy.schema.ts b/src/validate/proxy.schema.ts new file mode 100644 index 00000000..9985c070 --- /dev/null +++ b/src/validate/proxy.schema.ts @@ -0,0 +1,19 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +export const proxySchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + host: { type: 'string' }, + port: { type: 'string' }, + protocol: { type: 'string' }, + username: { type: 'string' }, + password: { type: 'string' }, + }, + required: ['enabled', 'host', 'port', 'protocol'], + ...isNotEmpty('enabled', 'host', 'port', 'protocol'), +}; diff --git a/src/validate/settings.schema.ts b/src/validate/settings.schema.ts new file mode 100644 index 00000000..98b1aa93 --- /dev/null +++ b/src/validate/settings.schema.ts @@ -0,0 +1,20 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { isNotEmpty } from './validate.schema'; + +export const settingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + rejectCall: { type: 'boolean' }, + msgCall: { type: 'string' }, + groupsIgnore: { type: 'boolean' }, + alwaysOnline: { type: 'boolean' }, + readMessages: { type: 'boolean' }, + readStatus: { type: 'boolean' }, + syncFullHistory: { type: 'boolean' }, + }, + required: ['rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'], + ...isNotEmpty('rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'), +}; diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index b7021ba0..4855d8bb 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,7 +1,4 @@ -import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; -import { v4 } from 'uuid'; - -import { Integration } from '../api/types/wa.types'; +import { JSONSchema7 } from 'json-schema'; // Integrations Schema export * from '../api/integrations/chatwoot/validate/chatwoot.schema'; @@ -9,7 +6,18 @@ export * from '../api/integrations/rabbitmq/validate/rabbitmq.schema'; export * from '../api/integrations/sqs/validate/sqs.schema'; export * from '../api/integrations/typebot/validate/typebot.schema'; -const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { +// Instance Schema +export * from './chat.schema'; +export * from './group.schema'; +export * from './instance.schema'; +export * from './label.schema'; +export * from './message.schema'; +export * from './proxy.schema'; +export * from './settings.schema'; +export * from './webhook.schema'; +export * from './websocket.schema'; + +export const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { const properties = {}; propertyNames.forEach( (property) => @@ -28,7 +36,7 @@ const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { }; }; -const Events = [ +export const Events = [ 'APPLICATION_STARTUP', 'QRCODE_UPDATED', 'MESSAGES_SET', @@ -54,1074 +62,3 @@ const Events = [ 'TYPEBOT_START', 'TYPEBOT_CHANGE_STATUS', ]; - -// Instance Schema -export const instanceSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - // Instance - instanceName: { type: 'string' }, - token: { type: 'string' }, - number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, - qrcode: { type: 'boolean' }, - Integration: { - type: 'string', - enum: Object.values(Integration), - }, - // Settings - rejectCall: { type: 'boolean' }, - msgCall: { type: 'string' }, - groupsIgnore: { type: 'boolean' }, - alwaysOnline: { type: 'boolean' }, - readMessages: { type: 'boolean' }, - readStatus: { type: 'boolean' }, - syncFullHistory: { type: 'boolean' }, - // Proxy - proxyHost: { type: 'string' }, - proxyPort: { type: 'string' }, - proxyProtocol: { type: 'string' }, - proxyUsername: { type: 'string' }, - proxyPassword: { type: 'string' }, - // Webhook - webhookUrl: { type: 'string' }, - webhookByEvents: { type: 'boolean' }, - webhookBase64: { type: 'boolean' }, - webhookEvents: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: Events, - }, - }, - // RabbitMQ - rabbitmqEnabled: { type: 'boolean' }, - rabbitmqEvents: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: Events, - }, - }, - // SQS - sqsEnabled: { type: 'boolean' }, - sqsEvents: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: Events, - }, - }, - // Chatwoot - chatwootAccountId: { type: 'string' }, - chatwootToken: { type: 'string' }, - chatwootUrl: { type: 'string' }, - chatwootSignMsg: { type: 'boolean' }, - chatwootReopenConversation: { type: 'boolean' }, - chatwootConversationPending: { type: 'boolean' }, - chatwootImportContacts: { type: 'boolean' }, - chatwootNameInbox: { type: 'string' }, - chatwootMergeBrazilContacts: { type: 'boolean' }, - chatwootImportMessages: { type: 'boolean' }, - chatwootDaysLimitImportMessages: { type: 'number' }, - // Typebot - typebotUrl: { type: 'string' }, - typebot: { type: 'boolean' }, - typebotExpire: { type: 'number' }, - typebotKeywordFinish: { type: 'string' }, - typebotDelayMessage: { type: 'number' }, - typebotUnknownMessage: { type: 'string' }, - typebotListeningFromMe: { type: 'boolean' }, - }, - ...isNotEmpty('instanceName'), -}; - -export const presenceOnlySchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - presence: { - type: 'string', - enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], - }, - }, - required: ['presence'], -}; - -const quotedOptionsSchema: JSONSchema7 = { - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id'], - ...isNotEmpty('id'), - }, - message: { type: 'object' }, - }, -}; - -const mentionsOptionsSchema: JSONSchema7 = { - properties: { - everyOne: { type: 'boolean', enum: [true, false] }, - mentioned: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - pattern: '^\\d+', - description: '"mentioned" must be an array of numeric strings', - }, - }, - }, -}; - -// Send Message Schema -const optionsSchema: JSONSchema7 = { - properties: { - delay: { - type: 'integer', - description: 'Enter a value in milliseconds', - }, - presence: { - type: 'string', - enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], - }, - quoted: { ...quotedOptionsSchema }, - mentions: { ...mentionsOptionsSchema }, - }, -}; - -const numberDefinition: JSONSchema7Definition = { - type: 'string', - description: 'Invalid format', -}; - -export const textMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - textMessage: { - type: 'object', - properties: { - text: { type: 'string' }, - }, - required: ['text'], - ...isNotEmpty('text'), - }, - }, - required: ['textMessage', 'number'], -}; - -export const presenceSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema, required: ['presence', 'delay'] }, - }, - required: ['options', 'number'], -}; - -export const pollMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - pollMessage: { - type: 'object', - properties: { - name: { type: 'string' }, - selectableCount: { type: 'integer', minimum: 0, maximum: 10 }, - values: { - type: 'array', - minItems: 2, - maxItems: 10, - uniqueItems: true, - items: { - type: 'string', - }, - }, - }, - required: ['name', 'selectableCount', 'values'], - ...isNotEmpty('name', 'selectableCount', 'values'), - }, - }, - required: ['pollMessage', 'number'], -}; - -export const statusMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - statusMessage: { - type: 'object', - properties: { - type: { type: 'string', enum: ['text', 'image', 'audio', 'video'] }, - content: { type: 'string' }, - caption: { type: 'string' }, - backgroundColor: { type: 'string' }, - font: { type: 'integer', minimum: 0, maximum: 5 }, - statusJidList: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - pattern: '^\\d+', - description: '"statusJidList" must be an array of numeric strings', - }, - }, - allContacts: { type: 'boolean', enum: [true, false] }, - }, - required: ['type', 'content'], - ...isNotEmpty('type', 'content'), - }, - }, - required: ['statusMessage'], -}; - -export const mediaMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - mediaMessage: { - type: 'object', - properties: { - mediatype: { type: 'string', enum: ['image', 'document', 'video', 'audio'] }, - media: { type: 'string' }, - fileName: { type: 'string' }, - caption: { type: 'string' }, - }, - required: ['mediatype', 'media'], - ...isNotEmpty('fileName', 'caption', 'media'), - }, - }, - required: ['mediaMessage', 'number'], -}; - -export const stickerMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - stickerMessage: { - type: 'object', - properties: { - image: { type: 'string' }, - }, - required: ['image'], - ...isNotEmpty('image'), - }, - }, - required: ['stickerMessage', 'number'], -}; - -export const audioMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - audioMessage: { - type: 'object', - properties: { - audio: { type: 'string' }, - }, - required: ['audio'], - ...isNotEmpty('audio'), - }, - }, - required: ['audioMessage', 'number'], -}; - -export const templateMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - templateMessage: { - type: 'object', - properties: { - name: { type: 'string' }, - language: { type: 'string' }, - components: { type: 'array' }, - }, - required: ['name', 'language'], - ...isNotEmpty('name', 'language'), - }, - }, - required: ['templateMessage', 'number'], -}; - -export const buttonMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - buttonMessage: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - footerText: { type: 'string' }, - buttons: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - buttonText: { type: 'string' }, - buttonId: { type: 'string' }, - }, - required: ['buttonText', 'buttonId'], - ...isNotEmpty('buttonText', 'buttonId'), - }, - }, - mediaMessage: { - type: 'object', - properties: { - media: { type: 'string' }, - fileName: { type: 'string' }, - mediatype: { type: 'string', enum: ['image', 'document', 'video'] }, - }, - required: ['media', 'mediatype'], - ...isNotEmpty('media', 'fileName'), - }, - }, - required: ['title', 'buttons'], - ...isNotEmpty('title', 'description'), - }, - }, - required: ['number', 'buttonMessage'], -}; - -export const locationMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - locationMessage: { - type: 'object', - properties: { - latitude: { type: 'number' }, - longitude: { type: 'number' }, - name: { type: 'string' }, - address: { type: 'string' }, - }, - required: ['latitude', 'longitude'], - ...isNotEmpty('name', 'addresss'), - }, - }, - required: ['number', 'locationMessage'], -}; - -export const listMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - listMessage: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - footerText: { type: 'string' }, - buttonText: { type: 'string' }, - sections: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - title: { type: 'string' }, - rows: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - rowId: { type: 'string' }, - }, - required: ['title', 'rowId'], - ...isNotEmpty('title', 'description', 'rowId'), - }, - }, - }, - required: ['title', 'rows'], - ...isNotEmpty('title'), - }, - }, - }, - required: ['title', 'description', 'buttonText', 'sections'], - ...isNotEmpty('title', 'description', 'buttonText', 'footerText'), - }, - }, - required: ['number', 'listMessage'], -}; - -export const contactMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - contactMessage: { - type: 'array', - items: { - type: 'object', - properties: { - fullName: { type: 'string' }, - wuid: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"wuid" must be a numeric string', - }, - phoneNumber: { type: 'string', minLength: 10 }, - organization: { type: 'string' }, - email: { type: 'string' }, - url: { type: 'string' }, - }, - required: ['fullName', 'phoneNumber'], - ...isNotEmpty('fullName'), - }, - minItems: 1, - uniqueItems: true, - }, - }, - required: ['number', 'contactMessage'], -}; - -export const reactionMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - reactionMessage: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'remoteJid', 'fromMe'], - ...isNotEmpty('id', 'remoteJid'), - }, - reaction: { type: 'string' }, - }, - required: ['key', 'reaction'], - ...isNotEmpty('reaction'), - }, - }, - required: ['reactionMessage'], -}; - -// Chat Schema -export const whatsappNumberSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - numbers: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - description: '"numbers" must be an array of numeric strings', - }, - }, - }, -}; - -export const readMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - read_messages: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - properties: { - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - remoteJid: { type: 'string' }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - }, - }, - required: ['read_messages'], -}; - -export const privacySettingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - privacySettings: { - type: 'object', - properties: { - readreceipts: { type: 'string', enum: ['all', 'none'] }, - profile: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - status: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - online: { type: 'string', enum: ['all', 'match_last_seen'] }, - last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] }, - groupadd: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - }, - required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'], - ...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'), - }, - }, - required: ['privacySettings'], -}; - -export const blockUserSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { type: 'string' }, - status: { type: 'string', enum: ['block', 'unblock'] }, - }, - required: ['number', 'status'], - ...isNotEmpty('number', 'status'), -}; - -export const archiveChatSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - chat: { type: 'string' }, - lastMessage: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - messageTimestamp: { type: 'integer', minLength: 1 }, - }, - required: ['key'], - ...isNotEmpty('messageTimestamp'), - }, - archive: { type: 'boolean', enum: [true, false] }, - }, - required: ['archive'], -}; - -export const markChatUnreadSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - chat: { type: 'string' }, - lastMessage: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - messageTimestamp: { type: 'integer', minLength: 1 }, - }, - required: ['key'], - ...isNotEmpty('messageTimestamp'), - }, - }, - required: ['lastMessage'], -}; - -export const deleteMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - remoteJid: { type: 'string' }, - participant: { type: 'string' }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid', 'participant'), -}; - -export const contactValidateSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string', minLength: 1 }, - pushName: { type: 'string', minLength: 1 }, - id: { type: 'string', minLength: 1 }, - }, - ...isNotEmpty('_id', 'id', 'pushName'), - }, - }, -}; - -export const profileNameSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - name: { type: 'string' }, - }, - ...isNotEmpty('name'), -}; - -export const profileStatusSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - status: { type: 'string' }, - }, - ...isNotEmpty('status'), -}; - -export const updateMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { type: 'string' }, - text: { type: 'string' }, - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - }, - ...isNotEmpty('number', 'text', 'key'), -}; - -export const profilePictureSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { type: 'string' }, - picture: { type: 'string' }, - }, -}; - -export const profileSchema: JSONSchema7 = { - type: 'object', - properties: { - wuid: { type: 'string' }, - name: { type: 'string' }, - picture: { type: 'string' }, - status: { type: 'string' }, - isBusiness: { type: 'boolean' }, - }, -}; - -export const messageValidateSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string', minLength: 1 }, - key: { - type: 'object', - if: { - propertyNames: { - enum: ['fromMe', 'remoteJid', 'id'], - }, - }, - then: { - properties: { - remoteJid: { - type: 'string', - minLength: 1, - description: 'The property cannot be empty', - }, - id: { - type: 'string', - minLength: 1, - description: 'The property cannot be empty', - }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - }, - }, - message: { type: 'object' }, - }, - ...isNotEmpty('_id'), - }, - limit: { type: 'integer' }, - }, -}; - -export const messageUpSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string' }, - remoteJid: { type: 'string' }, - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - participant: { type: 'string' }, - status: { - type: 'string', - enum: ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK', 'READ', 'PLAYED'], - }, - }, - ...isNotEmpty('_id', 'remoteJid', 'id', 'status'), - }, - limit: { type: 'integer' }, - }, -}; - -// Group Schema -export const createGroupSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - subject: { type: 'string' }, - description: { type: 'string' }, - profilePicture: { type: 'string' }, - promoteParticipants: { type: 'boolean', enum: [true, false] }, - participants: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"participants" must be an array of numeric strings', - }, - }, - }, - required: ['subject', 'participants'], - ...isNotEmpty('subject', 'description', 'profilePicture'), -}; - -export const groupJidSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string', pattern: '^[\\d-]+@g.us$' }, - }, - required: ['groupJid'], - ...isNotEmpty('groupJid'), -}; - -export const getParticipantsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - getParticipants: { type: 'string', enum: ['true', 'false'] }, - }, - required: ['getParticipants'], - ...isNotEmpty('getParticipants'), -}; - -export const groupSendInviteSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - description: { type: 'string' }, - numbers: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"numbers" must be an array of numeric strings', - }, - }, - }, - required: ['groupJid', 'description', 'numbers'], - ...isNotEmpty('groupJid', 'description', 'numbers'), -}; - -export const groupInviteSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, - }, - required: ['inviteCode'], - ...isNotEmpty('inviteCode'), -}; - -export const AcceptGroupInviteSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, - }, - required: ['inviteCode'], - ...isNotEmpty('inviteCode'), -}; - -export const updateParticipantsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - action: { - type: 'string', - enum: ['add', 'remove', 'promote', 'demote'], - }, - participants: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"participants" must be an array of numeric strings', - }, - }, - }, - required: ['groupJid', 'action', 'participants'], - ...isNotEmpty('groupJid', 'action'), -}; - -export const updateSettingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - action: { - type: 'string', - enum: ['announcement', 'not_announcement', 'locked', 'unlocked'], - }, - }, - required: ['groupJid', 'action'], - ...isNotEmpty('groupJid', 'action'), -}; - -export const toggleEphemeralSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - expiration: { - type: 'number', - enum: [0, 86400, 604800, 7776000], - }, - }, - required: ['groupJid', 'expiration'], - ...isNotEmpty('groupJid', 'expiration'), -}; - -export const updateGroupPictureSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - image: { type: 'string' }, - }, - required: ['groupJid', 'image'], - ...isNotEmpty('groupJid', 'image'), -}; - -export const updateGroupSubjectSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - subject: { type: 'string' }, - }, - required: ['groupJid', 'subject'], - ...isNotEmpty('groupJid', 'subject'), -}; - -export const updateGroupDescriptionSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - description: { type: 'string' }, - }, - required: ['groupJid', 'description'], - ...isNotEmpty('groupJid', 'description'), -}; - -export const webhookSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - url: { type: 'string' }, - 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', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - ], - }, - }, - }, - required: ['url'], - ...isNotEmpty('url'), -}; - -export const settingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - reject_call: { type: 'boolean', enum: [true, false] }, - msg_call: { type: 'string' }, - groups_ignore: { type: 'boolean', enum: [true, false] }, - always_online: { type: 'boolean', enum: [true, false] }, - read_messages: { type: 'boolean', enum: [true, false] }, - read_status: { type: 'boolean', enum: [true, false] }, - sync_full_history: { type: 'boolean', enum: [true, false] }, - }, - required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'], - ...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'), -}; - -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', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - ], - }, - }, - }, - required: ['enabled'], - ...isNotEmpty('enabled'), -}; - -export const proxySchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - host: { type: 'string' }, - port: { type: 'string' }, - protocol: { type: 'string' }, - username: { type: 'string' }, - password: { type: 'string' }, - }, - required: ['enabled', 'host', 'port', 'protocol'], - ...isNotEmpty('enabled', 'host', 'port', 'protocol'), -}; - -export const handleLabelSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - labelId: { type: 'string' }, - action: { type: 'string', enum: ['add', 'remove'] }, - }, - required: ['number', 'labelId', 'action'], -}; diff --git a/src/validate/webhook.schema.ts b/src/validate/webhook.schema.ts new file mode 100644 index 00000000..0ecd1df8 --- /dev/null +++ b/src/validate/webhook.schema.ts @@ -0,0 +1,25 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { Events, isNotEmpty } from './validate.schema'; + +export const webhookSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean' }, + url: { type: 'string' }, + webhookByEvents: { type: 'boolean' }, + webhookBase64: { type: 'boolean' }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: Events, + }, + }, + }, + required: ['enabled', 'url'], + ...isNotEmpty('enabled', 'url'), +}; diff --git a/src/validate/websocket.schema.ts b/src/validate/websocket.schema.ts new file mode 100644 index 00000000..3cdb2869 --- /dev/null +++ b/src/validate/websocket.schema.ts @@ -0,0 +1,22 @@ +import { JSONSchema7 } from 'json-schema'; +import { v4 } from 'uuid'; + +import { Events, isNotEmpty } from './validate.schema'; + +export const websocketSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: Events, + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +};