Skip to content

Commit

Permalink
feat: delay first message in webhooker service
Browse files Browse the repository at this point in the history
  • Loading branch information
clairton committed Sep 30, 2024
1 parent 76c2445 commit 79e83c4
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 68 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 1 addition & 39 deletions __tests__/services/blacklist.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,13 @@

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<typeof redisGet>
const redisKeysMock = redisKeys as jest.MockedFunction<typeof redisKeys>
const blacklistMock = blacklist as jest.MockedFunction<typeof blacklist>

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('')
Expand Down
39 changes: 39 additions & 0 deletions __tests__/services/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DecryptError,
getNormalizedMessage,
isSaveMedia,
extractDestinyPhone,
} from '../../src/services/transformer'
const key = { remoteJid: '[email protected]', id: 'abc' }

Expand All @@ -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('[email protected]')
})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
27 changes: 26 additions & 1 deletion src/jobs/webhooker.ts
Original file line number Diff line number Diff line change
@@ -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<String, boolean> = 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
Expand All @@ -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)
}
Expand Down
28 changes: 1 addition & 27 deletions src/services/blacklist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,33 +16,6 @@ export interface isInBlacklist {
(from: string, webhookId: string, payload: object): Promise<String>
}

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}`
}
Expand Down
27 changes: 27 additions & 0 deletions src/services/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('+', '')
Expand Down

0 comments on commit 79e83c4

Please sign in to comment.