mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-16 12:12:55 -06:00
Merge branch 'release/2.2.1'
This commit is contained in:
commit
7f10a0eecd
@ -26,7 +26,7 @@ DEL_INSTANCE=false
|
|||||||
|
|
||||||
# Provider: postgresql | mysql
|
# Provider: postgresql | mysql
|
||||||
DATABASE_PROVIDER=postgresql
|
DATABASE_PROVIDER=postgresql
|
||||||
DATABASE_CONNECTION_URI='postgresql://user:pass@localhost:5432/evolution?schema=public'
|
DATABASE_CONNECTION_URI='postgresql://user:pass@postgres:5432/evolution?schema=public'
|
||||||
# Client name for the database connection
|
# Client name for the database connection
|
||||||
# It is used to separate an API installation from another that uses the same database.
|
# It is used to separate an API installation from another that uses the same database.
|
||||||
DATABASE_CONNECTION_CLIENT_NAME=evolution_exchange
|
DATABASE_CONNECTION_CLIENT_NAME=evolution_exchange
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: 'CommonJS',
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
warnOnUnsupportedTypeScriptVersion: false,
|
||||||
|
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint', 'simple-import-sort', 'import'],
|
plugins: ['@typescript-eslint', 'simple-import-sort', 'import'],
|
||||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
||||||
|
28
.github/workflows/check_code_quality.yml
vendored
Normal file
28
.github/workflows/check_code_quality.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Check Code Quality
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-lint-and-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Check linting
|
||||||
|
run: npm run lint:check
|
||||||
|
|
||||||
|
- name: Check build
|
||||||
|
run: npm run db:generate
|
||||||
|
|
||||||
|
- name: Check build
|
||||||
|
run: npm run build
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,7 +21,6 @@ lerna-debug.log*
|
|||||||
# Package
|
# Package
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
/pnpm-lock.yaml
|
/pnpm-lock.yaml
|
||||||
/package-lock.json
|
|
||||||
|
|
||||||
# IDEs
|
# IDEs
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,4 +1,19 @@
|
|||||||
# 2.2.0 (develop)
|
# 2.2.1 (2025-01-22 14:37)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Retry system for send webhooks
|
||||||
|
* Message filtering to support timestamp range queries
|
||||||
|
* Chats filtering to support timestamp range queries
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Correction of webhook global
|
||||||
|
* Fixed send audio with whatsapp cloud api
|
||||||
|
* Refactor on fetch chats
|
||||||
|
* Refactor on Evolution Channel
|
||||||
|
|
||||||
|
# 2.2.0 (2024-10-18 10:00)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add git ffmpeg wget curl bash
|
apk add git ffmpeg wget curl bash openssl
|
||||||
|
|
||||||
LABEL version="2.2.0" description="Api to control whatsapp features through http requests."
|
LABEL version="2.2.1" description="Api to control whatsapp features through http requests."
|
||||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||||
LABEL contact="contato@atendai.com"
|
LABEL contact="contato@atendai.com"
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ WORKDIR /evolution
|
|||||||
|
|
||||||
COPY ./package.json ./tsconfig.json ./
|
COPY ./package.json ./tsconfig.json ./
|
||||||
|
|
||||||
RUN npm install -f
|
RUN npm install
|
||||||
|
|
||||||
COPY ./src ./src
|
COPY ./src ./src
|
||||||
COPY ./public ./public
|
COPY ./public ./public
|
||||||
@ -32,7 +32,7 @@ RUN npm run build
|
|||||||
FROM node:20-alpine AS final
|
FROM node:20-alpine AS final
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add tzdata ffmpeg bash
|
apk add tzdata ffmpeg bash openssl
|
||||||
|
|
||||||
ENV TZ=America/Sao_Paulo
|
ENV TZ=America/Sao_Paulo
|
||||||
|
|
||||||
|
@ -34,12 +34,15 @@ services:
|
|||||||
image: postgres:15
|
image: postgres:15
|
||||||
networks:
|
networks:
|
||||||
- evolution-net
|
- evolution-net
|
||||||
command: ["postgres", "-c", "max_connections=1000"]
|
command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"]
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=PASSWORD
|
- POSTGRES_USER=user
|
||||||
|
- POSTGRES_PASSWORD=pass
|
||||||
|
- POSTGRES_DB=evolution
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
expose:
|
expose:
|
||||||
|
150
local_install.sh
Executable file
150
local_install.sh
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Definir cores para melhor legibilidade
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Função para log
|
||||||
|
log() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar se está rodando como root
|
||||||
|
if [ "$(id -u)" = "0" ]; then
|
||||||
|
log_error "Este script não deve ser executado como root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verificar sistema operacional
|
||||||
|
OS="$(uname -s)"
|
||||||
|
case "${OS}" in
|
||||||
|
Linux*)
|
||||||
|
if [ ! -x "$(command -v curl)" ]; then
|
||||||
|
log_warning "Curl não está instalado. Tentando instalar..."
|
||||||
|
if [ -x "$(command -v apt-get)" ]; then
|
||||||
|
sudo apt-get update && sudo apt-get install -y curl
|
||||||
|
elif [ -x "$(command -v yum)" ]; then
|
||||||
|
sudo yum install -y curl
|
||||||
|
else
|
||||||
|
log_error "Não foi possível instalar curl automaticamente. Por favor, instale manualmente."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Darwin*)
|
||||||
|
if [ ! -x "$(command -v curl)" ]; then
|
||||||
|
log_error "Curl não está instalado. Por favor, instale o Xcode Command Line Tools."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Sistema operacional não suportado: ${OS}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Verificar conexão com a internet antes de prosseguir
|
||||||
|
if ! ping -c 1 8.8.8.8 &> /dev/null; then
|
||||||
|
log_error "Sem conexão com a internet. Por favor, verifique sua conexão."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Adicionar verificação de espaço em disco
|
||||||
|
REQUIRED_SPACE=1000000 # 1GB em KB
|
||||||
|
AVAILABLE_SPACE=$(df -k . | awk 'NR==2 {print $4}')
|
||||||
|
if [ $AVAILABLE_SPACE -lt $REQUIRED_SPACE ]; then
|
||||||
|
log_error "Espaço em disco insuficiente. Necessário pelo menos 1GB livre."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Adicionar tratamento de erro para comandos npm
|
||||||
|
npm_install_with_retry() {
|
||||||
|
local max_attempts=3
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
log "Tentativa $attempt de $max_attempts para npm install"
|
||||||
|
if npm install; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
[ $attempt -le $max_attempts ] && log_warning "Falha na instalação. Tentando novamente em 5 segundos..." && sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "Falha ao executar npm install após $max_attempts tentativas"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Adicionar timeout para comandos
|
||||||
|
execute_with_timeout() {
|
||||||
|
timeout 300 $@ || log_error "Comando excedeu o tempo limite de 5 minutos: $@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar se o NVM já está instalado
|
||||||
|
if [ -d "$HOME/.nvm" ]; then
|
||||||
|
log "NVM já está instalado."
|
||||||
|
else
|
||||||
|
log "Instalando NVM..."
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Carregar o NVM no ambiente atual
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||||
|
|
||||||
|
# Verificar se a versão do Node.js já está instalada
|
||||||
|
if command -v node >/dev/null 2>&1 && [ "$(node -v)" = "v20.10.0" ]; then
|
||||||
|
log "Node.js v20.10.0 já está instalado."
|
||||||
|
else
|
||||||
|
log "Instalando Node.js v20.10.0..."
|
||||||
|
nvm install v20.10.0
|
||||||
|
fi
|
||||||
|
|
||||||
|
nvm use v20.10.0
|
||||||
|
|
||||||
|
# Verificar as versões instaladas
|
||||||
|
log "Verificando as versões instaladas:"
|
||||||
|
log "Node.js: $(node -v)"
|
||||||
|
log "npm: $(npm -v)"
|
||||||
|
|
||||||
|
# Instala dependências do projeto
|
||||||
|
log "Instalando dependências do projeto..."
|
||||||
|
rm -rf node_modules
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Deploy do banco de dados
|
||||||
|
log "Deploy do banco de dados..."
|
||||||
|
npm run db:generate
|
||||||
|
npm run db:deploy
|
||||||
|
|
||||||
|
# Iniciar o projeto
|
||||||
|
log "Iniciando o projeto..."
|
||||||
|
if [ "$1" = "-dev" ]; then
|
||||||
|
npm run dev:server
|
||||||
|
else
|
||||||
|
npm run build
|
||||||
|
npm run start:prod
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Instalação concluída com sucesso!"
|
||||||
|
|
||||||
|
# Criar arquivo de log
|
||||||
|
LOGFILE="./installation_log_$(date +%Y%m%d_%H%M%S).log"
|
||||||
|
exec 1> >(tee -a "$LOGFILE")
|
||||||
|
exec 2>&1
|
||||||
|
|
||||||
|
# Adicionar trap para limpeza em caso de interrupção
|
||||||
|
cleanup() {
|
||||||
|
log "Limpando recursos temporários..."
|
||||||
|
# Adicione comandos de limpeza aqui
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
12227
package-lock.json
generated
Normal file
12227
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
90
package.json
90
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
@ -11,10 +11,13 @@
|
|||||||
"dev:server": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
"dev:server": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
||||||
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
||||||
"lint": "eslint --fix --ext .ts src",
|
"lint": "eslint --fix --ext .ts src",
|
||||||
|
"lint:check": "eslint --ext .ts src",
|
||||||
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||||
"db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
"db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||||
|
"db:deploy:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate deploy --schema prisma\\DATABASE_PROVIDER-schema.prisma\"",
|
||||||
"db:studio": "node runWithProvider.js \"npx prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
"db:studio": "node runWithProvider.js \"npx prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||||
"db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\""
|
"db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\"",
|
||||||
|
"db:migrate:dev:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate dev --schema prisma\\DATABASE_PROVIDER-schema.prisma\""
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -47,72 +50,75 @@
|
|||||||
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
|
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adiwajshing/keyed-db": "^0.2.4",
|
"@adiwajshing/keyed-db": "^0.2.4",
|
||||||
"@aws-sdk/client-sqs": "^3.569.0",
|
"@aws-sdk/client-sqs": "^3.723.0",
|
||||||
"@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",
|
||||||
"@prisma/client": "^5.15.0",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@sentry/node": "^8.28.0",
|
"@prisma/client": "^6.1.0",
|
||||||
"amqplib": "^0.10.3",
|
"@sentry/node": "^8.47.0",
|
||||||
"axios": "^1.6.5",
|
"amqplib": "^0.10.5",
|
||||||
|
"axios": "^1.7.9",
|
||||||
"baileys": "github:EvolutionAPI/Baileys",
|
"baileys": "github:EvolutionAPI/Baileys",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
"eventemitter2": "^6.4.9",
|
"eventemitter2": "^6.4.9",
|
||||||
"express": "^4.18.2",
|
"express": "^4.21.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.1",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"i18next": "^23.7.19",
|
"i18next": "^23.7.19",
|
||||||
"jimp": "^0.16.13",
|
"jimp": "^0.16.13",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"link-preview-js": "^3.0.4",
|
"link-preview-js": "^3.0.13",
|
||||||
"long": "^5.2.3",
|
"long": "^5.2.3",
|
||||||
"mediainfo.js": "^0.3.2",
|
"mediainfo.js": "^0.3.4",
|
||||||
"mime": "^3.0.0",
|
"mime": "^4.0.0",
|
||||||
"minio": "^8.0.1",
|
"mime-types": "^2.1.35",
|
||||||
|
"minio": "^8.0.3",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"openai": "^4.52.7",
|
"openai": "^4.77.3",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.13.1",
|
||||||
"pino": "^8.11.0",
|
"pino": "^8.11.0",
|
||||||
"prisma": "^5.15.0",
|
"prisma": "^6.1.0",
|
||||||
"pusher": "^5.2.0",
|
"pusher": "^5.2.0",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.4",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"redis": "^4.6.5",
|
"redis": "^4.7.0",
|
||||||
"sharp": "^0.32.2",
|
"sharp": "^0.32.6",
|
||||||
"socket.io": "^4.7.1",
|
"socket.io": "^4.8.1",
|
||||||
"tsup": "^8.2.4",
|
"socket.io-client": "^4.8.1",
|
||||||
"uuid": "^9.0.0"
|
"tsup": "^8.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.5",
|
||||||
"@types/cors": "^2.8.13",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.18",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"@types/mime": "3.0.0",
|
"@types/mime": "^4.0.0",
|
||||||
"@types/node": "^18.15.11",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/qrcode-terminal": "^0.12.0",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^10.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.4.2",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
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 `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 `createdAt` on the `EvolutionBot` 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 `EvolutionBot` 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 `EvolutionBotSetting` 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 `EvolutionBotSetting` 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 `Flowise` 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 `Flowise` 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 `FlowiseSetting` 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 `FlowiseSetting` 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 `IntegrationSession` 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 `IntegrationSession` 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 `IsOnWhatsapp` 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 `IsOnWhatsapp` 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 `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 `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`.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Chat` ADD COLUMN `unreadMessages` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Dify` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `DifySetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `EvolutionBot` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `EvolutionBotSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Flowise` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `FlowiseSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Message` MODIFY `status` VARCHAR(30) NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `OpenaiBot` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `OpenaiSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
|
||||||
|
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `updatedAt` TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Pusher` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`enabled` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`appId` VARCHAR(100) NOT NULL,
|
||||||
|
`key` VARCHAR(100) NOT NULL,
|
||||||
|
`secret` VARCHAR(100) NOT NULL,
|
||||||
|
`cluster` VARCHAR(100) NOT NULL,
|
||||||
|
`useTLS` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`events` JSON NOT NULL,
|
||||||
|
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updatedAt` TIMESTAMP NOT NULL,
|
||||||
|
`instanceId` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE INDEX `Pusher_instanceId_key`(`instanceId`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `Chat_remoteJid_idx` ON `Chat`(`remoteJid`);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `Contact_remoteJid_idx` ON `Contact`(`remoteJid`);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `Setting_instanceId_idx` ON `Setting`(`instanceId`);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX `Webhook_instanceId_idx` ON `Webhook`(`instanceId`);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Pusher` ADD CONSTRAINT `Pusher_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER TABLE `Chat` RENAME INDEX `Chat_instanceId_fkey` TO `Chat_instanceId_idx`;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER TABLE `Contact` RENAME INDEX `Contact_instanceId_fkey` TO `Contact_instanceId_idx`;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER TABLE `Message` RENAME INDEX `Message_instanceId_fkey` TO `Message_instanceId_idx`;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER TABLE `MessageUpdate` RENAME INDEX `MessageUpdate_instanceId_fkey` TO `MessageUpdate_instanceId_idx`;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER TABLE `MessageUpdate` RENAME INDEX `MessageUpdate_messageId_fkey` TO `MessageUpdate_messageId_idx`;
|
@ -127,6 +127,7 @@ model Chat {
|
|||||||
unreadMessages Int @default(0)
|
unreadMessages Int @default(0)
|
||||||
@@index([instanceId])
|
@@index([instanceId])
|
||||||
@@index([remoteJid])
|
@@index([remoteJid])
|
||||||
|
@@unique([instanceId, remoteJid])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Contact {
|
model Contact {
|
||||||
@ -263,6 +264,7 @@ model Setting {
|
|||||||
readMessages Boolean @default(false)
|
readMessages Boolean @default(false)
|
||||||
readStatus Boolean @default(false)
|
readStatus Boolean @default(false)
|
||||||
syncFullHistory Boolean @default(false)
|
syncFullHistory Boolean @default(false)
|
||||||
|
wavoipToken String? @db.VarChar(100)
|
||||||
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
|
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
|
||||||
updatedAt DateTime @updatedAt @db.Timestamp
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'Setting'
|
||||||
|
AND column_name = 'wavoipToken'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "wavoipToken" VARCHAR(100);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
@ -264,6 +264,7 @@ model Setting {
|
|||||||
readMessages Boolean @default(false) @db.Boolean
|
readMessages Boolean @default(false) @db.Boolean
|
||||||
readStatus Boolean @default(false) @db.Boolean
|
readStatus Boolean @default(false) @db.Boolean
|
||||||
syncFullHistory Boolean @default(false) @db.Boolean
|
syncFullHistory Boolean @default(false) @db.Boolean
|
||||||
|
wavoipToken String? @db.VarChar(100)
|
||||||
createdAt DateTime? @default(now()) @db.Timestamp
|
createdAt DateTime? @default(now()) @db.Timestamp
|
||||||
updatedAt DateTime @updatedAt @db.Timestamp
|
updatedAt DateTime @updatedAt @db.Timestamp
|
||||||
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const { existsSync } = require('fs');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const { DATABASE_PROVIDER } = process.env;
|
const { DATABASE_PROVIDER } = process.env;
|
||||||
const databaseProviderDefault = DATABASE_PROVIDER ?? "postgresql"
|
const databaseProviderDefault = DATABASE_PROVIDER ?? 'postgresql';
|
||||||
|
|
||||||
if (!DATABASE_PROVIDER) {
|
if (!DATABASE_PROVIDER) {
|
||||||
console.error(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
|
console.warn(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
|
||||||
// process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = process.argv
|
let command = process.argv
|
||||||
.slice(2)
|
.slice(2)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.replace(/\DATABASE_PROVIDER/g, databaseProviderDefault);
|
.replace(/DATABASE_PROVIDER/g, databaseProviderDefault);
|
||||||
|
|
||||||
|
if (command.includes('rmdir') && existsSync('prisma\\migrations')) {
|
||||||
|
try {
|
||||||
|
execSync('rmdir /S /Q prisma\\migrations', { stdio: 'inherit' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error removing directory: prisma\\migrations`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else if (command.includes('rmdir')) {
|
||||||
|
console.warn(`Directory 'prisma\\migrations' does not exist, skipping removal.`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execSync(command, { stdio: 'inherit' });
|
execSync(command, { stdio: 'inherit' });
|
||||||
|
@ -63,6 +63,9 @@ export class InstanceController {
|
|||||||
instanceId,
|
instanceId,
|
||||||
integration: instanceData.integration,
|
integration: instanceData.integration,
|
||||||
instanceName: instanceData.instanceName,
|
instanceName: instanceData.instanceName,
|
||||||
|
ownerJid: instanceData.ownerJid,
|
||||||
|
profileName: instanceData.profileName,
|
||||||
|
profilePicUrl: instanceData.profilePicUrl,
|
||||||
hash,
|
hash,
|
||||||
number: instanceData.number,
|
number: instanceData.number,
|
||||||
businessId: instanceData.businessId,
|
businessId: instanceData.businessId,
|
||||||
@ -119,6 +122,7 @@ export class InstanceController {
|
|||||||
readMessages: instanceData.readMessages === true,
|
readMessages: instanceData.readMessages === true,
|
||||||
readStatus: instanceData.readStatus === true,
|
readStatus: instanceData.readStatus === true,
|
||||||
syncFullHistory: instanceData.syncFullHistory === true,
|
syncFullHistory: instanceData.syncFullHistory === true,
|
||||||
|
wavoipToken: instanceData.wavoipToken || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.settingsService.create(instance, settings);
|
await this.settingsService.create(instance, settings);
|
||||||
@ -409,15 +413,11 @@ export class InstanceController {
|
|||||||
|
|
||||||
public async deleteInstance({ instanceName }: InstanceDto) {
|
public async deleteInstance({ instanceName }: InstanceDto) {
|
||||||
const { instance } = await this.connectionState({ instanceName });
|
const { instance } = await this.connectionState({ instanceName });
|
||||||
|
|
||||||
if (instance.state === 'open') {
|
|
||||||
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const waInstances = this.waMonitor.waInstances[instanceName];
|
const waInstances = this.waMonitor.waInstances[instanceName];
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) waInstances?.clearCacheChatwoot();
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) waInstances?.clearCacheChatwoot();
|
||||||
|
|
||||||
if (instance.state === 'connecting') {
|
if (instance.state === 'connecting' || instance.state === 'open') {
|
||||||
await this.logout({ instanceName });
|
await this.logout({ instanceName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@ import axios from 'axios';
|
|||||||
const logger = new Logger('ProxyController');
|
const logger = new Logger('ProxyController');
|
||||||
|
|
||||||
export class ProxyController {
|
export class ProxyController {
|
||||||
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
|
constructor(
|
||||||
|
private readonly proxyService: ProxyService,
|
||||||
|
private readonly waMonitor: WAMonitoringService,
|
||||||
|
) {}
|
||||||
|
|
||||||
public async createProxy(instance: InstanceDto, data: ProxyDto) {
|
public async createProxy(instance: InstanceDto, data: ProxyDto) {
|
||||||
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { IntegrationDto } from '@api/integrations/integration.dto';
|
import { IntegrationDto } from '@api/integrations/integration.dto';
|
||||||
|
import { JsonValue } from '@prisma/client/runtime/library';
|
||||||
import { WAPresence } from 'baileys';
|
import { WAPresence } from 'baileys';
|
||||||
|
|
||||||
export class InstanceDto extends IntegrationDto {
|
export class InstanceDto extends IntegrationDto {
|
||||||
@ -10,6 +11,9 @@ export class InstanceDto extends IntegrationDto {
|
|||||||
integration?: string;
|
integration?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
ownerJid?: string;
|
||||||
|
profileName?: string;
|
||||||
|
profilePicUrl?: string;
|
||||||
// settings
|
// settings
|
||||||
rejectCall?: boolean;
|
rejectCall?: boolean;
|
||||||
msgCall?: string;
|
msgCall?: string;
|
||||||
@ -18,12 +22,35 @@ export class InstanceDto extends IntegrationDto {
|
|||||||
readMessages?: boolean;
|
readMessages?: boolean;
|
||||||
readStatus?: boolean;
|
readStatus?: boolean;
|
||||||
syncFullHistory?: boolean;
|
syncFullHistory?: boolean;
|
||||||
|
wavoipToken?: string;
|
||||||
// proxy
|
// proxy
|
||||||
proxyHost?: string;
|
proxyHost?: string;
|
||||||
proxyPort?: string;
|
proxyPort?: string;
|
||||||
proxyProtocol?: string;
|
proxyProtocol?: string;
|
||||||
proxyUsername?: string;
|
proxyUsername?: string;
|
||||||
proxyPassword?: string;
|
proxyPassword?: string;
|
||||||
|
webhook?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
events?: string[];
|
||||||
|
headers?: JsonValue;
|
||||||
|
url?: string;
|
||||||
|
byEvents?: boolean;
|
||||||
|
base64?: boolean;
|
||||||
|
};
|
||||||
|
chatwootAccountId?: string;
|
||||||
|
chatwootConversationPending?: boolean;
|
||||||
|
chatwootAutoCreate?: boolean;
|
||||||
|
chatwootDaysLimitImportMessages?: number;
|
||||||
|
chatwootImportContacts?: boolean;
|
||||||
|
chatwootImportMessages?: boolean;
|
||||||
|
chatwootLogo?: string;
|
||||||
|
chatwootMergeBrazilContacts?: boolean;
|
||||||
|
chatwootNameInbox?: string;
|
||||||
|
chatwootOrganization?: string;
|
||||||
|
chatwootReopenConversation?: boolean;
|
||||||
|
chatwootSignMsg?: boolean;
|
||||||
|
chatwootToken?: string;
|
||||||
|
chatwootUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SetPresenceDto {
|
export class SetPresenceDto {
|
||||||
|
@ -6,4 +6,5 @@ export class SettingsDto {
|
|||||||
readMessages?: boolean;
|
readMessages?: boolean;
|
||||||
readStatus?: boolean;
|
readStatus?: boolean;
|
||||||
syncFullHistory?: boolean;
|
syncFullHistory?: boolean;
|
||||||
|
wavoipToken?: string;
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,6 @@ export class ChannelController {
|
|||||||
data.prismaRepository,
|
data.prismaRepository,
|
||||||
data.cache,
|
data.cache,
|
||||||
data.chatwootCache,
|
data.chatwootCache,
|
||||||
data.baileysCache,
|
|
||||||
data.providerFiles,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,16 @@ import { Router } from 'express';
|
|||||||
|
|
||||||
import { EvolutionRouter } from './evolution/evolution.router';
|
import { EvolutionRouter } from './evolution/evolution.router';
|
||||||
import { MetaRouter } from './meta/meta.router';
|
import { MetaRouter } from './meta/meta.router';
|
||||||
|
import { BaileysRouter } from './whatsapp/baileys.router';
|
||||||
|
|
||||||
export class ChannelRouter {
|
export class ChannelRouter {
|
||||||
public readonly router: Router;
|
public readonly router: Router;
|
||||||
|
|
||||||
constructor(configService: any) {
|
constructor(configService: any, ...guards: any[]) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
|
|
||||||
this.router.use('/', new EvolutionRouter(configService).router);
|
this.router.use('/', new EvolutionRouter(configService).router);
|
||||||
this.router.use('/', new MetaRouter(configService).router);
|
this.router.use('/', new MetaRouter(configService).router);
|
||||||
|
this.router.use('/baileys', new BaileysRouter(...guards).router);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,27 @@
|
|||||||
import { MediaMessage, Options, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { ProviderFiles } from '@api/provider/sessions';
|
import {
|
||||||
|
MediaMessage,
|
||||||
|
Options,
|
||||||
|
SendAudioDto,
|
||||||
|
SendButtonsDto,
|
||||||
|
SendMediaDto,
|
||||||
|
SendTextDto,
|
||||||
|
} from '@api/dto/sendMessage.dto';
|
||||||
|
import * as s3Service from '@api/integrations/storage/s3/libs/minio.server';
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { chatbotController } from '@api/server.module';
|
import { chatbotController } from '@api/server.module';
|
||||||
import { CacheService } from '@api/services/cache.service';
|
import { CacheService } from '@api/services/cache.service';
|
||||||
import { ChannelStartupService } from '@api/services/channel.service';
|
import { ChannelStartupService } from '@api/services/channel.service';
|
||||||
import { Events, wa } from '@api/types/wa.types';
|
import { Events, wa } from '@api/types/wa.types';
|
||||||
import { Chatwoot, ConfigService, Openai } from '@config/env.config';
|
import { Chatwoot, ConfigService, Openai, S3 } from '@config/env.config';
|
||||||
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||||
import { status } from '@utils/renderStatus';
|
import { createJid } from '@utils/createJid';
|
||||||
import { isURL } from 'class-validator';
|
import axios from 'axios';
|
||||||
|
import { isBase64, isURL } from 'class-validator';
|
||||||
import EventEmitter2 from 'eventemitter2';
|
import EventEmitter2 from 'eventemitter2';
|
||||||
import mime from 'mime';
|
import FormData from 'form-data';
|
||||||
|
import mimeTypes from 'mime-types';
|
||||||
|
import { join } from 'path';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
export class EvolutionStartupService extends ChannelStartupService {
|
export class EvolutionStartupService extends ChannelStartupService {
|
||||||
@ -20,8 +31,6 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
public readonly prismaRepository: PrismaRepository,
|
public readonly prismaRepository: PrismaRepository,
|
||||||
public readonly cache: CacheService,
|
public readonly cache: CacheService,
|
||||||
public readonly chatwootCache: CacheService,
|
public readonly chatwootCache: CacheService,
|
||||||
public readonly baileysCache: CacheService,
|
|
||||||
private readonly providerFiles: ProviderFiles,
|
|
||||||
) {
|
) {
|
||||||
super(configService, eventEmitter, prismaRepository, chatwootCache);
|
super(configService, eventEmitter, prismaRepository, chatwootCache);
|
||||||
|
|
||||||
@ -56,8 +65,34 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
await this.closeClient();
|
await this.closeClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setInstance(instance: InstanceDto) {
|
||||||
|
this.logger.setInstance(instance.instanceId);
|
||||||
|
|
||||||
|
this.instance.name = instance.instanceName;
|
||||||
|
this.instance.id = instance.instanceId;
|
||||||
|
this.instance.integration = instance.integration;
|
||||||
|
this.instance.number = instance.number;
|
||||||
|
this.instance.token = instance.token;
|
||||||
|
this.instance.businessId = instance.businessId;
|
||||||
|
|
||||||
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||||
|
this.chatwootService.eventWhatsapp(
|
||||||
|
Events.STATUS_INSTANCE,
|
||||||
|
{
|
||||||
|
instanceName: this.instance.name,
|
||||||
|
instanceId: this.instance.id,
|
||||||
|
integration: instance.integration,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instance: this.instance.name,
|
||||||
|
status: 'created',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async profilePicture(number: string) {
|
public async profilePicture(number: string) {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wuid: jid,
|
wuid: jid,
|
||||||
@ -78,11 +113,12 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async connectToWhatsapp(data?: any): Promise<any> {
|
public async connectToWhatsapp(data?: any): Promise<any> {
|
||||||
if (!data) return;
|
if (!data) {
|
||||||
|
this.loadChatwoot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loadChatwoot();
|
|
||||||
|
|
||||||
this.eventHandler(data);
|
this.eventHandler(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
@ -99,6 +135,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
id: received.key.id || v4(),
|
id: received.key.id || v4(),
|
||||||
remoteJid: received.key.remoteJid,
|
remoteJid: received.key.remoteJid,
|
||||||
fromMe: received.key.fromMe,
|
fromMe: received.key.fromMe,
|
||||||
|
profilePicUrl: received.profilePicUrl,
|
||||||
};
|
};
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
key,
|
key,
|
||||||
@ -110,7 +147,9 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.configService.get<Openai>('OPENAI').ENABLED) {
|
const isAudio = received?.message?.audioMessage;
|
||||||
|
|
||||||
|
if (this.configService.get<Openai>('OPENAI').ENABLED && isAudio) {
|
||||||
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
|
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
|
||||||
where: {
|
where: {
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
@ -165,7 +204,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
await this.updateContact({
|
await this.updateContact({
|
||||||
remoteJid: messageRaw.key.remoteJid,
|
remoteJid: messageRaw.key.remoteJid,
|
||||||
pushName: messageRaw.key.fromMe ? '' : messageRaw.key.fromMe == null ? '' : received.pushName,
|
pushName: messageRaw.pushName,
|
||||||
profilePicUrl: received.profilePicUrl,
|
profilePicUrl: received.profilePicUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -175,35 +214,6 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateContact(data: { remoteJid: string; pushName?: string; profilePicUrl?: string }) {
|
private async updateContact(data: { remoteJid: string; pushName?: string; profilePicUrl?: string }) {
|
||||||
const contact = await this.prismaRepository.contact.findFirst({
|
|
||||||
where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (contact) {
|
|
||||||
const contactRaw: any = {
|
|
||||||
remoteJid: data.remoteJid,
|
|
||||||
pushName: data?.pushName,
|
|
||||||
instanceId: this.instanceId,
|
|
||||||
profilePicUrl: data?.profilePicUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
|
|
||||||
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
|
||||||
await this.chatwootService.eventWhatsapp(
|
|
||||||
Events.CONTACTS_UPDATE,
|
|
||||||
{ instanceName: this.instance.name, instanceId: this.instanceId },
|
|
||||||
contactRaw,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prismaRepository.contact.updateMany({
|
|
||||||
where: { remoteJid: contact.remoteJid, instanceId: this.instanceId },
|
|
||||||
data: contactRaw,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactRaw: any = {
|
const contactRaw: any = {
|
||||||
remoteJid: data.remoteJid,
|
remoteJid: data.remoteJid,
|
||||||
pushName: data?.pushName,
|
pushName: data?.pushName,
|
||||||
@ -211,11 +221,40 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
profilePicUrl: data?.profilePicUrl,
|
profilePicUrl: data?.profilePicUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const existingContact = await this.prismaRepository.contact.findFirst({
|
||||||
|
where: {
|
||||||
|
remoteJid: data.remoteJid,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingContact) {
|
||||||
|
await this.prismaRepository.contact.updateMany({
|
||||||
|
where: {
|
||||||
|
remoteJid: data.remoteJid,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
},
|
||||||
|
data: contactRaw,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.prismaRepository.contact.create({
|
||||||
|
data: contactRaw,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
|
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
|
||||||
|
|
||||||
await this.prismaRepository.contact.create({
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||||
data: contactRaw,
|
await this.chatwootService.eventWhatsapp(
|
||||||
});
|
Events.CONTACTS_UPDATE,
|
||||||
|
{
|
||||||
|
instanceName: this.instance.name,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
integration: this.instance.integration,
|
||||||
|
},
|
||||||
|
contactRaw,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const chat = await this.prismaRepository.chat.findFirst({
|
const chat = await this.prismaRepository.chat.findFirst({
|
||||||
where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
|
||||||
@ -247,7 +286,13 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) {
|
protected async sendMessageWithTyping(
|
||||||
|
number: string,
|
||||||
|
message: any,
|
||||||
|
options?: Options,
|
||||||
|
file?: any,
|
||||||
|
isIntegration = false,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
let quoted: any;
|
let quoted: any;
|
||||||
let webhookUrl: any;
|
let webhookUrl: any;
|
||||||
@ -272,64 +317,187 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
webhookUrl = options.webhookUrl;
|
webhookUrl = options.webhookUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let audioFile;
|
||||||
|
|
||||||
const messageId = v4();
|
const messageId = v4();
|
||||||
|
|
||||||
let messageRaw: any = {
|
let messageRaw: any;
|
||||||
key: { fromMe: true, id: messageId, remoteJid: number },
|
|
||||||
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
|
||||||
webhookUrl,
|
|
||||||
source: 'unknown',
|
|
||||||
instanceId: this.instanceId,
|
|
||||||
status: status[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (message?.mediaType === 'image') {
|
if (message?.mediaType === 'image') {
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
...messageRaw,
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
message: {
|
message: {
|
||||||
mediaUrl: message.media,
|
base64: isBase64(message.media) ? message.media : undefined,
|
||||||
|
mediaUrl: isURL(message.media) ? message.media : undefined,
|
||||||
quoted,
|
quoted,
|
||||||
},
|
},
|
||||||
messageType: 'imageMessage',
|
messageType: 'imageMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
} else if (message?.mediaType === 'video') {
|
} else if (message?.mediaType === 'video') {
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
...messageRaw,
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
message: {
|
message: {
|
||||||
mediaUrl: message.media,
|
base64: isBase64(message.media) ? message.media : undefined,
|
||||||
|
mediaUrl: isURL(message.media) ? message.media : undefined,
|
||||||
quoted,
|
quoted,
|
||||||
},
|
},
|
||||||
messageType: 'videoMessage',
|
messageType: 'videoMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
} else if (message?.mediaType === 'audio') {
|
} else if (message?.mediaType === 'audio') {
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
...messageRaw,
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
message: {
|
message: {
|
||||||
mediaUrl: message.media,
|
base64: isBase64(message.media) ? message.media : undefined,
|
||||||
|
mediaUrl: isURL(message.media) ? message.media : undefined,
|
||||||
quoted,
|
quoted,
|
||||||
},
|
},
|
||||||
messageType: 'audioMessage',
|
messageType: 'audioMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const buffer = Buffer.from(message.media, 'base64');
|
||||||
|
audioFile = {
|
||||||
|
buffer,
|
||||||
|
mimetype: 'audio/mp4',
|
||||||
|
originalname: `${messageId}.mp4`,
|
||||||
};
|
};
|
||||||
} else if (message?.mediaType === 'document') {
|
} else if (message?.mediaType === 'document') {
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
...messageRaw,
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
message: {
|
message: {
|
||||||
mediaUrl: message.media,
|
base64: isBase64(message.media) ? message.media : undefined,
|
||||||
|
mediaUrl: isURL(message.media) ? message.media : undefined,
|
||||||
quoted,
|
quoted,
|
||||||
},
|
},
|
||||||
messageType: 'documentMessage',
|
messageType: 'documentMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
};
|
||||||
|
} else if (message.buttonMessage) {
|
||||||
|
messageRaw = {
|
||||||
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
|
message: {
|
||||||
|
...message.buttonMessage,
|
||||||
|
buttons: message.buttonMessage.buttons,
|
||||||
|
footer: message.buttonMessage.footer,
|
||||||
|
body: message.buttonMessage.body,
|
||||||
|
quoted,
|
||||||
|
},
|
||||||
|
messageType: 'buttonMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
};
|
||||||
|
} else if (message.listMessage) {
|
||||||
|
messageRaw = {
|
||||||
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
|
message: {
|
||||||
|
...message.listMessage,
|
||||||
|
quoted,
|
||||||
|
},
|
||||||
|
messageType: 'listMessage',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
...messageRaw,
|
key: { fromMe: true, id: messageId, remoteJid: number },
|
||||||
message: {
|
message: {
|
||||||
...message,
|
...message,
|
||||||
quoted,
|
quoted,
|
||||||
},
|
},
|
||||||
messageType: 'conversation',
|
messageType: 'conversation',
|
||||||
|
messageTimestamp: Math.round(new Date().getTime() / 1000),
|
||||||
|
webhookUrl,
|
||||||
|
source: 'unknown',
|
||||||
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (messageRaw.message.contextInfo) {
|
||||||
|
messageRaw.contextInfo = {
|
||||||
|
...messageRaw.message.contextInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageRaw.contextInfo?.stanzaId) {
|
||||||
|
const key: any = {
|
||||||
|
id: messageRaw.contextInfo.stanzaId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const findMessage = await this.prismaRepository.message.findFirst({
|
||||||
|
where: {
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (findMessage) {
|
||||||
|
messageRaw.contextInfo.quotedMessage = findMessage.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64 = messageRaw.message.base64;
|
||||||
|
delete messageRaw.message.base64;
|
||||||
|
|
||||||
|
if (base64 || file || audioFile) {
|
||||||
|
if (this.configService.get<S3>('S3').ENABLE) {
|
||||||
|
try {
|
||||||
|
const fileBuffer = audioFile?.buffer || file?.buffer;
|
||||||
|
const buffer = base64 ? Buffer.from(base64, 'base64') : fileBuffer;
|
||||||
|
|
||||||
|
let mediaType: string;
|
||||||
|
let mimetype = audioFile?.mimetype || file.mimetype;
|
||||||
|
|
||||||
|
if (messageRaw.messageType === 'documentMessage') {
|
||||||
|
mediaType = 'document';
|
||||||
|
mimetype = !mimetype ? 'application/pdf' : mimetype;
|
||||||
|
} else if (messageRaw.messageType === 'imageMessage') {
|
||||||
|
mediaType = 'image';
|
||||||
|
mimetype = !mimetype ? 'image/png' : mimetype;
|
||||||
|
} else if (messageRaw.messageType === 'audioMessage') {
|
||||||
|
mediaType = 'audio';
|
||||||
|
mimetype = !mimetype ? 'audio/mp4' : mimetype;
|
||||||
|
} else if (messageRaw.messageType === 'videoMessage') {
|
||||||
|
mediaType = 'video';
|
||||||
|
mimetype = !mimetype ? 'video/mp4' : mimetype;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = `${messageRaw.key.id}.${mimetype.split('/')[1]}`;
|
||||||
|
|
||||||
|
const size = buffer.byteLength;
|
||||||
|
|
||||||
|
const fullName = join(`${this.instance.id}`, messageRaw.key.remoteJid, mediaType, fileName);
|
||||||
|
|
||||||
|
await s3Service.uploadFile(fullName, buffer, size, {
|
||||||
|
'Content-Type': mimetype,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
||||||
|
|
||||||
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log(messageRaw);
|
this.logger.log(messageRaw);
|
||||||
|
|
||||||
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
|
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
|
||||||
@ -375,6 +543,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
mentionsEveryOne: data?.mentionsEveryOne,
|
mentionsEveryOne: data?.mentionsEveryOne,
|
||||||
mentioned: data?.mentioned,
|
mentioned: data?.mentioned,
|
||||||
},
|
},
|
||||||
|
null,
|
||||||
isIntegration,
|
isIntegration,
|
||||||
);
|
);
|
||||||
return res;
|
return res;
|
||||||
@ -396,7 +565,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
mediaMessage.fileName = 'video.mp4';
|
mediaMessage.fileName = 'video.mp4';
|
||||||
}
|
}
|
||||||
|
|
||||||
let mimetype: string;
|
let mimetype: string | false;
|
||||||
|
|
||||||
const prepareMedia: any = {
|
const prepareMedia: any = {
|
||||||
caption: mediaMessage?.caption,
|
caption: mediaMessage?.caption,
|
||||||
@ -407,9 +576,9 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isURL(mediaMessage.media)) {
|
if (isURL(mediaMessage.media)) {
|
||||||
mimetype = mime.getType(mediaMessage.media);
|
mimetype = mimeTypes.lookup(mediaMessage.media);
|
||||||
} else {
|
} else {
|
||||||
mimetype = mime.getType(mediaMessage.fileName);
|
mimetype = mimeTypes.lookup(mediaMessage.fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareMedia.mimetype = mimetype;
|
prepareMedia.mimetype = mimetype;
|
||||||
@ -439,33 +608,78 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
mentionsEveryOne: data?.mentionsEveryOne,
|
mentionsEveryOne: data?.mentionsEveryOne,
|
||||||
mentioned: data?.mentioned,
|
mentioned: data?.mentioned,
|
||||||
},
|
},
|
||||||
|
file,
|
||||||
isIntegration,
|
isIntegration,
|
||||||
);
|
);
|
||||||
|
|
||||||
return mediaSent;
|
return mediaSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async processAudio(audio: string, number: string) {
|
public async processAudio(audio: string, number: string, file: any) {
|
||||||
number = number.replace(/\D/g, '');
|
number = number.replace(/\D/g, '');
|
||||||
const hash = `${number}-${new Date().getTime()}`;
|
const hash = `${number}-${new Date().getTime()}`;
|
||||||
|
|
||||||
let mimetype: string;
|
if (process.env.API_AUDIO_CONVERTER) {
|
||||||
|
try {
|
||||||
|
this.logger.verbose('Using audio converter API');
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
const prepareMedia: any = {
|
if (file) {
|
||||||
fileName: `${hash}.mp4`,
|
formData.append('file', file.buffer, {
|
||||||
mediaType: 'audio',
|
filename: file.originalname,
|
||||||
media: audio,
|
contentType: file.mimetype,
|
||||||
};
|
});
|
||||||
|
} else if (isURL(audio)) {
|
||||||
|
formData.append('url', audio);
|
||||||
|
} else {
|
||||||
|
formData.append('base64', audio);
|
||||||
|
}
|
||||||
|
|
||||||
if (isURL(audio)) {
|
formData.append('format', 'mp4');
|
||||||
mimetype = mime.getType(audio);
|
|
||||||
|
const response = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||||
|
headers: {
|
||||||
|
...formData.getHeaders(),
|
||||||
|
apikey: process.env.API_AUDIO_CONVERTER_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response?.data?.audio) {
|
||||||
|
throw new InternalServerErrorException('Failed to convert audio');
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepareMedia: any = {
|
||||||
|
fileName: `${hash}.mp4`,
|
||||||
|
mediaType: 'audio',
|
||||||
|
media: response?.data?.audio,
|
||||||
|
mimetype: 'audio/mpeg',
|
||||||
|
};
|
||||||
|
|
||||||
|
return prepareMedia;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error?.response?.data || error);
|
||||||
|
throw new InternalServerErrorException(error?.response?.data?.message || error?.toString() || error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mimetype = mime.getType(prepareMedia.fileName);
|
let mimetype: string;
|
||||||
|
|
||||||
|
const prepareMedia: any = {
|
||||||
|
fileName: `${hash}.mp3`,
|
||||||
|
mediaType: 'audio',
|
||||||
|
media: audio,
|
||||||
|
mimetype: 'audio/mpeg',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isURL(audio)) {
|
||||||
|
mimetype = mimeTypes.lookup(audio).toString();
|
||||||
|
} else {
|
||||||
|
mimetype = mimeTypes.lookup(prepareMedia.fileName).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
@ -478,7 +692,7 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
throw new Error('File or buffer is undefined.');
|
throw new Error('File or buffer is undefined.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await this.processAudio(mediaData.audio, data.number);
|
const message = await this.processAudio(mediaData.audio, data.number, file);
|
||||||
|
|
||||||
const audioSent = await this.sendMessageWithTyping(
|
const audioSent = await this.sendMessageWithTyping(
|
||||||
data.number,
|
data.number,
|
||||||
@ -491,14 +705,34 @@ export class EvolutionStartupService extends ChannelStartupService {
|
|||||||
mentionsEveryOne: data?.mentionsEveryOne,
|
mentionsEveryOne: data?.mentionsEveryOne,
|
||||||
mentioned: data?.mentioned,
|
mentioned: data?.mentioned,
|
||||||
},
|
},
|
||||||
|
file,
|
||||||
isIntegration,
|
isIntegration,
|
||||||
);
|
);
|
||||||
|
|
||||||
return audioSent;
|
return audioSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async buttonMessage() {
|
public async buttonMessage(data: SendButtonsDto, isIntegration = false) {
|
||||||
throw new BadRequestException('Method not available on Evolution Channel');
|
return await this.sendMessageWithTyping(
|
||||||
|
data.number,
|
||||||
|
{
|
||||||
|
buttonMessage: {
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
footer: data.footer,
|
||||||
|
buttons: data.buttons,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: data?.delay,
|
||||||
|
presence: 'composing',
|
||||||
|
quoted: data?.quoted,
|
||||||
|
mentionsEveryOne: data?.mentionsEveryOne,
|
||||||
|
mentioned: data?.mentioned,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
isIntegration,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
public async locationMessage() {
|
public async locationMessage() {
|
||||||
throw new BadRequestException('Method not available on Evolution Channel');
|
throw new BadRequestException('Method not available on Evolution Channel');
|
||||||
|
@ -22,13 +22,14 @@ import { ChannelStartupService } from '@api/services/channel.service';
|
|||||||
import { Events, wa } from '@api/types/wa.types';
|
import { Events, wa } from '@api/types/wa.types';
|
||||||
import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
|
import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
|
||||||
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||||
|
import { createJid } from '@utils/createJid';
|
||||||
import { status } from '@utils/renderStatus';
|
import { status } from '@utils/renderStatus';
|
||||||
import axios from 'axios';
|
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 { createReadStream } from 'fs';
|
||||||
import mime from 'mime';
|
import mimeTypes from 'mime-types';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
export class BusinessStartupService extends ChannelStartupService {
|
export class BusinessStartupService extends ChannelStartupService {
|
||||||
@ -70,6 +71,10 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
await this.closeClient();
|
await this.closeClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isMediaMessage(message: any) {
|
||||||
|
return message.document || message.image || message.audio || message.video;
|
||||||
|
}
|
||||||
|
|
||||||
private async post(message: any, params: string) {
|
private async post(message: any, params: string) {
|
||||||
try {
|
try {
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
||||||
@ -84,7 +89,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async profilePicture(number: string) {
|
public async profilePicture(number: string) {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wuid: jid,
|
wuid: jid,
|
||||||
@ -128,9 +133,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.eventHandler(content);
|
this.eventHandler(content);
|
||||||
|
|
||||||
this.phoneNumber = this.createJid(
|
this.phoneNumber = createJid(content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id);
|
||||||
content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
throw new InternalServerErrorException(error?.toString());
|
throw new InternalServerErrorException(error?.toString());
|
||||||
@ -227,7 +230,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!contact.phones[0]?.wa_id) {
|
if (!contact.phones[0]?.wa_id) {
|
||||||
contact.phones[0].wa_id = this.createJid(contact.phones[0].phone);
|
contact.phones[0].wa_id = createJid(contact.phones[0].phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
result +=
|
result +=
|
||||||
@ -301,12 +304,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
remoteJid: this.phoneNumber,
|
remoteJid: this.phoneNumber,
|
||||||
fromMe: received.messages[0].from === received.metadata.phone_number_id,
|
fromMe: received.messages[0].from === received.metadata.phone_number_id,
|
||||||
};
|
};
|
||||||
if (
|
if (this.isMediaMessage(received?.messages[0])) {
|
||||||
received?.messages[0].document ||
|
|
||||||
received?.messages[0].image ||
|
|
||||||
received?.messages[0].audio ||
|
|
||||||
received?.messages[0].video
|
|
||||||
) {
|
|
||||||
messageRaw = {
|
messageRaw = {
|
||||||
key,
|
key,
|
||||||
pushName,
|
pushName,
|
||||||
@ -331,15 +329,19 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
|
const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
|
||||||
|
|
||||||
const mediaType = message.messages[0].document
|
let mediaType;
|
||||||
? 'document'
|
|
||||||
: message.messages[0].image
|
|
||||||
? 'image'
|
|
||||||
: message.messages[0].audio
|
|
||||||
? 'audio'
|
|
||||||
: 'video';
|
|
||||||
|
|
||||||
const mimetype = result.headers['content-type'];
|
if (message.messages[0].document) {
|
||||||
|
mediaType = 'document';
|
||||||
|
} else if (message.messages[0].image) {
|
||||||
|
mediaType = 'image';
|
||||||
|
} else if (message.messages[0].audio) {
|
||||||
|
mediaType = 'audio';
|
||||||
|
} else {
|
||||||
|
mediaType = 'video';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimetype = result.data?.mime_type || result.headers['content-type'];
|
||||||
|
|
||||||
const contentDisposition = result.headers['content-disposition'];
|
const contentDisposition = result.headers['content-disposition'];
|
||||||
let fileName = `${message.messages[0].id}.${mimetype.split('/')[1]}`;
|
let fileName = `${message.messages[0].id}.${mimetype.split('/')[1]}`;
|
||||||
@ -352,15 +354,19 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const size = result.headers['content-length'] || buffer.data.byteLength;
|
const size = result.headers['content-length'] || buffer.data.byteLength;
|
||||||
|
|
||||||
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
|
const fullName = join(`${this.instance.id}`, key.remoteJid, mediaType, fileName);
|
||||||
|
|
||||||
await s3Service.uploadFile(fullName, buffer.data, size, {
|
await s3Service.uploadFile(fullName, buffer.data, size, {
|
||||||
'Content-Type': mimetype,
|
'Content-Type': mimetype,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createdMessage = await this.prismaRepository.message.create({
|
||||||
|
data: messageRaw,
|
||||||
|
});
|
||||||
|
|
||||||
await this.prismaRepository.media.create({
|
await this.prismaRepository.media.create({
|
||||||
data: {
|
data: {
|
||||||
messageId: received.messages[0].id,
|
messageId: createdMessage.id,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
type: mediaType,
|
type: mediaType,
|
||||||
fileName: fullName,
|
fileName: fullName,
|
||||||
@ -371,6 +377,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
||||||
|
|
||||||
messageRaw.message.mediaUrl = mediaUrl;
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
|
messageRaw.message.base64 = buffer.data.toString('base64');
|
||||||
} 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]);
|
||||||
}
|
}
|
||||||
@ -458,16 +465,23 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const audioMessage = received?.messages[0]?.audio;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
openAiDefaultSettings &&
|
openAiDefaultSettings &&
|
||||||
openAiDefaultSettings.openaiCredsId &&
|
openAiDefaultSettings.openaiCredsId &&
|
||||||
openAiDefaultSettings.speechToText &&
|
openAiDefaultSettings.speechToText &&
|
||||||
received?.message?.audioMessage
|
audioMessage
|
||||||
) {
|
) {
|
||||||
messageRaw.message.speechToText = await this.openaiService.speechToText(
|
messageRaw.message.speechToText = await this.openaiService.speechToText(
|
||||||
openAiDefaultSettings.OpenaiCreds,
|
openAiDefaultSettings.OpenaiCreds,
|
||||||
received,
|
{
|
||||||
this.client.updateMediaMessage,
|
message: {
|
||||||
|
mediaUrl: messageRaw.message.mediaUrl,
|
||||||
|
...messageRaw,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,9 +511,11 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prismaRepository.message.create({
|
if (!this.isMediaMessage(received?.messages[0])) {
|
||||||
data: messageRaw,
|
await this.prismaRepository.message.create({
|
||||||
});
|
data: messageRaw,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const contact = await this.prismaRepository.contact.findFirst({
|
const contact = await this.prismaRepository.contact.findFirst({
|
||||||
where: { instanceId: this.instanceId, remoteJid: key.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: key.remoteJid },
|
||||||
@ -783,6 +799,8 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
return await this.post(content, 'messages');
|
return await this.post(content, 'messages');
|
||||||
}
|
}
|
||||||
if (message['media']) {
|
if (message['media']) {
|
||||||
|
const isImage = message['mimetype']?.startsWith('image/');
|
||||||
|
|
||||||
content = {
|
content = {
|
||||||
messaging_product: 'whatsapp',
|
messaging_product: 'whatsapp',
|
||||||
recipient_type: 'individual',
|
recipient_type: 'individual',
|
||||||
@ -791,6 +809,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
[message['mediaType']]: {
|
[message['mediaType']]: {
|
||||||
[message['type']]: message['id'],
|
[message['type']]: message['id'],
|
||||||
preview_url: linkPreview,
|
preview_url: linkPreview,
|
||||||
|
...(message['fileName'] && !isImage && { filename: message['fileName'] }),
|
||||||
caption: message['caption'],
|
caption: message['caption'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -895,7 +914,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messageRaw: any = {
|
const messageRaw: any = {
|
||||||
key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: this.createJid(number) },
|
key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: createJid(number) },
|
||||||
message: this.convertMessageToRaw(message, content),
|
message: this.convertMessageToRaw(message, content),
|
||||||
messageType: this.renderMessageType(content.type),
|
messageType: this.renderMessageType(content.type),
|
||||||
messageTimestamp: (messageSent?.messages[0]?.timestamp as number) || Math.round(new Date().getTime() / 1000),
|
messageTimestamp: (messageSent?.messages[0]?.timestamp as number) || Math.round(new Date().getTime() / 1000),
|
||||||
@ -997,7 +1016,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
mediaMessage.fileName = 'video.mp4';
|
mediaMessage.fileName = 'video.mp4';
|
||||||
}
|
}
|
||||||
|
|
||||||
let mimetype: string;
|
let mimetype: string | false;
|
||||||
|
|
||||||
const prepareMedia: any = {
|
const prepareMedia: any = {
|
||||||
caption: mediaMessage?.caption,
|
caption: mediaMessage?.caption,
|
||||||
@ -1008,11 +1027,11 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isURL(mediaMessage.media)) {
|
if (isURL(mediaMessage.media)) {
|
||||||
mimetype = mime.getType(mediaMessage.media);
|
mimetype = mimeTypes.lookup(mediaMessage.media);
|
||||||
prepareMedia.id = mediaMessage.media;
|
prepareMedia.id = mediaMessage.media;
|
||||||
prepareMedia.type = 'link';
|
prepareMedia.type = 'link';
|
||||||
} else {
|
} else {
|
||||||
mimetype = mime.getType(mediaMessage.fileName);
|
mimetype = mimeTypes.lookup(mediaMessage.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';
|
||||||
@ -1055,7 +1074,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
number = number.replace(/\D/g, '');
|
number = number.replace(/\D/g, '');
|
||||||
const hash = `${number}-${new Date().getTime()}`;
|
const hash = `${number}-${new Date().getTime()}`;
|
||||||
|
|
||||||
let mimetype: string;
|
let mimetype: string | false;
|
||||||
|
|
||||||
const prepareMedia: any = {
|
const prepareMedia: any = {
|
||||||
fileName: `${hash}.mp3`,
|
fileName: `${hash}.mp3`,
|
||||||
@ -1064,11 +1083,11 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isURL(audio)) {
|
if (isURL(audio)) {
|
||||||
mimetype = mime.getType(audio);
|
mimetype = mimeTypes.lookup(audio);
|
||||||
prepareMedia.id = audio;
|
prepareMedia.id = audio;
|
||||||
prepareMedia.type = 'link';
|
prepareMedia.type = 'link';
|
||||||
} else {
|
} else {
|
||||||
mimetype = mime.getType(prepareMedia.fileName);
|
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';
|
||||||
@ -1084,6 +1103,9 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (file?.buffer) {
|
if (file?.buffer) {
|
||||||
mediaData.audio = file.buffer.toString('base64');
|
mediaData.audio = file.buffer.toString('base64');
|
||||||
|
} else if (isURL(mediaData.audio)) {
|
||||||
|
// DO NOTHING
|
||||||
|
// mediaData.audio = mediaData.audio;
|
||||||
} else {
|
} else {
|
||||||
console.error('El archivo no tiene buffer o file es undefined');
|
console.error('El archivo no tiene buffer o file es undefined');
|
||||||
throw new Error('File or buffer is undefined');
|
throw new Error('File or buffer is undefined');
|
||||||
@ -1251,7 +1273,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!contact.wuid) {
|
if (!contact.wuid) {
|
||||||
contact.wuid = this.createJid(contact.phoneNumber);
|
contact.wuid = createJid(contact.phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD';
|
result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD';
|
||||||
|
60
src/api/integrations/channel/whatsapp/baileys.controller.ts
Normal file
60
src/api/integrations/channel/whatsapp/baileys.controller.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
|
|
||||||
|
export class BaileysController {
|
||||||
|
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||||
|
|
||||||
|
public async onWhatsapp({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysOnWhatsapp(body?.jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async profilePictureUrl({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysProfilePictureUrl(body?.jid, body?.type, body?.timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async assertSessions({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysAssertSessions(body?.jids, body?.force);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createParticipantNodes({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysCreateParticipantNodes(body?.jids, body?.message, body?.extraAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUSyncDevices({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysGetUSyncDevices(body?.jids, body?.useCache, body?.ignoreZeroDevices);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async generateMessageTag({ instanceName }: InstanceDto) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysGenerateMessageTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendNode({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysSendNode(body?.stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async signalRepositoryDecryptMessage({ instanceName }: InstanceDto, body: any) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysSignalRepositoryDecryptMessage(body?.jid, body?.type, body?.ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAuthState({ instanceName }: InstanceDto) {
|
||||||
|
const instance = this.waMonitor.waInstances[instanceName];
|
||||||
|
|
||||||
|
return instance.baileysGetAuthState();
|
||||||
|
}
|
||||||
|
}
|
105
src/api/integrations/channel/whatsapp/baileys.router.ts
Normal file
105
src/api/integrations/channel/whatsapp/baileys.router.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||||
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
|
import { HttpStatus } from '@api/routes/index.router';
|
||||||
|
import { baileysController } from '@api/server.module';
|
||||||
|
import { instanceSchema } from '@validate/instance.schema';
|
||||||
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
|
export class BaileysRouter extends RouterBroker {
|
||||||
|
constructor(...guards: RequestHandler[]) {
|
||||||
|
super();
|
||||||
|
this.router
|
||||||
|
.post(this.routerPath('onWhatsapp'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.onWhatsapp(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('profilePictureUrl'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.profilePictureUrl(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('assertSessions'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.assertSessions(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('createParticipantNodes'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.createParticipantNodes(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('getUSyncDevices'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.getUSyncDevices(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('generateMessageTag'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.generateMessageTag(instance),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('sendNode'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.sendNode(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('signalRepositoryDecryptMessage'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.signalRepositoryDecryptMessage(instance, req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
})
|
||||||
|
.post(this.routerPath('getAuthState'), ...guards, async (req, res) => {
|
||||||
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
request: req,
|
||||||
|
schema: instanceSchema,
|
||||||
|
ClassRef: InstanceDto,
|
||||||
|
execute: (instance) => baileysController.getAuthState(instance),
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(HttpStatus.OK).json(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly router: Router = Router();
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import { BinaryNode, Contact, JidWithDevice, proto, WAConnectionState } from 'baileys';
|
||||||
|
|
||||||
|
export interface ServerToClientEvents {
|
||||||
|
withAck: (d: string, callback: (e: number) => void) => void;
|
||||||
|
onWhatsApp: onWhatsAppType;
|
||||||
|
profilePictureUrl: ProfilePictureUrlType;
|
||||||
|
assertSessions: AssertSessionsType;
|
||||||
|
createParticipantNodes: CreateParticipantNodesType;
|
||||||
|
getUSyncDevices: GetUSyncDevicesType;
|
||||||
|
generateMessageTag: GenerateMessageTagType;
|
||||||
|
sendNode: SendNodeType;
|
||||||
|
'signalRepository:decryptMessage': SignalRepositoryDecryptMessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientToServerEvents {
|
||||||
|
init: (
|
||||||
|
me: Contact | undefined,
|
||||||
|
account: proto.IADVSignedDeviceIdentity | undefined,
|
||||||
|
status: WAConnectionState,
|
||||||
|
) => void;
|
||||||
|
'CB:call': (packet: any) => void;
|
||||||
|
'CB:ack,class:call': (packet: any) => void;
|
||||||
|
'connection.update:status': (
|
||||||
|
me: Contact | undefined,
|
||||||
|
account: proto.IADVSignedDeviceIdentity | undefined,
|
||||||
|
status: WAConnectionState,
|
||||||
|
) => void;
|
||||||
|
'connection.update:qr': (qr: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type onWhatsAppType = (jid: string, callback: onWhatsAppCallback) => void;
|
||||||
|
export type onWhatsAppCallback = (
|
||||||
|
response: {
|
||||||
|
exists: boolean;
|
||||||
|
jid: string;
|
||||||
|
}[],
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type ProfilePictureUrlType = (
|
||||||
|
jid: string,
|
||||||
|
type: 'image' | 'preview',
|
||||||
|
timeoutMs: number | undefined,
|
||||||
|
callback: ProfilePictureUrlCallback,
|
||||||
|
) => void;
|
||||||
|
export type ProfilePictureUrlCallback = (response: string | undefined) => void;
|
||||||
|
|
||||||
|
export type AssertSessionsType = (jids: string[], force: boolean, callback: AssertSessionsCallback) => void;
|
||||||
|
export type AssertSessionsCallback = (response: boolean) => void;
|
||||||
|
|
||||||
|
export type CreateParticipantNodesType = (
|
||||||
|
jids: string[],
|
||||||
|
message: any,
|
||||||
|
extraAttrs: any,
|
||||||
|
callback: CreateParticipantNodesCallback,
|
||||||
|
) => void;
|
||||||
|
export type CreateParticipantNodesCallback = (nodes: any, shouldIncludeDeviceIdentity: boolean) => void;
|
||||||
|
|
||||||
|
export type GetUSyncDevicesType = (
|
||||||
|
jids: string[],
|
||||||
|
useCache: boolean,
|
||||||
|
ignoreZeroDevices: boolean,
|
||||||
|
callback: GetUSyncDevicesTypeCallback,
|
||||||
|
) => void;
|
||||||
|
export type GetUSyncDevicesTypeCallback = (jids: JidWithDevice[]) => void;
|
||||||
|
|
||||||
|
export type GenerateMessageTagType = (callback: GenerateMessageTagTypeCallback) => void;
|
||||||
|
export type GenerateMessageTagTypeCallback = (response: string) => void;
|
||||||
|
|
||||||
|
export type SendNodeType = (stanza: BinaryNode, callback: SendNodeTypeCallback) => void;
|
||||||
|
export type SendNodeTypeCallback = (response: boolean) => void;
|
||||||
|
|
||||||
|
export type SignalRepositoryDecryptMessageType = (
|
||||||
|
jid: string,
|
||||||
|
type: 'pkmsg' | 'msg',
|
||||||
|
ciphertext: Buffer,
|
||||||
|
callback: SignalRepositoryDecryptMessageCallback,
|
||||||
|
) => void;
|
||||||
|
export type SignalRepositoryDecryptMessageCallback = (response: any) => void;
|
@ -0,0 +1,181 @@
|
|||||||
|
import { ConnectionState, WAConnectionState, WASocket } from 'baileys';
|
||||||
|
import { io, Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
import { ClientToServerEvents, ServerToClientEvents } from './transport.type';
|
||||||
|
|
||||||
|
let baileys_connection_state: WAConnectionState = 'close';
|
||||||
|
|
||||||
|
export const useVoiceCallsBaileys = async (
|
||||||
|
wavoip_token: string,
|
||||||
|
baileys_sock: WASocket,
|
||||||
|
status?: WAConnectionState,
|
||||||
|
logger?: boolean,
|
||||||
|
) => {
|
||||||
|
baileys_connection_state = status ?? 'close';
|
||||||
|
|
||||||
|
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('https://devices.wavoip.com/baileys', {
|
||||||
|
transports: ['websocket'],
|
||||||
|
path: `/${wavoip_token}/websocket`,
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
if (logger) console.log('[*] - Wavoip connected', socket.id);
|
||||||
|
|
||||||
|
socket.emit(
|
||||||
|
'init',
|
||||||
|
baileys_sock.authState.creds.me,
|
||||||
|
baileys_sock.authState.creds.account,
|
||||||
|
baileys_connection_state,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
if (logger) console.log('[*] - Wavoip disconnect');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('connect_error', (error) => {
|
||||||
|
if (socket.active) {
|
||||||
|
if (logger)
|
||||||
|
console.log(
|
||||||
|
'[*] - Wavoip connection error temporary failure, the socket will automatically try to reconnect',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (logger) console.log('[*] - Wavoip connection error', error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('onWhatsApp', async (jid, callback) => {
|
||||||
|
try {
|
||||||
|
const response: any = await baileys_sock.onWhatsApp(jid);
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call onWhatsApp function', response, jid);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call onWhatsApp function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('profilePictureUrl', async (jid, type, timeoutMs, callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.profilePictureUrl(jid, type, timeoutMs);
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call profilePictureUrl function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call profilePictureUrl function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('assertSessions', async (jids, force, callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.assertSessions(jids, force);
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call assertSessions function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call assertSessions function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('createParticipantNodes', async (jids, message, extraAttrs, callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.createParticipantNodes(jids, message, extraAttrs);
|
||||||
|
|
||||||
|
callback(response, true);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call createParticipantNodes function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call createParticipantNodes function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('getUSyncDevices', async (jids, useCache, ignoreZeroDevices, callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.getUSyncDevices(jids, useCache, ignoreZeroDevices);
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call getUSyncDevices function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call getUSyncDevices function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('generateMessageTag', async (callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.generateMessageTag();
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call generateMessageTag function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call generateMessageTag function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('sendNode', async (stanza, callback) => {
|
||||||
|
try {
|
||||||
|
console.log('sendNode', JSON.stringify(stanza));
|
||||||
|
const response = await baileys_sock.sendNode(stanza);
|
||||||
|
|
||||||
|
callback(true);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call sendNode function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call sendNode function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('signalRepository:decryptMessage', async (jid, type, ciphertext, callback) => {
|
||||||
|
try {
|
||||||
|
const response = await baileys_sock.signalRepository.decryptMessage({
|
||||||
|
jid: jid,
|
||||||
|
type: type,
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(response);
|
||||||
|
|
||||||
|
if (logger) console.log('[*] Success on call signalRepository:decryptMessage function', response);
|
||||||
|
} catch (error) {
|
||||||
|
if (logger) console.error('[*] Error on call signalRepository:decryptMessage function', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// we only use this connection data to inform the webphone that the device is connected and creeds account to generate e2e whatsapp key for make call packets
|
||||||
|
baileys_sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
|
||||||
|
const { connection } = update;
|
||||||
|
|
||||||
|
if (connection) {
|
||||||
|
baileys_connection_state = connection;
|
||||||
|
socket
|
||||||
|
.timeout(1000)
|
||||||
|
.emit(
|
||||||
|
'connection.update:status',
|
||||||
|
baileys_sock.authState.creds.me,
|
||||||
|
baileys_sock.authState.creds.account,
|
||||||
|
connection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.qr) {
|
||||||
|
socket.timeout(1000).emit('connection.update:qr', update.qr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
baileys_sock.ws.on('CB:call', (packet) => {
|
||||||
|
if (logger) console.log('[*] Signling received');
|
||||||
|
socket.volatile.timeout(1000).emit('CB:call', packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
baileys_sock.ws.on('CB:ack,class:call', (packet) => {
|
||||||
|
if (logger) console.log('[*] Signling ack received');
|
||||||
|
socket.volatile.timeout(1000).emit('CB:ack,class:call', packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
};
|
@ -76,7 +76,9 @@ import {
|
|||||||
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions';
|
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@exceptions';
|
||||||
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
|
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
|
||||||
import { Boom } from '@hapi/boom';
|
import { Boom } from '@hapi/boom';
|
||||||
|
import { createId as cuid } from '@paralleldrive/cuid2';
|
||||||
import { Instance } from '@prisma/client';
|
import { Instance } from '@prisma/client';
|
||||||
|
import { createJid } from '@utils/createJid';
|
||||||
import { makeProxyAgent } from '@utils/makeProxyAgent';
|
import { makeProxyAgent } from '@utils/makeProxyAgent';
|
||||||
import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache';
|
import { getOnWhatsappCache, saveOnWhatsappCache } from '@utils/onWhatsappCache';
|
||||||
import { status } from '@utils/renderStatus';
|
import { status } from '@utils/renderStatus';
|
||||||
@ -130,7 +132,7 @@ import ffmpeg from 'fluent-ffmpeg';
|
|||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import mime from 'mime';
|
import mimeTypes from 'mime-types';
|
||||||
import NodeCache from 'node-cache';
|
import NodeCache from 'node-cache';
|
||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import { release } from 'os';
|
import { release } from 'os';
|
||||||
@ -142,6 +144,8 @@ import sharp from 'sharp';
|
|||||||
import { PassThrough, Readable } from 'stream';
|
import { PassThrough, Readable } from 'stream';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
|
||||||
|
|
||||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
||||||
|
|
||||||
// Adicione a função getVideoDuration no início do arquivo
|
// Adicione a função getVideoDuration no início do arquivo
|
||||||
@ -270,7 +274,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
public async getProfileStatus() {
|
public async getProfileStatus() {
|
||||||
const status = await this.client.fetchStatus(this.instance.wuid);
|
const status = await this.client.fetchStatus(this.instance.wuid);
|
||||||
|
|
||||||
return status?.status;
|
return status[0]?.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get profilePictureUrl() {
|
public get profilePictureUrl() {
|
||||||
@ -309,6 +313,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
instance: this.instance.name,
|
instance: this.instance.name,
|
||||||
state: 'refused',
|
state: 'refused',
|
||||||
statusReason: DisconnectReason.connectionClosed,
|
statusReason: DisconnectReason.connectionClosed,
|
||||||
|
wuid: this.instance.wuid,
|
||||||
|
profileName: await this.getProfileName(),
|
||||||
|
profilePictureUrl: this.instance.profilePictureUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.endSession = true;
|
this.endSession = true;
|
||||||
@ -388,11 +395,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
state: connection,
|
state: connection,
|
||||||
statusReason: (lastDisconnect?.error as Boom)?.output?.statusCode ?? 200,
|
statusReason: (lastDisconnect?.error as Boom)?.output?.statusCode ?? 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
|
|
||||||
instance: this.instance.name,
|
|
||||||
...this.stateConnection,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection === 'close') {
|
if (connection === 'close') {
|
||||||
@ -434,6 +436,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
this.eventEmitter.emit('logout.instance', this.instance.name, 'inner');
|
this.eventEmitter.emit('logout.instance', this.instance.name, 'inner');
|
||||||
this.client?.ws?.close();
|
this.client?.ws?.close();
|
||||||
this.client.end(new Error('Close connection'));
|
this.client.end(new Error('Close connection'));
|
||||||
|
|
||||||
|
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
|
||||||
|
instance: this.instance.name,
|
||||||
|
...this.stateConnection,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +488,21 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
);
|
);
|
||||||
this.syncChatwootLostMessages();
|
this.syncChatwootLostMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
|
||||||
|
instance: this.instance.name,
|
||||||
|
wuid: this.instance.wuid,
|
||||||
|
profileName: await this.getProfileName(),
|
||||||
|
profilePictureUrl: this.instance.profilePictureUrl,
|
||||||
|
...this.stateConnection,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection === 'connecting') {
|
||||||
|
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
|
||||||
|
instance: this.instance.name,
|
||||||
|
...this.stateConnection,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,8 +694,30 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.client = makeWASocket(socketConfig);
|
this.client = makeWASocket(socketConfig);
|
||||||
|
|
||||||
|
if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) {
|
||||||
|
useVoiceCallsBaileys(this.localSettings.wavoipToken, this.client, this.connectionStatus.state as any, true);
|
||||||
|
}
|
||||||
|
|
||||||
this.eventHandler();
|
this.eventHandler();
|
||||||
|
|
||||||
|
this.client.ws.on('CB:call', (packet) => {
|
||||||
|
console.log('CB:call', packet);
|
||||||
|
const payload = {
|
||||||
|
event: 'CB:call',
|
||||||
|
packet: packet,
|
||||||
|
};
|
||||||
|
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.ws.on('CB:ack,class:call', (packet) => {
|
||||||
|
console.log('CB:ack,class:call', packet);
|
||||||
|
const payload = {
|
||||||
|
event: 'CB:ack,class:call',
|
||||||
|
packet: packet,
|
||||||
|
};
|
||||||
|
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
|
||||||
|
});
|
||||||
|
|
||||||
this.phoneNumber = number;
|
this.phoneNumber = number;
|
||||||
|
|
||||||
return this.client;
|
return this.client;
|
||||||
@ -973,7 +1017,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const messagesRaw: any[] = [];
|
const messagesRaw: any[] = [];
|
||||||
|
|
||||||
const messagesRepository = new Set(
|
const messagesRepository: Set<string> = new Set(
|
||||||
chatwootImport.getRepositoryMessagesCache(instance) ??
|
chatwootImport.getRepositoryMessagesCache(instance) ??
|
||||||
(
|
(
|
||||||
await this.prismaRepository.message.findMany({
|
await this.prismaRepository.message.findMany({
|
||||||
@ -1135,21 +1179,25 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
const existingChat = await this.prismaRepository.chat.findFirst({
|
const existingChat = await this.prismaRepository.chat.findFirst({
|
||||||
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
|
||||||
|
select: { id: true, name: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingChat) {
|
if (
|
||||||
const chatToInsert = {
|
existingChat &&
|
||||||
remoteJid: received.key.remoteJid,
|
received.pushName &&
|
||||||
instanceId: this.instanceId,
|
existingChat.name !== received.pushName &&
|
||||||
name: received.pushName || '',
|
received.pushName.trim().length > 0
|
||||||
unreadMessages: 0,
|
) {
|
||||||
};
|
this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]);
|
||||||
|
|
||||||
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
|
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||||
await this.prismaRepository.chat.create({
|
try {
|
||||||
data: chatToInsert,
|
await this.prismaRepository.chat.update({
|
||||||
});
|
where: { id: existingChat.id },
|
||||||
|
data: { name: received.pushName },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1243,7 +1291,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
const mimetype = mime.getType(fileName).toString();
|
const mimetype = mimeTypes.lookup(fileName).toString();
|
||||||
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
|
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
|
||||||
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, {
|
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, {
|
||||||
'Content-Type': mimetype,
|
'Content-Type': mimetype,
|
||||||
@ -1276,17 +1324,21 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (this.localWebhook.enabled) {
|
if (this.localWebhook.enabled) {
|
||||||
if (isMedia && this.localWebhook.webhookBase64) {
|
if (isMedia && this.localWebhook.webhookBase64) {
|
||||||
const buffer = await downloadMediaMessage(
|
try {
|
||||||
{ key: received.key, message: received?.message },
|
const buffer = await downloadMediaMessage(
|
||||||
'buffer',
|
{ key: received.key, message: received?.message },
|
||||||
{},
|
'buffer',
|
||||||
{
|
{},
|
||||||
logger: P({ level: 'error' }) as any,
|
{
|
||||||
reuploadRequest: this.client.updateMediaMessage,
|
logger: P({ level: 'error' }) as any,
|
||||||
},
|
reuploadRequest: this.client.updateMediaMessage,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
messageRaw.message.base64 = buffer ? buffer.toString('base64') : undefined;
|
messageRaw.message.base64 = buffer ? buffer.toString('base64') : undefined;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(['Error converting media to base64', error?.message]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1483,9 +1535,16 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
|
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||||
await this.prismaRepository.chat.create({
|
try {
|
||||||
data: chatToInsert,
|
await this.prismaRepository.chat.update({
|
||||||
});
|
where: {
|
||||||
|
id: existingChat.id,
|
||||||
|
},
|
||||||
|
data: chatToInsert,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1523,6 +1582,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
private readonly labelHandle = {
|
private readonly labelHandle = {
|
||||||
[Events.LABELS_EDIT]: async (label: Label) => {
|
[Events.LABELS_EDIT]: async (label: Label) => {
|
||||||
|
this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name });
|
||||||
|
|
||||||
const labelsRepository = await this.prismaRepository.label.findMany({
|
const labelsRepository = await this.prismaRepository.label.findMany({
|
||||||
where: { instanceId: this.instanceId },
|
where: { instanceId: this.instanceId },
|
||||||
});
|
});
|
||||||
@ -1557,7 +1618,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
create: labelData,
|
create: labelData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1565,26 +1625,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
data: { association: LabelAssociation; type: 'remove' | 'add' },
|
data: { association: LabelAssociation; type: 'remove' | 'add' },
|
||||||
database: Database,
|
database: Database,
|
||||||
) => {
|
) => {
|
||||||
|
this.logger.info(
|
||||||
|
`labels association - ${data?.association?.chatId} (${data.type}-${data?.association?.type}): ${data?.association?.labelId}`,
|
||||||
|
);
|
||||||
if (database.SAVE_DATA.CHATS) {
|
if (database.SAVE_DATA.CHATS) {
|
||||||
const chats = await this.prismaRepository.chat.findMany({
|
const instanceId = this.instanceId;
|
||||||
where: { instanceId: this.instanceId },
|
const chatId = data.association.chatId;
|
||||||
});
|
const labelId = data.association.labelId;
|
||||||
const chat = chats.find((c) => c.remoteJid === data.association.chatId);
|
|
||||||
if (chat) {
|
|
||||||
const labelsArray = Array.isArray(chat.labels) ? chat.labels.map((event) => String(event)) : [];
|
|
||||||
let labels = [...labelsArray];
|
|
||||||
|
|
||||||
if (data.type === 'remove') {
|
if (data.type === 'add') {
|
||||||
labels = labels.filter((label) => label !== data.association.labelId);
|
await this.addLabel(labelId, instanceId, chatId);
|
||||||
} else if (data.type === 'add') {
|
} else if (data.type === 'remove') {
|
||||||
labels = [...labels, data.association.labelId];
|
await this.removeLabel(labelId, instanceId, chatId);
|
||||||
}
|
|
||||||
await this.prismaRepository.chat.update({
|
|
||||||
where: { id: chat.id },
|
|
||||||
data: {
|
|
||||||
labels,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1762,7 +1814,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async profilePicture(number: string) {
|
public async profilePicture(number: string) {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image');
|
const profilePictureUrl = await this.client.profilePictureUrl(jid, 'image');
|
||||||
@ -1780,12 +1832,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getStatus(number: string) {
|
public async getStatus(number: string) {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
wuid: jid,
|
wuid: jid,
|
||||||
status: (await this.client.fetchStatus(jid))?.status,
|
status: (await this.client.fetchStatus(jid))[0]?.status,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
@ -1796,7 +1848,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchProfile(instanceName: string, number?: string) {
|
public async fetchProfile(instanceName: string, number?: string) {
|
||||||
const jid = number ? this.createJid(number) : this.client?.user?.id;
|
const jid = number ? createJid(number) : this.client?.user?.id;
|
||||||
|
|
||||||
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||||
|
|
||||||
@ -1852,7 +1904,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async offerCall({ number, isVideo, callDuration }: OfferCallDto) {
|
public async offerCall({ number, isVideo, callDuration }: OfferCallDto) {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const call = await this.client.offerCall(jid, isVideo);
|
const call = await this.client.offerCall(jid, isVideo);
|
||||||
@ -2121,9 +2173,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (options?.mentionsEveryOne) {
|
if (options?.mentionsEveryOne) {
|
||||||
mentions = group.participants.map((participant) => participant.id);
|
mentions = group.participants.map((participant) => participant.id);
|
||||||
} else if (options.mentioned?.length) {
|
} else if (options?.mentioned?.length) {
|
||||||
mentions = options.mentioned.map((mention) => {
|
mentions = options.mentioned.map((mention) => {
|
||||||
const jid = this.createJid(mention);
|
const jid = createJid(mention);
|
||||||
if (isJidGroup(jid)) {
|
if (isJidGroup(jid)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -2205,7 +2257,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
|
|
||||||
const mimetype = mime.getType(fileName).toString();
|
const mimetype = mimeTypes.lookup(fileName).toString();
|
||||||
|
|
||||||
const fullName = join(
|
const fullName = join(
|
||||||
`${this.instance.id}`,
|
`${this.instance.id}`,
|
||||||
@ -2245,17 +2297,21 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
if (this.localWebhook.enabled) {
|
if (this.localWebhook.enabled) {
|
||||||
if (isMedia && this.localWebhook.webhookBase64) {
|
if (isMedia && this.localWebhook.webhookBase64) {
|
||||||
const buffer = await downloadMediaMessage(
|
try {
|
||||||
{ key: messageRaw.key, message: messageRaw?.message },
|
const buffer = await downloadMediaMessage(
|
||||||
'buffer',
|
{ key: messageRaw.key, message: messageRaw?.message },
|
||||||
{},
|
'buffer',
|
||||||
{
|
{},
|
||||||
logger: P({ level: 'error' }) as any,
|
{
|
||||||
reuploadRequest: this.client.updateMediaMessage,
|
logger: P({ level: 'error' }) as any,
|
||||||
},
|
reuploadRequest: this.client.updateMediaMessage,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
messageRaw.message.base64 = buffer ? buffer.toString('base64') : undefined;
|
messageRaw.message.base64 = buffer ? buffer.toString('base64') : undefined;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(['Error converting media to base64', error?.message]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2527,12 +2583,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
mediaMessage.fileName = 'video.mp4';
|
mediaMessage.fileName = 'video.mp4';
|
||||||
}
|
}
|
||||||
|
|
||||||
let mimetype: string;
|
let mimetype: string | false;
|
||||||
|
|
||||||
if (mediaMessage.mimetype) {
|
if (mediaMessage.mimetype) {
|
||||||
mimetype = mediaMessage.mimetype;
|
mimetype = mediaMessage.mimetype;
|
||||||
} else {
|
} else {
|
||||||
mimetype = mime.getType(mediaMessage.fileName);
|
mimetype = mimeTypes.lookup(mediaMessage.fileName);
|
||||||
|
|
||||||
if (!mimetype && isURL(mediaMessage.media)) {
|
if (!mimetype && isURL(mediaMessage.media)) {
|
||||||
let config: any = {
|
let config: any = {
|
||||||
@ -3193,7 +3249,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!contact.wuid) {
|
if (!contact.wuid) {
|
||||||
contact.wuid = this.createJid(contact.phoneNumber);
|
contact.wuid = createJid(contact.phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD';
|
result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD';
|
||||||
@ -3243,7 +3299,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
data.numbers.forEach((number) => {
|
data.numbers.forEach((number) => {
|
||||||
const jid = this.createJid(number);
|
const jid = createJid(number);
|
||||||
|
|
||||||
if (isJidGroup(jid)) {
|
if (isJidGroup(jid)) {
|
||||||
jids.groups.push({ number, jid });
|
jids.groups.push({ number, jid });
|
||||||
@ -3436,7 +3492,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
archive: data.archive,
|
archive: data.archive,
|
||||||
lastMessages: [last_message],
|
lastMessages: [last_message],
|
||||||
},
|
},
|
||||||
this.createJid(number),
|
createJid(number),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -3473,7 +3529,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
markRead: false,
|
markRead: false,
|
||||||
lastMessages: [last_message],
|
lastMessages: [last_message],
|
||||||
},
|
},
|
||||||
this.createJid(number),
|
createJid(number),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -3585,7 +3641,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
);
|
);
|
||||||
const typeMessage = getContentType(msg.message);
|
const typeMessage = getContentType(msg.message);
|
||||||
|
|
||||||
const ext = mime.getExtension(mediaMessage?.['mimetype']);
|
const ext = mimeTypes.extension(mediaMessage?.['mimetype']);
|
||||||
const fileName = mediaMessage?.['fileName'] || `${msg.key.id}.${ext}` || `${v4()}.${ext}`;
|
const fileName = mediaMessage?.['fileName'] || `${msg.key.id}.${ext}` || `${v4()}.${ext}`;
|
||||||
|
|
||||||
if (convertToMp4 && typeMessage === 'audioMessage') {
|
if (convertToMp4 && typeMessage === 'audioMessage') {
|
||||||
@ -3678,7 +3734,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
public async fetchBusinessProfile(number: string): Promise<NumberBusiness> {
|
public async fetchBusinessProfile(number: string): Promise<NumberBusiness> {
|
||||||
try {
|
try {
|
||||||
const jid = number ? this.createJid(number) : this.instance.wuid;
|
const jid = number ? createJid(number) : this.instance.wuid;
|
||||||
|
|
||||||
const profile = await this.client.getBusinessProfile(jid);
|
const profile = await this.client.getBusinessProfile(jid);
|
||||||
|
|
||||||
@ -3826,7 +3882,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateMessage(data: UpdateMessageDto) {
|
public async updateMessage(data: UpdateMessageDto) {
|
||||||
const jid = this.createJid(data.number);
|
const jid = createJid(data.number);
|
||||||
|
|
||||||
const options = await this.formatUpdateMessage(data);
|
const options = await this.formatUpdateMessage(data);
|
||||||
|
|
||||||
@ -3874,11 +3930,13 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
try {
|
try {
|
||||||
if (data.action === 'add') {
|
if (data.action === 'add') {
|
||||||
await this.client.addChatLabel(contact.jid, data.labelId);
|
await this.client.addChatLabel(contact.jid, data.labelId);
|
||||||
|
await this.addLabel(data.labelId, this.instanceId, contact.jid);
|
||||||
|
|
||||||
return { numberJid: contact.jid, labelId: data.labelId, add: true };
|
return { numberJid: contact.jid, labelId: data.labelId, add: true };
|
||||||
}
|
}
|
||||||
if (data.action === 'remove') {
|
if (data.action === 'remove') {
|
||||||
await this.client.removeChatLabel(contact.jid, data.labelId);
|
await this.client.removeChatLabel(contact.jid, data.labelId);
|
||||||
|
await this.removeLabel(data.labelId, this.instanceId, contact.jid);
|
||||||
|
|
||||||
return { numberJid: contact.jid, labelId: data.labelId, remove: true };
|
return { numberJid: contact.jid, labelId: data.labelId, remove: true };
|
||||||
}
|
}
|
||||||
@ -4114,7 +4172,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const inviteUrl = inviteCode.inviteUrl;
|
const inviteUrl = inviteCode.inviteUrl;
|
||||||
|
|
||||||
const numbers = id.numbers.map((number) => this.createJid(number));
|
const numbers = id.numbers.map((number) => createJid(number));
|
||||||
const description = id.description ?? '';
|
const description = id.description ?? '';
|
||||||
|
|
||||||
const msg = `${description}\n\n${inviteUrl}`;
|
const msg = `${description}\n\n${inviteUrl}`;
|
||||||
@ -4185,7 +4243,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
public async updateGParticipant(update: GroupUpdateParticipantDto) {
|
public async updateGParticipant(update: GroupUpdateParticipantDto) {
|
||||||
try {
|
try {
|
||||||
const participants = update.participants.map((p) => this.createJid(p));
|
const participants = update.participants.map((p) => createJid(p));
|
||||||
const updateParticipants = await this.client.groupParticipantsUpdate(
|
const updateParticipants = await this.client.groupParticipantsUpdate(
|
||||||
update.groupJid,
|
update.groupJid,
|
||||||
participants,
|
participants,
|
||||||
@ -4223,6 +4281,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
throw new BadRequestException('Unable to leave the group', error.toString());
|
throw new BadRequestException('Unable to leave the group', error.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async templateMessage() {
|
public async templateMessage() {
|
||||||
throw new Error('Method not available in the Baileys service');
|
throw new Error('Method not available in the Baileys service');
|
||||||
}
|
}
|
||||||
@ -4259,6 +4318,19 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
delete messageRaw.message.documentWithCaptionMessage;
|
delete messageRaw.message.documentWithCaptionMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const quotedMessage = messageRaw?.contextInfo?.quotedMessage;
|
||||||
|
if (quotedMessage) {
|
||||||
|
if (quotedMessage.extendedTextMessage) {
|
||||||
|
quotedMessage.conversation = quotedMessage.extendedTextMessage.text;
|
||||||
|
delete quotedMessage.extendedTextMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quotedMessage.documentWithCaptionMessage) {
|
||||||
|
quotedMessage.documentMessage = quotedMessage.documentWithCaptionMessage.message.documentMessage;
|
||||||
|
delete quotedMessage.documentWithCaptionMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return messageRaw;
|
return messageRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4326,4 +4398,133 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
return unreadMessages;
|
return unreadMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async addLabel(labelId: string, instanceId: string, chatId: string) {
|
||||||
|
const id = cuid();
|
||||||
|
|
||||||
|
await this.prismaRepository.$executeRawUnsafe(
|
||||||
|
`INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt")
|
||||||
|
VALUES ($4, $2, $3, to_jsonb(ARRAY[$1]::text[]), NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid")
|
||||||
|
DO
|
||||||
|
UPDATE
|
||||||
|
SET "labels" = (
|
||||||
|
SELECT to_jsonb(array_agg(DISTINCT elem))
|
||||||
|
FROM (
|
||||||
|
SELECT jsonb_array_elements_text("Chat"."labels") AS elem
|
||||||
|
UNION
|
||||||
|
SELECT $1::text AS elem
|
||||||
|
) sub
|
||||||
|
),
|
||||||
|
"updatedAt" = NOW();`,
|
||||||
|
labelId,
|
||||||
|
instanceId,
|
||||||
|
chatId,
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async removeLabel(labelId: string, instanceId: string, chatId: string) {
|
||||||
|
const id = cuid();
|
||||||
|
|
||||||
|
await this.prismaRepository.$executeRawUnsafe(
|
||||||
|
`INSERT INTO "Chat" ("id", "instanceId", "remoteJid", "labels", "createdAt", "updatedAt")
|
||||||
|
VALUES ($4, $2, $3, '[]'::jsonb, NOW(), NOW()) ON CONFLICT ("instanceId", "remoteJid")
|
||||||
|
DO
|
||||||
|
UPDATE
|
||||||
|
SET "labels" = COALESCE (
|
||||||
|
(
|
||||||
|
SELECT jsonb_agg(elem)
|
||||||
|
FROM jsonb_array_elements_text("Chat"."labels") AS elem
|
||||||
|
WHERE elem <> $1
|
||||||
|
),
|
||||||
|
'[]'::jsonb
|
||||||
|
),
|
||||||
|
"updatedAt" = NOW();`,
|
||||||
|
labelId,
|
||||||
|
instanceId,
|
||||||
|
chatId,
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysOnWhatsapp(jid: string) {
|
||||||
|
const response = await this.client.onWhatsApp(jid);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysProfilePictureUrl(jid: string, type: 'image' | 'preview', timeoutMs: number) {
|
||||||
|
const response = await this.client.profilePictureUrl(jid, type, timeoutMs);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysAssertSessions(jids: string[], force: boolean) {
|
||||||
|
const response = await this.client.assertSessions(jids, force);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysCreateParticipantNodes(jids: string[], message: proto.IMessage, extraAttrs: any) {
|
||||||
|
const response = await this.client.createParticipantNodes(jids, message, extraAttrs);
|
||||||
|
|
||||||
|
const convertedResponse = {
|
||||||
|
...response,
|
||||||
|
nodes: response.nodes.map((node: any) => ({
|
||||||
|
...node,
|
||||||
|
content: node.content?.map((c: any) => ({
|
||||||
|
...c,
|
||||||
|
content: c.content instanceof Uint8Array ? Buffer.from(c.content).toString('base64') : c.content,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
return convertedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysSendNode(stanza: any) {
|
||||||
|
console.log('stanza', JSON.stringify(stanza));
|
||||||
|
const response = await this.client.sendNode(stanza);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysGetUSyncDevices(jids: string[], useCache: boolean, ignoreZeroDevices: boolean) {
|
||||||
|
const response = await this.client.getUSyncDevices(jids, useCache, ignoreZeroDevices);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysGenerateMessageTag() {
|
||||||
|
const response = await this.client.generateMessageTag();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysSignalRepositoryDecryptMessage(jid: string, type: 'pkmsg' | 'msg', ciphertext: string) {
|
||||||
|
try {
|
||||||
|
const ciphertextBuffer = Buffer.from(ciphertext, 'base64');
|
||||||
|
|
||||||
|
const response = await this.client.signalRepository.decryptMessage({
|
||||||
|
jid,
|
||||||
|
type,
|
||||||
|
ciphertext: ciphertextBuffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response instanceof Uint8Array ? Buffer.from(response).toString('base64') : response;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error decrypting message:');
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async baileysGetAuthState() {
|
||||||
|
const response = {
|
||||||
|
me: this.client.authState.creds.me,
|
||||||
|
account: this.client.authState.creds.account,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,6 @@ export class ChatbotController {
|
|||||||
|
|
||||||
public async findBotTrigger(
|
public async findBotTrigger(
|
||||||
botRepository: any,
|
botRepository: any,
|
||||||
settingsRepository: any,
|
|
||||||
content: string,
|
content: string,
|
||||||
instance: InstanceDto,
|
instance: InstanceDto,
|
||||||
session?: IntegrationSession,
|
session?: IntegrationSession,
|
||||||
@ -192,7 +191,7 @@ export class ChatbotController {
|
|||||||
let findBot: null;
|
let findBot: null;
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
findBot = await findBotByTrigger(botRepository, settingsRepository, content, instance.instanceId);
|
findBot = await findBotByTrigger(botRepository, content, instance.instanceId);
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
return;
|
return;
|
||||||
|
@ -28,7 +28,7 @@ import dayjs from 'dayjs';
|
|||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import Jimp from 'jimp';
|
import Jimp from 'jimp';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import mime from 'mime';
|
import mimeTypes from 'mime-types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
@ -704,7 +704,7 @@ export class ChatwootService {
|
|||||||
conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id);
|
conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id);
|
||||||
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(conversation)}`);
|
this.logger.verbose(`Found conversation in reopenConversation mode: ${JSON.stringify(conversation)}`);
|
||||||
|
|
||||||
if (this.provider.conversationPending) {
|
if (this.provider.conversationPending && conversation.status !== 'open') {
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
await client.conversations.toggleStatus({
|
await client.conversations.toggleStatus({
|
||||||
accountId: this.provider.accountId,
|
accountId: this.provider.accountId,
|
||||||
@ -1066,7 +1066,7 @@ export class ChatwootService {
|
|||||||
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string, options?: Options) {
|
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string, options?: Options) {
|
||||||
try {
|
try {
|
||||||
const parsedMedia = path.parse(decodeURIComponent(media));
|
const parsedMedia = path.parse(decodeURIComponent(media));
|
||||||
let mimeType = mime.getType(parsedMedia?.ext) || '';
|
let mimeType = mimeTypes.lookup(parsedMedia?.ext) || '';
|
||||||
let fileName = parsedMedia?.name + parsedMedia?.ext;
|
let fileName = parsedMedia?.name + parsedMedia?.ext;
|
||||||
|
|
||||||
if (!mimeType) {
|
if (!mimeType) {
|
||||||
@ -1958,7 +1958,7 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!nameFile) {
|
if (!nameFile) {
|
||||||
nameFile = `${Math.random().toString(36).substring(7)}.${mime.getExtension(downloadBase64.mimetype) || ''}`;
|
nameFile = `${Math.random().toString(36).substring(7)}.${mimeTypes.extension(downloadBase64.mimetype) || ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
||||||
@ -1970,11 +1970,21 @@ export class ChatwootService {
|
|||||||
|
|
||||||
if (body.key.remoteJid.includes('@g.us')) {
|
if (body.key.remoteJid.includes('@g.us')) {
|
||||||
const participantName = body.pushName;
|
const participantName = body.pushName;
|
||||||
|
const rawPhoneNumber = body.key.participant.split('@')[0];
|
||||||
|
const phoneMatch = rawPhoneNumber.match(/^(\d{2})(\d{2})(\d{4})(\d{4})$/);
|
||||||
|
|
||||||
|
let formattedPhoneNumber: string;
|
||||||
|
|
||||||
|
if (phoneMatch) {
|
||||||
|
formattedPhoneNumber = `+${phoneMatch[1]} (${phoneMatch[2]}) ${phoneMatch[3]}-${phoneMatch[4]}`;
|
||||||
|
} else {
|
||||||
|
formattedPhoneNumber = `+${rawPhoneNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
let content: string;
|
let content: string;
|
||||||
|
|
||||||
if (!body.key.fromMe) {
|
if (!body.key.fromMe) {
|
||||||
content = `**${participantName}:**\n\n${bodyMessage}`;
|
content = `**${formattedPhoneNumber} - ${participantName}:**\n\n${bodyMessage}`;
|
||||||
} else {
|
} else {
|
||||||
content = `${bodyMessage}`;
|
content = `${bodyMessage}`;
|
||||||
}
|
}
|
||||||
@ -2047,8 +2057,8 @@ export class ChatwootService {
|
|||||||
if (isAdsMessage) {
|
if (isAdsMessage) {
|
||||||
const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
|
const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
|
||||||
|
|
||||||
const extension = mime.getExtension(imgBuffer.headers['content-type']);
|
const extension = mimeTypes.extension(imgBuffer.headers['content-type']);
|
||||||
const mimeType = extension && mime.getType(extension);
|
const mimeType = extension && mimeTypes.lookup(extension);
|
||||||
|
|
||||||
if (!mimeType) {
|
if (!mimeType) {
|
||||||
this.logger.warn('mimetype of Ads message not found');
|
this.logger.warn('mimetype of Ads message not found');
|
||||||
@ -2056,7 +2066,7 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const random = Math.random().toString(36).substring(7);
|
const random = Math.random().toString(36).substring(7);
|
||||||
const nameFile = `${random}.${mime.getExtension(mimeType)}`;
|
const nameFile = `${random}.${mimeTypes.extension(mimeType)}`;
|
||||||
const fileData = Buffer.from(imgBuffer.data, 'binary');
|
const fileData = Buffer.from(imgBuffer.data, 'binary');
|
||||||
|
|
||||||
const img = await Jimp.read(fileData);
|
const img = await Jimp.read(fileData);
|
||||||
@ -2099,11 +2109,21 @@ export class ChatwootService {
|
|||||||
|
|
||||||
if (body.key.remoteJid.includes('@g.us')) {
|
if (body.key.remoteJid.includes('@g.us')) {
|
||||||
const participantName = body.pushName;
|
const participantName = body.pushName;
|
||||||
|
const rawPhoneNumber = body.key.participant.split('@')[0];
|
||||||
|
const phoneMatch = rawPhoneNumber.match(/^(\d{2})(\d{2})(\d{4})(\d{4})$/);
|
||||||
|
|
||||||
|
let formattedPhoneNumber: string;
|
||||||
|
|
||||||
|
if (phoneMatch) {
|
||||||
|
formattedPhoneNumber = `+${phoneMatch[1]} (${phoneMatch[2]}) ${phoneMatch[3]}-${phoneMatch[4]}`;
|
||||||
|
} else {
|
||||||
|
formattedPhoneNumber = `+${rawPhoneNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
let content: string;
|
let content: string;
|
||||||
|
|
||||||
if (!body.key.fromMe) {
|
if (!body.key.fromMe) {
|
||||||
content = `**${participantName}**\n\n${bodyMessage}`;
|
content = `**${formattedPhoneNumber} - ${participantName}:**\n\n${bodyMessage}`;
|
||||||
} else {
|
} else {
|
||||||
content = `${bodyMessage}`;
|
content = `${bodyMessage}`;
|
||||||
}
|
}
|
||||||
|
@ -756,13 +756,7 @@ export class DifyController extends ChatbotController implements ChatbotControll
|
|||||||
|
|
||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(
|
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as DifyModel;
|
||||||
this.botRepository,
|
|
||||||
this.settingsRepository,
|
|
||||||
content,
|
|
||||||
instance,
|
|
||||||
session,
|
|
||||||
)) as DifyModel;
|
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
const fallback = await this.settingsRepository.findFirst({
|
||||||
|
@ -428,8 +428,8 @@ export class DifyService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
|
@ -728,13 +728,7 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
|||||||
|
|
||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(
|
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as EvolutionBot;
|
||||||
this.botRepository,
|
|
||||||
this.settingsRepository,
|
|
||||||
content,
|
|
||||||
instance,
|
|
||||||
session,
|
|
||||||
)) as EvolutionBot;
|
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
const fallback = await this.settingsRepository.findFirst({
|
||||||
|
@ -190,8 +190,8 @@ export class EvolutionBotService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
@ -274,8 +274,8 @@ export class EvolutionBotService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
sendTelemetry('/message/sendText');
|
||||||
|
@ -728,13 +728,7 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
|||||||
|
|
||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(
|
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as Flowise;
|
||||||
this.botRepository,
|
|
||||||
this.settingsRepository,
|
|
||||||
content,
|
|
||||||
instance,
|
|
||||||
session,
|
|
||||||
)) as Flowise;
|
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
const fallback = await this.settingsRepository.findFirst({
|
||||||
|
@ -189,8 +189,8 @@ export class FlowiseService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
@ -273,8 +273,8 @@ export class FlowiseService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
sendTelemetry('/message/sendText');
|
||||||
|
@ -965,13 +965,7 @@ export class OpenaiController extends ChatbotController implements ChatbotContro
|
|||||||
|
|
||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(
|
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as OpenaiBot;
|
||||||
this.botRepository,
|
|
||||||
this.settingsRepository,
|
|
||||||
content,
|
|
||||||
instance,
|
|
||||||
session,
|
|
||||||
)) as OpenaiBot;
|
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
const fallback = await this.settingsRepository.findFirst({
|
||||||
|
@ -234,8 +234,8 @@ export class OpenaiService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
@ -318,8 +318,8 @@ export class OpenaiService {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
textBuffer = '';
|
|
||||||
}
|
}
|
||||||
|
textBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTelemetry('/message/sendText');
|
sendTelemetry('/message/sendText');
|
||||||
|
@ -943,13 +943,7 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
|
|
||||||
const content = getConversationMessage(msg);
|
const content = getConversationMessage(msg);
|
||||||
|
|
||||||
let findBot = (await this.findBotTrigger(
|
let findBot = (await this.findBotTrigger(this.botRepository, content, instance, session)) as TypebotModel;
|
||||||
this.botRepository,
|
|
||||||
this.settingsRepository,
|
|
||||||
content,
|
|
||||||
instance,
|
|
||||||
session,
|
|
||||||
)) as TypebotModel;
|
|
||||||
|
|
||||||
if (!findBot) {
|
if (!findBot) {
|
||||||
const fallback = await this.settingsRepository.findFirst({
|
const fallback = await this.settingsRepository.findFirst({
|
||||||
|
@ -13,6 +13,7 @@ export type EmitData = {
|
|||||||
sender: string;
|
sender: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
|
integration?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EventControllerInterface {
|
export interface EventControllerInterface {
|
||||||
@ -23,7 +24,7 @@ export interface EventControllerInterface {
|
|||||||
|
|
||||||
export class EventController {
|
export class EventController {
|
||||||
public prismaRepository: PrismaRepository;
|
public prismaRepository: PrismaRepository;
|
||||||
private waMonitor: WAMonitoringService;
|
protected waMonitor: WAMonitoringService;
|
||||||
private integrationStatus: boolean;
|
private integrationStatus: boolean;
|
||||||
private integrationName: string;
|
private integrationName: string;
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ export class EventManager {
|
|||||||
sender: string;
|
sender: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
|
integration?: string[];
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await this.websocket.emit(eventData);
|
await this.websocket.emit(eventData);
|
||||||
await this.rabbitmq.emit(eventData);
|
await this.rabbitmq.emit(eventData);
|
||||||
|
@ -120,7 +120,11 @@ export class PusherController extends EventController implements EventController
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
local,
|
local,
|
||||||
|
integration,
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
|
if (integration && !integration.includes('pusher')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.status) {
|
if (!this.status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,12 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
dateTime,
|
dateTime,
|
||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
|
integration,
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
|
if (integration && !integration.includes('rabbitmq')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.status) {
|
if (!this.status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,12 @@ export class SqsController extends EventController implements EventControllerInt
|
|||||||
dateTime,
|
dateTime,
|
||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
|
integration,
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
|
if (integration && !integration.includes('sqs')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.status) {
|
if (!this.status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { wa } from '@api/types/wa.types';
|
|||||||
import { configService, Log, Webhook } from '@config/env.config';
|
import { configService, Log, Webhook } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import axios from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { isURL } from 'class-validator';
|
import { isURL } from 'class-validator';
|
||||||
|
|
||||||
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
import { EmitData, EventController, EventControllerInterface } from '../event.controller';
|
||||||
@ -64,13 +64,14 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
local,
|
local,
|
||||||
|
integration,
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
const instance = (await this.get(instanceName)) as wa.LocalWebHook;
|
if (integration && !integration.includes('webhook')) {
|
||||||
|
|
||||||
if (!instance || !instance?.enabled) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instance = (await this.get(instanceName)) as wa.LocalWebHook;
|
||||||
|
|
||||||
const webhookConfig = configService.get<Webhook>('WEBHOOK');
|
const webhookConfig = configService.get<Webhook>('WEBHOOK');
|
||||||
const webhookLocal = instance?.events;
|
const webhookLocal = instance?.events;
|
||||||
const webhookHeaders = instance?.headers;
|
const webhookHeaders = instance?.headers;
|
||||||
@ -82,14 +83,14 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
destination: instance?.url,
|
destination: instance?.url || `${webhookConfig.GLOBAL.URL}/${transformedWe}`,
|
||||||
date_time: dateTime,
|
date_time: dateTime,
|
||||||
sender,
|
sender,
|
||||||
server_url: serverUrl,
|
server_url: serverUrl,
|
||||||
apikey: apiKey,
|
apikey: apiKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (local) {
|
if (local && instance?.enabled) {
|
||||||
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
|
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
|
||||||
let baseURL: string;
|
let baseURL: string;
|
||||||
|
|
||||||
@ -116,12 +117,12 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
headers: webhookHeaders as Record<string, string> | undefined,
|
headers: webhookHeaders as Record<string, string> | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await httpService.post('', webhookData);
|
await this.retryWebhookRequest(httpService, webhookData, `${origin}.sendData-Webhook`, baseURL, serverUrl);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
local: `${origin}.sendData-Webhook`,
|
local: `${origin}.sendData-Webhook`,
|
||||||
message: error?.message,
|
message: `Todas as tentativas falharam: ${error?.message}`,
|
||||||
hostName: error?.hostname,
|
hostName: error?.hostname,
|
||||||
syscall: error?.syscall,
|
syscall: error?.syscall,
|
||||||
code: error?.code,
|
code: error?.code,
|
||||||
@ -157,12 +158,18 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
if (isURL(globalURL)) {
|
if (isURL(globalURL)) {
|
||||||
const httpService = axios.create({ baseURL: globalURL });
|
const httpService = axios.create({ baseURL: globalURL });
|
||||||
|
|
||||||
await httpService.post('', webhookData);
|
await this.retryWebhookRequest(
|
||||||
|
httpService,
|
||||||
|
webhookData,
|
||||||
|
`${origin}.sendData-Webhook-Global`,
|
||||||
|
globalURL,
|
||||||
|
serverUrl,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
local: `${origin}.sendData-Webhook-Global`,
|
local: `${origin}.sendData-Webhook-Global`,
|
||||||
message: error?.message,
|
message: `Todas as tentativas falharam: ${error?.message}`,
|
||||||
hostName: error?.hostname,
|
hostName: error?.hostname,
|
||||||
syscall: error?.syscall,
|
syscall: error?.syscall,
|
||||||
code: error?.code,
|
code: error?.code,
|
||||||
@ -176,4 +183,51 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async retryWebhookRequest(
|
||||||
|
httpService: AxiosInstance,
|
||||||
|
webhookData: any,
|
||||||
|
origin: string,
|
||||||
|
baseURL: string,
|
||||||
|
serverUrl: string,
|
||||||
|
maxRetries = 10,
|
||||||
|
delaySeconds = 30,
|
||||||
|
): Promise<void> {
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
while (attempts < maxRetries) {
|
||||||
|
try {
|
||||||
|
await httpService.post('', webhookData);
|
||||||
|
if (attempts > 0) {
|
||||||
|
this.logger.log({
|
||||||
|
local: `${origin}`,
|
||||||
|
message: `Sucesso no envio após ${attempts + 1} tentativas`,
|
||||||
|
url: baseURL,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
local: `${origin}`,
|
||||||
|
message: `Tentativa ${attempts}/${maxRetries} falhou: ${error?.message}`,
|
||||||
|
hostName: error?.hostname,
|
||||||
|
syscall: error?.syscall,
|
||||||
|
code: error?.code,
|
||||||
|
error: error?.errno,
|
||||||
|
stack: error?.stack,
|
||||||
|
name: error?.name,
|
||||||
|
url: baseURL,
|
||||||
|
server_url: serverUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (attempts === maxRetries) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,10 @@ import { instanceSchema, webhookSchema } from '@validate/validate.schema';
|
|||||||
import { RequestHandler, Router } from 'express';
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
export class WebhookRouter extends RouterBroker {
|
export class WebhookRouter extends RouterBroker {
|
||||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
constructor(
|
||||||
|
readonly configService: ConfigService,
|
||||||
|
...guards: RequestHandler[]
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.router
|
this.router
|
||||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||||
|
@ -35,6 +35,16 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
this.logger.info('User disconnected');
|
this.logger.info('User disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('sendNode', async (data) => {
|
||||||
|
try {
|
||||||
|
await this.waMonitor.waInstances[data.instanceId].baileysSendNode(data.stanza);
|
||||||
|
this.logger.info('Node sent successfully');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error sending node:');
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info('Socket.io initialized');
|
this.logger.info('Socket.io initialized');
|
||||||
@ -65,7 +75,12 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
dateTime,
|
dateTime,
|
||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
|
integration,
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
|
if (integration && !integration.includes('websocket')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.status) {
|
if (!this.status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { StorageRouter } from '@api/integrations/storage/storage.router';
|
|||||||
import { configService } from '@config/env.config';
|
import { configService } from '@config/env.config';
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import mime from 'mime';
|
import mimeTypes from 'mime-types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { CallRouter } from './call.router';
|
import { CallRouter } from './call.router';
|
||||||
@ -49,7 +49,7 @@ router.get('/assets/*', (req, res) => {
|
|||||||
const filePath = path.join(basePath, 'assets/', fileName);
|
const filePath = path.join(basePath, 'assets/', fileName);
|
||||||
|
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
res.set('Content-Type', mime.getType(filePath) || 'text/css');
|
res.set('Content-Type', mimeTypes.lookup(filePath) || 'text/css');
|
||||||
res.send(fs.readFileSync(filePath));
|
res.send(fs.readFileSync(filePath));
|
||||||
} else {
|
} else {
|
||||||
res.status(404).send('File not found');
|
res.status(404).send('File not found');
|
||||||
@ -87,7 +87,7 @@ router
|
|||||||
.use('/settings', new SettingsRouter(...guards).router)
|
.use('/settings', new SettingsRouter(...guards).router)
|
||||||
.use('/proxy', new ProxyRouter(...guards).router)
|
.use('/proxy', new ProxyRouter(...guards).router)
|
||||||
.use('/label', new LabelRouter(...guards).router)
|
.use('/label', new LabelRouter(...guards).router)
|
||||||
.use('', new ChannelRouter(configService).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);
|
||||||
|
@ -8,10 +8,14 @@ import { RequestHandler, Router } from 'express';
|
|||||||
import { HttpStatus } from './index.router';
|
import { HttpStatus } from './index.router';
|
||||||
|
|
||||||
export class InstanceRouter extends RouterBroker {
|
export class InstanceRouter extends RouterBroker {
|
||||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
constructor(
|
||||||
|
readonly configService: ConfigService,
|
||||||
|
...guards: RequestHandler[]
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.router
|
this.router
|
||||||
.post('/create', ...guards, async (req, res) => {
|
.post('/create', ...guards, async (req, res) => {
|
||||||
|
console.log('create instance', req.body);
|
||||||
const response = await this.dataValidate<InstanceDto>({
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
request: req,
|
request: req,
|
||||||
schema: instanceSchema,
|
schema: instanceSchema,
|
||||||
|
@ -9,7 +9,10 @@ import { RequestHandler, Router } from 'express';
|
|||||||
import { HttpStatus } from './index.router';
|
import { HttpStatus } from './index.router';
|
||||||
|
|
||||||
export class TemplateRouter extends RouterBroker {
|
export class TemplateRouter extends RouterBroker {
|
||||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
constructor(
|
||||||
|
readonly configService: ConfigService,
|
||||||
|
...guards: RequestHandler[]
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.router
|
this.router
|
||||||
.post(this.routerPath('create'), ...guards, async (req, res) => {
|
.post(this.routerPath('create'), ...guards, async (req, res) => {
|
||||||
|
@ -15,6 +15,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 { 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';
|
||||||
import { ChatwootService } from './integrations/chatbot/chatwoot/services/chatwoot.service';
|
import { ChatwootService } from './integrations/chatbot/chatwoot/services/chatwoot.service';
|
||||||
@ -107,7 +108,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 baileysController = new BaileysController(waMonitor);
|
||||||
// chatbots
|
// chatbots
|
||||||
const typebotService = new TypebotService(waMonitor, configService, prismaRepository);
|
const typebotService = new TypebotService(waMonitor, configService, prismaRepository);
|
||||||
export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor);
|
export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor);
|
||||||
|
@ -12,7 +12,8 @@ import { Events, wa } from '@api/types/wa.types';
|
|||||||
import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
import { Auth, Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { NotFoundException } from '@exceptions';
|
import { NotFoundException } from '@exceptions';
|
||||||
import { Contact, Message } from '@prisma/client';
|
import { Contact, Message, Prisma } from '@prisma/client';
|
||||||
|
import { createJid } from '@utils/createJid';
|
||||||
import { WASocket } from 'baileys';
|
import { WASocket } from 'baileys';
|
||||||
import { isArray } from 'class-validator';
|
import { isArray } from 'class-validator';
|
||||||
import EventEmitter2 from 'eventemitter2';
|
import EventEmitter2 from 'eventemitter2';
|
||||||
@ -151,6 +152,7 @@ export class ChannelStartupService {
|
|||||||
this.localSettings.readMessages = data?.readMessages;
|
this.localSettings.readMessages = data?.readMessages;
|
||||||
this.localSettings.readStatus = data?.readStatus;
|
this.localSettings.readStatus = data?.readStatus;
|
||||||
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
||||||
|
this.localSettings.wavoipToken = data?.wavoipToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setSettings(data: SettingsDto) {
|
public async setSettings(data: SettingsDto) {
|
||||||
@ -166,6 +168,7 @@ export class ChannelStartupService {
|
|||||||
readMessages: data.readMessages,
|
readMessages: data.readMessages,
|
||||||
readStatus: data.readStatus,
|
readStatus: data.readStatus,
|
||||||
syncFullHistory: data.syncFullHistory,
|
syncFullHistory: data.syncFullHistory,
|
||||||
|
wavoipToken: data.wavoipToken,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
rejectCall: data.rejectCall,
|
rejectCall: data.rejectCall,
|
||||||
@ -175,6 +178,7 @@ export class ChannelStartupService {
|
|||||||
readMessages: data.readMessages,
|
readMessages: data.readMessages,
|
||||||
readStatus: data.readStatus,
|
readStatus: data.readStatus,
|
||||||
syncFullHistory: data.syncFullHistory,
|
syncFullHistory: data.syncFullHistory,
|
||||||
|
wavoipToken: data.wavoipToken,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -186,6 +190,12 @@ export class ChannelStartupService {
|
|||||||
this.localSettings.readMessages = data?.readMessages;
|
this.localSettings.readMessages = data?.readMessages;
|
||||||
this.localSettings.readStatus = data?.readStatus;
|
this.localSettings.readStatus = data?.readStatus;
|
||||||
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
this.localSettings.syncFullHistory = data?.syncFullHistory;
|
||||||
|
this.localSettings.wavoipToken = data?.wavoipToken;
|
||||||
|
|
||||||
|
if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) {
|
||||||
|
this.client.ws.close();
|
||||||
|
this.client.ws.connect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findSettings() {
|
public async findSettings() {
|
||||||
@ -207,6 +217,7 @@ export class ChannelStartupService {
|
|||||||
readMessages: data.readMessages,
|
readMessages: data.readMessages,
|
||||||
readStatus: data.readStatus,
|
readStatus: data.readStatus,
|
||||||
syncFullHistory: data.syncFullHistory,
|
syncFullHistory: data.syncFullHistory,
|
||||||
|
wavoipToken: data.wavoipToken,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +430,7 @@ export class ChannelStartupService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
|
public async sendDataWebhook<T = any>(event: Events, data: T, local = true, integration?: string[]) {
|
||||||
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
||||||
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
||||||
@ -439,6 +450,7 @@ export class ChannelStartupService {
|
|||||||
sender: this.wuid,
|
sender: this.wuid,
|
||||||
apiKey: expose && instanceApikey ? instanceApikey : null,
|
apiKey: expose && instanceApikey ? instanceApikey : null,
|
||||||
local,
|
local,
|
||||||
|
integration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,47 +488,11 @@ export class ChannelStartupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public createJid(number: string): string {
|
|
||||||
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (number.includes('@broadcast')) {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
number = number
|
|
||||||
?.replace(/\s/g, '')
|
|
||||||
.replace(/\+/g, '')
|
|
||||||
.replace(/\(/g, '')
|
|
||||||
.replace(/\)/g, '')
|
|
||||||
.split(':')[0]
|
|
||||||
.split('@')[0];
|
|
||||||
|
|
||||||
if (number.includes('-') && number.length >= 24) {
|
|
||||||
number = number.replace(/[^\d-]/g, '');
|
|
||||||
return `${number}@g.us`;
|
|
||||||
}
|
|
||||||
|
|
||||||
number = number.replace(/\D/g, '');
|
|
||||||
|
|
||||||
if (number.length >= 18) {
|
|
||||||
number = number.replace(/[^\d-]/g, '');
|
|
||||||
return `${number}@g.us`;
|
|
||||||
}
|
|
||||||
|
|
||||||
number = this.formatMXOrARNumber(number);
|
|
||||||
|
|
||||||
number = this.formatBRNumber(number);
|
|
||||||
|
|
||||||
return `${number}@s.whatsapp.net`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async fetchContacts(query: Query<Contact>) {
|
public async fetchContacts(query: Query<Contact>) {
|
||||||
const remoteJid = query?.where?.remoteJid
|
const remoteJid = query?.where?.remoteJid
|
||||||
? query?.where?.remoteJid.includes('@')
|
? query?.where?.remoteJid.includes('@')
|
||||||
? query.where?.remoteJid
|
? query.where?.remoteJid
|
||||||
: this.createJid(query.where?.remoteJid)
|
: createJid(query.where?.remoteJid)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
@ -532,6 +508,64 @@ export class ChannelStartupService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public cleanMessageData(message: any) {
|
||||||
|
if (!message) return message;
|
||||||
|
|
||||||
|
const cleanedMessage = { ...message };
|
||||||
|
|
||||||
|
const mediaUrl = cleanedMessage.message.mediaUrl;
|
||||||
|
|
||||||
|
delete cleanedMessage.message.base64;
|
||||||
|
|
||||||
|
if (cleanedMessage.message) {
|
||||||
|
// Limpa imageMessage
|
||||||
|
if (cleanedMessage.message.imageMessage) {
|
||||||
|
cleanedMessage.message.imageMessage = {
|
||||||
|
caption: cleanedMessage.message.imageMessage.caption,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa videoMessage
|
||||||
|
if (cleanedMessage.message.videoMessage) {
|
||||||
|
cleanedMessage.message.videoMessage = {
|
||||||
|
caption: cleanedMessage.message.videoMessage.caption,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa audioMessage
|
||||||
|
if (cleanedMessage.message.audioMessage) {
|
||||||
|
cleanedMessage.message.audioMessage = {
|
||||||
|
seconds: cleanedMessage.message.audioMessage.seconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa stickerMessage
|
||||||
|
if (cleanedMessage.message.stickerMessage) {
|
||||||
|
cleanedMessage.message.stickerMessage = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa documentMessage
|
||||||
|
if (cleanedMessage.message.documentMessage) {
|
||||||
|
cleanedMessage.message.documentMessage = {
|
||||||
|
caption: cleanedMessage.message.documentMessage.caption,
|
||||||
|
name: cleanedMessage.message.documentMessage.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa documentWithCaptionMessage
|
||||||
|
if (cleanedMessage.message.documentWithCaptionMessage) {
|
||||||
|
cleanedMessage.message.documentWithCaptionMessage = {
|
||||||
|
caption: cleanedMessage.message.documentWithCaptionMessage.caption,
|
||||||
|
name: cleanedMessage.message.documentWithCaptionMessage.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaUrl) cleanedMessage.message.mediaUrl = mediaUrl;
|
||||||
|
|
||||||
|
return cleanedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
public async fetchMessages(query: Query<Message>) {
|
public async fetchMessages(query: Query<Message>) {
|
||||||
const keyFilters = query?.where?.key as {
|
const keyFilters = query?.where?.key as {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -540,12 +574,23 @@ export class ChannelStartupService {
|
|||||||
participants?: string;
|
participants?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const timestampFilter = {};
|
||||||
|
if (query?.where?.messageTimestamp) {
|
||||||
|
if (query.where.messageTimestamp['gte'] && query.where.messageTimestamp['lte']) {
|
||||||
|
timestampFilter['messageTimestamp'] = {
|
||||||
|
gte: Math.floor(new Date(query.where.messageTimestamp['gte']).getTime() / 1000),
|
||||||
|
lte: Math.floor(new Date(query.where.messageTimestamp['lte']).getTime() / 1000),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const count = await this.prismaRepository.message.count({
|
const count = await this.prismaRepository.message.count({
|
||||||
where: {
|
where: {
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
id: query?.where?.id,
|
id: query?.where?.id,
|
||||||
source: query?.where?.source,
|
source: query?.where?.source,
|
||||||
messageType: query?.where?.messageType,
|
messageType: query?.where?.messageType,
|
||||||
|
...timestampFilter,
|
||||||
AND: [
|
AND: [
|
||||||
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
||||||
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
||||||
@ -569,6 +614,7 @@ export class ChannelStartupService {
|
|||||||
id: query?.where?.id,
|
id: query?.where?.id,
|
||||||
source: query?.where?.source,
|
source: query?.where?.source,
|
||||||
messageType: query?.where?.messageType,
|
messageType: query?.where?.messageType,
|
||||||
|
...timestampFilter,
|
||||||
AND: [
|
AND: [
|
||||||
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
||||||
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
||||||
@ -625,113 +671,103 @@ export class ChannelStartupService {
|
|||||||
const remoteJid = query?.where?.remoteJid
|
const remoteJid = query?.where?.remoteJid
|
||||||
? query?.where?.remoteJid.includes('@')
|
? query?.where?.remoteJid.includes('@')
|
||||||
? query.where?.remoteJid
|
? query.where?.remoteJid
|
||||||
: this.createJid(query.where?.remoteJid)
|
: createJid(query.where?.remoteJid)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
let results = [];
|
const where = {
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
};
|
||||||
|
|
||||||
if (!remoteJid) {
|
if (remoteJid) {
|
||||||
results = await this.prismaRepository.$queryRaw`
|
where['remoteJid'] = remoteJid;
|
||||||
SELECT
|
|
||||||
"Chat"."id",
|
|
||||||
"Chat"."remoteJid",
|
|
||||||
"Chat"."name",
|
|
||||||
"Chat"."labels",
|
|
||||||
"Chat"."createdAt",
|
|
||||||
"Chat"."updatedAt",
|
|
||||||
"Contact"."pushName",
|
|
||||||
"Contact"."profilePicUrl",
|
|
||||||
"Chat"."unreadMessages",
|
|
||||||
(ARRAY_AGG("Message"."id" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_id,
|
|
||||||
(ARRAY_AGG("Message"."key" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_key,
|
|
||||||
(ARRAY_AGG("Message"."pushName" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_push_name,
|
|
||||||
(ARRAY_AGG("Message"."participant" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_participant,
|
|
||||||
(ARRAY_AGG("Message"."messageType" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message_type,
|
|
||||||
(ARRAY_AGG("Message"."message" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message,
|
|
||||||
(ARRAY_AGG("Message"."contextInfo" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_context_info,
|
|
||||||
(ARRAY_AGG("Message"."source" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_source,
|
|
||||||
(ARRAY_AGG("Message"."messageTimestamp" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message_timestamp,
|
|
||||||
(ARRAY_AGG("Message"."instanceId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_instance_id,
|
|
||||||
(ARRAY_AGG("Message"."sessionId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_session_id,
|
|
||||||
(ARRAY_AGG("Message"."status" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_status
|
|
||||||
FROM "Chat"
|
|
||||||
LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid"
|
|
||||||
LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid"
|
|
||||||
WHERE
|
|
||||||
"Chat"."instanceId" = ${this.instanceId}
|
|
||||||
GROUP BY
|
|
||||||
"Chat"."id",
|
|
||||||
"Chat"."remoteJid",
|
|
||||||
"Contact"."id"
|
|
||||||
ORDER BY last_message_message_timestamp DESC NULLS LAST, "Chat"."updatedAt" DESC;
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
results = await this.prismaRepository.$queryRaw`
|
|
||||||
SELECT
|
|
||||||
"Chat"."id",
|
|
||||||
"Chat"."remoteJid",
|
|
||||||
"Chat"."name",
|
|
||||||
"Chat"."labels",
|
|
||||||
"Chat"."createdAt",
|
|
||||||
"Chat"."updatedAt",
|
|
||||||
"Contact"."pushName",
|
|
||||||
"Contact"."profilePicUrl",
|
|
||||||
"Chat"."unreadMessages",
|
|
||||||
(ARRAY_AGG("Message"."id" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_id,
|
|
||||||
(ARRAY_AGG("Message"."key" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_key,
|
|
||||||
(ARRAY_AGG("Message"."pushName" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_push_name,
|
|
||||||
(ARRAY_AGG("Message"."participant" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_participant,
|
|
||||||
(ARRAY_AGG("Message"."messageType" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message_type,
|
|
||||||
(ARRAY_AGG("Message"."message" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message,
|
|
||||||
(ARRAY_AGG("Message"."contextInfo" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_context_info,
|
|
||||||
(ARRAY_AGG("Message"."source" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_source,
|
|
||||||
(ARRAY_AGG("Message"."messageTimestamp" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_message_timestamp,
|
|
||||||
(ARRAY_AGG("Message"."instanceId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_instance_id,
|
|
||||||
(ARRAY_AGG("Message"."sessionId" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_session_id,
|
|
||||||
(ARRAY_AGG("Message"."status" ORDER BY "Message"."messageTimestamp" DESC))[1] AS last_message_status
|
|
||||||
FROM "Chat"
|
|
||||||
LEFT JOIN "Message" ON "Message"."messageType" != 'reactionMessage' and "Message"."key"->>'remoteJid' = "Chat"."remoteJid"
|
|
||||||
LEFT JOIN "Contact" ON "Chat"."remoteJid" = "Contact"."remoteJid"
|
|
||||||
WHERE
|
|
||||||
"Chat"."instanceId" = ${this.instanceId} AND "Chat"."remoteJid" = ${remoteJid} and "Message"."messageType" != 'reactionMessage'
|
|
||||||
GROUP BY
|
|
||||||
"Chat"."id",
|
|
||||||
"Chat"."remoteJid",
|
|
||||||
"Contact"."id"
|
|
||||||
ORDER BY last_message_message_timestamp DESC NULLS LAST, "Chat"."updatedAt" DESC;
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timestampFilter =
|
||||||
|
query?.where?.messageTimestamp?.gte && query?.where?.messageTimestamp?.lte
|
||||||
|
? Prisma.sql`
|
||||||
|
AND "Message"."messageTimestamp" >= ${Math.floor(new Date(query.where.messageTimestamp.gte).getTime() / 1000)}
|
||||||
|
AND "Message"."messageTimestamp" <= ${Math.floor(new Date(query.where.messageTimestamp.lte).getTime() / 1000)}`
|
||||||
|
: Prisma.sql``;
|
||||||
|
|
||||||
|
const results = await this.prismaRepository.$queryRaw`
|
||||||
|
WITH rankedMessages AS (
|
||||||
|
SELECT DISTINCT ON ("Contact"."remoteJid")
|
||||||
|
"Contact"."id",
|
||||||
|
"Contact"."remoteJid",
|
||||||
|
"Contact"."pushName",
|
||||||
|
"Contact"."profilePicUrl",
|
||||||
|
COALESCE(
|
||||||
|
to_timestamp("Message"."messageTimestamp"::double precision),
|
||||||
|
"Contact"."updatedAt"
|
||||||
|
) as "updatedAt",
|
||||||
|
"Chat"."createdAt" as "windowStart",
|
||||||
|
"Chat"."createdAt" + INTERVAL '24 hours' as "windowExpires",
|
||||||
|
CASE
|
||||||
|
WHEN "Chat"."createdAt" + INTERVAL '24 hours' > NOW() THEN true
|
||||||
|
ELSE false
|
||||||
|
END as "windowActive",
|
||||||
|
"Message"."id" AS lastMessageId,
|
||||||
|
"Message"."key" AS lastMessage_key,
|
||||||
|
"Message"."pushName" AS lastMessagePushName,
|
||||||
|
"Message"."participant" AS lastMessageParticipant,
|
||||||
|
"Message"."messageType" AS lastMessageMessageType,
|
||||||
|
"Message"."message" AS lastMessageMessage,
|
||||||
|
"Message"."contextInfo" AS lastMessageContextInfo,
|
||||||
|
"Message"."source" AS lastMessageSource,
|
||||||
|
"Message"."messageTimestamp" AS lastMessageMessageTimestamp,
|
||||||
|
"Message"."instanceId" AS lastMessageInstanceId,
|
||||||
|
"Message"."sessionId" AS lastMessageSessionId,
|
||||||
|
"Message"."status" AS lastMessageStatus
|
||||||
|
FROM "Contact"
|
||||||
|
INNER JOIN "Message" ON "Message"."key"->>'remoteJid' = "Contact"."remoteJid"
|
||||||
|
LEFT JOIN "Chat" ON "Chat"."remoteJid" = "Contact"."remoteJid"
|
||||||
|
AND "Chat"."instanceId" = "Contact"."instanceId"
|
||||||
|
WHERE
|
||||||
|
"Contact"."instanceId" = ${this.instanceId}
|
||||||
|
AND "Message"."instanceId" = ${this.instanceId}
|
||||||
|
${remoteJid ? Prisma.sql`AND "Contact"."remoteJid" = ${remoteJid}` : Prisma.sql``}
|
||||||
|
${timestampFilter}
|
||||||
|
ORDER BY
|
||||||
|
"Contact"."remoteJid",
|
||||||
|
"Message"."messageTimestamp" DESC
|
||||||
|
)
|
||||||
|
SELECT * FROM rankedMessages
|
||||||
|
ORDER BY updatedAt DESC NULLS LAST;
|
||||||
|
`;
|
||||||
|
|
||||||
if (results && isArray(results) && results.length > 0) {
|
if (results && isArray(results) && results.length > 0) {
|
||||||
return results.map((chat) => {
|
const mappedResults = results.map((contact) => {
|
||||||
|
const lastMessage = contact.lastMessageId
|
||||||
|
? {
|
||||||
|
id: contact.lastMessageId,
|
||||||
|
key: contact.lastMessageKey,
|
||||||
|
pushName: contact.lastMessagePushName,
|
||||||
|
participant: contact.lastMessageParticipant,
|
||||||
|
messageType: contact.lastMessageMessageType,
|
||||||
|
message: contact.lastMessageMessage,
|
||||||
|
contextInfo: contact.lastMessageContextInfo,
|
||||||
|
source: contact.lastMessageSource,
|
||||||
|
messageTimestamp: contact.lastMessageMessageTimestamp,
|
||||||
|
instanceId: contact.lastMessageInstanceId,
|
||||||
|
sessionId: contact.lastMessageSessionId,
|
||||||
|
status: contact.lastMessageStatus,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: chat.id,
|
id: contact.id,
|
||||||
remoteJid: chat.remoteJid,
|
remoteJid: contact.remoteJid,
|
||||||
name: chat.name,
|
pushName: contact.pushName,
|
||||||
labels: chat.labels,
|
profilePicUrl: contact.profilePicUrl,
|
||||||
createdAt: chat.createdAt,
|
updatedAt: contact.updatedAt,
|
||||||
updatedAt: chat.updatedAt,
|
windowStart: contact.windowStart,
|
||||||
pushName: chat.pushName,
|
windowExpires: contact.windowExpires,
|
||||||
profilePicUrl: chat.profilePicUrl,
|
windowActive: contact.windowActive,
|
||||||
unreadMessages: chat.unreadMessages,
|
lastMessage: lastMessage ? this.cleanMessageData(lastMessage) : undefined,
|
||||||
lastMessage: chat.last_message_id
|
|
||||||
? {
|
|
||||||
id: chat.last_message_id,
|
|
||||||
key: chat.last_message_key,
|
|
||||||
pushName: chat.last_message_push_name,
|
|
||||||
participant: chat.last_message_participant,
|
|
||||||
messageType: chat.last_message_message_type,
|
|
||||||
message: chat.last_message_message,
|
|
||||||
contextInfo: chat.last_message_context_info,
|
|
||||||
source: chat.last_message_source,
|
|
||||||
messageTimestamp: chat.last_message_message_timestamp,
|
|
||||||
instanceId: chat.last_message_instance_id,
|
|
||||||
sessionId: chat.last_message_session_id,
|
|
||||||
status: chat.last_message_status,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return mappedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
@ -42,20 +42,23 @@ export class WAMonitoringService {
|
|||||||
public delInstanceTime(instance: string) {
|
public delInstanceTime(instance: string) {
|
||||||
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
||||||
if (typeof time === 'number' && time > 0) {
|
if (typeof time === 'number' && time > 0) {
|
||||||
setTimeout(async () => {
|
setTimeout(
|
||||||
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
async () => {
|
||||||
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
||||||
if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) {
|
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
||||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) {
|
||||||
this.waInstances[instance]?.client?.ws?.close();
|
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||||
this.waInstances[instance]?.client?.end(undefined);
|
this.waInstances[instance]?.client?.ws?.close();
|
||||||
|
this.waInstances[instance]?.client?.end(undefined);
|
||||||
|
}
|
||||||
|
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
||||||
|
} else {
|
||||||
|
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
||||||
}
|
}
|
||||||
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
|
||||||
} else {
|
|
||||||
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, 1000 * 60 * time);
|
1000 * 60 * time,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,14 +75,15 @@ export class WAMonitoringService {
|
|||||||
|
|
||||||
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
|
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
|
||||||
|
|
||||||
const where = instanceNames && instanceNames.length > 0
|
const where =
|
||||||
? {
|
instanceNames && instanceNames.length > 0
|
||||||
name: {
|
? {
|
||||||
in: instanceNames,
|
name: {
|
||||||
},
|
in: instanceNames,
|
||||||
clientName,
|
},
|
||||||
}
|
clientName,
|
||||||
: { clientName };
|
}
|
||||||
|
: { clientName };
|
||||||
|
|
||||||
const instances = await this.prismaRepository.instance.findMany({
|
const instances = await this.prismaRepository.instance.findMany({
|
||||||
where,
|
where,
|
||||||
@ -217,8 +221,11 @@ export class WAMonitoringService {
|
|||||||
data: {
|
data: {
|
||||||
id: data.instanceId,
|
id: data.instanceId,
|
||||||
name: data.instanceName,
|
name: data.instanceName,
|
||||||
|
ownerJid: data.ownerJid,
|
||||||
|
profileName: data.profileName,
|
||||||
|
profilePicUrl: data.profilePicUrl,
|
||||||
connectionStatus:
|
connectionStatus:
|
||||||
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: data.hash,
|
||||||
|
@ -85,6 +85,7 @@ export declare namespace wa {
|
|||||||
readMessages?: boolean;
|
readMessages?: boolean;
|
||||||
readStatus?: boolean;
|
readStatus?: boolean;
|
||||||
syncFullHistory?: boolean;
|
syncFullHistory?: boolean;
|
||||||
|
wavoipToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LocalEvent = {
|
export type LocalEvent = {
|
||||||
@ -131,7 +132,14 @@ export declare namespace wa {
|
|||||||
export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';
|
export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage', 'ptvMessage'];
|
export const TypeMediaMessage = [
|
||||||
|
'imageMessage',
|
||||||
|
'documentMessage',
|
||||||
|
'audioMessage',
|
||||||
|
'videoMessage',
|
||||||
|
'stickerMessage',
|
||||||
|
'ptvMessage',
|
||||||
|
];
|
||||||
|
|
||||||
export const MessageSubtype = [
|
export const MessageSubtype = [
|
||||||
'ephemeralMessage',
|
'ephemeralMessage',
|
||||||
|
5
src/cache/cacheengine.ts
vendored
5
src/cache/cacheengine.ts
vendored
@ -10,7 +10,10 @@ const logger = new Logger('CacheEngine');
|
|||||||
export class CacheEngine {
|
export class CacheEngine {
|
||||||
private engine: ICache;
|
private engine: ICache;
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService, module: string) {
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
module: string,
|
||||||
|
) {
|
||||||
const cacheConf = configService.get<CacheConf>('CACHE');
|
const cacheConf = configService.get<CacheConf>('CACHE');
|
||||||
|
|
||||||
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
|
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
|
||||||
|
5
src/cache/localcache.ts
vendored
5
src/cache/localcache.ts
vendored
@ -9,7 +9,10 @@ export class LocalCache implements ICache {
|
|||||||
private conf: CacheConfLocal;
|
private conf: CacheConfLocal;
|
||||||
static localCache = new NodeCache();
|
static localCache = new NodeCache();
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly module: string,
|
||||||
|
) {
|
||||||
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
|
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
src/cache/rediscache.ts
vendored
5
src/cache/rediscache.ts
vendored
@ -11,7 +11,10 @@ export class RedisCache implements ICache {
|
|||||||
private client: RedisClientType;
|
private client: RedisClientType;
|
||||||
private conf: CacheConfRedis;
|
private conf: CacheConfRedis;
|
||||||
|
|
||||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly module: string,
|
||||||
|
) {
|
||||||
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
|
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
|
||||||
this.client = redisClient.getConnection();
|
this.client = redisClient.getConnection();
|
||||||
}
|
}
|
||||||
|
25
src/main.ts
25
src/main.ts
@ -1,3 +1,7 @@
|
|||||||
|
// Import this first from sentry instrument!
|
||||||
|
import '@utils/instrumentSentry';
|
||||||
|
|
||||||
|
// Now import other modules
|
||||||
import { ProviderFiles } from '@api/provider/sessions';
|
import { ProviderFiles } from '@api/provider/sessions';
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { HttpStatus, router } from '@api/routes/index.router';
|
import { HttpStatus, router } from '@api/routes/index.router';
|
||||||
@ -21,19 +25,6 @@ function initWA() {
|
|||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const logger = new Logger('SERVER');
|
const logger = new Logger('SERVER');
|
||||||
const app = express();
|
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,
|
|
||||||
profilesSampleRate: 1.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
Sentry.setupExpressErrorHandler(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
let providerFiles: ProviderFiles = null;
|
let providerFiles: ProviderFiles = null;
|
||||||
if (configService.get<ProviderSession>('PROVIDER').ENABLED) {
|
if (configService.get<ProviderSession>('PROVIDER').ENABLED) {
|
||||||
@ -141,6 +132,14 @@ async function bootstrap() {
|
|||||||
|
|
||||||
eventManager.init(server);
|
eventManager.init(server);
|
||||||
|
|
||||||
|
if (process.env.SENTRY_DSN) {
|
||||||
|
logger.info('Sentry - ON');
|
||||||
|
|
||||||
|
// Add this after all routes,
|
||||||
|
// but before any and other error-handling middlewares are defined
|
||||||
|
Sentry.setupExpressErrorHandler(app);
|
||||||
|
}
|
||||||
|
|
||||||
server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT));
|
server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT));
|
||||||
|
|
||||||
initWA();
|
initWA();
|
||||||
|
71
src/utils/createJid.ts
Normal file
71
src/utils/createJid.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Check if the number is MX or AR
|
||||||
|
function formatMXOrARNumber(jid: string): string {
|
||||||
|
const countryCode = jid.substring(0, 2);
|
||||||
|
|
||||||
|
if (Number(countryCode) === 52 || Number(countryCode) === 54) {
|
||||||
|
if (jid.length === 13) {
|
||||||
|
const number = countryCode + jid.substring(3);
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jid;
|
||||||
|
}
|
||||||
|
return jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the number is br
|
||||||
|
function formatBRNumber(jid: string) {
|
||||||
|
const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/);
|
||||||
|
if (regexp.test(jid)) {
|
||||||
|
const match = regexp.exec(jid);
|
||||||
|
if (match && match[1] === '55') {
|
||||||
|
const joker = Number.parseInt(match[3][0]);
|
||||||
|
const ddd = Number.parseInt(match[2]);
|
||||||
|
if (joker < 7 || ddd < 31) {
|
||||||
|
return match[0];
|
||||||
|
}
|
||||||
|
return match[1] + match[2] + match[3];
|
||||||
|
}
|
||||||
|
return jid;
|
||||||
|
} else {
|
||||||
|
return jid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createJid(number: string): string {
|
||||||
|
number = number.replace(/:\d+/, '');
|
||||||
|
|
||||||
|
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number.includes('@broadcast')) {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
number = number
|
||||||
|
?.replace(/\s/g, '')
|
||||||
|
.replace(/\+/g, '')
|
||||||
|
.replace(/\(/g, '')
|
||||||
|
.replace(/\)/g, '')
|
||||||
|
.split(':')[0]
|
||||||
|
.split('@')[0];
|
||||||
|
|
||||||
|
if (number.includes('-') && number.length >= 24) {
|
||||||
|
number = number.replace(/[^\d-]/g, '');
|
||||||
|
return `${number}@g.us`;
|
||||||
|
}
|
||||||
|
|
||||||
|
number = number.replace(/\D/g, '');
|
||||||
|
|
||||||
|
if (number.length >= 18) {
|
||||||
|
number = number.replace(/[^\d-]/g, '');
|
||||||
|
return `${number}@g.us`;
|
||||||
|
}
|
||||||
|
|
||||||
|
number = formatMXOrARNumber(number);
|
||||||
|
|
||||||
|
number = formatBRNumber(number);
|
||||||
|
|
||||||
|
return `${number}@s.whatsapp.net`;
|
||||||
|
}
|
@ -1,11 +1,6 @@
|
|||||||
import { advancedOperatorsSearch } from './advancedOperatorsSearch';
|
import { advancedOperatorsSearch } from './advancedOperatorsSearch';
|
||||||
|
|
||||||
export const findBotByTrigger = async (
|
export const findBotByTrigger = async (botRepository: any, content: string, instanceId: string) => {
|
||||||
botRepository: any,
|
|
||||||
settingsRepository: any,
|
|
||||||
content: string,
|
|
||||||
instanceId: string,
|
|
||||||
) => {
|
|
||||||
// Check for triggerType 'all'
|
// Check for triggerType 'all'
|
||||||
const findTriggerAll = await botRepository.findFirst({
|
const findTriggerAll = await botRepository.findFirst({
|
||||||
where: {
|
where: {
|
||||||
|
@ -17,13 +17,14 @@ const getTypeMessage = (msg: any) => {
|
|||||||
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
||||||
listResponseMessage: msg?.message?.listResponseMessage?.title,
|
listResponseMessage: msg?.message?.listResponseMessage?.title,
|
||||||
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||||
templateButtonReplyMessage: msg?.message?.templateButtonReplyMessage?.selectedId,
|
templateButtonReplyMessage:
|
||||||
|
msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId,
|
||||||
// Medias
|
// Medias
|
||||||
audioMessage: msg?.message?.speechToText
|
audioMessage: msg?.message?.speechToText
|
||||||
? msg?.message?.speechToText
|
? msg?.message?.speechToText
|
||||||
: msg?.message?.audioMessage
|
: msg?.message?.audioMessage
|
||||||
? `audioMessage|${mediaId}`
|
? `audioMessage|${mediaId}`
|
||||||
: undefined,
|
: undefined,
|
||||||
imageMessage: msg?.message?.imageMessage
|
imageMessage: msg?.message?.imageMessage
|
||||||
? `imageMessage|${mediaId}${msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : ''}`
|
? `imageMessage|${mediaId}${msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : ''}`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
12
src/utils/instrumentSentry.ts
Normal file
12
src/utils/instrumentSentry.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
|
const dsn = process.env.SENTRY_DSN;
|
||||||
|
|
||||||
|
if (dsn) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: dsn,
|
||||||
|
environment: process.env.NODE_ENV || 'development',
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
profilesSampleRate: 1.0,
|
||||||
|
});
|
||||||
|
}
|
@ -43,6 +43,7 @@ export const instanceSchema: JSONSchema7 = {
|
|||||||
readMessages: { type: 'boolean' },
|
readMessages: { type: 'boolean' },
|
||||||
readStatus: { type: 'boolean' },
|
readStatus: { type: 'boolean' },
|
||||||
syncFullHistory: { type: 'boolean' },
|
syncFullHistory: { type: 'boolean' },
|
||||||
|
wavoipToken: { type: 'string' },
|
||||||
// Proxy
|
// Proxy
|
||||||
proxyHost: { type: 'string' },
|
proxyHost: { type: 'string' },
|
||||||
proxyPort: { type: 'string' },
|
proxyPort: { type: 'string' },
|
||||||
|
@ -31,6 +31,7 @@ export const settingsSchema: JSONSchema7 = {
|
|||||||
readMessages: { type: 'boolean' },
|
readMessages: { type: 'boolean' },
|
||||||
readStatus: { type: 'boolean' },
|
readStatus: { type: 'boolean' },
|
||||||
syncFullHistory: { type: 'boolean' },
|
syncFullHistory: { type: 'boolean' },
|
||||||
|
wavoipToken: { type: 'string' },
|
||||||
},
|
},
|
||||||
required: ['rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'],
|
required: ['rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'],
|
||||||
...isNotEmpty('rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'),
|
...isNotEmpty('rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"target": "es2020",
|
"target": "es2020",
|
||||||
"module": "commonjs",
|
"module": "CommonJS",
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
@ -26,7 +26,8 @@
|
|||||||
"@libs/*": ["./src/libs/*"],
|
"@libs/*": ["./src/libs/*"],
|
||||||
"@utils/*": ["./src/utils/*"],
|
"@utils/*": ["./src/utils/*"],
|
||||||
"@validate/*": ["./src/validate/*"]
|
"@validate/*": ["./src/validate/*"]
|
||||||
}
|
},
|
||||||
|
"moduleResolution": "Node"
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "./test", "./dist", "./prisma"],
|
"exclude": ["node_modules", "./test", "./dist", "./prisma"],
|
||||||
"include": [
|
"include": [
|
||||||
|
Loading…
Reference in New Issue
Block a user