Compare commits

...

9 Commits

Author SHA1 Message Date
Thiago Borges
28036a811d correção 2025-07-09 14:24:51 -03:00
Thiago Borges
3bdcf2c641 prod 2025-07-09 13:22:17 -03:00
Thiago Borges
2bb4843b87 prod 2025-07-09 13:12:44 -03:00
Thiago Borges
091b372997 prod 2025-07-09 12:11:33 -03:00
Thiago Borges
83fd374b06 mudando networks 2025-07-09 11:56:52 -03:00
Thiago Borges
4880d45b41 removendo env do dockerfile 2025-07-09 11:51:44 -03:00
Thiago Borges
68f085a674 removendo env do dockerfile 2025-07-09 11:49:36 -03:00
Thiago Borges
1afaa3d560 versao 2025-07-09 11:37:39 -03:00
Thiago Borges
490ba48501 teste 2025-07-09 11:28:29 -03:00
15 changed files with 550 additions and 369 deletions

View File

@ -19,7 +19,7 @@ COPY ./src ./src
COPY ./public ./public COPY ./public ./public
COPY ./prisma ./prisma COPY ./prisma ./prisma
COPY ./manager ./manager COPY ./manager ./manager
COPY ./.env.example ./.env #COPY ./.env.example ./.env
COPY ./runWithProvider.js ./ COPY ./runWithProvider.js ./
COPY ./tsup.config.ts ./ COPY ./tsup.config.ts ./
@ -59,7 +59,7 @@ COPY --from=builder /evolution/dist ./dist
COPY --from=builder /evolution/prisma ./prisma COPY --from=builder /evolution/prisma ./prisma
COPY --from=builder /evolution/manager ./manager COPY --from=builder /evolution/manager ./manager
COPY --from=builder /evolution/public ./public COPY --from=builder /evolution/public ./public
COPY --from=builder /evolution/.env ./.env #COPY --from=builder /evolution/.env ./.env
COPY --from=builder /evolution/Docker ./Docker COPY --from=builder /evolution/Docker ./Docker
COPY --from=builder /evolution/runWithProvider.js ./runWithProvider.js COPY --from=builder /evolution/runWithProvider.js ./runWithProvider.js
COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts

20
docker-compose.prod.yaml Normal file
View File

@ -0,0 +1,20 @@
version: "3.7"
services:
api:
build:
context: .
dockerfile: Dockerfile
target: final
restart: always
volumes:
- evolution_instances:/evolution/instances
networks:
- Docker
volumes:
evolution_instances:
networks:
Docker: ## Nome da rede interna
external: true
name: Docker ## Nome da rede interna

View File

@ -1,3 +1,4 @@
version: "3.7"
services: services:
api: api:
build: build:
@ -14,16 +15,14 @@ services:
volumes: volumes:
- evolution_instances:/evolution/instances - evolution_instances:/evolution/instances
networks: networks:
- evolution-net - Docker
env_file:
- .env
expose: expose:
- 8080 - 8080
redis: redis:
image: redis:latest image: redis:latest
networks: networks:
- evolution-net - Docker
container_name: redis container_name: redis
command: > command: >
redis-server --port 6379 --appendonly yes redis-server --port 6379 --appendonly yes
@ -36,7 +35,7 @@ services:
container_name: postgres container_name: postgres
image: postgres:15 image: postgres:15
networks: networks:
- evolution-net - Docker
#command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"] #command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"]
restart: always restart: always
ports: ports:
@ -58,6 +57,6 @@ volumes:
networks: networks:
evolution-net: Docker: ## Nome da rede interna
name: evolution-net external: true
driver: bridge name: Docker ## Nome da rede interna

310
package-lock.json generated
View File

@ -14,6 +14,7 @@
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.16", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@nestjs/event-emitter": "^3.0.1",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
"@sentry/node": "^8.47.0", "@sentry/node": "^8.47.0",
@ -1939,6 +1940,170 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
<<<<<<< Updated upstream
=======
<<<<<<< HEAD
"node_modules/@lukeed/csprng": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@nestjs/common": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.3.tgz",
"integrity": "sha512-ogEK+GriWodIwCw6buQ1rpcH4Kx+G7YQ9EwuPySI3rS05pSdtQ++UhucjusSI9apNidv+QURBztJkRecwwJQXg==",
"license": "MIT",
"peer": true,
"dependencies": {
"file-type": "21.0.0",
"iterare": "1.2.1",
"load-esm": "1.0.2",
"tslib": "2.8.1",
"uid": "2.0.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nest"
},
"peerDependencies": {
"class-transformer": ">=0.4.1",
"class-validator": ">=0.13.2",
"reflect-metadata": "^0.1.12 || ^0.2.0",
"rxjs": "^7.1.0"
},
"peerDependenciesMeta": {
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/common/node_modules/file-type": {
"version": "21.0.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz",
"integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/inflate": "^0.2.7",
"strtok3": "^10.2.2",
"token-types": "^6.0.0",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/@nestjs/common/node_modules/strtok3": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.1.tgz",
"integrity": "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@nestjs/common/node_modules/token-types": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.3.tgz",
"integrity": "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@nestjs/core": {
"version": "11.1.3",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz",
"integrity": "sha512-5lTni0TCh8x7bXETRD57pQFnKnEg1T6M+VLE7wAmyQRIecKQU+2inRGZD+A4v2DC1I04eA0WffP0GKLxjOKlzw==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@nuxt/opencollective": "0.4.1",
"fast-safe-stringify": "2.1.1",
"iterare": "1.2.1",
"path-to-regexp": "8.2.0",
"tslib": "2.8.1",
"uid": "2.0.2"
},
"engines": {
"node": ">= 20"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nest"
},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
"@nestjs/microservices": "^11.0.0",
"@nestjs/platform-express": "^11.0.0",
"@nestjs/websockets": "^11.0.0",
"reflect-metadata": "^0.1.12 || ^0.2.0",
"rxjs": "^7.1.0"
},
"peerDependenciesMeta": {
"@nestjs/microservices": {
"optional": true
},
"@nestjs/platform-express": {
"optional": true
},
"@nestjs/websockets": {
"optional": true
}
}
},
"node_modules/@nestjs/core/node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=16"
}
},
"node_modules/@nestjs/event-emitter": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-3.0.1.tgz",
"integrity": "sha512-0Ln/x+7xkU6AJFOcQI9tIhUMXVF7D5itiaQGOyJbXtlAfAIt8gzDdJm+Im7cFzKoWkiW5nCXCPh6GSvdQd/3Dw==",
"license": "MIT",
"dependencies": {
"eventemitter2": "6.4.9"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
=======
>>>>>>> Stashed changes
"node_modules/@keyv/serialize": { "node_modules/@keyv/serialize": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz",
@ -1968,6 +2133,10 @@
"dependencies": { "dependencies": {
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"ieee754": "^1.2.1" "ieee754": "^1.2.1"
<<<<<<< Updated upstream
=======
>>>>>>> 8d6e59598e993cbb039606733568ce94b6004375
>>>>>>> Stashed changes
} }
}, },
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
@ -2016,6 +2185,23 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@nuxt/opencollective": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz",
"integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"consola": "^3.2.3"
},
"bin": {
"opencollective": "bin/opencollective.js"
},
"engines": {
"node": "^14.18.0 || >=16.10.0",
"npm": ">=5.10.0"
}
},
"node_modules/@opentelemetry/api": { "node_modules/@opentelemetry/api": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
@ -3743,6 +3929,43 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@tokenizer/inflate": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
"integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.4.0",
"fflate": "^0.8.2",
"token-types": "^6.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/inflate/node_modules/token-types": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.3.tgz",
"integrity": "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": { "node_modules/@tokenizer/token": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@ -6799,6 +7022,13 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT",
"peer": true
},
"node_modules/fast-xml-parser": { "node_modules/fast-xml-parser": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
@ -6829,6 +7059,13 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT",
"peer": true
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -8067,6 +8304,16 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
}, },
"node_modules/iterare": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
"integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@ -8344,6 +8591,26 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/load-esm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/Borewit"
},
{
"type": "buymeacoffee",
"url": "https://buymeacoffee.com/borewit"
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=13.2.0"
}
},
"node_modules/load-tsconfig": { "node_modules/load-tsconfig": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
@ -10179,6 +10446,13 @@
"@redis/time-series": "1.1.0" "@redis/time-series": "1.1.0"
} }
}, },
"node_modules/reflect-metadata": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@ -10372,6 +10646,16 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-array-concat": { "node_modules/safe-array-concat": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@ -11882,6 +12166,32 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/uid": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",
"integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@lukeed/csprng": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/uint8array-extras": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
"integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",

View File

@ -54,6 +54,7 @@
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.16", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@nestjs/event-emitter": "^3.0.1",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
"@sentry/node": "^8.47.0", "@sentry/node": "^8.47.0",

View File

@ -51,6 +51,8 @@ export class InstanceDto extends IntegrationDto {
chatwootSignMsg?: boolean; chatwootSignMsg?: boolean;
chatwootToken?: string; chatwootToken?: string;
chatwootUrl?: string; chatwootUrl?: string;
phoneNumberId?: string;
name?: string;
} }
export class SetPresenceDto { export class SetPresenceDto {

View File

@ -0,0 +1,49 @@
import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service';
import { Logger } from '@config/logger.config';
import { ChannelController, ChannelControllerInterface } from '../channel.controller';
export class SerproController extends ChannelController implements ChannelControllerInterface {
private readonly logger = new Logger('SerproController');
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
integrationEnabled: boolean;
// OBRIGATÓRIO para a interface!
public async receiveWebhook(data: any) {
// Pode redirecionar para o SERPRO específico
return this.receiveWebhookSerpro(data);
}
public async receiveWebhookSerpro(data: any) {
const numberId = data.metadata?.display_phone_number || data.display_phone_number || '552121996300';
if (!numberId) {
this.logger.error('WebhookService -> receiveWebhookSerpro -> numberId not found');
return {
status: 'success',
};
}
const instance = await this.prismaRepository.instance.findFirst({
where: { number: numberId },
});
if (!instance) {
this.logger.error('WebhookService -> receiveWebhookSerpro -> instance not found');
return {
status: 'success',
};
}
await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
return {
status: 'success',
};
}
}

View File

@ -0,0 +1,12 @@
import { serproController } from '@api/server.module';
import { Router } from 'express';
const serproRouter = Router();
serproRouter.post('/webhook', async (req, res) => {
const { body } = req;
const response = await serproController.receiveWebhook(body);
return res.status(200).json(response);
});
export { serproRouter };

View File

@ -28,6 +28,7 @@ import axios from 'axios';
import { arrayUnique, isURL } from 'class-validator'; import { arrayUnique, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import FormData from 'form-data'; import FormData from 'form-data';
import { createReadStream } from 'fs';
import mimeTypes from 'mime-types'; import mimeTypes from 'mime-types';
import { join } from 'path'; import { join } from 'path';
@ -146,20 +147,11 @@ export class BusinessStartupService extends ChannelStartupService {
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION; const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${id}`; urlServer = `${urlServer}/${version}/${id}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` }; const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
// Primeiro, obtenha a URL do arquivo
let result = await axios.get(urlServer, { headers }); let result = await axios.get(urlServer, { headers });
result = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
// Depois, baixe o arquivo usando a URL retornada
result = await axios.get(result.data.url, {
headers: { Authorization: `Bearer ${this.token}` }, // Use apenas o token de autorização para download
responseType: 'arraybuffer',
});
return result.data; return result.data;
} catch (e) { } catch (e) {
this.logger.error(`Error downloading media: ${e}`); this.logger.error(e);
throw e;
} }
} }
@ -167,23 +159,7 @@ export class BusinessStartupService extends ChannelStartupService {
const message = received.messages[0]; const message = received.messages[0];
let content: any = message.type + 'Message'; let content: any = message.type + 'Message';
content = { [content]: message[message.type] }; content = { [content]: message[message.type] };
if (message.context) { message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content;
}
private messageAudioJson(received: any) {
const message = received.messages[0];
let content: any = {
audioMessage: {
...message.audio,
ptt: message.audio.voice || false, // Define se é mensagem de voz
},
};
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content; return content;
} }
@ -216,77 +192,17 @@ export class BusinessStartupService extends ChannelStartupService {
} }
private messageTextJson(received: any) { private messageTextJson(received: any) {
// Verificar que received y received.messages existen
if (!received || !received.messages || received.messages.length === 0) {
this.logger.error('Error: received object or messages array is undefined or empty');
return null;
}
const message = received.messages[0];
let content: any; let content: any;
const message = received.messages[0];
// Verificar si es un mensaje de tipo sticker, location u otro tipo que no tiene text
if (!message.text) {
// Si no hay texto, manejamos diferente según el tipo de mensaje
if (message.type === 'sticker') {
content = { stickerMessage: {} };
} else if (message.type === 'location') {
content = {
locationMessage: {
degreesLatitude: message.location?.latitude,
degreesLongitude: message.location?.longitude,
name: message.location?.name,
address: message.location?.address,
},
};
} else {
// Para otros tipos de mensajes sin texto, creamos un contenido genérico
this.logger.log(`Mensaje de tipo ${message.type} sin campo text`);
content = { [message.type + 'Message']: message[message.type] || {} };
}
// Añadir contexto si existe
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content;
}
// Si el mensaje tiene texto, procesamos normalmente
if (!received.metadata || !received.metadata.phone_number_id) {
this.logger.error('Error: metadata or phone_number_id is undefined');
return null;
}
if (message.from === received.metadata.phone_number_id) { if (message.from === received.metadata.phone_number_id) {
content = { content = {
extendedTextMessage: { text: message.text.body }, extendedTextMessage: { text: message.text.body },
}; };
if (message.context) { message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
} else { } else {
content = { conversation: message.text.body }; content = { conversation: message.text.body };
if (message.context) { message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
} }
return content;
}
private messageLocationJson(received: any) {
const message = received.messages[0];
let content: any = {
locationMessage: {
degreesLatitude: message.location.latitude,
degreesLongitude: message.location.longitude,
name: message.location?.name,
address: message.location?.address,
},
};
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
return content; return content;
} }
@ -367,12 +283,6 @@ export class BusinessStartupService extends ChannelStartupService {
case 'template': case 'template':
messageType = 'conversation'; messageType = 'conversation';
break; break;
case 'location':
messageType = 'locationMessage';
break;
case 'sticker':
messageType = 'stickerMessage';
break;
default: default:
messageType = 'conversation'; messageType = 'conversation';
break; break;
@ -389,36 +299,17 @@ export class BusinessStartupService extends ChannelStartupService {
if (received.contacts) pushName = received.contacts[0].profile.name; if (received.contacts) pushName = received.contacts[0].profile.name;
if (received.messages) { if (received.messages) {
const message = received.messages[0]; // Añadir esta línea para definir message
const key = { const key = {
id: message.id, id: received.messages[0].id,
remoteJid: this.phoneNumber, remoteJid: this.phoneNumber,
fromMe: message.from === received.metadata.phone_number_id, fromMe: received.messages[0].from === received.metadata.phone_number_id,
}; };
if (this.isMediaMessage(received?.messages[0])) {
if (message.type === 'sticker') {
this.logger.log('Procesando mensaje de tipo sticker');
messageRaw = { messageRaw = {
key, key,
pushName, pushName,
message: { message: this.messageMediaJson(received),
stickerMessage: message.sticker || {}, contextInfo: this.messageMediaJson(received)?.contextInfo,
},
messageType: 'stickerMessage',
messageTimestamp: parseInt(message.timestamp) as number,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (this.isMediaMessage(message)) {
const messageContent =
message.type === 'audio' ? this.messageAudioJson(received) : this.messageMediaJson(received);
messageRaw = {
key,
pushName,
message: messageContent,
contextInfo: messageContent?.contextInfo,
messageType: this.renderMessageType(received.messages[0].type), messageType: this.renderMessageType(received.messages[0].type),
messageTimestamp: parseInt(received.messages[0].timestamp) as number, messageTimestamp: parseInt(received.messages[0].timestamp) as number,
source: 'unknown', source: 'unknown',
@ -436,10 +327,7 @@ export class BusinessStartupService extends ChannelStartupService {
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` }; const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
const result = await axios.get(urlServer, { headers }); const result = await axios.get(urlServer, { headers });
const buffer = await axios.get(result.data.url, { const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
headers: { Authorization: `Bearer ${this.token}` }, // Use apenas o token de autorização para download
responseType: 'arraybuffer',
});
let mediaType; let mediaType;
@ -464,17 +352,6 @@ export class BusinessStartupService extends ChannelStartupService {
} }
} }
// Para áudio, garantir extensão correta baseada no mimetype
if (mediaType === 'audio') {
if (mimetype.includes('ogg')) {
fileName = `${message.messages[0].id}.ogg`;
} else if (mimetype.includes('mp3')) {
fileName = `${message.messages[0].id}.mp3`;
} else if (mimetype.includes('m4a')) {
fileName = `${message.messages[0].id}.m4a`;
}
}
const size = result.headers['content-length'] || buffer.data.byteLength; const size = result.headers['content-length'] || buffer.data.byteLength;
const fullName = join(`${this.instance.id}`, key.remoteJid, mediaType, fileName); const fullName = join(`${this.instance.id}`, key.remoteJid, mediaType, fileName);
@ -501,72 +378,13 @@ export class BusinessStartupService extends ChannelStartupService {
messageRaw.message.mediaUrl = mediaUrl; messageRaw.message.mediaUrl = mediaUrl;
messageRaw.message.base64 = buffer.data.toString('base64'); messageRaw.message.base64 = buffer.data.toString('base64');
// Processar OpenAI speech-to-text para áudio após o mediaUrl estar disponível
if (this.configService.get<Openai>('OPENAI').ENABLED && mediaType === 'audio') {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (
openAiDefaultSettings &&
openAiDefaultSettings.openaiCredsId &&
openAiDefaultSettings.speechToText
) {
try {
messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
{
message: {
mediaUrl: messageRaw.message.mediaUrl,
...messageRaw,
},
},
)}`;
} catch (speechError) {
this.logger.error(`Error processing speech-to-text: ${speechError}`);
}
}
}
} catch (error) { } catch (error) {
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
} }
} else { } else {
const buffer = await this.downloadMediaMessage(received?.messages[0]); const buffer = await this.downloadMediaMessage(received?.messages[0]);
messageRaw.message.base64 = buffer.toString('base64'); messageRaw.message.base64 = buffer.toString('base64');
// Processar OpenAI speech-to-text para áudio mesmo sem S3
if (this.configService.get<Openai>('OPENAI').ENABLED && message.type === 'audio') {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) {
try {
messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
{
message: {
base64: messageRaw.message.base64,
...messageRaw,
},
},
)}`;
} catch (speechError) {
this.logger.error(`Error processing speech-to-text: ${speechError}`);
}
}
}
} }
} else if (received?.messages[0].interactive) { } else if (received?.messages[0].interactive) {
messageRaw = { messageRaw = {
@ -637,6 +455,33 @@ export class BusinessStartupService extends ChannelStartupService {
// await this.client.readMessages([received.key]); // await this.client.readMessages([received.key]);
} }
if (this.configService.get<Openai>('OPENAI').ENABLED) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
const audioMessage = received?.messages[0]?.audio;
if (
openAiDefaultSettings &&
openAiDefaultSettings.openaiCredsId &&
openAiDefaultSettings.speechToText &&
audioMessage
) {
messageRaw.message.speechToText = await this.openaiService.speechToText(openAiDefaultSettings.OpenaiCreds, {
message: {
mediaUrl: messageRaw.message.mediaUrl,
...messageRaw,
},
});
}
}
this.logger.log(messageRaw); this.logger.log(messageRaw);
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
@ -662,7 +507,7 @@ export class BusinessStartupService extends ChannelStartupService {
} }
} }
if (!this.isMediaMessage(message) && message.type !== 'sticker') { if (!this.isMediaMessage(received?.messages[0])) {
await this.prismaRepository.message.create({ await this.prismaRepository.message.create({
data: messageRaw, data: messageRaw,
}); });
@ -865,54 +710,17 @@ export class BusinessStartupService extends ChannelStartupService {
} }
protected async eventHandler(content: any) { protected async eventHandler(content: any) {
try { const database = this.configService.get<Database>('DATABASE');
// Registro para depuración const settings = await this.findSettings();
this.logger.log('Contenido recibido en eventHandler:');
this.logger.log(JSON.stringify(content, null, 2));
const database = this.configService.get<Database>('DATABASE'); this.messageHandle(content, database, settings);
const settings = await this.findSettings();
// Si hay mensajes, verificar primero el tipo
if (content.messages && content.messages.length > 0) {
const message = content.messages[0];
this.logger.log(`Tipo de mensaje recibido: ${message.type}`);
// Verificamos el tipo de mensaje antes de procesarlo
if (
message.type === 'text' ||
message.type === 'image' ||
message.type === 'video' ||
message.type === 'audio' ||
message.type === 'document' ||
message.type === 'sticker' ||
message.type === 'location' ||
message.type === 'contacts' ||
message.type === 'interactive' ||
message.type === 'button' ||
message.type === 'reaction'
) {
// Procesar el mensaje normalmente
this.messageHandle(content, database, settings);
} else {
this.logger.warn(`Tipo de mensaje no reconocido: ${message.type}`);
}
} else if (content.statuses) {
// Procesar actualizaciones de estado
this.messageHandle(content, database, settings);
} else {
this.logger.warn('No se encontraron mensajes ni estados en el contenido recibido');
}
} catch (error) {
this.logger.error('Error en eventHandler:');
this.logger.error(error);
}
} }
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) { protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) {
try { try {
let quoted: any; let quoted: any;
let webhookUrl: any; let webhookUrl: any;
const linkPreview = options?.linkPreview != false ? undefined : false;
if (options?.quoted) { if (options?.quoted) {
const m = options?.quoted; const m = options?.quoted;
@ -980,7 +788,7 @@ export class BusinessStartupService extends ChannelStartupService {
to: number.replace(/\D/g, ''), to: number.replace(/\D/g, ''),
text: { text: {
body: message['conversation'], body: message['conversation'],
preview_url: Boolean(options?.linkPreview), preview_url: linkPreview,
}, },
}; };
quoted ? (content.context = { message_id: quoted.id }) : content; quoted ? (content.context = { message_id: quoted.id }) : content;
@ -996,10 +804,9 @@ export class BusinessStartupService extends ChannelStartupService {
to: number.replace(/\D/g, ''), to: number.replace(/\D/g, ''),
[message['mediaType']]: { [message['mediaType']]: {
[message['type']]: message['id'], [message['type']]: message['id'],
...(message['mediaType'] !== 'audio' && preview_url: linkPreview,
message['fileName'] && ...(message['fileName'] && !isImage && { filename: message['fileName'] }),
!isImage && { filename: message['fileName'] }), caption: message['caption'],
...(message['mediaType'] !== 'audio' && message['caption'] && { caption: message['caption'] }),
}, },
}; };
quoted ? (content.context = { message_id: quoted.id }) : content; quoted ? (content.context = { message_id: quoted.id }) : content;
@ -1097,7 +904,7 @@ export class BusinessStartupService extends ChannelStartupService {
} }
})(); })();
if (messageSent?.error_data || messageSent.message) { if (messageSent?.error_data) {
this.logger.error(messageSent); this.logger.error(messageSent);
return messageSent; return messageSent;
} }
@ -1164,50 +971,29 @@ export class BusinessStartupService extends ChannelStartupService {
return res; return res;
} }
private async getIdMedia(mediaMessage: any, isFile = false) { private async getIdMedia(mediaMessage: any) {
try { const formData = new FormData();
const formData = new FormData();
if (isFile === false) { const fileStream = createReadStream(mediaMessage.media);
if (isURL(mediaMessage.media)) {
const response = await axios.get(mediaMessage.media, { responseType: 'arraybuffer' });
const buffer = Buffer.from(response.data, 'base64');
formData.append('file', buffer, {
filename: mediaMessage.fileName || 'media',
contentType: mediaMessage.mimetype,
});
} else {
const buffer = Buffer.from(mediaMessage.media, 'base64');
formData.append('file', buffer, {
filename: mediaMessage.fileName || 'media',
contentType: mediaMessage.mimetype,
});
}
} else {
formData.append('file', mediaMessage.media.buffer, {
filename: mediaMessage.media.originalname,
contentType: mediaMessage.media.mimetype,
});
}
const mimetype = mediaMessage.mimetype || mediaMessage.media.mimetype; formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype });
formData.append('typeFile', mediaMessage.mimetype);
formData.append('messaging_product', 'whatsapp');
formData.append('typeFile', mimetype); // const fileBuffer = await fs.readFile(mediaMessage.media);
formData.append('messaging_product', 'whatsapp');
const token = this.token; // const fileBlob = new Blob([fileBuffer], { type: mediaMessage.mimetype });
// formData.append('file', fileBlob);
// formData.append('typeFile', mediaMessage.mimetype);
// formData.append('messaging_product', 'whatsapp');
const headers = { Authorization: `Bearer ${token}` }; const headers = { Authorization: `Bearer ${this.token}` };
const url = `${this.configService.get<WaBusiness>('WA_BUSINESS').URL}/${ const res = await axios.post(
this.configService.get<WaBusiness>('WA_BUSINESS').VERSION process.env.API_URL + '/' + process.env.VERSION + '/' + this.number + '/media',
}/${this.number}/media`; formData,
{ headers },
const res = await axios.post(url, formData, { headers }); );
return res.data.id; return res.data.id;
} catch (error) {
this.logger.error(error.response.data);
throw new InternalServerErrorException(error?.toString() || error);
}
} }
protected async prepareMediaMessage(mediaMessage: MediaMessage) { protected async prepareMediaMessage(mediaMessage: MediaMessage) {
@ -1280,87 +1066,48 @@ export class BusinessStartupService extends ChannelStartupService {
return mediaSent; return mediaSent;
} }
public async processAudio(audio: string, number: string, file: any) { public async processAudio(audio: string, number: string) {
number = number.replace(/\D/g, ''); number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`; const hash = `${number}-${new Date().getTime()}`;
if (process.env.API_AUDIO_CONVERTER) { let mimetype: string | false;
this.logger.verbose('Using audio converter API');
const formData = new FormData();
if (file) { const prepareMedia: any = {
formData.append('file', file.buffer, { fileName: `${hash}.mp3`,
filename: file.originalname, mediaType: 'audio',
contentType: file.mimetype, media: audio,
}); };
} else if (isURL(audio)) {
formData.append('url', audio);
} else {
formData.append('base64', audio);
}
formData.append('format', 'mp3');
const response = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
headers: {
...formData.getHeaders(),
apikey: process.env.API_AUDIO_CONVERTER_KEY,
},
});
const audioConverter = response?.data?.audio || response?.data?.url;
if (!audioConverter) {
throw new InternalServerErrorException('Failed to convert audio');
}
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audioConverter,
mimetype: 'audio/mpeg',
};
if (isURL(audio)) {
mimetype = mimeTypes.lookup(audio);
prepareMedia.id = audio;
prepareMedia.type = 'link';
} else {
mimetype = mimeTypes.lookup(prepareMedia.fileName);
const id = await this.getIdMedia(prepareMedia); const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id; prepareMedia.id = id;
prepareMedia.type = 'id'; prepareMedia.type = 'id';
this.logger.verbose('Audio converted');
return prepareMedia;
} else {
let mimetype: string | false;
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audio,
};
if (isURL(audio)) {
mimetype = mimeTypes.lookup(audio);
prepareMedia.id = audio;
prepareMedia.type = 'link';
} else if (audio && !file) {
mimetype = mimeTypes.lookup(prepareMedia.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
} else if (file) {
prepareMedia.media = file;
const id = await this.getIdMedia(prepareMedia, true);
prepareMedia.id = id;
prepareMedia.type = 'id';
mimetype = file.mimetype;
}
prepareMedia.mimetype = mimetype;
return prepareMedia;
} }
prepareMedia.mimetype = mimetype;
return prepareMedia;
} }
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) { public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
const message = await this.processAudio(data.audio, data.number, file); const mediaData: SendAudioDto = { ...data };
if (file?.buffer) {
mediaData.audio = file.buffer.toString('base64');
} else if (isURL(mediaData.audio)) {
// DO NOTHING
// mediaData.audio = mediaData.audio;
} else {
console.error('El archivo no tiene buffer o file es undefined');
throw new Error('File or buffer is undefined');
}
const message = await this.processAudio(mediaData.audio, data.number);
const audioSent = await this.sendMessageWithTyping( const audioSent = await this.sendMessageWithTyping(
data.number, data.number,

View File

@ -10,6 +10,11 @@ export class Query<T> {
} }
export class PrismaRepository extends PrismaClient { export class PrismaRepository extends PrismaClient {
message: any;
media: any;
openaiSetting: any;
contact: any;
messageUpdate: any;
constructor(private readonly configService: ConfigService) { constructor(private readonly configService: ConfigService) {
super(); super();
} }

View File

@ -2,6 +2,7 @@ import { authGuard } from '@api/guards/auth.guard';
import { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard'; import { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';
import Telemetry from '@api/guards/telemetry.guard'; import Telemetry from '@api/guards/telemetry.guard';
import { ChannelRouter } from '@api/integrations/channel/channel.router'; import { ChannelRouter } from '@api/integrations/channel/channel.router';
import { serproRouter } from '@api/integrations/channel/meta/serpro.router';
import { ChatbotRouter } from '@api/integrations/chatbot/chatbot.router'; import { ChatbotRouter } from '@api/integrations/chatbot/chatbot.router';
import { EventRouter } from '@api/integrations/event/event.router'; import { EventRouter } from '@api/integrations/event/event.router';
import { StorageRouter } from '@api/integrations/storage/storage.router'; import { StorageRouter } from '@api/integrations/storage/storage.router';
@ -95,6 +96,6 @@ router
.use('', new ChannelRouter(configService, ...guards).router) .use('', new ChannelRouter(configService, ...guards).router)
.use('', new EventRouter(configService, ...guards).router) .use('', new EventRouter(configService, ...guards).router)
.use('', new ChatbotRouter(...guards).router) .use('', new ChatbotRouter(...guards).router)
.use('', new StorageRouter(...guards).router); .use('', new StorageRouter(...guards).router)
.use('/serpro', serproRouter);
export { HttpStatus, router }; export { HttpStatus, router };

View File

@ -16,6 +16,7 @@ import { TemplateController } from './controllers/template.controller';
import { ChannelController } from './integrations/channel/channel.controller'; import { ChannelController } from './integrations/channel/channel.controller';
import { EvolutionController } from './integrations/channel/evolution/evolution.controller'; import { EvolutionController } from './integrations/channel/evolution/evolution.controller';
import { MetaController } from './integrations/channel/meta/meta.controller'; import { MetaController } from './integrations/channel/meta/meta.controller';
import { SerproController } from './integrations/channel/meta/serpro.controller';
import { BaileysController } from './integrations/channel/whatsapp/baileys.controller'; import { BaileysController } from './integrations/channel/whatsapp/baileys.controller';
import { ChatbotController } from './integrations/chatbot/chatbot.controller'; import { ChatbotController } from './integrations/chatbot/chatbot.controller';
import { ChatwootController } from './integrations/chatbot/chatwoot/controllers/chatwoot.controller'; import { ChatwootController } from './integrations/chatbot/chatwoot/controllers/chatwoot.controller';
@ -114,6 +115,7 @@ export const channelController = new ChannelController(prismaRepository, waMonit
// channels // channels
export const evolutionController = new EvolutionController(prismaRepository, waMonitor); export const evolutionController = new EvolutionController(prismaRepository, waMonitor);
export const metaController = new MetaController(prismaRepository, waMonitor); export const metaController = new MetaController(prismaRepository, waMonitor);
export const serproController = new SerproController(prismaRepository, waMonitor);
export const baileysController = new BaileysController(waMonitor); export const baileysController = new BaileysController(waMonitor);
const openaiService = new OpenaiService(waMonitor, prismaRepository, configService); const openaiService = new OpenaiService(waMonitor, prismaRepository, configService);

View File

@ -12,6 +12,7 @@ import EventEmitter2 from 'eventemitter2';
import { rmSync } from 'fs'; import { rmSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ChatwootService } from '../../api/integrations/chatbot/chatwoot/services/chatwoot.service';
import { CacheService } from './cache.service'; import { CacheService } from './cache.service';
export class WAMonitoringService { export class WAMonitoringService {
@ -144,7 +145,7 @@ export class WAMonitoringService {
if (findInstance) { if (findInstance) {
const instance = await this.prismaRepository.instance.update({ const instance = await this.prismaRepository.instance.update({
where: { name: instanceName }, where: { id: findInstance.id },
data: { connectionStatus: 'close' }, data: { connectionStatus: 'close' },
}); });
@ -220,9 +221,17 @@ export class WAMonitoringService {
public async saveInstance(data: any) { public async saveInstance(data: any) {
try { try {
const clientName = await this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME; const clientName = await this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
await this.prismaRepository.instance.create({
data: { // Pegue o token do .env/config
id: data.instanceId, const token = process.env.SERPRO_CLIENT_SECRET;
// Garanta que nome está preenchido!
if (!data.instanceName) throw new Error('instanceName é obrigatório no saveInstance!');
await this.prismaRepository.instance.upsert({
where: { name: data.instanceName },
create: {
id: data.instanceId || data.instanceName, // sempre defina!
name: data.instanceName, name: data.instanceName,
ownerJid: data.ownerJid, ownerJid: data.ownerJid,
profileName: data.profileName, profileName: data.profileName,
@ -231,7 +240,19 @@ export class WAMonitoringService {
data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : (data.status ?? 'open'), data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : (data.status ?? 'open'),
number: data.number, number: data.number,
integration: data.integration || Integration.WHATSAPP_BAILEYS, integration: data.integration || Integration.WHATSAPP_BAILEYS,
token: data.hash, token: token, // PEGA DO ENV!
clientName: clientName,
businessId: data.businessId,
},
update: {
ownerJid: data.ownerJid,
profileName: data.profileName,
profilePicUrl: data.profilePicUrl,
connectionStatus:
data.integration && data.integration === Integration.WHATSAPP_BAILEYS ? 'close' : (data.status ?? 'open'),
number: data.number,
integration: data.integration || Integration.WHATSAPP_BAILEYS,
token: token, // ATUALIZA DO ENV!
clientName: clientName, clientName: clientName,
businessId: data.businessId, businessId: data.businessId,
}, },

View File

@ -317,6 +317,11 @@ export interface Env {
S3?: S3; S3?: S3;
AUTHENTICATION: Auth; AUTHENTICATION: Auth;
PRODUCTION?: Production; PRODUCTION?: Production;
SERPRO: {
API_BASE_URL: string;
CLIENT_ID: string;
CLIENT_SECRET: string;
};
} }
export type Key = keyof Env; export type Key = keyof Env;
@ -661,6 +666,11 @@ export class ConfigService {
}, },
EXPOSE_IN_FETCH_INSTANCES: process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true', EXPOSE_IN_FETCH_INSTANCES: process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
}, },
SERPRO: {
API_BASE_URL: process.env.SERPRO_API_BASE_URL || '',
CLIENT_ID: process.env.SERPRO_CLIENT_ID || '',
CLIENT_SECRET: process.env.SERPRO_CLIENT_SECRET || '',
},
}; };
} }
} }

View File

@ -15,8 +15,10 @@ import { ServerUP } from '@utils/server-up';
import axios from 'axios'; import axios from 'axios';
import compression from 'compression'; import compression from 'compression';
import cors from 'cors'; import cors from 'cors';
import dotenv from 'dotenv';
import express, { json, NextFunction, Request, Response, urlencoded } from 'express'; import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
import { join } from 'path'; import { join } from 'path';
dotenv.config();
function initWA() { function initWA() {
waMonitor.loadInstance(); waMonitor.loadInstance();