diff --git a/.env.example b/.env.example index b7e2d3a9..9e7ad91f 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,7 @@ SERVER_PORT=8080 # Server URL - Set your application url SERVER_URL=http://localhost:8080 -TELEMETRY=true -TELEMETRY_URL= +SENTRY_DSN= # Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com' CORS_ORIGIN=* @@ -22,8 +21,6 @@ LOG_BAILEYS=error # If you don't even want an expiration, enter the value false DEL_INSTANCE=false -# Permanent data storage -DATABASE_ENABLED=true # Provider: postgresql | mysql DATABASE_PROVIDER=postgresql DATABASE_CONNECTION_URI='postgresql://user:pass@localhost:5432/evolution?schema=public' @@ -178,6 +175,7 @@ S3_SECRET_KEY= S3_BUCKET=evolution S3_PORT=443 S3_ENDPOINT=s3.domain.com +S3_REGION=eu-west-3 S3_USE_SSL=true # AMAZON S3 - Environment variables @@ -186,6 +184,7 @@ S3_USE_SSL=true # S3_ACCESS_KEY=access_key_id # S3_SECRET_KEY=secret_access_key # S3_ENDPOINT=s3.amazonaws.com # region: s3.eu-west-3.amazonaws.com +# S3_REGION=eu-west-3 # MINIO Use SSL - Environment variables # S3_ENABLED=true diff --git a/.eslintrc.js b/.eslintrc.js index f805da92..74a4c2ea 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,50 +1,42 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - sourceType: 'CommonJS', + sourceType: 'CommonJS', }, - plugins: [ - '@typescript-eslint', - 'simple-import-sort', - 'import' - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended' - ], + plugins: ['@typescript-eslint', 'simple-import-sort', 'import'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', }, root: true, env: { - node: true, - jest: true, + node: true, + jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unused-vars': 'error', - 'import/first': 'error', - 'import/no-duplicates': 'error', - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', - '@typescript-eslint/ban-types': [ - 'error', - { - extendDefaults: true, - types: { - '{}': false, - Object: false, - }, + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'import/first': 'error', + 'import/no-duplicates': 'error', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + '@typescript-eslint/ban-types': [ + 'error', + { + extendDefaults: true, + types: { + '{}': false, + Object: false, }, - ], - 'prettier/prettier': ['error', { endOfLine: 'auto' }], + }, + ], + 'prettier/prettier': ['error', { endOfLine: 'auto' }], }, -}; \ No newline at end of file +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index b34fe32a..e6496521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,23 @@ -# 2.0.9 (pre release) +# 2.0.10 (2024-08-16 16:23) + +### Features + +* OpenAI send images when markdown +* Dify send images when markdown +* Sentry implemented + +### Fixed + +* Fix on get profilePicture +* Added S3_REGION on minio settings + +# 2.0.9 (2024-08-15 12:31) ### Features * Added ignoreJids in chatwoot settings * Dify now identifies images +* Openai now identifies images ### Fixed @@ -16,6 +30,9 @@ * Deprecate buttons and list in new Baileys version * Changed labels to be unique on the same instance * Remove instance from redis even if using database +* Unified integration session system so they don't overlap +* Temporary fix for pictureUrl bug in groups +* Fix on migrations # 2.0.9-rc (2024-08-09 18:00) diff --git a/Docker/scripts/deploy_database.sh b/Docker/scripts/deploy_database.sh index a3cf379d..fde46d12 100755 --- a/Docker/scripts/deploy_database.sh +++ b/Docker/scripts/deploy_database.sh @@ -10,16 +10,16 @@ if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]] export DATABASE_URL echo "Deploying migrations for $DATABASE_PROVIDER" echo "Database URL: $DATABASE_URL" - rm -rf ./prisma/migrations - cp -r ./prisma/$DATABASE_PROVIDER-migrations ./prisma/migrations - npx prisma migrate deploy --schema ./prisma/$DATABASE_PROVIDER-schema.prisma + # rm -rf ./prisma/migrations + # cp -r ./prisma/$DATABASE_PROVIDER-migrations ./prisma/migrations + npm run db:deploy if [ $? -ne 0 ]; then echo "Migration failed" exit 1 else echo "Migration succeeded" fi - npx prisma generate --schema ./prisma/$DATABASE_PROVIDER-schema.prisma + npm run db:generate if [ $? -ne 0 ]; then echo "Prisma generate failed" exit 1 diff --git a/Docker/scripts/generate_database.sh b/Docker/scripts/generate_database.sh index 570a60d8..892682ef 100644 --- a/Docker/scripts/generate_database.sh +++ b/Docker/scripts/generate_database.sh @@ -10,7 +10,7 @@ if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]] export DATABASE_URL echo "Generating database for $DATABASE_PROVIDER" echo "Database URL: $DATABASE_URL" - npx prisma generate --schema=prisma/$DATABASE_PROVIDER-schema.prisma + npm run db:generate if [ $? -ne 0 ]; then echo "Prisma generate failed" exit 1 diff --git a/Docker/swarm/evolution_api_v2.yaml b/Docker/swarm/evolution_api_v2.yaml index 8d63ef55..e46e40d9 100644 --- a/Docker/swarm/evolution_api_v2.yaml +++ b/Docker/swarm/evolution_api_v2.yaml @@ -2,7 +2,7 @@ version: "3.7" services: evolution_v2: - image: atendai/evolution-api:v2.0.9-rc + image: atendai/evolution-api:v2.0.9 volumes: - evolution_instances:/evolution/instances networks: @@ -10,7 +10,6 @@ services: environment: - SERVER_URL=https://evo2.site.com - DEL_INSTANCE=false - - DATABASE_ENABLED=true - DATABASE_PROVIDER=postgresql - DATABASE_CONNECTION_URI=postgresql://postgres:SENHA@postgres:5432/evolution - DATABASE_SAVE_DATA_INSTANCE=true diff --git a/Dockerfile b/Dockerfile index c2373396..27b6333c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM node:20-alpine AS builder RUN apk update && \ apk add git ffmpeg wget curl bash -LABEL version="2.0.9-rc" description="Api to control whatsapp features through http requests." +LABEL version="2.0.10" description="Api to control whatsapp features through http requests." LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL contact="contato@agenciadgcode.com" @@ -18,6 +18,8 @@ COPY ./public ./public COPY ./prisma ./prisma COPY ./manager ./manager COPY ./.env.example ./.env +COPY ./runWithProvider.js ./ +COPY ./tsup.config.ts ./ COPY ./Docker ./Docker @@ -47,6 +49,8 @@ COPY --from=builder /evolution/manager ./manager COPY --from=builder /evolution/public ./public COPY --from=builder /evolution/.env ./.env COPY --from=builder /evolution/Docker ./Docker +COPY --from=builder /evolution/runWithProvider.js ./runWithProvider.js +COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts ENV DOCKER_ENV=true diff --git a/package.json b/package.json index 273caa01..6810b12d 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "evolution-api", - "version": "2.0.9", + "version": "2.0.10", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs", "scripts": { - "build": "tsup", + "build": "tsc --noEmit && tsup", "start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts", "start:prod": "node dist/main", "dev:server": "clear && tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts", @@ -52,10 +52,11 @@ "@figuro/chatwoot-sdk": "^1.1.16", "@hapi/boom": "^10.0.1", "@prisma/client": "^5.15.0", - "@sentry/node": "^7.59.2", + "@sentry/node": "^7.119.0", + "@sentry/profiling-node": "^8.26.0", "amqplib": "^0.10.3", "axios": "^1.6.5", - "baileys": "6.7.6", + "baileys": "6.7.5", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/prisma/mysql-migrations/20240813153900_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql b/prisma/mysql-migrations/20240813153900_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql index 5d6d0c04..65dbdb69 100644 --- a/prisma/mysql-migrations/20240813153900_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql +++ b/prisma/mysql-migrations/20240813153900_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql @@ -1,151 +1,185 @@ /* - Warnings: - - - You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `DifySession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `DifySession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `OpenaiSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `OpenaiSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `TypebotSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `TypebotSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. - - A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Contact` will be added. If there are existing duplicate values, this will fail. - +Warnings: +- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `DifySession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `DifySession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `OpenaiSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `OpenaiSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `TypebotSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `TypebotSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. +- A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Contact` will be added. If there are existing duplicate values, this will fail. */ -- AlterTable -ALTER TABLE `Chat` ADD COLUMN `name` VARCHAR(100) NULL, - MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NULL; +ALTER TABLE `Chat` +ADD COLUMN `name` VARCHAR(100) NULL, +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NULL; -- AlterTable -ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Chatwoot` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NULL; +ALTER TABLE `Contact` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NULL; -- AlterTable -ALTER TABLE `Dify` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Dify` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `DifySession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `DifySession` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `DifySetting` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL, - MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NULL; +ALTER TABLE `Instance` +MODIFY `disconnectionAt` TIMESTAMP NULL, +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NULL; -- AlterTable -ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Label` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE `Media` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP; -- AlterTable -ALTER TABLE `OpenaiBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `OpenaiBot` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `OpenaiCreds` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `OpenaiSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `OpenaiSession` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `OpenaiSetting` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Proxy` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Rabbitmq` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE `Session` +MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; -- AlterTable -ALTER TABLE `Setting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Setting` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Sqs` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Template` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NULL; +ALTER TABLE `Typebot` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NULL; -- AlterTable -ALTER TABLE `TypebotSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `TypebotSession` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `TypebotSetting` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Webhook` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; -- AlterTable -ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - MODIFY `updatedAt` TIMESTAMP NOT NULL; +ALTER TABLE `Websocket` +MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, +MODIFY `updatedAt` TIMESTAMP NOT NULL; + +-- Remove the duplicates +DELETE c1 +FROM `Contact` c1 + INNER JOIN ( + SELECT MIN(id) as id + FROM `Contact` + GROUP BY + `remoteJid`, `instanceId` + ) c2 ON c1.`remoteJid` = c2.`remoteJid` + AND c1.`instanceId` = c2.`instanceId` + AND c1.id != c2.id; -- CreateIndex -CREATE UNIQUE INDEX `Contact_remoteJid_instanceId_key` ON `Contact`(`remoteJid`, `instanceId`); +CREATE UNIQUE INDEX `Contact_remoteJid_instanceId_key` ON `Contact` (`remoteJid`, `instanceId`); \ No newline at end of file diff --git a/prisma/postgresql-migrations/20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql b/prisma/postgresql-migrations/20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql index b0289bb2..1adcb7f4 100644 --- a/prisma/postgresql-migrations/20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql +++ b/prisma/postgresql-migrations/20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql @@ -4,5 +4,14 @@ - A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Contact` will be added. If there are existing duplicate values, this will fail. */ +-- Remove the duplicates +DELETE FROM "Contact" +WHERE ctid NOT IN ( + SELECT min(ctid) + FROM "Contact" + GROUP BY "remoteJid", "instanceId" +); + + -- CreateIndex CREATE UNIQUE INDEX "Contact_remoteJid_instanceId_key" ON "Contact"("remoteJid", "instanceId"); diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index 7c5c78c1..c3657142 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -538,7 +538,7 @@ export class InstanceController { if (state == 'close') { await instance.connectToWhatsapp(number); - await delay(5000); + await delay(2000); return instance.qrCode; } diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 00da7fdd..fc2ff5d3 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -1,11 +1,4 @@ -import { - proto, - WAPresence, - WAPrivacyGroupAddValue, - WAPrivacyOnlineValue, - WAPrivacyValue, - WAReadReceiptsValue, -} from 'baileys'; +import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from 'baileys'; export class OnWhatsAppDto { constructor( @@ -91,7 +84,7 @@ export class PrivacySettingDto { status: WAPrivacyValue; online: WAPrivacyOnlineValue; last: WAPrivacyValue; - groupadd: WAPrivacyGroupAddValue; + groupadd: WAPrivacyValue; } export class DeleteMessage { diff --git a/src/api/guards/auth.guard.ts b/src/api/guards/auth.guard.ts index 89b0874e..9ad20b61 100644 --- a/src/api/guards/auth.guard.ts +++ b/src/api/guards/auth.guard.ts @@ -34,7 +34,7 @@ async function apikey(req: Request, _: Response, next: NextFunction) { return next(); } } else { - if (req.originalUrl.includes('/instance/fetchInstances') && db.ENABLED) { + if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) { const instanceByKey = await prismaRepository.instance.findFirst({ where: { token: key }, }); diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index 9f8eb090..29c320ec 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -1,13 +1,12 @@ import { InstanceDto } from '@api/dto/instance.dto'; import { cache, waMonitor } from '@api/server.module'; -import { CacheConf, configService, Database } from '@config/env.config'; +import { CacheConf, configService } from '@config/env.config'; import { BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException } from '@exceptions'; import { prismaServer } from '@libs/prisma.connect'; import { NextFunction, Request, Response } from 'express'; async function getInstance(instanceName: string) { try { - const db = configService.get('DATABASE'); const cacheConf = configService.get('CACHE'); const exists = !!waMonitor.waInstances[instanceName]; @@ -18,13 +17,9 @@ async function getInstance(instanceName: string) { return exists || keyExists; } - if (db.ENABLED) { - const prisma = prismaServer; + const prisma = prismaServer; - return exists || (await prisma.instance.findMany({ where: { name: instanceName } })).length > 0; - } - - return false; + return exists || (await prisma.instance.findMany({ where: { name: instanceName } })).length > 0; } catch (error) { throw new InternalServerErrorException(error?.toString()); } diff --git a/src/api/integrations/chatwoot/services/chatwoot.service.ts b/src/api/integrations/chatwoot/services/chatwoot.service.ts index 3df8795a..0904ac61 100644 --- a/src/api/integrations/chatwoot/services/chatwoot.service.ts +++ b/src/api/integrations/chatwoot/services/chatwoot.service.ts @@ -354,7 +354,7 @@ export class ChatwootService { return contact; } catch (error) { - this.logger.error(error); + return null; } } diff --git a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts index e5f0dbc9..765e9cf3 100644 --- a/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts +++ b/src/api/integrations/chatwoot/utils/chatwoot-import-helper.ts @@ -289,7 +289,12 @@ class ChatwootImport { this.deleteHistoryMessages(instance); this.deleteRepositoryMessagesCache(instance); - this.importHistoryContacts(instance, provider); + const providerData: ChatwootDto = { + ...provider, + ignoreJids: Array.isArray(provider.ignoreJids) ? provider.ignoreJids.map((event) => String(event)) : [], + }; + + this.importHistoryContacts(instance, providerData); return totalMessagesImported; } catch (error) { diff --git a/src/api/integrations/dify/services/dify.service.ts b/src/api/integrations/dify/services/dify.service.ts index 3fb1dca6..7a2cc6ba 100644 --- a/src/api/integrations/dify/services/dify.service.ts +++ b/src/api/integrations/dify/services/dify.service.ts @@ -1101,7 +1101,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.query = contentSplit[2]; + payload.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1118,14 +1118,51 @@ export class DifyService { const message = response?.data?.answer; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1169,7 +1206,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.inputs.query = contentSplit[2]; + payload.inputs.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1186,14 +1223,51 @@ export class DifyService { const message = response?.data?.answer; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1237,7 +1311,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.query = contentSplit[2]; + payload.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1274,14 +1348,51 @@ export class DifyService { const message = response?.data?.answer; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1329,7 +1440,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.inputs.query = contentSplit[2]; + payload.inputs.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1346,14 +1457,51 @@ export class DifyService { const message = response?.data?.data.outputs.text; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } if (settings.keepOpen) { await this.prismaRepository.integrationSession.update({ @@ -1506,7 +1654,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.query = contentSplit[2]; + payload.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1523,14 +1671,51 @@ export class DifyService { const message = response?.data?.answer; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1574,7 +1759,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.inputs.query = contentSplit[2]; + payload.inputs.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1591,14 +1776,51 @@ export class DifyService { const message = response?.data?.answer; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1642,7 +1864,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.query = contentSplit[2]; + payload.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1741,7 +1963,7 @@ export class DifyService { url: contentSplit[1].split('?')[0], }, ]; - payload.inputs.query = contentSplit[2]; + payload.inputs.query = contentSplit[2] || content; } await instance.client.presenceSubscribe(remoteJid); @@ -1758,14 +1980,51 @@ export class DifyService { const message = response?.data?.data.outputs.text; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } if (settings.keepOpen) { await this.prismaRepository.integrationSession.update({ diff --git a/src/api/integrations/openai/services/openai.service.ts b/src/api/integrations/openai/services/openai.service.ts index 501f08f8..45310162 100644 --- a/src/api/integrations/openai/services/openai.service.ts +++ b/src/api/integrations/openai/services/openai.service.ts @@ -874,11 +874,27 @@ export class OpenaiService { : msg?.message?.audioMessage ? `audioMessage|${mediaId}` : undefined, - imageMessage: msg?.message?.imageMessage ? `imageMessage|${mediaId}` : undefined, - videoMessage: msg?.message?.videoMessage ? `videoMessage|${mediaId}` : undefined, - documentMessage: msg?.message?.documentMessage ? `documentMessage|${mediaId}` : undefined, - documentWithCaptionMessage: msg?.message?.auddocumentWithCaptionMessageioMessage - ? `documentWithCaptionMessage|${mediaId}` + imageMessage: msg?.message?.imageMessage + ? `imageMessage|${mediaId}${ + msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : '' + }` + : undefined, + videoMessage: msg?.message?.videoMessage + ? `videoMessage|${mediaId}${ + msg?.message?.videoMessage?.caption ? `|${msg?.message?.videoMessage?.caption}` : '' + }` + : undefined, + documentMessage: msg?.message?.documentMessage + ? `documentMessage|${mediaId}${ + msg?.message?.documentMessage?.caption ? `|${msg?.message?.documentMessage?.caption}` : '' + }` + : undefined, + documentWithCaptionMessage: msg?.message?.documentWithCaptionMessage?.message?.documentMessage + ? `documentWithCaptionMessage|${mediaId}${ + msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption + ? `|${msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption}` + : '' + }` : undefined, }; @@ -1303,10 +1319,28 @@ export class OpenaiService { session = data.session; } - await this.client.beta.threads.messages.create(data.session.sessionId, { + const messageData: any = { role: 'user', - content, - }); + content: [{ type: 'text', text: content }], + }; + + if (this.isImageMessage(content)) { + const contentSplit = content.split('|'); + + const url = contentSplit[1].split('?')[0]; + + messageData.content = [ + { type: 'text', text: contentSplit[2] || content }, + { + type: 'image_url', + image_url: { + url: url, + }, + }, + ]; + } + + await this.client.beta.threads.messages.create(data.session.sessionId, messageData); const runAssistant = await this.client.beta.threads.runs.create(data.session.sessionId, { assistant_id: openaiBot.assistantId, @@ -1322,14 +1356,51 @@ export class OpenaiService { const message = response?.data[0].content[0].text.value; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1420,6 +1491,10 @@ export class OpenaiService { } } + private isImageMessage(content: string) { + return content.includes('imageMessage'); + } + private async processOpenaiAssistant( instance: any, remoteJid: string, @@ -1531,10 +1606,28 @@ export class OpenaiService { const threadId = session.sessionId; - await this.client.beta.threads.messages.create(threadId, { + const messageData: any = { role: 'user', - content, - }); + content: [{ type: 'text', text: content }], + }; + + if (this.isImageMessage(content)) { + const contentSplit = content.split('|'); + + const url = contentSplit[1].split('?')[0]; + + messageData.content = [ + { type: 'text', text: contentSplit[2] || content }, + { + type: 'image_url', + image_url: { + url: url, + }, + }, + ]; + } + + await this.client.beta.threads.messages.create(threadId, messageData); const runAssistant = await this.client.beta.threads.runs.create(threadId, { assistant_id: openaiBot.assistantId, @@ -1550,14 +1643,51 @@ export class OpenaiService { const message = response?.data[0].content[0].text.value; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1654,15 +1784,28 @@ export class OpenaiService { }; }); - const messages: any[] = [ - ...messagesSystem, - ...messagesAssistant, - ...messagesUser, - { - role: 'user', - content: content, - }, - ]; + const messageData: any = { + role: 'user', + content: [{ type: 'text', text: content }], + }; + + if (this.isImageMessage(content)) { + const contentSplit = content.split('|'); + + const url = contentSplit[1].split('?')[0]; + + messageData.content = [ + { type: 'text', text: contentSplit[2] || content }, + { + type: 'image_url', + image_url: { + url: url, + }, + }, + ]; + } + + const messages: any[] = [...messagesSystem, ...messagesAssistant, ...messagesUser, messageData]; await instance.client.presenceSubscribe(remoteJid); @@ -1678,14 +1821,51 @@ export class OpenaiService { const message = completions.choices[0].message.content; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { @@ -1838,15 +2018,28 @@ export class OpenaiService { }; }); - const messages: any[] = [ - ...messagesSystem, - ...messagesAssistant, - ...messagesUser, - { - role: 'user', - content: content, - }, - ]; + const messageData: any = { + role: 'user', + content: [{ type: 'text', text: content }], + }; + + if (this.isImageMessage(content)) { + const contentSplit = content.split('|'); + + const url = contentSplit[1].split('?')[0]; + + messageData.content = [ + { type: 'text', text: contentSplit[2] || content }, + { + type: 'image_url', + image_url: { + url: url, + }, + }, + ]; + } + + const messages: any[] = [...messagesSystem, ...messagesAssistant, ...messagesUser, messageData]; await instance.client.presenceSubscribe(remoteJid); @@ -1862,14 +2055,51 @@ export class OpenaiService { const message = completions.choices[0].message.content; - await instance.textMessage( - { - number: remoteJid.split('@')[0], - delay: settings?.delayMessage || 1000, - text: message, - }, - false, - ); + const regex = /!?\[(.*?)\]\((.*?)\)/g; + + const result = []; + let lastIndex = 0; + + let match; + while ((match = regex.exec(message)) !== null) { + if (match.index > lastIndex) { + result.push({ text: message.slice(lastIndex, match.index).trim() }); + } + + result.push({ caption: match[1], url: match[2] }); + + lastIndex = regex.lastIndex; + } + + if (lastIndex < message.length) { + result.push({ text: message.slice(lastIndex).trim() }); + } + + for (const item of result) { + if (item.text) { + await instance.textMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + text: item.text, + }, + false, + ); + } + + if (item.url) { + await instance.mediaMessage( + { + number: remoteJid.split('@')[0], + delay: settings?.delayMessage || 1000, + mediatype: 'image', + media: item.url, + caption: item.caption, + }, + false, + ); + } + } await this.prismaRepository.integrationSession.update({ where: { diff --git a/src/api/integrations/s3/libs/minio.server.ts b/src/api/integrations/s3/libs/minio.server.ts index e432555b..70869cd8 100644 --- a/src/api/integrations/s3/libs/minio.server.ts +++ b/src/api/integrations/s3/libs/minio.server.ts @@ -21,6 +21,7 @@ const minioClient = (() => { useSSL: BUCKET.USE_SSL, accessKey: BUCKET.ACCESS_KEY, secretKey: BUCKET.SECRET_KEY, + region: BUCKET.REGION, }); } })(); diff --git a/src/api/services/channels/whatsapp.baileys.service.ts b/src/api/services/channels/whatsapp.baileys.service.ts index 5294cb29..07d1c455 100644 --- a/src/api/services/channels/whatsapp.baileys.service.ts +++ b/src/api/services/channels/whatsapp.baileys.service.ts @@ -68,7 +68,6 @@ import { S3, Typebot, } from '@config/env.config'; -import { INSTANCE_DIR } from '@config/path.config'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions'; import ffmpegPath from '@ffmpeg-installer/ffmpeg'; import { Boom } from '@hapi/boom'; @@ -98,6 +97,7 @@ import makeWASocket, { GroupParticipant, isJidBroadcast, isJidGroup, + // isJidNewsletter, isJidUser, makeCacheableSignalKeyStore, MessageUpsertType, @@ -118,10 +118,8 @@ import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; import { isBase64, isURL } from 'class-validator'; import { randomBytes } from 'crypto'; import EventEmitter2 from 'eventemitter2'; -// import { exec } from 'child_process'; import ffmpeg from 'fluent-ffmpeg'; -// import ffmpeg from 'fluent-ffmpeg'; -import { existsSync, readFileSync } from 'fs'; +import { readFileSync } from 'fs'; import Long from 'long'; import mime from 'mime'; import NodeCache from 'node-cache'; @@ -148,7 +146,7 @@ export class BaileysStartupService extends ChannelStartupService { ) { super(configService, eventEmitter, prismaRepository, chatwootCache); this.instance.qrcode = { count: 0 }; - this.recoveringMessages(); + // this.recoveringMessages(); this.authStateProvider = new AuthStateProvider(this.providerFiles); } @@ -163,57 +161,57 @@ export class BaileysStartupService extends ChannelStartupService { public phoneNumber: string; - private async recoveringMessages() { - const cacheConf = this.configService.get('CACHE'); + // private async recoveringMessages() { + // const cacheConf = this.configService.get('CACHE'); - if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { - this.logger.info('Recovering messages lost from cache'); - setInterval(async () => { - this.baileysCache.keys().then((keys) => { - keys.forEach(async (key) => { - const data = await this.baileysCache.get(key.split(':')[2]); + // if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + // this.logger.info('Recovering messages lost from cache'); + // setInterval(async () => { + // this.baileysCache.keys().then((keys) => { + // keys.forEach(async (key) => { + // const data = await this.baileysCache.get(key.split(':')[2]); - let message: any; - let retry: number; + // let message: any; + // let retry: number; - if (!data?.message) { - message = data; - retry = 0; - } else { - message = data.message; - retry = data.retry; - } + // if (!data?.message) { + // message = data; + // retry = 0; + // } else { + // message = data.message; + // retry = data.retry; + // } - if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { - retry = retry + 1; - this.logger.info(`Message absent from node, retrying to send, key: ${key.split(':')[2]} retry: ${retry}`); - if (message.messageStubParameters[1]) { - await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); - } + // if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') { + // retry = retry + 1; + // this.logger.info(`Message absent from node, retrying to send, key: ${key.split(':')[2]} retry: ${retry}`); + // if (message.messageStubParameters[1]) { + // await this.client.sendMessageAck(JSON.parse(message.messageStubParameters[1], BufferJSON.reviver)); + // } - this.baileysCache.set(key.split(':')[2], { message, retry }); + // this.baileysCache.set(key.split(':')[2], { message, retry }); - if (retry >= 100) { - this.logger.warn(`Message absent from node, retry limit reached, key: ${key.split(':')[2]}`); - this.baileysCache.delete(key.split(':')[2]); - return; - } - } - }); - }); - // 15 minutes - }, 15 * 60 * 1000); - } - } + // if (retry >= 100) { + // this.logger.warn(`Message absent from node, retry limit reached, key: ${key.split(':')[2]}`); + // this.baileysCache.delete(key.split(':')[2]); + // return; + // } + // } + // }); + // }); + // // 15 minutes + // }, 15 * 60 * 1000); + // } + // } - private async forceUpdateGroupMetadataCache() { - this.logger.verbose('Force update group metadata cache'); - const groups = await this.fetchAllGroups({ getParticipants: 'false' }); + // private async forceUpdateGroupMetadataCache() { + // this.logger.verbose('Force update group metadata cache'); + // const groups = await this.fetchAllGroups({ getParticipants: 'false' }); - for (const group of groups) { - await this.updateGroupMetadataCache(group.id); - } - } + // for (const group of groups) { + // await this.updateGroupMetadataCache(group.id); + // } + // } public get connectionStatus() { return this.stateConnection; @@ -239,21 +237,12 @@ export class BaileysStartupService extends ChannelStartupService { public async getProfileName() { let profileName = this.client.user?.name ?? this.client.user?.verifiedName; if (!profileName) { - if (this.configService.get('DATABASE').ENABLED) { - const data = await this.prismaRepository.session.findUnique({ - where: { sessionId: this.instanceId }, - }); + const data = await this.prismaRepository.session.findUnique({ + where: { sessionId: this.instanceId }, + }); - if (data) { - const creds = JSON.parse(JSON.stringify(data.creds), BufferJSON.reviver); - profileName = creds.me?.name || creds.me?.verifiedName; - } - } else if (existsSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'))) { - const creds = JSON.parse( - readFileSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'), { - encoding: 'utf-8', - }), - ); + if (data) { + const creds = JSON.parse(JSON.stringify(data.creds), BufferJSON.reviver); profileName = creds.me?.name || creds.me?.verifiedName; } } @@ -404,17 +393,15 @@ export class BaileysStartupService extends ChannelStartupService { disconnectionObject: JSON.stringify(lastDisconnect), }); - if (this.configService.get('DATABASE').ENABLED) { - await this.prismaRepository.instance.update({ - where: { id: this.instanceId }, - data: { - connectionStatus: 'close', - disconnectionAt: new Date(), - disconnectionReasonCode: statusCode, - disconnectionObject: JSON.stringify(lastDisconnect), - }, - }); - } + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { + connectionStatus: 'close', + disconnectionAt: new Date(), + disconnectionReasonCode: statusCode, + disconnectionObject: JSON.stringify(lastDisconnect), + }, + }); if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled) { this.chatwootService.eventWhatsapp( @@ -462,17 +449,15 @@ export class BaileysStartupService extends ChannelStartupService { `, ); - if (this.configService.get('DATABASE').ENABLED) { - await this.prismaRepository.instance.update({ - where: { id: this.instanceId }, - data: { - ownerJid: this.instance.wuid, - profileName: (await this.getProfileName()) as string, - profilePicUrl: this.instance.profilePictureUrl, - connectionStatus: 'open', - }, - }); - } + await this.prismaRepository.instance.update({ + where: { id: this.instanceId }, + data: { + ownerJid: this.instance.wuid, + profileName: (await this.getProfileName()) as string, + profilePicUrl: this.instance.profilePictureUrl, + connectionStatus: 'open', + }, + }); if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot.enabled) { this.chatwootService.eventWhatsapp( @@ -520,6 +505,7 @@ export class BaileysStartupService extends ChannelStartupService { return webMessageInfo[0].message; } catch (error) { + this.logger.error('line 508'); return { conversation: '' }; } } @@ -539,7 +525,7 @@ export class BaileysStartupService extends ChannelStartupService { return await useMultiFileAuthStateRedisDb(this.instance.id, this.cache); } - if (db.SAVE_DATA.INSTANCE && db.ENABLED) { + if (db.SAVE_DATA.INSTANCE) { return await useMultiFileAuthStatePrisma(this.instance.id, this.cache); } } @@ -619,56 +605,40 @@ export class BaileysStartupService extends ChannelStartupService { const socketConfig: UserFacingSocketConfig = { ...options, + version, + logger: P({ level: this.logBaileys }), + printQRInTerminal: false, auth: { creds: this.instance.authState.state.creds, keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, + msgRetryCounterCache: this.msgRetryCounterCache, + generateHighQualityLinkPreview: true, + getMessage: async (key) => (await this.getMessage(key)) as Promise, ...browserOptions, - version, markOnlineOnConnect: this.localSettings.alwaysOnline, retryRequestDelayMs: 350, maxMsgRetryCount: 4, fireInitQueries: true, - connectTimeoutMs: 20_000, + connectTimeoutMs: 30_000, keepAliveIntervalMs: 30_000, qrTimeout: 45_000, emitOwnEvents: false, shouldIgnoreJid: (jid) => { const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); - const isNewsletter = jid ? jid.includes('newsletter') : false; + // const isNewsletter = !isJidNewsletter(jid); + const isNewsletter = jid && jid.includes('newsletter'); return isGroupJid || isBroadcast || isNewsletter; }, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, syncFullHistory: this.localSettings.syncFullHistory, shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => { return this.historySyncNotification(msg); }, + // cachedGroupMetadata: this.getGroupMetadataCache, userDevicesCache: this.userDevicesCache, transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 }, - patchMessageBeforeSending(message) { - if ( - message.deviceSentMessage?.message?.listMessage?.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST - ) { - message = JSON.parse(JSON.stringify(message)); - - message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) { - message = JSON.parse(JSON.stringify(message)); - - message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT; - } - - return message; - }, - forceGroupsPrekeys: false, }; this.endSession = false; @@ -693,6 +663,7 @@ export class BaileysStartupService extends ChannelStartupService { return await this.createClient(number); } catch (error) { + this.logger.error('line 667'); this.logger.error(error); throw new InternalServerErrorException(error?.toString()); } @@ -702,6 +673,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.createClient(this.phoneNumber); } catch (error) { + this.logger.error('line 677'); this.logger.error(error); throw new InternalServerErrorException(error?.toString()); } @@ -819,7 +791,7 @@ export class BaileysStartupService extends ChannelStartupService { if (updatedContacts.length > 0) { await Promise.all( - updatedContacts.map(async function (contact) { + updatedContacts.map(async (contact) => { const update = this.prismaRepository.contact.updateMany({ where: { remoteJid: contact.remoteJid, instanceId: this.instanceId }, data: { @@ -829,7 +801,7 @@ export class BaileysStartupService extends ChannelStartupService { const instance = { instanceName: this.instance.name, instanceId: this.instance.id }; - const findParticipant = await this.findContact(instance, contact.remoteJid.split('@')[0]); + const findParticipant = await this.chatwootService.findContact(instance, contact.remoteJid.split('@')[0]); this.chatwootService.updateContact(instance, findParticipant.id, { name: contact.pushName, @@ -841,6 +813,7 @@ export class BaileysStartupService extends ChannelStartupService { ); } } catch (error) { + this.logger.error('line 817'); this.logger.error(`Error: ${error.message}`); } }, @@ -879,13 +852,25 @@ export class BaileysStartupService extends ChannelStartupService { messages, chats, contacts, + isLatest, + progress, + syncType, }: { chats: Chat[]; contacts: Contact[]; messages: proto.IWebMessageInfo[]; - isLatest: boolean; + isLatest?: boolean; + progress?: number; + syncType?: proto.HistorySync.HistorySyncType; }) => { try { + if (syncType === proto.HistorySync.HistorySyncType.ON_DEMAND) { + console.log('received on-demand history sync, messages=', messages); + } + console.log( + `recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest}, progress: ${progress}%), type: ${syncType}`, + ); + const instance: InstanceDto = { instanceName: this.instance.name }; let timestampLimitToImport = null; @@ -1022,6 +1007,7 @@ export class BaileysStartupService extends ChannelStartupService { messages = undefined; chats = undefined; } catch (error) { + this.logger.error('line 1011'); this.logger.error(error); } }, @@ -1030,14 +1016,31 @@ export class BaileysStartupService extends ChannelStartupService { { messages, type, + requestId, }: { messages: proto.IWebMessageInfo[]; type: MessageUpsertType; + requestId?: string; }, settings: any, ) => { try { for (const received of messages) { + if (received.message?.conversation || received.message?.extendedTextMessage?.text) { + const text = received.message?.conversation || received.message?.extendedTextMessage?.text; + if (text == 'requestPlaceholder' && !requestId) { + // const messageId = await this.client.requestPlaceholderResend(received.key); + // console.log('requested placeholder resync, id=', messageId); + } else if (requestId) { + console.log('Message received from phone, id=', requestId, received); + } + + if (text == 'onDemandHistSync') { + // const messageId = await this.client.fetchMessageHistory(50, received.key, received.messageTimestamp!); + // console.log('requested on-demand sync, id=', messageId); + } + } + if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { const editedMessage = received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage; @@ -1156,7 +1159,7 @@ export class BaileysStartupService extends ChannelStartupService { const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName); - await s3Service.uploadFile(fullName, buffer, size.fileLength, { + await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, { 'Content-Type': mimetype, }); @@ -1174,6 +1177,7 @@ export class BaileysStartupService extends ChannelStartupService { messageRaw.message.mediaUrl = mediaUrl; } catch (error) { + this.logger.error('line 1181'); this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); } } @@ -1310,6 +1314,7 @@ export class BaileysStartupService extends ChannelStartupService { }); } } catch (error) { + this.logger.error('line 1318'); this.logger.error(error); } }, @@ -1488,7 +1493,7 @@ export class BaileysStartupService extends ChannelStartupService { data: { association: LabelAssociation; type: 'remove' | 'add' }, database: Database, ) => { - if (database.ENABLED && database.SAVE_DATA.CHATS) { + if (database.SAVE_DATA.CHATS) { const chats = await this.prismaRepository.chat.findMany({ where: { instanceId: this.instanceId }, }); @@ -1511,7 +1516,6 @@ export class BaileysStartupService extends ChannelStartupService { } } - // Envia dados para o webhook this.sendDataWebhook(Events.LABELS_ASSOCIATION, { instance: this.instance.name, type: data.type, @@ -1558,7 +1562,7 @@ export class BaileysStartupService extends ChannelStartupService { if (events['messaging-history.set']) { const payload = events['messaging-history.set']; - this.messageHandle['messaging-history.set'](payload as any); + this.messageHandle['messaging-history.set'](payload); } if (events['messages.upsert']) { @@ -1670,9 +1674,9 @@ export class BaileysStartupService extends ChannelStartupService { public async profilePicture(number: string) { const jid = this.createJid(number); - const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image'); - try { + const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image'); + return { wuid: jid, profilePictureUrl, @@ -1771,11 +1775,11 @@ export class BaileysStartupService extends ChannelStartupService { }; if (isJidGroup(sender)) { + option.useCachedGroupMetadata = true; if (participants) option.cachedGroupMetadata = async () => { return { participants: participants as GroupParticipant[] }; }; - else option.cachedGroupMetadata = this.getGroupMetadataCache; } if (ephemeralExpiration) option.ephemeralExpiration = ephemeralExpiration; @@ -2002,7 +2006,7 @@ export class BaileysStartupService extends ChannelStartupService { quoted, null, group?.ephemeralDuration, - group.participants, + // group?.participants, ); } else { messageSent = await this.sendMessage(sender, message, mentions, linkPreview, quoted); @@ -2073,6 +2077,7 @@ export class BaileysStartupService extends ChannelStartupService { return messageSent; } catch (error) { + this.logger.error('line 2081'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2125,6 +2130,7 @@ export class BaileysStartupService extends ChannelStartupService { return { presence: data.presence }; } catch (error) { + this.logger.error('line 2134'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2137,6 +2143,7 @@ export class BaileysStartupService extends ChannelStartupService { return { presence: data.presence }; } catch (error) { + this.logger.error('line 2147'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -2367,6 +2374,7 @@ export class BaileysStartupService extends ChannelStartupService { { userJid: this.instance.wuid }, ); } catch (error) { + this.logger.error('line 2378'); this.logger.error(error); throw new InternalServerErrorException(error?.toString() || error); } @@ -2408,6 +2416,7 @@ export class BaileysStartupService extends ChannelStartupService { return webpBuffer; } catch (error) { + this.logger.error('line 2420'); console.error('Erro ao converter a imagem para WebP:', error); throw error; } @@ -2805,6 +2814,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.readMessages(keys); return { message: 'Read messages', read: 'success' }; } catch (error) { + this.logger.error('line 2818'); throw new InternalServerErrorException('Read messages fail', error.toString()); } } @@ -2870,6 +2880,7 @@ export class BaileysStartupService extends ChannelStartupService { archived: true, }; } catch (error) { + this.logger.error('line 2884'); throw new InternalServerErrorException({ archived: false, message: ['An error occurred while archiving the chat. Open a calling.', error.toString()], @@ -2907,6 +2918,7 @@ export class BaileysStartupService extends ChannelStartupService { markedChatUnread: true, }; } catch (error) { + this.logger.error('line 2922'); throw new InternalServerErrorException({ markedChatUnread: false, message: ['An error occurred while marked unread the chat. Open a calling.', error.toString()], @@ -2918,6 +2930,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.client.sendMessage(del.remoteJid, { delete: del }); } catch (error) { + this.logger.error('line 2934'); throw new InternalServerErrorException('Error while deleting message for everyone', error?.toString()); } } @@ -3009,6 +3022,7 @@ export class BaileysStartupService extends ChannelStartupService { buffer: getBuffer ? buffer : null, }; } catch (error) { + this.logger.error('line 3026'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3050,6 +3064,7 @@ export class BaileysStartupService extends ChannelStartupService { }, }; } catch (error) { + this.logger.error('line 3068'); throw new InternalServerErrorException('Error updating privacy settings', error.toString()); } } @@ -3075,6 +3090,7 @@ export class BaileysStartupService extends ChannelStartupService { ...profile, }; } catch (error) { + this.logger.error('line 3094'); throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -3085,6 +3101,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3105'); throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -3095,6 +3112,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3116'); throw new InternalServerErrorException('Error updating profile status', error.toString()); } } @@ -3136,6 +3154,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3158'); throw new InternalServerErrorException('Error updating profile picture', error.toString()); } } @@ -3148,6 +3167,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3171'); throw new InternalServerErrorException('Error removing profile picture', error.toString()); } } @@ -3168,6 +3188,7 @@ export class BaileysStartupService extends ChannelStartupService { return { block: 'success' }; } catch (error) { + this.logger.error('line 3192'); throw new InternalServerErrorException('Error blocking user', error.toString()); } } @@ -3198,6 +3219,7 @@ export class BaileysStartupService extends ChannelStartupService { return null; } catch (error) { + this.logger.error('line 3223'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3219,6 +3241,7 @@ export class BaileysStartupService extends ChannelStartupService { edit: data.key, }); } catch (error) { + this.logger.error('line 3245'); this.logger.error(error); throw new BadRequestException(error.toString()); } @@ -3261,6 +3284,7 @@ export class BaileysStartupService extends ChannelStartupService { return { numberJid: contact.jid, labelId: data.labelId, remove: true }; } } catch (error) { + this.logger.error('line 3288'); throw new BadRequestException(`Unable to ${data.action} label to chat`, error.toString()); } } @@ -3270,14 +3294,19 @@ export class BaileysStartupService extends ChannelStartupService { try { const meta = await this.client.groupMetadata(groupJid); - this.logger.verbose(`Updating cache for group: ${groupJid}`); - await groupMetadataCache.set(groupJid, { - timestamp: Date.now(), - data: meta, - }); + const cacheConf = this.configService.get('CACHE'); + + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + this.logger.verbose(`Updating cache for group: ${groupJid}`); + await groupMetadataCache.set(groupJid, { + timestamp: Date.now(), + data: meta, + }); + } return meta; } catch (error) { + this.logger.error('line 3310'); this.logger.error(error); return null; } @@ -3286,19 +3315,25 @@ export class BaileysStartupService extends ChannelStartupService { private async getGroupMetadataCache(groupJid: string) { if (!isJidGroup(groupJid)) return null; - if (await groupMetadataCache.has(groupJid)) { - console.log(`Cache request for group: ${groupJid}`); - const meta = await groupMetadataCache.get(groupJid); + const cacheConf = this.configService.get('CACHE'); - if (Date.now() - meta.timestamp > 3600000) { - await this.updateGroupMetadataCache(groupJid); + if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) { + if (await groupMetadataCache.has(groupJid)) { + console.log(`Cache request for group: ${groupJid}`); + const meta = await groupMetadataCache.get(groupJid); + + if (Date.now() - meta.timestamp > 3600000) { + await this.updateGroupMetadataCache(groupJid); + } + + return meta.data; } - return meta.data; + console.log(`Cache request for group: ${groupJid} - not found`); + return await this.updateGroupMetadataCache(groupJid); } - console.log(`Cache request for group: ${groupJid} - not found`); - return await this.updateGroupMetadataCache(groupJid); + return await this.findGroup({ groupJid }, 'inner'); } public async createGroup(create: CreateGroupDto) { @@ -3324,6 +3359,7 @@ export class BaileysStartupService extends ChannelStartupService { return group; } catch (error) { + this.logger.error('line 3363'); this.logger.error(error); throw new InternalServerErrorException('Error creating group', error.toString()); } @@ -3363,6 +3399,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3403'); throw new InternalServerErrorException('Error update group picture', error.toString()); } } @@ -3373,6 +3410,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3414'); throw new InternalServerErrorException('Error updating group subject', error.toString()); } } @@ -3383,6 +3421,7 @@ export class BaileysStartupService extends ChannelStartupService { return { update: 'success' }; } catch (error) { + this.logger.error('line 3425'); throw new InternalServerErrorException('Error updating group description', error.toString()); } } @@ -3417,6 +3456,7 @@ export class BaileysStartupService extends ChannelStartupService { if (reply === 'inner') { return; } + this.logger.error('line 3460'); throw new NotFoundException('Error fetching group', error.toString()); } } @@ -3426,8 +3466,7 @@ export class BaileysStartupService extends ChannelStartupService { let groups = []; for (const group of fetch) { - const picture = null; - // const picture = await this.profilePicture(group.id); + const picture = await this.profilePicture(group.id); const result = { id: group.id, @@ -3459,6 +3498,7 @@ export class BaileysStartupService extends ChannelStartupService { const code = await this.client.groupInviteCode(id.groupJid); return { inviteUrl: `https://chat.whatsapp.com/${code}`, inviteCode: code }; } catch (error) { + this.logger.error('line 3502'); throw new NotFoundException('No invite code', error.toString()); } } @@ -3467,6 +3507,7 @@ export class BaileysStartupService extends ChannelStartupService { try { return await this.client.groupGetInviteInfo(id.inviteCode); } catch (error) { + this.logger.error('line 3511'); throw new NotFoundException('No invite info', id.inviteCode); } } @@ -3492,6 +3533,7 @@ export class BaileysStartupService extends ChannelStartupService { return { send: true, inviteUrl }; } catch (error) { + this.logger.error('line 3537'); throw new NotFoundException('No send invite'); } } @@ -3501,6 +3543,7 @@ export class BaileysStartupService extends ChannelStartupService { const groupJid = await this.client.groupAcceptInvite(id.inviteCode); return { accepted: true, groupJid: groupJid }; } catch (error) { + this.logger.error('line 3547'); throw new NotFoundException('Accept invite error', error.toString()); } } @@ -3510,6 +3553,7 @@ export class BaileysStartupService extends ChannelStartupService { const inviteCode = await this.client.groupRevokeInvite(id.groupJid); return { revoked: true, inviteCode }; } catch (error) { + this.logger.error('line 3557'); throw new NotFoundException('Revoke error', error.toString()); } } @@ -3535,6 +3579,7 @@ export class BaileysStartupService extends ChannelStartupService { }); return { participants: parsedParticipants }; } catch (error) { + this.logger.error('line 3583'); throw new NotFoundException('No participants', error.toString()); } } @@ -3549,6 +3594,7 @@ export class BaileysStartupService extends ChannelStartupService { ); return { updateParticipants: updateParticipants }; } catch (error) { + this.logger.error('line 3598'); throw new BadRequestException('Error updating participants', error.toString()); } } @@ -3558,6 +3604,7 @@ export class BaileysStartupService extends ChannelStartupService { const updateSetting = await this.client.groupSettingUpdate(update.groupJid, update.action); return { updateSetting: updateSetting }; } catch (error) { + this.logger.error('line 3608'); throw new BadRequestException('Error updating setting', error.toString()); } } @@ -3567,6 +3614,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.groupToggleEphemeral(update.groupJid, update.expiration); return { success: true }; } catch (error) { + this.logger.error('line 3618'); throw new BadRequestException('Error updating setting', error.toString()); } } @@ -3576,6 +3624,7 @@ export class BaileysStartupService extends ChannelStartupService { await this.client.groupLeave(id.groupJid); return { groupJid: id.groupJid, leave: true }; } catch (error) { + this.logger.error('line 3628'); throw new BadRequestException('Unable to leave the group', error.toString()); } } diff --git a/src/api/services/monitor.service.ts b/src/api/services/monitor.service.ts index 07c7646c..de06cfe8 100644 --- a/src/api/services/monitor.service.ts +++ b/src/api/services/monitor.service.ts @@ -121,7 +121,7 @@ export class WAMonitoringService { public async cleaningUp(instanceName: string) { let instanceDbId: string; - if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + if (this.db.SAVE_DATA.INSTANCE) { const instance = await this.prismaRepository.instance.update({ where: { name: instanceName }, data: { connectionStatus: 'close' }, @@ -181,7 +181,7 @@ export class WAMonitoringService { try { if (this.providerSession?.ENABLED) { await this.loadInstancesFromProvider(); - } else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + } else if (this.db.SAVE_DATA.INSTANCE) { await this.loadInstancesFromDatabasePostgres(); } else if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) { await this.loadInstancesFromRedis(); @@ -193,21 +193,19 @@ export class WAMonitoringService { public async saveInstance(data: any) { try { - if (this.db.ENABLED) { - const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; - await this.prismaRepository.instance.create({ - data: { - id: data.instanceId, - name: data.instanceName, - connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BUSINESS ? 'open' : 'close', - number: data.number, - integration: data.integration || Integration.WHATSAPP_BAILEYS, - token: data.hash, - clientName: clientName, - businessId: data.businessId, - }, - }); - } + const clientName = await this.configService.get('DATABASE').CONNECTION.CLIENT_NAME; + await this.prismaRepository.instance.create({ + data: { + id: data.instanceId, + name: data.instanceName, + connectionStatus: data.integration && data.integration === Integration.WHATSAPP_BUSINESS ? 'open' : 'close', + number: data.number, + integration: data.integration || Integration.WHATSAPP_BAILEYS, + token: data.hash, + clientName: clientName, + businessId: data.businessId, + }, + }); } catch (error) { this.logger.error(error); } diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 3bebc8dc..f3e3f0ae 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -51,7 +51,6 @@ export type DBConnection = { }; export type Database = { CONNECTION: DBConnection; - ENABLED: boolean; PROVIDER: string; SAVE_DATA: SaveData; }; @@ -202,6 +201,7 @@ export type S3 = { ENABLE: boolean; PORT?: number; USE_SSL?: boolean; + REGION?: string; }; export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal }; @@ -285,7 +285,6 @@ export class ConfigService { URI: process.env.DATABASE_CONNECTION_URI || '', CLIENT_NAME: process.env.DATABASE_CONNECTION_CLIENT_NAME || 'evolution', }, - ENABLED: process.env?.DATABASE_ENABLED === 'true', PROVIDER: process.env.DATABASE_PROVIDER || 'postgresql', SAVE_DATA: { INSTANCE: process.env?.DATABASE_SAVE_DATA_INSTANCE === 'true', @@ -464,6 +463,7 @@ export class ConfigService { ENABLE: process.env?.S3_ENABLED === 'true', PORT: Number.parseInt(process.env?.S3_PORT || '9000'), USE_SSL: process.env?.S3_USE_SSL === 'true', + REGION: process.env?.S3_REGION, }, AUTHENTICATION: { API_KEY: { diff --git a/src/libs/prisma.connect.ts b/src/libs/prisma.connect.ts index 849d26e2..fa8d6600 100644 --- a/src/libs/prisma.connect.ts +++ b/src/libs/prisma.connect.ts @@ -1,21 +1,16 @@ -import { configService, Database } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { PrismaClient } from '@prisma/client'; const logger = new Logger('Prisma'); -const db = configService.get('DATABASE'); - export const prismaServer = (() => { - if (db.ENABLED) { - logger.verbose('connecting'); - const db = new PrismaClient(); + logger.verbose('connecting'); + const db = new PrismaClient(); - process.on('beforeExit', () => { - logger.verbose('instance destroyed'); - db.$disconnect(); - }); + process.on('beforeExit', () => { + logger.verbose('instance destroyed'); + db.$disconnect(); + }); - return db; - } + return db; })(); diff --git a/src/main.ts b/src/main.ts index 1993ea43..08594819 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,3 @@ -import 'express-async-errors'; - import { initAMQP, initGlobalQueues } from '@api/integrations/rabbitmq/libs/amqp.server'; import { initSQS } from '@api/integrations/sqs/libs/sqs.server'; import { ProviderFiles } from '@api/provider/sessions'; @@ -10,6 +8,7 @@ import { Auth, configService, Cors, HttpServer, ProviderSession, Rabbitmq, Sqs, import { onUnexpectedError } from '@config/error.config'; import { Logger } from '@config/logger.config'; import { ROOT_DIR } from '@config/path.config'; +import * as Sentry from '@sentry/node'; import { ServerUP } from '@utils/server-up'; import axios from 'axios'; import compression from 'compression'; @@ -24,6 +23,19 @@ function initWA() { async function bootstrap() { const logger = new Logger('SERVER'); const app = express(); + const dsn = process.env.SENTRY_DSN; + + if (dsn) { + logger.info('Sentry - ON'); + Sentry.init({ + dsn: dsn, + environment: process.env.NODE_ENV || 'development', + tracesSampleRate: 1.0, + }); + app.use(Sentry.Handlers.requestHandler()); + app.use(Sentry.Handlers.tracingHandler()); + app.use(Sentry.Handlers.errorHandler()); + } let providerFiles: ProviderFiles = null; if (configService.get('PROVIDER').ENABLED) { diff --git a/tsup.config.ts b/tsup.config.ts index 8ea3c3a7..2450b52f 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,3 +1,5 @@ +import { cpSync } from 'node:fs'; + import { defineConfig } from 'tsup'; export default defineConfig({ @@ -8,4 +10,10 @@ export default defineConfig({ clean: true, minify: true, format: ['cjs', 'esm'], + onSuccess: async () => { + cpSync('src/utils/translations', 'dist/translations', { recursive: true }); + }, + loader: { + '.json': 'file', + }, });