This commit is contained in:
Allyson de Paula 2023-08-17 13:07:05 -03:00
commit 29e429a02e
75 changed files with 3569 additions and 279 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,3 +1,31 @@
# 1.5.0 (homolog)
### Feature
* New instance manager in /manager route
* Added extra files for chatwoot and appsmith
* Added Get Last Message and Archive for Chat
* Added env var QRCODE_COLOR
* Added websocket to send events
* Added rabbitmq to send events
* Added Typebot integration
* Added proxy endpoint
### Fixed
* Solved problem when disconnecting from the instance the instance was deleted
* Encoded spaces in chatwoot webhook
* Adjustment in the saving of contacts, saving the information of the number and Jid
* Update Dockerfile
* If you pass empty events in create instance and set webhook it is understood as all
* Fixed issue that did not output base64 averages
### Integrations
- Chatwoot: v2.18.0 - v3.0.0
- Typebot: v2.16.0
- Manager Evolution API
# 1.4.8 (2023-07-27 10:27)
### Fixed

View File

@ -31,7 +31,7 @@ CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_ENABLED=false
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker
@ -42,10 +42,15 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_ENABLED=false
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances
@ -84,6 +89,7 @@ CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit
QRCODE_LIMIT=30
QRCODE_COLOR=#198754
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,

View File

@ -1,16 +1,17 @@
FROM node:16.18-alpine
LABEL version="1.1.3" description="Api to control whatsapp features through http requests."
LABEL version="1.5.0" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
RUN apk update && apk upgrade && \
apk add --no-cache git
apk add --no-cache git tzdata ffmpeg wget curl
WORKDIR /evolution
COPY ./package.json .
ENV TZ=America/Sao_Paulo
ENV DOCKER_ENV=true
ENV SERVER_URL=http://localhost:8080
@ -50,7 +51,12 @@ ENV REDIS_ENABLED=false
ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution
ENV WEBHOOK_GLOBAL_URL=<url>
ENV RABBITMQ_ENABLED=false
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBSOCKET_ENABLED=false
ENV WEBHOOK_GLOBAL_URL=
ENV WEBHOOK_GLOBAL_ENABLED=false
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
@ -82,6 +88,7 @@ ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
ENV CONFIG_SESSION_PHONE_NAME=chrome
ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754
ENV AUTHENTICATION_TYPE=apikey

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,241 @@
{
"name": "[Evolution] Configurar Admin",
"nodes": [
{
"parameters": {
"values": {
"string": [
{
"name": "api_access_token",
"value": "CHATWOOT_ADMIN_USER_TOKEN"
},
{
"name": "chatwoot_url",
"value": "https://CHATWOOT_URL"
},
{
"name": "n8n_url",
"value": "https://N8N_URL"
},
{
"name": "organization",
"value": "ORGANIZATION_NAME"
},
{
"name": "logo",
"value": "ORGANIZATION_LOGO"
}
]
},
"options": {}
},
"id": "7a89a538-2cae-4032-8896-09627c07bc68",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
620,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/contacts/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
"options": {}
},
"id": "12a39df3-6b95-4f83-a0bc-50b25adaca7f",
"name": "Cria Contato Bot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1020,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
"options": {}
},
"id": "bed7c54d-e232-4fe4-9584-0515e9679868",
"name": "Cria Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
820,
480
]
},
{
"parameters": {},
"id": "36ada769-a757-4193-989b-0cc4ea504b80",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
420,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Company Chatwoot\",\n \"description\": \"Create Company Chatwoot\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/criadorchatwoot\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"Tema Criador de Empresa:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "f5bbb285-71a8-4c58-a4d7-e56002d697f0",
"name": "Cria Automação Empresas",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1220,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Info Base').item.json[\"api_access_token\"] }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n \n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n\n\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "a36bebdc-a318-40a2-8532-c7f476f8adb7",
"name": "Cria Automação Inboxes",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1420,
480
]
},
{
"parameters": {
"content": "## Workflow Para Configurar admin\n**Aqui você prepara o Chatwoot Principal com um usuário (Superadmin) que poderá criar empresas e caixas de entrada**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token do usuário que irá poder criar as empresas**",
"width": 894.6435495898575
},
"id": "db66e867-e9f4-452d-b521-725eeac652c8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
420,
280
]
}
],
"pinData": {},
"connections": {
"Info Base": {
"main": [
[
{
"node": "Cria Inbox Start",
"type": "main",
"index": 0
}
]
]
},
"Cria Contato Bot": {
"main": [
[
{
"node": "Cria Automação Empresas",
"type": "main",
"index": 0
}
]
]
},
"Cria Inbox Start": {
"main": [
[
{
"node": "Cria Contato Bot",
"type": "main",
"index": 0
}
]
]
},
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Cria Automação Empresas": {
"main": [
[
{
"node": "Cria Automação Inboxes",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "78f155dc-7809-4bfc-9282-63f49b07fc4d",
"id": "BSATyGpGWLR4ZwNm",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

View File

@ -0,0 +1,456 @@
{
"name": "[Evolution] Criador de Empresas",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "criadorchatwoot",
"options": {}
},
"id": "5a47c10a-e43c-4fa5-baad-4b6cc511bfcd",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
1420,
860
],
"webhookId": "6fe428e3-1752-453c-9358-abf18b793387"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "name",
"value": "={{ $json.name_company }}"
},
{
"name": "locale",
"value": "pt_BR"
}
]
},
"options": {}
},
"id": "8295c119-3a96-424e-9386-43d75f6816f5",
"name": "Cria Conta",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2020,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/users",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "name",
"value": "={{ $('Info Base').item.json.name_admin }}"
},
{
"name": "email",
"value": "={{ $('Info Base').item.json[\"email\"] }}"
},
{
"name": "password",
"value": "={{ $('Info Base').item.json[\"password\"] }}"
}
]
},
"options": {}
},
"id": "4fe5007a-3a6b-490a-a446-e45cc168189f",
"name": "Cria Usuario",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2220,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts/{{ $node[\"Cria Conta\"].json[\"id\"] }}/account_users",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "user_id",
"value": "={{ $node[\"Cria Usuario\"].json[\"id\"] }}"
},
{
"name": "role",
"value": "administrator"
}
]
},
"options": {}
},
"id": "848c55e2-5678-4291-9602-c94d994da95b",
"name": "Add Usuario a Conta",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2420,
860
]
},
{
"parameters": {
"fromEmail": "={{ $('Info Base').item.json[\"from_email\"] }}",
"toEmail": "={{ $('LimpaDados').item.json.email }}",
"subject": "=Bem vindo à {{ $('Info Base').item.json[\"organization\"] }}",
"text": "=Olá seja bem vindo:\n\nAbaixo segue seus dados de acesso:\n\nURL: {{ $('Info Base').item.json[\"chatwoot_url\"] }}\n\nuser: {{ $('LimpaDados').item.json[\"email\"] }}\n\nSenha: {{ $('LimpaDados').item.json[\"password\"] }}",
"options": {}
},
"id": "27f3b24f-1cf2-4d0d-a354-ecba066059f6",
"name": "Send Email",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2,
"position": [
3220,
860
],
"credentials": {
"smtp": {
"id": "6BxluEUV8zrXKoVG",
"name": "[Dgcode] SMTP"
}
}
},
{
"parameters": {
"values": {
"string": [
{
"name": "api_access_token",
"value": "CHATWOOT_PLATFORM_TOKEN"
},
{
"name": "chatwoot_url",
"value": "https://CHATWOOT_URL"
},
{
"name": "n8n_url",
"value": "https://N8N_URL"
},
{
"name": "organization",
"value": "ORGANIZATION_NAME"
},
{
"name": "logo",
"value": "ORGANIZATION_LOGO"
},
{
"name": "from_email",
"value": "FROM_EMAIL"
},
{
"name": "name",
"value": "={{ $json.name_company }}"
},
{
"name": "email",
"value": "={{ $json.email }}"
},
{
"name": "password",
"value": "={{ $json.password }}"
},
{
"name": "name_company",
"value": "={{ $json.name_company }}"
}
]
},
"options": {}
},
"id": "38b4069d-e51e-4db7-933f-941b1be6d124",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1820,
860
]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "name_admin",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome Usuario Administrador: ([^\\n]+)/)[1];}}"
},
{
"name": "name_company",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome da Empresa: ([^\\n]+)/)[1];}}"
},
{
"name": "email",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Email: ([^\\s]+)/)[1];}}"
},
{
"name": "password",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Senha: ([^\\s]+)/)[1];}}"
}
]
},
"options": {}
},
"id": "28e29e73-aadc-49ca-bd6d-b57ee0160a21",
"name": "LimpaDados",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1620,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/contacts/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
"options": {}
},
"id": "bb671443-bdb4-4f56-99af-f0baef246a3e",
"name": "Cria Contato Bot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2820,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Cria Usuario').item.json.access_token }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "e016a2af-b212-4e00-a3ff-8cd03530aa06",
"name": "Cria Automação",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
3020,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $json.account_id }}/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
"options": {}
},
"id": "d3c42148-8920-4c98-a874-eb7113f2dd22",
"name": "Cria Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2620,
860
]
},
{
"parameters": {
"content": "## Workflow Criador de Empresas\n**Cria Contas (Empresas) e Usuários através de tema**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token PlatformApp encontrado no acesso ao Super Admin**\n**Tema para criar novas empresa:**\n\nTema Criador de Empresa:\n\nNome Usuario Administrador: Joao Linhares\nNome da Empresa: Oficina Linhates\nEmail: machineteste24@gmail.com\nSenha: Mfcd62!!",
"height": 304.02684563758396,
"width": 1129.7777777777778
},
"id": "d07516c0-4c8e-43ab-ba86-c8d063b09be5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1420,
520
]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "LimpaDados",
"type": "main",
"index": 0
}
]
]
},
"Cria Conta": {
"main": [
[
{
"node": "Cria Usuario",
"type": "main",
"index": 0
}
]
]
},
"Cria Usuario": {
"main": [
[
{
"node": "Add Usuario a Conta",
"type": "main",
"index": 0
}
]
]
},
"Add Usuario a Conta": {
"main": [
[
{
"node": "Cria Inbox Start",
"type": "main",
"index": 0
}
]
]
},
"Info Base": {
"main": [
[
{
"node": "Cria Conta",
"type": "main",
"index": 0
}
]
]
},
"LimpaDados": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Cria Contato Bot": {
"main": [
[
{
"node": "Cria Automação",
"type": "main",
"index": 0
}
]
]
},
"Cria Automação": {
"main": [
[
{
"node": "Send Email",
"type": "main",
"index": 0
}
]
]
},
"Cria Inbox Start": {
"main": [
[
{
"node": "Cria Contato Bot",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"versionId": "3ffd6d3f-6966-4de4-af8f-1fda464bc1b8",
"id": "79R6qQDtfyCwgYjJ",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

View File

@ -0,0 +1,510 @@
{
"name": "[Evolution] Criador de Inbox",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "inbox_whatsapp",
"options": {}
},
"id": "8205b929-73e9-456a-9b0d-e1474991663a",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
320,
300
],
"webhookId": "cf37002d-3869-4bb1-af3a-739fdd3c1756"
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.evolution_url }}/instance/create",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "apikey",
"value": "={{ $json.global_api_key }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "instanceName",
"value": "={{ $json.instance_name }}"
},
{
"name": "qrcode",
"value": "={{ $json.qrcode }}"
},
{
"name": "chatwoot_account_id",
"value": "={{ $json.chatwoot_account_id }}"
},
{
"name": "chatwoot_token",
"value": "={{ $json.chatwoot_token }}"
},
{
"name": "chatwoot_url",
"value": "={{ $json.chatwoot_url }}"
},
{
"name": "chatwoot_sign_msg",
"value": "={{ $json.chatwoot_sign_msg }}"
},
{
"name": "chatwoot_reopen_conversation",
"value": "={{ $json.chatwoot_reopen_conversation }}"
},
{
"name": "chatwoot_conversation_pending",
"value": "={{ $json.chatwoot_conversation_pending }}"
},
{
"name": "reject_call",
"value": "={{ $json.reject_call }}"
},
{
"name": "msg_call",
"value": "={{ $json.msg_call }}"
},
{
"name": "groups_ignore",
"value": "={{ $json.groups_ignore }}"
},
{
"name": "always_online",
"value": "={{ $json.always_online }}"
},
{
"name": "read_messages",
"value": "={{ $json.read_messages }}"
},
{
"name": "read_status",
"value": "={{ $json.read_status }}"
}
]
},
"options": {}
},
"id": "275aa370-2fdb-42f4-844a-2fb3051301bd",
"name": "Cria Instancia",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
760,
300
]
},
{
"parameters": {
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
}
]
},
"options": {}
},
"id": "e4650812-ba0a-4f72-8bd8-a235eca4b2de",
"name": "Lista Inboxes",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
980,
300
]
},
{
"parameters": {
"content": "## Workflow Para Criar Inbox\n**Aqui você configura a comunicação entre o chatwoot e a Evolution API para criar novas instâncias a partir do chatwoot**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e Evolution API**",
"width": 1129.7777777777778
},
"id": "aa763d9e-d973-44fc-8399-277bb24718a5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
320,
80
]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "chatwoot_url",
"value": "CHATWOOT_URL"
},
{
"name": "evolution_url",
"value": "EVOLUTION_URL"
},
{
"name": "global_api_key",
"value": "EVOLUTION_GLOBAL_API_KEY"
},
{
"name": "organization",
"value": "={{ $json.query.organization }}"
},
{
"name": "instance_name",
"value": "={{ $json.body.messages[0].content.split(':')[1] }}-cwId-{{ $json.body.messages[0].account_id }}"
},
{
"name": "chatwoot_token",
"value": "={{ $json.query.utoken }}"
},
{
"name": "msg_call",
"value": "Não aceitamos chamadas, por favor deixe uma mensagem!"
}
],
"boolean": [
{
"name": "qrcode",
"value": true
},
{
"name": "chatwoot_sign_msg",
"value": true
},
{
"name": "chatwoot_reopen_conversation",
"value": true
},
{
"name": "chatwoot_conversation_pending"
},
{
"name": "reject_call",
"value": true
},
{
"name": "groups_ignore"
},
{
"name": "always_online",
"value": true
},
{
"name": "read_messages",
"value": true
},
{
"name": "read_status"
}
],
"number": [
{
"name": "chatwoot_account_id",
"value": "={{ $json.body.messages[0].account_id }}"
}
]
},
"options": {}
},
"id": "297df325-ecc4-4a34-817c-092d16d5753b",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
540,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.name }}",
"value2": "=Start {{ $('Info Base').item.json[\"organization\"] }}"
}
]
}
},
"id": "a8d955e6-ac51-4316-aeec-09d4d65e943a",
"name": "é Start Inbox?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1660,
200
]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "0d2d2194-aa4a-4241-9022-217d88bb581f",
"name": "Split In Batches",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 2,
"position": [
1420,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.name }}",
"value2": "={{ $('Webhook').item.json[\"body\"][\"messages\"][0][\"content\"].split(':')[1] }}"
}
]
}
},
"id": "0bfbc2cb-eff5-423c-bd3a-b266aaf6a943",
"name": "é_pre-existente?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1900,
340
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n\"channel\": {\n\"webhook_url\": \"{{ $('Info Base').item.json[\"evolution_url\"] }}/chatwoot/webhook/{{ encodeURIComponent($('Info Base').item.json[\"instance_name\"]) }}\"\n}\n}",
"options": {}
},
"id": "fb589456-5566-4a45-96a7-75986d0aa1d5",
"name": "Update_webhook_url",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2120,
340
]
},
{
"parameters": {
"method": "DELETE",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
}
]
},
"options": {}
},
"id": "e6094941-410f-496c-9c9c-7b95fd9349af",
"name": "Deleta Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1900,
100
]
},
{
"parameters": {},
"id": "8cf9a78f-9e8a-4288-9d7b-801790af68d5",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1660,
400
]
},
{
"parameters": {
"fieldToSplitOut": "payload",
"options": {}
},
"id": "9468896a-5f86-4598-9d20-e8f495cae859",
"name": "Ajusta lista",
"type": "n8n-nodes-base.itemLists",
"typeVersion": 2.2,
"position": [
1200,
300
]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Lista Inboxes": {
"main": [
[
{
"node": "Ajusta lista",
"type": "main",
"index": 0
}
]
]
},
"Cria Instancia": {
"main": [
[
{
"node": "Lista Inboxes",
"type": "main",
"index": 0
}
]
]
},
"Info Base": {
"main": [
[
{
"node": "Cria Instancia",
"type": "main",
"index": 0
}
]
]
},
"é Start Inbox?": {
"main": [
[
{
"node": "Deleta Inbox Start",
"type": "main",
"index": 0
}
],
[
{
"node": "é_pre-existente?",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches": {
"main": [
[
{
"node": "é Start Inbox?",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"é_pre-existente?": {
"main": [
[
{
"node": "Update_webhook_url",
"type": "main",
"index": 0
}
],
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Update_webhook_url": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Deleta Inbox Start": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Ajusta lista": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"versionId": "ab910349-b559-4738-9ac6-de6b06d6bbce",
"id": "ByW2ccjR4XPrOyio",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,8 @@
[![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com)
[![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998)
[![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/davidsongomes1998)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://bmc.link/evolutionapi)
</div>
@ -34,7 +35,15 @@ This code was produced based on the baileys library and it is still under develo
<div align="center">
<a href="https://app.picpay.com/user/davidsongomes1998" target="_blank" rel="noopener noreferrer">
<img src="./public/images/picpay-image.png" style="width: 50% !important;">
<img src="./public/images/picpay-qr.jpeg" style="width: 50% !important;">
</a>
</div>
#### Buy me coffe
<div align="center">
<a href="https://bmc.link/evolutionapi" target="_blank" rel="noopener noreferrer">
<img src="./public/images/bmc_qr.png" style="width: 50% !important;">
</a>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.4.8",
"version": "1.5.0",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@ -47,6 +47,7 @@
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "github:EvolutionAPI/Baileys",
"amqplib": "^0.10.3",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
"compression": "^1.7.4",
@ -63,16 +64,18 @@
"js-yaml": "^4.1.0",
"jsonschema": "^1.4.1",
"jsonwebtoken": "^8.5.1",
"libphonenumber-js": "^1.10.39",
"link-preview-js": "^3.0.4",
"mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"node-mime-types": "^1.1.0",
"pino": "^8.11.0",
"proxy-agent": "^6.2.1",
"proxy-agent": "^6.3.0",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.30.7",
"socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1",
"uuid": "^9.0.0"
},

BIN
public/images/bmc_qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -61,6 +61,15 @@ export type Redis = {
PREFIX_KEY: string;
};
export type Rabbitmq = {
ENABLED: boolean;
URI: string;
};
export type Websocket = {
ENABLED: boolean;
};
export type EventsWebhook = {
APPLICATION_STARTUP: boolean;
QRCODE_UPDATED: boolean;
@ -105,7 +114,7 @@ export type GlobalWebhook = {
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
export type QrCode = { LIMIT: number };
export type QrCode = { LIMIT: number; COLOR: string };
export type Production = boolean;
export interface Env {
@ -116,6 +125,8 @@ export interface Env {
CLEAN_STORE: CleanStoreConf;
DATABASE: Database;
REDIS: Redis;
RABBITMQ: Rabbitmq;
WEBSOCKET: Websocket;
LOG: Log;
DEL_INSTANCE: DelInstance;
WEBHOOK: Webhook;
@ -201,6 +212,13 @@ export class ConfigService {
URI: process.env.REDIS_URI,
PREFIX_KEY: process.env.REDIS_PREFIX_KEY,
},
RABBITMQ: {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
URI: process.env.RABBITMQ_URI,
},
WEBSOCKET: {
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
},
LOG: {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
COLOR: process.env?.LOG_COLOR === 'true',
@ -245,6 +263,7 @@ export class ConfigService {
},
QRCODE: {
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
COLOR: process.env.QRCODE_COLOR || '#198754',
},
AUTHENTICATION: {
TYPE: process.env.AUTHENTICATION_TYPE as 'jwt',

View File

@ -1,6 +1,8 @@
import dayjs from 'dayjs';
import fs from 'fs';
import { configService, Log } from './env.config';
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const formatDateLog = (timestamp: number) =>
dayjs(timestamp)
@ -74,6 +76,8 @@ export class Logger {
/*Command.UNDERSCORE +*/ Command.BRIGHT + Level[type],
'[Evolution API]',
Command.BRIGHT + Color[type],
`v${packageJson.version}`,
Command.BRIGHT + Color[type],
process.pid.toString(),
Command.RESET,
Command.BRIGHT + Color[type],

View File

@ -79,6 +79,13 @@ REDIS:
URI: "redis://localhost:6379"
PREFIX_KEY: "evolution"
RABBITMQ:
ENABLED: false
URI: "amqp://guest:guest@localhost:5672"
WEBSOCKET:
ENABLED: false
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
WEBHOOK:
@ -122,6 +129,7 @@ CONFIG_SESSION_PHONE:
# Set qrcode display limit
QRCODE:
LIMIT: 30
COLOR: '#198754'
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,

39
src/libs/amqp.server.ts Normal file
View File

@ -0,0 +1,39 @@
import * as amqp from 'amqplib/callback_api';
import { configService, Rabbitmq } from '../config/env.config';
import { Logger } from '../config/logger.config';
const logger = new Logger('AMQP');
let amqpChannel: amqp.Channel | null = null;
export const initAMQP = () => {
return new Promise<void>((resolve, reject) => {
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
amqp.connect(uri, (error, connection) => {
if (error) {
reject(error);
return;
}
connection.createChannel((channelError, channel) => {
if (channelError) {
reject(channelError);
return;
}
const exchangeName = 'evolution_exchange';
channel.assertExchange(exchangeName, 'topic', { durable: false });
amqpChannel = channel;
logger.info('AMQP initialized');
resolve();
});
});
});
};
export const getAMQP = (): amqp.Channel | null => {
return amqpChannel;
};

View File

@ -5,6 +5,10 @@ import { Redis } from '../config/env.config';
import { Logger } from '../config/logger.config';
export class RedisCache {
async disconnect() {
await this.client.disconnect();
this.statusConnection = false;
}
constructor() {
this.logger.verbose('instance created');
process.on('beforeExit', async () => {

44
src/libs/socket.server.ts Normal file
View File

@ -0,0 +1,44 @@
import { Server } from 'http';
import { Server as SocketIO } from 'socket.io';
import { configService, Cors, Websocket } from '../config/env.config';
import { Logger } from '../config/logger.config';
const logger = new Logger('Socket');
let io: SocketIO;
const cors = configService.get<Cors>('CORS').ORIGIN;
export const initIO = (httpServer: Server) => {
if (configService.get<Websocket>('WEBSOCKET').ENABLED) {
io = new SocketIO(httpServer, {
cors: {
origin: cors,
},
});
io.on('connection', (socket) => {
logger.info('User connected');
socket.on('disconnect', () => {
logger.info('User disconnected');
});
});
logger.info('Socket.io initialized');
return io;
}
return null;
};
export const getIO = (): SocketIO => {
logger.verbose('Getting Socket.io');
if (!io) {
logger.error('Socket.io not initialized');
throw new Error('Socket.io not initialized');
}
return io;
};

View File

@ -5,10 +5,12 @@ import cors from 'cors';
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
import { join } from 'path';
import { configService, Cors, HttpServer } from './config/env.config';
import { configService, Cors, HttpServer, Rabbitmq } from './config/env.config';
import { onUnexpectedError } from './config/error.config';
import { Logger } from './config/logger.config';
import { ROOT_DIR } from './config/path.config';
import { initAMQP } from './libs/amqp.server';
import { initIO } from './libs/socket.server';
import { ServerUP } from './utils/server-up';
import { HttpStatus, router } from './whatsapp/routers/index.router';
import { waMonitor } from './whatsapp/whatsapp.module';
@ -83,6 +85,10 @@ function bootstrap() {
initWA();
initIO(server);
if (configService.get<Rabbitmq>('RABBITMQ').ENABLED) initAMQP();
onUnexpectedError();
}

View File

@ -9,7 +9,7 @@ import {
import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { dbserver } from '../db/db.connect';
import { dbserver } from '../libs/db.connect';
export async function useMultiFileAuthStateDb(
coll: string,

View File

@ -7,7 +7,7 @@ import {
} from '@whiskeysockets/baileys';
import { Logger } from '../config/logger.config';
import { RedisCache } from '../db/redis.client';
import { RedisCache } from '../libs/redis.client';
export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{
state: AuthenticationState;

View File

@ -508,6 +508,7 @@ export const archiveChatSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
chat: { type: 'string' },
lastMessage: {
type: 'object',
properties: {
@ -528,7 +529,7 @@ export const archiveChatSchema: JSONSchema7 = {
},
archive: { type: 'boolean', enum: [true, false] },
},
required: ['lastMessage', 'archive'],
required: ['archive'],
};
export const deleteMessageSchema: JSONSchema7 = {
@ -823,13 +824,11 @@ export const updateGroupDescriptionSchema: JSONSchema7 = {
...isNotEmpty('groupJid', 'description'),
};
// Webhook Schema
export const webhookSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
url: { type: 'string' },
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
@ -861,7 +860,7 @@ export const webhookSchema: JSONSchema7 = {
},
},
},
required: ['url', 'enabled'],
required: ['url'],
...isNotEmpty('url'),
};
@ -895,3 +894,120 @@ export const settingsSchema: JSONSchema7 = {
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'],
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'),
};
export const websocketSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};
export const rabbitmqSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};
export const typebotSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
url: { type: 'string' },
typebot: { type: 'string' },
expire: { type: 'integer' },
delay_message: { type: 'integer' },
unknown_message: { type: 'string' },
},
required: ['enabled', 'url', 'typebot', 'expire'],
...isNotEmpty('enabled', 'url', 'typebot', 'expire'),
};
export const typebotStatusSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
status: { type: 'string', enum: ['opened', 'closed', 'paused'] },
},
required: ['remoteJid', 'status'],
...isNotEmpty('remoteJid', 'status'),
};
export const proxySchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
proxy: { type: 'string' },
},
required: ['enabled', 'proxy'],
...isNotEmpty('enabled', 'proxy'),
};

View File

@ -52,7 +52,7 @@ export class ChatwootController {
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
};
return response;
@ -78,7 +78,7 @@ export class ChatwootController {
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
};
return response;
@ -90,10 +90,4 @@ export class ChatwootController {
return chatwootService.receiveWebhook(instance, data);
}
public async newInstance(data: any) {
const chatwootService = new ChatwootService(waMonitor, this.configService);
return chatwootService.newInstance(data);
}
}

View File

@ -4,15 +4,18 @@ import EventEmitter2 from 'eventemitter2';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { RedisCache } from '../../db/redis.client';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { RedisCache } from '../../libs/redis.client';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { ChatwootService } from '../services/chatwoot.service';
import { WAMonitoringService } from '../services/monitor.service';
import { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service';
import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service';
import { WAStartupService } from '../services/whatsapp.service';
import { wa } from '../types/wa.types';
@ -26,6 +29,9 @@ export class InstanceController {
private readonly webhookService: WebhookService,
private readonly chatwootService: ChatwootService,
private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService,
private readonly typebotService: TypebotService,
private readonly cache: RedisCache,
) {}
@ -51,6 +57,16 @@ export class InstanceController {
always_online,
read_messages,
read_status,
websocket_enabled,
websocket_events,
rabbitmq_enabled,
rabbitmq_events,
typebot_url,
typebot,
typebot_expire,
typebot_keyword_finish,
typebot_delay_message,
typebot_unknown_message,
}: InstanceDto) {
try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@ -77,7 +93,7 @@ export class InstanceController {
this.logger.verbose('hash: ' + hash + ' generated');
let getEvents: string[];
let webhookEvents: string[];
if (webhook) {
if (!isURL(webhook, { require_tld: false })) {
@ -86,14 +102,152 @@ export class InstanceController {
this.logger.verbose('creating webhook');
try {
let newEvents: string[] = [];
if (events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
} else {
newEvents = events;
}
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events,
events: newEvents,
webhook_by_events,
});
getEvents = (await this.webhookService.find(instance)).events;
webhookEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let websocketEvents: string[];
if (websocket_enabled) {
this.logger.verbose('creating websocket');
try {
let newEvents: string[] = [];
if (websocket_events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
} else {
newEvents = events;
}
this.websocketService.create(instance, {
enabled: true,
events: newEvents,
});
websocketEvents = (await this.websocketService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let rabbitmqEvents: string[];
if (rabbitmq_enabled) {
this.logger.verbose('creating rabbitmq');
try {
let newEvents: string[] = [];
if (rabbitmq_events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
} else {
newEvents = events;
}
this.rabbitmqService.create(instance, {
enabled: true,
events: newEvents,
});
rabbitmqEvents = (await this.rabbitmqService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
if (typebot_url) {
try {
if (!isURL(typebot_url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in typebot_url');
}
this.logger.verbose('creating typebot');
this.typebotService.create(instance, {
enabled: true,
url: typebot_url,
typebot: typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
});
} catch (error) {
this.logger.log(error);
}
@ -129,9 +283,28 @@ export class InstanceController {
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
webhook: {
webhook,
webhook_by_events,
events: webhookEvents,
},
websocket: {
enabled: websocket_enabled,
events: websocketEvents,
},
rabbitmq: {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
},
settings,
qrcode: getQrcode,
};
@ -179,7 +352,7 @@ export class InstanceController {
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
name_inbox: instance.instanceName,
name_inbox: instance.instanceName.split('-cwId-')[0],
number,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
@ -187,8 +360,8 @@ export class InstanceController {
this.chatwootService.initInstanceChatwoot(
instance,
instance.instanceName,
`${urlServer}/chatwoot/webhook/${instance.instanceName}`,
instance.instanceName.split('-cwId-')[0],
`${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
qrcode,
number,
);
@ -202,9 +375,28 @@ export class InstanceController {
status: 'created',
},
hash,
webhook,
webhook_by_events,
events: getEvents,
webhook: {
webhook,
webhook_by_events,
events: webhookEvents,
},
websocket: {
enabled: websocket_enabled,
events: websocketEvents,
},
rabbitmq: {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
},
settings,
chatwoot: {
enabled: true,
@ -216,7 +408,7 @@ export class InstanceController {
conversation_pending: chatwoot_conversation_pending || false,
number,
name_inbox: instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
},
};
} catch (error) {
@ -234,6 +426,10 @@ export class InstanceController {
this.logger.verbose('state: ' + state);
if (!state) {
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
}
if (state == 'open') {
return await this.connectionState({ instanceName });
}

View File

@ -0,0 +1,26 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto';
import { ProxyService } from '../services/proxy.service';
const logger = new Logger('ProxyController');
export class ProxyController {
constructor(private readonly proxyService: ProxyService) {}
public async createProxy(instance: InstanceDto, data: ProxyDto) {
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('proxy disabled');
data.proxy = '';
}
return this.proxyService.create(instance, data);
}
public async findProxy(instance: InstanceDto) {
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
return this.proxyService.find(instance);
}
}

View File

@ -0,0 +1,53 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqService } from '../services/rabbitmq.service';
const logger = new Logger('RabbitmqController');
export class RabbitmqController {
constructor(private readonly rabbitmqService: RabbitmqService) {}
public async createRabbitmq(instance: InstanceDto, data: RabbitmqDto) {
logger.verbose('requested createRabbitmq from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('rabbitmq disabled');
data.events = [];
}
if (data.events.length === 0) {
logger.verbose('rabbitmq events empty');
data.events = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
}
return this.rabbitmqService.create(instance, data);
}
public async findRabbitmq(instance: InstanceDto) {
logger.verbose('requested findRabbitmq from ' + instance.instanceName + ' instance');
return this.rabbitmqService.find(instance);
}
}

View File

@ -0,0 +1,41 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { TypebotDto } from '../dto/typebot.dto';
import { TypebotService } from '../services/typebot.service';
const logger = new Logger('TypebotController');
export class TypebotController {
constructor(private readonly typebotService: TypebotService) {}
public async createTypebot(instance: InstanceDto, data: TypebotDto) {
logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('typebot disabled');
data.url = '';
data.typebot = '';
data.expire = 0;
data.sessions = [];
} else {
const saveData = await this.typebotService.find(instance);
if (saveData.enabled) {
logger.verbose('typebot enabled');
data.sessions = saveData.sessions;
}
}
return this.typebotService.create(instance, data);
}
public async findTypebot(instance: InstanceDto) {
logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance');
return this.typebotService.find(instance);
}
public async changeStatus(instance: InstanceDto, data: any) {
logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance');
return this.typebotService.changeStatus(instance, data);
}
}

View File

@ -1,24 +1,15 @@
import { Request, Response } from 'express';
import { Auth, ConfigService } from '../../config/env.config';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { ConfigService } from '../../config/env.config';
import { HttpStatus } from '../routers/index.router';
import { WAMonitoringService } from '../services/monitor.service';
export class ViewsController {
constructor(private readonly waMonit: WAMonitoringService, private readonly configService: ConfigService) {}
public async qrcode(request: Request, response: Response) {
public async manager(request: Request, response: Response) {
try {
const param = request.params as unknown as InstanceDto;
const instance = this.waMonit.waInstances[param.instanceName];
if (instance.connectionStatus.state === 'open') {
throw new BadRequestException('The instance is already connected');
}
const type = this.configService.get<Auth>('AUTHENTICATION').TYPE;
return response.status(HttpStatus.OK).render('qrcode', { type, ...param });
return response.status(HttpStatus.OK).render('manager');
} catch (error) {
console.log('ERROR: ', error);
}

View File

@ -14,14 +14,41 @@ export class WebhookController {
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
if (data.enabled && !isURL(data.url, { require_tld: false })) {
if (!isURL(data.url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property');
}
data.enabled = data.enabled ?? true;
if (!data.enabled) {
logger.verbose('webhook disabled');
data.url = '';
data.events = [];
} else if (data.events.length === 0) {
logger.verbose('webhook events empty');
data.events = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
}
return this.webhookService.create(instance, data);

View File

@ -0,0 +1,53 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketService } from '../services/websocket.service';
const logger = new Logger('WebsocketController');
export class WebsocketController {
constructor(private readonly websocketService: WebsocketService) {}
public async createWebsocket(instance: InstanceDto, data: WebsocketDto) {
logger.verbose('requested createWebsocket from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('websocket disabled');
data.events = [];
}
if (data.events.length === 0) {
logger.verbose('websocket events empty');
data.events = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
];
}
return this.websocketService.create(instance, data);
}
public async findWebsocket(instance: InstanceDto) {
logger.verbose('requested findWebsocket from ' + instance.instanceName + ' instance');
return this.websocketService.find(instance);
}
}

View File

@ -53,13 +53,14 @@ export class ReadMessageDto {
read_messages: Key[];
}
class LastMessage {
export class LastMessage {
key: Key;
messageTimestamp?: number;
}
export class ArchiveChatDto {
lastMessage: LastMessage;
lastMessage?: LastMessage;
chat?: string;
archive: boolean;
}

View File

@ -18,4 +18,16 @@ export class InstanceDto {
chatwoot_sign_msg?: boolean;
chatwoot_reopen_conversation?: boolean;
chatwoot_conversation_pending?: boolean;
websocket_enabled?: boolean;
websocket_events?: string[];
rabbitmq_enabled?: boolean;
rabbitmq_events?: string[];
typebot_url?: string;
typebot?: string;
typebot_expire?: number;
typebot_keyword_finish?: string;
typebot_delay_message?: number;
typebot_unknown_message?: string;
proxy_enabled?: boolean;
proxy_proxy?: string;
}

View File

@ -0,0 +1,4 @@
export class ProxyDto {
enabled: boolean;
proxy: string;
}

View File

@ -0,0 +1,4 @@
export class RabbitmqDto {
enabled: boolean;
events?: string[];
}

View File

@ -0,0 +1,18 @@
export class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
}
export class TypebotDto {
enabled?: boolean;
url: string;
typebot?: string;
expire?: number;
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
sessions?: Session[];
}

View File

@ -0,0 +1,4 @@
export class WebsocketDto {
enabled: boolean;
events?: string[];
}

View File

@ -55,7 +55,7 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
}
}
async function apikey(req: Request, res: Response, next: NextFunction) {
async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey');

View File

@ -4,8 +4,8 @@ import { join } from 'path';
import { configService, Database, Redis } from '../../config/env.config';
import { INSTANCE_DIR } from '../../config/path.config';
import { dbserver } from '../../db/db.connect';
import { BadRequestException, ForbiddenException, NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { InstanceDto } from '../dto/instance.dto';
import { cache, waMonitor } from '../whatsapp.module';

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class AuthRaw {
_id?: string;

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class ChatRaw {
_id?: string;

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class ChatwootRaw {
_id?: string;

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class ContactRaw {
_id?: string;

View File

@ -3,5 +3,9 @@ export * from './chat.model';
export * from './chatwoot.model';
export * from './contact.model';
export * from './message.model';
export * from './proxy.model';
export * from './rabbitmq.model';
export * from './settings.model';
export * from './typebot.model';
export * from './webhook.model';
export * from './websocket.model';

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
import { wa } from '../types/wa.types';
class Key {

View File

@ -0,0 +1,18 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class ProxyRaw {
_id?: string;
enabled?: boolean;
proxy?: string;
}
const proxySchema = new Schema<ProxyRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
proxy: { type: String, required: true },
});
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');
export type IProxyModel = typeof ProxyModel;

View File

@ -0,0 +1,18 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class RabbitmqRaw {
_id?: string;
enabled?: boolean;
events?: string[];
}
const rabbitmqSchema = new Schema<RabbitmqRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
events: { type: [String], required: true },
});
export const RabbitmqModel = dbserver?.model(RabbitmqRaw.name, rabbitmqSchema, 'rabbitmq');
export type IRabbitmqModel = typeof RabbitmqModel;

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class SettingsRaw {
_id?: string;

View File

@ -0,0 +1,46 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
}
export class TypebotRaw {
_id?: string;
enabled?: boolean;
url: string;
typebot?: string;
expire?: number;
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
sessions?: Session[];
}
const typebotSchema = new Schema<TypebotRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
url: { type: String, required: true },
typebot: { type: String, required: true },
expire: { type: Number, required: true },
keyword_finish: { type: String, required: true },
delay_message: { type: Number, required: true },
unknown_message: { type: String, required: true },
sessions: [
{
remoteJid: { type: String, required: true },
sessionId: { type: String, required: true },
status: { type: String, required: true },
createdAt: { type: Number, required: true },
updateAt: { type: Number, required: true },
},
],
});
export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot');
export type ITypebotModel = typeof TypebotModel;

View File

@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
export class WebhookRaw {
_id?: string;

View File

@ -0,0 +1,18 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class WebsocketRaw {
_id?: string;
enabled?: boolean;
events?: string[];
}
const websocketSchema = new Schema<WebsocketRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
events: { type: [String], required: true },
});
export const WebsocketModel = dbserver?.model(WebsocketRaw.name, websocketSchema, 'websocket');
export type IWebsocketModel = typeof WebsocketModel;

View File

@ -0,0 +1,62 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IProxyModel, ProxyRaw } from '../models';
export class ProxyRepository extends Repository {
constructor(private readonly proxyModel: IProxyModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('ProxyRepository');
public async create(data: ProxyRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating proxy');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving proxy to db');
const insert = await this.proxyModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('proxy saved to db: ' + insert.modifiedCount + ' proxy');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving proxy to store');
this.writeStore<ProxyRaw>({
path: join(this.storePath, 'proxy'),
fileName: instance,
data,
});
this.logger.verbose('proxy saved to store in path: ' + join(this.storePath, 'proxy') + '/' + instance);
this.logger.verbose('proxy created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<ProxyRaw> {
try {
this.logger.verbose('finding proxy');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding proxy in db');
return await this.proxyModel.findOne({ _id: instance });
}
this.logger.verbose('finding proxy in store');
return JSON.parse(
readFileSync(join(this.storePath, 'proxy', instance + '.json'), {
encoding: 'utf-8',
}),
) as ProxyRaw;
} catch (error) {
return {};
}
}
}

View File

@ -0,0 +1,62 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IRabbitmqModel, RabbitmqRaw } from '../models';
export class RabbitmqRepository extends Repository {
constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('RabbitmqRepository');
public async create(data: RabbitmqRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating rabbitmq');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving rabbitmq to db');
const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving rabbitmq to store');
this.writeStore<RabbitmqRaw>({
path: join(this.storePath, 'rabbitmq'),
fileName: instance,
data,
});
this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance);
this.logger.verbose('rabbitmq created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<RabbitmqRaw> {
try {
this.logger.verbose('finding rabbitmq');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding rabbitmq in db');
return await this.rabbitmqModel.findOne({ _id: instance });
}
this.logger.verbose('finding rabbitmq in store');
return JSON.parse(
readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), {
encoding: 'utf-8',
}),
) as RabbitmqRaw;
} catch (error) {
return {};
}
}
}

View File

@ -10,8 +10,12 @@ import { ChatwootRepository } from './chatwoot.repository';
import { ContactRepository } from './contact.repository';
import { MessageRepository } from './message.repository';
import { MessageUpRepository } from './messageUp.repository';
import { ProxyRepository } from './proxy.repository';
import { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository';
import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository';
export class RepositoryBroker {
constructor(
public readonly message: MessageRepository,
@ -21,6 +25,10 @@ export class RepositoryBroker {
public readonly webhook: WebhookRepository,
public readonly chatwoot: ChatwootRepository,
public readonly settings: SettingsRepository,
public readonly websocket: WebsocketRepository,
public readonly rabbitmq: RabbitmqRepository,
public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository,
public readonly auth: AuthRepository,
private configService: ConfigService,
dbServer?: MongoClient,
@ -51,6 +59,10 @@ export class RepositoryBroker {
const webhookDir = join(storePath, 'webhook');
const chatwootDir = join(storePath, 'chatwoot');
const settingsDir = join(storePath, 'settings');
const websocketDir = join(storePath, 'websocket');
const rabbitmqDir = join(storePath, 'rabbitmq');
const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy');
const tempDir = join(storePath, 'temp');
if (!fs.existsSync(authDir)) {
@ -85,6 +97,22 @@ export class RepositoryBroker {
this.logger.verbose('creating settings dir: ' + settingsDir);
fs.mkdirSync(settingsDir, { recursive: true });
}
if (!fs.existsSync(websocketDir)) {
this.logger.verbose('creating websocket dir: ' + websocketDir);
fs.mkdirSync(websocketDir, { recursive: true });
}
if (!fs.existsSync(rabbitmqDir)) {
this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir);
fs.mkdirSync(rabbitmqDir, { recursive: true });
}
if (!fs.existsSync(typebotDir)) {
this.logger.verbose('creating typebot dir: ' + typebotDir);
fs.mkdirSync(typebotDir, { recursive: true });
}
if (!fs.existsSync(proxyDir)) {
this.logger.verbose('creating proxy dir: ' + proxyDir);
fs.mkdirSync(proxyDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true });

View File

@ -0,0 +1,68 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ITypebotModel, TypebotRaw } from '../models';
export class TypebotRepository extends Repository {
constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('TypebotRepository');
public async create(data: TypebotRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating typebot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving typebot to db');
const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving typebot to store');
this.writeStore<TypebotRaw>({
path: join(this.storePath, 'typebot'),
fileName: instance,
data,
});
this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance);
this.logger.verbose('typebot created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<TypebotRaw> {
try {
this.logger.verbose('finding typebot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding typebot in db');
return await this.typebotModel.findOne({ _id: instance });
}
this.logger.verbose('finding typebot in store');
return JSON.parse(
readFileSync(join(this.storePath, 'typebot', instance + '.json'), {
encoding: 'utf-8',
}),
) as TypebotRaw;
} catch (error) {
return {
enabled: false,
url: '',
typebot: '',
expire: 0,
sessions: [],
};
}
}
}

View File

@ -0,0 +1,62 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { IWebsocketModel, WebsocketRaw } from '../models';
export class WebsocketRepository extends Repository {
constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('WebsocketRepository');
public async create(data: WebsocketRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating websocket');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving websocket to db');
const insert = await this.websocketModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('websocket saved to db: ' + insert.modifiedCount + ' websocket');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving websocket to store');
this.writeStore<WebsocketRaw>({
path: join(this.storePath, 'websocket'),
fileName: instance,
data,
});
this.logger.verbose('websocket saved to store in path: ' + join(this.storePath, 'websocket') + '/' + instance);
this.logger.verbose('websocket created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<WebsocketRaw> {
try {
this.logger.verbose('finding websocket');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding websocket in db');
return await this.websocketModel.findOne({ _id: instance });
}
this.logger.verbose('finding websocket in store');
return JSON.parse(
readFileSync(join(this.storePath, 'websocket', instance + '.json'), {
encoding: 'utf-8',
}),
) as WebsocketRaw;
} catch (error) {
return {};
}
}
}

View File

@ -8,10 +8,14 @@ import { ChatRouter } from './chat.router';
import { ChatwootRouter } from './chatwoot.router';
import { GroupRouter } from './group.router';
import { InstanceRouter } from './instance.router';
import { ProxyRouter } from './proxy.router';
import { RabbitmqRouter } from './rabbitmq.router';
import { MessageRouter } from './sendMessage.router';
import { SettingsRouter } from './settings.router';
import { TypebotRouter } from './typebot.router';
import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router';
import { WebsocketRouter } from './websocket.router';
enum HttpStatus {
OK = 200,
@ -37,12 +41,17 @@ router
version: packageJson.version,
});
})
.use('/instance', new InstanceRouter(configService, ...guards).router, new ViewsRouter(instanceExistsGuard).router)
.use('/instance', new InstanceRouter(configService, ...guards).router)
.use('/manager', new ViewsRouter().router)
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router)
.use('/group', new GroupRouter(...guards).router)
.use('/webhook', new WebhookRouter(...guards).router)
.use('/chatwoot', new ChatwootRouter(...guards).router)
.use('/settings', new SettingsRouter(...guards).router);
.use('/settings', new SettingsRouter(...guards).router)
.use('/websocket', new WebsocketRouter(...guards).router)
.use('/rabbitmq', new RabbitmqRouter(...guards).router)
.use('/typebot', new TypebotRouter(...guards).router)
.use('/proxy', new ProxyRouter(...guards).router);
export { HttpStatus, router };

View File

@ -2,7 +2,7 @@ import { RequestHandler, Router } from 'express';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { dbserver } from '../../db/db.connect';
import { dbserver } from '../../libs/db.connect';
import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';

View File

@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, proxySchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto';
import { proxyController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('ProxyRouter');
export class ProxyRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setProxy');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ProxyDto>({
request: req,
schema: proxySchema,
ClassRef: ProxyDto,
execute: (instance, data) => proxyController.createProxy(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findProxy');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => proxyController.findProxy(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, rabbitmqSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { rabbitmqController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('RabbitmqRouter');
export class RabbitmqRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setRabbitmq');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<RabbitmqDto>({
request: req,
schema: rabbitmqSchema,
ClassRef: RabbitmqDto,
execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findRabbitmq');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => rabbitmqController.findRabbitmq(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -0,0 +1,68 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, typebotSchema, typebotStatusSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { TypebotDto } from '../dto/typebot.dto';
import { typebotController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('TypebotRouter');
export class TypebotRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setTypebot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<TypebotDto>({
request: req,
schema: typebotSchema,
ClassRef: TypebotDto,
execute: (instance, data) => typebotController.createTypebot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findTypebot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => typebotController.findTypebot(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
logger.verbose('request received in findTypebot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: typebotStatusSchema,
ClassRef: InstanceDto,
execute: (instance, data) => typebotController.changeStatus(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -1,14 +1,14 @@
import { RequestHandler, Router } from 'express';
import { Router } from 'express';
import { RouterBroker } from '../abstract/abstract.router';
import { viewsController } from '../whatsapp.module';
export class ViewsRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
constructor() {
super();
this.router.get(this.routerPath('qrcode'), ...guards, (req, res) => {
return viewsController.qrcode(req, res);
this.router.get('/', (req, res) => {
return viewsController.manager(req, res);
});
}

View File

@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { instanceNameSchema, websocketSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto';
import { websocketController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('WebsocketRouter');
export class WebsocketRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setWebsocket');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<WebsocketDto>({
request: req,
schema: websocketSchema,
ClassRef: WebsocketDto,
execute: (instance, data) => websocketController.createWebsocket(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findWebsocket');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceNameSchema,
ClassRef: InstanceDto,
execute: (instance) => websocketController.findWebsocket(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -5,7 +5,7 @@ import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
import mimeTypes from 'mime-types';
import path from 'path';
import { ConfigService, HttpServer } from '../../config/env.config';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ROOT_DIR } from '../../config/path.config';
import { ChatwootDto } from '../dto/chatwoot.dto';
@ -205,7 +205,14 @@ export class ChatwootService {
this.logger.verbose('find contact in chatwoot and create if not exists');
const contact =
(await this.findContact(instance, '123456')) ||
((await this.createContact(instance, '123456', inboxId, false, 'EvolutionAPI')) as any);
((await this.createContact(
instance,
'123456',
inboxId,
false,
'EvolutionAPI',
'https://evolution-api.com/files/evolution-api-favicon.png',
)) as any);
if (!contact) {
this.logger.warn('contact not found');
@ -237,10 +244,10 @@ export class ChatwootService {
this.logger.verbose('create message for init instance in chatwoot');
let contentMsg = '/init';
let contentMsg = 'init';
if (number) {
contentMsg = `/init:${number}`;
contentMsg = `init:${number}`;
}
const message = await client.messages.create({
@ -269,6 +276,7 @@ export class ChatwootService {
isGroup: boolean,
name?: string,
avatar_url?: string,
jid?: string,
) {
this.logger.verbose('create contact to instance: ' + instance.instanceName);
@ -286,6 +294,7 @@ export class ChatwootService {
inbox_id: inboxId,
name: name || phoneNumber,
phone_number: `+${phoneNumber}`,
identifier: jid,
avatar_url: avatar_url,
};
} else {
@ -437,6 +446,7 @@ export class ChatwootService {
false,
body.pushName,
picture_url.profilePictureUrl || null,
body.key.participant,
);
}
}
@ -452,6 +462,7 @@ export class ChatwootService {
if (findContact) {
contact = findContact;
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
@ -459,6 +470,7 @@ export class ChatwootService {
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
} else {
@ -472,6 +484,7 @@ export class ChatwootService {
contact = findContact;
}
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
@ -479,6 +492,7 @@ export class ChatwootService {
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
}
@ -578,7 +592,7 @@ export class ChatwootService {
}
this.logger.verbose('find inbox by name');
const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName);
const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName.split('-cwId-')[0]);
if (!findByName) {
this.logger.warn('inbox not found');
@ -996,39 +1010,6 @@ export class ChatwootService {
await waInstance?.client?.logout('Log out instance: ' + instance.instanceName);
await waInstance?.client?.ws?.close();
}
if (command.includes('new_instance')) {
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY;
const data = {
instanceName: command.split(':')[1],
qrcode: true,
chatwoot_account_id: this.provider.account_id,
chatwoot_token: this.provider.token,
chatwoot_url: this.provider.url,
chatwoot_sign_msg: this.provider.sign_msg,
chatwoot_reopen_conversation: this.provider.reopen_conversation,
chatwoot_conversation_pending: this.provider.conversation_pending,
};
if (command.split(':')[2]) {
data['number'] = command.split(':')[2];
}
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${urlServer}/instance/create`,
headers: {
'Content-Type': 'application/json',
apikey: apiKey,
},
data: data,
};
await axios.request(config);
}
}
if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') {
@ -1055,7 +1036,7 @@ export class ChatwootService {
if (senderName === null || senderName === undefined) {
formatText = messageReceived;
} else {
formatText = this.provider.sign_msg ? `*${senderName}:*\n\n${messageReceived}` : messageReceived;
formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived;
}
for (const message of body.conversation.messages) {
@ -1342,7 +1323,7 @@ export class ChatwootService {
if (!body.key.fromMe) {
this.logger.verbose('message is not from me');
content = `**${participantName}**\n\n${bodyMessage}`;
content = `**${participantName}:**\n\n${bodyMessage}`;
} else {
this.logger.verbose('message is from me');
content = `${bodyMessage}`;
@ -1478,7 +1459,7 @@ export class ChatwootService {
this.logger.verbose('event qrcode.updated');
if (body.statusCode === 500) {
this.logger.verbose('qrcode error');
const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the /init message again.`;
const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.`;
this.logger.verbose('send message to chatwoot');
return await this.createBotMessage(instance, erroQRcode, 'incoming');
@ -1515,50 +1496,4 @@ export class ChatwootService {
this.logger.error(error);
}
}
public async newInstance(data: any) {
try {
const instanceName = data.instanceName;
const qrcode = true;
const number = data.number;
const accountId = data.accountId;
const chatwootToken = data.token;
const chatwootUrl = data.url;
const signMsg = true;
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY;
const requestData = {
instanceName,
qrcode,
chatwoot_account_id: accountId,
chatwoot_token: chatwootToken,
chatwoot_url: chatwootUrl,
chatwoot_sign_msg: signMsg,
};
if (number) {
requestData['number'] = number;
}
// eslint-disable-next-line
const config = {
method: 'post',
maxBodyLength: Infinity,
url: `${urlServer}/instance/create`,
headers: {
'Content-Type': 'application/json',
apikey: apiKey,
},
data: requestData,
};
// await axios.request(config);
return true;
} catch (error) {
this.logger.error(error);
return null;
}
}
}

View File

@ -7,9 +7,9 @@ import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { dbserver } from '../../db/db.connect';
import { RedisCache } from '../../db/redis.client';
import { NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import {
AuthModel,
ChatwootModel,
@ -94,7 +94,7 @@ export class WAMonitoringService {
if (findChatwoot && findChatwoot.enabled) {
chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${key}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
};
}
@ -161,8 +161,7 @@ export class WAMonitoringService {
});
this.logger.verbose('instance files deleted: ' + name);
});
// } else if (this.redis.ENABLED) {
} else {
} else if (!this.redis.ENABLED) {
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
if (dirent.isDirectory()) {
@ -200,6 +199,7 @@ export class WAMonitoringService {
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
this.cache.reference = instanceName;
await this.cache.delAll();
this.cache.disconnect();
return;
}
@ -263,6 +263,7 @@ export class WAMonitoringService {
} else {
this.logger.verbose('no instance keys found');
}
this.cache.disconnect();
return;
}
@ -335,11 +336,14 @@ export class WAMonitoringService {
this.logger.verbose('checking instances without connection');
this.eventEmitter.on('no.connection', async (instanceName) => {
try {
this.logger.verbose('instance: ' + instanceName + ' - removing from memory');
this.waInstances[instanceName] = undefined;
this.logger.verbose('logging out instance: ' + instanceName);
await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
this.logger.verbose('request cleaning up instance: ' + instanceName);
this.cleaningUp(instanceName);
this.logger.verbose('close connection instance: ' + instanceName);
this.waInstances[instanceName]?.client?.ws?.close();
this.waInstances[instanceName].instance.qrcode = { count: 0 };
this.waInstances[instanceName].stateConnection.state = 'close';
} catch (error) {
this.logger.error({
localError: 'noConnection',

View File

@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto';
import { ProxyRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class ProxyService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(ProxyService.name);
public create(instance: InstanceDto, data: ProxyDto) {
this.logger.verbose('create proxy: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setProxy(data);
return { proxy: { ...instance, proxy: data } };
}
public async find(instance: InstanceDto): Promise<ProxyRaw> {
try {
this.logger.verbose('find proxy: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findProxy();
if (Object.keys(result).length === 0) {
throw new Error('Proxy not found');
}
return result;
} catch (error) {
return { enabled: false, proxy: '' };
}
}
}

View File

@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class RabbitmqService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(RabbitmqService.name);
public create(instance: InstanceDto, data: RabbitmqDto) {
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
return { rabbitmq: { ...instance, rabbitmq: data } };
}
public async find(instance: InstanceDto): Promise<RabbitmqRaw> {
try {
this.logger.verbose('find rabbitmq: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq();
if (Object.keys(result).length === 0) {
throw new Error('Rabbitmq not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

View File

@ -0,0 +1,421 @@
import axios from 'axios';
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { Session, TypebotDto } from '../dto/typebot.dto';
import { MessageRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class TypebotService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(TypebotService.name);
public create(instance: InstanceDto, data: TypebotDto) {
this.logger.verbose('create typebot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setTypebot(data);
return { typebot: { ...instance, typebot: data } };
}
public async find(instance: InstanceDto): Promise<TypebotDto> {
try {
this.logger.verbose('find typebot: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot();
if (Object.keys(result).length === 0) {
throw new Error('Typebot not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] };
}
}
public async changeStatus(instance: InstanceDto, data: any) {
const remoteJid = data.remoteJid;
const status = data.status;
const findData = await this.find(instance);
const session = findData.sessions.find((session) => session.remoteJid === remoteJid);
if (session) {
if (status === 'closed') {
findData.sessions.splice(findData.sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
sessions: findData.sessions,
};
this.create(instance, typebotData);
return { typebot: { ...instance, typebot: typebotData } };
}
findData.sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.status = status;
}
});
}
const typebotData = {
enabled: true,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
sessions: findData.sessions,
};
this.create(instance, typebotData);
return { typebot: { ...instance, typebot: typebotData } };
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
};
this.logger.verbose('type message: ' + types);
return types;
}
private getMessageContent(types: any) {
this.logger.verbose('get message content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
const result = typeKey ? types[typeKey] : undefined;
this.logger.verbose('message content: ' + result);
return result;
}
private getConversationMessage(msg: any) {
this.logger.verbose('get conversation message');
const types = this.getTypeMessage(msg);
const messageContent = this.getMessageContent(types);
this.logger.verbose('conversation message: ' + messageContent);
return messageContent;
}
public async createNewSession(instance: InstanceDto, data: any) {
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
sessionId: id,
startParams: {
typebot: data.typebot,
prefilledVariables: {
remoteJid: data.remoteJid,
pushName: data.pushName,
instanceName: instance.instanceName,
},
},
};
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
if (request.data.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`,
status: 'opened',
createdAt: Date.now(),
updateAt: Date.now(),
});
const typebotData = {
enabled: true,
url: data.url,
typebot: data.typebot,
expire: data.expire,
keyword_finish: data.keyword_finish,
delay_message: data.delay_message,
unknown_message: data.unknown_message,
sessions: data.sessions,
};
this.create(instance, typebotData);
}
return request.data;
}
public async sendWAMessage(instance: InstanceDto, remoteJid: string, messages: any[], input: any[]) {
processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input).catch((err) => {
console.error('Erro ao processar mensagens:', err);
});
async function processMessages(instance, messages, input) {
for (const message of messages) {
if (message.type === 'text') {
let formattedText = '';
let linkPreview = false;
for (const richText of message.content.richText) {
for (const element of richText.children) {
let text = '';
if (element.text) {
text = element.text;
}
if (element.bold) {
text = `*${text}*`;
}
if (element.italic) {
text = `_${text}_`;
}
if (element.underline) {
text = `~${text}~`;
}
if (element.url) {
const linkText = element.children[0].text;
text = `[${linkText}](${element.url})`;
linkPreview = true;
}
formattedText += text;
}
formattedText += '\n';
}
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
linkPreview: linkPreview,
},
textMessage: {
text: formattedText,
},
});
}
if (message.type === 'image') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'image',
media: message.content.url,
},
});
}
if (message.type === 'video') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'video',
media: message.content.url,
},
});
}
if (message.type === 'audio') {
await instance.audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: message.content.url,
},
});
}
}
if (input) {
if (input.type === 'choice input') {
let formattedText = '';
const items = input.items;
for (const item of items) {
formattedText += `▶️ ${item.content}\n`;
}
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: 1200,
presence: 'composing',
linkPreview: false,
},
textMessage: {
text: formattedText,
},
});
}
}
}
}
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
const url = (await this.find(instance)).url;
const typebot = (await this.find(instance)).typebot;
const sessions = ((await this.find(instance)).sessions as Session[]) ?? [];
const expire = (await this.find(instance)).expire;
const keyword_finish = (await this.find(instance)).keyword_finish;
const delay_message = (await this.find(instance)).delay_message;
const unknown_message = (await this.find(instance)).unknown_message;
const session = sessions.find((session) => session.remoteJid === remoteJid);
if (session && expire && expire > 0) {
const now = Date.now();
const diff = now - session.updateAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) {
sessions.splice(sessions.indexOf(session), 1);
const data = await this.createNewSession(instance, {
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input);
return;
}
}
if (session && session.status !== 'opened') {
return;
}
if (!session) {
const data = await this.createNewSession(instance, {
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input);
return;
}
sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.updateAt = Date.now();
}
});
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
sessions,
};
this.create(instance, typebotData);
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
sessions,
};
this.create(instance, typebotData);
return;
}
const reqData = {
message: content,
sessionId: session.sessionId.split('-')[1],
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
await this.sendWAMessage(instance, remoteJid, request.data.messages, request.data.input);
return;
}
}

View File

@ -0,0 +1,33 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class WebsocketService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebsocketService.name);
public create(instance: InstanceDto, data: WebsocketDto) {
this.logger.verbose('create websocket: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebsocket(data);
return { websocket: { ...instance, websocket: data } };
}
public async find(instance: InstanceDto): Promise<WebsocketRaw> {
try {
this.logger.verbose('find websocket: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket();
if (Object.keys(result).length === 0) {
throw new Error('Websocket not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

View File

@ -44,6 +44,7 @@ import { getMIMEType } from 'node-mime-types';
import { release } from 'os';
import { join } from 'path';
import P from 'pino';
import { ProxyAgent } from 'proxy-agent';
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
import qrcodeTerminal from 'qrcode-terminal';
import sharp from 'sharp';
@ -60,18 +61,22 @@ import {
QrCode,
Redis,
Webhook,
Websocket,
} from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config';
import { dbserver } from '../../db/db.connect';
import { RedisCache } from '../../db/redis.client';
import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions';
import { getAMQP } from '../../libs/amqp.server';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import { getIO } from '../../libs/socket.server';
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
import {
ArchiveChatDto,
DeleteMessage,
getBase64FromMediaMessageDto,
LastMessage,
NumberBusiness,
OnWhatsAppDto,
PrivacySettingDto,
@ -108,12 +113,13 @@ import {
SendTextDto,
StatusMessage,
} from '../dto/sendMessage.dto';
import { SettingsRaw } from '../models';
import { ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
import { ChatRaw } from '../models/chat.model';
import { ChatwootRaw } from '../models/chatwoot.model';
import { ContactRaw } from '../models/contact.model';
import { MessageRaw, MessageUpdateRaw } from '../models/message.model';
import { WebhookRaw } from '../models/webhook.model';
import { WebsocketRaw } from '../models/websocket.model';
import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository';
import { MessageUpQuery } from '../repository/messageUp.repository';
@ -121,7 +127,8 @@ import { RepositoryBroker } from '../repository/repository.manager';
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types';
import { waMonitor } from '../whatsapp.module';
import { ChatwootService } from './chatwoot.service';
import { SocksProxyAgent } from 'socks-proxy-agent';
//import { SocksProxyAgent } from './socks-proxy-agent';
import { TypebotService } from './typebot.service';
export class WAStartupService {
constructor(
@ -136,12 +143,16 @@ export class WAStartupService {
}
private readonly logger = new Logger(WAStartupService.name);
private readonly instance: wa.Instance = {};
public readonly instance: wa.Instance = {};
public client: WASocket;
private readonly localWebhook: wa.LocalWebHook = {};
private readonly localChatwoot: wa.LocalChatwoot = {};
private readonly localSettings: wa.LocalSettings = {};
private stateConnection: wa.StateConnection = { state: 'close' };
private readonly localWebsocket: wa.LocalWebsocket = {};
private readonly localRabbitmq: wa.LocalRabbitmq = {};
public readonly localTypebot: wa.LocalTypebot = {};
private readonly localProxy: wa.LocalProxy = {};
public stateConnection: wa.StateConnection = { state: 'close' };
public readonly storePath = join(ROOT_DIR, 'store');
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
private readonly userDevicesCache: CacheStore = new NodeCache();
@ -152,6 +163,8 @@ export class WAStartupService {
private chatwootService = new ChatwootService(waMonitor, this.configService);
private typebotService = new TypebotService(waMonitor);
public set instanceName(name: string) {
this.logger.verbose(`Initializing instance '${name}'`);
if (!name) {
@ -410,9 +423,168 @@ export class WAStartupService {
return data;
}
private async loadWebsocket() {
this.logger.verbose('Loading websocket');
const data = await this.repository.websocket.find(this.instanceName);
this.localWebsocket.enabled = data?.enabled;
this.logger.verbose(`Websocket enabled: ${this.localWebsocket.enabled}`);
this.localWebsocket.events = data?.events;
this.logger.verbose(`Websocket events: ${this.localWebsocket.events}`);
this.logger.verbose('Websocket loaded');
}
public async setWebsocket(data: WebsocketRaw) {
this.logger.verbose('Setting websocket');
await this.repository.websocket.create(data, this.instanceName);
this.logger.verbose(`Websocket events: ${data.events}`);
Object.assign(this.localWebsocket, data);
this.logger.verbose('Websocket set');
}
public async findWebsocket() {
this.logger.verbose('Finding websocket');
const data = await this.repository.websocket.find(this.instanceName);
if (!data) {
this.logger.verbose('Websocket not found');
throw new NotFoundException('Websocket not found');
}
this.logger.verbose(`Websocket events: ${data.events}`);
return data;
}
private async loadRabbitmq() {
this.logger.verbose('Loading rabbitmq');
const data = await this.repository.rabbitmq.find(this.instanceName);
this.localRabbitmq.enabled = data?.enabled;
this.logger.verbose(`Rabbitmq enabled: ${this.localRabbitmq.enabled}`);
this.localRabbitmq.events = data?.events;
this.logger.verbose(`Rabbitmq events: ${this.localRabbitmq.events}`);
this.logger.verbose('Rabbitmq loaded');
}
public async setRabbitmq(data: RabbitmqRaw) {
this.logger.verbose('Setting rabbitmq');
await this.repository.rabbitmq.create(data, this.instanceName);
this.logger.verbose(`Rabbitmq events: ${data.events}`);
Object.assign(this.localRabbitmq, data);
this.logger.verbose('Rabbitmq set');
}
public async findRabbitmq() {
this.logger.verbose('Finding rabbitmq');
const data = await this.repository.rabbitmq.find(this.instanceName);
if (!data) {
this.logger.verbose('Rabbitmq not found');
throw new NotFoundException('Rabbitmq not found');
}
this.logger.verbose(`Rabbitmq events: ${data.events}`);
return data;
}
private async loadTypebot() {
this.logger.verbose('Loading typebot');
const data = await this.repository.typebot.find(this.instanceName);
this.localTypebot.enabled = data?.enabled;
this.logger.verbose(`Typebot enabled: ${this.localTypebot.enabled}`);
this.localTypebot.url = data?.url;
this.logger.verbose(`Typebot url: ${this.localTypebot.url}`);
this.localTypebot.typebot = data?.typebot;
this.logger.verbose(`Typebot typebot: ${this.localTypebot.typebot}`);
this.localTypebot.expire = data?.expire;
this.logger.verbose(`Typebot expire: ${this.localTypebot.expire}`);
this.localTypebot.keyword_finish = data?.keyword_finish;
this.logger.verbose(`Typebot keyword_finish: ${this.localTypebot.keyword_finish}`);
this.localTypebot.delay_message = data?.delay_message;
this.logger.verbose(`Typebot delay_message: ${this.localTypebot.delay_message}`);
this.localTypebot.unknown_message = data?.unknown_message;
this.logger.verbose(`Typebot unknown_message: ${this.localTypebot.unknown_message}`);
this.localTypebot.sessions = data?.sessions;
this.logger.verbose('Typebot loaded');
}
public async setTypebot(data: TypebotRaw) {
this.logger.verbose('Setting typebot');
await this.repository.typebot.create(data, this.instanceName);
this.logger.verbose(`Typebot typebot: ${data.typebot}`);
this.logger.verbose(`Typebot expire: ${data.expire}`);
this.logger.verbose(`Typebot keyword_finish: ${data.keyword_finish}`);
this.logger.verbose(`Typebot delay_message: ${data.delay_message}`);
this.logger.verbose(`Typebot unknown_message: ${data.unknown_message}`);
Object.assign(this.localTypebot, data);
this.logger.verbose('Typebot set');
}
public async findTypebot() {
this.logger.verbose('Finding typebot');
const data = await this.repository.typebot.find(this.instanceName);
if (!data) {
this.logger.verbose('Typebot not found');
throw new NotFoundException('Typebot not found');
}
return data;
}
private async loadProxy() {
this.logger.verbose('Loading proxy');
const data = await this.repository.proxy.find(this.instanceName);
this.localProxy.enabled = data?.enabled;
this.logger.verbose(`Proxy enabled: ${this.localProxy.enabled}`);
this.localProxy.proxy = data?.proxy;
this.logger.verbose(`Proxy proxy: ${this.localProxy.proxy}`);
this.logger.verbose('Proxy loaded');
}
public async setProxy(data: ProxyRaw) {
this.logger.verbose('Setting proxy');
await this.repository.proxy.create(data, this.instanceName);
this.logger.verbose(`Proxy proxy: ${data.proxy}`);
Object.assign(this.localProxy, data);
this.logger.verbose('Proxy set');
this.client?.ws?.close();
}
public async findProxy() {
this.logger.verbose('Finding proxy');
const data = await this.repository.proxy.find(this.instanceName);
if (!data) {
this.logger.verbose('Proxy not found');
throw new NotFoundException('Proxy not found');
}
return data;
}
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
const webhookLocal = this.localWebhook.events;
const websocketLocal = this.localWebsocket.events;
const rabbitmqLocal = this.localRabbitmq.events;
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
const we = event.replace(/[.-]/gm, '_').toUpperCase();
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
@ -421,6 +593,59 @@ export class WAStartupService {
const tokenStore = await this.repository.auth.find(this.instanceName);
const instanceApikey = tokenStore?.apikey || 'Apikey not found';
if (this.localRabbitmq.enabled) {
const amqp = getAMQP();
if (amqp) {
if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) {
const exchangeName = 'evolution_exchange';
amqp.assertExchange(exchangeName, 'topic', { durable: false });
const queueName = `${this.instanceName}.${event}`;
amqp.assertQueue(queueName, { durable: false });
amqp.bindQueue(queueName, exchangeName, event);
const message = {
event,
instance: this.instance.name,
data,
server_url: serverUrl,
};
if (expose && instanceApikey) {
message['apikey'] = instanceApikey;
}
amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
}
}
}
if (this.configService.get<Websocket>('WEBSOCKET').ENABLED && this.localWebsocket.enabled) {
this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name);
if (Array.isArray(websocketLocal) && websocketLocal.includes(we)) {
this.logger.verbose('Sending data to websocket on event: ' + event);
const io = getIO();
const message = {
event,
instance: this.instance.name,
data,
server_url: serverUrl,
};
if (expose && instanceApikey) {
message['apikey'] = instanceApikey;
}
this.logger.verbose('Sending data to socket.io in channel: ' + this.instance.name);
io.of(`/${this.instance.name}`).emit(event, message);
}
}
const globalApiKey = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
if (local) {
@ -586,23 +811,6 @@ export class WAStartupService {
statusReason: DisconnectReason.connectionClosed,
});
this.logger.verbose('Sending data to webhook in event STATUS_INSTANCE');
this.sendDataWebhook(Events.STATUS_INSTANCE, {
instance: this.instance.name,
status: 'removed',
});
if (this.localChatwoot.enabled) {
this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE,
{ instanceName: this.instance.name },
{
instance: this.instance.name,
status: 'removed',
},
);
}
this.logger.verbose('endSession defined as true');
this.endSession = true;
@ -613,11 +821,13 @@ export class WAStartupService {
this.logger.verbose('Incrementing QR code count');
this.instance.qrcode.count++;
const color = this.configService.get<QrCode>('QRCODE').COLOR;
const optsQrcode: QRCodeToDataURLOptions = {
margin: 3,
scale: 4,
errorCorrectionLevel: 'H',
color: { light: '#ffffff', dark: '#198754' },
color: { light: '#ffffff', dark: color },
};
if (this.phoneNumber) {
@ -828,6 +1038,10 @@ export class WAStartupService {
this.loadWebhook();
this.loadChatwoot();
this.loadSettings();
this.loadWebsocket();
this.loadRabbitmq();
this.loadTypebot();
this.loadProxy();
this.instance.authState = await this.defineAuthState();
@ -837,7 +1051,18 @@ export class WAStartupService {
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
this.logger.verbose('Browser: ' + JSON.stringify(browser));
let options;
if (this.localProxy.enabled) {
this.logger.verbose('Proxy enabled');
options = {
agent: new ProxyAgent(this.localProxy.proxy as any),
fetchAgent: new ProxyAgent(this.localProxy.proxy as any),
};
}
const socketConfig: UserFacingSocketConfig = {
...options,
auth: {
creds: this.instance.authState.state.creds,
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' })),
@ -875,7 +1100,6 @@ export class WAStartupService {
return message;
},
// agent: new SocksProxyAgent('socks5://192.168.1.4:1080'),
};
this.endSession = false;
@ -1136,6 +1360,14 @@ export class WAStartupService {
);
}
if (this.localTypebot.enabled && messageRaw.key.remoteJid.includes('@s.whatsapp.net')) {
await this.typebotService.sendTypebot(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
);
}
this.logger.verbose('Inserting message in database');
await this.repository.message.insert([messageRaw], this.instance.name, database.SAVE_DATA.NEW_MESSAGE);
@ -1947,6 +2179,14 @@ export class WAStartupService {
this.logger.verbose('File name: ' + mediaMessage.fileName);
}
if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) {
mediaMessage.fileName = 'image.png';
}
if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
if (isURL(mediaMessage.media)) {
@ -2348,20 +2588,55 @@ export class WAStartupService {
}
}
public async getLastMessage(number: string) {
const messages = await this.fetchMessages({
where: {
key: {
remoteJid: number,
},
owner: this.instance.name,
},
});
let lastMessage = messages.pop();
for (const message of messages) {
if (message.messageTimestamp >= lastMessage.messageTimestamp) {
lastMessage = message;
}
}
return lastMessage as unknown as LastMessage;
}
public async archiveChat(data: ArchiveChatDto) {
this.logger.verbose('Archiving chat');
try {
data.lastMessage.messageTimestamp = data.lastMessage?.messageTimestamp ?? Date.now();
let last_message = data.lastMessage;
let number = data.chat;
if (!last_message && number) {
last_message = await this.getLastMessage(number);
} else {
last_message = data.lastMessage;
last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now();
number = last_message?.key?.remoteJid;
}
if (!last_message || Object.keys(last_message).length === 0) {
throw new NotFoundException('Last message not found');
}
await this.client.chatModify(
{
archive: data.archive,
lastMessages: [data.lastMessage],
lastMessages: [last_message],
},
data.lastMessage.key.remoteJid,
this.createJid(number),
);
return {
chatId: data.lastMessage.key.remoteJid,
chatId: number,
archived: true,
};
} catch (error) {

View File

@ -70,6 +70,38 @@ export declare namespace wa {
read_status?: boolean;
};
export type LocalWebsocket = {
enabled?: boolean;
events?: string[];
};
export type LocalRabbitmq = {
enabled?: boolean;
events?: string[];
};
type Session = {
remoteJid?: string;
sessionId?: string;
createdAt?: number;
};
export type LocalTypebot = {
enabled?: boolean;
url?: string;
typebot?: string;
expire?: number;
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
sessions?: Session[];
};
export type LocalProxy = {
enabled?: boolean;
proxy?: string;
};
export type StateConnection = {
instance?: string;
state?: WAConnectionState | 'refused';

View File

@ -1,16 +1,20 @@
import { configService } from '../config/env.config';
import { eventEmitter } from '../config/event.config';
import { Logger } from '../config/logger.config';
import { dbserver } from '../db/db.connect';
import { RedisCache } from '../db/redis.client';
import { dbserver } from '../libs/db.connect';
import { RedisCache } from '../libs/redis.client';
import { ChatController } from './controllers/chat.controller';
import { ChatwootController } from './controllers/chatwoot.controller';
import { GroupController } from './controllers/group.controller';
import { InstanceController } from './controllers/instance.controller';
import { ProxyController } from './controllers/proxy.controller';
import { RabbitmqController } from './controllers/rabbitmq.controller';
import { SendMessageController } from './controllers/sendMessage.controller';
import { SettingsController } from './controllers/settings.controller';
import { TypebotController } from './controllers/typebot.controller';
import { ViewsController } from './controllers/views.controller';
import { WebhookController } from './controllers/webhook.controller';
import { WebsocketController } from './controllers/websocket.controller';
import {
AuthModel,
ChatModel,
@ -18,8 +22,12 @@ import {
ContactModel,
MessageModel,
MessageUpModel,
ProxyModel,
RabbitmqModel,
SettingsModel,
TypebotModel,
WebhookModel,
WebsocketModel,
} from './models';
import { AuthRepository } from './repository/auth.repository';
import { ChatRepository } from './repository/chat.repository';
@ -27,14 +35,22 @@ import { ChatwootRepository } from './repository/chatwoot.repository';
import { ContactRepository } from './repository/contact.repository';
import { MessageRepository } from './repository/message.repository';
import { MessageUpRepository } from './repository/messageUp.repository';
import { ProxyRepository } from './repository/proxy.repository';
import { RabbitmqRepository } from './repository/rabbitmq.repository';
import { RepositoryBroker } from './repository/repository.manager';
import { SettingsRepository } from './repository/settings.repository';
import { TypebotRepository } from './repository/typebot.repository';
import { WebhookRepository } from './repository/webhook.repository';
import { WebsocketRepository } from './repository/websocket.repository';
import { AuthService } from './services/auth.service';
import { ChatwootService } from './services/chatwoot.service';
import { WAMonitoringService } from './services/monitor.service';
import { ProxyService } from './services/proxy.service';
import { RabbitmqService } from './services/rabbitmq.service';
import { SettingsService } from './services/settings.service';
import { TypebotService } from './services/typebot.service';
import { WebhookService } from './services/webhook.service';
import { WebsocketService } from './services/websocket.service';
const logger = new Logger('WA MODULE');
@ -42,7 +58,11 @@ const messageRepository = new MessageRepository(MessageModel, configService);
const chatRepository = new ChatRepository(ChatModel, configService);
const contactRepository = new ContactRepository(ContactModel, configService);
const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService);
const typebotRepository = new TypebotRepository(TypebotModel, configService);
const webhookRepository = new WebhookRepository(WebhookModel, configService);
const websocketRepository = new WebsocketRepository(WebsocketModel, configService);
const proxyRepository = new ProxyRepository(ProxyModel, configService);
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
const settingsRepository = new SettingsRepository(SettingsModel, configService);
const authRepository = new AuthRepository(AuthModel, configService);
@ -55,6 +75,10 @@ export const repository = new RepositoryBroker(
webhookRepository,
chatwootRepository,
settingsRepository,
websocketRepository,
rabbitmqRepository,
typebotRepository,
proxyRepository,
authRepository,
configService,
dbserver?.getClient(),
@ -66,10 +90,26 @@ export const waMonitor = new WAMonitoringService(eventEmitter, configService, re
const authService = new AuthService(configService, waMonitor, repository);
const typebotService = new TypebotService(waMonitor);
export const typebotController = new TypebotController(typebotService);
const webhookService = new WebhookService(waMonitor);
export const webhookController = new WebhookController(webhookService);
const websocketService = new WebsocketService(waMonitor);
export const websocketController = new WebsocketController(websocketService);
const proxyService = new ProxyService(waMonitor);
export const proxyController = new ProxyController(proxyService);
const rabbitmqService = new RabbitmqService(waMonitor);
export const rabbitmqController = new RabbitmqController(rabbitmqService);
const chatwootService = new ChatwootService(waMonitor, configService);
export const chatwootController = new ChatwootController(chatwootService, configService);
@ -87,6 +127,9 @@ export const instanceController = new InstanceController(
webhookService,
chatwootService,
settingsService,
websocketService,
rabbitmqService,
typebotService,
cache,
);
export const viewsController = new ViewsController(waMonitor, configService);

18
views/manager.hbs Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="https://evolution-api.com/files/evolution-api-favicon.png" type="image/x-icon">
<title>Instance Manager</title>
</head>
<body>
<iframe src="https://app.smith.dgcode.com.br/app/evolutionapi-public/home-64ca60783615e270291978b4?embed=true" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
</body>
</html>

View File

@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" 'unsafe-inline' crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
<link rel="shortcut icon" href="/images/atendai-logo.png" type="image/x-icon">
<style>
code {
padding: 10px;
background-color: whitesmoke;
color: rgb(104, 104, 104);
}
</style>
<title>Generate QRCode</title>
</head>
<body>
<div id="root" class="container mt-5 mb-5 w-50">
<div id="display-qrcode">
<h2 class="mb-3 text-secondary">Connect to whatsapp</h2>
<div class="input-group mb-3">
<input id="input-auth" type="text" class="form-control" aria-describedby="btn-qr-g" value="" placeholder="{{type}}">
<input id="input-session" type="text" class="form-control" aria-describedby="btn-qr-g" disabled
value="{{instanceName}}">
<button id="gen-qrcode" class="btn btn-info text-light" type="submit">Generate qrcode</button>
</div>
<div id="qrcode-img"></div>
</div>
<hr>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script type="module">
const log = (...data) => console.log(data);
$('#gen-qrcode').click(() => {
const keyAuth = '{{type}}' === 'jwt'? 'authorization': 'apikey';
const valueAuth = '{{type}}' === 'jwt'? `Bearer ${$('#input-auth').val()}`: $('#input-auth').val()
$.ajax({
url: `/instance/connect/{{instanceName}}`,
headers: { [keyAuth]: valueAuth },
type: 'GET',
success: (qrcode, status) => {
$(`#update-qrcode`).remove();
$('#qrcode-img')
.append(
`<div id="update-qrcode" class="card mb-2">
<h5 class="card-title text-center text-secondary mt-3">{{name}}</h5>
<div class="card-body container d-flex justify-content-center">
<img class="img-thumbnail mt-2 w-50" alt="qrcode.png"
src="${qrcode.base64}">
</div>
<div class="card-body">
<code class="text-card d-block">${JSON.stringify({ code: qrcode.code })}</code>
</div>
</div>`
);
return;
},
error: (error) => console.log(error),
});
});
</script>
</body>
</html>