From 79e83c47ca2437066f2e4792936c0da079fb1bc1 Mon Sep 17 00:00:00 2001 From: Clairton Rodrigo Heinzen Date: Mon, 30 Sep 2024 09:08:14 -0300 Subject: [PATCH] feat: delay first message in webhooker service --- README.md | 1 + __tests__/services/blacklist.ts | 40 +------------------------------ __tests__/services/transformer.ts | 39 ++++++++++++++++++++++++++++++ package.json | 2 +- src/defaults.ts | 1 + src/jobs/webhooker.ts | 27 ++++++++++++++++++++- src/services/blacklist.ts | 28 +--------------------- src/services/transformer.ts | 27 +++++++++++++++++++++ 8 files changed, 97 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 1e8c4b37..110ab0ee 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,7 @@ SEND_REACTION_AS_REPLY=true to send reactions as replay, default false SEND_PROFILE_PICTURE=true to send profile picture users and groups, default is true UNOAPI_RETRY_REQUEST_DELAY_MS=retry delay in miliseconds when decrypt failed, default is 1_000(a second) UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS=to service had time do create contact and conversation before send next messages, default 0 +UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS=to service had time do create contact and conversation in first message after unoapi up, before send next messages, default 0 UNOAPI_DELAY_BETWEEN_MESSAGES_MS=to not duplicate timestamp message. defaul 0 PROXY_URL=the socks proxy url, default not use CLEAN_CONFIG_ON_DISCONNECT=true to clean all saved redis configurations on disconnect number, default is false diff --git a/__tests__/services/blacklist.ts b/__tests__/services/blacklist.ts index 625367a5..a866218b 100644 --- a/__tests__/services/blacklist.ts +++ b/__tests__/services/blacklist.ts @@ -1,6 +1,6 @@ jest.mock('../../src/services/redis') -import { extractDestinyPhone, isInBlacklistInMemory, addToBlacklistInMemory, cleanBlackList, isInBlacklistInRedis } from '../../src/services/blacklist' +import { isInBlacklistInMemory, addToBlacklistInMemory, cleanBlackList, isInBlacklistInRedis } from '../../src/services/blacklist' import { redisGet, redisKeys, blacklist } from '../../src/services/redis' const redisGetMock = redisGet as jest.MockedFunction @@ -8,44 +8,6 @@ const redisKeysMock = redisKeys as jest.MockedFunction const blacklistMock = blacklist as jest.MockedFunction describe('service blacklist webhook', () => { - test('return y extractDestinyPhone from webhook payload message', async () => { - const payload = { - entry: [ - { - changes: [ - { - value: { - contacts: [{ wa_id: 'y' }], - }, - }, - ], - }, - ], - } - expect(extractDestinyPhone(payload)).toBe('y') - }) - - test('return x extractDestinyPhone from webhook payload status', async () => { - const payload = { - entry: [ - { - changes: [ - { - value: { - statuses: [{ recipient_id: 'x' }] - } - } - ] - } - ] - } - expect(extractDestinyPhone(payload)).toBe('x') - }) - - test('return empty extractDestinyPhone from api payload', async () => { - expect(extractDestinyPhone({ to: 'y' })).toBe('y') - }) - test('return false isInBlacklistInMemory', async () => { await cleanBlackList() expect(await isInBlacklistInMemory('x', 'y', { to: 'w' })).toBe('') diff --git a/__tests__/services/transformer.ts b/__tests__/services/transformer.ts index a75f2be5..ad8d166f 100644 --- a/__tests__/services/transformer.ts +++ b/__tests__/services/transformer.ts @@ -12,6 +12,7 @@ import { DecryptError, getNormalizedMessage, isSaveMedia, + extractDestinyPhone, } from '../../src/services/transformer' const key = { remoteJid: 'XXXX@s.whatsapp.net', id: 'abc' } @@ -33,6 +34,44 @@ const inputDocumentMessage: WAMessage = { } describe('service transformer', () => { + test('return y extractDestinyPhone from webhook payload message', async () => { + const payload = { + entry: [ + { + changes: [ + { + value: { + contacts: [{ wa_id: 'y' }], + }, + }, + ], + }, + ], + } + expect(extractDestinyPhone(payload)).toBe('y') + }) + + test('return x extractDestinyPhone from webhook payload status', async () => { + const payload = { + entry: [ + { + changes: [ + { + value: { + statuses: [{ recipient_id: 'x' }] + } + } + ] + } + ] + } + expect(extractDestinyPhone(payload)).toBe('x') + }) + + test('return empty extractDestinyPhone from api payload', async () => { + expect(extractDestinyPhone({ to: 'y' })).toBe('y') + }) + test('phoneNumberToJid with nine digit', async () => { expect(phoneNumberToJid('+5549988290955')).toEqual('5549988290955@s.whatsapp.net') }) diff --git a/package.json b/package.json index 66c1941b..8b98ca75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unoapi-cloud", - "version": "1.18.3", + "version": "1.18.4", "description": "Unoapi Cloud", "exports": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/src/defaults.ts b/src/defaults.ts index 84992933..97276ecb 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -64,6 +64,7 @@ export const UNOAPI_MESSAGE_RETRY_LIMIT = parseInt(process.env.MESSAGE_RETRY_LIM export const UNOAPI_MESSAGE_RETRY_DELAY = parseInt(process.env.MESSAGE_RETRY_DELAY || '10000') export const UNOAPI_DELAY_BETWEEN_MESSAGES_MS = parseInt(process.env.UNOAPI_DELAY_BETWEEN_MESSAGES_MS || '0') export const UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS = parseInt(process.env.UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS || '0') +export const UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS = parseInt(process.env.UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS || '0') export const UNOAPI_BULK_BATCH = parseInt(process.env.UNOAPI_BULK_BATCH || '5') export const UNOAPI_BULK_DELAY = parseInt(process.env.UNOAPI_BULK_DELAY || '60') export const UNOAPI_BULK_MESSAGE_DELAY = parseInt(process.env.UNOAPI_BULK_DELAY || '12') diff --git a/src/jobs/webhooker.ts b/src/jobs/webhooker.ts index dd3467c2..740dd97f 100644 --- a/src/jobs/webhooker.ts +++ b/src/jobs/webhooker.ts @@ -1,7 +1,31 @@ import { Webhook } from '../services/config' import { Outgoing } from '../services/outgoing' import { amqpEnqueue } from '../amqp' -import { UNOAPI_JOB_WEBHOOKER } from '../defaults' +import { UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS, UNOAPI_JOB_WEBHOOKER } from '../defaults' +import { extractDestinyPhone } from '../services/transformer' +import logger from '../services/logger' +const delays: Map = new Map() + +const sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +const delayFunc = UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS ? async (phone, payload) => { + let to = '' + try { + to = extractDestinyPhone(payload) + } catch (error) { + logger.error('Error on extract to from payload', error) + } + if (to) { + const key = `${phone}:${to}` + if (!delays.get(key)) { + delays.set(key, true); + logger.debug(`Delay for first message %s`, UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS) + sleep(UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS) + } + } +} : async (_phone, _payload) => {} export class WebhookerJob { private service: Outgoing @@ -22,6 +46,7 @@ export class WebhookerJob { ) } else if (a.webhook) { await this.service.sendHttp(phone, a.webhook, payload, {}) + await delayFunc(phone, payload) } else { await this.service.send(phone, payload) } diff --git a/src/services/blacklist.ts b/src/services/blacklist.ts index 5d50bfbd..80696409 100644 --- a/src/services/blacklist.ts +++ b/src/services/blacklist.ts @@ -3,6 +3,7 @@ import { amqpEnqueue } from '../amqp' import { UNOAPI_JOB_BLACKLIST_ADD } from '../defaults' import { blacklist, redisTtl, redisKeys } from './redis' import logger from './logger' +import { extractDestinyPhone } from './transformer' const DATA = new NodeCache() let searchData = true @@ -15,33 +16,6 @@ export interface isInBlacklist { (from: string, webhookId: string, payload: object): Promise } -export const extractDestinyPhone = (payload: object) => { - const data = payload as any - const number = data?.to || ( - ( - data.entry - && data.entry[0] - && data.entry[0].changes - && data.entry[0].changes[0] - && data.entry[0].changes[0].value - ) && ( - ( - data.entry[0].changes[0].value.contacts - && data.entry[0].changes[0].value.contacts[0] - && data.entry[0].changes[0].value.contacts[0].wa_id?.replace('+', '') - ) || ( - data.entry[0].changes[0].value.statuses - && data.entry[0].changes[0].value.statuses[0] - && data.entry[0].changes[0].value.statuses[0].recipient_id?.replace('+', '') - ) - ) - ) - if (!number) { - throw Error(`error on get phone number from ${JSON.stringify(payload)}`) - } - return number -} - export const blacklistInMemory = (from: string, webhookId: string, to: string) => { return `${from}:${webhookId}:${to}` } diff --git a/src/services/transformer.ts b/src/services/transformer.ts index 65717c79..c9fb177b 100644 --- a/src/services/transformer.ts +++ b/src/services/transformer.ts @@ -310,6 +310,33 @@ export const isValidPhoneNumber = (value: string, nine = false): boolean => { return !isInValid } +export const extractDestinyPhone = (payload: object) => { + const data = payload as any + const number = data?.to || ( + ( + data.entry + && data.entry[0] + && data.entry[0].changes + && data.entry[0].changes[0] + && data.entry[0].changes[0].value + ) && ( + ( + data.entry[0].changes[0].value.contacts + && data.entry[0].changes[0].value.contacts[0] + && data.entry[0].changes[0].value.contacts[0].wa_id?.replace('+', '') + ) || ( + data.entry[0].changes[0].value.statuses + && data.entry[0].changes[0].value.statuses[0] + && data.entry[0].changes[0].value.statuses[0].recipient_id?.replace('+', '') + ) + ) + ) + if (!number) { + throw Error(`error on get phone number from ${JSON.stringify(payload)}`) + } + return number +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const jidToPhoneNumber = (value: any, plus = '+', retry = true): string => { const number = (value || '').split('@')[0].split(':')[0].replace('+', '')