diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7aad93d5..a448a16e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,17 +2,17 @@ name: ci on: push: - branches: [ main develop ] + branches: [ main, develop ] tags: - 'v*.*.*' pull_request: - branches: [ main develop ] + branches: [ main, develop ] jobs: test: runs-on: ubuntu-latest - steps: + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: @@ -35,25 +35,20 @@ jobs: uses: docker/metadata-action@v5 with: images: | - ${{ github.repository }} + ghcr.io/${{ github.repository }} tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha + ${{ github.ref_name }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - name: Login to Docker Hub + - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -67,9 +62,5 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - - - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} \ No newline at end of file + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index e0aa16bc..7aec7cfe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ data/medias/* data/stores/* data/sessions/* .env +.idea/* + .yarn/* .yarnrc.yml \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..c2580db2 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +nodejs 24.7.0 +yarn 1.22.22 diff --git a/__tests__/jobs/timer.ts b/__tests__/jobs/timer.ts index 95910f38..3012c918 100644 --- a/__tests__/jobs/timer.ts +++ b/__tests__/jobs/timer.ts @@ -7,7 +7,7 @@ const delLastTimerMock = delLastTimer as jest.MockedFunction { let incoming, job, payload, phone, to, message, time, sendSpy, mockGetLastTimer, incomingPayload - + beforeEach(() => { incoming = mock() mockGetLastTimer = jest.fn() @@ -17,21 +17,24 @@ describe('timer', () => { message = `${new Date().getTime()}s sdfhosfo` time = '2011-10-05T14:48:00.000Z' payload = { - phone, to, message, time + phone, + to, + message, + time, } sendSpy = jest.spyOn(incoming, 'send') - incomingPayload =[ + incomingPayload = [ phone, { messaging_product: 'whatsapp', to, type: 'text', text: { - body: message - } + body: message, + }, }, - {} + {}, ] delLastTimerMock.mockResolvedValue(Promise.resolve()) }) diff --git a/__tests__/routes/blacklist.ts b/__tests__/routes/blacklist.ts index b7e1dae4..38ebae8d 100644 --- a/__tests__/routes/blacklist.ts +++ b/__tests__/routes/blacklist.ts @@ -24,8 +24,8 @@ describe('blacklist routes', () => { const reload = mock() const logout = mock() const app: App = new App(incoming, outgoing, '', getConfigTest, sessionStore, onNewLogin, addToBlacklist, reload, logout) - const res = await request(app.server).post('/2/blacklist/1').send({ttl: 1, to: '3'}) - expect(addToBlacklist).toHaveBeenCalledWith('2', '1', '3', 1); + const res = await request(app.server).post('/2/blacklist/1').send({ ttl: 1, to: '3' }) + expect(addToBlacklist).toHaveBeenCalledWith('2', '1', '3', 1) expect(res.status).toEqual(200) }) }) diff --git a/__tests__/services/blacklist.ts b/__tests__/services/blacklist.ts index a866218b..eb368f83 100644 --- a/__tests__/services/blacklist.ts +++ b/__tests__/services/blacklist.ts @@ -1,4 +1,3 @@ - jest.mock('../../src/services/redis') import { isInBlacklistInMemory, addToBlacklistInMemory, cleanBlackList, isInBlacklistInRedis } from '../../src/services/blacklist' import { redisGet, redisKeys, blacklist } from '../../src/services/redis' diff --git a/__tests__/services/media_store_file.ts b/__tests__/services/media_store_file.ts index e5d606d7..3dbef31a 100644 --- a/__tests__/services/media_store_file.ts +++ b/__tests__/services/media_store_file.ts @@ -8,12 +8,12 @@ const phone = `${new Date().getTime()}` const messageId = `wa.${new Date().getTime()}` const url = `http://somehost` const mimetype = 'text/plain' -const extension = 'txt' +const extension = 'txt' const message = { messaging_product: 'whatsapp', id: `${phone}/${messageId}`, - mime_type: mimetype + mime_type: mimetype, } const dataStore = mock() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -32,7 +32,7 @@ describe('media routes', () => { test('getMedia', async () => { const response = { url: `${url}/v15.0/download/${phone}/${messageId}.${extension}`, - ...message + ...message, } expect(await mediaStore.getMedia(url, messageId)).toStrictEqual(response) }) diff --git a/__tests__/services/outgoing_cloud_api.ts b/__tests__/services/outgoing_cloud_api.ts index f1965364..79af9004 100644 --- a/__tests__/services/outgoing_cloud_api.ts +++ b/__tests__/services/outgoing_cloud_api.ts @@ -44,7 +44,7 @@ describe('service outgoing whatsapp cloud api', () => { service = new OutgoingCloudApi(getConfig, isInBlacklistInMemory) textPayload = { text: { - body: 'test' + body: 'test', }, type: 'text', to: 'abc', @@ -57,8 +57,8 @@ describe('service outgoing whatsapp cloud api', () => { { value: { metadata: { display_phone_number: 'abc' }, - messages: [ { from: 'abc' }, ] - } + messages: [{ from: 'abc' }], + }, }, ], }, @@ -71,7 +71,7 @@ describe('service outgoing whatsapp cloud api', () => { changes: [ { value: { - statuses: [ { status: 'deleted' } ] + statuses: [{ status: 'deleted' }], }, }, ], diff --git a/__tests__/services/session_store_file.ts b/__tests__/services/session_store_file.ts index ac5efdbe..0356dd84 100644 --- a/__tests__/services/session_store_file.ts +++ b/__tests__/services/session_store_file.ts @@ -38,7 +38,7 @@ describe('service session store file', () => { return MAX_CONNECT_RETRY + 1 } return getConnectCount(session) - } + } expect(await store.verifyStatusStandBy(session)).toBe(true) }) test('return a no standby on count and verify', async () => { @@ -50,7 +50,7 @@ describe('service session store file', () => { return MAX_CONNECT_RETRY - 2 } return getConnectCount(session) - } - expect(!!await store.verifyStatusStandBy(session)).toBe(false) + } + expect(!!(await store.verifyStatusStandBy(session))).toBe(false) }) }) diff --git a/__tests__/services/socket.ts b/__tests__/services/socket.ts index dd93984b..6d7c7b5d 100644 --- a/__tests__/services/socket.ts +++ b/__tests__/services/socket.ts @@ -49,7 +49,7 @@ describe('service socket', () => { onNewLogin, attempts: 1, time: 1, - config: { ...defaultConfig, whatsappVersion } + config: { ...defaultConfig, whatsappVersion }, }) expect(response && response.status.attempt).toBe(1) }) @@ -65,7 +65,7 @@ describe('service socket', () => { onNewLogin, attempts: 1, time: 1, - config: { ...defaultConfig, whatsappVersion } + config: { ...defaultConfig, whatsappVersion }, }) expect(mockOn).toHaveBeenCalled() }) diff --git a/__tests__/services/transformer.ts b/__tests__/services/transformer.ts index 12d0694c..c6046e3d 100644 --- a/__tests__/services/transformer.ts +++ b/__tests__/services/transformer.ts @@ -61,12 +61,12 @@ describe('service transformer', () => { changes: [ { value: { - statuses: [{ recipient_id: 'x' }] - } - } - ] - } - ] + statuses: [{ recipient_id: 'x' }], + }, + }, + ], + }, + ], } expect(extractDestinyPhone(payload)).toBe('x') }) @@ -78,12 +78,12 @@ describe('service transformer', () => { changes: [ { value: { - statuses: [{ recipient_id: 'x' }] - } - } - ] - } - ] + statuses: [{ recipient_id: 'x' }], + }, + }, + ], + }, + ], } expect(isGroupMessage(payload)).toBe(false) }) @@ -108,7 +108,7 @@ describe('service transformer', () => { test('getChatAndNumberAndId with lid and without group', async () => { const senderPn = '554988290955' const remoteJid = '24788516941@lid' - const payload = { key: { remoteJid, senderPn }} + const payload = { key: { remoteJid, senderPn } } const a = getChatAndNumberAndId(payload) expect(a[0]).toBe(remoteJid) expect(a[1]).toBe('5549988290955') @@ -119,7 +119,7 @@ describe('service transformer', () => { const participantPn = '554988290955' const remoteJid = '24788516941@g.us' const participant = '554988290955@s.whatsapp.net' - const payload = { key: { remoteJid, participant, participantPn }} + const payload = { key: { remoteJid, participant, participantPn } } const a = getChatAndNumberAndId(payload) expect(a[0]).toBe(remoteJid) expect(a[1]).toBe('5549988290955') @@ -130,7 +130,7 @@ describe('service transformer', () => { const participantPn = '554988290955' const remoteJid = '24788516941@g.us' const participantLid = '24788516941@lid' - const payload = { key: { remoteJid, participantLid, participantPn }} + const payload = { key: { remoteJid, participantLid, participantPn } } const a = getChatAndNumberAndId(payload) expect(a[0]).toBe(remoteJid) expect(a[1]).toBe('5549988290955') @@ -140,7 +140,7 @@ describe('service transformer', () => { test('getChatAndNumberAndId with senderLid and without group', async () => { const senderPn = '554988290955' const remoteJid = '24788516941@lid' - const payload = { key: { remoteJid, senderLid: remoteJid, senderPn }} + const payload = { key: { remoteJid, senderLid: remoteJid, senderPn } } const a = getChatAndNumberAndId(payload) expect(a[0]).toBe(remoteJid) expect(a[1]).toBe('5549988290955') @@ -296,7 +296,6 @@ describe('service transformer', () => { test('jidToPhoneNumber without + and put 9˚ digit', async () => { expect(jidToPhoneNumber('+554988290955@s.whatsapp.net', '')).toEqual('5549988290955') }) - test('fromBaileysMessageContent with editedMessage for imageMessage', async () => { const phoneNumer = '5549998360838' @@ -310,24 +309,24 @@ describe('service transformer', () => { key: { remoteJid, fromMe: false, - id + id, }, message: { editedMessage: { message: { protocolMessage: { key: { - id: '3AD0FEAAF5915DAEAA07' + id: '3AD0FEAAF5915DAEAA07', }, type: 'MESSAGE_EDIT', editedMessage: { imageMessage: { - caption: body - } - } - } - } - } + caption: body, + }, + }, + }, + }, + }, }, pushName, messageTimestamp, @@ -1093,12 +1092,12 @@ describe('service transformer', () => { messageTimestamp, pushName, message: { - editedMessage:{ + editedMessage: { message: { - conversation - } - } - } + conversation, + }, + }, + }, } const output = { object: 'whatsapp_business_account', @@ -1141,7 +1140,7 @@ describe('service transformer', () => { const messageTimestamp = Math.floor(new Date().getTime() / 1000).toString() const phoneNumer = '5549998093075' const conversation = `blablabla2.${new Date().getTime()}` - const input = { + const input = { key: { remoteJid: remoteJid, fromMe: true, @@ -1149,19 +1148,19 @@ describe('service transformer', () => { }, messageTimestamp, pushName, - message: { - protocolMessage: { - key: { - remoteJid, - fromMe: true, - id: id2 - }, - type: 'MESSAGE_EDIT', - editedMessage: { - conversation, - }, - } - } + message: { + protocolMessage: { + key: { + remoteJid, + fromMe: true, + id: id2, + }, + type: 'MESSAGE_EDIT', + editedMessage: { + conversation, + }, + }, + }, } const output = { object: 'whatsapp_business_account', @@ -1198,12 +1197,12 @@ describe('service transformer', () => { test('getMessageType with viewOnceMessage', async () => { const input = { message: { - protocolMessage: {}, + protocolMessage: {}, type: 'MESSAGE_EDIT', - editedMessage: { - conversation: 'blablabla2' - } - } + editedMessage: { + conversation: 'blablabla2', + }, + }, } expect(getMessageType(input)).toEqual('editedMessage') }) @@ -1240,15 +1239,11 @@ describe('service transformer', () => { contacts: [ { name: { formatted_name: displayName }, - phones: [{ phone, wa_id }] - } - ] + phones: [{ phone, wa_id }], + }, + ], } - const vcard = 'BEGIN:VCARD\n' - + 'VERSION:3.0\n' - + `N:${displayName}\n` - + `TEL;type=CELL;type=VOICE;waid=${wa_id}:${phone}\n` - + 'END:VCARD' + const vcard = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + `N:${displayName}\n` + `TEL;type=CELL;type=VOICE;waid=${wa_id}:${phone}\n` + 'END:VCARD' const output = { contacts: { displayName, contacts: [{ vcard }] } } expect(toBaileysMessageContent(input)).toEqual(output) }) @@ -1462,21 +1457,20 @@ describe('service transformer', () => { expect(toBaileysMessageContent(input)).toEqual(output) }) - test('fromBaileysMessageContent participant outside key', async () => { const phoneNumer = '5549998093075' const remotePhoneNumber = '11115551212' const input = { key: { remoteJid: '554988189915-1593526912@g.us', - fromMe: false, - id: '583871ED40A7FBC09B5C3A7C2CC760A0' + fromMe: false, + id: '583871ED40A7FBC09B5C3A7C2CC760A0', }, message: { - conversation: '🤷‍♂️' + conversation: '🤷‍♂️', }, participant: `${remotePhoneNumber}@s.whatsapp.net`, - isMentionedInStatus :false + isMentionedInStatus: false, } const resp = fromBaileysMessageContent(phoneNumer, input)[0] const from = resp.entry[0].changes[0].value.messages[0].from @@ -1493,10 +1487,10 @@ describe('service transformer', () => { const messageTimestamp = Math.floor(new Date().getTime() / 1000).toString() const phoneNumer = '5549998360838' const input = { - key:{ + key: { remoteJid, fromMe: false, - id + id, }, message: { extendedTextMessage: { @@ -1508,13 +1502,13 @@ describe('service transformer', () => { statusMentionMessage: { message: { protocolMessage: { - type: 'STATUS_MENTION_MESSAGE' - } - } - } - } - } - } + type: 'STATUS_MENTION_MESSAGE', + }, + }, + }, + }, + }, + }, }, pushName, messageTimestamp, @@ -1554,6 +1548,6 @@ describe('service transformer', () => { } expect(fromBaileysMessageContent(phoneNumer, input)[0]).toEqual(output) }) -// {"key":{"remoteJid":"555533800800@s.whatsapp.net","fromMe":false,"id":"1BE283407E62E5A073"},"messageTimestamp":1753900800,"pushName":"555533800800","broadcast":false,"message":{"messageContextInfo":{"deviceListMetadata":{"recipientKeyHash":"BuoOcp2GlUsdsQ==","recipientTimestamp":"1753278139","recipientKeyIndexes":[0,5]},"deviceListMetadataVersion":2},"buttonsMessage":{"contentText":"Para confirmar, estou falando com *IM Agronegócios* e o seu CNPJ é *41.281.5xx/xxxx-xx*?","buttons":[{"buttonId":"1","buttonText":{"displayText":"Sim"},"type":"RESPONSE"},{"buttonId":"2","buttonText":{"displayText":"Não"},"type":"RESPONSE"}],"headerType":"EMPTY"}},"verifiedBizName":"Unifique"} -// {"key":{"remoteJid":"555533800800@s.whatsapp.net","fromMe":true,"id":"3EB02FCD7C12A71F06DE34"}, "messageTimestamp":1753900805,"pushName":"Im Agronegócios","broadcast":false,"status":2, "message":{"buttonsResponseMessage":{"selectedButtonId":"1","selectedDisplayText":"Sim","contextInfo":{"stanzaId":"1BE283407E62E5A073","participant":"555533800800@s.whatsapp.net","quotedMessage":{"messageContextInfo":{},"buttonsMessage":{"contentText":"Para confirmar, estou falando com *IM Agronegócios* e o seu CNPJ é *41.281.5xx/xxxx-xx*?","buttons":[{"buttonId":"1","buttonText":{"displayText":"Sim"},"type":"RESPONSE"},{"buttonId":"2","buttonText":{"displayText":"Não"},"type":"RESPONSE"}],"headerType":"EMPTY"}}},"type":"DISPLAY_TEXT"}}} + // {"key":{"remoteJid":"555533800800@s.whatsapp.net","fromMe":false,"id":"1BE283407E62E5A073"},"messageTimestamp":1753900800,"pushName":"555533800800","broadcast":false,"message":{"messageContextInfo":{"deviceListMetadata":{"recipientKeyHash":"BuoOcp2GlUsdsQ==","recipientTimestamp":"1753278139","recipientKeyIndexes":[0,5]},"deviceListMetadataVersion":2},"buttonsMessage":{"contentText":"Para confirmar, estou falando com *IM Agronegócios* e o seu CNPJ é *41.281.5xx/xxxx-xx*?","buttons":[{"buttonId":"1","buttonText":{"displayText":"Sim"},"type":"RESPONSE"},{"buttonId":"2","buttonText":{"displayText":"Não"},"type":"RESPONSE"}],"headerType":"EMPTY"}},"verifiedBizName":"Unifique"} + // {"key":{"remoteJid":"555533800800@s.whatsapp.net","fromMe":true,"id":"3EB02FCD7C12A71F06DE34"}, "messageTimestamp":1753900805,"pushName":"Im Agronegócios","broadcast":false,"status":2, "message":{"buttonsResponseMessage":{"selectedButtonId":"1","selectedDisplayText":"Sim","contextInfo":{"stanzaId":"1BE283407E62E5A073","participant":"555533800800@s.whatsapp.net","quotedMessage":{"messageContextInfo":{},"buttonsMessage":{"contentText":"Para confirmar, estou falando com *IM Agronegócios* e o seu CNPJ é *41.281.5xx/xxxx-xx*?","buttons":[{"buttonId":"1","buttonText":{"displayText":"Sim"},"type":"RESPONSE"},{"buttonId":"2","buttonText":{"displayText":"Não"},"type":"RESPONSE"}],"headerType":"EMPTY"}}},"type":"DISPLAY_TEXT"}}} }) diff --git a/jest.config.js b/jest.config.js index b413e106..813f8ce3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,12 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + '^.+\\.(js|jsx)$': 'ts-jest', + }, + + transformIgnorePatterns: [ + '/node_modules/(?!(baileys|@adiwajshing/keyed-db|pino|pino-pretty)/)', + ], }; \ No newline at end of file diff --git a/package.json b/package.json index 5dd25f96..47747b19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unoapi-cloud", - "version": "2.5.0-alpha-11", + "version": "2.5.0", "description": "Unoapi Cloud", "exports": "./dist/index.js", "types": "./dist/index.d.ts", @@ -83,7 +83,7 @@ "amqplib": "^0.10.8", "audio2textjs": "^1.0.5", "awesome-phonenumber": "^6.8.0", - "baileys": "git+https://github.com/WhiskeySockets/Baileys#19124426b2ded31f6d28ed60c53b75f101865bc3", + "baileys": "^6.7.19", "dotenv": "^16.4.5", "express": "^4.19.2", "i18n": "^0.15.1", diff --git a/src/amqp.ts b/src/amqp.ts index e711409a..f142b731 100644 --- a/src/amqp.ts +++ b/src/amqp.ts @@ -14,7 +14,7 @@ import { UNOAPI_SERVER_NAME, UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_EXCHANGE_BRIDGE_NAME, - IGNORED_TO_NUMBERS + IGNORED_TO_NUMBERS, } from './defaults' import logger from './services/logger' import { version } from '../package.json' @@ -22,14 +22,8 @@ import { extractDestinyPhone } from './services/transformer' const withTimeout = (millis, error, promise) => { let timeoutPid - const timeout = new Promise((_resolve, reject) => - timeoutPid = setTimeout( - () => reject(error), - millis)) - return Promise.race([ - promise, - timeout - ]).finally(() => { + const timeout = new Promise((_resolve, reject) => (timeoutPid = setTimeout(() => reject(error), millis))) + return Promise.race([promise, timeout]).finally(() => { if (timeoutPid) { clearTimeout(timeoutPid) } @@ -124,15 +118,18 @@ export const amqpGetExchange = async (exchange: string, type: ExchagenType, pref logger.info('Creating exchange %s...', exchange) const channel = await amqpGetChannel() await channel.prefetch(prefetch) - await channel.assertExchange(exchange, type, { durable: true, arguments: { 'x-max-priority': 5 }}) + await channel.assertExchange(exchange, type, { durable: true, arguments: { 'x-max-priority': 5 } }) const exchangeDeadId = queueDeadName(exchange) await amqpChannel.assertExchange(exchangeDeadId, 'topic', { durable: true }) const exchangeDelayedId = queueDelayedName(exchange) - await amqpChannel.assertExchange(exchangeDelayedId, 'topic', { durable: true , arguments: { - 'x-dead-letter-exchange': exchange - }}) + await amqpChannel.assertExchange(exchangeDelayedId, 'topic', { + durable: true, + arguments: { + 'x-dead-letter-exchange': exchange, + }, + }) logger.info('Created exchange %s!', exchange) exchanges.set(exchange, true) } @@ -159,7 +156,7 @@ export const amqpGetQueue = async ( priority: 0, notifyFailedMessages: NOTIFY_FAILED_MESSAGES, type: 'topic', - prefetch: 1 + prefetch: 1, }, ): Promise => { if (!queues.get(queue)) { @@ -176,16 +173,18 @@ export const amqpGetQueue = async ( const exchangeDelayedId = queueDelayedName(exchange) const queueDelayedId = queueDelayedName(queue) - const queueDelayed = await amqpChannel.assertQueue(queueDelayedId, { durable: true, arguments: { - 'x-dead-letter-exchange': deadLetterExchange - }}) + const queueDelayed = await amqpChannel.assertQueue(queueDelayedId, { + durable: true, + arguments: { + 'x-dead-letter-exchange': deadLetterExchange, + }, + }) await amqpChannel.bindQueue(queueDelayedId, exchangeDelayedId, `${queueDelayedId}.*`) queues.set(queue, { queueMain, queueDead, queueDelayed }) logger.info('Created queue %s!', queue) } - validateRoutingKey(routingKey) if (/^\d+$/.test(routingKey) && !routes.get(routingKey)) { await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_BIND}.${UNOAPI_SERVER_NAME}`, '', { routingKey }, { type: 'direct' }) @@ -194,7 +193,6 @@ export const amqpGetQueue = async ( return queues.get(queue)! } - const getExchangeType = (exchange): ExchagenType => { if (UNOAPI_EXCHANGE_BRIDGE_NAME == exchange) { return 'direct' @@ -210,12 +208,12 @@ export const amqpPublish = async ( queue: string, routingKey: string, payload: object, - options: Partial = { + options: Partial = { delay: 0, dead: false, maxRetries: UNOAPI_MESSAGE_RETRY_LIMIT, countRetries: 0, - priority: 0 + priority: 0, }, ) => { validateRoutingKey(routingKey) @@ -254,7 +252,7 @@ export const amqpPublish = async ( exchangeUsed, destiny, JSON.stringify(payload), - JSON.stringify(properties) + JSON.stringify(properties), ) } @@ -264,9 +262,9 @@ export const amqpConsume = async ( routingKey: string, callback: ConsumeCallback, options: Partial = { - delay: UNOAPI_MESSAGE_RETRY_DELAY, + delay: UNOAPI_MESSAGE_RETRY_DELAY, priority: 0, - notifyFailedMessages: NOTIFY_FAILED_MESSAGES + notifyFailedMessages: NOTIFY_FAILED_MESSAGES, }, ) => { logger.debug('Configurate to consume exchange: %s, queue: %s, routing key: %s and type: %s', exchange, queue, routingKey, options.type) @@ -285,7 +283,13 @@ export const amqpConsume = async ( const maxRetries = parseInt(headers[UNOAPI_X_MAX_RETRIES] || UNOAPI_MESSAGE_RETRY_LIMIT) const countRetries = parseInt(headers[UNOAPI_X_COUNT_RETRIES] || '0') + 1 try { - logger.debug('Received in queue %s, with routing key: %s, with message: %s with headers: %s', queue, routingKey, content, JSON.stringify(payload.properties.headers)) + logger.debug( + 'Received in queue %s, with routing key: %s, with message: %s with headers: %s', + queue, + routingKey, + content, + JSON.stringify(payload.properties.headers), + ) if (IGNORED_CONNECTIONS_NUMBERS.includes(routingKey)) { logger.info(`Ignore messages from ${routingKey}`) } else if (IGNORED_TO_NUMBERS.length > 0 && IGNORED_TO_NUMBERS.includes(extractDestinyPhone(data.payload, false))) { @@ -311,8 +315,9 @@ export const amqpConsume = async ( to: routingKey, type: 'text', text: { - body: `Unoapi version ${version} message failed in queue ${queue}\n\nstack trace: ${error.stack}\n\n\nerror: ${error.message - }\n\ndata: ${JSON.stringify(data, undefined, 2)}`, + body: `Unoapi version ${version} message failed in queue ${queue}\n\nstack trace: ${error.stack}\n\n\nerror: ${ + error.message + }\n\ndata: ${JSON.stringify(data, undefined, 2)}`, }, }, }, diff --git a/src/app.ts b/src/app.ts index f8e46dac..16e442b1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -46,21 +46,21 @@ export class App { this.socket = new Server(this.server, { path: '/ws', cors: { - origin: '*' - } + origin: '*', + }, }) this.router( - incoming, - outgoing, - baseUrl, + incoming, + outgoing, + baseUrl, getConfig, sessionStore, this.socket, onNewLogin, - addToBlacklist, - reload, + addToBlacklist, + reload, logout, - middleware, + middleware, injectRoute, contact, ) @@ -82,17 +82,17 @@ export class App { contact: Contact, ) { const roter = router( - incoming, - outgoing, - baseUrl, - getConfig, - sessionStore, - socket, - onNewLogin, - addToBlacklist, - reload, - logout, - middleware, + incoming, + outgoing, + baseUrl, + getConfig, + sessionStore, + socket, + onNewLogin, + addToBlacklist, + reload, + logout, + middleware, injectRoute, contact, ) diff --git a/src/bridge.ts b/src/bridge.ts index 0e4ef6b3..918de9c3 100644 --- a/src/bridge.ts +++ b/src/bridge.ts @@ -6,13 +6,7 @@ import { BindBridgeJob } from './jobs/bind_bridge' import { SessionStoreRedis } from './services/session_store_redis' import { SessionStore } from './services/session_store' import { autoConnect } from './services/auto_connect' -import { - UNOAPI_QUEUE_BIND, - UNOAPI_QUEUE_RELOAD, - UNOAPI_QUEUE_LOGOUT, - UNOAPI_SERVER_NAME, - UNOAPI_EXCHANGE_BRIDGE_NAME, -} from './defaults' +import { UNOAPI_QUEUE_BIND, UNOAPI_QUEUE_RELOAD, UNOAPI_QUEUE_LOGOUT, UNOAPI_SERVER_NAME, UNOAPI_EXCHANGE_BRIDGE_NAME } from './defaults' import { amqpConsume } from './amqp' import { startRedis } from './services/redis' import { getConfig } from './services/config' @@ -54,40 +48,22 @@ const startBrigde = async () => { logger.info('Unoapi Cloud version %s starting bridge...', version) logger.info('Starting bind consumer') - await amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_BIND}.${UNOAPI_SERVER_NAME}`, - '', - bindJob.consume.bind(bindJob), - { - prefetch: 1, - type: 'direct' - } - ) + await amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_BIND}.${UNOAPI_SERVER_NAME}`, '', bindJob.consume.bind(bindJob), { + prefetch: 1, + type: 'direct', + }) logger.info('Starting reload consumer') - await amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_RELOAD}.${UNOAPI_SERVER_NAME}`, - '', - reloadJob.consume.bind(reloadJob), - { - prefetch: 1, - type: 'direct' - } - ) + await amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_RELOAD}.${UNOAPI_SERVER_NAME}`, '', reloadJob.consume.bind(reloadJob), { + prefetch: 1, + type: 'direct', + }) logger.info('Starting logout consumer') - await amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_LOGOUT}.${UNOAPI_SERVER_NAME}`, - '', - logoutJob.consume.bind(logoutJob), - { - prefetch: 1, - type: 'direct' - } - ) + await amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_LOGOUT}.${UNOAPI_SERVER_NAME}`, '', logoutJob.consume.bind(logoutJob), { + prefetch: 1, + type: 'direct', + }) const sessionStore: SessionStore = new SessionStoreRedis() diff --git a/src/broker.ts b/src/broker.ts index 587e9dbc..f75f777c 100644 --- a/src/broker.ts +++ b/src/broker.ts @@ -1,7 +1,7 @@ import * as dotenv from 'dotenv' dotenv.config() -import { +import { UNOAPI_QUEUE_RELOAD, UNOAPI_SERVER_NAME, UNOAPI_QUEUE_MEDIA, @@ -65,85 +65,55 @@ const startBroker = async () => { logger.info('Unoapi Cloud version %s starting broker...', version) logger.info('Starting reload consumer') - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_RELOAD, - '*', - reloadJob.consume.bind(reloadJob), - { type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_RELOAD, '*', reloadJob.consume.bind(reloadJob), { type: 'topic' }) logger.info('Starting media consumer') - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_MEDIA, - '*', - mediaJob.consume.bind(mediaJob), - { type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_MEDIA, '*', mediaJob.consume.bind(mediaJob), { type: 'topic' }) logger.info('Binding queues consumer for server %s', UNOAPI_SERVER_NAME) const notifyFailedMessages = NOTIFY_FAILED_MESSAGES logger.info('Starting outgoing consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_OUTGOING, - '*', - outgingJob.consume.bind(outgingJob), - { notifyFailedMessages, prefetch, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_OUTGOING, '*', outgingJob.consume.bind(outgingJob), { + notifyFailedMessages, + prefetch, + type: 'topic', + }) logger.info('Starting transcriber consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_TRANSCRIBER, - '*', - transcriberJob.consume.bind(transcriberJob), - { notifyFailedMessages, prefetch, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TRANSCRIBER, '*', transcriberJob.consume.bind(transcriberJob), { + notifyFailedMessages, + prefetch, + type: 'topic', + }) logger.info('Starting timer consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_TIMER, - '*', - timerJob.consume.bind(timerJob), - { notifyFailedMessages, prefetch, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TIMER, '*', timerJob.consume.bind(timerJob), { + notifyFailedMessages, + prefetch, + type: 'topic', + }) if (notifyFailedMessages) { logger.debug('Starting notification consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_NOTIFICATION, - '*', - notificationJob.consume.bind(notificationJob), - { notifyFailedMessages: false, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_NOTIFICATION, '*', notificationJob.consume.bind(notificationJob), { + notifyFailedMessages: false, + type: 'topic', + }) } if (STATUS_FAILED_WEBHOOK_URL) { const job = new WebhookStatusFailedJob(STATUS_FAILED_WEBHOOK_URL) logger.debug('Starting webhook status failed consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED, - '*', - job.consume.bind(job), - { notifyFailedMessages: false, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED, '*', job.consume.bind(job), { + notifyFailedMessages: false, + type: 'topic', + }) } logger.info('Starting blacklist add consumer %s', UNOAPI_SERVER_NAME) - await amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_BLACKLIST_ADD, - '*', - addToBlacklist, - { notifyFailedMessages, prefetch, type: 'topic' } - ) + await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BLACKLIST_ADD, '*', addToBlacklist, { notifyFailedMessages, prefetch, type: 'topic' }) logger.info('Unoapi Cloud version %s started broker!', version) } diff --git a/src/bulker.ts b/src/bulker.ts index 0d0cebab..8c697910 100644 --- a/src/bulker.ts +++ b/src/bulker.ts @@ -35,7 +35,6 @@ if (process.env.SENTRY_DSN) { const getConfigLocal: getConfig = getConfigRedis const incomingAmqp: Incoming = new IncomingAmqp(getConfigLocal) - const outgoingCloudApi: Outgoing = new OutgoingCloudApi(getConfigLocal, isInBlacklistInRedis) const commanderJob = new CommanderJob(outgoingCloudApi, getConfigRedis) const bulkParserJob = new BulkParserJob(outgoingCloudApi, getConfigRedis) diff --git a/src/controllers/index_controller.ts b/src/controllers/index_controller.ts index d341d66c..98d0eba3 100644 --- a/src/controllers/index_controller.ts +++ b/src/controllers/index_controller.ts @@ -3,7 +3,6 @@ import logger from '../services/logger' import path from 'path' class IndexController { - public root(req: Request, res: Response) { logger.debug('root method %s', JSON.stringify(req.method)) logger.debug('root headers %s', JSON.stringify(req.headers)) diff --git a/src/controllers/marketing_messages_controller.ts b/src/controllers/marketing_messages_controller.ts index 91fb52d1..16993958 100644 --- a/src/controllers/marketing_messages_controller.ts +++ b/src/controllers/marketing_messages_controller.ts @@ -5,7 +5,6 @@ import { Outgoing } from '../services/outgoing' import { MessagesController } from './messages_controller' export class MarketingMessagesController extends MessagesController { - constructor(incoming: Incoming, outgoing: Outgoing) { super(incoming, outgoing) this.endpoint = 'marketing_messages' diff --git a/src/controllers/pairing_code_controller.ts b/src/controllers/pairing_code_controller.ts index d5e832f2..b7532e60 100644 --- a/src/controllers/pairing_code_controller.ts +++ b/src/controllers/pairing_code_controller.ts @@ -25,8 +25,8 @@ export class PairingCodeController { to: phone, type: 'text', text: { - body: 'Request Pairing code' - } + body: 'Request Pairing code', + }, } this.service.send(phone, message, {}) return res.status(200).json({ success: true }) diff --git a/src/controllers/registration_controller.ts b/src/controllers/registration_controller.ts index 004ecae4..56157a0f 100644 --- a/src/controllers/registration_controller.ts +++ b/src/controllers/registration_controller.ts @@ -43,4 +43,4 @@ export class RegistrationController { await this.logout.run(phone) return res.status(204).send() } -} \ No newline at end of file +} diff --git a/src/controllers/templates_controller.ts b/src/controllers/templates_controller.ts index 1bcc0a01..06adac94 100644 --- a/src/controllers/templates_controller.ts +++ b/src/controllers/templates_controller.ts @@ -58,7 +58,6 @@ export class TemplatesController { const templates = await store.dataStore.loadTemplates() return res.status(200).json({ data: templates }) } - } catch (e) { return res.status(400).json({ status: 'error', message: `${phone} could not create template, error: ${e.message}` }) } diff --git a/src/controllers/webhook_controller.ts b/src/controllers/webhook_controller.ts index add3e124..72b45926 100644 --- a/src/controllers/webhook_controller.ts +++ b/src/controllers/webhook_controller.ts @@ -35,7 +35,7 @@ export class WebhookController { const token = req.query['hub.verify_token'] const challenge = req.query['hub.challenge'] const config = (await this.getConfig(phone.replace('+', ''))) || { authToken: UNOAPI_AUTH_TOKEN } - + if (mode === 'subscribe' && token === config.authToken) { res.status(200).send(challenge) } else { diff --git a/src/controllers/webhook_fake_controller.ts b/src/controllers/webhook_fake_controller.ts index a061a5e4..1008963a 100644 --- a/src/controllers/webhook_fake_controller.ts +++ b/src/controllers/webhook_fake_controller.ts @@ -2,7 +2,6 @@ import { Request, Response } from 'express' import logger from '../services/logger' export class WebhookFakeController { - public async fake(req: Request, res: Response) { logger.debug('webhook fake method %s', req.method) logger.debug('webhook fake headers %s', JSON.stringify(req.headers)) diff --git a/src/defaults.ts b/src/defaults.ts index 313e8460..934d6803 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -14,7 +14,7 @@ export const UNO_LOG_LEVEL = process.env.UNO_LOG_LEVEL || LOG_LEVEL export const DEFAULT_LOCALE = process.env.DEFAULT_LOCALE || 'en' -export const VALIDATE_MEDIA_LINK_BEFORE_SEND = +export const VALIDATE_MEDIA_LINK_BEFORE_SEND = process.env.VALIDATE_MEDIA_LINK_BEFORE_SEND == _undefined ? false : process.env.VALIDATE_MEDIA_LINK_BEFORE_SEND == 'true' export const WEBHOOK_FORWARD_PHONE_NUMBER_ID = process.env.WEBHOOK_FORWARD_PHONE_NUMBER_ID || '' @@ -36,8 +36,10 @@ export const CONNECTION_TYPE = process.env.CONNECTION_TYPE || 'qrcode' export const CONSUMER_TIMEOUT_MS = parseInt(process.env.CONSUMER_TIMEOUT_MS || '360000') export const WEBHOOK_SEND_NEW_MESSAGES = process.env.WEBHOOK_SEND_NEW_MESSAGES == _undefined ? false : process.env.WEBHOOK_SEND_NEW_MESSAGES == 'true' -export const WEBHOOK_SEND_INCOMING_MESSAGES = process.env.WEBHOOK_SEND_INCOMING_MESSAGES == _undefined ? true : process.env.WEBHOOK_SEND_INCOMING_MESSAGES == 'true' -export const WEBHOOK_SEND_GROUP_MESSAGES = process.env.WEBHOOK_SEND_GROUP_MESSAGES == _undefined ? true : process.env.WEBHOOK_SEND_GROUP_MESSAGES == 'true' +export const WEBHOOK_SEND_INCOMING_MESSAGES = + process.env.WEBHOOK_SEND_INCOMING_MESSAGES == _undefined ? true : process.env.WEBHOOK_SEND_INCOMING_MESSAGES == 'true' +export const WEBHOOK_SEND_GROUP_MESSAGES = + process.env.WEBHOOK_SEND_GROUP_MESSAGES == _undefined ? true : process.env.WEBHOOK_SEND_GROUP_MESSAGES == 'true' export const WEBHOOK_SEND_OUTGOING_MESSAGES = process.env.WEBHOOK_SEND_OUTGOING_MESSAGES == _undefined ? true : process.env.WEBHOOK_SEND_OUTGOING_MESSAGES == 'true' export const WEBHOOK_SEND_TRANSCRIBE_AUDIO = @@ -65,7 +67,7 @@ export const SESSION_TTL: number = parseInt(process.env.SESSION_TTL || '-1') export const UNOAPI_X_COUNT_RETRIES: string = process.env.UNOAPI_X_COUNT_RETRIES || 'x-unoapi-count-retries' export const UNOAPI_X_MAX_RETRIES: string = process.env.UNOAPI_X_MAX_RETRIES || 'x-unoapi-max-retries' export const UNOAPI_EXCHANGE_NAME = process.env.UNOAPI_EXCHANGE_NAME || 'unoapi' -export const UNOAPI_EXCHANGE_BROKER_NAME =`${UNOAPI_EXCHANGE_NAME}.broker` +export const UNOAPI_EXCHANGE_BROKER_NAME = `${UNOAPI_EXCHANGE_NAME}.broker` export const UNOAPI_EXCHANGE_BRIDGE_NAME = `${UNOAPI_EXCHANGE_NAME}.brigde` export const UNOAPI_QUEUE_NAME = process.env.UNOAPI_QUEUE_NAME || 'unoapi' export const UNOAPI_QUEUE_OUTGOING_PREFETCH = parseInt(process.env.UNOAPI_QUEUE_OUTGOING_PREFETCH || '1') @@ -113,7 +115,8 @@ export const BASE_STORE = process.env.UNOAPI_BASE_STORE || process.env.BASE_STOR export const AUTO_CONNECT: boolean = process.env.AUTO_CONNECT === _undefined ? true : process.env.AUTO_CONNECT == 'true' export const COMPOSING_MESSAGE: boolean = process.env.COMPOSING_MESSAGE === _undefined ? false : process.env.COMPOSING_MESSAGE == 'true' export const IGNORE_GROUP_MESSAGES: boolean = process.env.IGNORE_GROUP_MESSAGES == _undefined ? true : process.env.IGNORE_GROUP_MESSAGES == 'true' -export const IGNORE_NEWSLETTER_MESSAGES: boolean = process.env.IGNORE_NEWSLETTER_MESSAGES == _undefined ? true : process.env.IGNORE_NEWSLETTER_MESSAGES == 'true' +export const IGNORE_NEWSLETTER_MESSAGES: boolean = + process.env.IGNORE_NEWSLETTER_MESSAGES == _undefined ? true : process.env.IGNORE_NEWSLETTER_MESSAGES == 'true' export const IGNORE_BROADCAST_STATUSES: boolean = process.env.IGNORE_BROADCAST_STATUSES === _undefined ? true : process.env.IGNORE_BROADCAST_STATUSES == 'true' export const READ_ON_RECEIPT: boolean = process.env.READ_ON_RECEIPT === _undefined ? false : process.env.READ_ON_RECEIPT == 'true' @@ -147,7 +150,7 @@ export const VALIDATE_ROUTING_KEY = process.env.VALIDATE_ROUTING_KEY === _undefi export const CONFIG_SESSION_PHONE_CLIENT = process.env.CONFIG_SESSION_PHONE_CLIENT || 'Unoapi' export const CONFIG_SESSION_PHONE_NAME = process.env.CONFIG_SESSION_PHONE_NAME || 'Chrome' export const MESSAGE_CHECK_WAAPP = process.env.MESSAGE_CHECK_WAAPP || '' -export const WHATSAPP_VERSION = process.env.WHATSAPP_VERSION ? JSON.parse(process.env.WHATSAPP_VERSION) as WAVersion : undefined +export const WHATSAPP_VERSION = process.env.WHATSAPP_VERSION ? (JSON.parse(process.env.WHATSAPP_VERSION) as WAVersion) : undefined export const AVAILABLE_LOCALES = JSON.parse(process.env.AVAILABLE_LOCALES || '["en", "pt_BR", "pt"]') export const WAVOIP_TOKEN = process.env.WAVOIP_TOKEN || '' export const ONLY_HELLO_TEMPLATE: boolean = process.env.ONLY_HELLO_TEMPLATE === _undefined ? false : process.env.ONLY_HELLO_TEMPLATE == 'true' diff --git a/src/i18n.ts b/src/i18n.ts index 6f5c608c..3b3ba8bc 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -12,7 +12,7 @@ const i18n = new I18n({ i18n.setLocale(DEFAULT_LOCALE) -export const t = (phraseOrOptions: string | TranslateOptions, ...replace: any[]) => { +export const t = (phraseOrOptions: string | TranslateOptions, ...replace: any[]) => { const string = i18n.__(phraseOrOptions, ...replace) return string -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 216a6033..88838260 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,17 @@ const sessionStore: SessionStore = new SessionStoreFile() const reload = new ReloadBaileys(getClientBaileys, getConfigByEnv, listenerBaileys, onNewLoginn) const logout = new LogoutBaileys(getClientBaileys, getConfigByEnv, listenerBaileys, onNewLoginn) -const app: App = new App(incomingBaileys, outgoingCloudApi, BASE_URL, getConfigByEnv, sessionStore, onNewLoginn, addToBlacklistInMemory, reload, logout) +const app: App = new App( + incomingBaileys, + outgoingCloudApi, + BASE_URL, + getConfigByEnv, + sessionStore, + onNewLoginn, + addToBlacklistInMemory, + reload, + logout, +) broadcast.setSever(app.socket) app.server.listen(PORT, '0.0.0.0', async () => { @@ -68,4 +78,4 @@ process.on('unhandledRejection', (reason: any, promise) => { logger.error('unhandledRejection: %s', reason.stack) logger.error('promise: %s', promise) process.exit(1) -}) \ No newline at end of file +}) diff --git a/src/jobs/add_to_blacklist.ts b/src/jobs/add_to_blacklist.ts index 99241ddf..c63626da 100644 --- a/src/jobs/add_to_blacklist.ts +++ b/src/jobs/add_to_blacklist.ts @@ -6,4 +6,4 @@ export const addToBlacklist = async (_phone: string, data: object) => { logger.debug('Add blacklist from: %s, webhook: %s, to: %s, ttl: %s', from, webhookId, to, ttl) await addToBlacklistRedis(from, webhookId, to, ttl) } -export default addToBlacklist \ No newline at end of file +export default addToBlacklist diff --git a/src/jobs/bind_bridge.ts b/src/jobs/bind_bridge.ts index 93795eb3..be2b57fa 100644 --- a/src/jobs/bind_bridge.ts +++ b/src/jobs/bind_bridge.ts @@ -1,13 +1,7 @@ import { IncomingJob } from './incoming' import { ListenerJob } from './listener' import { Broadcast } from '../services/broadcast' -import { - UNOAPI_QUEUE_INCOMING, - UNOAPI_QUEUE_COMMANDER, - UNOAPI_QUEUE_LISTENER, - UNOAPI_SERVER_NAME, - UNOAPI_EXCHANGE_BRIDGE_NAME, -} from '../defaults' +import { UNOAPI_QUEUE_INCOMING, UNOAPI_QUEUE_COMMANDER, UNOAPI_QUEUE_LISTENER, UNOAPI_SERVER_NAME, UNOAPI_EXCHANGE_BRIDGE_NAME } from '../defaults' import { amqpConsume } from '../amqp' import { getConfig } from '../services/config' import { getConfigRedis } from '../services/config_redis' @@ -61,29 +55,29 @@ export class BindBridgeJob { logger.info('Starting listener baileys consumer %s', routingKey) await amqpConsume( UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, + `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, routingKey, listenerJob.consume.bind(listenerJob), { notifyFailedMessages, priority: 5, prefetch: 1, - type: 'direct' - } + type: 'direct', + }, ) logger.info('Starting incoming consumer %s', routingKey) await amqpConsume( UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_INCOMING}.${UNOAPI_SERVER_NAME}`, - routingKey, - incomingJob.consume.bind(incomingJob), + `${UNOAPI_QUEUE_INCOMING}.${UNOAPI_SERVER_NAME}`, + routingKey, + incomingJob.consume.bind(incomingJob), { notifyFailedMessages, priority: 5, prefetch: 1 /* allways 1 */, - type: 'direct' - } + type: 'direct', + }, ) } } diff --git a/src/jobs/bulk_parser.ts b/src/jobs/bulk_parser.ts index eec677dc..c19d86c0 100644 --- a/src/jobs/bulk_parser.ts +++ b/src/jobs/bulk_parser.ts @@ -235,9 +235,15 @@ export class BulkParserJob { }, } this.outgoing.formatAndSend(phone, phone, message) - await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, this.queueBulkSender, phone, { - payload: { messages, id, length: messages.length }, - }, { type: 'topic' }) + await amqpPublish( + UNOAPI_EXCHANGE_BROKER_NAME, + this.queueBulkSender, + phone, + { + payload: { messages, id, length: messages.length }, + }, + { type: 'topic' }, + ) } catch (error) { logger.error(error, 'Error on parse bulk') const message = { diff --git a/src/jobs/bulk_report.ts b/src/jobs/bulk_report.ts index fcc6b946..b717afca 100644 --- a/src/jobs/bulk_report.ts +++ b/src/jobs/bulk_report.ts @@ -29,13 +29,15 @@ export class BulkReportJob { if (count >= 10) { message = { body: `Bulk ${id} phone ${phone} with ${length}, has retried generate ${count} and not retried more` } } else { - message = { body: `Bulk ${id} phone ${phone} with ${length}, some messages is already scheduled status, try again later, this is ${count} try...` } + message = { + body: `Bulk ${id} phone ${phone} with ${length}, some messages is already scheduled status, try again later, this is ${count} try...`, + } await amqpPublish( UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BULK_REPORT, phone, { payload: { id, length, count } }, - { delay: UNOAPI_BULK_DELAY * 1000, type: 'topic' } + { delay: UNOAPI_BULK_DELAY * 1000, type: 'topic' }, ) } } else { @@ -54,7 +56,7 @@ export class BulkReportJob { message = { url: base64, mime_type: 'text/csv', - filename, + filename, caption, id: mediaKey, } diff --git a/src/jobs/bulk_sender.ts b/src/jobs/bulk_sender.ts index 4e2cc375..c4d3d4d0 100644 --- a/src/jobs/bulk_sender.ts +++ b/src/jobs/bulk_sender.ts @@ -1,5 +1,12 @@ import { amqpPublish } from '../amqp' -import { UNOAPI_BULK_BATCH, UNOAPI_BULK_DELAY, UNOAPI_QUEUE_BULK_SENDER, UNOAPI_QUEUE_BULK_REPORT, UNOAPI_BULK_MESSAGE_DELAY, UNOAPI_EXCHANGE_BROKER_NAME } from '../defaults' +import { + UNOAPI_BULK_BATCH, + UNOAPI_BULK_DELAY, + UNOAPI_QUEUE_BULK_SENDER, + UNOAPI_QUEUE_BULK_REPORT, + UNOAPI_BULK_MESSAGE_DELAY, + UNOAPI_EXCHANGE_BROKER_NAME, +} from '../defaults' import { Incoming } from '../services/incoming' import { Outgoing } from '../services/outgoing' import { setMessageStatus, setbulkMessage } from '../services/redis' @@ -64,9 +71,10 @@ export class BulkSenderJob { statusMessage = `Bulk ${id} phone ${phone} is finished with ${messagesToSend.length} message(s)!` await amqpPublish( UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_BULK_REPORT, phone, - { payload: { id, length } }, - { delay: UNOAPI_BULK_DELAY * 1000, type: 'topic' } + UNOAPI_QUEUE_BULK_REPORT, + phone, + { payload: { id, length } }, + { delay: UNOAPI_BULK_DELAY * 1000, type: 'topic' }, ) } const messageUpdate = { diff --git a/src/jobs/commander.ts b/src/jobs/commander.ts index 6670ae7c..2fdcc324 100644 --- a/src/jobs/commander.ts +++ b/src/jobs/commander.ts @@ -39,16 +39,18 @@ export class CommanderJob { logger.debug(`Commander processing`) const id = uuid() await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_BULK_PARSER, - phone, { + UNOAPI_EXCHANGE_BROKER_NAME, + UNOAPI_QUEUE_BULK_PARSER, + phone, + { phone, payload: { id, template: 'sisodonto', url: payload?.document?.link, }, - }, { type: 'topic' } + }, + { type: 'topic' }, ) const message = { type: 'text', @@ -73,7 +75,7 @@ export class CommanderJob { const config = { webhooks } logger.debug('Template webhooks %s', phone, JSON.stringify(webhooks)) await setConfig(phone, config) - await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, `${UNOAPI_QUEUE_RELOAD}.${currentConfig.server!}`, phone , { phone }, { type: 'topic' }) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, `${UNOAPI_QUEUE_RELOAD}.${currentConfig.server!}`, phone, { phone }, { type: 'topic' }) } else if (payload?.to && phone === payload?.to && payload?.template && payload?.template.name == 'unoapi-bulk-report') { logger.debug('Parsing bulk report template... %s', phone) const service = new Template(this.getConfig) @@ -85,7 +87,13 @@ export class CommanderJob { throw new YamlParseError(doc.errors) } const { bulk } = doc.toJS() - await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BULK_REPORT, phone, { payload: { phone, id: bulk, unverified: true } }, { type: 'topic' }) + await amqpPublish( + UNOAPI_EXCHANGE_BROKER_NAME, + UNOAPI_QUEUE_BULK_REPORT, + phone, + { payload: { phone, id: bulk, unverified: true } }, + { type: 'topic' }, + ) } else if (payload?.to && phone === payload?.to && payload?.template && payload?.template.name == 'unoapi-config') { logger.debug('Parsing config template... %s', phone) const service = new Template(this.getConfig) diff --git a/src/jobs/incoming.ts b/src/jobs/incoming.ts index 1c233621..49471f22 100644 --- a/src/jobs/incoming.ts +++ b/src/jobs/incoming.ts @@ -26,7 +26,7 @@ export class IncomingJob { const config = await this.getConfig(phone) if (config.server !== UNOAPI_SERVER_NAME) { logger.info(`Ignore incoming with ${phone} server ${config.server} is not server current server ${UNOAPI_SERVER_NAME}...`) - return; + return } // eslint-disable-next-line @typescript-eslint/no-explicit-any const a = data as any @@ -46,7 +46,7 @@ export class IncomingJob { await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, this.queueCommander, phone, { payload }, { type: 'topic' }) } const { ok, error } = response - const optionsOutgoing: Partial = {} + const optionsOutgoing: Partial = {} if (ok && ok.messages && ok.messages[0] && ok.messages[0].id) { const idProvider: string = ok.messages[0].id logger.debug('%s id %s to Unoapi id %s', config.provider, idProvider, idUno) @@ -64,7 +64,7 @@ export class IncomingJob { const mimetype = getMimetype(payload) const extension = mime.extension(mimetype) const fileName = `${mediaKey}.${extension}` - const response: Response = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET'}) + const response: Response = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) const buffer = toBuffer(await response.arrayBuffer()) await mediaStore.saveMediaBuffer(fileName, buffer) messagePayload = { @@ -176,13 +176,7 @@ export class IncomingJob { ], } } - await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_BULK_STATUS, - phone, - { payload: outgingPayload, type: 'whatsapp' }, - { type: 'topic' } - ) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BULK_STATUS, phone, { payload: outgingPayload, type: 'whatsapp' }, { type: 'topic' }) await Promise.all(config.webhooks.map((w) => this.outgoing.sendHttp(phone, w, outgingPayload, optionsOutgoing))) return response } diff --git a/src/jobs/listener.ts b/src/jobs/listener.ts index 765208de..41df09f6 100644 --- a/src/jobs/listener.ts +++ b/src/jobs/listener.ts @@ -17,15 +17,15 @@ export class ListenerJob { this.getConfig = getConfig } - async consume(phone: string, data: object, options?: { countRetries: number; maxRetries: number, priority: 0 }) { + async consume(phone: string, data: object, options?: { countRetries: number; maxRetries: number; priority: 0 }) { const config = await this.getConfig(phone) if (config.server !== UNOAPI_SERVER_NAME) { logger.info(`Ignore listener routing key ${phone} server ${config.server} is not server current server ${UNOAPI_SERVER_NAME}...`) - return; + return } if (config.provider !== 'baileys') { logger.info(`Ignore listener routing key ${phone} is not provider baileys...`) - return; + return } // eslint-disable-next-line @typescript-eslint/no-explicit-any const a = data as any @@ -50,21 +50,21 @@ export class ListenerJob { `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, phone, { messages: { keys: [m] }, type, splited: true }, - { type: 'direct' } + { type: 'direct' }, ) - }) + }), ) } else { - await Promise.all(messages. - map(async (m: object) => { + await Promise.all( + messages.map(async (m: object) => { return amqpPublish( UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, phone, { messages: [m], type, splited: true }, - { type: 'direct' } + { type: 'direct' }, ) - }) + }), ) } } diff --git a/src/jobs/outgoing.ts b/src/jobs/outgoing.ts index 2f74f573..5f1b7bdf 100644 --- a/src/jobs/outgoing.ts +++ b/src/jobs/outgoing.ts @@ -7,46 +7,48 @@ import { UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_OUTGOING, UNOAPI_QUEUE_TRANSCRIBER, - UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED + UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED, } from '../defaults' import { extractDestinyPhone, isAudioMessage, isIncomingMessage, jidToPhoneNumber, TYPE_MESSAGES_MEDIA } from '../services/transformer' import logger from '../services/logger' import { getConfig } from '../services/config' import { isUpdateMessage, isFailedStatus } from '../services/transformer' -const dUntil: Map = new Map() -const dVerified: Map = new Map() +const dUntil: Map = new Map() +const dVerified: 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) => { - const to = extractDestinyPhone(payload, false) - - if (to) { - const key = `${phone}:${to}` - if (!dVerified.get(key)) { - let nextMessageTime = dUntil.get(key) - const epochMS: number = Math.floor(Date.now()); - if (nextMessageTime === undefined) { - nextMessageTime = epochMS + UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS - dUntil.set(key, nextMessageTime); - logger.debug('Key %s First message', key) - } else { - const thisMessageDelay: number = Math.floor(nextMessageTime - epochMS) - if (thisMessageDelay > 0) { - logger.debug('Key %s Message delayed by %s ms', key, thisMessageDelay) - await sleep(thisMessageDelay) - } else { - logger.debug('Key %s doesn\'t need more delays', key) - dVerified.set(key, true); - dUntil.delete(key); +const delayFunc = UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS + ? async (phone, payload) => { + const to = extractDestinyPhone(payload, false) + + if (to) { + const key = `${phone}:${to}` + if (!dVerified.get(key)) { + let nextMessageTime = dUntil.get(key) + const epochMS: number = Math.floor(Date.now()) + if (nextMessageTime === undefined) { + nextMessageTime = epochMS + UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS + dUntil.set(key, nextMessageTime) + logger.debug('Key %s First message', key) + } else { + const thisMessageDelay: number = Math.floor(nextMessageTime - epochMS) + if (thisMessageDelay > 0) { + logger.debug('Key %s Message delayed by %s ms', key, thisMessageDelay) + await sleep(thisMessageDelay) + } else { + logger.debug("Key %s doesn't need more delays", key) + dVerified.set(key, true) + dUntil.delete(key) + } + } } - } + } } - } -} : async (_phone, _payload) => {} + : async (_phone, _payload) => {} export class OutgoingJob { private service: Outgoing @@ -64,28 +66,22 @@ export class OutgoingJob { if (a.webhooks) { const webhooks: Webhook[] = a.webhooks if (isFailedStatus(payload) && STATUS_FAILED_WEBHOOK_URL) { - await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED, - phone, - { payload }, - { type: 'topic' } - ) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_WEBHOOK_STATUS_FAILED, phone, { payload }, { type: 'topic' }) } await Promise.all( webhooks.map(async (webhook) => { - return amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_OUTGOING, phone, { payload, webhook }) + return amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_OUTGOING, phone, { payload, webhook }) }), ) if (isAudioMessage(payload)) { - const webhooks = a.webhooks.filter(w => { + const webhooks = a.webhooks.filter((w) => { if (w.sendTranscribeAudio) { logger.debug('Session phone %s webhook %s configured to send transcribe audio message for this webhook', phone, w.id) return true } }) if (webhooks.length > 0) { - await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TRANSCRIBER, phone, { payload, webhooks }, { type: 'topic' }) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TRANSCRIBER, phone, { payload, webhooks }, { type: 'topic' }) } } } else if (a.webhook) { @@ -97,25 +93,25 @@ export class OutgoingJob { const { dataStore } = store if (isUpdateMessage(payload)) { payload.entry[0].changes[0].value.statuses = await Promise.all( - payload.entry[0].changes[0].value.statuses.map(async status => { + payload.entry[0].changes[0].value.statuses.map(async (status) => { const currentId = status.id const unoId = await dataStore.loadUnoId(currentId) if (unoId) { status.id = unoId } return status - }) + }), ) } else { payload.entry[0].changes[0].value.contacts = await Promise.all( - payload.entry[0].changes[0].value.contacts.map(async contact => { + payload.entry[0].changes[0].value.contacts.map(async (contact) => { contact.wa_id = jidToPhoneNumber(contact.wa_id, '') return contact - }) + }), ) payload.entry[0].changes[0].value.messages = await Promise.all( - payload.entry[0].changes[0].value.messages.map(async message => { + payload.entry[0].changes[0].value.messages.map(async (message) => { if (TYPE_MESSAGES_MEDIA.includes(message.type)) { const { mediaStore } = store message = await mediaStore.saveMediaForwarder(message) @@ -134,7 +130,7 @@ export class OutgoingJob { } message.from = jidToPhoneNumber(message.from, '') return message - }) + }), ) } } diff --git a/src/jobs/reload.ts b/src/jobs/reload.ts index 85420b25..b3325c59 100644 --- a/src/jobs/reload.ts +++ b/src/jobs/reload.ts @@ -1,4 +1,4 @@ -import logger from '../services/logger'; +import logger from '../services/logger' import { Reload } from '../services/reload' export class ReloadJob { diff --git a/src/jobs/timer.ts b/src/jobs/timer.ts index c938045d..a15d96da 100644 --- a/src/jobs/timer.ts +++ b/src/jobs/timer.ts @@ -19,7 +19,7 @@ export class TimerJob { const string = await this.getLastTimerFunction(phone, to) const lastTime = string ? Date.parse(string) : undefined logger.debug('timer comsumer phone %s to %s time %s last time %s', phone, to, time, lastTime) - if (!lastTime || (lastTime > messageDate)) { + if (!lastTime || lastTime > messageDate) { logger.debug('timer comsumer expired phone %s to %s', phone, to) } else { logger.debug('timer consumer enqueue phone %s to %s', phone, to) @@ -28,8 +28,8 @@ export class TimerJob { to, type: 'text', text: { - body: message - } + body: message, + }, } await this.incoming.send(phone, body, {}) } diff --git a/src/jobs/transcriber.ts b/src/jobs/transcriber.ts index f3404470..51c9e9d9 100644 --- a/src/jobs/transcriber.ts +++ b/src/jobs/transcriber.ts @@ -22,20 +22,11 @@ export class TranscriberJob { const { payload, webhooks }: { payload: any; webhooks: Webhook[] } = data as any const destinyPhone = extractDestinyPhone(payload) const payloadEntry = payload?.entry && payload.entry[0] - const payloadValue = payloadEntry && - payload.entry[0].changes && - payload.entry[0].changes[0] && - payload.entry[0].changes[0].value - const audioMessage = payloadValue && - payload.entry[0].changes[0].value.messages && - payload.entry[0].changes[0].value.messages[0] + const payloadValue = payloadEntry && payload.entry[0].changes && payload.entry[0].changes[0] && payload.entry[0].changes[0].value + const audioMessage = payloadValue && payload.entry[0].changes[0].value.messages && payload.entry[0].changes[0].value.messages[0] const mediaKey = audioMessage.audio.id const mediaUrl = `${BASE_URL}/v13.0/${mediaKey}` - const { buffer, link }= await mediaToBuffer( - mediaUrl, - UNOAPI_AUTH_TOKEN!, - webhooks[0].timeoutMs || 0, - ) + const { buffer, link } = await mediaToBuffer(mediaUrl, UNOAPI_AUTH_TOKEN!, webhooks[0].timeoutMs || 0) let transcriptionText = '' if (OPENAI_API_KEY) { logger.debug('Transcriber audio with OpenAI for session %s to %s', phone, destinyPhone) @@ -50,9 +41,9 @@ export class TranscriberJob { } else { logger.debug('Transcriber audio with Audio2TextJS for session %s to %s', phone, destinyPhone) const converter = new Audio2TextJS({ - threads: 4, - processors: 1, - outputJson: true, + threads: 4, + processors: 1, + outputJson: true, }) if (!existsSync(SESSION_DIR)) { mkdirSync(SESSION_DIR) @@ -68,17 +59,19 @@ export class TranscriberJob { } logger.debug('Transcriber audio content for session %s and to %s is %s', phone, destinyPhone, transcriptionText) const output = { ...payload } - output.entry[0].changes[0].value.messages = [{ - context: { - message_id: audioMessage.id, - id: audioMessage.id, + output.entry[0].changes[0].value.messages = [ + { + context: { + message_id: audioMessage.id, + id: audioMessage.id, + }, + from: audioMessage.from, + id: uuid(), + text: { body: transcriptionText }, + type: 'text', + timestamp: `${parseInt(audioMessage.timestamp) + 1}`, }, - from: audioMessage.from, - id: uuid(), - text: { body: transcriptionText }, - type: 'text', - timestamp: `${parseInt(audioMessage.timestamp) + 1}`, - }] + ] await Promise.all( webhooks.map(async (w) => { logger.debug('Transcriber phone %s to %s sending webhook %s', phone, destinyPhone, w.id) diff --git a/src/jobs/webhook_status_failed.ts b/src/jobs/webhook_status_failed.ts index 317229a4..28b3139d 100644 --- a/src/jobs/webhook_status_failed.ts +++ b/src/jobs/webhook_status_failed.ts @@ -1,4 +1,4 @@ -import logger from "../services/logger"; +import logger from '../services/logger' export class WebhookStatusFailedJob { private url: string @@ -14,10 +14,10 @@ export class WebhookStatusFailedJob { await fetch(this.url, { method: 'POST', headers: { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ text }), - }); + }) } catch (error) { logger.error('Error on webhook status failed') logger.error(error) diff --git a/src/locales/en.json b/src/locales/en.json index 14da564d..55efcff4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,31 +1,32 @@ { - "without_whatsapp": "The phone number %s does not have Whatsapp account!", - "invalid_phone_number": "The phone number %s is invalid!", - "offline_session": "offline session, connecting....", - "disconnected_session": "disconnect number, please send a message do try reconnect and read qr code if necessary", + "without_whatsapp": "The phone number %s does not have Whatsapp account!", + "invalid_phone_number": "The phone number %s is invalid!", + "offline_session": "offline session, connecting....", + "disconnected_session": "disconnect number, please send a message do try reconnect and read qr code if necessary", "reloaded_session": "Session reloaded, send a message to connect again", - "connecting_session": "Wait a moment, connecting process", + "connecting_session": "Wait a moment, connecting process", "invalid_link": "Error on retrieve media, http status %s in link %s", - "attempts_exceeded": "The %s times of generate qrcode is exceeded!", - "received_pending_notifications": "Received pending notifications", - "online_session": "Online session", - "connection_timed_out": "Connecting %s timed out %s ms, change to disconnect", - "connecting": "Connecting...", - "connected": "Connected with %s using Whatsapp Version v%s, latest Baileys version is v%s at %s", - "removed": "The session is removed in Whatsapp App, send a message here to reconnect!", - "unique": "The session must be unique, close connection, send a message here to reconnect if him was offline!", - "closed": "The connection is closed with status: %s, detail: %s!", - "connecting_attemps": "Try connnecting time %s of %s...", - "qrcode_attemps": "Please, read the QR Code to connect on Whatsapp Web, attempt %s of %s", - "auto_restart": "Config to auto restart in %s milliseconds.", - "failed_decrypt": "🕒 The message could not be read. Please ask to send it again or open WhatsApp on your phone.", - "error": "Error -> %s.", - "on_read_qrcode": "Awesome, read the qrcode if you not yet. For now you need to update config to use this auth token %s", - "pairing_code": "Open your WhatsApp and go to: Connected Devices > Connect a new Device > Connect using phone number > And put your connection code > %s", + "attempts_exceeded": "The %s times of generate qrcode is exceeded!", + "received_pending_notifications": "Received pending notifications", + "online_session": "Online session", + "connection_timed_out": "Connecting %s timed out %s ms, change to disconnect", + "connecting": "Connecting...", + "connected": "Connected with %s using Whatsapp Version v%s, latest Baileys version is v%s at %s", + "removed": "The session is removed in Whatsapp App, send a message here to reconnect!", + "unique": "The session must be unique, close connection, send a message here to reconnect if him was offline!", + "closed": "The connection is closed with status: %s, detail: %s!", + "connecting_attemps": "Try connnecting time %s of %s...", + "qrcode_attemps": "Please, read the QR Code to connect on Whatsapp Web, attempt %s of %s", + "auto_restart": "Config to auto restart in %s milliseconds.", + "failed_decrypt": "🕒 The message could not be read. Please ask to send it again or open WhatsApp on your phone.", + "error": "Error -> %s.", + "on_read_qrcode": "Awesome, read the qrcode if you not yet. For now you need to update config to use this auth token %s", + "pairing_code": "Open your WhatsApp and go to: Connected Devices > Connect a new Device > Connect using phone number > And put your connection code > %s", "restart": "Restarting session", "standby": "Standby session, waiting for time configured to try connect again, %s error in %s seconds", "proxy_error": "Error on connect to proxy: %s", "session_conflict": "The session number is %s but the configured number %s", "waiting_information": "Waiting for qrcode/pairing code", - "reload": "Reload" -} \ No newline at end of file + "reload": "Reload", + "deleted_message": "Deleted message: " +} diff --git a/src/locales/pt.json b/src/locales/pt.json index e1fbf552..9f7deb4d 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -6,26 +6,27 @@ "reloaded_session": "Sessão terminada, envie uma mensagem para conectar novamente", "connecting_session": "Aguarde um momento, a estabelecer a sessão...", "invalid_link": "Houve um erro ao carregar o conteúdo, estado %s, link %s", - "attempts_exceeded": "O limite de %s vezes de geração de QR Code foi excedido!", - "received_pending_notifications": "A sincronizar mensagens enviadas enquanto estava offline", - "online_session": "Sessão estabelecida", - "connection_timed_out": "A sessão %s excedeu o tempo de %s ms, sessão alterada para estado Offline", - "connecting": "A estabelecer...", - "connected": "Sessão do número %s estabelecida, versão do WhatsApp v%s, a última versão do WhatsApp v%s, às %s", - "removed": "A sessão foi removida via aplicação WhatsApp, envie uma nova mensagem para gerar novo QR Code e voltar a estabelecer a ligação!", - "unique": "A sessão só pode ser estabelecida uma vez, a atual sessão está a ser desligada, envie uma mensagem para voltar a estabelecer a ligação!", - "closed": "A sessão foi encerrada com o estado: %s, detalhe: %s!", - "connecting_attemps": "Tentativa de estabelecimento de sessão %s de %s...", - "qrcode_attemps": "Por favor, leia QR Code para estabelecer a ligação, tentativa %s de %s", - "auto_restart": "Configurado para reiniciar a sessão a cada %s milliseconds.", - "failed_decrypt": "🕒 Não foi possível apresentar a mensagem. Peça para enviar novamente a mensagem ou abra a APP do Whatsapp para ver o conteúdo.", - "error": "Erro não previsto: %s.", - "on_read_qrcode": "Fantástico, se ainda não o fez, leia o QR Code. O token de authenticação para esta sessão é %s", - "pairing_code": "Informe o código para conectar no whatsapp > %s", + "attempts_exceeded": "O limite de %s vezes de geração de QR Code foi excedido!", + "received_pending_notifications": "A sincronizar mensagens enviadas enquanto estava offline", + "online_session": "Sessão estabelecida", + "connection_timed_out": "A sessão %s excedeu o tempo de %s ms, sessão alterada para estado Offline", + "connecting": "A estabelecer...", + "connected": "Sessão do número %s estabelecida, versão do WhatsApp v%s, a última versão do WhatsApp v%s, às %s", + "removed": "A sessão foi removida via aplicação WhatsApp, envie uma nova mensagem para gerar novo QR Code e voltar a estabelecer a ligação!", + "unique": "A sessão só pode ser estabelecida uma vez, a atual sessão está a ser desligada, envie uma mensagem para voltar a estabelecer a ligação!", + "closed": "A sessão foi encerrada com o estado: %s, detalhe: %s!", + "connecting_attemps": "Tentativa de estabelecimento de sessão %s de %s...", + "qrcode_attemps": "Por favor, leia QR Code para estabelecer a ligação, tentativa %s de %s", + "auto_restart": "Configurado para reiniciar a sessão a cada %s milliseconds.", + "failed_decrypt": "🕒 Não foi possível apresentar a mensagem. Peça para enviar novamente a mensagem ou abra a APP do Whatsapp para ver o conteúdo.", + "error": "Erro não previsto: %s.", + "on_read_qrcode": "Fantástico, se ainda não o fez, leia o QR Code. O token de authenticação para esta sessão é %s", + "pairing_code": "Informe o código para conectar no whatsapp > %s", "restart": "Reiniciando sessão", "standby": "Sessão colocada em standby, esperando pelo tempo configurado para tentar conectar novamente: %s", "proxy_error": "Erro ao conectar no proxy: %s", "session_conflict": "O número the sessão usado é o %s mas o número da configuração é %s", "waiting_information": "Esperando por qrcode/pairing code", - "reload": "Recarregar" -} \ No newline at end of file + "reload": "Recarregar", + "deleted_message": "Esta mensagem foi apagada: " +} diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 2eb4529e..c2f5497d 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -6,26 +6,27 @@ "reloaded_session": "Sessão terminada, envie uma mensagem para conectar novamente", "connecting_session": "Espere um momento, conectando a sessão...", "invalid_link": "Houve um erro ao recuperar a midia, status %s para o link %s", - "attempts_exceeded": "O limite de %s vezes de geração de qrcode foi excedida!", - "received_pending_notifications": "Recebendo mensagens enviadas enquanto estava offline", - "online_session": "Sessão está online", - "connection_timed_out": "A sessão %s excedeu o tempo de %s ms para conectar, sessão alterada para estado Offline", - "connecting": "Conectando...", - "connected": "Conectado com o número %s utilizando a versao do Whatsapp v%s, a última versão do WhatsApp v%s, às %s", - "removed": "A sessão foi removida no aplicativo do Whatsapp, envie uma mesagem para gerar o qrcode e conectar novamente!", - "unique": "A sessão só pode ser conectado uma vez, saindo da atual, envia uma mensagem para conectar novamente!", - "closed": "A sessão for encerrada com status: %s, detalhe: %s!", - "connecting_attemps": "Tentativa de conexão %s de %s...", - "qrcode_attemps": "Por favor, leia QR Code para conectar, tentativa %s de %s", - "auto_restart": "Configura para reiniciar a sessão a cada %s milliseconds.", - "failed_decrypt": "🕒 Não foi possível ler a mensagem. Peça para enviar novamente ou abra o Whatsapp no celular.", - "error": "Erro não tratado: %s.", - "on_read_qrcode": "Maravilha, leia o qrcode, se ainda não leu. O token de authenticação para essa sessão é %s", - "pairing_code": "Informe o código para conectar no whatsapp: %s", + "attempts_exceeded": "O limite de %s vezes de geração de qrcode foi excedida!", + "received_pending_notifications": "Recebendo mensagens enviadas enquanto estava offline", + "online_session": "Sessão está online", + "connection_timed_out": "A sessão %s excedeu o tempo de %s ms para conectar, sessão alterada para estado Offline", + "connecting": "Conectando...", + "connected": "Conectado com o número %s utilizando a versao do Whatsapp v%s, a última versão do WhatsApp v%s, às %s", + "removed": "A sessão foi removida no aplicativo do Whatsapp, envie uma mesagem para gerar o qrcode e conectar novamente!", + "unique": "A sessão só pode ser conectado uma vez, saindo da atual, envia uma mensagem para conectar novamente!", + "closed": "A sessão for encerrada com status: %s, detalhe: %s!", + "connecting_attemps": "Tentativa de conexão %s de %s...", + "qrcode_attemps": "Por favor, leia QR Code para conectar, tentativa %s de %s", + "auto_restart": "Configura para reiniciar a sessão a cada %s milliseconds.", + "failed_decrypt": "🕒 Não foi possível ler a mensagem. Peça para enviar novamente ou abra o Whatsapp no celular.", + "error": "Erro não tratado: %s.", + "on_read_qrcode": "Maravilha, leia o qrcode, se ainda não leu. O token de authenticação para essa sessão é %s", + "pairing_code": "Informe o código para conectar no whatsapp: %s", "restart": "Reiniciando sessão", "standby": "Sessão colocada em standby, esperando pelo tempo configurado para tentar conectar novamente: %s", "proxy_error": "Erro ao conectar no proxy: %s", "session_conflict": "O número the sessão usado é o %s mas o número da configuração é %s", "waiting_information": "Esperando por qrcode/pairing code", - "reload": "Recarregar" -} \ No newline at end of file + "reload": "Recarregar", + "deleted_message": "Esta mensagem foi apagada: " +} diff --git a/src/router.ts b/src/router.ts index b1662aa4..9d7e964d 100644 --- a/src/router.ts +++ b/src/router.ts @@ -30,7 +30,6 @@ import { ContactDummy } from './services/contact_dummy' import { middlewareNext } from './services/middleware_next' import { TimerController } from './controllers/timer_controller' - export const router = ( incoming: Incoming, outgoing: Outgoing, @@ -62,7 +61,6 @@ export const router = ( const connectController = new ConnectController(reload) const timerController = new TimerController() - // Webhook for forward connection router.post('/webhooks/whatsapp/:phone', webhookController.whatsapp.bind(webhookController)) router.get('/webhooks/whatsapp/:phone', middleware, webhookController.whatsappVerify.bind(webhookController)) diff --git a/src/services/auto_connect.ts b/src/services/auto_connect.ts index 6b1cc315..be82b4e7 100644 --- a/src/services/auto_connect.ts +++ b/src/services/auto_connect.ts @@ -22,22 +22,22 @@ export const autoConnect = async ( const config = await getConfig(phone) if (config.provider && !['forwarder', 'baileys'].includes(config.provider)) { logger.info(`Ignore connecting phone ${phone} provider ${config.provider}...`) - continue; + continue } if (config.server !== UNOAPI_SERVER_NAME) { logger.info(`Ignore connecting phone ${phone} server ${config.server} is not server current server ${UNOAPI_SERVER_NAME}...`) - continue; + continue } await sessionStore.syncConnection(phone) if (await sessionStore.isStatusStandBy(phone)) { logger.info(`Session standby ${phone}...`) - continue; + continue } logger.info(`Auto connecting phone ${phone}...`) try { const store = await config.getStore(phone, config) const { sessionStore } = store - if (await sessionStore.isStatusConnecting(phone) || await sessionStore.isStatusOnline(phone)) { + if ((await sessionStore.isStatusConnecting(phone)) || (await sessionStore.isStatusOnline(phone))) { logger.info(`Update session status to auto connect ${phone}...`) await sessionStore.setStatus(phone, 'offline') } diff --git a/src/services/blacklist.ts b/src/services/blacklist.ts index 09e110ad..c4c5128c 100644 --- a/src/services/blacklist.ts +++ b/src/services/blacklist.ts @@ -52,9 +52,9 @@ export const isInBlacklistInRedis: isInBlacklist = async (from: string, webhookI const pattern = `${blacklist('', '', '').replaceAll('::', '')}*` const keys = await redisKeys(pattern) logger.info(`Load ${keys.length} items in blacklist`) - const promises = keys.map(async key => { + const promises = keys.map(async (key) => { const ttl = await redisTtl(key) - const [ _k, from, webhookId, to ] = key.split(':') + const [_k, from, webhookId, to] = key.split(':') return addToBlacklistInMemory(from, webhookId, to, ttl) }) await Promise.all(promises) @@ -71,4 +71,4 @@ export const addToBlacklistRedis: addToBlacklist = async (from: string, webhookI export const addToBlacklistJob: addToBlacklist = async (from: string, webhookId: string, to: string, ttl: number) => { await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BLACKLIST_ADD, from, { from, webhookId, to, ttl }, { type: 'topic' }) return true -} \ No newline at end of file +} diff --git a/src/services/broadcast.ts b/src/services/broadcast.ts index dcf5df12..077816c5 100644 --- a/src/services/broadcast.ts +++ b/src/services/broadcast.ts @@ -14,4 +14,3 @@ export class Broadcast { await this.server.emit('broadcast', { phone, type, content }) } } - \ No newline at end of file diff --git a/src/services/client.ts b/src/services/client.ts index 61b4fbc1..ddee571f 100644 --- a/src/services/client.ts +++ b/src/services/client.ts @@ -5,7 +5,7 @@ import { Listener } from './listener' export const clients: Map = new Map() -export type ContactStatus = 'valid' | 'processing' | 'invalid'| 'failed' +export type ContactStatus = 'valid' | 'processing' | 'invalid' | 'failed' export interface Contact { wa_id: String | undefined @@ -14,17 +14,7 @@ export interface Contact { } export interface getClient { - ({ - phone, - listener, - getConfig, - onNewLogin, - }: { - phone: string - listener: Listener - getConfig: getConfig - onNewLogin: OnNewLogin - }): Promise + ({ phone, listener, getConfig, onNewLogin }: { phone: string; listener: Listener; getConfig: getConfig; onNewLogin: OnNewLogin }): Promise } export class ConnectionInProgress extends Error { @@ -37,7 +27,7 @@ export interface Client { connect(time: number): Promise disconnect(): Promise - + logout(): Promise // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/services/client_baileys.ts b/src/services/client_baileys.ts index 091e5a48..dd1f7f12 100644 --- a/src/services/client_baileys.ts +++ b/src/services/client_baileys.ts @@ -19,7 +19,14 @@ import { } from './socket' import { Client, getClient, clients, Contact } from './client' import { Config, configs, defaultConfig, getConfig, getMessageMetadataDefault } from './config' -import { toBaileysMessageContent, phoneNumberToJid, jidToPhoneNumber, getMessageType, TYPE_MESSAGES_TO_READ, TYPE_MESSAGES_MEDIA } from './transformer' +import { + toBaileysMessageContent, + phoneNumberToJid, + jidToPhoneNumber, + getMessageType, + TYPE_MESSAGES_TO_READ, + TYPE_MESSAGES_MEDIA, +} from './transformer' import { v1 as uuid } from 'uuid' import { Response } from './response' import QRCode from 'qrcode' @@ -30,6 +37,133 @@ import { t } from '../i18n' import { ClientForward } from './client_forward' import { SendError } from './send_error' +// Adicione esta classe antes da classe ClientBaileys +class PresignedLinkValidator { + private static isPresignedLink(url: string): boolean { + return url.includes('X-Amz-Algorithm') || url.includes('response-content-disposition') || url.includes('X-Amz-Signature') + } + + static async validateLink(url: string): Promise { + const isPresigned = this.isPresignedLink(url) + + if (!isPresigned) { + // Para links normais, tenta HEAD primeiro, depois GET como fallback + try { + const response = await fetch(url, { + signal: AbortSignal.timeout(10000), + method: 'HEAD', + }) + return response.ok + } catch (error) { + logger.warn(`Normal link HEAD failed, trying GET: ${error.message}`) + try { + const response = await fetch(url, { + signal: AbortSignal.timeout(10000), + method: 'GET', + headers: { + Range: 'bytes=0-0', + }, + }) + return response.ok + } catch (getError) { + logger.warn(`Normal link validation failed: ${getError.message}`) + return false + } + } + } + + // Para links pré-assinados, usa GET com Range para evitar problemas com HEAD + logger.info(`Detected presigned link, starting validation with GET Range: ${url}`) + + const maxAttempts = 40 + const baseDelay = 1500 + const maxDelay = 15000 + const totalTimeout = 8 * 60 * 1000 + + const startTime = Date.now() + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + if (Date.now() - startTime > totalTimeout) { + logger.warn(`Presigned link validation timeout after ${totalTimeout}ms: ${url}`) + throw new SendError(11, t('link_validation_timeout', url)) + } + + try { + logger.debug(`Validating presigned link attempt ${attempt}/${maxAttempts}: ${url}`) + + // Usa GET com Range para baixar apenas 1 byte (evita problema HEAD) + const response = await fetch(url, { + signal: AbortSignal.timeout(8000), + method: 'GET', + headers: { + Range: 'bytes=0-0', + 'User-Agent': 'UnoAPI/1.0', + Accept: '*/*', + 'Cache-Control': 'no-cache', + }, + }) + + // Status 206 (Partial Content) ou 200 (OK) indicam sucesso + if (response.ok || response.status === 206) { + logger.info(`Presigned link validated successfully on attempt ${attempt} (status: ${response.status}): ${url}`) + + // Consume o response body para evitar memory leak + try { + await response.text() + } catch (e) { + // Ignora erro ao consumir body + } + + return true + } + + // Para links pré-assinados, 403/404/416 podem ser temporários + if ([403, 404, 416, 502, 503].includes(response.status)) { + logger.debug(`Presigned link not ready (${response.status}), attempt ${attempt}/${maxAttempts}`) + + // Calcula delay inteligente + let delay = baseDelay + if (attempt <= 15) { + delay = baseDelay + } else if (attempt <= 25) { + delay = baseDelay * 1.5 + } else { + delay = Math.min(baseDelay * 2, maxDelay) + } + + if (attempt < maxAttempts) { + logger.debug(`Waiting ${delay}ms before next attempt...`) + await new Promise((resolve) => setTimeout(resolve, delay)) + } + continue + } + + // Outros códigos de erro são definitivos + logger.error(`Presigned link validation failed with status ${response.status}: ${url}`) + throw new SendError(11, t('invalid_link', response.status, url)) + } catch (error) { + if (error instanceof SendError) { + throw error + } + + // Erros de rede/timeout podem ser temporários + if (error.name === 'AbortError' || error.message.includes('timeout') || error.name === 'FetchError' || error.message.includes('ECONNRESET')) { + logger.debug(`Network error on attempt ${attempt}: ${error.message}`) + if (attempt < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, baseDelay)) + continue + } + } + + logger.error(`Unexpected error validating presigned link: ${error.message}`) + throw new SendError(11, t('link_validation_error', error.message)) + } + } + + throw new SendError(11, t('link_validation_failed_after_retries', maxAttempts, url)) + } +} + const attempts = 3 interface Delay { @@ -107,9 +241,9 @@ const closeDefault = async () => logger.info(`Close connection`) export class ClientBaileys implements Client { // eslint-disable-next-line @typescript-eslint/no-unused-vars readonly sendMessageDefault: sendMessage = async (_phone: string, _message: AnyMessageContent, _options: unknown) => { - const sessionStore = this?.phone && await (await this?.config?.getStore(this.phone, this.config)).sessionStore + const sessionStore = this?.phone && (await (await this?.config?.getStore(this.phone, this.config)).sessionStore) if (sessionStore) { - if (!await sessionStore.isStatusConnecting(this.phone)) { + if (!(await sessionStore.isStatusConnecting(this.phone))) { clients.delete(this.phone) } if (await sessionStore.isStatusOnline(this.phone)) { @@ -141,11 +275,7 @@ export class ClientBaileys implements Client { private onWebhookError = async (error: any) => { const { sessionStore } = this.store! if (!this.config.throwWebhookError && error.name === 'FetchError' && (await sessionStore.isStatusOnline(this.phone))) { - return this.sendMessage( - phoneNumberToJid(this.phone), - { text: `Error on send message to webhook: ${error.message}`}, - {} - ) + return this.sendMessage(phoneNumberToJid(this.phone), { text: `Error on send message to webhook: ${error.message}` }, {}) } if (this.config.throwWebhookError) { throw error @@ -198,7 +328,7 @@ export class ClientBaileys implements Client { remoteJid, id, } - const message = t('qrcode_attemps', time, limit) + const message = t('qrcode_attemps', time, limit) const waMessage: WAMessage = { key: waMessageKey, message: { @@ -278,7 +408,7 @@ export class ClientBaileys implements Client { onNewLogin: this.onNewLogin, config: this.config, onDisconnected: async () => this.disconnect(), - onReconnect: this.onReconnect + onReconnect: this.onReconnect, }) if (!result) { logger.error('Socket connect return empty %s', this.phone) @@ -334,7 +464,7 @@ export class ClientBaileys implements Client { }) .map(async (message: any) => { return this.readMessages([message.key!]) - }) + }), ) } }) @@ -366,7 +496,18 @@ export class ClientBaileys implements Client { this.calls.set(from, true) if (this.config.rejectCalls && this.rejectCall) { await this.rejectCall(id, from) - await this.sendMessage(from, { text: this.config.rejectCalls }, {}); + const response = await this.sendMessage(from, { text: this.config.rejectCalls }, {}) + const message = { + key: { + fromMe: true, + remoteJid: from, + id: response.key.id, + }, + message: { + conversation: this.config.rejectCalls, + }, + } + await this.listener.process(this.phone, [message], 'append') logger.info('Rejecting calls %s %s', this.phone, this.config.rejectCalls) } const messageCallsWebhook = this.config.rejectCallsWebhook || this.config.messageCallsWebhook @@ -459,12 +600,24 @@ export class ClientBaileys implements Client { const template = new Template(this.getConfig) content = await template.bind(this.phone, payload.template.name, payload.template.components) } else { + // Na função send(), substitua a validação existente: + // Na função send(), substitua toda a validação de mídia por: if (VALIDATE_MEDIA_LINK_BEFORE_SEND && TYPE_MESSAGES_MEDIA.includes(type)) { const link = payload[type] && payload[type].link + if (link) { - const response: FetchResponse = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'HEAD'}) - if (!response.ok) { - throw new SendError(11, t('invalid_link', response.status, link)) + logger.info(`Starting media link validation for ${type}: ${link}`) + + try { + await PresignedLinkValidator.validateLink(link) + logger.info(`Media link validation completed successfully for ${type}`) + } catch (error) { + logger.error(`Media link validation failed for ${type}: ${error.message}`) + + if (error instanceof SendError) { + throw error + } + throw new SendError(11, t('media_validation_error', error.message)) } } } @@ -557,7 +710,7 @@ export class ClientBaileys implements Client { if (ee.message == 'Media upload failed on all hosts') { const link = payload[type] && payload[type].link if (link) { - const response: FetchResponse = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'HEAD'}) + const response: FetchResponse = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'HEAD' }) if (!response.ok) { e = new SendError(11, t('invalid_link', response.status, link)) } @@ -631,7 +784,7 @@ export class ClientBaileys implements Client { } async getMessageMetadata(message: T) { - if (!this.store || !await this.store.sessionStore.isStatusOnline(this.phone)) { + if (!this.store || !(await this.store.sessionStore.isStatusOnline(this.phone))) { return message } const key = message && message['key'] @@ -702,7 +855,7 @@ export class ClientBaileys implements Client { contacts.push({ wa_id: realJid, input: number, - status: realJid ? 'valid' : 'invalid' + status: realJid ? 'valid' : 'invalid', }) } return contacts diff --git a/src/services/client_forward.ts b/src/services/client_forward.ts index f9d26686..fd855a7d 100644 --- a/src/services/client_forward.ts +++ b/src/services/client_forward.ts @@ -1,7 +1,7 @@ -import { Client, Contact } from './client'; -import { getConfig } from './config'; -import { Listener } from './listener'; -import logger from './logger'; +import { Client, Contact } from './client' +import { getConfig } from './config' +import { Listener } from './listener' +import logger from './logger' export class ClientForward implements Client { private phone: string @@ -19,7 +19,7 @@ export class ClientForward implements Client { const body = JSON.stringify(payload) const headers = { 'Content-Type': 'application/json; charset=utf-8', - 'Authorization': `Bearer ${config.webhookForward.token}` + Authorization: `Bearer ${config.webhookForward.token}`, } const endpoint = options.endpoint && payload.type ? options.endpoint : 'messages' const url = `${config.webhookForward.url}/${config.webhookForward.version}/${config.webhookForward.phoneNumberId}/${endpoint}` @@ -46,10 +46,10 @@ export class ClientForward implements Client { public async connect(_time: number) { const message = { message: { - conversation: 'Starting unoapi forwarder......' - } + conversation: 'Starting unoapi forwarder......', + }, } - return this.listener.process(this.phone, [message] , 'status') + return this.listener.process(this.phone, [message], 'status') } public getMessageMetadata(_message: T): Promise { @@ -63,7 +63,7 @@ export class ClientForward implements Client { public async disconnect() { throw 'ClientCloudApi not disconnect' } - + public async logout() { throw 'ClientCloudApi not logout' } diff --git a/src/services/config.ts b/src/services/config.ts index f1de9a3a..ac6e260a 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -14,7 +14,7 @@ export interface GetMessageMetadata { export const getMessageMetadataDefault: GetMessageMetadata = async (data: T) => data export type Webhook = { - id: string, + id: string url: string urlAbsolute: string token: string @@ -73,17 +73,17 @@ export type Config = { authToken: string | undefined authHeader: string | undefined provider: 'baileys' | 'forwarder' | undefined - server: string | undefined + server: string | undefined connectionType: connectionType - wavoipToken: string | undefined + wavoipToken: string | undefined useRedis: boolean useS3: boolean qrTimeoutMs: number label: string overrideWebhooks: boolean customMessageCharacters: string[] - customMessageCharactersFunction: (message: string) => string, - whatsappVersion: WAVersion | undefined, + customMessageCharactersFunction: (message: string) => string + whatsappVersion: WAVersion | undefined } export const defaultConfig: Config = { @@ -149,7 +149,7 @@ export const defaultConfig: Config = { overrideWebhooks: false, customMessageCharacters: [], customMessageCharactersFunction: (message: string) => message, - whatsappVersion: undefined + whatsappVersion: undefined, } export interface getConfig { diff --git a/src/services/config_by_env.ts b/src/services/config_by_env.ts index da66acf5..a046238d 100644 --- a/src/services/config_by_env.ts +++ b/src/services/config_by_env.ts @@ -116,7 +116,7 @@ export const getConfigByEnv: getConfig = async (phone: string): Promise if (config.customMessageCharacters.length > 0) { const getRandomChar = () => { - const randomIndex = Math.floor(Math.random() * config.customMessageCharacters.length); + const randomIndex = Math.floor(Math.random() * config.customMessageCharacters.length) return config.customMessageCharacters[randomIndex] } config.customMessageCharactersFunction = (message: string) => { diff --git a/src/services/config_redis.ts b/src/services/config_redis.ts index 186642fd..1542f855 100644 --- a/src/services/config_redis.ts +++ b/src/services/config_redis.ts @@ -24,11 +24,11 @@ export const getConfigRedis: getConfig = async (phone: string): Promise // override by env, if not present in redis webhook[keyWebhook] = config.webhooks[0][keyWebhook] } - }); + }) webhooks.push(webhook) }) configRedis[key] = webhooks - } else if (key === 'webhookForward'){ + } else if (key === 'webhookForward') { const webhookForward = configRedis[key] Object.keys(configRedis[key]).forEach((k) => { if (!webhookForward[k]) { @@ -37,15 +37,15 @@ export const getConfigRedis: getConfig = async (phone: string): Promise }) configRedis[key] = webhookForward } - logger.debug('Override env config by redis config in %s: %s => %s', phone, key, JSON.stringify(configRedis[key])); - config[key] = configRedis[key]; + logger.debug('Override env config by redis config in %s: %s => %s', phone, key, JSON.stringify(configRedis[key])) + config[key] = configRedis[key] } - }); + }) } config.server = config.server || 'server_1' config.provider = config.provider || 'baileys' - + const filter: MessageFilter = new MessageFilter(phone, config) config.shouldIgnoreJid = filter.isIgnoreJid.bind(filter) config.shouldIgnoreKey = filter.isIgnoreKey.bind(filter) diff --git a/src/services/contact_baileys.ts b/src/services/contact_baileys.ts index 2505d090..62a6fdcb 100644 --- a/src/services/contact_baileys.ts +++ b/src/services/contact_baileys.ts @@ -29,7 +29,7 @@ export default class ContactBaileys implements Contact { if (webhook) { const body = JSON.stringify({ contacts }) const headers = { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'application/json; charset=utf-8', } let response: Response try { @@ -47,4 +47,4 @@ export default class ContactBaileys implements Contact { } return { contacts } } -} \ No newline at end of file +} diff --git a/src/services/contact_dummy.ts b/src/services/contact_dummy.ts index d3f7056c..6da317cf 100644 --- a/src/services/contact_dummy.ts +++ b/src/services/contact_dummy.ts @@ -1,7 +1,7 @@ -import { Contact, ContactResponse } from './contact'; +import { Contact, ContactResponse } from './contact' export class ContactDummy implements Contact { public async verify(_phone: String, _numbers: String[], webhook: string | undefined) { return { contacts: [] } as ContactResponse } -} \ No newline at end of file +} diff --git a/src/services/data_store.ts b/src/services/data_store.ts index dfd24860..46139a25 100644 --- a/src/services/data_store.ts +++ b/src/services/data_store.ts @@ -7,20 +7,21 @@ export interface getDataStore { (phone: string, config: Config): Promise } -export type MessageStatus = 'scheduled' - | 'pending' - | 'without-whatsapp' - | 'invalid-phone-number' - | 'error' - | 'failed' - | 'sent' - | 'delivered' - | 'read' - | 'played' - | 'accepted' - | 'deleted' +export type MessageStatus = + | 'scheduled' + | 'pending' + | 'without-whatsapp' + | 'invalid-phone-number' + | 'error' + | 'failed' + | 'sent' + | 'delivered' + | 'read' + | 'played' + | 'accepted' + | 'deleted' -export type DataStore = { +export type DataStore = { state: AuthenticationState saveCreds: () => Promise type: string diff --git a/src/services/data_store_file.ts b/src/services/data_store_file.ts index f04288ae..474ad972 100644 --- a/src/services/data_store_file.ts +++ b/src/services/data_store_file.ts @@ -1,11 +1,4 @@ -import { - proto, - WAMessage, - WAMessageKey, - WASocket, - useMultiFileAuthState, - GroupMetadata -} from 'baileys' +import { proto, WAMessage, WAMessageKey, WASocket, useMultiFileAuthState, GroupMetadata } from 'baileys' import { isIndividualJid, jidToPhoneNumber, phoneNumberToJid } from './transformer' import { existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'fs' import { DataStore, MessageStatus } from './data_store' @@ -31,17 +24,17 @@ export const getDataStoreFile: getDataStore = async (phone: string, config: Conf } const deepMerge = (obj1, obj2) => { - const result = { ...obj1 }; + const result = { ...obj1 } for (let key in obj2) { if (obj2.hasOwnProperty(key)) { if (obj2[key] instanceof Object && obj1[key] instanceof Object) { - result[key] = deepMerge(obj1[key], obj2[key]); + result[key] = deepMerge(obj1[key], obj2[key]) } else { - result[key] = obj2[key]; + result[key] = obj2[key] } } } - return result; + return result } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -56,22 +49,21 @@ const dataStoreFile = async (phone: string, config: Config): Promise const store = await useMultiFileAuthState(SESSION_DIR) const dataStore = store as DataStore dataStore.type = 'file' - - dataStore.loadMessage = async(jid: string, id: string) => messages.get(`${jid}-${id}`), - dataStore.toJSON = () => { - return { - messages, - keys, - jids, - ids, - statuses, - groups: groups.keys().reduce((acc, key) => { + ;((dataStore.loadMessage = async (jid: string, id: string) => messages.get(`${jid}-${id}`)), + (dataStore.toJSON = () => { + return { + messages, + keys, + jids, + ids, + statuses, + groups: groups.keys().reduce((acc, key) => { acc.set(key, groups.get(key)) return acc }, new Map()), - medias, - } - } + medias, + } + })) dataStore.fromJSON = (json) => { json?.messages.entries().forEach(([key, value]) => { messages.set(key, value) @@ -95,7 +87,7 @@ const dataStoreFile = async (phone: string, config: Config): Promise medias.set(key, value) }) } - dataStore.writeToFile = (path: string) => { + dataStore.writeToFile = (path: string) => { const { writeFileSync } = require('fs') // for(const a in Object.keys(dataStore.toJSON())) { // console.log(a) @@ -104,7 +96,7 @@ const dataStoreFile = async (phone: string, config: Config): Promise } dataStore.readFromFile = (path: string) => { const { readFileSync, existsSync } = require('fs') - if(existsSync(path)) { + if (existsSync(path)) { logger.debug({ path }, 'reading from file') const jsonStr = readFileSync(path, { encoding: 'utf-8' }) const json = JSON.parse(jsonStr) @@ -170,7 +162,7 @@ const dataStoreFile = async (phone: string, config: Config): Promise return statuses.get(id) } - dataStore.loadUnoId = async (id: string) => ids.get(id) || ids.get(`${phone}-${id}`) + dataStore.loadUnoId = async (id: string) => ids.get(id) || ids.get(`${phone}-${id}`) dataStore.setUnoId = async (id: string, unoId: string) => { ids.set(`${phone}-${id}`, unoId) } @@ -195,9 +187,7 @@ const dataStoreFile = async (phone: string, config: Config): Promise } else if ('status@broadcast' == phoneOrJid) { return phoneOrJid } - } catch (error) { - - } + } catch (error) {} } const result = results && results[0] const test = result && result?.exists && result?.jid diff --git a/src/services/data_store_redis.ts b/src/services/data_store_redis.ts index d3a80b06..81f674cc 100644 --- a/src/services/data_store_redis.ts +++ b/src/services/data_store_redis.ts @@ -136,7 +136,7 @@ const dataStoreRedis = async (phone: string, config: Config): Promise ], } - if(!ONLY_HELLO_TEMPLATE) { + if (!ONLY_HELLO_TEMPLATE) { const bulkReport = { id: 2, name: 'unoapi-bulk-report', @@ -213,7 +213,6 @@ const dataStoreRedis = async (phone: string, config: Config): Promise } else { return [hello] } - } } return store diff --git a/src/services/incoming_amqp.ts b/src/services/incoming_amqp.ts index 70fe2dba..efb57282 100644 --- a/src/services/incoming_amqp.ts +++ b/src/services/incoming_amqp.ts @@ -15,17 +15,11 @@ export class IncomingAmqp implements Incoming { public async send(phone: string, payload: object, options: object = {}) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { status, type, to } = payload as any - const config = await this.getConfig(phone); + const config = await this.getConfig(phone) if (status) { options['type'] = 'direct' options['priority'] = 3 // update status is always middle important - await amqpPublish( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_INCOMING}.${config.server!}`, - phone, - { payload, options }, - options - ) + await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_INCOMING}.${config.server!}`, phone, { payload, options }, options) return { ok: { success: true } } } else if (type) { const id = uuid() @@ -33,13 +27,7 @@ export class IncomingAmqp implements Incoming { options['priority'] = 5 // send message without bulk is very important } options['type'] = 'direct' - await amqpPublish( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_INCOMING}.${config.server!}`, - phone, - { payload, id, options }, - options - ) + await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_INCOMING}.${config.server!}`, phone, { payload, id, options }, options) const ok = { messaging_product: 'whatsapp', contacts: [ diff --git a/src/services/listener_amqp.ts b/src/services/listener_amqp.ts index 192db657..f9017b3f 100644 --- a/src/services/listener_amqp.ts +++ b/src/services/listener_amqp.ts @@ -3,22 +3,22 @@ import { PublishOption, amqpPublish } from '../amqp' import { UNOAPI_EXCHANGE_BRIDGE_NAME, UNOAPI_QUEUE_LISTENER, UNOAPI_SERVER_NAME } from '../defaults' const priorities = { - 'qrcode': 5, - 'status': 3, - 'history': 0, - 'append': 5, - 'notify': 5, - 'message': 5, - 'update': 3, - 'delete': 3, + qrcode: 5, + status: 3, + history: 0, + append: 5, + notify: 5, + message: 5, + update: 3, + delete: 3, } const delay = new Map() const delays = { - 'qrcode': _ => 0, - 'status': _ => 0, - 'history': (phone: string) => { + qrcode: (_) => 0, + status: (_) => 0, + history: (phone: string) => { const current = delay.get(phone) if (current) { delay.set(phone, current + 1000) @@ -28,26 +28,19 @@ const delays = { return 0 } }, - 'append': _ => 0, - 'notify': _ => 0, - 'message': _ => 0, - 'update': _ => 0, - 'delete': _ => 0, + append: (_) => 0, + notify: (_) => 0, + message: (_) => 0, + update: (_) => 0, + delete: (_) => 0, } - export class ListenerAmqp implements Listener { public async process(phone: string, messages: object[], type: eventType) { const options: Partial = {} options.priority = options.priority || priorities[type] || 5 options.delay = options.delay || delays[type](phone) || 0 options.type = 'direct' - await amqpPublish( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, - phone, - { messages, type }, - options - ) + await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_LISTENER}.${UNOAPI_SERVER_NAME}`, phone, { messages, type }, options) } } diff --git a/src/services/listener_baileys.ts b/src/services/listener_baileys.ts index 13eee040..b7ba2bd9 100644 --- a/src/services/listener_baileys.ts +++ b/src/services/listener_baileys.ts @@ -3,30 +3,36 @@ import logger from './logger' import { Outgoing } from './outgoing' import { Broadcast } from './broadcast' import { getConfig } from './config' -import { fromBaileysMessageContent, getMessageType, BindTemplateError, isSaveMedia } from './transformer' +import { fromBaileysMessageContent, getMessageType, BindTemplateError, isSaveMedia, getBinMessage, getNormalizedMessage } from './transformer' +import { t } from '../i18n' import { WAMessage, delay } from 'baileys' import { Template } from './template' import { UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS, UNOAPI_DELAY_BETWEEN_MESSAGES_MS } from '../defaults' import { v1 as uuid } from 'uuid' -const delays: Map = new Map() +const delays: Map = new Map() -const delayFunc = UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS && UNOAPI_DELAY_BETWEEN_MESSAGES_MS ? async (phone, to) => { - if (to) { - const key = `${phone}:${to}` - const epochMS: number = Math.floor(Date.now()); - const lastMessage = (delays.get(key) || 0) as number - const timeForNextMessage = lastMessage ? Math.floor(lastMessage + (UNOAPI_DELAY_BETWEEN_MESSAGES_MS)) : Math.floor(epochMS + (UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS)) - const ms = timeForNextMessage - epochMS > 0 ? Math.floor((timeForNextMessage - epochMS)) : 0; - logger.debug(`Delay for this message is: %s`, ms) - if (ms) { - delays.set(key, timeForNextMessage) - await delay(ms) - } else { - delays.set(key, epochMS) - } - } -} : async (_phone, _to) => {} +const delayFunc = + UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS && UNOAPI_DELAY_BETWEEN_MESSAGES_MS + ? async (phone, to) => { + if (to) { + const key = `${phone}:${to}` + const epochMS: number = Math.floor(Date.now()) + const lastMessage = (delays.get(key) || 0) as number + const timeForNextMessage = lastMessage + ? Math.floor(lastMessage + UNOAPI_DELAY_BETWEEN_MESSAGES_MS) + : Math.floor(epochMS + UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS) + const ms = timeForNextMessage - epochMS > 0 ? Math.floor(timeForNextMessage - epochMS) : 0 + logger.debug(`Delay for this message is: %s`, ms) + if (ms) { + delays.set(key, timeForNextMessage) + await delay(ms) + } else { + delays.set(key, epochMS) + } + } + } + : async (_phone, _to) => {} export class ListenerBaileys implements Listener { private outgoing: Outgoing @@ -41,13 +47,36 @@ export class ListenerBaileys implements Listener { async process(phone: string, messages: object[], type: eventType) { logger.debug('Received %s(s) %s', type, messages.length, phone) + const config = await this.getConfig(phone) if (type == 'delete' && messages.keys) { + const store = await config.getStore(phone, config) // eslint-disable-next-line @typescript-eslint/no-explicit-any - messages = (messages.keys as any).map((key: any) => { - return { key, update: { status: 'DELETED' } } - }) + messages = await Promise.all( + (messages.keys as any).map(async (key: any) => { + if (key.fromMe) { + return { key, update: { status: 'DELETED' } } + } + const original = await store.dataStore.loadMessage(key.remoteJid!, key.id) + let originalText = '' + if (original) { + const normalized = getNormalizedMessage(original) + const bin = normalized && getBinMessage(normalized) + if (bin) { + if (bin.messageType === 'conversation') { + originalText = bin.message as string + } else if (bin.messageType === 'extendedTextMessage') { + originalText = bin.message.text + } else if (bin.message && bin.message.caption) { + originalText = bin.message.caption + } + } + } + const text = originalText ? `${t('deleted_message')}${originalText}` : t('deleted_message') + return { key, message: { editedMessage: { message: { conversation: text } } } } + }), + ) + type = 'update' } - const config = await this.getConfig(phone) if (type === 'append' && !config.ignoreOwnMessages) { // filter self message send with this session to not send same message many times // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -57,22 +86,14 @@ export class ListenerBaileys implements Listener { return } } else if (type == 'qrcode') { - await this.broadcast.send( - phone, - type, - messages[0]['message']['imageMessage']['url'] - ) + await this.broadcast.send(phone, type, messages[0]['message']['imageMessage']['url']) // await this.broadcast.send( // phone, // 'status', // messages[0]['message']['imageMessage']['caption'] // ) - } else if(type === 'status') { - await this.broadcast.send( - phone, - type, - messages[0]['message']['conversation'] - ) + } else if (type === 'status') { + await this.broadcast.send(phone, type, messages[0]['message']['conversation']) } // eslint-disable-next-line @typescript-eslint/no-explicit-any const filteredMessages = messages.filter((m: any) => { @@ -112,7 +133,7 @@ export class ListenerBaileys implements Listener { const key = i.key // possible update message or delete message - if (key?.id && (key?.fromMe || (!key?.fromMe && ((message as any)?.update?.messageStubType == 1)))) { + if (key?.id && (key?.fromMe || (!key?.fromMe && (message as any)?.update?.messageStubType == 1))) { const idUno = await store.dataStore.loadUnoId(key.id) logger.debug('Unoapi id %s to Baileys id %s', idUno, key.id) if (idUno) { @@ -180,4 +201,4 @@ export class ListenerBaileys implements Listener { logger.debug(`Not send message type ${messageType} to http phone %s message id %s`, phone, i?.key?.id) } } -} \ No newline at end of file +} diff --git a/src/services/logout_amqp.ts b/src/services/logout_amqp.ts index 8073af46..e06b4ade 100644 --- a/src/services/logout_amqp.ts +++ b/src/services/logout_amqp.ts @@ -10,14 +10,8 @@ export class LogoutAmqp implements Logout { this.getConfig = getConfig } - public async run(phone: string) { + public async run(phone: string) { const config = await this.getConfig(phone) - await amqpPublish( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_LOGOUT}.${config.server!}`, - '', - { phone }, - { type: 'direct' } - ) + await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_LOGOUT}.${config.server!}`, '', { phone }, { type: 'direct' }) } } diff --git a/src/services/media_store_file.ts b/src/services/media_store_file.ts index c5221ec0..79b813ed 100644 --- a/src/services/media_store_file.ts +++ b/src/services/media_store_file.ts @@ -40,7 +40,7 @@ export const mediaStoreFile = (phone: string, config: Config, getDataStore: getD mediaStore.saveMediaForwarder = async (message: any) => { const filePath = mediaStore.getFilePath(phone, message.id, message[message.type].mime_type) - const url = `${config.webhookForward.url}/${config.webhookForward.version}/${ message[message.type].id}` + const url = `${config.webhookForward.url}/${config.webhookForward.version}/${message[message.type].id}` const { buffer } = await mediaToBuffer(url, config.webhookForward.token!, config.webhookForward?.timeoutMs || 0) logger.debug('Saving buffer %s...', filePath) await mediaStore.saveMediaBuffer(filePath, buffer) @@ -175,7 +175,7 @@ export const mediaStoreFile = (phone: string, config: Config, getDataStore: getD if (!existsSync(base)) { mkdirSync(base, { recursive: true }) } - const response: FetchResponse = await fetch(contact.imgUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET'}) + const response: FetchResponse = await fetch(contact.imgUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) const buffer = toBuffer(await response.arrayBuffer()) await writeFile(complete, buffer) logger.debug('Saved profile picture file %s!!', phoneNumber) diff --git a/src/services/media_store_s3.ts b/src/services/media_store_s3.ts index 4d3d4483..52820752 100644 --- a/src/services/media_store_s3.ts +++ b/src/services/media_store_s3.ts @@ -13,7 +13,6 @@ import { Config } from './config' import logger from './logger' import fetch, { Response as FetchResponse } from 'node-fetch' - export const getMediaStoreS3: getMediaStore = (phone: string, config: Config, getDataStore: getDataStore): MediaStore => { if (!mediaStores.has(phone)) { logger.debug('Creating s3 data store %s', phone) @@ -45,13 +44,7 @@ export const mediaStoreS3 = (phone: string, config: Config, getDataStore: getDat const abortSignal = AbortSignal.timeout(s3Config.timeoutMs) await s3Client.send(new PutObjectCommand(putParams), { abortSignal }) logger.debug(`Uploaded file ${fileName} to bucket ${bucket}!`) - await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_MEDIA, - phone, - { fileName: fileName }, - { delay: DATA_TTL * 1000, type: 'topic' } - ) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_MEDIA, phone, { fileName: fileName }, { delay: DATA_TTL * 1000, type: 'topic' }) return true } @@ -65,9 +58,7 @@ export const mediaStoreS3 = (phone: string, config: Config, getDataStore: getDat const link = await getSignedUrl(s3Client, command, { expiresIn }) return link } catch (error) { - logger.error( - `Error on generate s3 signed url for bucket: ${bucket} file name: ${fileName} expires in: ${expiresIn} -> ${error.message}` - ) + logger.error(`Error on generate s3 signed url for bucket: ${bucket} file name: ${fileName} expires in: ${expiresIn} -> ${error.message}`) throw error } } @@ -90,7 +81,7 @@ export const mediaStoreS3 = (phone: string, config: Config, getDataStore: getDat logger.debug(`Downloaded media ${file}!`) return response.Body as Readable } - + mediaStore.getProfilePictureUrl = async (_baseUrl: string, jid: string) => { const phoneNumber = jidToPhoneNumberIfUser(jid) const fileName = `${phone}/${PROFILE_PICTURE_FOLDER}/${profilePictureFileName(phoneNumber)}` @@ -114,7 +105,7 @@ export const mediaStoreS3 = (phone: string, config: Config, getDataStore: getDat await mediaStore.removeMedia(fileName) } else if (contact.imgUrl) { logger.debug('Saving profile picture s3 %s...', phoneNumber) - const response: FetchResponse = await fetch(contact.imgUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET'}) + const response: FetchResponse = await fetch(contact.imgUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) const buffer = toBuffer(await response.arrayBuffer()) await mediaStore.saveMediaBuffer(fileName, buffer) logger.debug('Saved profile picture s3 %s!', phoneNumber) diff --git a/src/services/outgoing_amqp.ts b/src/services/outgoing_amqp.ts index 1f9c3024..d67fdf7a 100644 --- a/src/services/outgoing_amqp.ts +++ b/src/services/outgoing_amqp.ts @@ -20,9 +20,10 @@ export class OutgoingAmqp implements Outgoing { const config = await this.getConfig(phone) await amqpPublish( UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_OUTGOING, phone, + UNOAPI_QUEUE_OUTGOING, + phone, { webhooks: config.webhooks, payload, split: true }, - { type: 'topic' } + { type: 'topic' }, ) } diff --git a/src/services/outgoing_cloud_api.ts b/src/services/outgoing_cloud_api.ts index 87351ecf..2d8310c6 100644 --- a/src/services/outgoing_cloud_api.ts +++ b/src/services/outgoing_cloud_api.ts @@ -54,7 +54,7 @@ export class OutgoingCloudApi implements Outgoing { } const body = JSON.stringify(message) const headers = { - 'Content-Type': 'application/json; charset=utf-8' + 'Content-Type': 'application/json; charset=utf-8', } if (webhook.header && webhook.token) { headers[webhook.header] = webhook.token diff --git a/src/services/redis.ts b/src/services/redis.ts index 94d25388..891f08f3 100644 --- a/src/services/redis.ts +++ b/src/services/redis.ts @@ -253,7 +253,7 @@ export const getTemplates = async (phone: string) => { export const setTemplates = async (phone: string, value: any) => { const { id } = value if (!id) { - throw new Error(`New template has no ID or an invalid format`); + throw new Error(`New template has no ID or an invalid format`) } const current = (await getTemplates(phone)) || {} const key = templateKey(phone) @@ -267,11 +267,11 @@ export const setTemplates = async (phone: string, value: any) => { } } else { config = [] - current.forEach(element => { + current.forEach((element) => { if (element.id !== id) { config.push(element) } - }); + }) config.push(value) } } @@ -292,12 +292,12 @@ export const getConfig = async (phone: string) => { export const setConfig = async (phone: string, value: any) => { const currentConfig = await getConfig(phone) const key = configKey(phone) - const currentWebhooks: Webhook[] = currentConfig && currentConfig.webhooks || [] - const newWebhooks: Webhook[] = value && value.webhooks || [] + const currentWebhooks: Webhook[] = (currentConfig && currentConfig.webhooks) || [] + const newWebhooks: Webhook[] = (value && value.webhooks) || [] const updatedWebooks: Webhook[] = [] const baseWebhook = value.overrideWebhooks || currentWebhooks.length == 0 ? newWebhooks : currentWebhooks const searchWebhooks = value.overrideWebhooks ? currentWebhooks : newWebhooks - baseWebhook.forEach(n => { + baseWebhook.forEach((n) => { const c = searchWebhooks.find((c) => c.id === n.id) if (c) { updatedWebooks.push({ ...c, ...n }) @@ -398,17 +398,17 @@ export const getMessage = async (phone: string, jid: string, id: string): Pro } } -export const getConnectCount = async(phone: string) => { +export const getConnectCount = async (phone: string) => { const keyPattern = connectCountKey(phone, '*') const keys = await redisKeys(keyPattern) return keys.length || 0 } -export const clearConnectCount = async(phone: string) => { +export const clearConnectCount = async (phone: string) => { const keyPattern = connectCountKey(phone, '*') const keys = await redisKeys(keyPattern) for (let index = 0; index < keys.length.length; index++) { - const key = keys[index]; + const key = keys[index] await redisDel(key) } } diff --git a/src/services/reload_amqp.ts b/src/services/reload_amqp.ts index bbd13692..79c74df8 100644 --- a/src/services/reload_amqp.ts +++ b/src/services/reload_amqp.ts @@ -13,19 +13,7 @@ export class ReloadAmqp extends Reload { public async run(phone: string) { const config = await this.getConfig(phone) - await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_RELOAD, - phone, - { phone }, - { type: 'topic' } - ) - await amqpPublish( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_RELOAD}.${config.server!}`, - '', - { phone }, - { type: 'direct' } - ) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_RELOAD, phone, { phone }, { type: 'topic' }) + await amqpPublish(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_RELOAD}.${config.server!}`, '', { phone }, { type: 'direct' }) } } diff --git a/src/services/reload_baileys.ts b/src/services/reload_baileys.ts index 2ea0b7f9..af9634e6 100644 --- a/src/services/reload_baileys.ts +++ b/src/services/reload_baileys.ts @@ -35,7 +35,7 @@ export class ReloadBaileys extends Reload { }) const store = await config.getStore(phone, config) const { sessionStore } = store - if (await sessionStore.isStatusOnline(phone) || await sessionStore.isStatusStandBy(phone) || await sessionStore.isStatusConnecting(phone)) { + if ((await sessionStore.isStatusOnline(phone)) || (await sessionStore.isStatusStandBy(phone)) || (await sessionStore.isStatusConnecting(phone))) { logger.warn('Reload disconnect session %s!', phone) await currentClient.disconnect() } diff --git a/src/services/send_error.ts b/src/services/send_error.ts index f6c902b9..2ef15d61 100644 --- a/src/services/send_error.ts +++ b/src/services/send_error.ts @@ -6,4 +6,4 @@ export class SendError extends Error { this.code = code this.title = title } -} \ No newline at end of file +} diff --git a/src/services/session_store.ts b/src/services/session_store.ts index 04dfc162..809fc4aa 100644 --- a/src/services/session_store.ts +++ b/src/services/session_store.ts @@ -6,7 +6,6 @@ export type sessionStatus = 'offline' | 'online' | 'disconnected' | 'connecting' const statuses: Map = new Map() const retries: Map = new Map() - export abstract class SessionStore { abstract getPhones(): Promise @@ -16,27 +15,27 @@ export abstract class SessionStore { async setStatus(phone: string, status: sessionStatus) { logger.info(`Session status ${phone} change from ${await this.getStatus(phone)} to ${status}`) - statuses.set(phone, status) + statuses.set(phone, status) } async isStatusOnline(phone: string) { - return await this.getStatus(phone) == 'online' + return (await this.getStatus(phone)) == 'online' } async isStatusConnecting(phone: string) { - return await this.getStatus(phone) == 'connecting' + return (await this.getStatus(phone)) == 'connecting' } async isStatusOffline(phone: string) { - return await this.getStatus(phone) == 'offline' + return (await this.getStatus(phone)) == 'offline' } async isStatusDisconnect(phone: string) { - return await this.getStatus(phone) == 'disconnected' + return (await this.getStatus(phone)) == 'disconnected' } async isStatusRestartRequired(phone: string) { - return await this.getStatus(phone) == 'restart_required' + return (await this.getStatus(phone)) == 'restart_required' } async getConnectCount(phone: string) { @@ -48,12 +47,12 @@ export abstract class SessionStore { } async isStatusStandBy(phone: string) { - return await this.getStatus(phone) == 'standby' + return (await this.getStatus(phone)) == 'standby' } async verifyStatusStandBy(phone: string) { const count = await this.getConnectCount(phone) - if (await this.getStatus(phone) == 'standby') { + if ((await this.getStatus(phone)) == 'standby') { if (count < MAX_CONNECT_RETRY) { logger.warn('Standby removed %s', phone) await this.setStatus(phone, 'offline') @@ -61,7 +60,7 @@ export abstract class SessionStore { } logger.warn('Standby %s', phone) return true - } else if (count > MAX_CONNECT_RETRY && !await this.isStatusRestartRequired(phone)) { + } else if (count > MAX_CONNECT_RETRY && !(await this.isStatusRestartRequired(phone))) { this.setStatus(phone, 'standby') return true } diff --git a/src/services/session_store_redis.ts b/src/services/session_store_redis.ts index 38b5da64..8650d279 100644 --- a/src/services/session_store_redis.ts +++ b/src/services/session_store_redis.ts @@ -1,5 +1,17 @@ import { SessionStore, sessionStatus } from './session_store' -import { configKey, authKey, redisKeys, getSessionStatus, setSessionStatus, sessionStatusKey, redisGet, getConnectCount, setConnectCount, delAuth, clearConnectCount } from './redis' +import { + configKey, + authKey, + redisKeys, + getSessionStatus, + setSessionStatus, + sessionStatusKey, + redisGet, + getConnectCount, + setConnectCount, + delAuth, + clearConnectCount, +} from './redis' import logger from './logger' import { MAX_CONNECT_RETRY, MAX_CONNECT_TIME } from '../defaults' @@ -19,7 +31,7 @@ export class SessionStoreRedis extends SessionStore { } async getStatus(phone: string) { - return await getSessionStatus(phone) || 'disconnected' + return (await getSessionStatus(phone)) || 'disconnected' } async setStatus(phone: string, status: sessionStatus) { @@ -50,7 +62,7 @@ export class SessionStoreRedis extends SessionStore { const pattern = sessionStatusKey('*') const keys = await redisKeys(pattern) for (let i = 0; i < keys.length; i++) { - const key = keys[i]; + const key = keys[i] const phone = key.replace(toReplaceStatus, '') await this.syncConnection(phone) } @@ -63,7 +75,7 @@ export class SessionStoreRedis extends SessionStore { async syncConnection(phone: string) { logger.info(`Syncing ${phone} lost connection`) - if(await this.isStatusRestartRequired(phone)) { + if (await this.isStatusRestartRequired(phone)) { logger.info(`Is not lost connection, is restart required ${phone}`) return } @@ -75,9 +87,9 @@ export class SessionStoreRedis extends SessionStore { await this.setStatus(phone, 'disconnected') } const key = sessionStatusKey(phone) - if (await redisGet(key) == 'standby' && await this.getConnectCount(phone) < MAX_CONNECT_RETRY) { + if ((await redisGet(key)) == 'standby' && (await this.getConnectCount(phone)) < MAX_CONNECT_RETRY) { logger.info(`Sync ${phone} standby!`) await this.setStatus(phone, 'offline') } } -} \ No newline at end of file +} diff --git a/src/services/socket.ts b/src/services/socket.ts index dbb7da44..33a39c97 100644 --- a/src/services/socket.ts +++ b/src/services/socket.ts @@ -24,7 +24,7 @@ import { Level } from 'pino' import { SocksProxyAgent } from 'socks-proxy-agent' import { HttpsProxyAgent } from 'https-proxy-agent' import { useVoiceCallsBaileys } from 'voice-calls-baileys/lib/services/transport.model' -import { +import { DEFAULT_BROWSER, LOG_LEVEL, CONNECTING_TIMEOUT_MS, @@ -142,7 +142,7 @@ export const connect = async ({ logger.info(`First save creds with number is ${phoneCreds} and configured number ${phone}`) if (VALIDATE_SESSION_NUMBER && phoneCreds != phone) { await logout() - const message = t('session_conflict', phoneCreds, phone) + const message = t('session_conflict', phoneCreds, phone) logger.error(message) await onNotification(message, true) currentSaveCreds = async () => logger.error(message) @@ -164,7 +164,7 @@ export const connect = async ({ logger.debug('onConnectionUpdate connectionType %s ==> %s %s', config.connectionType, phone, JSON.stringify(event)) if (event.qr && config.connectionType == 'qrcode') { if (status.attempt > attempts) { - const message = t('attempts_exceeded', attempts) + const message = t('attempts_exceeded', attempts) logger.debug(message) await onNotification(message, true) status.attempt = 1 @@ -188,13 +188,13 @@ export const connect = async ({ await sessionStore.setStatus(phone, 'online') await onNotification(t('online_session'), true) } - + switch (event.connection) { case 'open': await onOpen() break - - case 'close': + + case 'close': await onClose(event) break @@ -259,7 +259,7 @@ export const connect = async ({ logger.info(`${phone} disconnected with status: ${statusCode}`) if ([DisconnectReason.loggedOut, DisconnectReason.forbidden].includes(statusCode)) { status.attempt = 1 - if (!await sessionStore.isStatusConnecting(phone)) { + if (!(await sessionStore.isStatusConnecting(phone))) { const message = t('removed') await onNotification(message, true) } @@ -318,12 +318,12 @@ export const connect = async ({ const reconnect = async () => { logger.info(`${phone} reconnecting`, status.attempt) if (status.attempt > attempts) { - const message = t('attempts_exceeded', attempts) + const message = t('attempts_exceeded', attempts) await onNotification(message, true) status.attempt = 1 return close() } else { - const message = t('connecting_attemps', status.attempt, attempts) + const message = t('connecting_attemps', status.attempt, attempts) await onNotification(message, false) await close() return onReconnect(status.attempt++) @@ -344,8 +344,8 @@ export const connect = async ({ // WebSocket.OPEN (1) // WebSocket.CLOSING (2) // WebSocket.CLOSED (3) - if (`${webSocket['readyState']}` == '1'){ - if (await sessionStore.isStatusConnecting(phone) || await sessionStore.isStatusOnline(phone)) { + if (`${webSocket['readyState']}` == '1') { + if ((await sessionStore.isStatusConnecting(phone)) || (await sessionStore.isStatusOnline(phone))) { try { await sock?.end(undefined) } catch (e) { @@ -359,7 +359,7 @@ export const connect = async ({ } } sock = undefined - if (!await sessionStore.isStatusRestartRequired(phone)) { + if (!(await sessionStore.isStatusRestartRequired(phone))) { await sessionStore.setStatus(phone, 'offline') } } @@ -367,9 +367,9 @@ export const connect = async ({ const logout = async () => { logger.info(`${phone} logout`) try { - return sock && await sock.logout() + return sock && (await sock.logout()) } catch (error) { - logger.error(`Error on remove session ${phone}: ${error.message}`,) + logger.error(`Error on remove session ${phone}: ${error.message}`) // ignore de unique error if already diconected session } finally { logger.info(`${phone} destroyed`) @@ -397,7 +397,7 @@ export const connect = async ({ if (await sessionStore.isStatusConnecting(phone)) { await verifyConnectingTimeout() throw new SendError(5, t('connecting_session')) - } else if (await sessionStore.isStatusDisconnect(phone) || !sock) { + } else if ((await sessionStore.isStatusDisconnect(phone)) || !sock) { throw new SendError(3, t('disconnected_session')) } else if (await sessionStore.isStatusOffline(phone)) { throw new SendError(12, t('offline_session')) @@ -416,7 +416,7 @@ export const connect = async ({ options: { composing: boolean; quoted: boolean | undefined } = { composing: false, quoted: undefined }, ) => { await validateStatus() - const id = isIndividualJid(to) ? await exists(to) : to + const id = isIndividualJid(to) ? await exists(to) : to if (id) { if (options.composing) { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -489,7 +489,7 @@ export const connect = async ({ const loggerBaileys = MAIN_LOGGER.child({}) logger.level = config.logLevel as Level - loggerBaileys.level = (LOG_LEVEL) as Level + loggerBaileys.level = LOG_LEVEL as Level let agent let fetchAgent @@ -537,7 +537,7 @@ export const connect = async ({ throw error } } - } + }, } sock = new Proxy(proxy, handler) } catch (error: any) { @@ -555,9 +555,9 @@ export const connect = async ({ if (sock) { event('connection.update', onConnectionUpdate) event('creds.update', verifyAndSaveCreds) - sock.ev.process(async(events) => { + sock.ev.process(async (events) => { const keys = Object.keys(events) - for(const i in keys) { + for (const i in keys) { const key = keys[i] if (eventsMap.has(key)) { eventsMap.get(key)(events[key]) @@ -587,7 +587,7 @@ export const connect = async ({ return false } - if (!await connect()) { + if (!(await connect())) { await sessionStore.setStatus(phone, 'offline') return } diff --git a/src/services/store.ts b/src/services/store.ts index 0ca04d42..910fa479 100644 --- a/src/services/store.ts +++ b/src/services/store.ts @@ -11,8 +11,8 @@ export interface getStore { } export type Store = { - dataStore: DataStore, - sessionStore: SessionStore, + dataStore: DataStore + sessionStore: SessionStore state: AuthenticationState saveCreds: () => Promise mediaStore: MediaStore diff --git a/src/services/store_file.ts b/src/services/store_file.ts index 2e5b32d9..24992305 100644 --- a/src/services/store_file.ts +++ b/src/services/store_file.ts @@ -81,7 +81,7 @@ const storeFile: store = async (phone: string, config: Config): Promise = } } setInterval(() => { - dataStore.writeToFile(dataFile), 10_0000 + ;(dataStore.writeToFile(dataFile), 10_0000) }) } else { logger.info('Store data not save') diff --git a/src/services/timer.ts b/src/services/timer.ts index 55362b1c..200dcedb 100644 --- a/src/services/timer.ts +++ b/src/services/timer.ts @@ -3,23 +3,19 @@ import { UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TIMER } from '../defaults' import { setLastTimer } from './redis' import logger from './logger' - export const start = async (phone, to, timeout, message) => { const now = new Date() const payload = { - phone, to, message, time: now.toISOString() + phone, + to, + message, + time: now.toISOString(), } logger.debug('timer start phone %s to %s timeout %s', phone, to, timeout) await setLastTimer(phone, to, now) - await amqpPublish( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_TIMER, - phone, - { payload }, - { type: 'topic', delay: timeout } - ) + await amqpPublish(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_TIMER, phone, { payload }, { type: 'topic', delay: timeout }) } export const stop = async (from, to) => { return setLastTimer(from, to, new Date()) -} \ No newline at end of file +} diff --git a/src/services/transformer.ts b/src/services/transformer.ts index 216db078..656c0053 100644 --- a/src/services/transformer.ts +++ b/src/services/transformer.ts @@ -63,12 +63,7 @@ export const TYPE_MESSAGES_TO_READ = [ 'ptvMessage', ] -const OTHER_MESSAGES_TO_PROCESS = [ - 'protocolMessage', - 'senderKeyDistributionMessage', - 'messageContextInfo', - 'messageStubType', -] +const OTHER_MESSAGES_TO_PROCESS = ['protocolMessage', 'senderKeyDistributionMessage', 'messageContextInfo', 'messageStubType'] export const getMimetype = (payload: any) => { const { type } = payload @@ -120,9 +115,7 @@ export const getMessageType = (payload: any) => { return 'receipt' } else if (payload.message) { const { message } = payload - return TYPE_MESSAGES_TO_READ.find((t) => message[t]) || - OTHER_MESSAGES_TO_PROCESS.find((t) => message[t]) || - Object.keys(payload.message)[0] + return TYPE_MESSAGES_TO_READ.find((t) => message[t]) || OTHER_MESSAGES_TO_PROCESS.find((t) => message[t]) || Object.keys(payload.message)[0] } else if (payload.messageStubType) { return 'messageStubType' } @@ -149,7 +142,7 @@ export const getNormalizedMessage = (waMessage: WAMessage): WAMessage | undefine let { message } = binMessage if (message.editedMessage) { message = message.protocolMessage?.editedMessage - }else if (message.protocolMessage?.editedMessage) { + } else if (message.protocolMessage?.editedMessage) { message = message.protocolMessage?.editedMessage } return { key: waMessage.key, message: { [binMessage.messageType]: message } } @@ -277,11 +270,7 @@ export const toBaileysMessageContent = (payload: any, customMessageCharactersFun const phone = contact['phones'][index] const waid = phone['wa_id'] const number = phone['phone'] - const vcard = 'BEGIN:VCARD\n' - + 'VERSION:3.0\n' - + `N:${contacName}\n` - + `TEL;type=CELL;type=VOICE;waid=${waid}:${number}\n` - + 'END:VCARD' + const vcard = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + `N:${contacName}\n` + `TEL;type=CELL;type=VOICE;waid=${waid}:${number}\n` + 'END:VCARD' contacts.push({ vcard }) } const displayName = contact['phones'].length > 1 ? `${contact['phones'].length} contacts` : contacName @@ -324,7 +313,9 @@ export const isIndividualMessage = (payload: any) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const getChatAndNumberAndId = (payload: any): [string, string, string] => { - const { key: { remoteJid } } = payload + const { + key: { remoteJid }, + } = payload if (isIndividualJid(remoteJid)) { return [remoteJid, jidToPhoneNumber(remoteJid), remoteJid] } else { @@ -337,7 +328,7 @@ export const getNumberAndId = (payload: any): [string, string] => { const { key: { remoteJid, senderPn, participantPn, participant, senderLid, participantLid }, participant: participant2, - participantPn: participantPn2 + participantPn: participantPn2, } = payload const value = senderLid || participantLid || participant || participant2 || remoteJid @@ -366,29 +357,22 @@ export const isValidPhoneNumber = (value: string, nine = false): boolean => { export const extractDestinyPhone = (payload: object, throwError = true) => { 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('+', '') - ) || ( - data.entry[0].changes[0].value.messages - && data.entry[0].changes[0].value.messages[0] - && data.entry[0].changes[0].value.messages[0].from?.replace('+', '') - ) - ) - ) + 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('+', '')) || + (data.entry[0].changes[0].value.messages && + data.entry[0].changes[0].value.messages[0] && + data.entry[0].changes[0].value.messages[0].from?.replace('+', '')))) if (!number && throwError) { throw Error(`error on get phone number from ${JSON.stringify(payload)}`) } @@ -398,18 +382,15 @@ export const extractDestinyPhone = (payload: object, throwError = true) => { export const getGroupId = (payload: object) => { const data = payload as any return ( - 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].group_id - ) - ) + 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].group_id + ) } export const isGroupMessage = (payload: object) => { @@ -421,13 +402,12 @@ export const isNewsletterMessage = (payload: object) => { return groupId && isJidNewsletter(groupId) } -export const extractSessionPhone = (payload: object) => { +export const extractSessionPhone = (payload: object) => { const data = payload as any - const session = data.entry[0].changes[0].value.messages - && data.entry[0].changes[0].value.metadata - && data.entry[0].changes[0].value.metadata.display_phone_number + const session = + data.entry[0].changes[0].value.messages && data.entry[0].changes[0].value.metadata && data.entry[0].changes[0].value.metadata.display_phone_number - return `${(session || '')}`.replaceAll('+', '') + return `${session || ''}`.replaceAll('+', '') } export const isOutgoingMessage = (payload: object) => { @@ -450,17 +430,14 @@ export const isIncomingMessage = (payload: object) => { export const extractTypeMessage = (payload: object) => { const data = payload as any return ( - ( - 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.messages - && data.entry[0].changes[0].value.messages[0] - && data.entry[0].changes[0].value.messages[0].type - ) + 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.messages && + data.entry[0].changes[0].value.messages[0] && + data.entry[0].changes[0].value.messages[0].type ) } @@ -468,12 +445,12 @@ export const isAudioMessage = (payload: object) => { return 'audio' == extractTypeMessage(payload) } - export const isFailedStatus = (payload: object) => { const data = payload as any - return 'failed' == (data.entry[0].changes[0].value.statuses - && data.entry[0].changes[0].value.statuses[0] - && data.entry[0].changes[0].value.statuses[0].status) + return ( + 'failed' == + (data.entry[0].changes[0].value.statuses && data.entry[0].changes[0].value.statuses[0] && data.entry[0].changes[0].value.statuses[0].status) + ) } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -497,7 +474,7 @@ export const jidToPhoneNumber = (value: any, plus = '+', retry = true): string = } export const jidToPhoneNumberIfUser = (value: any): string => { - return isIndividualJid(value) ? jidToPhoneNumber(value, '') : value + return isIndividualJid(value) ? jidToPhoneNumber(value, '') : value } /* @@ -541,7 +518,9 @@ export const jidToPhoneNumberIfUser = (value: any): string => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const fromBaileysMessageContent = (phone: string, payload: any, config?: Partial): [any, string, string] => { try { - const { key: { id: whatsappMessageId, fromMe } } = payload + const { + key: { id: whatsappMessageId, fromMe }, + } = payload const [chatJid, senderPhone, senderId] = getChatAndNumberAndId(payload) const messageType = getMessageType(payload) const binMessage = payload.update || payload.receipt || (messageType && payload.message && payload.message[messageType]) @@ -621,7 +600,7 @@ export const fromBaileysMessageContent = (phone: string, payload: any, config?: if (mediaType == 'pvt') { mediaType = mimetype.split('/')[0] } - message[mediaType] = { + message[mediaType] = { caption: binMessage.caption, filename, mime_type: mimetype, @@ -670,18 +649,22 @@ export const fromBaileysMessageContent = (phone: string, payload: any, config?: } const editedMessageType = getMessageType(editedMessagePayload) const editedBinMessage = getBinMessage(editedMessagePayload) - if (editedMessageType && TYPE_MESSAGES_TO_PROCESS_FILE.includes(editedMessageType) && !editedBinMessage?.message?.url && editedBinMessage?.message?.caption) { + if ( + editedMessageType && + TYPE_MESSAGES_TO_PROCESS_FILE.includes(editedMessageType) && + !editedBinMessage?.message?.url && + editedBinMessage?.message?.caption + ) { editedMessagePayload.message = { - conversation: editedBinMessage?.message?.caption + conversation: editedBinMessage?.message?.caption, } } return fromBaileysMessageContent(phone, editedMessagePayload, config) - case 'protocolMessage': // {"key":{"remoteJid":"351912490567@s.whatsapp.net","fromMe":false,"id":"3EB0C77FBE5C8DACBEC5"},"messageTimestamp":1741714271,"pushName":"Pedro Paiva","broadcast":false,"message":{"protocolMessage":{"key":{"remoteJid":"351211450051@s.whatsapp.net","fromMe":true,"id":"3EB05C0B7B1A0C12284EE0"},"type":"MESSAGE_EDIT","editedMessage":{"conversation":"blablabla2","messageContextInfo":{"messageSecret":"4RYW9eIV1O4j5vjNmY059bZRymJ+B2aTfi9it9+2RxA="}},"timestampMs":"1741714271693"},"messageContextInfo":{"deviceListMetadata":{"senderKeyHash":"UgdPt0CEKvqhyg==","senderTimestamp":"1741018303","senderAccountType":"E2EE","receiverAccountType":"E2EE","recipientKeyHash":"EhuHta8R2tH+8g==","recipientTimestamp":"1740522549"},"deviceListMetadataVersion":2,"messageSecret":"4RYW9eIV1O4j5vjNmY059bZRymJ+B2aTfi9it9+2RxA="}}} if (binMessage.editedMessage) { - return fromBaileysMessageContent(phone, { ...payload, message: { editedMessage: { message: { protocolMessage: binMessage }}}}, config) + return fromBaileysMessageContent(phone, { ...payload, message: { editedMessage: { message: { protocolMessage: binMessage } } } }, config) } else { logger.debug(`Ignore message type ${messageType}`) return [null, senderPhone, senderId] @@ -755,10 +738,12 @@ export const fromBaileysMessageContent = (phone: string, payload: any, config?: case 'messageStubType': MESSAGE_STUB_TYPE_ERRORS - if (payload.messageStubType == 2 && - payload.messageStubParameters && - payload.messageStubParameters[0] && - MESSAGE_STUB_TYPE_ERRORS.includes(payload.messageStubParameters[0].toLowerCase())) { + if ( + payload.messageStubType == 2 && + payload.messageStubParameters && + payload.messageStubParameters[0] && + MESSAGE_STUB_TYPE_ERRORS.includes(payload.messageStubParameters[0].toLowerCase()) + ) { message.text = { body: MESSAGE_CHECK_WAAPP || t('failed_decrypt'), } @@ -933,10 +918,10 @@ export const fromBaileysMessageContent = (phone: string, payload: any, config?: } export const toBuffer = (arrayBuffer) => { - const buffer = Buffer.alloc(arrayBuffer.byteLength); - const view = new Uint8Array(arrayBuffer); + const buffer = Buffer.alloc(arrayBuffer.byteLength) + const view = new Uint8Array(arrayBuffer) for (let i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; + buffer[i] = view[i] } - return buffer; + return buffer } diff --git a/src/standalone.ts b/src/standalone.ts index fa94e55d..a6623148 100644 --- a/src/standalone.ts +++ b/src/standalone.ts @@ -39,14 +39,14 @@ import { OnNewLogin } from './services/socket' import { onNewLoginAlert } from './services/on_new_login_alert' import { onNewLoginGenerateToken } from './services/on_new_login_generate_token' import { Broadcast } from './services/broadcast' -import { +import { isInBlacklistInMemory, addToBlacklistInMemory, addToBlacklist, addToBlacklistRedis, addToBlacklistJob, isInBlacklist, - isInBlacklistInRedis + isInBlacklistInRedis, } from './services/blacklist' import { Listener } from './services/listener' import { ListenerBaileys } from './services/listener_baileys' @@ -97,7 +97,7 @@ let middlewareVar: middleware = middlewareNext if (process.env.REDIS_URL) { logger.info('Starting with redis') - startRedis().catch( error => { + startRedis().catch((error) => { console.error(error, 'Erro on start') process.exit(1) }) @@ -111,7 +111,7 @@ if (process.env.REDIS_URL) { if (process.env.AMQP_URL) { logger.info('Starting with broker') - amqpConnect().catch( error => { + amqpConnect().catch((error) => { console.error(error, 'Erro on start rabbitmq') process.exit(1) }) @@ -125,29 +125,13 @@ if (process.env.AMQP_URL) { const bindBridgeJob = new BindBridgeJob() const logoutJob = new LogoutJob(logout) logger.info('Starting bind bridge consumer') - amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_BIND}.${UNOAPI_SERVER_NAME}`, - '*', - bindBridgeJob.consume.bind(bindBridgeJob), - { type: 'direct' } - ) + amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_BIND}.${UNOAPI_SERVER_NAME}`, '*', bindBridgeJob.consume.bind(bindBridgeJob), { + type: 'direct', + }) logger.info('Starting reload consumer') - amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_RELOAD}.${UNOAPI_SERVER_NAME}`, - '', - reloadJob.consume.bind(reloadJob), - { type: 'direct' } - ) + amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_RELOAD}.${UNOAPI_SERVER_NAME}`, '', reloadJob.consume.bind(reloadJob), { type: 'direct' }) logger.info('Starting logout consumer') - amqpConsume( - UNOAPI_EXCHANGE_BRIDGE_NAME, - `${UNOAPI_QUEUE_LOGOUT}.${UNOAPI_SERVER_NAME}`, - '', - logoutJob.consume.bind(logoutJob), - { type: 'direct' } - ) + amqpConsume(UNOAPI_EXCHANGE_BRIDGE_NAME, `${UNOAPI_QUEUE_LOGOUT}.${UNOAPI_SERVER_NAME}`, '', logoutJob.consume.bind(logoutJob), { type: 'direct' }) logger.info('Starting media consumer') const mediaJob = new MediaJob(getConfigVar) amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_MEDIA, '*', mediaJob.consume.bind(mediaJob), { type: 'topic' }) @@ -157,32 +141,22 @@ if (process.env.AMQP_URL) { logger.info('Starting outgoing consumer %s', UNOAPI_SERVER_NAME) const outgoingCloudApi: Outgoing = new OutgoingCloudApi(getConfigRedis, isInBlacklistInRedis) const outgoinJob = new OutgoingJob(getConfigVar, outgoingCloudApi) - amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_OUTGOING, - '*', - outgoinJob.consume.bind(outgoinJob), - { notifyFailedMessages, prefetch, type: 'topic' } - ) + amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_OUTGOING, '*', outgoinJob.consume.bind(outgoinJob), { + notifyFailedMessages, + prefetch, + type: 'topic', + }) if (notifyFailedMessages) { logger.debug('Starting notification consumer %s', UNOAPI_SERVER_NAME) const notificationJob = new NotificationJob(incoming) - amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_NOTIFICATION, - '*', - notificationJob.consume.bind(notificationJob), - { notifyFailedMessages: false, type: 'topic' }) + amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_NOTIFICATION, '*', notificationJob.consume.bind(notificationJob), { + notifyFailedMessages: false, + type: 'topic', + }) } logger.info('Starting blacklist add consumer %s', UNOAPI_SERVER_NAME) - amqpConsume( - UNOAPI_EXCHANGE_BROKER_NAME, - UNOAPI_QUEUE_BLACKLIST_ADD, - '*', - atbl, - { notifyFailedMessages, prefetch, type: 'topic' } - ) + amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BLACKLIST_ADD, '*', atbl, { notifyFailedMessages, prefetch, type: 'topic' }) } else { logger.info('Starting standard mode') } @@ -209,7 +183,7 @@ const app: App = new App( logout, middlewareVar, injectRouteDummy, - contact + contact, ) broadcast.setSever(app.socket) @@ -233,4 +207,4 @@ process.on('unhandledRejection', (reason: any, promise) => { logger.error('unhandledRejection: %s', reason.stack) logger.error('promise: %s', promise) process.exit(1) -}) \ No newline at end of file +}) diff --git a/src/utils/media_to_buffer.ts b/src/utils/media_to_buffer.ts index a862b670..6f01122c 100644 --- a/src/utils/media_to_buffer.ts +++ b/src/utils/media_to_buffer.ts @@ -1,4 +1,3 @@ - import fetch, { Response, RequestInit } from 'node-fetch' import logger from '../services/logger' import { toBuffer } from '../services/transformer' @@ -6,7 +5,7 @@ import { toBuffer } from '../services/transformer' export default async function (url: string, token: string, timeoutMs: number) { const headers = { 'Content-Type': 'application/json; charset=utf-8', - 'Authorization': `Bearer ${token}` + Authorization: `Bearer ${token}`, } const options: RequestInit = { method: 'GET', headers } if (timeoutMs > 0) { @@ -36,4 +35,4 @@ export default async function (url: string, token: string, timeoutMs: number) { } const arrayBuffer = await response.arrayBuffer() return { buffer: toBuffer(arrayBuffer), link: json['url'] } -} \ No newline at end of file +} diff --git a/src/waker.ts b/src/waker.ts index 7485aba6..819552ac 100644 --- a/src/waker.ts +++ b/src/waker.ts @@ -28,7 +28,7 @@ const bridgeQueues = [UNOAPI_QUEUE_LISTENER, UNOAPI_QUEUE_INCOMING] const queues = bridgeQueues.concat(brokerQueues) -const getExchangeName = queue => { +const getExchangeName = (queue) => { if (bridgeQueues.includes(queue)) { return UNOAPI_EXCHANGE_BRIDGE_NAME } else if (brokerQueues.includes(queue)) { @@ -38,10 +38,10 @@ const getExchangeName = queue => { } } -(async () => { +;(async () => { return Promise.all( - queues.map(async queue => { - const connection = await amqpConnect() + queues.map(async (queue) => { + const connection = await amqpConnect() const queueName = queueDeadName(queue) const exchangeName = queueDeadName(getExchangeName(queue)) const exchangeType = 'topic' @@ -54,17 +54,13 @@ const getExchangeName = queue => { if (!payload) { throw 'payload not be null' } - await amqpPublish( - exchangeName, - queue, - extractRoutingKeyFromBindingKey(payload.fields.routingKey), - JSON.parse(payload.content.toString()), - { type: exchangeType } - ) + await amqpPublish(exchangeName, queue, extractRoutingKeyFromBindingKey(payload.fields.routingKey), JSON.parse(payload.content.toString()), { + type: exchangeType, + }) return channel.ack(payload) }) await channel.unbindQueue(queueName, exchangeName) - }) + }), ) })() @@ -83,4 +79,4 @@ process.on('unhandledRejection', (reason: any, promise) => { logger.error('unhandledRejection: %s', reason.stack) logger.error('promise: %s', promise) process.exit(1) -}) \ No newline at end of file +}) diff --git a/src/web.ts b/src/web.ts index e9716579..95d58483 100644 --- a/src/web.ts +++ b/src/web.ts @@ -8,8 +8,8 @@ import { Outgoing } from './services/outgoing' import { OutgoingAmqp } from './services/outgoing_amqp' import { SessionStore } from './services/session_store' import { SessionStoreRedis } from './services/session_store_redis' -import { - BASE_URL, +import { + BASE_URL, PORT, CONFIG_SESSION_PHONE_CLIENT, CONFIG_SESSION_PHONE_NAME, @@ -58,10 +58,15 @@ app.server.listen(PORT, '0.0.0.0', async () => { logger.info('Starting broadcast consumer') await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_BROADCAST, '*', broadcastJob.consume.bind(broadcastJob), { type: 'topic' }) await amqpConsume(UNOAPI_EXCHANGE_BROKER_NAME, UNOAPI_QUEUE_RELOAD, '*', reload.run.bind(reloadJob), { type: 'topic' }) - logger.info('Unoapi Cloud version: %s, listening on port: %s | Linked Device: %s(%s)', version, PORT, CONFIG_SESSION_PHONE_CLIENT, CONFIG_SESSION_PHONE_NAME) + logger.info( + 'Unoapi Cloud version: %s, listening on port: %s | Linked Device: %s(%s)', + version, + PORT, + CONFIG_SESSION_PHONE_CLIENT, + CONFIG_SESSION_PHONE_NAME, + ) }) - process.on('uncaughtException', (reason: any) => { if (process.env.SENTRY_DSN) { Sentry.captureException(reason) diff --git a/tsconfig.json b/tsconfig.json index 0315d95b..9c4acf30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "module": "NodeNext", "moduleResolution": "nodenext", "experimentalDecorators": true, - "allowJs": false, + "allowJs": true, "checkJs": false, "outDir": "dist", "strict": false, @@ -21,7 +21,7 @@ "include": ["src/**/*.ts", "src/**/*.json"], "ts-node": { "compilerOptions": { - "esModuleInterop": true, + "esModuleInterop": true } } } diff --git a/yarn.lock b/yarn.lock index 667b4a8c..5aeade07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -871,6 +871,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@borewit/text-codec@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.1.1.tgz#7e7f27092473d5eabcffef693a849f2cc48431da" + integrity sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA== + +"@borewit/text-codec@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@borewit/text-codec/-/text-codec-0.2.0.tgz#1dbf3097b136b0dda56bce3ef53886c338ea3202" + integrity sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA== + "@cacheable/node-cache@^1.4.0": version "1.6.1" resolved "https://registry.yarnpkg.com/@cacheable/node-cache/-/node-cache-1.6.1.tgz#8e845eea011518ab96999a4c893e273812eeb00f" @@ -2611,6 +2621,15 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@tokenizer/inflate@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.2.7.tgz#32dd9dfc9abe457c89b3d9b760fc0690c85a103b" + integrity sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg== + dependencies: + debug "^4.4.0" + fflate "^0.8.2" + token-types "^6.0.0" + "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -3332,7 +3351,22 @@ babel-preset-jest@^29.6.3: babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" -baileys@^6.7.8, "baileys@git+https://github.com/WhiskeySockets/Baileys#19124426b2ded31f6d28ed60c53b75f101865bc3": +baileys@^6.7.19: + version "6.7.19" + resolved "https://registry.yarnpkg.com/baileys/-/baileys-6.7.19.tgz#7f278ed4c41b0c98e95766e7ebca5881cc539b16" + integrity sha512-15ZZVZoN5CQl6UeTT+3CogIZm40G+E5LO9meVi4NhqxmYYiIGoh7iuq5UlHjmTvZIpFtMPCnz+EDmyEABy8oig== + dependencies: + "@cacheable/node-cache" "^1.4.0" + "@hapi/boom" "^9.1.3" + async-mutex "^0.5.0" + axios "^1.6.0" + libsignal "git+https://github.com/whiskeysockets/libsignal-node" + music-metadata "^11.7.0" + pino "^9.6" + protobufjs "^7.2.4" + ws "^8.13.0" + +baileys@^6.7.8: version "6.7.18" resolved "git+https://github.com/WhiskeySockets/Baileys#19124426b2ded31f6d28ed60c53b75f101865bc3" dependencies: @@ -3861,7 +3895,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7: +debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@^4.4.0, debug@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== @@ -4442,6 +4476,11 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -4458,6 +4497,16 @@ file-type@^16.5.4: strtok3 "^6.2.4" token-types "^4.1.1" +file-type@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-21.0.0.tgz#b6c5990064bc4b704f8e5c9b6010c59064d268bc" + integrity sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg== + dependencies: + "@tokenizer/inflate" "^0.2.7" + strtok3 "^10.2.2" + token-types "^6.0.0" + uint8array-extras "^1.4.0" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -5618,6 +5667,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +"libsignal@git+https://github.com/whiskeysockets/libsignal-node.git": + version "2.0.1" + resolved "git+https://github.com/whiskeysockets/libsignal-node.git#4d08331a833727c338c1a90041d17b870210dfae" + dependencies: + curve25519-js "^0.0.4" + protobufjs "6.8.8" + "libsignal@github:WhiskeySockets/libsignal-node": version "2.0.1" resolved "https://codeload.github.com/WhiskeySockets/libsignal-node/tar.gz/4d08331a833727c338c1a90041d17b870210dfae" @@ -5858,6 +5914,21 @@ ms@2.1.3, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +music-metadata@^11.7.0: + version "11.8.3" + resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-11.8.3.tgz#fcba77613e11644bfd9b3c11876aecbee18f95be" + integrity sha512-Tgiv4MlCgDb6XzelziB1mmL2xeoHls0KTpCm3Z3qr+LfF4mBEpkuc5vNrc927IT5+S5fv+vzStfI+HYC0igDpA== + dependencies: + "@borewit/text-codec" "^0.2.0" + "@tokenizer/token" "^0.3.0" + content-type "^1.0.5" + debug "^4.4.1" + file-type "^21.0.0" + media-typer "^1.1.0" + strtok3 "^10.3.4" + token-types "^6.1.1" + uint8array-extras "^1.4.1" + music-metadata@^7.12.3: version "7.14.0" resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-7.14.0.tgz#74e3e5fc8e09b86d1a3e791fb5ce9ccdc4347ad9" @@ -7090,6 +7161,13 @@ strnum@^2.1.0: resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.1.1.tgz#cf2a6e0cf903728b8b2c4b971b7e36b4e82d46ab" integrity sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw== +strtok3@^10.2.2, strtok3@^10.3.4: + version "10.3.4" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-10.3.4.tgz#793ebd0d59df276a085586134b73a406e60be9c1" + integrity sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg== + dependencies: + "@tokenizer/token" "^0.3.0" + strtok3@^6.2.4, strtok3@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" @@ -7226,6 +7304,15 @@ token-types@^4.1.1, token-types@^4.2.1: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +token-types@^6.0.0, token-types@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-6.1.1.tgz#85bd0ada82939b9178ecd5285881a538c4c00fdd" + integrity sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ== + dependencies: + "@borewit/text-codec" "^0.1.0" + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + touch@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" @@ -7370,6 +7457,11 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== +uint8array-extras@^1.4.0, uint8array-extras@^1.4.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/uint8array-extras/-/uint8array-extras-1.5.0.tgz#10d2a85213de3ada304fea1c454f635c73839e86" + integrity sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A== + undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"