├── .DS_Store
├── .dockerignore
├── .env.example
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── -en--bug-report.yaml
│ ├── -en--feature-request.yaml
│ ├── -pt--reportar-bug.yaml
│ └── -pt--solicitar-recurso.yaml
└── workflows
│ ├── check_code_quality.yml
│ ├── publish_docker_image.yml
│ ├── publish_docker_image_homolog.yml
│ └── publish_docker_image_latest.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── CHANGELOG.md
├── Docker
├── minio
│ └── docker-compose.yaml
├── mysql
│ └── docker-compose.yaml
├── postgres
│ └── docker-compose.yaml
├── rabbitmq
│ └── docker-compose.yaml
├── redis
│ └── docker-compose.yaml
├── scripts
│ ├── deploy_database.sh
│ ├── env_functions.sh
│ └── generate_database.sh
└── swarm
│ └── evolution_api_v2.yaml
├── Dockerfile
├── Extras
└── chatwoot
│ ├── configurar_admin.json
│ ├── criador_de_empresas.json
│ └── criador_de_inbox.json
├── LICENSE
├── README.md
├── docker-compose.dev.yaml
├── docker-compose.yaml
├── local_install.sh
├── manager
└── dist
│ ├── assets
│ ├── images
│ │ └── evolution-logo.png
│ ├── index-CFAZX6IV.js
│ └── index-DNOCacL_.css
│ └── index.html
├── package-lock.json
├── package.json
├── prisma
├── mysql-migrations
│ ├── 20240809105427_init
│ │ └── migration.sql
│ ├── 20240813153900_add_unique_index_for_remoted_jid_and_instance_in_contacts
│ │ └── migration.sql
│ ├── 20240814173138_add_ignore_jids_chatwoot
│ │ └── migration.sql
│ ├── 20240814214314_integrations_unification
│ │ └── migration.sql
│ ├── 20240821203259_add_postgres_migrations
│ │ └── migration.sql
│ ├── 20240824162012_add_type_on_integration_sessions
│ │ └── migration.sql
│ ├── 20240825131301_change_to_evolution_bot
│ │ └── migration.sql
│ ├── 20241001172800_add_message_status
│ │ └── migration.sql
│ ├── 20241108101333_fix_message_status_as_string
│ │ └── migration.sql
│ └── migration_lock.toml
├── mysql-schema.prisma
├── postgresql-migrations
│ ├── 20240609181238_init
│ │ └── migration.sql
│ ├── 20240610144159_create_column_profile_name_instance
│ │ └── migration.sql
│ ├── 20240611125754_create_columns_whitelabel_chatwoot
│ │ └── migration.sql
│ ├── 20240611202817_create_columns_debounce_time_typebot
│ │ └── migration.sql
│ ├── 20240712144948_add_business_id_column_to_instances
│ │ └── migration.sql
│ ├── 20240712150256_create_templates_table
│ │ └── migration.sql
│ ├── 20240712155950_adjusts_in_templates_table
│ │ └── migration.sql
│ ├── 20240712162206_remove_templates_table
│ │ └── migration.sql
│ ├── 20240712223655_column_fallback_typebot
│ │ └── migration.sql
│ ├── 20240712230631_column_ignore_jids_typebot
│ │ └── migration.sql
│ ├── 20240713184337_add_media_table
│ │ └── migration.sql
│ ├── 20240718121437_add_openai_tables
│ │ └── migration.sql
│ ├── 20240718123923_adjusts_openai_tables
│ │ └── migration.sql
│ ├── 20240722173259_add_name_column_to_openai_creds
│ │ └── migration.sql
│ ├── 20240722173518_add_name_column_to_openai_creds
│ │ └── migration.sql
│ ├── 20240723152648_adjusts_in_column_openai_creds
│ │ └── migration.sql
│ ├── 20240723200254_add_webhookurl_on_message
│ │ └── migration.sql
│ ├── 20240725184147_create_template_table
│ │ └── migration.sql
│ ├── 20240725202651_add_webhook_url_template_table
│ │ └── migration.sql
│ ├── 20240725221646_modify_token_instance_table
│ │ └── migration.sql
│ ├── 20240729115127_modify_trigger_type_openai_typebot_table
│ │ └── migration.sql
│ ├── 20240729180347_modify_typebot_session_status_openai_typebot_table
│ │ └── migration.sql
│ ├── 20240730152156_create_dify_tables
│ │ └── migration.sql
│ ├── 20240801193907_add_column_speech_to_text_openai_setting_table
│ │ └── migration.sql
│ ├── 20240803163908_add_column_description_on_integrations_table
│ │ └── migration.sql
│ ├── 20240808210239_add_column_function_url_openaibot_table
│ │ └── migration.sql
│ ├── 20240811021156_add_chat_name_column
│ │ └── migration.sql
│ ├── 20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts
│ │ └── migration.sql
│ ├── 20240813003116_make_label_unique_for_instance
│ │ └── migration.sql
│ ├── 20240814173033_add_ignore_jids_chatwoot
│ │ └── migration.sql
│ ├── 20240814202359_integrations_unification
│ │ └── migration.sql
│ ├── 20240817110155_add_trigger_type_advanced
│ │ └── migration.sql
│ ├── 20240819154941_add_context_to_integration_session
│ │ └── migration.sql
│ ├── 20240821120816_bot_id_integration_session
│ │ └── migration.sql
│ ├── 20240821171327_add_generic_bot_table
│ │ └── migration.sql
│ ├── 20240821194524_add_flowise_table
│ │ └── migration.sql
│ ├── 20240824161333_add_type_on_integration_sessions
│ │ └── migration.sql
│ ├── 20240825130616_change_to_evolution_bot
│ │ └── migration.sql
│ ├── 20240828140837_add_is_on_whatsapp_table
│ │ └── migration.sql
│ ├── 20240828141556_remove_name_column_from_on_whatsapp_table
│ │ └── migration.sql
│ ├── 20240830193533_changed_table_case
│ │ └── migration.sql
│ ├── 20240906202019_add_headers_on_webhook_config
│ │ └── migration.sql
│ ├── 20241001180457_add_message_status
│ │ └── migration.sql
│ ├── 20241006130306_alter_status_on_message_table
│ │ └── migration.sql
│ ├── 20241007164026_add_unread_messages_on_chat_table
│ │ └── migration.sql
│ ├── 20241011085129_create_pusher_table
│ │ └── migration.sql
│ ├── 20241011100803_split_messages_and_time_per_char_integrations
│ │ └── migration.sql
│ ├── 20241017144950_create_index
│ │ └── migration.sql
│ ├── 20250116001415_add_wavoip_token_to_settings_table
│ │ └── migration.sql
│ └── migration_lock.toml
└── postgresql-schema.prisma
├── public
└── images
│ ├── atendai-logo.png
│ ├── bmc_qr.png
│ ├── code.png
│ ├── cover.png
│ ├── evolution-logo.png
│ ├── evolution-pro.png
│ ├── picpay-image.png
│ ├── picpay-qr.jpeg
│ ├── qrcode-pix.png
│ └── video-cover.png
├── runWithProvider.js
├── src
├── @types
│ └── express.d.ts
├── api
│ ├── abstract
│ │ ├── abstract.cache.ts
│ │ ├── abstract.repository.ts
│ │ └── abstract.router.ts
│ ├── controllers
│ │ ├── call.controller.ts
│ │ ├── chat.controller.ts
│ │ ├── group.controller.ts
│ │ ├── instance.controller.ts
│ │ ├── label.controller.ts
│ │ ├── proxy.controller.ts
│ │ ├── sendMessage.controller.ts
│ │ ├── settings.controller.ts
│ │ └── template.controller.ts
│ ├── dto
│ │ ├── call.dto.ts
│ │ ├── chat.dto.ts
│ │ ├── chatbot.dto.ts
│ │ ├── group.dto.ts
│ │ ├── instance.dto.ts
│ │ ├── label.dto.ts
│ │ ├── proxy.dto.ts
│ │ ├── sendMessage.dto.ts
│ │ ├── settings.dto.ts
│ │ └── template.dto.ts
│ ├── guards
│ │ ├── auth.guard.ts
│ │ ├── instance.guard.ts
│ │ └── telemetry.guard.ts
│ ├── integrations
│ │ ├── channel
│ │ │ ├── channel.controller.ts
│ │ │ ├── channel.router.ts
│ │ │ ├── evolution
│ │ │ │ ├── evolution.channel.service.ts
│ │ │ │ ├── evolution.controller.ts
│ │ │ │ └── evolution.router.ts
│ │ │ ├── meta
│ │ │ │ ├── meta.controller.ts
│ │ │ │ ├── meta.router.ts
│ │ │ │ └── whatsapp.business.service.ts
│ │ │ └── whatsapp
│ │ │ │ ├── baileys.controller.ts
│ │ │ │ ├── baileys.router.ts
│ │ │ │ ├── voiceCalls
│ │ │ │ ├── transport.type.ts
│ │ │ │ └── useVoiceCallsBaileys.ts
│ │ │ │ └── whatsapp.baileys.service.ts
│ │ ├── chatbot
│ │ │ ├── chatbot.controller.ts
│ │ │ ├── chatbot.router.ts
│ │ │ ├── chatbot.schema.ts
│ │ │ ├── chatwoot
│ │ │ │ ├── controllers
│ │ │ │ │ └── chatwoot.controller.ts
│ │ │ │ ├── dto
│ │ │ │ │ └── chatwoot.dto.ts
│ │ │ │ ├── libs
│ │ │ │ │ └── postgres.client.ts
│ │ │ │ ├── routes
│ │ │ │ │ └── chatwoot.router.ts
│ │ │ │ ├── services
│ │ │ │ │ └── chatwoot.service.ts
│ │ │ │ ├── utils
│ │ │ │ │ └── chatwoot-import-helper.ts
│ │ │ │ └── validate
│ │ │ │ │ └── chatwoot.schema.ts
│ │ │ ├── dify
│ │ │ │ ├── controllers
│ │ │ │ │ └── dify.controller.ts
│ │ │ │ ├── dto
│ │ │ │ │ └── dify.dto.ts
│ │ │ │ ├── routes
│ │ │ │ │ └── dify.router.ts
│ │ │ │ ├── services
│ │ │ │ │ └── dify.service.ts
│ │ │ │ └── validate
│ │ │ │ │ └── dify.schema.ts
│ │ │ ├── evolutionBot
│ │ │ │ ├── controllers
│ │ │ │ │ └── evolutionBot.controller.ts
│ │ │ │ ├── dto
│ │ │ │ │ └── evolutionBot.dto.ts
│ │ │ │ ├── routes
│ │ │ │ │ └── evolutionBot.router.ts
│ │ │ │ ├── services
│ │ │ │ │ └── evolutionBot.service.ts
│ │ │ │ └── validate
│ │ │ │ │ └── evolutionBot.schema.ts
│ │ │ ├── flowise
│ │ │ │ ├── controllers
│ │ │ │ │ └── flowise.controller.ts
│ │ │ │ ├── dto
│ │ │ │ │ └── flowise.dto.ts
│ │ │ │ ├── routes
│ │ │ │ │ └── flowise.router.ts
│ │ │ │ ├── services
│ │ │ │ │ └── flowise.service.ts
│ │ │ │ └── validate
│ │ │ │ │ └── flowise.schema.ts
│ │ │ ├── openai
│ │ │ │ ├── controllers
│ │ │ │ │ └── openai.controller.ts
│ │ │ │ ├── dto
│ │ │ │ │ └── openai.dto.ts
│ │ │ │ ├── routes
│ │ │ │ │ └── openai.router.ts
│ │ │ │ ├── services
│ │ │ │ │ └── openai.service.ts
│ │ │ │ └── validate
│ │ │ │ │ └── openai.schema.ts
│ │ │ └── typebot
│ │ │ │ ├── controllers
│ │ │ │ └── typebot.controller.ts
│ │ │ │ ├── dto
│ │ │ │ └── typebot.dto.ts
│ │ │ │ ├── routes
│ │ │ │ └── typebot.router.ts
│ │ │ │ ├── services
│ │ │ │ └── typebot.service.ts
│ │ │ │ └── validate
│ │ │ │ └── typebot.schema.ts
│ │ ├── event
│ │ │ ├── event.controller.ts
│ │ │ ├── event.dto.ts
│ │ │ ├── event.manager.ts
│ │ │ ├── event.router.ts
│ │ │ ├── event.schema.ts
│ │ │ ├── pusher
│ │ │ │ ├── pusher.controller.ts
│ │ │ │ ├── pusher.router.ts
│ │ │ │ └── pusher.schema.ts
│ │ │ ├── rabbitmq
│ │ │ │ ├── rabbitmq.controller.ts
│ │ │ │ └── rabbitmq.router.ts
│ │ │ ├── sqs
│ │ │ │ ├── sqs.controller.ts
│ │ │ │ └── sqs.router.ts
│ │ │ ├── webhook
│ │ │ │ ├── webhook.controller.ts
│ │ │ │ ├── webhook.router.ts
│ │ │ │ └── webhook.schema.ts
│ │ │ └── websocket
│ │ │ │ ├── websocket.controller.ts
│ │ │ │ └── websocket.router.ts
│ │ ├── integration.dto.ts
│ │ └── storage
│ │ │ ├── s3
│ │ │ ├── controllers
│ │ │ │ └── s3.controller.ts
│ │ │ ├── dto
│ │ │ │ └── media.dto.ts
│ │ │ ├── libs
│ │ │ │ └── minio.server.ts
│ │ │ ├── routes
│ │ │ │ └── s3.router.ts
│ │ │ ├── services
│ │ │ │ └── s3.service.ts
│ │ │ └── validate
│ │ │ │ └── s3.schema.ts
│ │ │ └── storage.router.ts
│ ├── provider
│ │ └── sessions.ts
│ ├── repository
│ │ └── repository.service.ts
│ ├── routes
│ │ ├── call.router.ts
│ │ ├── chat.router.ts
│ │ ├── group.router.ts
│ │ ├── index.router.ts
│ │ ├── instance.router.ts
│ │ ├── label.router.ts
│ │ ├── proxy.router.ts
│ │ ├── sendMessage.router.ts
│ │ ├── settings.router.ts
│ │ ├── template.router.ts
│ │ └── view.router.ts
│ ├── server.module.ts
│ ├── services
│ │ ├── auth.service.ts
│ │ ├── cache.service.ts
│ │ ├── channel.service.ts
│ │ ├── monitor.service.ts
│ │ ├── proxy.service.ts
│ │ ├── settings.service.ts
│ │ └── template.service.ts
│ └── types
│ │ └── wa.types.ts
├── cache
│ ├── cacheengine.ts
│ ├── localcache.ts
│ ├── rediscache.client.ts
│ └── rediscache.ts
├── config
│ ├── env.config.ts
│ ├── error.config.ts
│ ├── event.config.ts
│ ├── logger.config.ts
│ └── path.config.ts
├── exceptions
│ ├── 400.exception.ts
│ ├── 401.exception.ts
│ ├── 403.exception.ts
│ ├── 404.exception.ts
│ ├── 500.exception.ts
│ └── index.ts
├── main.ts
├── utils
│ ├── advancedOperatorsSearch.ts
│ ├── createJid.ts
│ ├── findBotByTrigger.ts
│ ├── getConversationMessage.ts
│ ├── i18n.ts
│ ├── instrumentSentry.ts
│ ├── makeProxyAgent.ts
│ ├── onWhatsappCache.ts
│ ├── renderStatus.ts
│ ├── sendTelemetry.ts
│ ├── server-up.ts
│ ├── translations
│ │ ├── en.json
│ │ ├── es.json
│ │ └── pt-BR.json
│ ├── use-multi-file-auth-state-prisma.ts
│ ├── use-multi-file-auth-state-provider-files.ts
│ └── use-multi-file-auth-state-redis-db.ts
└── validate
│ ├── chat.schema.ts
│ ├── group.schema.ts
│ ├── instance.schema.ts
│ ├── label.schema.ts
│ ├── message.schema.ts
│ ├── proxy.schema.ts
│ ├── settings.schema.ts
│ ├── template.schema.ts
│ └── validate.schema.ts
├── tsconfig.json
└── tsup.config.ts
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/.DS_Store
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
5 | dist
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /node-modules
2 | /dist
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | warnOnUnsupportedTypeScriptVersion: false,
8 | EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
9 | },
10 | plugins: ['@typescript-eslint', 'simple-import-sort', 'import'],
11 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
12 | globals: {
13 | Atomics: 'readonly',
14 | SharedArrayBuffer: 'readonly',
15 | },
16 | root: true,
17 | env: {
18 | node: true,
19 | jest: true,
20 | },
21 | ignorePatterns: ['.eslintrc.js'],
22 | rules: {
23 | '@typescript-eslint/interface-name-prefix': 'off',
24 | '@typescript-eslint/explicit-function-return-type': 'off',
25 | '@typescript-eslint/explicit-module-boundary-types': 'off',
26 | '@typescript-eslint/no-explicit-any': 'off',
27 | '@typescript-eslint/no-empty-function': 'off',
28 | '@typescript-eslint/no-non-null-assertion': 'off',
29 | '@typescript-eslint/no-unused-vars': 'error',
30 | 'import/first': 'error',
31 | 'import/no-duplicates': 'error',
32 | 'simple-import-sort/imports': 'error',
33 | 'simple-import-sort/exports': 'error',
34 | '@typescript-eslint/ban-types': [
35 | 'error',
36 | {
37 | extendDefaults: true,
38 | types: {
39 | '{}': false,
40 | Object: false,
41 | },
42 | },
43 | ],
44 | 'prettier/prettier': ['error', { endOfLine: 'auto' }],
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/-en--feature-request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest ideas for the project.
3 | labels:
4 | - enhancement
5 | - en
6 | # Automatically assign the issue to:
7 | # assignees: DavidsonGomes
8 | body:
9 | - type: checkboxes
10 | id: terms
11 | attributes:
12 | label: Welcome!
13 | description: '**DO NOT OPEN FOR GENERAL SUPPORT QUESTIONS.**'
14 |
15 | options:
16 | - label: Yes, I have searched for similar requests on [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) and found none.
17 | required: true
18 |
19 | - type: dropdown
20 | attributes:
21 | label: What type of feature?
22 | description: |
23 | How to write a good feature request?
24 |
25 | - Respect the issue template as much as possible.
26 | - The title should be short and descriptive.
27 | - Explain the conditions that led you to suggest this feature: the context.
28 | - The context should lead to something, an idea or problem you are facing.
29 | - Be clear and concise.
30 | - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
31 | options:
32 | - Integration
33 | - Functionality
34 | - Endpoint
35 | - Database adjustment
36 | - Other
37 | validations:
38 | required: true
39 |
40 | - type: textarea
41 | attributes:
42 | label: What is the motivation for the request?
43 | description: |
44 | What problem does the feature seek to solve?
45 | Clearly and in detail describe the functionality you want to be implemented.
46 | Explain how it fits into the context of the project.
47 | placeholder: Detailed description
48 | validations:
49 | required: true
50 |
51 | - type: textarea
52 | attributes:
53 | label: Usage Examples
54 | description: |
55 | Provide specific examples of how this functionality could be used.
56 | This can include scenarios or use cases where the feature would be particularly beneficial.
57 | placeholder: text - image - video - flowcharts
58 | validations:
59 | required: false
60 |
61 | - type: textarea
62 | attributes:
63 | label: How should the feature be developed?
64 | description: |
65 | Should it be inserted directly into the code?
66 | Should it be built as a different application that acts as an extension of the API?
67 | For example: a `worker`?
68 |
69 | Discuss how this new functionality could impact other parts of the project, if applicable.
70 |
71 | ---
72 |
73 | If you have ideas on how this functionality could be implemented, please share them here.
74 | This is not mandatory, but it can be helpful for the development team.
75 |
76 | placeholder: Insert feature ideas here
77 | validations:
78 | required: false
79 |
80 | - type: textarea
81 | attributes:
82 | label: Additional Notes
83 | description: Any other information you believe is relevant to your request.
84 | placeholder: Insert your observations here.
85 | validations:
86 | required: false
87 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/-pt--solicitar-recurso.yaml:
--------------------------------------------------------------------------------
1 | name: Solicitação de recursos
2 | description: Sugira ideias para o projeto.
3 | labels:
4 | - enhancement
5 | - pt-br
6 | # Automatically assign the issue to:
7 | # assignees: DavidsonGomes
8 | body:
9 | - type: checkboxes
10 | id: termos
11 | attributes:
12 | label: Bem-vindo!
13 | description: '**NÃO ABRA PARA PERGUNTAS GERAIS DE SUPORTE.**'
14 |
15 | options:
16 | - label: Sim, pesquisei solicitações semelhantes no [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) e não encontrei nenhum.
17 | required: true
18 |
19 | - type: dropdown
20 | attributes:
21 | label: Qual tipo de recurso?
22 | description: |
23 | Como escrever uma boa solicitação de bug?
24 |
25 | - Respeite o modelo de problema tanto quanto possível.
26 | - O título deve ser curto e descritivo.
27 | - Explique as condições que o levaram a reportar este problema: o contexto.
28 | - O contexto deve levar a algo, ideia ou problema que você está enfrentando.
29 | - Seja claro e conciso.
30 | - Formate suas mensagens para ajudar o leitor a se concentrar no que importa e entender a estrutura da sua mensagem, use [sintaxe Markdown](https://help.github.com/articles/github-flavored-markdown)
31 | options:
32 | - Integração
33 | - Funcionalidade
34 | - Endpoint
35 | - Ajuste de banco de dados
36 | - Outro
37 | validations:
38 | required: true
39 |
40 | - type: textarea
41 | attributes:
42 | label: Qual a motivação para a solicitação?
43 | description: |
44 | Qual problema o recurso busca resolver?
45 | Descreva claramente e em detalhes a funcionalidade que você deseja que seja implementada.
46 | Explique como isso se encaixa no contexto do projeto.
47 | placeholder: Descrição detalhada
48 | validations:
49 | required: true
50 |
51 | - type: textarea
52 | attributes:
53 | label: Exemplos de Uso
54 | description: |
55 | Forneça exemplos específicos de como essa funcionalidade poderia ser utilizada.
56 | Isso pode incluir cenários ou casos de uso onde a funcionalidade seria particularmente benéfica.
57 | placeholder: texto - imagem - video - fluxogramas
58 | validations:
59 | required: false
60 |
61 | - type: textarea
62 | attributes:
63 | label: Como o recurso deve ser desenvolvido?
64 | description: |
65 | Deve ser inserido diretamente no código?
66 | Deve ser construído uma aplicação diferente que atuará como uma extenção da api?
67 | Por exemplo: um `worker`?
68 |
69 | Discuta como essa nova funcionalidade poderia impactar outras partes do projeto, se aplicável.
70 |
71 | ---
72 |
73 | Se você tem ideias sobre como essa funcionalidade pode ser implementada, por favor, compartilhe-as aqui.
74 | Isso não é obrigatório, mas pode ser útil para a equipe de desenvolvimento.
75 |
76 | placeholder: Insira ideias para o recurso
77 | validations:
78 | required: false
79 |
80 | - type: textarea
81 | attributes:
82 | label: Notas Adicionais
83 | description: Qualquer outra informação que você acredita ser relevante para a sua solicitação.
84 | placeholder: Insira aqui as sua observções.
85 | validations:
86 | required: false
87 |
--------------------------------------------------------------------------------
/.github/workflows/check_code_quality.yml:
--------------------------------------------------------------------------------
1 | name: Check Code Quality
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | check-lint-and-build:
7 | runs-on: ubuntu-latest
8 | timeout-minutes: 10
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - name: Install Node
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: 20.x
17 |
18 | - name: Install packages
19 | run: npm install
20 |
21 | - name: Check linting
22 | run: npm run lint:check
23 |
24 | - name: Check build
25 | run: npm run db:generate
26 |
27 | - name: Check build
28 | run: npm run build
--------------------------------------------------------------------------------
/.github/workflows/publish_docker_image.yml:
--------------------------------------------------------------------------------
1 | name: Build Docker image
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*.*.*"
7 |
8 | jobs:
9 | build_deploy:
10 | name: Build and Deploy
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 | packages: write
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Docker meta
20 | id: meta
21 | uses: docker/metadata-action@v5
22 | with:
23 | images: atendai/evolution-api
24 | tags: type=semver,pattern=v{{version}}
25 |
26 | - name: Set up QEMU
27 | uses: docker/setup-qemu-action@v3
28 |
29 | - name: Set up Docker Buildx
30 | uses: docker/setup-buildx-action@v3
31 |
32 | - name: Login to GitHub Container Registry
33 | uses: docker/login-action@v3
34 | with:
35 | username: ${{ secrets.DOCKER_USERNAME }}
36 | password: ${{ secrets.DOCKER_PASSWORD }}
37 |
38 | - name: Build and push
39 | id: docker_build
40 | uses: docker/build-push-action@v5
41 | with:
42 | platforms: linux/amd64,linux/arm64
43 | push: true
44 | tags: ${{ steps.meta.outputs.tags }}
45 | labels: ${{ steps.meta.outputs.labels }}
46 |
47 | - name: Image digest
48 | run: echo ${{ steps.docker_build.outputs.digest }}
--------------------------------------------------------------------------------
/.github/workflows/publish_docker_image_homolog.yml:
--------------------------------------------------------------------------------
1 | name: Build Docker image
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | build_deploy:
10 | name: Build and Deploy
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 | packages: write
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Docker meta
20 | id: meta
21 | uses: docker/metadata-action@v5
22 | with:
23 | images: atendai/evolution-api
24 | tags: homolog
25 |
26 | - name: Set up QEMU
27 | uses: docker/setup-qemu-action@v3
28 |
29 | - name: Set up Docker Buildx
30 | uses: docker/setup-buildx-action@v3
31 |
32 | - name: Login to Docker Hub
33 | uses: docker/login-action@v3
34 | with:
35 | username: ${{ secrets.DOCKER_USERNAME }}
36 | password: ${{ secrets.DOCKER_PASSWORD }}
37 |
38 | - name: Build and push
39 | id: docker_build
40 | uses: docker/build-push-action@v5
41 | with:
42 | platforms: linux/amd64,linux/arm64
43 | push: true
44 | tags: ${{ steps.meta.outputs.tags }}
45 | labels: ${{ steps.meta.outputs.labels }}
46 |
47 | - name: Image digest
48 | run: echo ${{ steps.docker_build.outputs.digest }}
49 |
--------------------------------------------------------------------------------
/.github/workflows/publish_docker_image_latest.yml:
--------------------------------------------------------------------------------
1 | name: Build Docker image
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build_deploy:
10 | name: Build and Deploy
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 | packages: write
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Docker meta
20 | id: meta
21 | uses: docker/metadata-action@v5
22 | with:
23 | images: atendai/evolution-api
24 | tags: latest
25 |
26 | - name: Set up QEMU
27 | uses: docker/setup-qemu-action@v3
28 |
29 | - name: Set up Docker Buildx
30 | uses: docker/setup-buildx-action@v3
31 |
32 | - name: Login to Docker Hub
33 | uses: docker/login-action@v3
34 | with:
35 | username: ${{ secrets.DOCKER_USERNAME }}
36 | password: ${{ secrets.DOCKER_PASSWORD }}
37 |
38 | - name: Build and push
39 | id: docker_build
40 | uses: docker/build-push-action@v5
41 | with:
42 | platforms: linux/amd64,linux/arm64
43 | push: true
44 | tags: ${{ steps.meta.outputs.tags }}
45 | labels: ${{ steps.meta.outputs.labels }}
46 |
47 | - name: Image digest
48 | run: echo ${{ steps.docker_build.outputs.digest }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | /Docker/.env
6 |
7 | .vscode
8 |
9 | # Logs
10 | logs/**.json
11 | *.log
12 | npm-debug.log*
13 | pnpm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | lerna-debug.log*
17 |
18 | /docker-compose-data
19 | /docker-data
20 |
21 | # Package
22 | /yarn.lock
23 | /pnpm-lock.yaml
24 |
25 | # IDEs
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | .nova/*
32 | .idea/*
33 |
34 | # Project related
35 | /instances/*
36 | !/instances/.gitkeep
37 | /test/
38 | /src/env.yml
39 | /store
40 | *.env
41 |
42 | /temp/*
43 |
44 | .DS_Store
45 | *.DS_Store
46 | .tool-versions
47 |
48 | /prisma/migrations/*
49 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 120,
6 | arrowParens: 'always',
7 | tabWidth: 2,
8 | useTabs: false,
9 | bracketSameLine: false,
10 | bracketSpacing: true,
11 | parser: 'typescript'
12 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.fontSize": 13,
3 | "editor.fontLigatures": true,
4 | "editor.letterSpacing": 0.5,
5 | "editor.smoothScrolling": true,
6 | "editor.tabSize": 2,
7 | "editor.codeActionsOnSave": {
8 | "source.fixAll.eslint": "explicit",
9 | "source.fixAll": "explicit"
10 | },
11 | "prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
12 | "prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma",
13 | "i18n-ally.localesPaths": [
14 | "store/messages"
15 | ]
16 | }
--------------------------------------------------------------------------------
/Docker/minio/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | minio:
5 | container_name: minio
6 | image: quay.io/minio/minio
7 | networks:
8 | - evolution-net
9 | command: server /data --console-address ":9001"
10 | restart: always
11 | ports:
12 | - 5432:5432
13 | environment:
14 | - MINIO_ROOT_USER=USER
15 | - MINIO_ROOT_PASSWORD=PASSWORD
16 | - MINIO_BROWSER_REDIRECT_URL=http:/localhost:9001
17 | - MINIO_SERVER_URL=http://localhost:9000
18 | volumes:
19 | - minio_data:/data
20 | expose:
21 | - 9000
22 | - 9001
23 |
24 | volumes:
25 | minio_data:
26 |
27 |
28 | networks:
29 | evolution-net:
30 | name: evolution-net
31 | driver: bridge
32 |
--------------------------------------------------------------------------------
/Docker/mysql/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | mysql:
5 | container_name: mysql
6 | image: percona/percona-server:8.0
7 | networks:
8 | - evolution-net
9 | restart: always
10 | ports:
11 | - 3306:3306
12 | environment:
13 | - MYSQL_ROOT_PASSWORD=root
14 | - TZ=America/Bahia
15 | volumes:
16 | - mysql_data:/var/lib/mysql
17 | expose:
18 | - 3306
19 |
20 | volumes:
21 | mysql_data:
22 |
23 |
24 | networks:
25 | evolution-net:
26 | name: evolution-net
27 | driver: bridge
28 |
--------------------------------------------------------------------------------
/Docker/postgres/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | postgres:
5 | container_name: postgres
6 | image: postgres:15
7 | networks:
8 | - evolution-net
9 | command: ["postgres", "-c", "max_connections=1000"]
10 | restart: always
11 | ports:
12 | - 5432:5432
13 | environment:
14 | - POSTGRES_PASSWORD=PASSWORD
15 | volumes:
16 | - postgres_data:/var/lib/postgresql/data
17 | expose:
18 | - 5432
19 |
20 | pgadmin:
21 | image: dpage/pgadmin4:latest
22 | networks:
23 | - evolution-net
24 | environment:
25 | - PGADMIN_DEFAULT_EMAIL=EMAIL
26 | - PGADMIN_DEFAULT_PASSWORD=PASSWORD
27 | volumes:
28 | - pgadmin_data:/var/lib/pgadmin
29 | ports:
30 | - 4000:80
31 | links:
32 | - postgres
33 |
34 | volumes:
35 | postgres_data:
36 | pgadmin_data:
37 |
38 |
39 | networks:
40 | evolution-net:
41 | name: evolution-net
42 | driver: bridge
43 |
--------------------------------------------------------------------------------
/Docker/rabbitmq/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | rabbitmq:
5 | container_name: rabbitmq
6 | image: rabbitmq:management
7 | environment:
8 | - RABBITMQ_ERLANG_COOKIE=33H2CdkzF5WrnJ4ud6nkUdRTKXvbCHeFjvVL71p
9 | - RABBITMQ_DEFAULT_VHOST=default
10 | - RABBITMQ_DEFAULT_USER=USER
11 | - RABBITMQ_DEFAULT_PASS=PASSWORD
12 | volumes:
13 | - rabbitmq_data:/var/lib/rabbitmq/
14 | ports:
15 | - 5672:5672
16 | - 15672:15672
17 |
18 | volumes:
19 | rabbitmq_data:
20 |
21 |
22 | networks:
23 | evolution-net:
24 | name: evolution-net
25 | driver: bridge
26 |
--------------------------------------------------------------------------------
/Docker/redis/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 |
3 | services:
4 | redis:
5 | image: redis:latest
6 | networks:
7 | - evolution-net
8 | container_name: redis
9 | command: >
10 | redis-server --port 6379 --appendonly yes
11 | volumes:
12 | - evolution_redis:/data
13 | ports:
14 | - 6379:6379
15 |
16 | volumes:
17 | evolution_redis:
18 |
19 |
20 | networks:
21 | evolution-net:
22 | name: evolution-net
23 | driver: bridge
24 |
--------------------------------------------------------------------------------
/Docker/scripts/deploy_database.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | source ./Docker/scripts/env_functions.sh
4 |
5 | if [ "$DOCKER_ENV" != "true" ]; then
6 | export_env_vars
7 | fi
8 |
9 | if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
10 | export DATABASE_URL
11 | echo "Deploying migrations for $DATABASE_PROVIDER"
12 | echo "Database URL: $DATABASE_URL"
13 | # rm -rf ./prisma/migrations
14 | # cp -r ./prisma/$DATABASE_PROVIDER-migrations ./prisma/migrations
15 | npm run db:deploy
16 | if [ $? -ne 0 ]; then
17 | echo "Migration failed"
18 | exit 1
19 | else
20 | echo "Migration succeeded"
21 | fi
22 | npm run db:generate
23 | if [ $? -ne 0 ]; then
24 | echo "Prisma generate failed"
25 | exit 1
26 | else
27 | echo "Prisma generate succeeded"
28 | fi
29 | else
30 | echo "Error: Database provider $DATABASE_PROVIDER invalid."
31 | exit 1
32 | fi
33 |
--------------------------------------------------------------------------------
/Docker/scripts/env_functions.sh:
--------------------------------------------------------------------------------
1 | export_env_vars() {
2 | if [ -f .env ]; then
3 | while IFS='=' read -r key value; do
4 | if [[ -z "$key" || "$key" =~ ^\s*# || -z "$value" ]]; then
5 | continue
6 | fi
7 |
8 | key=$(echo "$key" | tr -d '[:space:]')
9 | value=$(echo "$value" | tr -d '[:space:]')
10 | value=$(echo "$value" | tr -d "'" | tr -d "\"")
11 |
12 | export "$key=$value"
13 | done < .env
14 | else
15 | echo ".env file not found"
16 | exit 1
17 | fi
18 | }
19 |
--------------------------------------------------------------------------------
/Docker/scripts/generate_database.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | source ./Docker/scripts/env_functions.sh
4 |
5 | if [ "$DOCKER_ENV" != "true" ]; then
6 | export_env_vars
7 | fi
8 |
9 | if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
10 | export DATABASE_URL
11 | echo "Generating database for $DATABASE_PROVIDER"
12 | echo "Database URL: $DATABASE_URL"
13 | npm run db:generate
14 | if [ $? -ne 0 ]; then
15 | echo "Prisma generate failed"
16 | exit 1
17 | else
18 | echo "Prisma generate succeeded"
19 | fi
20 | else
21 | echo "Error: Database provider $DATABASE_PROVIDER invalid."
22 | exit 1
23 | fi
24 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20-alpine AS builder
2 |
3 | RUN apk update && \
4 | apk add git ffmpeg wget curl bash openssl
5 |
6 | LABEL version="2.2.3" description="Api to control whatsapp features through http requests."
7 | LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
8 | LABEL contact="contato@atendai.com"
9 |
10 | WORKDIR /evolution
11 |
12 | COPY ./package.json ./tsconfig.json ./
13 |
14 | RUN npm install
15 |
16 | COPY ./src ./src
17 | COPY ./public ./public
18 | COPY ./prisma ./prisma
19 | COPY ./manager ./manager
20 | COPY ./.env.example ./.env
21 | COPY ./runWithProvider.js ./
22 | COPY ./tsup.config.ts ./
23 |
24 | COPY ./Docker ./Docker
25 |
26 | RUN chmod +x ./Docker/scripts/* && dos2unix ./Docker/scripts/*
27 |
28 | RUN ./Docker/scripts/generate_database.sh
29 |
30 | RUN npm run build
31 |
32 | FROM node:20-alpine AS final
33 |
34 | RUN apk update && \
35 | apk add tzdata ffmpeg bash openssl
36 |
37 | ENV TZ=America/Sao_Paulo
38 |
39 | WORKDIR /evolution
40 |
41 | COPY --from=builder /evolution/package.json ./package.json
42 | COPY --from=builder /evolution/package-lock.json ./package-lock.json
43 |
44 | COPY --from=builder /evolution/node_modules ./node_modules
45 | COPY --from=builder /evolution/dist ./dist
46 | COPY --from=builder /evolution/prisma ./prisma
47 | COPY --from=builder /evolution/manager ./manager
48 | COPY --from=builder /evolution/public ./public
49 | COPY --from=builder /evolution/.env ./.env
50 | COPY --from=builder /evolution/Docker ./Docker
51 | COPY --from=builder /evolution/runWithProvider.js ./runWithProvider.js
52 | COPY --from=builder /evolution/tsup.config.ts ./tsup.config.ts
53 |
54 | ENV DOCKER_ENV=true
55 |
56 | EXPOSE 8080
57 |
58 | ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # Evolution API License
2 |
3 | Evolution API is licensed under the Apache License 2.0, with the following additional conditions:
4 |
5 | 1. Evolution API may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. Should the conditions below be met, a commercial license must be obtained from the producer:
6 |
7 | a. LOGO and copyright information: In the process of using Evolution API's frontend components, you may not remove or modify the LOGO or copyright information in the Evolution API console or applications. This restriction is inapplicable to uses of Evolution API that do not involve its frontend components.
8 |
9 | b. Usage Notification Requirement: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer.
10 |
11 | Please contact contato@atendai.com to inquire about licensing matters.
12 |
13 | 2. As a contributor, you should agree that:
14 |
15 | a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
16 | b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
17 |
18 | Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
19 |
20 | © 2024 Evolution API
21 |
22 |
--------------------------------------------------------------------------------
/docker-compose.dev.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | api:
3 | container_name: evolution_api
4 | image: evolution/api:local
5 | build: .
6 | restart: always
7 | ports:
8 | - 8080:8080
9 | volumes:
10 | - evolution_instances:/evolution/instances
11 | networks:
12 | - evolution-net
13 | env_file:
14 | - .env
15 | expose:
16 | - 8080
17 |
18 | volumes:
19 | evolution_instances:
20 |
21 |
22 | networks:
23 | evolution-net:
24 | name: evolution-net
25 | driver: bridge
26 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | api:
3 | container_name: evolution_api
4 | image: atendai/evolution-api:homolog
5 | restart: always
6 | depends_on:
7 | - redis
8 | - postgres
9 | ports:
10 | - 8080:8080
11 | volumes:
12 | - evolution_instances:/evolution/instances
13 | networks:
14 | - evolution-net
15 | env_file:
16 | - .env
17 | expose:
18 | - 8080
19 |
20 | redis:
21 | image: redis:latest
22 | networks:
23 | - evolution-net
24 | container_name: redis
25 | command: >
26 | redis-server --port 6379 --appendonly yes
27 | volumes:
28 | - evolution_redis:/data
29 | ports:
30 | - 6379:6379
31 |
32 | postgres:
33 | container_name: postgres
34 | image: postgres:15
35 | networks:
36 | - evolution-net
37 | command: ["postgres", "-c", "max_connections=1000", "-c", "listen_addresses=*"]
38 | restart: always
39 | ports:
40 | - 5432:5432
41 | environment:
42 | - POSTGRES_USER=user
43 | - POSTGRES_PASSWORD=pass
44 | - POSTGRES_DB=evolution
45 | - POSTGRES_HOST_AUTH_METHOD=trust
46 | volumes:
47 | - postgres_data:/var/lib/postgresql/data
48 | expose:
49 | - 5432
50 |
51 | volumes:
52 | evolution_instances:
53 | evolution_redis:
54 | postgres_data:
55 |
56 |
57 | networks:
58 | evolution-net:
59 | name: evolution-net
60 | driver: bridge
61 |
--------------------------------------------------------------------------------
/manager/dist/assets/images/evolution-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/manager/dist/assets/images/evolution-logo.png
--------------------------------------------------------------------------------
/manager/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Evolution Manager
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/prisma/mysql-migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "mysql"
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240610144159_create_column_profile_name_instance/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Instance" ADD COLUMN "profileName" VARCHAR(100);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240611125754_create_columns_whitelabel_chatwoot/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chatwoot" ADD COLUMN "logo" VARCHAR(500),
3 | ADD COLUMN "organization" VARCHAR(100);
4 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240611202817_create_columns_debounce_time_typebot/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Typebot" ADD COLUMN "debounceTime" INTEGER;
3 |
4 | -- AlterTable
5 | ALTER TABLE "TypebotSetting" ADD COLUMN "debounceTime" INTEGER;
6 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712144948_add_business_id_column_to_instances/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Instance" ADD COLUMN "businessId" VARCHAR(100);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712150256_create_templates_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Template" (
3 | "id" TEXT NOT NULL,
4 | "name" VARCHAR(255) NOT NULL,
5 | "language" VARCHAR(255) NOT NULL,
6 | "templateId" VARCHAR(255) NOT NULL,
7 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
8 | "updatedAt" TIMESTAMP NOT NULL,
9 | "instanceId" TEXT NOT NULL,
10 |
11 | CONSTRAINT "Template_pkey" PRIMARY KEY ("id")
12 | );
13 |
14 | -- CreateIndex
15 | CREATE UNIQUE INDEX "Template_templateId_key" ON "Template"("templateId");
16 |
17 | -- CreateIndex
18 | CREATE UNIQUE INDEX "Template_instanceId_key" ON "Template"("instanceId");
19 |
20 | -- AddForeignKey
21 | ALTER TABLE "Template" ADD CONSTRAINT "Template_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712155950_adjusts_in_templates_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropIndex
2 | DROP INDEX "Template_instanceId_key";
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712162206_remove_templates_table/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `Template` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "Template" DROP CONSTRAINT "Template_instanceId_fkey";
9 |
10 | -- DropTable
11 | DROP TABLE "Template";
12 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712223655_column_fallback_typebot/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterEnum
2 | ALTER TYPE "TriggerOperator" ADD VALUE 'regex';
3 |
4 | -- AlterTable
5 | ALTER TABLE "TypebotSetting" ADD COLUMN "typebotIdFallback" VARCHAR(100);
6 |
7 | -- AddForeignKey
8 | ALTER TABLE "TypebotSetting" ADD CONSTRAINT "TypebotSetting_typebotIdFallback_fkey" FOREIGN KEY ("typebotIdFallback") REFERENCES "Typebot"("id") ON DELETE SET NULL ON UPDATE CASCADE;
9 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240712230631_column_ignore_jids_typebot/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Typebot" ADD COLUMN "ignoreJids" JSONB;
3 |
4 | -- AlterTable
5 | ALTER TABLE "TypebotSetting" ADD COLUMN "ignoreJids" JSONB;
6 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240713184337_add_media_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Media" (
3 | "id" TEXT NOT NULL,
4 | "fileName" VARCHAR(500) NOT NULL,
5 | "type" VARCHAR(100) NOT NULL,
6 | "mimetype" VARCHAR(100) NOT NULL,
7 | "createdAt" DATE DEFAULT CURRENT_TIMESTAMP,
8 | "messageId" TEXT NOT NULL,
9 | "instanceId" TEXT NOT NULL,
10 |
11 | CONSTRAINT "Media_pkey" PRIMARY KEY ("id")
12 | );
13 |
14 | -- CreateIndex
15 | CREATE UNIQUE INDEX "Media_fileName_key" ON "Media"("fileName");
16 |
17 | -- CreateIndex
18 | CREATE UNIQUE INDEX "Media_messageId_key" ON "Media"("messageId");
19 |
20 | -- AddForeignKey
21 | ALTER TABLE "Media" ADD CONSTRAINT "Media_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 |
23 | -- AddForeignKey
24 | ALTER TABLE "Media" ADD CONSTRAINT "Media_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
25 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240718123923_adjusts_openai_tables/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "OpenaiBot" ADD COLUMN "enabled" BOOLEAN NOT NULL DEFAULT true;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240722173259_add_name_column_to_openai_creds/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[name]` on the table `OpenaiCreds` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "OpenaiCreds" ADD COLUMN "name" VARCHAR(255),
9 | ALTER COLUMN "apiKey" DROP NOT NULL;
10 |
11 | -- CreateIndex
12 | CREATE UNIQUE INDEX "OpenaiCreds_name_key" ON "OpenaiCreds"("name");
13 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240722173518_add_name_column_to_openai_creds/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropIndex
2 | DROP INDEX "OpenaiCreds_instanceId_key";
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240723152648_adjusts_in_column_openai_creds/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[openaiCredsId]` on the table `OpenaiSetting` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 | -- CreateIndex
8 | CREATE UNIQUE INDEX "OpenaiSetting_openaiCredsId_key" ON "OpenaiSetting"("openaiCredsId");
9 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240723200254_add_webhookurl_on_message/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Message" ADD COLUMN "webhookUrl" VARCHAR(500);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240725184147_create_template_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Template" (
3 | "id" TEXT NOT NULL,
4 | "templateId" VARCHAR(255) NOT NULL,
5 | "name" VARCHAR(255) NOT NULL,
6 | "template" JSONB NOT NULL,
7 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
8 | "updatedAt" TIMESTAMP NOT NULL,
9 | "instanceId" TEXT NOT NULL,
10 |
11 | CONSTRAINT "Template_pkey" PRIMARY KEY ("id")
12 | );
13 |
14 | -- CreateIndex
15 | CREATE UNIQUE INDEX "Template_templateId_key" ON "Template"("templateId");
16 |
17 | -- CreateIndex
18 | CREATE UNIQUE INDEX "Template_name_key" ON "Template"("name");
19 |
20 | -- AddForeignKey
21 | ALTER TABLE "Template" ADD CONSTRAINT "Template_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240725202651_add_webhook_url_template_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Template" ADD COLUMN "webhookUrl" VARCHAR(500);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240725221646_modify_token_instance_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropIndex
2 | DROP INDEX "Instance_token_key";
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240729115127_modify_trigger_type_openai_typebot_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterEnum
2 | ALTER TYPE "TriggerType" ADD VALUE 'none';
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240729180347_modify_typebot_session_status_openai_typebot_table/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - The values [open] on the enum `TypebotSessionStatus` will be removed. If these variants are still used in the database, this will fail.
5 | - Changed the type of `status` on the `TypebotSession` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
6 |
7 | */
8 | -- AlterEnum
9 | BEGIN;
10 | CREATE TYPE "TypebotSessionStatus_new" AS ENUM ('opened', 'closed', 'paused');
11 | ALTER TABLE "TypebotSession" ALTER COLUMN "status" TYPE "TypebotSessionStatus_new" USING ("status"::text::"TypebotSessionStatus_new");
12 | ALTER TABLE "OpenaiSession" ALTER COLUMN "status" TYPE "TypebotSessionStatus_new" USING ("status"::text::"TypebotSessionStatus_new");
13 | ALTER TYPE "TypebotSessionStatus" RENAME TO "TypebotSessionStatus_old";
14 | ALTER TYPE "TypebotSessionStatus_new" RENAME TO "TypebotSessionStatus";
15 | DROP TYPE "TypebotSessionStatus_old";
16 | COMMIT;
17 |
18 | -- AlterTable
19 | ALTER TABLE "TypebotSession" DROP COLUMN "status",
20 | ADD COLUMN "status" "TypebotSessionStatus" NOT NULL;
21 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240801193907_add_column_speech_to_text_openai_setting_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "OpenaiSetting" ADD COLUMN "speechToText" BOOLEAN DEFAULT false;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240803163908_add_column_description_on_integrations_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Dify" ADD COLUMN "description" VARCHAR(255);
3 |
4 | -- AlterTable
5 | ALTER TABLE "OpenaiBot" ADD COLUMN "description" VARCHAR(255);
6 |
7 | -- AlterTable
8 | ALTER TABLE "Typebot" ADD COLUMN "description" VARCHAR(255);
9 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240808210239_add_column_function_url_openaibot_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Instance" ADD COLUMN "disconnectionAt" TIMESTAMP,
3 | ADD COLUMN "disconnectionObject" JSONB,
4 | ADD COLUMN "disconnectionReasonCode" INTEGER;
5 |
6 | -- AlterTable
7 | ALTER TABLE "OpenaiBot" ADD COLUMN "functionUrl" VARCHAR(500);
8 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240811021156_add_chat_name_column/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chat" ADD COLUMN "name" VARCHAR(100);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240811183328_add_unique_index_for_remoted_jid_and_instance_in_contacts/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Contact` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 | -- Remove the duplicates
8 | DELETE FROM "Contact"
9 | WHERE ctid NOT IN (
10 | SELECT min(ctid)
11 | FROM "Contact"
12 | GROUP BY "remoteJid", "instanceId"
13 | );
14 |
15 |
16 | -- CreateIndex
17 | CREATE UNIQUE INDEX "Contact_remoteJid_instanceId_key" ON "Contact"("remoteJid", "instanceId");
18 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240813003116_make_label_unique_for_instance/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[labelId,instanceId]` on the table `Label` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 | -- DropIndex
8 | DROP INDEX "Label_labelId_key";
9 |
10 | -- CreateIndex
11 | CREATE UNIQUE INDEX "Label_labelId_instanceId_key" ON "Label"("labelId", "instanceId");
12 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240814173033_add_ignore_jids_chatwoot/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chatwoot" ADD COLUMN "ignoreJids" JSONB;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240817110155_add_trigger_type_advanced/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterEnum
2 | ALTER TYPE "TriggerType" ADD VALUE 'advanced';
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240819154941_add_context_to_integration_session/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "IntegrationSession" ADD COLUMN "context" JSONB;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240821120816_bot_id_integration_session/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `difyId` on the `IntegrationSession` table. All the data in the column will be lost.
5 | - You are about to drop the column `openaiBotId` on the `IntegrationSession` table. All the data in the column will be lost.
6 | - You are about to drop the column `typebotId` on the `IntegrationSession` table. All the data in the column will be lost.
7 |
8 | */
9 | -- DropForeignKey
10 | ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_difyId_fkey";
11 |
12 | -- DropForeignKey
13 | ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_openaiBotId_fkey";
14 |
15 | -- DropForeignKey
16 | ALTER TABLE "IntegrationSession" DROP CONSTRAINT "IntegrationSession_typebotId_fkey";
17 |
18 | -- AlterTable
19 | ALTER TABLE "IntegrationSession" DROP COLUMN "difyId",
20 | DROP COLUMN "openaiBotId",
21 | DROP COLUMN "typebotId",
22 | ADD COLUMN "botId" TEXT;
23 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240821171327_add_generic_bot_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "GenericBot" (
3 | "id" TEXT NOT NULL,
4 | "enabled" BOOLEAN NOT NULL DEFAULT true,
5 | "description" VARCHAR(255),
6 | "apiUrl" VARCHAR(255),
7 | "apiKey" VARCHAR(255),
8 | "expire" INTEGER DEFAULT 0,
9 | "keywordFinish" VARCHAR(100),
10 | "delayMessage" INTEGER,
11 | "unknownMessage" VARCHAR(100),
12 | "listeningFromMe" BOOLEAN DEFAULT false,
13 | "stopBotFromMe" BOOLEAN DEFAULT false,
14 | "keepOpen" BOOLEAN DEFAULT false,
15 | "debounceTime" INTEGER,
16 | "ignoreJids" JSONB,
17 | "triggerType" "TriggerType",
18 | "triggerOperator" "TriggerOperator",
19 | "triggerValue" TEXT,
20 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
21 | "updatedAt" TIMESTAMP NOT NULL,
22 | "instanceId" TEXT NOT NULL,
23 |
24 | CONSTRAINT "GenericBot_pkey" PRIMARY KEY ("id")
25 | );
26 |
27 | -- CreateTable
28 | CREATE TABLE "GenericSetting" (
29 | "id" TEXT NOT NULL,
30 | "expire" INTEGER DEFAULT 0,
31 | "keywordFinish" VARCHAR(100),
32 | "delayMessage" INTEGER,
33 | "unknownMessage" VARCHAR(100),
34 | "listeningFromMe" BOOLEAN DEFAULT false,
35 | "stopBotFromMe" BOOLEAN DEFAULT false,
36 | "keepOpen" BOOLEAN DEFAULT false,
37 | "debounceTime" INTEGER,
38 | "ignoreJids" JSONB,
39 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40 | "updatedAt" TIMESTAMP NOT NULL,
41 | "botIdFallback" VARCHAR(100),
42 | "instanceId" TEXT NOT NULL,
43 |
44 | CONSTRAINT "GenericSetting_pkey" PRIMARY KEY ("id")
45 | );
46 |
47 | -- CreateIndex
48 | CREATE UNIQUE INDEX "GenericSetting_instanceId_key" ON "GenericSetting"("instanceId");
49 |
50 | -- AddForeignKey
51 | ALTER TABLE "GenericBot" ADD CONSTRAINT "GenericBot_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
52 |
53 | -- AddForeignKey
54 | ALTER TABLE "GenericSetting" ADD CONSTRAINT "GenericSetting_botIdFallback_fkey" FOREIGN KEY ("botIdFallback") REFERENCES "GenericBot"("id") ON DELETE SET NULL ON UPDATE CASCADE;
55 |
56 | -- AddForeignKey
57 | ALTER TABLE "GenericSetting" ADD CONSTRAINT "GenericSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
58 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240821194524_add_flowise_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Flowise" (
3 | "id" TEXT NOT NULL,
4 | "enabled" BOOLEAN NOT NULL DEFAULT true,
5 | "description" VARCHAR(255),
6 | "apiUrl" VARCHAR(255),
7 | "apiKey" VARCHAR(255),
8 | "expire" INTEGER DEFAULT 0,
9 | "keywordFinish" VARCHAR(100),
10 | "delayMessage" INTEGER,
11 | "unknownMessage" VARCHAR(100),
12 | "listeningFromMe" BOOLEAN DEFAULT false,
13 | "stopBotFromMe" BOOLEAN DEFAULT false,
14 | "keepOpen" BOOLEAN DEFAULT false,
15 | "debounceTime" INTEGER,
16 | "ignoreJids" JSONB,
17 | "triggerType" "TriggerType",
18 | "triggerOperator" "TriggerOperator",
19 | "triggerValue" TEXT,
20 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
21 | "updatedAt" TIMESTAMP NOT NULL,
22 | "instanceId" TEXT NOT NULL,
23 |
24 | CONSTRAINT "Flowise_pkey" PRIMARY KEY ("id")
25 | );
26 |
27 | -- CreateTable
28 | CREATE TABLE "FlowiseSetting" (
29 | "id" TEXT NOT NULL,
30 | "expire" INTEGER DEFAULT 0,
31 | "keywordFinish" VARCHAR(100),
32 | "delayMessage" INTEGER,
33 | "unknownMessage" VARCHAR(100),
34 | "listeningFromMe" BOOLEAN DEFAULT false,
35 | "stopBotFromMe" BOOLEAN DEFAULT false,
36 | "keepOpen" BOOLEAN DEFAULT false,
37 | "debounceTime" INTEGER,
38 | "ignoreJids" JSONB,
39 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40 | "updatedAt" TIMESTAMP NOT NULL,
41 | "flowiseIdFallback" VARCHAR(100),
42 | "instanceId" TEXT NOT NULL,
43 |
44 | CONSTRAINT "FlowiseSetting_pkey" PRIMARY KEY ("id")
45 | );
46 |
47 | -- CreateIndex
48 | CREATE UNIQUE INDEX "FlowiseSetting_instanceId_key" ON "FlowiseSetting"("instanceId");
49 |
50 | -- AddForeignKey
51 | ALTER TABLE "Flowise" ADD CONSTRAINT "Flowise_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
52 |
53 | -- AddForeignKey
54 | ALTER TABLE "FlowiseSetting" ADD CONSTRAINT "FlowiseSetting_flowiseIdFallback_fkey" FOREIGN KEY ("flowiseIdFallback") REFERENCES "Flowise"("id") ON DELETE SET NULL ON UPDATE CASCADE;
55 |
56 | -- AddForeignKey
57 | ALTER TABLE "FlowiseSetting" ADD CONSTRAINT "FlowiseSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
58 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240824161333_add_type_on_integration_sessions/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "IntegrationSession" ADD COLUMN "type" VARCHAR(100);
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240825130616_change_to_evolution_bot/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `GenericBot` table. If the table is not empty, all the data it contains will be lost.
5 | - You are about to drop the `GenericSetting` table. If the table is not empty, all the data it contains will be lost.
6 |
7 | */
8 | -- DropForeignKey
9 | ALTER TABLE "GenericBot" DROP CONSTRAINT "GenericBot_instanceId_fkey";
10 |
11 | -- DropForeignKey
12 | ALTER TABLE "GenericSetting" DROP CONSTRAINT "GenericSetting_botIdFallback_fkey";
13 |
14 | -- DropForeignKey
15 | ALTER TABLE "GenericSetting" DROP CONSTRAINT "GenericSetting_instanceId_fkey";
16 |
17 | -- DropTable
18 | DROP TABLE "GenericBot";
19 |
20 | -- DropTable
21 | DROP TABLE "GenericSetting";
22 |
23 | -- CreateTable
24 | CREATE TABLE "EvolutionBot" (
25 | "id" TEXT NOT NULL,
26 | "enabled" BOOLEAN NOT NULL DEFAULT true,
27 | "description" VARCHAR(255),
28 | "apiUrl" VARCHAR(255),
29 | "apiKey" VARCHAR(255),
30 | "expire" INTEGER DEFAULT 0,
31 | "keywordFinish" VARCHAR(100),
32 | "delayMessage" INTEGER,
33 | "unknownMessage" VARCHAR(100),
34 | "listeningFromMe" BOOLEAN DEFAULT false,
35 | "stopBotFromMe" BOOLEAN DEFAULT false,
36 | "keepOpen" BOOLEAN DEFAULT false,
37 | "debounceTime" INTEGER,
38 | "ignoreJids" JSONB,
39 | "triggerType" "TriggerType",
40 | "triggerOperator" "TriggerOperator",
41 | "triggerValue" TEXT,
42 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
43 | "updatedAt" TIMESTAMP NOT NULL,
44 | "instanceId" TEXT NOT NULL,
45 |
46 | CONSTRAINT "EvolutionBot_pkey" PRIMARY KEY ("id")
47 | );
48 |
49 | -- CreateTable
50 | CREATE TABLE "EvolutionBotSetting" (
51 | "id" TEXT NOT NULL,
52 | "expire" INTEGER DEFAULT 0,
53 | "keywordFinish" VARCHAR(100),
54 | "delayMessage" INTEGER,
55 | "unknownMessage" VARCHAR(100),
56 | "listeningFromMe" BOOLEAN DEFAULT false,
57 | "stopBotFromMe" BOOLEAN DEFAULT false,
58 | "keepOpen" BOOLEAN DEFAULT false,
59 | "debounceTime" INTEGER,
60 | "ignoreJids" JSONB,
61 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
62 | "updatedAt" TIMESTAMP NOT NULL,
63 | "botIdFallback" VARCHAR(100),
64 | "instanceId" TEXT NOT NULL,
65 |
66 | CONSTRAINT "EvolutionBotSetting_pkey" PRIMARY KEY ("id")
67 | );
68 |
69 | -- CreateIndex
70 | CREATE UNIQUE INDEX "EvolutionBotSetting_instanceId_key" ON "EvolutionBotSetting"("instanceId");
71 |
72 | -- AddForeignKey
73 | ALTER TABLE "EvolutionBot" ADD CONSTRAINT "EvolutionBot_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74 |
75 | -- AddForeignKey
76 | ALTER TABLE "EvolutionBotSetting" ADD CONSTRAINT "EvolutionBotSetting_botIdFallback_fkey" FOREIGN KEY ("botIdFallback") REFERENCES "EvolutionBot"("id") ON DELETE SET NULL ON UPDATE CASCADE;
77 |
78 | -- AddForeignKey
79 | ALTER TABLE "EvolutionBotSetting" ADD CONSTRAINT "EvolutionBotSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
80 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240828140837_add_is_on_whatsapp_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "is_on_whatsapp" (
3 | "id" TEXT NOT NULL,
4 | "remote_jid" VARCHAR(100) NOT NULL,
5 | "name" TEXT,
6 | "jid_options" TEXT NOT NULL,
7 | "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
8 | "updated_at" TIMESTAMP NOT NULL,
9 |
10 | CONSTRAINT "is_on_whatsapp_pkey" PRIMARY KEY ("id")
11 | );
12 |
13 | -- CreateIndex
14 | CREATE UNIQUE INDEX "is_on_whatsapp_remote_jid_key" ON "is_on_whatsapp"("remote_jid");
15 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240828141556_remove_name_column_from_on_whatsapp_table/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `name` on the `is_on_whatsapp` table. All the data in the column will be lost.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "is_on_whatsapp" DROP COLUMN "name";
9 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240830193533_changed_table_case/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `is_on_whatsapp` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropTable
8 | DROP TABLE "is_on_whatsapp";
9 |
10 | -- CreateTable
11 | CREATE TABLE "IsOnWhatsapp" (
12 | "id" TEXT NOT NULL,
13 | "remoteJid" VARCHAR(100) NOT NULL,
14 | "jidOptions" TEXT NOT NULL,
15 | "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
16 | "updatedAt" TIMESTAMP NOT NULL,
17 |
18 | CONSTRAINT "IsOnWhatsapp_pkey" PRIMARY KEY ("id")
19 | );
20 |
21 | -- CreateIndex
22 | CREATE UNIQUE INDEX "IsOnWhatsapp_remoteJid_key" ON "IsOnWhatsapp"("remoteJid");
23 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20240906202019_add_headers_on_webhook_config/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Webhook" ADD COLUMN "headers" JSONB;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241001180457_add_message_status/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Message" ADD COLUMN "status" INTEGER;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241006130306_alter_status_on_message_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Message"
3 | ALTER COLUMN "status"
4 | SET
5 | DATA TYPE VARCHAR(30);
6 |
7 | UPDATE "Message" SET "status" = 'PENDING';
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241007164026_add_unread_messages_on_chat_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chat" ADD COLUMN "unreadMessages" INTEGER NOT NULL DEFAULT 0;
3 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241011085129_create_pusher_table/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Pusher" (
3 | "id" TEXT NOT NULL,
4 | "enabled" BOOLEAN NOT NULL DEFAULT false,
5 | "appId" VARCHAR(100) NOT NULL,
6 | "key" VARCHAR(100) NOT NULL,
7 | "secret" VARCHAR(100) NOT NULL,
8 | "cluster" VARCHAR(100) NOT NULL,
9 | "useTLS" BOOLEAN NOT NULL DEFAULT false,
10 | "events" JSONB NOT NULL,
11 | "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12 | "updatedAt" TIMESTAMP NOT NULL,
13 | "instanceId" TEXT NOT NULL,
14 |
15 | CONSTRAINT "Pusher_pkey" PRIMARY KEY ("id")
16 | );
17 |
18 | -- CreateIndex
19 | CREATE UNIQUE INDEX "Pusher_instanceId_key" ON "Pusher"("instanceId");
20 |
21 | -- AddForeignKey
22 | ALTER TABLE "Pusher" ADD CONSTRAINT "Pusher_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
23 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241011100803_split_messages_and_time_per_char_integrations/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Dify" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
3 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
4 |
5 | -- AlterTable
6 | ALTER TABLE "DifySetting" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
7 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
8 |
9 | -- AlterTable
10 | ALTER TABLE "EvolutionBot" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
11 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
12 |
13 | -- AlterTable
14 | ALTER TABLE "EvolutionBotSetting" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
15 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
16 |
17 | -- AlterTable
18 | ALTER TABLE "Flowise" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
19 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
20 |
21 | -- AlterTable
22 | ALTER TABLE "FlowiseSetting" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
23 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
24 |
25 | -- AlterTable
26 | ALTER TABLE "OpenaiBot" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
27 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
28 |
29 | -- AlterTable
30 | ALTER TABLE "OpenaiSetting" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
31 | ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
32 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20241017144950_create_index/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateIndex
2 | CREATE INDEX "Chat_instanceId_idx" ON "Chat"("instanceId");
3 |
4 | -- CreateIndex
5 | CREATE INDEX "Chat_remoteJid_idx" ON "Chat"("remoteJid");
6 |
7 | -- CreateIndex
8 | CREATE INDEX "Contact_remoteJid_idx" ON "Contact"("remoteJid");
9 |
10 | -- CreateIndex
11 | CREATE INDEX "Contact_instanceId_idx" ON "Contact"("instanceId");
12 |
13 | -- CreateIndex
14 | CREATE INDEX "Message_instanceId_idx" ON "Message"("instanceId");
15 |
16 | -- CreateIndex
17 | CREATE INDEX "MessageUpdate_instanceId_idx" ON "MessageUpdate"("instanceId");
18 |
19 | -- CreateIndex
20 | CREATE INDEX "MessageUpdate_messageId_idx" ON "MessageUpdate"("messageId");
21 |
22 | -- CreateIndex
23 | CREATE INDEX "Setting_instanceId_idx" ON "Setting"("instanceId");
24 |
25 | -- CreateIndex
26 | CREATE INDEX "Webhook_instanceId_idx" ON "Webhook"("instanceId");
27 |
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/20250116001415_add_wavoip_token_to_settings_table/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 |
8 | -- AlterTable
9 | DO $$
10 | BEGIN
11 | IF NOT EXISTS (
12 | SELECT 1
13 | FROM information_schema.columns
14 | WHERE table_name = 'Setting'
15 | AND column_name = 'wavoipToken'
16 | ) THEN
17 | ALTER TABLE "Setting" ADD COLUMN "wavoipToken" VARCHAR(100);
18 | END IF;
19 | END $$;
--------------------------------------------------------------------------------
/prisma/postgresql-migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/public/images/atendai-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/atendai-logo.png
--------------------------------------------------------------------------------
/public/images/bmc_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/bmc_qr.png
--------------------------------------------------------------------------------
/public/images/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/code.png
--------------------------------------------------------------------------------
/public/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/cover.png
--------------------------------------------------------------------------------
/public/images/evolution-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/evolution-logo.png
--------------------------------------------------------------------------------
/public/images/evolution-pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/evolution-pro.png
--------------------------------------------------------------------------------
/public/images/picpay-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/picpay-image.png
--------------------------------------------------------------------------------
/public/images/picpay-qr.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/picpay-qr.jpeg
--------------------------------------------------------------------------------
/public/images/qrcode-pix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/qrcode-pix.png
--------------------------------------------------------------------------------
/public/images/video-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvolutionAPI/evolution-api/427c99499394d3894e4b065626323c45e91022e4/public/images/video-cover.png
--------------------------------------------------------------------------------
/runWithProvider.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv');
2 | const { execSync } = require('child_process');
3 | const { existsSync } = require('fs');
4 |
5 | dotenv.config();
6 |
7 | const { DATABASE_PROVIDER } = process.env;
8 | const databaseProviderDefault = DATABASE_PROVIDER ?? 'postgresql';
9 |
10 | if (!DATABASE_PROVIDER) {
11 | console.warn(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
12 | }
13 |
14 | let command = process.argv
15 | .slice(2)
16 | .join(' ')
17 | .replace(/DATABASE_PROVIDER/g, databaseProviderDefault);
18 |
19 | if (command.includes('rmdir') && existsSync('prisma\\migrations')) {
20 | try {
21 | execSync('rmdir /S /Q prisma\\migrations', { stdio: 'inherit' });
22 | } catch (error) {
23 | console.error(`Error removing directory: prisma\\migrations`);
24 | process.exit(1);
25 | }
26 | } else if (command.includes('rmdir')) {
27 | console.warn(`Directory 'prisma\\migrations' does not exist, skipping removal.`);
28 | }
29 |
30 | try {
31 | execSync(command, { stdio: 'inherit' });
32 | } catch (error) {
33 | console.error(`Error executing command: ${command}`);
34 | process.exit(1);
35 | }
36 |
--------------------------------------------------------------------------------
/src/@types/express.d.ts:
--------------------------------------------------------------------------------
1 | import { Multer } from 'multer';
2 |
3 | declare global {
4 | namespace Express {
5 | interface Request {
6 | file?: Multer.File;
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/api/abstract/abstract.cache.ts:
--------------------------------------------------------------------------------
1 | export interface ICache {
2 | get(key: string): Promise;
3 |
4 | hGet(key: string, field: string): Promise;
5 |
6 | set(key: string, value: any, ttl?: number): void;
7 |
8 | hSet(key: string, field: string, value: any): Promise;
9 |
10 | has(key: string): Promise;
11 |
12 | keys(appendCriteria?: string): Promise;
13 |
14 | delete(key: string | string[]): Promise;
15 |
16 | hDelete(key: string, field: string): Promise;
17 |
18 | deleteAll(appendCriteria?: string): Promise;
19 | }
20 |
--------------------------------------------------------------------------------
/src/api/abstract/abstract.repository.ts:
--------------------------------------------------------------------------------
1 | import { ConfigService, Database } from '@config/env.config';
2 | import { ROOT_DIR } from '@config/path.config';
3 | import { existsSync, mkdirSync, writeFileSync } from 'fs';
4 | import { join } from 'path';
5 |
6 | export type IInsert = { insertCount: number };
7 |
8 | export interface IRepository {
9 | insert(data: any, instanceName: string, saveDb?: boolean): Promise;
10 | update(data: any, instanceName: string, saveDb?: boolean): Promise;
11 | find(query: any): Promise;
12 | delete(query: any, force?: boolean): Promise;
13 |
14 | dbSettings: Database;
15 | readonly storePath: string;
16 | }
17 |
18 | type WriteStore = {
19 | path: string;
20 | fileName: string;
21 | data: U;
22 | };
23 |
24 | export abstract class Repository implements IRepository {
25 | constructor(configService: ConfigService) {
26 | this.dbSettings = configService.get('DATABASE');
27 | }
28 |
29 | dbSettings: Database;
30 | readonly storePath = join(ROOT_DIR, 'store');
31 |
32 | public writeStore = (create: WriteStore) => {
33 | if (!existsSync(create.path)) {
34 | mkdirSync(create.path, { recursive: true });
35 | }
36 | try {
37 | writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), {
38 | encoding: 'utf-8',
39 | });
40 |
41 | return { message: 'create - success' };
42 | } finally {
43 | create.data = undefined;
44 | }
45 | };
46 |
47 | // eslint-disable-next-line
48 | public insert(data: any, instanceName: string, saveDb = false): Promise {
49 | throw new Error('Method not implemented.');
50 | }
51 |
52 | // eslint-disable-next-line
53 | public update(data: any, instanceName: string, saveDb = false): Promise {
54 | throw new Error('Method not implemented.');
55 | }
56 |
57 | // eslint-disable-next-line
58 | public find(query: any): Promise {
59 | throw new Error('Method not implemented.');
60 | }
61 |
62 | // eslint-disable-next-line
63 | delete(query: any, force?: boolean): Promise {
64 | throw new Error('Method not implemented.');
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/api/controllers/call.controller.ts:
--------------------------------------------------------------------------------
1 | import { OfferCallDto } from '@api/dto/call.dto';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { WAMonitoringService } from '@api/services/monitor.service';
4 |
5 | export class CallController {
6 | constructor(private readonly waMonitor: WAMonitoringService) {}
7 |
8 | public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {
9 | return await this.waMonitor.waInstances[instanceName].offerCall(data);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/api/controllers/group.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AcceptGroupInvite,
3 | CreateGroupDto,
4 | GetParticipant,
5 | GroupDescriptionDto,
6 | GroupInvite,
7 | GroupJid,
8 | GroupPictureDto,
9 | GroupSendInvite,
10 | GroupSubjectDto,
11 | GroupToggleEphemeralDto,
12 | GroupUpdateParticipantDto,
13 | GroupUpdateSettingDto,
14 | } from '@api/dto/group.dto';
15 | import { InstanceDto } from '@api/dto/instance.dto';
16 | import { WAMonitoringService } from '@api/services/monitor.service';
17 |
18 | export class GroupController {
19 | constructor(private readonly waMonitor: WAMonitoringService) {}
20 |
21 | public async createGroup(instance: InstanceDto, create: CreateGroupDto) {
22 | return await this.waMonitor.waInstances[instance.instanceName].createGroup(create);
23 | }
24 |
25 | public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) {
26 | return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(update);
27 | }
28 |
29 | public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) {
30 | return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(update);
31 | }
32 |
33 | public async updateGroupDescription(instance: InstanceDto, update: GroupDescriptionDto) {
34 | return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(update);
35 | }
36 |
37 | public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) {
38 | return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid);
39 | }
40 |
41 | public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) {
42 | return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(getPaticipants);
43 | }
44 |
45 | public async inviteCode(instance: InstanceDto, groupJid: GroupJid) {
46 | return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid);
47 | }
48 |
49 | public async inviteInfo(instance: InstanceDto, inviteCode: GroupInvite) {
50 | return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode);
51 | }
52 |
53 | public async sendInvite(instance: InstanceDto, data: GroupSendInvite) {
54 | return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
55 | }
56 |
57 | public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
58 | return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
59 | }
60 |
61 | public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
62 | return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);
63 | }
64 |
65 | public async findParticipants(instance: InstanceDto, groupJid: GroupJid) {
66 | return await this.waMonitor.waInstances[instance.instanceName].findParticipants(groupJid);
67 | }
68 |
69 | public async updateGParticipate(instance: InstanceDto, update: GroupUpdateParticipantDto) {
70 | return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(update);
71 | }
72 |
73 | public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) {
74 | return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update);
75 | }
76 |
77 | public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) {
78 | return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update);
79 | }
80 |
81 | public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) {
82 | return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/api/controllers/label.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { HandleLabelDto } from '@api/dto/label.dto';
3 | import { WAMonitoringService } from '@api/services/monitor.service';
4 |
5 | export class LabelController {
6 | constructor(private readonly waMonitor: WAMonitoringService) {}
7 |
8 | public async fetchLabels({ instanceName }: InstanceDto) {
9 | return await this.waMonitor.waInstances[instanceName].fetchLabels();
10 | }
11 |
12 | public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) {
13 | return await this.waMonitor.waInstances[instanceName].handleLabel(data);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/api/controllers/proxy.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { ProxyDto } from '@api/dto/proxy.dto';
3 | import { WAMonitoringService } from '@api/services/monitor.service';
4 | import { ProxyService } from '@api/services/proxy.service';
5 | import { Logger } from '@config/logger.config';
6 | import { BadRequestException, NotFoundException } from '@exceptions';
7 | import { makeProxyAgent } from '@utils/makeProxyAgent';
8 | import axios from 'axios';
9 |
10 | const logger = new Logger('ProxyController');
11 |
12 | export class ProxyController {
13 | constructor(
14 | private readonly proxyService: ProxyService,
15 | private readonly waMonitor: WAMonitoringService,
16 | ) {}
17 |
18 | public async createProxy(instance: InstanceDto, data: ProxyDto) {
19 | if (!this.waMonitor.waInstances[instance.instanceName]) {
20 | throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
21 | }
22 |
23 | if (!data?.enabled) {
24 | data.host = '';
25 | data.port = '';
26 | data.protocol = '';
27 | data.username = '';
28 | data.password = '';
29 | }
30 |
31 | if (data.host) {
32 | const testProxy = await this.testProxy(data);
33 | if (!testProxy) {
34 | throw new BadRequestException('Invalid proxy');
35 | }
36 | }
37 |
38 | return this.proxyService.create(instance, data);
39 | }
40 |
41 | public async findProxy(instance: InstanceDto) {
42 | if (!this.waMonitor.waInstances[instance.instanceName]) {
43 | throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
44 | }
45 |
46 | return this.proxyService.find(instance);
47 | }
48 |
49 | public async testProxy(proxy: ProxyDto) {
50 | try {
51 | const serverIp = await axios.get('https://icanhazip.com/');
52 | const response = await axios.get('https://icanhazip.com/', {
53 | httpsAgent: makeProxyAgent(proxy),
54 | });
55 |
56 | return response?.data !== serverIp?.data;
57 | } catch (error) {
58 | if (axios.isAxiosError(error) && error.response?.data) {
59 | logger.error('testProxy error: ' + error.response.data);
60 | } else if (axios.isAxiosError(error)) {
61 | logger.error('testProxy error: ');
62 | } else {
63 | logger.error('testProxy error: ');
64 | }
65 | return false;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/api/controllers/settings.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { SettingsDto } from '@api/dto/settings.dto';
3 | import { SettingsService } from '@api/services/settings.service';
4 |
5 | export class SettingsController {
6 | constructor(private readonly settingsService: SettingsService) {}
7 |
8 | public async createSettings(instance: InstanceDto, data: SettingsDto) {
9 | return this.settingsService.create(instance, data);
10 | }
11 |
12 | public async findSettings(instance: InstanceDto) {
13 | const settings = this.settingsService.find(instance);
14 | return settings;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/api/controllers/template.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { TemplateDto } from '@api/dto/template.dto';
3 | import { TemplateService } from '@api/services/template.service';
4 |
5 | export class TemplateController {
6 | constructor(private readonly templateService: TemplateService) {}
7 |
8 | public async createTemplate(instance: InstanceDto, data: TemplateDto) {
9 | return this.templateService.create(instance, data);
10 | }
11 |
12 | public async findTemplate(instance: InstanceDto) {
13 | return this.templateService.find(instance);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/api/dto/call.dto.ts:
--------------------------------------------------------------------------------
1 | export class Metadata {
2 | number: string;
3 | }
4 |
5 | export class OfferCallDto extends Metadata {
6 | isVideo?: boolean;
7 | callDuration?: number;
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/dto/chat.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | proto,
3 | WAPresence,
4 | WAPrivacyGroupAddValue,
5 | WAPrivacyOnlineValue,
6 | WAPrivacyValue,
7 | WAReadReceiptsValue,
8 | } from 'baileys';
9 |
10 | export class OnWhatsAppDto {
11 | constructor(
12 | public readonly jid: string,
13 | public readonly exists: boolean,
14 | public readonly number: string,
15 | public readonly name?: string,
16 | ) {}
17 | }
18 |
19 | export class getBase64FromMediaMessageDto {
20 | message: proto.WebMessageInfo;
21 | convertToMp4?: boolean;
22 | }
23 |
24 | export class WhatsAppNumberDto {
25 | numbers: string[];
26 | }
27 |
28 | export class NumberDto {
29 | number: string;
30 | }
31 |
32 | export class NumberBusiness {
33 | wid?: string;
34 | jid?: string;
35 | exists?: boolean;
36 | isBusiness: boolean;
37 | name?: string;
38 | message?: string;
39 | description?: string;
40 | email?: string;
41 | websites?: string[];
42 | website?: string[];
43 | address?: string;
44 | about?: string;
45 | vertical?: string;
46 | profilehandle?: string;
47 | }
48 |
49 | export class ProfileNameDto {
50 | name: string;
51 | }
52 |
53 | export class ProfileStatusDto {
54 | status: string;
55 | }
56 |
57 | export class ProfilePictureDto {
58 | number?: string;
59 | // url or base64
60 | picture?: string;
61 | }
62 |
63 | class Key {
64 | id: string;
65 | fromMe: boolean;
66 | remoteJid: string;
67 | }
68 | export class ReadMessageDto {
69 | readMessages: Key[];
70 | }
71 |
72 | export class LastMessage {
73 | key: Key;
74 | messageTimestamp?: number;
75 | }
76 |
77 | export class ArchiveChatDto {
78 | lastMessage?: LastMessage;
79 | chat?: string;
80 | archive: boolean;
81 | }
82 |
83 | export class MarkChatUnreadDto {
84 | lastMessage?: LastMessage;
85 | chat?: string;
86 | }
87 |
88 | export class PrivacySettingDto {
89 | readreceipts: WAReadReceiptsValue;
90 | profile: WAPrivacyValue;
91 | status: WAPrivacyValue;
92 | online: WAPrivacyOnlineValue;
93 | last: WAPrivacyValue;
94 | groupadd: WAPrivacyGroupAddValue;
95 | }
96 |
97 | export class DeleteMessage {
98 | id: string;
99 | fromMe: boolean;
100 | remoteJid: string;
101 | participant?: string;
102 | }
103 | export class Options {
104 | delay?: number;
105 | presence?: WAPresence;
106 | }
107 | class OptionsMessage {
108 | options: Options;
109 | }
110 | export class Metadata extends OptionsMessage {
111 | number: string;
112 | }
113 |
114 | export class SendPresenceDto extends Metadata {
115 | presence: WAPresence;
116 | delay: number;
117 | }
118 |
119 | export class UpdateMessageDto extends Metadata {
120 | number: string;
121 | key: proto.IMessageKey;
122 | text: string;
123 | }
124 |
125 | export class BlockUserDto {
126 | number: string;
127 | status: 'block' | 'unblock';
128 | }
129 |
--------------------------------------------------------------------------------
/src/api/dto/chatbot.dto.ts:
--------------------------------------------------------------------------------
1 | export class Session {
2 | remoteJid?: string;
3 | sessionId?: string;
4 | status?: string;
5 | createdAt?: number;
6 | updateAt?: number;
7 | }
8 |
9 | export class IgnoreJidDto {
10 | remoteJid?: string;
11 | action?: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/api/dto/group.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateGroupDto {
2 | subject: string;
3 | participants: string[];
4 | description?: string;
5 | promoteParticipants?: boolean;
6 | }
7 |
8 | export class GroupPictureDto {
9 | groupJid: string;
10 | image: string;
11 | }
12 |
13 | export class GroupSubjectDto {
14 | groupJid: string;
15 | subject: string;
16 | }
17 |
18 | export class GroupDescriptionDto {
19 | groupJid: string;
20 | description: string;
21 | }
22 |
23 | export class GroupJid {
24 | groupJid: string;
25 | }
26 |
27 | export class GetParticipant {
28 | getParticipants: string;
29 | }
30 |
31 | export class GroupInvite {
32 | inviteCode: string;
33 | }
34 |
35 | export class AcceptGroupInvite {
36 | inviteCode: string;
37 | }
38 |
39 | export class GroupSendInvite {
40 | groupJid: string;
41 | description: string;
42 | numbers: string[];
43 | }
44 |
45 | export class GroupUpdateParticipantDto extends GroupJid {
46 | action: 'add' | 'remove' | 'promote' | 'demote';
47 | participants: string[];
48 | }
49 |
50 | export class GroupUpdateSettingDto extends GroupJid {
51 | action: 'announcement' | 'not_announcement' | 'unlocked' | 'locked';
52 | }
53 |
54 | export class GroupToggleEphemeralDto extends GroupJid {
55 | expiration: 0 | 86400 | 604800 | 7776000;
56 | }
57 |
--------------------------------------------------------------------------------
/src/api/dto/instance.dto.ts:
--------------------------------------------------------------------------------
1 | import { IntegrationDto } from '@api/integrations/integration.dto';
2 | import { JsonValue } from '@prisma/client/runtime/library';
3 | import { WAPresence } from 'baileys';
4 |
5 | export class InstanceDto extends IntegrationDto {
6 | instanceName: string;
7 | instanceId?: string;
8 | qrcode?: boolean;
9 | businessId?: string;
10 | number?: string;
11 | integration?: string;
12 | token?: string;
13 | status?: string;
14 | ownerJid?: string;
15 | profileName?: string;
16 | profilePicUrl?: string;
17 | // settings
18 | rejectCall?: boolean;
19 | msgCall?: string;
20 | groupsIgnore?: boolean;
21 | alwaysOnline?: boolean;
22 | readMessages?: boolean;
23 | readStatus?: boolean;
24 | syncFullHistory?: boolean;
25 | wavoipToken?: string;
26 | // proxy
27 | proxyHost?: string;
28 | proxyPort?: string;
29 | proxyProtocol?: string;
30 | proxyUsername?: string;
31 | proxyPassword?: string;
32 | webhook?: {
33 | enabled?: boolean;
34 | events?: string[];
35 | headers?: JsonValue;
36 | url?: string;
37 | byEvents?: boolean;
38 | base64?: boolean;
39 | };
40 | chatwootAccountId?: string;
41 | chatwootConversationPending?: boolean;
42 | chatwootAutoCreate?: boolean;
43 | chatwootDaysLimitImportMessages?: number;
44 | chatwootImportContacts?: boolean;
45 | chatwootImportMessages?: boolean;
46 | chatwootLogo?: string;
47 | chatwootMergeBrazilContacts?: boolean;
48 | chatwootNameInbox?: string;
49 | chatwootOrganization?: string;
50 | chatwootReopenConversation?: boolean;
51 | chatwootSignMsg?: boolean;
52 | chatwootToken?: string;
53 | chatwootUrl?: string;
54 | }
55 |
56 | export class SetPresenceDto {
57 | presence: WAPresence;
58 | }
59 |
--------------------------------------------------------------------------------
/src/api/dto/label.dto.ts:
--------------------------------------------------------------------------------
1 | export class LabelDto {
2 | id?: string;
3 | name: string;
4 | color: string;
5 | predefinedId?: string;
6 | }
7 |
8 | export class HandleLabelDto {
9 | number: string;
10 | labelId: string;
11 | action: 'add' | 'remove';
12 | }
13 |
--------------------------------------------------------------------------------
/src/api/dto/proxy.dto.ts:
--------------------------------------------------------------------------------
1 | export class ProxyDto {
2 | enabled?: boolean;
3 | host: string;
4 | port: string;
5 | protocol: string;
6 | username?: string;
7 | password?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/dto/settings.dto.ts:
--------------------------------------------------------------------------------
1 | export class SettingsDto {
2 | rejectCall?: boolean;
3 | msgCall?: string;
4 | groupsIgnore?: boolean;
5 | alwaysOnline?: boolean;
6 | readMessages?: boolean;
7 | readStatus?: boolean;
8 | syncFullHistory?: boolean;
9 | wavoipToken?: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/dto/template.dto.ts:
--------------------------------------------------------------------------------
1 | export class TemplateDto {
2 | name: string;
3 | category: string;
4 | allowCategoryChange: boolean;
5 | language: string;
6 | components: any;
7 | webhookUrl?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/guards/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { prismaRepository } from '@api/server.module';
3 | import { Auth, configService, Database } from '@config/env.config';
4 | import { Logger } from '@config/logger.config';
5 | import { ForbiddenException, UnauthorizedException } from '@exceptions';
6 | import { NextFunction, Request, Response } from 'express';
7 |
8 | const logger = new Logger('GUARD');
9 |
10 | async function apikey(req: Request, _: Response, next: NextFunction) {
11 | const env = configService.get('AUTHENTICATION').API_KEY;
12 | const key = req.get('apikey');
13 | const db = configService.get('DATABASE');
14 |
15 | if (!key) {
16 | throw new UnauthorizedException();
17 | }
18 |
19 | if (env.KEY === key) {
20 | return next();
21 | }
22 |
23 | if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
24 | throw new ForbiddenException('Missing global api key', 'The global api key must be set');
25 | }
26 | const param = req.params as unknown as InstanceDto;
27 |
28 | try {
29 | if (param?.instanceName) {
30 | const instance = await prismaRepository.instance.findUnique({
31 | where: { name: param.instanceName },
32 | });
33 | if (instance.token === key) {
34 | return next();
35 | }
36 | } else {
37 | if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {
38 | const instanceByKey = await prismaRepository.instance.findFirst({
39 | where: { token: key },
40 | });
41 | if (instanceByKey) {
42 | return next();
43 | }
44 | }
45 | }
46 | } catch (error) {
47 | logger.error(error);
48 | }
49 |
50 | throw new UnauthorizedException();
51 | }
52 |
53 | export const authGuard = { apikey };
54 |
--------------------------------------------------------------------------------
/src/api/guards/instance.guard.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { cache, prismaRepository, waMonitor } from '@api/server.module';
3 | import { CacheConf, configService } from '@config/env.config';
4 | import { BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException } from '@exceptions';
5 | import { NextFunction, Request, Response } from 'express';
6 |
7 | async function getInstance(instanceName: string) {
8 | try {
9 | const cacheConf = configService.get('CACHE');
10 |
11 | const exists = !!waMonitor.waInstances[instanceName];
12 |
13 | if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
14 | const keyExists = await cache.has(instanceName);
15 |
16 | return exists || keyExists;
17 | }
18 |
19 | return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;
20 | } catch (error) {
21 | throw new InternalServerErrorException(error?.toString());
22 | }
23 | }
24 |
25 | export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
26 | if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) {
27 | return next();
28 | }
29 |
30 | const param = req.params as unknown as InstanceDto;
31 | if (!param?.instanceName) {
32 | throw new BadRequestException('"instanceName" not provided.');
33 | }
34 |
35 | if (!(await getInstance(param.instanceName))) {
36 | throw new NotFoundException(`The "${param.instanceName}" instance does not exist`);
37 | }
38 |
39 | next();
40 | }
41 |
42 | export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {
43 | if (req.originalUrl.includes('/instance/create')) {
44 | const instance = req.body as InstanceDto;
45 | if (await getInstance(instance.instanceName)) {
46 | throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
47 | }
48 |
49 | if (waMonitor.waInstances[instance.instanceName]) {
50 | delete waMonitor.waInstances[instance.instanceName];
51 | }
52 | }
53 |
54 | next();
55 | }
56 |
--------------------------------------------------------------------------------
/src/api/guards/telemetry.guard.ts:
--------------------------------------------------------------------------------
1 | import { sendTelemetry } from '@utils/sendTelemetry';
2 | import { NextFunction, Request, Response } from 'express';
3 |
4 | class Telemetry {
5 | public collectTelemetry(req: Request, res: Response, next: NextFunction): void {
6 | sendTelemetry(req.path);
7 |
8 | next();
9 | }
10 | }
11 |
12 | export default Telemetry;
13 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/channel.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { ProviderFiles } from '@api/provider/sessions';
3 | import { PrismaRepository } from '@api/repository/repository.service';
4 | import { CacheService } from '@api/services/cache.service';
5 | import { WAMonitoringService } from '@api/services/monitor.service';
6 | import { Integration } from '@api/types/wa.types';
7 | import { ConfigService } from '@config/env.config';
8 | import { BadRequestException } from '@exceptions';
9 | import EventEmitter2 from 'eventemitter2';
10 |
11 | import { EvolutionStartupService } from './evolution/evolution.channel.service';
12 | import { BusinessStartupService } from './meta/whatsapp.business.service';
13 | import { BaileysStartupService } from './whatsapp/whatsapp.baileys.service';
14 |
15 | type ChannelDataType = {
16 | configService: ConfigService;
17 | eventEmitter: EventEmitter2;
18 | prismaRepository: PrismaRepository;
19 | cache: CacheService;
20 | chatwootCache: CacheService;
21 | baileysCache: CacheService;
22 | providerFiles: ProviderFiles;
23 | };
24 |
25 | export interface ChannelControllerInterface {
26 | receiveWebhook(data: any): Promise;
27 | }
28 |
29 | export class ChannelController {
30 | public prismaRepository: PrismaRepository;
31 | public waMonitor: WAMonitoringService;
32 |
33 | constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
34 | this.prisma = prismaRepository;
35 | this.monitor = waMonitor;
36 | }
37 |
38 | public set prisma(prisma: PrismaRepository) {
39 | this.prismaRepository = prisma;
40 | }
41 |
42 | public get prisma() {
43 | return this.prismaRepository;
44 | }
45 |
46 | public set monitor(waMonitor: WAMonitoringService) {
47 | this.waMonitor = waMonitor;
48 | }
49 |
50 | public get monitor() {
51 | return this.waMonitor;
52 | }
53 |
54 | public init(instanceData: InstanceDto, data: ChannelDataType) {
55 | if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {
56 | throw new BadRequestException('token is required');
57 | }
58 |
59 | if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
60 | return new BusinessStartupService(
61 | data.configService,
62 | data.eventEmitter,
63 | data.prismaRepository,
64 | data.cache,
65 | data.chatwootCache,
66 | data.baileysCache,
67 | data.providerFiles,
68 | );
69 | }
70 |
71 | if (instanceData.integration === Integration.EVOLUTION) {
72 | return new EvolutionStartupService(
73 | data.configService,
74 | data.eventEmitter,
75 | data.prismaRepository,
76 | data.cache,
77 | data.chatwootCache,
78 | );
79 | }
80 |
81 | if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {
82 | return new BaileysStartupService(
83 | data.configService,
84 | data.eventEmitter,
85 | data.prismaRepository,
86 | data.cache,
87 | data.chatwootCache,
88 | data.baileysCache,
89 | data.providerFiles,
90 | );
91 | }
92 |
93 | return null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/channel.router.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 |
3 | import { EvolutionRouter } from './evolution/evolution.router';
4 | import { MetaRouter } from './meta/meta.router';
5 | import { BaileysRouter } from './whatsapp/baileys.router';
6 |
7 | export class ChannelRouter {
8 | public readonly router: Router;
9 |
10 | constructor(configService: any, ...guards: any[]) {
11 | this.router = Router();
12 |
13 | this.router.use('/', new EvolutionRouter(configService).router);
14 | this.router.use('/', new MetaRouter(configService).router);
15 | this.router.use('/baileys', new BaileysRouter(...guards).router);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/evolution/evolution.controller.ts:
--------------------------------------------------------------------------------
1 | import { PrismaRepository } from '@api/repository/repository.service';
2 | import { WAMonitoringService } from '@api/services/monitor.service';
3 | import { Logger } from '@config/logger.config';
4 |
5 | import { ChannelController, ChannelControllerInterface } from '../channel.controller';
6 |
7 | export class EvolutionController extends ChannelController implements ChannelControllerInterface {
8 | private readonly logger = new Logger('EvolutionController');
9 |
10 | constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
11 | super(prismaRepository, waMonitor);
12 | }
13 |
14 | integrationEnabled: boolean;
15 |
16 | public async receiveWebhook(data: any) {
17 | const numberId = data.numberId;
18 |
19 | if (!numberId) {
20 | this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');
21 | return;
22 | }
23 |
24 | const instance = await this.prismaRepository.instance.findFirst({
25 | where: { number: numberId },
26 | });
27 |
28 | if (!instance) {
29 | this.logger.error('WebhookService -> receiveWebhook -> instance not found');
30 | return;
31 | }
32 |
33 | await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
34 |
35 | return {
36 | status: 'success',
37 | };
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/evolution/evolution.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { evolutionController } from '@api/server.module';
3 | import { ConfigService } from '@config/env.config';
4 | import { Router } from 'express';
5 |
6 | export class EvolutionRouter extends RouterBroker {
7 | constructor(readonly configService: ConfigService) {
8 | super();
9 | this.router.post(this.routerPath('webhook/evolution', false), async (req, res) => {
10 | const { body } = req;
11 | const response = await evolutionController.receiveWebhook(body);
12 |
13 | return res.status(200).json(response);
14 | });
15 | }
16 |
17 | public readonly router: Router = Router();
18 | }
19 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/meta/meta.controller.ts:
--------------------------------------------------------------------------------
1 | import { PrismaRepository } from '@api/repository/repository.service';
2 | import { WAMonitoringService } from '@api/services/monitor.service';
3 | import { Logger } from '@config/logger.config';
4 | import axios from 'axios';
5 |
6 | import { ChannelController, ChannelControllerInterface } from '../channel.controller';
7 |
8 | export class MetaController extends ChannelController implements ChannelControllerInterface {
9 | private readonly logger = new Logger('MetaController');
10 |
11 | constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
12 | super(prismaRepository, waMonitor);
13 | }
14 |
15 | integrationEnabled: boolean;
16 |
17 | public async receiveWebhook(data: any) {
18 | if (data.object === 'whatsapp_business_account') {
19 | if (data.entry[0]?.changes[0]?.field === 'message_template_status_update') {
20 | const template = await this.prismaRepository.template.findFirst({
21 | where: { templateId: `${data.entry[0].changes[0].value.message_template_id}` },
22 | });
23 |
24 | if (!template) {
25 | console.log('template not found');
26 | return;
27 | }
28 |
29 | const { webhookUrl } = template;
30 |
31 | await axios.post(webhookUrl, data.entry[0].changes[0].value, {
32 | headers: {
33 | 'Content-Type': 'application/json',
34 | },
35 | });
36 | return;
37 | }
38 |
39 | data.entry?.forEach(async (entry: any) => {
40 | const numberId = entry.changes[0].value.metadata.phone_number_id;
41 |
42 | if (!numberId) {
43 | this.logger.error('WebhookService -> receiveWebhookMeta -> numberId not found');
44 | return {
45 | status: 'success',
46 | };
47 | }
48 |
49 | const instance = await this.prismaRepository.instance.findFirst({
50 | where: { number: numberId },
51 | });
52 |
53 | if (!instance) {
54 | this.logger.error('WebhookService -> receiveWebhookMeta -> instance not found');
55 | return {
56 | status: 'success',
57 | };
58 | }
59 |
60 | await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
61 |
62 | return {
63 | status: 'success',
64 | };
65 | });
66 | }
67 |
68 | return {
69 | status: 'success',
70 | };
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/meta/meta.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { metaController } from '@api/server.module';
3 | import { ConfigService, WaBusiness } from '@config/env.config';
4 | import { Router } from 'express';
5 |
6 | export class MetaRouter extends RouterBroker {
7 | constructor(readonly configService: ConfigService) {
8 | super();
9 | this.router
10 | .get(this.routerPath('webhook/meta', false), async (req, res) => {
11 | if (req.query['hub.verify_token'] === configService.get('WA_BUSINESS').TOKEN_WEBHOOK)
12 | res.send(req.query['hub.challenge']);
13 | else res.send('Error, wrong validation token');
14 | })
15 | .post(this.routerPath('webhook/meta', false), async (req, res) => {
16 | const { body } = req;
17 | const response = await metaController.receiveWebhook(body);
18 |
19 | return res.status(200).json(response);
20 | });
21 | }
22 |
23 | public readonly router: Router = Router();
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/whatsapp/baileys.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { WAMonitoringService } from '@api/services/monitor.service';
3 |
4 | export class BaileysController {
5 | constructor(private readonly waMonitor: WAMonitoringService) {}
6 |
7 | public async onWhatsapp({ instanceName }: InstanceDto, body: any) {
8 | const instance = this.waMonitor.waInstances[instanceName];
9 |
10 | return instance.baileysOnWhatsapp(body?.jid);
11 | }
12 |
13 | public async profilePictureUrl({ instanceName }: InstanceDto, body: any) {
14 | const instance = this.waMonitor.waInstances[instanceName];
15 |
16 | return instance.baileysProfilePictureUrl(body?.jid, body?.type, body?.timeoutMs);
17 | }
18 |
19 | public async assertSessions({ instanceName }: InstanceDto, body: any) {
20 | const instance = this.waMonitor.waInstances[instanceName];
21 |
22 | return instance.baileysAssertSessions(body?.jids, body?.force);
23 | }
24 |
25 | public async createParticipantNodes({ instanceName }: InstanceDto, body: any) {
26 | const instance = this.waMonitor.waInstances[instanceName];
27 |
28 | return instance.baileysCreateParticipantNodes(body?.jids, body?.message, body?.extraAttrs);
29 | }
30 |
31 | public async getUSyncDevices({ instanceName }: InstanceDto, body: any) {
32 | const instance = this.waMonitor.waInstances[instanceName];
33 |
34 | return instance.baileysGetUSyncDevices(body?.jids, body?.useCache, body?.ignoreZeroDevices);
35 | }
36 |
37 | public async generateMessageTag({ instanceName }: InstanceDto) {
38 | const instance = this.waMonitor.waInstances[instanceName];
39 |
40 | return instance.baileysGenerateMessageTag();
41 | }
42 |
43 | public async sendNode({ instanceName }: InstanceDto, body: any) {
44 | const instance = this.waMonitor.waInstances[instanceName];
45 |
46 | return instance.baileysSendNode(body?.stanza);
47 | }
48 |
49 | public async signalRepositoryDecryptMessage({ instanceName }: InstanceDto, body: any) {
50 | const instance = this.waMonitor.waInstances[instanceName];
51 |
52 | return instance.baileysSignalRepositoryDecryptMessage(body?.jid, body?.type, body?.ciphertext);
53 | }
54 |
55 | public async getAuthState({ instanceName }: InstanceDto) {
56 | const instance = this.waMonitor.waInstances[instanceName];
57 |
58 | return instance.baileysGetAuthState();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/api/integrations/channel/whatsapp/voiceCalls/transport.type.ts:
--------------------------------------------------------------------------------
1 | import { BinaryNode, Contact, JidWithDevice, proto, WAConnectionState } from 'baileys';
2 |
3 | export interface ServerToClientEvents {
4 | withAck: (d: string, callback: (e: number) => void) => void;
5 | onWhatsApp: onWhatsAppType;
6 | profilePictureUrl: ProfilePictureUrlType;
7 | assertSessions: AssertSessionsType;
8 | createParticipantNodes: CreateParticipantNodesType;
9 | getUSyncDevices: GetUSyncDevicesType;
10 | generateMessageTag: GenerateMessageTagType;
11 | sendNode: SendNodeType;
12 | 'signalRepository:decryptMessage': SignalRepositoryDecryptMessageType;
13 | }
14 |
15 | export interface ClientToServerEvents {
16 | init: (
17 | me: Contact | undefined,
18 | account: proto.IADVSignedDeviceIdentity | undefined,
19 | status: WAConnectionState,
20 | ) => void;
21 | 'CB:call': (packet: any) => void;
22 | 'CB:ack,class:call': (packet: any) => void;
23 | 'connection.update:status': (
24 | me: Contact | undefined,
25 | account: proto.IADVSignedDeviceIdentity | undefined,
26 | status: WAConnectionState,
27 | ) => void;
28 | 'connection.update:qr': (qr: string) => void;
29 | }
30 |
31 | export type onWhatsAppType = (jid: string, callback: onWhatsAppCallback) => void;
32 | export type onWhatsAppCallback = (
33 | response: {
34 | exists: boolean;
35 | jid: string;
36 | }[],
37 | ) => void;
38 |
39 | export type ProfilePictureUrlType = (
40 | jid: string,
41 | type: 'image' | 'preview',
42 | timeoutMs: number | undefined,
43 | callback: ProfilePictureUrlCallback,
44 | ) => void;
45 | export type ProfilePictureUrlCallback = (response: string | undefined) => void;
46 |
47 | export type AssertSessionsType = (jids: string[], force: boolean, callback: AssertSessionsCallback) => void;
48 | export type AssertSessionsCallback = (response: boolean) => void;
49 |
50 | export type CreateParticipantNodesType = (
51 | jids: string[],
52 | message: any,
53 | extraAttrs: any,
54 | callback: CreateParticipantNodesCallback,
55 | ) => void;
56 | export type CreateParticipantNodesCallback = (nodes: any, shouldIncludeDeviceIdentity: boolean) => void;
57 |
58 | export type GetUSyncDevicesType = (
59 | jids: string[],
60 | useCache: boolean,
61 | ignoreZeroDevices: boolean,
62 | callback: GetUSyncDevicesTypeCallback,
63 | ) => void;
64 | export type GetUSyncDevicesTypeCallback = (jids: JidWithDevice[]) => void;
65 |
66 | export type GenerateMessageTagType = (callback: GenerateMessageTagTypeCallback) => void;
67 | export type GenerateMessageTagTypeCallback = (response: string) => void;
68 |
69 | export type SendNodeType = (stanza: BinaryNode, callback: SendNodeTypeCallback) => void;
70 | export type SendNodeTypeCallback = (response: boolean) => void;
71 |
72 | export type SignalRepositoryDecryptMessageType = (
73 | jid: string,
74 | type: 'pkmsg' | 'msg',
75 | ciphertext: Buffer,
76 | callback: SignalRepositoryDecryptMessageCallback,
77 | ) => void;
78 | export type SignalRepositoryDecryptMessageCallback = (response: any) => void;
79 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatbot.router.ts:
--------------------------------------------------------------------------------
1 | import { ChatwootRouter } from '@api/integrations/chatbot/chatwoot/routes/chatwoot.router';
2 | import { DifyRouter } from '@api/integrations/chatbot/dify/routes/dify.router';
3 | import { OpenaiRouter } from '@api/integrations/chatbot/openai/routes/openai.router';
4 | import { TypebotRouter } from '@api/integrations/chatbot/typebot/routes/typebot.router';
5 | import { Router } from 'express';
6 |
7 | import { EvolutionBotRouter } from './evolutionBot/routes/evolutionBot.router';
8 | import { FlowiseRouter } from './flowise/routes/flowise.router';
9 |
10 | export class ChatbotRouter {
11 | public readonly router: Router;
12 |
13 | constructor(...guards: any[]) {
14 | this.router = Router();
15 |
16 | this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);
17 | this.router.use('/chatwoot', new ChatwootRouter(...guards).router);
18 | this.router.use('/typebot', new TypebotRouter(...guards).router);
19 | this.router.use('/openai', new OpenaiRouter(...guards).router);
20 | this.router.use('/dify', new DifyRouter(...guards).router);
21 | this.router.use('/flowise', new FlowiseRouter(...guards).router);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatbot.schema.ts:
--------------------------------------------------------------------------------
1 | export * from '@api/integrations/chatbot/chatwoot/validate/chatwoot.schema';
2 | export * from '@api/integrations/chatbot/dify/validate/dify.schema';
3 | export * from '@api/integrations/chatbot/evolutionBot/validate/evolutionBot.schema';
4 | export * from '@api/integrations/chatbot/flowise/validate/flowise.schema';
5 | export * from '@api/integrations/chatbot/openai/validate/openai.schema';
6 | export * from '@api/integrations/chatbot/typebot/validate/typebot.schema';
7 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatwoot/controllers/chatwoot.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
3 | import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
4 | import { PrismaRepository } from '@api/repository/repository.service';
5 | import { waMonitor } from '@api/server.module';
6 | import { CacheService } from '@api/services/cache.service';
7 | import { CacheEngine } from '@cache/cacheengine';
8 | import { Chatwoot, ConfigService, HttpServer } from '@config/env.config';
9 | import { BadRequestException } from '@exceptions';
10 | import { isURL } from 'class-validator';
11 |
12 | export class ChatwootController {
13 | constructor(
14 | private readonly chatwootService: ChatwootService,
15 | private readonly configService: ConfigService,
16 | private readonly prismaRepository: PrismaRepository,
17 | ) {}
18 |
19 | public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
20 | if (!this.configService.get('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
21 |
22 | if (data?.enabled) {
23 | if (!isURL(data.url, { require_tld: false })) {
24 | throw new BadRequestException('url is not valid');
25 | }
26 |
27 | if (!data.accountId) {
28 | throw new BadRequestException('accountId is required');
29 | }
30 |
31 | if (!data.token) {
32 | throw new BadRequestException('token is required');
33 | }
34 |
35 | if (data.signMsg !== true && data.signMsg !== false) {
36 | throw new BadRequestException('signMsg is required');
37 | }
38 | if (data.signMsg === false) data.signDelimiter = null;
39 | }
40 |
41 | if (!data.nameInbox || data.nameInbox === '') {
42 | data.nameInbox = instance.instanceName;
43 | }
44 |
45 | const result = await this.chatwootService.create(instance, data);
46 |
47 | const urlServer = this.configService.get('SERVER').URL;
48 |
49 | const response = {
50 | ...result,
51 | webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
52 | };
53 |
54 | return response;
55 | }
56 |
57 | public async findChatwoot(instance: InstanceDto): Promise {
58 | if (!this.configService.get('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
59 |
60 | const result = await this.chatwootService.find(instance);
61 |
62 | const urlServer = this.configService.get('SERVER').URL;
63 |
64 | if (Object.keys(result || {}).length === 0) {
65 | return {
66 | enabled: false,
67 | url: '',
68 | accountId: '',
69 | token: '',
70 | signMsg: false,
71 | nameInbox: '',
72 | webhook_url: '',
73 | };
74 | }
75 |
76 | const response = {
77 | ...result,
78 | webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
79 | };
80 |
81 | return response;
82 | }
83 |
84 | public async receiveWebhook(instance: InstanceDto, data: any) {
85 | if (!this.configService.get('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
86 |
87 | const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
88 | const chatwootService = new ChatwootService(waMonitor, this.configService, this.prismaRepository, chatwootCache);
89 |
90 | return chatwootService.receiveWebhook(instance, data);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatwoot/dto/chatwoot.dto.ts:
--------------------------------------------------------------------------------
1 | import { Constructor } from '@api/integrations/integration.dto';
2 |
3 | export class ChatwootDto {
4 | enabled?: boolean;
5 | accountId?: string;
6 | token?: string;
7 | url?: string;
8 | nameInbox?: string;
9 | signMsg?: boolean;
10 | signDelimiter?: string;
11 | number?: string;
12 | reopenConversation?: boolean;
13 | conversationPending?: boolean;
14 | mergeBrazilContacts?: boolean;
15 | importContacts?: boolean;
16 | importMessages?: boolean;
17 | daysLimitImportMessages?: number;
18 | autoCreate?: boolean;
19 | organization?: string;
20 | logo?: string;
21 | ignoreJids?: string[];
22 | }
23 |
24 | export function ChatwootInstanceMixin(Base: TBase) {
25 | return class extends Base {
26 | chatwootAccountId?: string;
27 | chatwootToken?: string;
28 | chatwootUrl?: string;
29 | chatwootSignMsg?: boolean;
30 | chatwootReopenConversation?: boolean;
31 | chatwootConversationPending?: boolean;
32 | chatwootMergeBrazilContacts?: boolean;
33 | chatwootImportContacts?: boolean;
34 | chatwootImportMessages?: boolean;
35 | chatwootDaysLimitImportMessages?: number;
36 | chatwootNameInbox?: string;
37 | chatwootOrganization?: string;
38 | chatwootLogo?: string;
39 | chatwootAutoCreate?: boolean;
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatwoot/libs/postgres.client.ts:
--------------------------------------------------------------------------------
1 | import { Chatwoot, configService } from '@config/env.config';
2 | import { Logger } from '@config/logger.config';
3 | import postgresql from 'pg';
4 |
5 | const { Pool } = postgresql;
6 |
7 | class Postgres {
8 | private logger = new Logger('Postgres');
9 | private pool;
10 | private connected = false;
11 |
12 | getConnection(connectionString: string) {
13 | if (this.connected) {
14 | return this.pool;
15 | } else {
16 | this.pool = new Pool({
17 | connectionString,
18 | ssl: {
19 | rejectUnauthorized: false,
20 | },
21 | });
22 |
23 | this.pool.on('error', () => {
24 | this.logger.error('postgres disconnected');
25 | this.connected = false;
26 | });
27 |
28 | try {
29 | this.connected = true;
30 | } catch (e) {
31 | this.connected = false;
32 | this.logger.error('postgres connect exception caught: ' + e);
33 | return null;
34 | }
35 |
36 | return this.pool;
37 | }
38 | }
39 |
40 | getChatwootConnection() {
41 | const uri = configService.get('CHATWOOT').IMPORT.DATABASE.CONNECTION.URI;
42 |
43 | return this.getConnection(uri);
44 | }
45 | }
46 |
47 | export const postgresClient = new Postgres();
48 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatwoot/routes/chatwoot.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { chatwootController } from '@api/server.module';
6 | import { chatwootSchema, instanceSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 |
9 | export class ChatwootRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router
13 | .post(this.routerPath('set'), ...guards, async (req, res) => {
14 | const response = await this.dataValidate({
15 | request: req,
16 | schema: chatwootSchema,
17 | ClassRef: ChatwootDto,
18 | execute: (instance, data) => chatwootController.createChatwoot(instance, data),
19 | });
20 |
21 | res.status(HttpStatus.CREATED).json(response);
22 | })
23 | .get(this.routerPath('find'), ...guards, async (req, res) => {
24 | const response = await this.dataValidate({
25 | request: req,
26 | schema: instanceSchema,
27 | ClassRef: InstanceDto,
28 | execute: (instance) => chatwootController.findChatwoot(instance),
29 | });
30 |
31 | res.status(HttpStatus.OK).json(response);
32 | })
33 | .post(this.routerPath('webhook'), async (req, res) => {
34 | const response = await this.dataValidate({
35 | request: req,
36 | schema: instanceSchema,
37 | ClassRef: InstanceDto,
38 | execute: (instance, data) => chatwootController.receiveWebhook(instance, data),
39 | });
40 |
41 | res.status(HttpStatus.OK).json(response);
42 | });
43 | }
44 |
45 | public readonly router: Router = Router();
46 | }
47 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/chatwoot/validate/chatwoot.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const chatwootSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean', enum: [true, false] },
28 | accountId: { type: 'string' },
29 | token: { type: 'string' },
30 | url: { type: 'string' },
31 | signMsg: { type: 'boolean', enum: [true, false] },
32 | signDelimiter: { type: ['string', 'null'] },
33 | nameInbox: { type: ['string', 'null'] },
34 | reopenConversation: { type: 'boolean', enum: [true, false] },
35 | conversationPending: { type: 'boolean', enum: [true, false] },
36 | autoCreate: { type: 'boolean', enum: [true, false] },
37 | importContacts: { type: 'boolean', enum: [true, false] },
38 | mergeBrazilContacts: { type: 'boolean', enum: [true, false] },
39 | importMessages: { type: 'boolean', enum: [true, false] },
40 | daysLimitImportMessages: { type: 'number' },
41 | ignoreJids: { type: 'array', items: { type: 'string' } },
42 | },
43 | required: ['enabled', 'accountId', 'token', 'url', 'signMsg', 'reopenConversation', 'conversationPending'],
44 | ...isNotEmpty('enabled', 'accountId', 'token', 'url', 'signMsg', 'reopenConversation', 'conversationPending'),
45 | };
46 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/dify/dto/dify.dto.ts:
--------------------------------------------------------------------------------
1 | import { $Enums, TriggerOperator, TriggerType } from '@prisma/client';
2 |
3 | export class DifyDto {
4 | enabled?: boolean;
5 | description?: string;
6 | botType?: $Enums.DifyBotType;
7 | apiUrl?: string;
8 | apiKey?: string;
9 | expire?: number;
10 | keywordFinish?: string;
11 | delayMessage?: number;
12 | unknownMessage?: string;
13 | listeningFromMe?: boolean;
14 | stopBotFromMe?: boolean;
15 | keepOpen?: boolean;
16 | debounceTime?: number;
17 | triggerType?: TriggerType;
18 | triggerOperator?: TriggerOperator;
19 | triggerValue?: string;
20 | ignoreJids?: any;
21 | splitMessages?: boolean;
22 | timePerChar?: number;
23 | }
24 |
25 | export class DifySettingDto {
26 | expire?: number;
27 | keywordFinish?: string;
28 | delayMessage?: number;
29 | unknownMessage?: string;
30 | listeningFromMe?: boolean;
31 | stopBotFromMe?: boolean;
32 | keepOpen?: boolean;
33 | debounceTime?: number;
34 | difyIdFallback?: string;
35 | ignoreJids?: any;
36 | splitMessages?: boolean;
37 | timePerChar?: number;
38 | }
39 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/evolutionBot/dto/evolutionBot.dto.ts:
--------------------------------------------------------------------------------
1 | import { TriggerOperator, TriggerType } from '@prisma/client';
2 |
3 | export class EvolutionBotDto {
4 | enabled?: boolean;
5 | description?: string;
6 | apiUrl?: string;
7 | apiKey?: string;
8 | expire?: number;
9 | keywordFinish?: string;
10 | delayMessage?: number;
11 | unknownMessage?: string;
12 | listeningFromMe?: boolean;
13 | stopBotFromMe?: boolean;
14 | keepOpen?: boolean;
15 | debounceTime?: number;
16 | triggerType?: TriggerType;
17 | triggerOperator?: TriggerOperator;
18 | triggerValue?: string;
19 | ignoreJids?: any;
20 | splitMessages?: boolean;
21 | timePerChar?: number;
22 | }
23 |
24 | export class EvolutionBotSettingDto {
25 | expire?: number;
26 | keywordFinish?: string;
27 | delayMessage?: number;
28 | unknownMessage?: string;
29 | listeningFromMe?: boolean;
30 | stopBotFromMe?: boolean;
31 | keepOpen?: boolean;
32 | debounceTime?: number;
33 | botIdFallback?: string;
34 | ignoreJids?: any;
35 | splitMessages?: boolean;
36 | timePerChar?: number;
37 | }
38 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/evolutionBot/validate/evolutionBot.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const evolutionBotSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean' },
28 | description: { type: 'string' },
29 | apiUrl: { type: 'string' },
30 | apiKey: { type: 'string' },
31 | triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
32 | triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
33 | triggerValue: { type: 'string' },
34 | expire: { type: 'integer' },
35 | keywordFinish: { type: 'string' },
36 | delayMessage: { type: 'integer' },
37 | unknownMessage: { type: 'string' },
38 | listeningFromMe: { type: 'boolean' },
39 | stopBotFromMe: { type: 'boolean' },
40 | keepOpen: { type: 'boolean' },
41 | debounceTime: { type: 'integer' },
42 | ignoreJids: { type: 'array', items: { type: 'string' } },
43 | splitMessages: { type: 'boolean' },
44 | timePerChar: { type: 'integer' },
45 | },
46 | required: ['enabled', 'apiUrl', 'triggerType'],
47 | ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
48 | };
49 |
50 | export const evolutionBotStatusSchema: JSONSchema7 = {
51 | $id: v4(),
52 | type: 'object',
53 | properties: {
54 | remoteJid: { type: 'string' },
55 | status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
56 | },
57 | required: ['remoteJid', 'status'],
58 | ...isNotEmpty('remoteJid', 'status'),
59 | };
60 |
61 | export const evolutionBotSettingSchema: JSONSchema7 = {
62 | $id: v4(),
63 | type: 'object',
64 | properties: {
65 | expire: { type: 'integer' },
66 | keywordFinish: { type: 'string' },
67 | delayMessage: { type: 'integer' },
68 | unknownMessage: { type: 'string' },
69 | listeningFromMe: { type: 'boolean' },
70 | stopBotFromMe: { type: 'boolean' },
71 | keepOpen: { type: 'boolean' },
72 | debounceTime: { type: 'integer' },
73 | ignoreJids: { type: 'array', items: { type: 'string' } },
74 | botIdFallback: { type: 'string' },
75 | splitMessages: { type: 'boolean' },
76 | timePerChar: { type: 'integer' },
77 | },
78 | required: [
79 | 'expire',
80 | 'keywordFinish',
81 | 'delayMessage',
82 | 'unknownMessage',
83 | 'listeningFromMe',
84 | 'stopBotFromMe',
85 | 'keepOpen',
86 | 'debounceTime',
87 | 'ignoreJids',
88 | 'splitMessages',
89 | 'timePerChar',
90 | ],
91 | ...isNotEmpty(
92 | 'expire',
93 | 'keywordFinish',
94 | 'delayMessage',
95 | 'unknownMessage',
96 | 'listeningFromMe',
97 | 'stopBotFromMe',
98 | 'keepOpen',
99 | 'debounceTime',
100 | 'ignoreJids',
101 | 'splitMessages',
102 | 'timePerChar',
103 | ),
104 | };
105 |
106 | export const evolutionBotIgnoreJidSchema: JSONSchema7 = {
107 | $id: v4(),
108 | type: 'object',
109 | properties: {
110 | remoteJid: { type: 'string' },
111 | action: { type: 'string', enum: ['add', 'remove'] },
112 | },
113 | required: ['remoteJid', 'action'],
114 | ...isNotEmpty('remoteJid', 'action'),
115 | };
116 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/flowise/dto/flowise.dto.ts:
--------------------------------------------------------------------------------
1 | import { TriggerOperator, TriggerType } from '@prisma/client';
2 |
3 | export class FlowiseDto {
4 | enabled?: boolean;
5 | description?: string;
6 | apiUrl?: string;
7 | apiKey?: string;
8 | expire?: number;
9 | keywordFinish?: string;
10 | delayMessage?: number;
11 | unknownMessage?: string;
12 | listeningFromMe?: boolean;
13 | stopBotFromMe?: boolean;
14 | keepOpen?: boolean;
15 | debounceTime?: number;
16 | triggerType?: TriggerType;
17 | triggerOperator?: TriggerOperator;
18 | triggerValue?: string;
19 | ignoreJids?: any;
20 | splitMessages?: boolean;
21 | timePerChar?: number;
22 | }
23 |
24 | export class FlowiseSettingDto {
25 | expire?: number;
26 | keywordFinish?: string;
27 | delayMessage?: number;
28 | unknownMessage?: string;
29 | listeningFromMe?: boolean;
30 | stopBotFromMe?: boolean;
31 | keepOpen?: boolean;
32 | debounceTime?: number;
33 | flowiseIdFallback?: string;
34 | ignoreJids?: any;
35 | splitMessages?: boolean;
36 | timePerChar?: number;
37 | }
38 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/flowise/validate/flowise.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const flowiseSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean' },
28 | description: { type: 'string' },
29 | apiUrl: { type: 'string' },
30 | apiKey: { type: 'string' },
31 | triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
32 | triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
33 | triggerValue: { type: 'string' },
34 | expire: { type: 'integer' },
35 | keywordFinish: { type: 'string' },
36 | delayMessage: { type: 'integer' },
37 | unknownMessage: { type: 'string' },
38 | listeningFromMe: { type: 'boolean' },
39 | stopBotFromMe: { type: 'boolean' },
40 | keepOpen: { type: 'boolean' },
41 | debounceTime: { type: 'integer' },
42 | ignoreJids: { type: 'array', items: { type: 'string' } },
43 | },
44 | required: ['enabled', 'apiUrl', 'triggerType'],
45 | ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
46 | };
47 |
48 | export const flowiseStatusSchema: JSONSchema7 = {
49 | $id: v4(),
50 | type: 'object',
51 | properties: {
52 | remoteJid: { type: 'string' },
53 | status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
54 | },
55 | required: ['remoteJid', 'status'],
56 | ...isNotEmpty('remoteJid', 'status'),
57 | };
58 |
59 | export const flowiseSettingSchema: JSONSchema7 = {
60 | $id: v4(),
61 | type: 'object',
62 | properties: {
63 | expire: { type: 'integer' },
64 | keywordFinish: { type: 'string' },
65 | delayMessage: { type: 'integer' },
66 | unknownMessage: { type: 'string' },
67 | listeningFromMe: { type: 'boolean' },
68 | stopBotFromMe: { type: 'boolean' },
69 | keepOpen: { type: 'boolean' },
70 | debounceTime: { type: 'integer' },
71 | ignoreJids: { type: 'array', items: { type: 'string' } },
72 | botIdFallback: { type: 'string' },
73 | },
74 | required: [
75 | 'expire',
76 | 'keywordFinish',
77 | 'delayMessage',
78 | 'unknownMessage',
79 | 'listeningFromMe',
80 | 'stopBotFromMe',
81 | 'keepOpen',
82 | 'debounceTime',
83 | 'ignoreJids',
84 | ],
85 | ...isNotEmpty(
86 | 'expire',
87 | 'keywordFinish',
88 | 'delayMessage',
89 | 'unknownMessage',
90 | 'listeningFromMe',
91 | 'stopBotFromMe',
92 | 'keepOpen',
93 | 'debounceTime',
94 | 'ignoreJids',
95 | ),
96 | };
97 |
98 | export const flowiseIgnoreJidSchema: JSONSchema7 = {
99 | $id: v4(),
100 | type: 'object',
101 | properties: {
102 | remoteJid: { type: 'string' },
103 | action: { type: 'string', enum: ['add', 'remove'] },
104 | },
105 | required: ['remoteJid', 'action'],
106 | ...isNotEmpty('remoteJid', 'action'),
107 | };
108 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/openai/dto/openai.dto.ts:
--------------------------------------------------------------------------------
1 | import { TriggerOperator, TriggerType } from '@prisma/client';
2 |
3 | export class OpenaiCredsDto {
4 | name: string;
5 | apiKey: string;
6 | }
7 |
8 | export class OpenaiDto {
9 | enabled?: boolean;
10 | description?: string;
11 | openaiCredsId: string;
12 | botType?: string;
13 | assistantId?: string;
14 | functionUrl?: string;
15 | model?: string;
16 | systemMessages?: string[];
17 | assistantMessages?: string[];
18 | userMessages?: string[];
19 | maxTokens?: number;
20 | expire?: number;
21 | keywordFinish?: string;
22 | delayMessage?: number;
23 | unknownMessage?: string;
24 | listeningFromMe?: boolean;
25 | stopBotFromMe?: boolean;
26 | keepOpen?: boolean;
27 | debounceTime?: number;
28 | triggerType?: TriggerType;
29 | triggerOperator?: TriggerOperator;
30 | triggerValue?: string;
31 | ignoreJids?: any;
32 | splitMessages?: boolean;
33 | timePerChar?: number;
34 | }
35 |
36 | export class OpenaiSettingDto {
37 | openaiCredsId?: string;
38 | expire?: number;
39 | keywordFinish?: string;
40 | delayMessage?: number;
41 | unknownMessage?: string;
42 | listeningFromMe?: boolean;
43 | stopBotFromMe?: boolean;
44 | keepOpen?: boolean;
45 | debounceTime?: number;
46 | openaiIdFallback?: string;
47 | ignoreJids?: any;
48 | speechToText?: boolean;
49 | splitMessages?: boolean;
50 | timePerChar?: number;
51 | }
52 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/typebot/dto/typebot.dto.ts:
--------------------------------------------------------------------------------
1 | import { TriggerOperator, TriggerType } from '@prisma/client';
2 |
3 | export class PrefilledVariables {
4 | remoteJid?: string;
5 | pushName?: string;
6 | messageType?: string;
7 | additionalData?: { [key: string]: any };
8 | }
9 |
10 | export class TypebotDto {
11 | enabled?: boolean;
12 | description?: string;
13 | url: string;
14 | typebot?: string;
15 | expire?: number;
16 | keywordFinish?: string;
17 | delayMessage?: number;
18 | unknownMessage?: string;
19 | listeningFromMe?: boolean;
20 | stopBotFromMe?: boolean;
21 | keepOpen?: boolean;
22 | debounceTime?: number;
23 | triggerType?: TriggerType;
24 | triggerOperator?: TriggerOperator;
25 | triggerValue?: string;
26 | ignoreJids?: any;
27 | }
28 |
29 | export class TypebotSettingDto {
30 | expire?: number;
31 | keywordFinish?: string;
32 | delayMessage?: number;
33 | unknownMessage?: string;
34 | listeningFromMe?: boolean;
35 | stopBotFromMe?: boolean;
36 | keepOpen?: boolean;
37 | debounceTime?: number;
38 | typebotIdFallback?: string;
39 | ignoreJids?: any;
40 | }
41 |
--------------------------------------------------------------------------------
/src/api/integrations/chatbot/typebot/validate/typebot.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const typebotSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean' },
28 | description: { type: 'string' },
29 | url: { type: 'string' },
30 | typebot: { type: 'string' },
31 | triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
32 | triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
33 | triggerValue: { type: 'string' },
34 | expire: { type: 'integer' },
35 | keywordFinish: { type: 'string' },
36 | delayMessage: { type: 'integer' },
37 | unknownMessage: { type: 'string' },
38 | listeningFromMe: { type: 'boolean' },
39 | stopBotFromMe: { type: 'boolean' },
40 | ignoreJids: { type: 'array', items: { type: 'string' } },
41 | },
42 | required: ['enabled', 'url', 'typebot', 'triggerType'],
43 | ...isNotEmpty('enabled', 'url', 'typebot', 'triggerType'),
44 | };
45 |
46 | export const typebotStatusSchema: JSONSchema7 = {
47 | $id: v4(),
48 | type: 'object',
49 | properties: {
50 | remoteJid: { type: 'string' },
51 | status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
52 | },
53 | required: ['remoteJid', 'status'],
54 | ...isNotEmpty('remoteJid', 'status'),
55 | };
56 |
57 | export const typebotStartSchema: JSONSchema7 = {
58 | $id: v4(),
59 | type: 'object',
60 | properties: {
61 | remoteJid: { type: 'string' },
62 | url: { type: 'string' },
63 | typebot: { type: 'string' },
64 | },
65 | required: ['remoteJid', 'url', 'typebot'],
66 | ...isNotEmpty('remoteJid', 'url', 'typebot'),
67 | };
68 |
69 | export const typebotSettingSchema: JSONSchema7 = {
70 | $id: v4(),
71 | type: 'object',
72 | properties: {
73 | expire: { type: 'integer' },
74 | keywordFinish: { type: 'string' },
75 | delayMessage: { type: 'integer' },
76 | unknownMessage: { type: 'string' },
77 | listeningFromMe: { type: 'boolean' },
78 | stopBotFromMe: { type: 'boolean' },
79 | keepOpen: { type: 'boolean' },
80 | debounceTime: { type: 'integer' },
81 | typebotIdFallback: { type: 'string' },
82 | ignoreJids: { type: 'array', items: { type: 'string' } },
83 | },
84 | required: ['expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'],
85 | ...isNotEmpty('expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'),
86 | };
87 |
88 | export const typebotIgnoreJidSchema: JSONSchema7 = {
89 | $id: v4(),
90 | type: 'object',
91 | properties: {
92 | remoteJid: { type: 'string' },
93 | action: { type: 'string', enum: ['add', 'remove'] },
94 | },
95 | required: ['remoteJid', 'action'],
96 | ...isNotEmpty('remoteJid', 'action'),
97 | };
98 |
--------------------------------------------------------------------------------
/src/api/integrations/event/event.dto.ts:
--------------------------------------------------------------------------------
1 | import { Constructor } from '@api/integrations/integration.dto';
2 | import { JsonValue } from '@prisma/client/runtime/library';
3 |
4 | export class EventDto {
5 | webhook?: {
6 | enabled?: boolean;
7 | events?: string[];
8 | url?: string;
9 | headers?: JsonValue;
10 | byEvents?: boolean;
11 | base64?: boolean;
12 | };
13 |
14 | websocket?: {
15 | enabled?: boolean;
16 | events?: string[];
17 | };
18 |
19 | sqs?: {
20 | enabled?: boolean;
21 | events?: string[];
22 | };
23 |
24 | rabbitmq?: {
25 | enabled?: boolean;
26 | events?: string[];
27 | };
28 |
29 | pusher?: {
30 | enabled?: boolean;
31 | appId?: string;
32 | key?: string;
33 | secret?: string;
34 | cluster?: string;
35 | useTLS?: boolean;
36 | events?: string[];
37 | };
38 | }
39 |
40 | export function EventInstanceMixin(Base: TBase) {
41 | return class extends Base {
42 | webhook?: {
43 | enabled?: boolean;
44 | events?: string[];
45 | headers?: JsonValue;
46 | url?: string;
47 | byEvents?: boolean;
48 | base64?: boolean;
49 | };
50 |
51 | websocket?: {
52 | enabled?: boolean;
53 | events?: string[];
54 | };
55 |
56 | sqs?: {
57 | enabled?: boolean;
58 | events?: string[];
59 | };
60 |
61 | rabbitmq?: {
62 | enabled?: boolean;
63 | events?: string[];
64 | };
65 |
66 | pusher?: {
67 | enabled?: boolean;
68 | appId?: string;
69 | key?: string;
70 | secret?: string;
71 | cluster?: string;
72 | useTLS?: boolean;
73 | events?: string[];
74 | };
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/src/api/integrations/event/event.router.ts:
--------------------------------------------------------------------------------
1 | import { PusherRouter } from '@api/integrations/event/pusher/pusher.router';
2 | import { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';
3 | import { SqsRouter } from '@api/integrations/event/sqs/sqs.router';
4 | import { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';
5 | import { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';
6 | import { Router } from 'express';
7 |
8 | export class EventRouter {
9 | public readonly router: Router;
10 |
11 | constructor(configService: any, ...guards: any[]) {
12 | this.router = Router();
13 |
14 | this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);
15 | this.router.use('/websocket', new WebsocketRouter(...guards).router);
16 | this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);
17 | this.router.use('/pusher', new PusherRouter(...guards).router);
18 | this.router.use('/sqs', new SqsRouter(...guards).router);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/api/integrations/event/event.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | import { EventController } from './event.controller';
5 |
6 | export * from '@api/integrations/event/pusher/pusher.schema';
7 | export * from '@api/integrations/event/webhook/webhook.schema';
8 |
9 | export const eventSchema: JSONSchema7 = {
10 | $id: v4(),
11 | type: 'object',
12 | properties: {
13 | websocket: {
14 | $ref: '#/$defs/event',
15 | },
16 | rabbitmq: {
17 | $ref: '#/$defs/event',
18 | },
19 | sqs: {
20 | $ref: '#/$defs/event',
21 | },
22 | },
23 | $defs: {
24 | event: {
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean', enum: [true, false] },
28 | events: {
29 | type: 'array',
30 | minItems: 0,
31 | items: {
32 | type: 'string',
33 | enum: EventController.events,
34 | },
35 | },
36 | },
37 | required: ['enabled'],
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/src/api/integrations/event/pusher/pusher.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { EventDto } from '@api/integrations/event/event.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { eventManager } from '@api/server.module';
6 | import { instanceSchema, pusherSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 | export class PusherRouter extends RouterBroker {
9 | constructor(...guards: RequestHandler[]) {
10 | super();
11 | this.router
12 | .post(this.routerPath('set'), ...guards, async (req, res) => {
13 | const response = await this.dataValidate({
14 | request: req,
15 | schema: pusherSchema,
16 | ClassRef: EventDto,
17 | execute: (instance, data) => eventManager.pusher.set(instance.instanceName, data),
18 | });
19 | res.status(HttpStatus.CREATED).json(response);
20 | })
21 | .get(this.routerPath('find'), ...guards, async (req, res) => {
22 | const response = await this.dataValidate({
23 | request: req,
24 | schema: instanceSchema,
25 | ClassRef: InstanceDto,
26 | execute: (instance) => eventManager.pusher.get(instance.instanceName),
27 | });
28 | res.status(HttpStatus.OK).json(response);
29 | });
30 | }
31 | public readonly router: Router = Router();
32 | }
33 |
--------------------------------------------------------------------------------
/src/api/integrations/event/pusher/pusher.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | import { EventController } from '../event.controller';
5 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
6 | const properties = {};
7 | propertyNames.forEach(
8 | (property) =>
9 | (properties[property] = {
10 | minLength: 1,
11 | description: `The "${property}" cannot be empty`,
12 | }),
13 | );
14 | return {
15 | if: {
16 | propertyNames: {
17 | enum: [...propertyNames],
18 | },
19 | },
20 | then: { properties },
21 | };
22 | };
23 | export const pusherSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | pusher: {
28 | type: 'object',
29 | properties: {
30 | enabled: { type: 'boolean' },
31 | appId: { type: 'string' },
32 | key: { type: 'string' },
33 | secret: { type: 'string' },
34 | cluster: { type: 'string' },
35 | useTLS: { type: 'boolean' },
36 | events: {
37 | type: 'array',
38 | minItems: 0,
39 | items: {
40 | type: 'string',
41 | enum: EventController.events,
42 | },
43 | },
44 | },
45 | required: ['enabled', 'appId', 'key', 'secret', 'cluster', 'useTLS'],
46 | ...isNotEmpty('enabled', 'appId', 'key', 'secret', 'cluster', 'useTLS'),
47 | },
48 | },
49 | required: ['pusher'],
50 | };
51 |
--------------------------------------------------------------------------------
/src/api/integrations/event/rabbitmq/rabbitmq.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { EventDto } from '@api/integrations/event/event.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { eventManager } from '@api/server.module';
6 | import { eventSchema, instanceSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 |
9 | export class RabbitmqRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router
13 | .post(this.routerPath('set'), ...guards, async (req, res) => {
14 | const response = await this.dataValidate({
15 | request: req,
16 | schema: eventSchema,
17 | ClassRef: EventDto,
18 | execute: (instance, data) => eventManager.rabbitmq.set(instance.instanceName, data),
19 | });
20 |
21 | res.status(HttpStatus.CREATED).json(response);
22 | })
23 | .get(this.routerPath('find'), ...guards, async (req, res) => {
24 | const response = await this.dataValidate({
25 | request: req,
26 | schema: instanceSchema,
27 | ClassRef: InstanceDto,
28 | execute: (instance) => eventManager.rabbitmq.get(instance.instanceName),
29 | });
30 |
31 | res.status(HttpStatus.OK).json(response);
32 | });
33 | }
34 |
35 | public readonly router: Router = Router();
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/integrations/event/sqs/sqs.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { EventDto } from '@api/integrations/event/event.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { eventManager } from '@api/server.module';
6 | import { eventSchema, instanceSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 |
9 | export class SqsRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router
13 | .post(this.routerPath('set'), ...guards, async (req, res) => {
14 | const response = await this.dataValidate({
15 | request: req,
16 | schema: eventSchema,
17 | ClassRef: EventDto,
18 | execute: (instance, data) => eventManager.sqs.set(instance.instanceName, data),
19 | });
20 |
21 | res.status(HttpStatus.CREATED).json(response);
22 | })
23 | .get(this.routerPath('find'), ...guards, async (req, res) => {
24 | const response = await this.dataValidate({
25 | request: req,
26 | schema: instanceSchema,
27 | ClassRef: InstanceDto,
28 | execute: (instance) => eventManager.sqs.get(instance.instanceName),
29 | });
30 |
31 | res.status(HttpStatus.OK).json(response);
32 | });
33 | }
34 |
35 | public readonly router: Router = Router();
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/integrations/event/webhook/webhook.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { EventDto } from '@api/integrations/event/event.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { eventManager } from '@api/server.module';
6 | import { ConfigService } from '@config/env.config';
7 | import { instanceSchema, webhookSchema } from '@validate/validate.schema';
8 | import { RequestHandler, Router } from 'express';
9 |
10 | export class WebhookRouter extends RouterBroker {
11 | constructor(
12 | readonly configService: ConfigService,
13 | ...guards: RequestHandler[]
14 | ) {
15 | super();
16 | this.router
17 | .post(this.routerPath('set'), ...guards, async (req, res) => {
18 | const response = await this.dataValidate({
19 | request: req,
20 | schema: webhookSchema,
21 | ClassRef: EventDto,
22 | execute: (instance, data) => eventManager.webhook.set(instance.instanceName, data),
23 | });
24 |
25 | res.status(HttpStatus.CREATED).json(response);
26 | })
27 | .get(this.routerPath('find'), ...guards, async (req, res) => {
28 | const response = await this.dataValidate({
29 | request: req,
30 | schema: instanceSchema,
31 | ClassRef: InstanceDto,
32 | execute: (instance) => eventManager.webhook.get(instance.instanceName),
33 | });
34 |
35 | res.status(HttpStatus.OK).json(response);
36 | });
37 | }
38 |
39 | public readonly router: Router = Router();
40 | }
41 |
--------------------------------------------------------------------------------
/src/api/integrations/event/webhook/webhook.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | import { EventController } from '../event.controller';
5 |
6 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
7 | const properties = {};
8 | propertyNames.forEach(
9 | (property) =>
10 | (properties[property] = {
11 | minLength: 1,
12 | description: `The "${property}" cannot be empty`,
13 | }),
14 | );
15 | return {
16 | if: {
17 | propertyNames: {
18 | enum: [...propertyNames],
19 | },
20 | },
21 | then: { properties },
22 | };
23 | };
24 |
25 | export const webhookSchema: JSONSchema7 = {
26 | $id: v4(),
27 | type: 'object',
28 | properties: {
29 | webhook: {
30 | type: 'object',
31 | properties: {
32 | enabled: { type: 'boolean' },
33 | url: { type: 'string' },
34 | headers: { type: 'object' },
35 | byEvents: { type: 'boolean' },
36 | base64: { type: 'boolean' },
37 | events: {
38 | type: 'array',
39 | minItems: 0,
40 | items: {
41 | type: 'string',
42 | enum: EventController.events,
43 | },
44 | },
45 | },
46 | required: ['enabled', 'url'],
47 | ...isNotEmpty('enabled', 'url'),
48 | },
49 | },
50 | required: ['webhook'],
51 | };
52 |
--------------------------------------------------------------------------------
/src/api/integrations/event/websocket/websocket.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { EventDto } from '@api/integrations/event/event.dto';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { eventManager } from '@api/server.module';
6 | import { eventSchema, instanceSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 |
9 | export class WebsocketRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router
13 | .post(this.routerPath('set'), ...guards, async (req, res) => {
14 | const response = await this.dataValidate({
15 | request: req,
16 | schema: eventSchema,
17 | ClassRef: EventDto,
18 | execute: (instance, data) => eventManager.websocket.set(instance.instanceName, data),
19 | });
20 |
21 | res.status(HttpStatus.CREATED).json(response);
22 | })
23 | .get(this.routerPath('find'), ...guards, async (req, res) => {
24 | const response = await this.dataValidate({
25 | request: req,
26 | schema: instanceSchema,
27 | ClassRef: InstanceDto,
28 | execute: (instance) => eventManager.websocket.get(instance.instanceName),
29 | });
30 |
31 | res.status(HttpStatus.OK).json(response);
32 | });
33 | }
34 |
35 | public readonly router: Router = Router();
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/integrations/integration.dto.ts:
--------------------------------------------------------------------------------
1 | import { ChatwootInstanceMixin } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
2 | import { EventInstanceMixin } from '@api/integrations/event/event.dto';
3 |
4 | export type Constructor = new (...args: any[]) => T;
5 |
6 | export class IntegrationDto extends EventInstanceMixin(ChatwootInstanceMixin(class {})) {}
7 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/s3/controllers/s3.controller.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
3 | import { S3Service } from '@api/integrations/storage/s3/services/s3.service';
4 |
5 | export class S3Controller {
6 | constructor(private readonly s3Service: S3Service) {}
7 |
8 | public async getMedia(instance: InstanceDto, data: MediaDto) {
9 | return this.s3Service.getMedia(instance, data);
10 | }
11 |
12 | public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
13 | return this.s3Service.getMediaUrl(instance, data);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/s3/dto/media.dto.ts:
--------------------------------------------------------------------------------
1 | export class MediaDto {
2 | id?: string;
3 | type?: string;
4 | messageId?: number;
5 | expiry?: number;
6 | }
7 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/s3/routes/s3.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
3 | import { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';
4 | import { HttpStatus } from '@api/routes/index.router';
5 | import { s3Controller } from '@api/server.module';
6 | import { RequestHandler, Router } from 'express';
7 |
8 | export class S3Router extends RouterBroker {
9 | constructor(...guards: RequestHandler[]) {
10 | super();
11 | this.router
12 | .post(this.routerPath('getMedia'), ...guards, async (req, res) => {
13 | const response = await this.dataValidate({
14 | request: req,
15 | schema: s3Schema,
16 | ClassRef: MediaDto,
17 | execute: (instance, data) => s3Controller.getMedia(instance, data),
18 | });
19 |
20 | res.status(HttpStatus.CREATED).json(response);
21 | })
22 | .post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {
23 | const response = await this.dataValidate({
24 | request: req,
25 | schema: s3UrlSchema,
26 | ClassRef: MediaDto,
27 | execute: (instance, data) => s3Controller.getMediaUrl(instance, data),
28 | });
29 |
30 | res.status(HttpStatus.OK).json(response);
31 | });
32 | }
33 |
34 | public readonly router: Router = Router();
35 | }
36 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/s3/services/s3.service.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
3 | import { getObjectUrl } from '@api/integrations/storage/s3/libs/minio.server';
4 | import { PrismaRepository } from '@api/repository/repository.service';
5 | import { Logger } from '@config/logger.config';
6 | import { BadRequestException } from '@exceptions';
7 |
8 | export class S3Service {
9 | constructor(private readonly prismaRepository: PrismaRepository) {}
10 |
11 | private readonly logger = new Logger('S3Service');
12 |
13 | public async getMedia(instance: InstanceDto, query?: MediaDto) {
14 | try {
15 | const where: any = {
16 | instanceId: instance.instanceId,
17 | ...query,
18 | };
19 |
20 | const media = await this.prismaRepository.media.findMany({
21 | where,
22 | select: {
23 | id: true,
24 | fileName: true,
25 | type: true,
26 | mimetype: true,
27 | createdAt: true,
28 | Message: true,
29 | },
30 | });
31 |
32 | if (!media || media.length === 0) {
33 | throw 'Media not found';
34 | }
35 |
36 | return media;
37 | } catch (error) {
38 | throw new BadRequestException(error);
39 | }
40 | }
41 |
42 | public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
43 | const media = (await this.getMedia(instance, { id: data.id }))[0];
44 | const mediaUrl = await getObjectUrl(media.fileName, data.expiry);
45 | return {
46 | mediaUrl,
47 | ...media,
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/s3/validate/s3.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const s3Schema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | id: { type: 'string' },
28 | type: { type: 'string' },
29 | messageId: { type: 'integer' },
30 | },
31 | ...isNotEmpty('id', 'type', 'messageId'),
32 | };
33 |
34 | export const s3UrlSchema: JSONSchema7 = {
35 | $id: v4(),
36 | type: 'object',
37 | properties: {
38 | id: { type: 'string', pattern: '\\d+', minLength: 1 },
39 | expiry: { type: 'string', pattern: '\\d+', minLength: 1 },
40 | },
41 | ...isNotEmpty('id'),
42 | required: ['id'],
43 | };
44 |
--------------------------------------------------------------------------------
/src/api/integrations/storage/storage.router.ts:
--------------------------------------------------------------------------------
1 | import { S3Router } from '@api/integrations/storage/s3/routes/s3.router';
2 | import { Router } from 'express';
3 |
4 | export class StorageRouter {
5 | public readonly router: Router;
6 |
7 | constructor(...guards: any[]) {
8 | this.router = Router();
9 |
10 | this.router.use('/s3', new S3Router(...guards).router);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/api/repository/repository.service.ts:
--------------------------------------------------------------------------------
1 | import { ConfigService } from '@config/env.config';
2 | import { Logger } from '@config/logger.config';
3 | import { PrismaClient } from '@prisma/client';
4 |
5 | export class Query {
6 | where?: T;
7 | sort?: 'asc' | 'desc';
8 | page?: number;
9 | offset?: number;
10 | }
11 |
12 | export class PrismaRepository extends PrismaClient {
13 | constructor(private readonly configService: ConfigService) {
14 | super();
15 | }
16 |
17 | private readonly logger = new Logger('PrismaRepository');
18 |
19 | public async onModuleInit() {
20 | await this.$connect();
21 | this.logger.info('Repository:Prisma - ON');
22 | }
23 |
24 | public async onModuleDestroy() {
25 | await this.$disconnect();
26 | this.logger.warn('Repository:Prisma - OFF');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/api/routes/call.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { OfferCallDto } from '@api/dto/call.dto';
3 | import { callController } from '@api/server.module';
4 | import { offerCallSchema } from '@validate/validate.schema';
5 | import { RequestHandler, Router } from 'express';
6 |
7 | import { HttpStatus } from './index.router';
8 |
9 | export class CallRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router.post(this.routerPath('offer'), ...guards, async (req, res) => {
13 | const response = await this.dataValidate({
14 | request: req,
15 | schema: offerCallSchema,
16 | ClassRef: OfferCallDto,
17 | execute: (instance, data) => callController.offerCall(instance, data),
18 | });
19 |
20 | return res.status(HttpStatus.CREATED).json(response);
21 | });
22 | }
23 |
24 | public readonly router: Router = Router();
25 | }
26 |
--------------------------------------------------------------------------------
/src/api/routes/label.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
3 | import { labelController } from '@api/server.module';
4 | import { handleLabelSchema } from '@validate/validate.schema';
5 | import { RequestHandler, Router } from 'express';
6 |
7 | import { HttpStatus } from './index.router';
8 |
9 | export class LabelRouter extends RouterBroker {
10 | constructor(...guards: RequestHandler[]) {
11 | super();
12 | this.router
13 | .get(this.routerPath('findLabels'), ...guards, async (req, res) => {
14 | const response = await this.dataValidate({
15 | request: req,
16 | schema: null,
17 | ClassRef: LabelDto,
18 | execute: (instance) => labelController.fetchLabels(instance),
19 | });
20 |
21 | return res.status(HttpStatus.OK).json(response);
22 | })
23 | .post(this.routerPath('handleLabel'), ...guards, async (req, res) => {
24 | const response = await this.dataValidate({
25 | request: req,
26 | schema: handleLabelSchema,
27 | ClassRef: HandleLabelDto,
28 | execute: (instance, data) => labelController.handleLabel(instance, data),
29 | });
30 |
31 | return res.status(HttpStatus.OK).json(response);
32 | });
33 | }
34 |
35 | public readonly router: Router = Router();
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/routes/proxy.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { ProxyDto } from '@api/dto/proxy.dto';
4 | import { proxyController } from '@api/server.module';
5 | import { instanceSchema, proxySchema } from '@validate/validate.schema';
6 | import { RequestHandler, Router } from 'express';
7 |
8 | import { HttpStatus } from './index.router';
9 |
10 | export class ProxyRouter extends RouterBroker {
11 | constructor(...guards: RequestHandler[]) {
12 | super();
13 | this.router
14 | .post(this.routerPath('set'), ...guards, async (req, res) => {
15 | const response = await this.dataValidate({
16 | request: req,
17 | schema: proxySchema,
18 | ClassRef: ProxyDto,
19 | execute: (instance, data) => proxyController.createProxy(instance, data),
20 | });
21 |
22 | res.status(HttpStatus.CREATED).json(response);
23 | })
24 | .get(this.routerPath('find'), ...guards, async (req, res) => {
25 | const response = await this.dataValidate({
26 | request: req,
27 | schema: instanceSchema,
28 | ClassRef: InstanceDto,
29 | execute: (instance) => proxyController.findProxy(instance),
30 | });
31 |
32 | res.status(HttpStatus.OK).json(response);
33 | });
34 | }
35 |
36 | public readonly router: Router = Router();
37 | }
38 |
--------------------------------------------------------------------------------
/src/api/routes/settings.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { SettingsDto } from '@api/dto/settings.dto';
4 | import { settingsController } from '@api/server.module';
5 | import { settingsSchema } from '@validate/validate.schema';
6 | import { RequestHandler, Router } from 'express';
7 |
8 | import { HttpStatus } from './index.router';
9 |
10 | export class SettingsRouter extends RouterBroker {
11 | constructor(...guards: RequestHandler[]) {
12 | super();
13 | this.router
14 | .post(this.routerPath('set'), ...guards, async (req, res) => {
15 | const response = await this.dataValidate({
16 | request: req,
17 | schema: settingsSchema,
18 | ClassRef: SettingsDto,
19 | execute: (instance, data) => settingsController.createSettings(instance, data),
20 | });
21 |
22 | res.status(HttpStatus.CREATED).json(response);
23 | })
24 | .get(this.routerPath('find'), ...guards, async (req, res) => {
25 | const response = await this.dataValidate({
26 | request: req,
27 | schema: null,
28 | ClassRef: InstanceDto,
29 | execute: (instance) => settingsController.findSettings(instance),
30 | });
31 |
32 | res.status(HttpStatus.OK).json(response);
33 | });
34 | }
35 |
36 | public readonly router: Router = Router();
37 | }
38 |
--------------------------------------------------------------------------------
/src/api/routes/template.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import { InstanceDto } from '@api/dto/instance.dto';
3 | import { TemplateDto } from '@api/dto/template.dto';
4 | import { templateController } from '@api/server.module';
5 | import { ConfigService } from '@config/env.config';
6 | import { instanceSchema, templateSchema } from '@validate/validate.schema';
7 | import { RequestHandler, Router } from 'express';
8 |
9 | import { HttpStatus } from './index.router';
10 |
11 | export class TemplateRouter extends RouterBroker {
12 | constructor(
13 | readonly configService: ConfigService,
14 | ...guards: RequestHandler[]
15 | ) {
16 | super();
17 | this.router
18 | .post(this.routerPath('create'), ...guards, async (req, res) => {
19 | const response = await this.dataValidate({
20 | request: req,
21 | schema: templateSchema,
22 | ClassRef: TemplateDto,
23 | execute: (instance, data) => templateController.createTemplate(instance, data),
24 | });
25 |
26 | res.status(HttpStatus.CREATED).json(response);
27 | })
28 | .get(this.routerPath('find'), ...guards, async (req, res) => {
29 | const response = await this.dataValidate({
30 | request: req,
31 | schema: instanceSchema,
32 | ClassRef: InstanceDto,
33 | execute: (instance) => templateController.findTemplate(instance),
34 | });
35 |
36 | res.status(HttpStatus.OK).json(response);
37 | });
38 | }
39 |
40 | public readonly router: Router = Router();
41 | }
42 |
--------------------------------------------------------------------------------
/src/api/routes/view.router.ts:
--------------------------------------------------------------------------------
1 | import { RouterBroker } from '@api/abstract/abstract.router';
2 | import express, { Router } from 'express';
3 | import path from 'path';
4 |
5 | export class ViewsRouter extends RouterBroker {
6 | public readonly router: Router;
7 |
8 | constructor() {
9 | super();
10 | this.router = Router();
11 |
12 | const basePath = path.join(process.cwd(), 'manager', 'dist');
13 | const indexPath = path.join(basePath, 'index.html');
14 |
15 | this.router.use(express.static(basePath));
16 |
17 | this.router.get('*', (req, res) => {
18 | res.sendFile(indexPath);
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/api/services/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { PrismaRepository } from '@api/repository/repository.service';
2 | import { BadRequestException } from '@exceptions';
3 |
4 | export class AuthService {
5 | constructor(private readonly prismaRepository: PrismaRepository) {}
6 |
7 | public async checkDuplicateToken(token: string) {
8 | if (!token) {
9 | return true;
10 | }
11 |
12 | const instances = await this.prismaRepository.instance.findMany({
13 | where: { token },
14 | });
15 |
16 | if (instances.length > 0) {
17 | throw new BadRequestException('Token already exists');
18 | }
19 |
20 | return true;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/api/services/cache.service.ts:
--------------------------------------------------------------------------------
1 | import { ICache } from '@api/abstract/abstract.cache';
2 | import { Logger } from '@config/logger.config';
3 | import { BufferJSON } from 'baileys';
4 |
5 | export class CacheService {
6 | private readonly logger = new Logger('CacheService');
7 |
8 | constructor(private readonly cache: ICache) {
9 | if (cache) {
10 | this.logger.verbose(`cacheservice created using cache engine: ${cache.constructor?.name}`);
11 | } else {
12 | this.logger.verbose(`cacheservice disabled`);
13 | }
14 | }
15 |
16 | async get(key: string): Promise {
17 | if (!this.cache) {
18 | return;
19 | }
20 | return this.cache.get(key);
21 | }
22 |
23 | public async hGet(key: string, field: string) {
24 | if (!this.cache) {
25 | return null;
26 | }
27 | try {
28 | const data = await this.cache.hGet(key, field);
29 |
30 | if (data) {
31 | return JSON.parse(data, BufferJSON.reviver);
32 | }
33 |
34 | return null;
35 | } catch (error) {
36 | this.logger.error(error);
37 | return null;
38 | }
39 | }
40 |
41 | async set(key: string, value: any, ttl?: number) {
42 | if (!this.cache) {
43 | return;
44 | }
45 | this.cache.set(key, value, ttl);
46 | }
47 |
48 | public async hSet(key: string, field: string, value: any) {
49 | if (!this.cache) {
50 | return;
51 | }
52 | try {
53 | const json = JSON.stringify(value, BufferJSON.replacer);
54 |
55 | await this.cache.hSet(key, field, json);
56 | } catch (error) {
57 | this.logger.error(error);
58 | }
59 | }
60 |
61 | async has(key: string) {
62 | if (!this.cache) {
63 | return;
64 | }
65 | return this.cache.has(key);
66 | }
67 |
68 | async delete(key: string) {
69 | if (!this.cache) {
70 | return;
71 | }
72 | return this.cache.delete(key);
73 | }
74 |
75 | async hDelete(key: string, field: string) {
76 | if (!this.cache) {
77 | return false;
78 | }
79 | try {
80 | await this.cache.hDelete(key, field);
81 | return true;
82 | } catch (error) {
83 | this.logger.error(error);
84 | return false;
85 | }
86 | }
87 |
88 | async deleteAll(appendCriteria?: string) {
89 | if (!this.cache) {
90 | return;
91 | }
92 | return this.cache.deleteAll(appendCriteria);
93 | }
94 |
95 | async keys(appendCriteria?: string) {
96 | if (!this.cache) {
97 | return;
98 | }
99 | return this.cache.keys(appendCriteria);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/api/services/proxy.service.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { ProxyDto } from '@api/dto/proxy.dto';
3 | import { Logger } from '@config/logger.config';
4 | import { Proxy } from '@prisma/client';
5 |
6 | import { WAMonitoringService } from './monitor.service';
7 |
8 | export class ProxyService {
9 | constructor(private readonly waMonitor: WAMonitoringService) {}
10 |
11 | private readonly logger = new Logger('ProxyService');
12 |
13 | public create(instance: InstanceDto, data: ProxyDto) {
14 | this.waMonitor.waInstances[instance.instanceName].setProxy(data);
15 |
16 | return { proxy: { ...instance, proxy: data } };
17 | }
18 |
19 | public async find(instance: InstanceDto): Promise {
20 | try {
21 | const result = await this.waMonitor.waInstances[instance.instanceName].findProxy();
22 |
23 | if (Object.keys(result).length === 0) {
24 | throw new Error('Proxy not found');
25 | }
26 |
27 | return result;
28 | } catch (error) {
29 | return null;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/api/services/settings.service.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { SettingsDto } from '@api/dto/settings.dto';
3 | import { Logger } from '@config/logger.config';
4 |
5 | import { WAMonitoringService } from './monitor.service';
6 |
7 | export class SettingsService {
8 | constructor(private readonly waMonitor: WAMonitoringService) {}
9 |
10 | private readonly logger = new Logger('SettingsService');
11 |
12 | public async create(instance: InstanceDto, data: SettingsDto) {
13 | await this.waMonitor.waInstances[instance.instanceName].setSettings(data);
14 |
15 | return { settings: { ...instance, settings: data } };
16 | }
17 |
18 | public async find(instance: InstanceDto): Promise {
19 | try {
20 | const result = await this.waMonitor.waInstances[instance.instanceName].findSettings();
21 |
22 | if (Object.keys(result).length === 0) {
23 | throw new Error('Settings not found');
24 | }
25 |
26 | return result;
27 | } catch (error) {
28 | return null;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/api/services/template.service.ts:
--------------------------------------------------------------------------------
1 | import { InstanceDto } from '@api/dto/instance.dto';
2 | import { TemplateDto } from '@api/dto/template.dto';
3 | import { PrismaRepository } from '@api/repository/repository.service';
4 | import { ConfigService, WaBusiness } from '@config/env.config';
5 | import { Logger } from '@config/logger.config';
6 | import axios from 'axios';
7 |
8 | import { WAMonitoringService } from './monitor.service';
9 |
10 | export class TemplateService {
11 | constructor(
12 | private readonly waMonitor: WAMonitoringService,
13 | public readonly prismaRepository: PrismaRepository,
14 | private readonly configService: ConfigService,
15 | ) {}
16 |
17 | private readonly logger = new Logger('TemplateService');
18 |
19 | private businessId: string;
20 | private token: string;
21 |
22 | public async find(instance: InstanceDto) {
23 | const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
24 |
25 | if (!getInstance) {
26 | throw new Error('Instance not found');
27 | }
28 |
29 | this.businessId = getInstance.businessId;
30 | this.token = getInstance.token;
31 |
32 | const response = await this.requestTemplate({}, 'GET');
33 |
34 | if (!response) {
35 | throw new Error('Error to create template');
36 | }
37 |
38 | return response.data;
39 | }
40 |
41 | public async create(instance: InstanceDto, data: TemplateDto) {
42 | try {
43 | const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
44 |
45 | if (!getInstance) {
46 | throw new Error('Instance not found');
47 | }
48 |
49 | this.businessId = getInstance.businessId;
50 | this.token = getInstance.token;
51 |
52 | const postData = {
53 | name: data.name,
54 | category: data.category,
55 | allow_category_change: data.allowCategoryChange,
56 | language: data.language,
57 | components: data.components,
58 | };
59 |
60 | const response = await this.requestTemplate(postData, 'POST');
61 |
62 | if (!response || response.error) {
63 | throw new Error('Error to create template');
64 | }
65 |
66 | const template = await this.prismaRepository.template.create({
67 | data: {
68 | templateId: response.id,
69 | name: data.name,
70 | template: response,
71 | webhookUrl: data.webhookUrl,
72 | instanceId: getInstance.id,
73 | },
74 | });
75 |
76 | return template;
77 | } catch (error) {
78 | this.logger.error(error);
79 | throw new Error('Error to create template');
80 | }
81 | }
82 |
83 | private async requestTemplate(data: any, method: string) {
84 | try {
85 | let urlServer = this.configService.get('WA_BUSINESS').URL;
86 | const version = this.configService.get('WA_BUSINESS').VERSION;
87 | urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`;
88 | const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
89 | if (method === 'GET') {
90 | const result = await axios.get(urlServer, { headers });
91 | return result.data;
92 | } else if (method === 'POST') {
93 | const result = await axios.post(urlServer, data, { headers });
94 | return result.data;
95 | }
96 | } catch (e) {
97 | this.logger.error(e.response.data);
98 | return e.response.data.error;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/cache/cacheengine.ts:
--------------------------------------------------------------------------------
1 | import { ICache } from '@api/abstract/abstract.cache';
2 | import { CacheConf, ConfigService } from '@config/env.config';
3 | import { Logger } from '@config/logger.config';
4 |
5 | import { LocalCache } from './localcache';
6 | import { RedisCache } from './rediscache';
7 |
8 | const logger = new Logger('CacheEngine');
9 |
10 | export class CacheEngine {
11 | private engine: ICache;
12 |
13 | constructor(
14 | private readonly configService: ConfigService,
15 | module: string,
16 | ) {
17 | const cacheConf = configService.get('CACHE');
18 |
19 | if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
20 | logger.verbose(`RedisCache initialized for ${module}`);
21 | this.engine = new RedisCache(configService, module);
22 | } else if (cacheConf?.LOCAL?.ENABLED) {
23 | logger.verbose(`LocalCache initialized for ${module}`);
24 | this.engine = new LocalCache(configService, module);
25 | }
26 | }
27 |
28 | public getEngine() {
29 | return this.engine;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/cache/localcache.ts:
--------------------------------------------------------------------------------
1 | import { ICache } from '@api/abstract/abstract.cache';
2 | import { CacheConf, CacheConfLocal, ConfigService } from '@config/env.config';
3 | import { Logger } from '@config/logger.config';
4 | import { BufferJSON } from 'baileys';
5 | import NodeCache from 'node-cache';
6 |
7 | export class LocalCache implements ICache {
8 | private readonly logger = new Logger('LocalCache');
9 | private conf: CacheConfLocal;
10 | static localCache = new NodeCache();
11 |
12 | constructor(
13 | private readonly configService: ConfigService,
14 | private readonly module: string,
15 | ) {
16 | this.conf = this.configService.get('CACHE')?.LOCAL;
17 | }
18 |
19 | async get(key: string): Promise {
20 | return LocalCache.localCache.get(this.buildKey(key));
21 | }
22 |
23 | async set(key: string, value: any, ttl?: number) {
24 | return LocalCache.localCache.set(this.buildKey(key), value, ttl || this.conf.TTL);
25 | }
26 |
27 | async has(key: string) {
28 | return LocalCache.localCache.has(this.buildKey(key));
29 | }
30 |
31 | async delete(key: string) {
32 | return LocalCache.localCache.del(this.buildKey(key));
33 | }
34 |
35 | async deleteAll(appendCriteria?: string) {
36 | const keys = await this.keys(appendCriteria);
37 | if (!keys?.length) {
38 | return 0;
39 | }
40 |
41 | return LocalCache.localCache.del(keys);
42 | }
43 |
44 | async keys(appendCriteria?: string) {
45 | const filter = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}`;
46 |
47 | return LocalCache.localCache.keys().filter((key) => key.substring(0, filter.length) === filter);
48 | }
49 |
50 | buildKey(key: string) {
51 | return `${this.module}:${key}`;
52 | }
53 |
54 | async hGet(key: string, field: string) {
55 | try {
56 | const data = LocalCache.localCache.get(this.buildKey(key)) as Object;
57 |
58 | if (data && field in data) {
59 | return JSON.parse(data[field], BufferJSON.reviver);
60 | }
61 |
62 | return null;
63 | } catch (error) {
64 | this.logger.error(error);
65 | }
66 | }
67 |
68 | async hSet(key: string, field: string, value: any) {
69 | try {
70 | const json = JSON.stringify(value, BufferJSON.replacer);
71 |
72 | let hash = LocalCache.localCache.get(this.buildKey(key));
73 |
74 | if (!hash) {
75 | hash = {};
76 | }
77 |
78 | hash[field] = json;
79 | LocalCache.localCache.set(this.buildKey(key), hash);
80 | } catch (error) {
81 | this.logger.error(error);
82 | }
83 | }
84 |
85 | async hDelete(key: string, field: string) {
86 | try {
87 | const data = LocalCache.localCache.get(this.buildKey(key)) as Object;
88 |
89 | if (data && field in data) {
90 | delete data[field];
91 | LocalCache.localCache.set(this.buildKey(key), data);
92 | return 1;
93 | }
94 |
95 | return 0;
96 | } catch (error) {
97 | this.logger.error(error);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/cache/rediscache.client.ts:
--------------------------------------------------------------------------------
1 | import { CacheConf, CacheConfRedis, configService } from '@config/env.config';
2 | import { Logger } from '@config/logger.config';
3 | import { createClient, RedisClientType } from 'redis';
4 |
5 | class Redis {
6 | private logger = new Logger('Redis');
7 | private client: RedisClientType = null;
8 | private conf: CacheConfRedis;
9 | private connected = false;
10 |
11 | constructor() {
12 | this.conf = configService.get('CACHE')?.REDIS;
13 | }
14 |
15 | getConnection(): RedisClientType {
16 | if (this.connected) {
17 | return this.client;
18 | } else {
19 | this.client = createClient({
20 | url: this.conf.URI,
21 | });
22 |
23 | this.client.on('connect', () => {
24 | this.logger.verbose('redis connecting');
25 | });
26 |
27 | this.client.on('ready', () => {
28 | this.logger.verbose('redis ready');
29 | this.connected = true;
30 | });
31 |
32 | this.client.on('error', () => {
33 | this.logger.error('redis disconnected');
34 | this.connected = false;
35 | });
36 |
37 | this.client.on('end', () => {
38 | this.logger.verbose('redis connection ended');
39 | this.connected = false;
40 | });
41 |
42 | try {
43 | this.client.connect();
44 | this.connected = true;
45 | } catch (e) {
46 | this.connected = false;
47 | this.logger.error('redis connect exception caught: ' + e);
48 | return null;
49 | }
50 |
51 | return this.client;
52 | }
53 | }
54 | }
55 |
56 | export const redisClient = new Redis();
57 |
--------------------------------------------------------------------------------
/src/cache/rediscache.ts:
--------------------------------------------------------------------------------
1 | import { ICache } from '@api/abstract/abstract.cache';
2 | import { CacheConf, CacheConfRedis, ConfigService } from '@config/env.config';
3 | import { Logger } from '@config/logger.config';
4 | import { BufferJSON } from 'baileys';
5 | import { RedisClientType } from 'redis';
6 |
7 | import { redisClient } from './rediscache.client';
8 |
9 | export class RedisCache implements ICache {
10 | private readonly logger = new Logger('RedisCache');
11 | private client: RedisClientType;
12 | private conf: CacheConfRedis;
13 |
14 | constructor(
15 | private readonly configService: ConfigService,
16 | private readonly module: string,
17 | ) {
18 | this.conf = this.configService.get('CACHE')?.REDIS;
19 | this.client = redisClient.getConnection();
20 | }
21 | async get(key: string): Promise {
22 | try {
23 | return JSON.parse(await this.client.get(this.buildKey(key)));
24 | } catch (error) {
25 | this.logger.error(error);
26 | }
27 | }
28 |
29 | async hGet(key: string, field: string) {
30 | try {
31 | const data = await this.client.hGet(this.buildKey(key), field);
32 |
33 | if (data) {
34 | return JSON.parse(data, BufferJSON.reviver);
35 | }
36 |
37 | return null;
38 | } catch (error) {
39 | this.logger.error(error);
40 | }
41 | }
42 |
43 | async set(key: string, value: any, ttl?: number) {
44 | try {
45 | await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value));
46 | } catch (error) {
47 | this.logger.error(error);
48 | }
49 | }
50 |
51 | async hSet(key: string, field: string, value: any) {
52 | try {
53 | const json = JSON.stringify(value, BufferJSON.replacer);
54 |
55 | await this.client.hSet(this.buildKey(key), field, json);
56 | } catch (error) {
57 | this.logger.error(error);
58 | }
59 | }
60 |
61 | async has(key: string) {
62 | try {
63 | return (await this.client.exists(this.buildKey(key))) > 0;
64 | } catch (error) {
65 | this.logger.error(error);
66 | }
67 | }
68 |
69 | async delete(key: string) {
70 | try {
71 | return await this.client.del(this.buildKey(key));
72 | } catch (error) {
73 | this.logger.error(error);
74 | }
75 | }
76 |
77 | async hDelete(key: string, field: string) {
78 | try {
79 | return await this.client.hDel(this.buildKey(key), field);
80 | } catch (error) {
81 | this.logger.error(error);
82 | }
83 | }
84 |
85 | async deleteAll(appendCriteria?: string) {
86 | try {
87 | const keys = await this.keys(appendCriteria);
88 | if (!keys?.length) {
89 | return 0;
90 | }
91 |
92 | return await this.client.del(keys);
93 | } catch (error) {
94 | this.logger.error(error);
95 | }
96 | }
97 |
98 | async keys(appendCriteria?: string) {
99 | try {
100 | const match = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}*`;
101 | const keys = [];
102 | for await (const key of this.client.scanIterator({
103 | MATCH: match,
104 | COUNT: 100,
105 | })) {
106 | keys.push(key);
107 | }
108 |
109 | return [...new Set(keys)];
110 | } catch (error) {
111 | this.logger.error(error);
112 | }
113 | }
114 |
115 | buildKey(key: string) {
116 | return `${this.conf?.PREFIX_KEY}:${this.module}:${key}`;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/config/error.config.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from './logger.config';
2 |
3 | export function onUnexpectedError() {
4 | process.on('uncaughtException', (error, origin) => {
5 | const logger = new Logger('uncaughtException');
6 | logger.error({
7 | origin,
8 | stderr: process.stderr.fd,
9 | error,
10 | });
11 | });
12 |
13 | process.on('unhandledRejection', (error, origin) => {
14 | const logger = new Logger('unhandledRejection');
15 | logger.error({
16 | origin,
17 | stderr: process.stderr.fd,
18 | });
19 | logger.error(error);
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/src/config/event.config.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter2 from 'eventemitter2';
2 |
3 | const maxListeners = parseInt(process.env.EVENT_EMITTER_MAX_LISTENERS, 10) || 50;
4 |
5 | export const eventEmitter = new EventEmitter2({
6 | delimiter: '.',
7 | newListener: false,
8 | ignoreErrors: false,
9 | maxListeners: maxListeners,
10 | });
11 |
--------------------------------------------------------------------------------
/src/config/path.config.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 |
3 | export const ROOT_DIR = process.cwd();
4 | export const INSTANCE_DIR = join(ROOT_DIR, 'instances');
5 | export const SRC_DIR = join(ROOT_DIR, 'src');
6 | export const AUTH_DIR = join(ROOT_DIR, 'store', 'auth');
7 | export const STORE_DIR = join(ROOT_DIR, 'store');
8 |
--------------------------------------------------------------------------------
/src/exceptions/400.exception.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@api/routes/index.router';
2 |
3 | export class BadRequestException {
4 | constructor(...objectError: any[]) {
5 | throw {
6 | status: HttpStatus.BAD_REQUEST,
7 | error: 'Bad Request',
8 | message: objectError.length > 0 ? objectError : undefined,
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/401.exception.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@api/routes/index.router';
2 |
3 | export class UnauthorizedException {
4 | constructor(...objectError: any[]) {
5 | throw {
6 | status: HttpStatus.UNAUTHORIZED,
7 | error: 'Unauthorized',
8 | message: objectError.length > 0 ? objectError : 'Unauthorized',
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/403.exception.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@api/routes/index.router';
2 |
3 | export class ForbiddenException {
4 | constructor(...objectError: any[]) {
5 | throw {
6 | status: HttpStatus.FORBIDDEN,
7 | error: 'Forbidden',
8 | message: objectError.length > 0 ? objectError : undefined,
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/404.exception.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@api/routes/index.router';
2 |
3 | export class NotFoundException {
4 | constructor(...objectError: any[]) {
5 | throw {
6 | status: HttpStatus.NOT_FOUND,
7 | error: 'Not Found',
8 | message: objectError.length > 0 ? objectError : undefined,
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/500.exception.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatus } from '@api/routes/index.router';
2 |
3 | export class InternalServerErrorException {
4 | constructor(...objectError: any[]) {
5 | throw {
6 | status: HttpStatus.INTERNAL_SERVER_ERROR,
7 | error: 'Internal Server Error',
8 | message: objectError.length > 0 ? objectError : undefined,
9 | };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './400.exception';
2 | export * from './401.exception';
3 | export * from './403.exception';
4 | export * from './404.exception';
5 | export * from './500.exception';
6 |
--------------------------------------------------------------------------------
/src/utils/advancedOperatorsSearch.ts:
--------------------------------------------------------------------------------
1 | function normalizeString(str: string): string {
2 | return str
3 | .normalize('NFD')
4 | .replace(/[\u0300-\u036f]/g, '')
5 | .toLowerCase();
6 | }
7 |
8 | export function advancedOperatorsSearch(data: string, query: string): boolean {
9 | const filters = query.split(' ').reduce((acc: Record, filter) => {
10 | const [operator, ...values] = filter.split(':');
11 | const value = values.join(':');
12 |
13 | if (!acc[operator]) {
14 | acc[operator] = [];
15 | }
16 | acc[operator].push(value);
17 | return acc;
18 | }, {});
19 |
20 | const normalizedItem = normalizeString(data);
21 |
22 | return Object.entries(filters).every(([operator, values]) => {
23 | return values.some((val) => {
24 | const subValues = val.split(',');
25 | return subValues.every((subVal) => {
26 | const normalizedSubVal = normalizeString(subVal);
27 |
28 | switch (operator.toLowerCase()) {
29 | case 'contains':
30 | return normalizedItem.includes(normalizedSubVal);
31 | case 'notcontains':
32 | return !normalizedItem.includes(normalizedSubVal);
33 | case 'startswith':
34 | return normalizedItem.startsWith(normalizedSubVal);
35 | case 'endswith':
36 | return normalizedItem.endsWith(normalizedSubVal);
37 | case 'exact':
38 | return normalizedItem === normalizedSubVal;
39 | default:
40 | return false;
41 | }
42 | });
43 | });
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/src/utils/createJid.ts:
--------------------------------------------------------------------------------
1 | // Check if the number is MX or AR
2 | function formatMXOrARNumber(jid: string): string {
3 | const countryCode = jid.substring(0, 2);
4 |
5 | if (Number(countryCode) === 52 || Number(countryCode) === 54) {
6 | if (jid.length === 13) {
7 | const number = countryCode + jid.substring(3);
8 | return number;
9 | }
10 |
11 | return jid;
12 | }
13 | return jid;
14 | }
15 |
16 | // Check if the number is br
17 | function formatBRNumber(jid: string) {
18 | const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/);
19 | if (regexp.test(jid)) {
20 | const match = regexp.exec(jid);
21 | if (match && match[1] === '55') {
22 | const joker = Number.parseInt(match[3][0]);
23 | const ddd = Number.parseInt(match[2]);
24 | if (joker < 7 || ddd < 31) {
25 | return match[0];
26 | }
27 | return match[1] + match[2] + match[3];
28 | }
29 | return jid;
30 | } else {
31 | return jid;
32 | }
33 | }
34 |
35 | export function createJid(number: string): string {
36 | number = number.replace(/:\d+/, '');
37 |
38 | if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
39 | return number;
40 | }
41 |
42 | if (number.includes('@broadcast')) {
43 | return number;
44 | }
45 |
46 | number = number
47 | ?.replace(/\s/g, '')
48 | .replace(/\+/g, '')
49 | .replace(/\(/g, '')
50 | .replace(/\)/g, '')
51 | .split(':')[0]
52 | .split('@')[0];
53 |
54 | if (number.includes('-') && number.length >= 24) {
55 | number = number.replace(/[^\d-]/g, '');
56 | return `${number}@g.us`;
57 | }
58 |
59 | number = number.replace(/\D/g, '');
60 |
61 | if (number.length >= 18) {
62 | number = number.replace(/[^\d-]/g, '');
63 | return `${number}@g.us`;
64 | }
65 |
66 | number = formatMXOrARNumber(number);
67 |
68 | number = formatBRNumber(number);
69 |
70 | return `${number}@s.whatsapp.net`;
71 | }
72 |
--------------------------------------------------------------------------------
/src/utils/findBotByTrigger.ts:
--------------------------------------------------------------------------------
1 | import { advancedOperatorsSearch } from './advancedOperatorsSearch';
2 |
3 | export const findBotByTrigger = async (botRepository: any, content: string, instanceId: string) => {
4 | // Check for triggerType 'all'
5 | const findTriggerAll = await botRepository.findFirst({
6 | where: {
7 | enabled: true,
8 | triggerType: 'all',
9 | instanceId: instanceId,
10 | },
11 | });
12 |
13 | if (findTriggerAll) return findTriggerAll;
14 |
15 | const findTriggerAdvanced = await botRepository.findMany({
16 | where: {
17 | enabled: true,
18 | triggerType: 'advanced',
19 | instanceId: instanceId,
20 | },
21 | });
22 | for (const advanced of findTriggerAdvanced) {
23 | if (advancedOperatorsSearch(content, advanced.triggerValue)) {
24 | return advanced;
25 | }
26 | }
27 |
28 | // Check for exact match
29 | const findTriggerEquals = await botRepository.findFirst({
30 | where: {
31 | enabled: true,
32 | triggerType: 'keyword',
33 | triggerOperator: 'equals',
34 | triggerValue: content,
35 | instanceId: instanceId,
36 | },
37 | });
38 |
39 | if (findTriggerEquals) return findTriggerEquals;
40 |
41 | // Check for regex match
42 | const findRegex = await botRepository.findMany({
43 | where: {
44 | enabled: true,
45 | triggerType: 'keyword',
46 | triggerOperator: 'regex',
47 | instanceId: instanceId,
48 | },
49 | });
50 |
51 | let findTriggerRegex = null;
52 |
53 | for (const regex of findRegex) {
54 | const regexValue = new RegExp(regex.triggerValue);
55 |
56 | if (regexValue.test(content)) {
57 | findTriggerRegex = regex;
58 | break;
59 | }
60 | }
61 |
62 | if (findTriggerRegex) return findTriggerRegex;
63 |
64 | // Check for startsWith match
65 | const findStartsWith = await botRepository.findMany({
66 | where: {
67 | enabled: true,
68 | triggerType: 'keyword',
69 | triggerOperator: 'startsWith',
70 | instanceId: instanceId,
71 | },
72 | });
73 |
74 | let findTriggerStartsWith = null;
75 |
76 | for (const startsWith of findStartsWith) {
77 | if (content.startsWith(startsWith.triggerValue)) {
78 | findTriggerStartsWith = startsWith;
79 | break;
80 | }
81 | }
82 |
83 | if (findTriggerStartsWith) return findTriggerStartsWith;
84 |
85 | // Check for endsWith match
86 | const findEndsWith = await botRepository.findMany({
87 | where: {
88 | enabled: true,
89 | triggerType: 'keyword',
90 | triggerOperator: 'endsWith',
91 | instanceId: instanceId,
92 | },
93 | });
94 |
95 | let findTriggerEndsWith = null;
96 |
97 | for (const endsWith of findEndsWith) {
98 | if (content.endsWith(endsWith.triggerValue)) {
99 | findTriggerEndsWith = endsWith;
100 | break;
101 | }
102 | }
103 |
104 | if (findTriggerEndsWith) return findTriggerEndsWith;
105 |
106 | // Check for contains match
107 | const findContains = await botRepository.findMany({
108 | where: {
109 | enabled: true,
110 | triggerType: 'keyword',
111 | triggerOperator: 'contains',
112 | instanceId: instanceId,
113 | },
114 | });
115 |
116 | let findTriggerContains = null;
117 |
118 | for (const contains of findContains) {
119 | if (content.includes(contains.triggerValue)) {
120 | findTriggerContains = contains;
121 | break;
122 | }
123 | }
124 |
125 | if (findTriggerContains) return findTriggerContains;
126 |
127 | return null;
128 | };
129 |
--------------------------------------------------------------------------------
/src/utils/getConversationMessage.ts:
--------------------------------------------------------------------------------
1 | import { configService, S3 } from '@config/env.config';
2 |
3 | const getTypeMessage = (msg: any) => {
4 | let mediaId: string;
5 |
6 | if (configService.get('S3').ENABLE) mediaId = msg.message.mediaUrl;
7 | else mediaId = msg.key.id;
8 |
9 | const types = {
10 | conversation: msg?.message?.conversation,
11 | extendedTextMessage: msg?.message?.extendedTextMessage?.text,
12 | contactMessage: msg?.message?.contactMessage?.displayName,
13 | locationMessage: msg?.message?.locationMessage?.degreesLatitude,
14 | viewOnceMessageV2:
15 | msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
16 | msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
17 | msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
18 | listResponseMessage: msg?.message?.listResponseMessage?.title,
19 | responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
20 | templateButtonReplyMessage:
21 | msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId,
22 | // Medias
23 | audioMessage: msg?.message?.speechToText
24 | ? msg?.message?.speechToText
25 | : msg?.message?.audioMessage
26 | ? `audioMessage|${mediaId}`
27 | : undefined,
28 | imageMessage: msg?.message?.imageMessage
29 | ? `imageMessage|${mediaId}${msg?.message?.imageMessage?.caption ? `|${msg?.message?.imageMessage?.caption}` : ''}`
30 | : undefined,
31 | videoMessage: msg?.message?.videoMessage
32 | ? `videoMessage|${mediaId}${msg?.message?.videoMessage?.caption ? `|${msg?.message?.videoMessage?.caption}` : ''}`
33 | : undefined,
34 | documentMessage: msg?.message?.documentMessage
35 | ? `documentMessage|${mediaId}${
36 | msg?.message?.documentMessage?.caption ? `|${msg?.message?.documentMessage?.caption}` : ''
37 | }`
38 | : undefined,
39 | documentWithCaptionMessage: msg?.message?.documentWithCaptionMessage?.message?.documentMessage
40 | ? `documentWithCaptionMessage|${mediaId}${
41 | msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption
42 | ? `|${msg?.message?.documentWithCaptionMessage?.message?.documentMessage?.caption}`
43 | : ''
44 | }`
45 | : undefined,
46 | externalAdReplyBody: msg?.contextInfo?.externalAdReply?.body
47 | ? `externalAdReplyBody|${msg.contextInfo.externalAdReply.body}`
48 | : undefined,
49 | };
50 |
51 | const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown';
52 |
53 | return { ...types, messageType };
54 | };
55 |
56 | const getMessageContent = (types: any) => {
57 | const typeKey = Object.keys(types).find((key) => key !== 'externalAdReplyBody' && types[key] !== undefined);
58 |
59 | let result = typeKey ? types[typeKey] : undefined;
60 |
61 | if (types.externalAdReplyBody) {
62 | result = result ? `${result}\n${types.externalAdReplyBody}` : types.externalAdReplyBody;
63 | }
64 |
65 | return result;
66 | };
67 |
68 | export const getConversationMessage = (msg: any) => {
69 | const types = getTypeMessage(msg);
70 |
71 | const messageContent = getMessageContent(types);
72 |
73 | return messageContent;
74 | };
75 |
--------------------------------------------------------------------------------
/src/utils/i18n.ts:
--------------------------------------------------------------------------------
1 | import { ConfigService, Language } from '@config/env.config';
2 | import fs from 'fs';
3 | import i18next from 'i18next';
4 | import path from 'path';
5 |
6 | const languages = ['en', 'pt-BR', 'es'];
7 | const translationsPath = path.join(__dirname, 'translations');
8 | const configService: ConfigService = new ConfigService();
9 |
10 | const resources: any = {};
11 |
12 | languages.forEach((language) => {
13 | const languagePath = path.join(translationsPath, `${language}.json`);
14 | if (fs.existsSync(languagePath)) {
15 | resources[language] = {
16 | translation: require(languagePath),
17 | };
18 | }
19 | });
20 |
21 | i18next.init({
22 | resources,
23 | fallbackLng: 'en',
24 | lng: configService.get('LANGUAGE'),
25 | debug: false,
26 |
27 | interpolation: {
28 | escapeValue: false,
29 | },
30 | });
31 | export default i18next;
32 |
--------------------------------------------------------------------------------
/src/utils/instrumentSentry.ts:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/node';
2 |
3 | const dsn = process.env.SENTRY_DSN;
4 |
5 | if (dsn) {
6 | Sentry.init({
7 | dsn: dsn,
8 | environment: process.env.NODE_ENV || 'development',
9 | tracesSampleRate: 1.0,
10 | profilesSampleRate: 1.0,
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/makeProxyAgent.ts:
--------------------------------------------------------------------------------
1 | import { HttpsProxyAgent } from 'https-proxy-agent';
2 |
3 | type Proxy = {
4 | host: string;
5 | password?: string;
6 | port: string;
7 | protocol: string;
8 | username?: string;
9 | };
10 |
11 | export function makeProxyAgent(proxy: Proxy | string) {
12 | if (typeof proxy === 'string') {
13 | return new HttpsProxyAgent(proxy);
14 | }
15 |
16 | const { host, password, port, protocol, username } = proxy;
17 | let proxyUrl = `${protocol}://${host}:${port}`;
18 |
19 | if (username && password) {
20 | proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`;
21 | }
22 | return new HttpsProxyAgent(proxyUrl);
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/onWhatsappCache.ts:
--------------------------------------------------------------------------------
1 | import { prismaRepository } from '@api/server.module';
2 | import { configService, Database } from '@config/env.config';
3 | import dayjs from 'dayjs';
4 |
5 | function getAvailableNumbers(remoteJid: string) {
6 | const numbersAvailable: string[] = [];
7 |
8 | if (remoteJid.startsWith('+')) {
9 | remoteJid = remoteJid.slice(1);
10 | }
11 |
12 | const [number, domain] = remoteJid.split('@');
13 |
14 | // Brazilian numbers
15 | if (remoteJid.startsWith('55')) {
16 | const numberWithDigit =
17 | number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;
18 | const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);
19 |
20 | numbersAvailable.push(numberWithDigit);
21 | numbersAvailable.push(numberWithoutDigit);
22 | }
23 |
24 | // Mexican/Argentina numbers
25 | // Ref: https://faq.whatsapp.com/1294841057948784
26 | else if (number.startsWith('52') || number.startsWith('54')) {
27 | let prefix = '';
28 | if (number.startsWith('52')) {
29 | prefix = '1';
30 | }
31 | if (number.startsWith('54')) {
32 | prefix = '9';
33 | }
34 |
35 | const numberWithDigit =
36 | number.slice(2, 3) === prefix && number.length === 13
37 | ? number
38 | : `${number.slice(0, 2)}${prefix}${number.slice(2)}`;
39 | const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);
40 |
41 | numbersAvailable.push(numberWithDigit);
42 | numbersAvailable.push(numberWithoutDigit);
43 | }
44 |
45 | // Other countries
46 | else {
47 | numbersAvailable.push(remoteJid);
48 | }
49 |
50 | return numbersAvailable.map((number) => `${number}@${domain}`);
51 | }
52 |
53 | interface ISaveOnWhatsappCacheParams {
54 | remoteJid: string;
55 | }
56 | export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
57 | if (configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
58 | const upsertsQuery = data.map((item) => {
59 | const remoteJid = item.remoteJid.startsWith('+') ? item.remoteJid.slice(1) : item.remoteJid;
60 | const numbersAvailable = getAvailableNumbers(remoteJid);
61 |
62 | return prismaRepository.isOnWhatsapp.upsert({
63 | create: { remoteJid: remoteJid, jidOptions: numbersAvailable.join(',') },
64 | update: { jidOptions: numbersAvailable.join(',') },
65 | where: { remoteJid: remoteJid },
66 | });
67 | });
68 |
69 | await prismaRepository.$transaction(upsertsQuery);
70 | }
71 | }
72 |
73 | export async function getOnWhatsappCache(remoteJids: string[]) {
74 | let results: {
75 | remoteJid: string;
76 | number: string;
77 | jidOptions: string[];
78 | }[] = [];
79 |
80 | if (configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
81 | const remoteJidsWithoutPlus = remoteJids.map((remoteJid) => getAvailableNumbers(remoteJid)).flat();
82 |
83 | const onWhatsappCache = await prismaRepository.isOnWhatsapp.findMany({
84 | where: {
85 | OR: remoteJidsWithoutPlus.map((remoteJid) => ({ jidOptions: { contains: remoteJid } })),
86 | updatedAt: {
87 | gte: dayjs().subtract(configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP_DAYS, 'days').toDate(),
88 | },
89 | },
90 | });
91 |
92 | results = onWhatsappCache.map((item) => ({
93 | remoteJid: item.remoteJid,
94 | number: item.remoteJid.split('@')[0],
95 | jidOptions: item.jidOptions.split(','),
96 | }));
97 | }
98 |
99 | return results;
100 | }
101 |
--------------------------------------------------------------------------------
/src/utils/renderStatus.ts:
--------------------------------------------------------------------------------
1 | import { wa } from '@api/types/wa.types';
2 |
3 | export const status: Record = {
4 | 0: 'ERROR',
5 | 1: 'PENDING',
6 | 2: 'SERVER_ACK',
7 | 3: 'DELIVERY_ACK',
8 | 4: 'READ',
9 | 5: 'PLAYED',
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/sendTelemetry.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import fs from 'fs';
3 |
4 | const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
5 |
6 | export interface TelemetryData {
7 | route: string;
8 | apiVersion: string;
9 | timestamp: Date;
10 | }
11 |
12 | export const sendTelemetry = async (route: string): Promise => {
13 | const enabled = process.env.TELEMETRY_ENABLED === undefined || process.env.TELEMETRY_ENABLED === 'true';
14 |
15 | if (!enabled) {
16 | return;
17 | }
18 |
19 | if (route === '/') {
20 | return;
21 | }
22 |
23 | const telemetry: TelemetryData = {
24 | route,
25 | apiVersion: `${packageJson.version}`,
26 | timestamp: new Date(),
27 | };
28 |
29 | const url =
30 | process.env.TELEMETRY_URL && process.env.TELEMETRY_URL !== ''
31 | ? process.env.TELEMETRY_URL
32 | : 'https://log.evolution-api.com/telemetry';
33 |
34 | axios
35 | .post(url, telemetry)
36 | .then(() => {})
37 | .catch(() => {});
38 | };
39 |
--------------------------------------------------------------------------------
/src/utils/server-up.ts:
--------------------------------------------------------------------------------
1 | import { configService, SslConf } from '@config/env.config';
2 | import { Express } from 'express';
3 | import { readFileSync } from 'fs';
4 | import * as http from 'http';
5 | import * as https from 'https';
6 |
7 | export class ServerUP {
8 | static #app: Express;
9 |
10 | static set app(e: Express) {
11 | this.#app = e;
12 | }
13 |
14 | static get https() {
15 | const { FULLCHAIN, PRIVKEY } = configService.get('SSL_CONF');
16 | return https.createServer(
17 | {
18 | cert: readFileSync(FULLCHAIN),
19 | key: readFileSync(PRIVKEY),
20 | },
21 | ServerUP.#app,
22 | );
23 | }
24 |
25 | static get http() {
26 | return http.createServer(ServerUP.#app);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "qrgeneratedsuccesfully": "QRCode successfully generated!",
3 | "scanqr": "Scan this QR code within the next 40 seconds.",
4 | "qrlimitreached": "QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.",
5 | "cw.inbox.connected": "🚀 Connection successfully established!",
6 | "cw.inbox.disconnect": "🚨 Disconnecting WhatsApp from inbox *{{inboxName}}*.",
7 | "cw.inbox.alreadyConnected": "🚨 {{inboxName}} instance is connected.",
8 | "cw.inbox.clearCache": "✅ {{inboxName}} instance cache cleared.",
9 | "cw.inbox.notFound": "⚠️ {{inboxName}} instance not found.",
10 | "cw.inbox.status": "⚠️ {{inboxName}} instance status: *{{state}}*.",
11 | "cw.import.startImport": "💬 Starting to import messages. Please wait...",
12 | "cw.import.importingMessages": "💬 Importing messages. More one moment...",
13 | "cw.import.messagesImported": "💬 {{totalMessagesImported}} messages imported. Refresh page to see the new messages.",
14 | "cw.import.messagesException": "💬 Something went wrong in importing messages.",
15 | "cw.locationMessage.location": "Location",
16 | "cw.locationMessage.latitude": "Latitude",
17 | "cw.locationMessage.longitude": "Longitude",
18 | "cw.locationMessage.locationName": "Name",
19 | "cw.locationMessage.locationAddress": "Address",
20 | "cw.locationMessage.locationUrl": "URL",
21 | "cw.contactMessage.contact": "Contact",
22 | "cw.contactMessage.name": "Name",
23 | "cw.contactMessage.number": "Number",
24 | "cw.message.notsent": "🚨 The message could not be sent. Please check your connection. {{error}}",
25 | "cw.message.numbernotinwhatsapp": "🚨 The message was not sent as the contact is not a valid Whatsapp number.",
26 | "cw.message.edited": "Edited Message"
27 | }
--------------------------------------------------------------------------------
/src/utils/translations/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "qrgeneratedsuccesfully": "Código QR generado exitosamente!",
3 | "scanqr": "Escanea este código QR en los próximos 40 segundos.",
4 | "qrlimitreached": "🚨 Se alcanzó el límite de generación de QRCode. Para generar un nuevo QRCode, envíe el mensaje 'init' nuevamente.",
5 | "cw.inbox.connected": "🚀 ¡Conexión establecida exitosamente!",
6 | "cw.inbox.disconnect": "🚨 Instancia *{{inboxName}}* desconectado de Whatsapp.",
7 | "cw.inbox.alreadyConnected": "🚨 La instancia {{inboxName}} está conectada.",
8 | "cw.inbox.clearCache": "✅ Caché de la instancia {{inboxName}} borrada.",
9 | "cw.inbox.notFound": "⚠️ Instancia {{inboxName}} no encontrada.",
10 | "cw.inbox.status": "⚠️ Estado de la instancia {{inboxName}}: *{{state}}*.",
11 | "cw.import.startImport": "💬 Empezando a importar mensajes. Espere por favor...",
12 | "cw.import.importingMessages": "💬 Importando mensajes. mas un momento...",
13 | "cw.import.messagesImported": "💬 {{totalMessagesImported}} mensajes importados. Actualiza la página para ver los nuevos mensajes..",
14 | "cw.import.messagesException": "⚠️ Algo salió mal al importar mensajes..",
15 | "cw.locationMessage.location": "Ubicación",
16 | "cw.locationMessage.latitude": "Latitude",
17 | "cw.locationMessage.longitude": "Longitude",
18 | "cw.locationMessage.locationName": "Nombre",
19 | "cw.locationMessage.locationAddress": "Direccion",
20 | "cw.locationMessage.locationUrl": "URL",
21 | "cw.contactMessage.contact": "Contacto",
22 | "cw.contactMessage.name": "Nombre",
23 | "cw.contactMessage.number": "Numero",
24 | "cw.message.notsent": "🚨 El mensaje no se pudo enviar. Comprueba tu conexión. {{error}}",
25 | "cw.message.numbernotinwhatsapp": "🚨 El mensaje no fue enviado porque el contacto no es un número de Whatsapp válido.",
26 | "cw.message.edited": "Mensaje editado"
27 | }
--------------------------------------------------------------------------------
/src/utils/translations/pt-BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "qrgeneratedsuccesfully": "QRCode gerado com sucesso!",
3 | "scanqr": "Escaneie o QRCode com o WhatsApp nos próximos 40 segundos.",
4 | "qrlimitreached": "Limite de geração de QRCode atingido! Para gerar um novo QRCode, envie o texto 'init' nesta conversa.",
5 | "cw.inbox.connected": "🚀 Conectado com sucesso!",
6 | "cw.inbox.disconnect": "🚨 Instância *{{inboxName}}* desconectada do WhatsApp.",
7 | "cw.inbox.alreadyConnected": "🚨 Instância *{{inboxName}}* já está conectada.",
8 | "cw.inbox.clearCache": "✅ Instância *{{inboxName}}* cache removido.",
9 | "cw.inbox.notFound": "⚠️ Instância *{{inboxName}}* não encontrada.",
10 | "cw.inbox.status": "⚠️ Status da instância {{inboxName}}: *{{state}}*.",
11 | "cw.import.startImport": "💬 Iniciando importação de mensagens. Por favor, aguarde...",
12 | "cw.import.importingMessages": "💬 Importando mensagens. Mais um momento...",
13 | "cw.import.messagesImported": "💬 {{totalMessagesImported}} mensagens importadas. Atualize a página para ver as novas mensagens.",
14 | "cw.import.messagesException": "💬 Não foi possível importar as mensagens.",
15 | "cw.locationMessage.location": "Localização",
16 | "cw.locationMessage.latitude": "Latitude",
17 | "cw.locationMessage.longitude": "Longitude",
18 | "cw.locationMessage.locationName": "Nome",
19 | "cw.locationMessage.locationAddress": "Endereço",
20 | "cw.locationMessage.locationUrl": "URL",
21 | "cw.contactMessage.contact": "Contato",
22 | "cw.contactMessage.name": "Nome",
23 | "cw.contactMessage.number": "Número",
24 | "cw.message.notsent": "🚨 Não foi possível enviar a mensagem. Verifique sua conexão. {{error}}",
25 | "cw.message.numbernotinwhatsapp": "🚨 A mensagem não foi enviada, pois o contato não é um número válido do WhatsApp.",
26 | "cw.message.edited": "Mensagem editada"
27 | }
--------------------------------------------------------------------------------
/src/utils/use-multi-file-auth-state-redis-db.ts:
--------------------------------------------------------------------------------
1 | import { CacheService } from '@api/services/cache.service';
2 | import { Logger } from '@config/logger.config';
3 | import { AuthenticationCreds, AuthenticationState, initAuthCreds, proto, SignalDataTypeMap } from 'baileys';
4 |
5 | export async function useMultiFileAuthStateRedisDb(
6 | instanceName: string,
7 | cache: CacheService,
8 | ): Promise<{
9 | state: AuthenticationState;
10 | saveCreds: () => Promise;
11 | }> {
12 | const logger = new Logger('useMultiFileAuthStateRedisDb');
13 |
14 | const writeData = async (data: any, key: string): Promise => {
15 | try {
16 | return await cache.hSet(instanceName, key, data);
17 | } catch (error) {
18 | return logger.error({ localError: 'writeData', error });
19 | }
20 | };
21 |
22 | const readData = async (key: string): Promise => {
23 | try {
24 | return await cache.hGet(instanceName, key);
25 | } catch (error) {
26 | logger.error({ localError: 'readData', error });
27 | return;
28 | }
29 | };
30 |
31 | const removeData = async (key: string) => {
32 | try {
33 | return await cache.hDelete(instanceName, key);
34 | } catch (error) {
35 | logger.error({ readData: 'removeData', error });
36 | }
37 | };
38 |
39 | const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
40 |
41 | return {
42 | state: {
43 | creds,
44 | keys: {
45 | get: async (type, ids: string[]) => {
46 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
47 | // @ts-ignore
48 | const data: { [_: string]: SignalDataTypeMap[type] } = {};
49 | await Promise.all(
50 | ids.map(async (id) => {
51 | let value = await readData(`${type}-${id}`);
52 | if (type === 'app-state-sync-key' && value) {
53 | value = proto.Message.AppStateSyncKeyData.fromObject(value);
54 | }
55 |
56 | data[id] = value;
57 | }),
58 | );
59 |
60 | return data;
61 | },
62 | set: async (data: any) => {
63 | const tasks: Promise[] = [];
64 | for (const category in data) {
65 | for (const id in data[category]) {
66 | const value = data[category][id];
67 | const key = `${category}-${id}`;
68 | tasks.push(value ? await writeData(value, key) : await removeData(key));
69 | }
70 | }
71 |
72 | await Promise.all(tasks);
73 | },
74 | },
75 | },
76 | saveCreds: async () => {
77 | return await writeData(creds, 'creds');
78 | },
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/src/validate/label.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7, JSONSchema7Definition } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | const numberDefinition: JSONSchema7Definition = {
24 | type: 'string',
25 | description: 'Invalid format',
26 | };
27 |
28 | export const handleLabelSchema: JSONSchema7 = {
29 | $id: v4(),
30 | type: 'object',
31 | properties: {
32 | number: { ...numberDefinition },
33 | labelId: { type: 'string' },
34 | action: { type: 'string', enum: ['add', 'remove'] },
35 | },
36 | required: ['number', 'labelId', 'action'],
37 | ...isNotEmpty('number', 'labelId', 'action'),
38 | };
39 |
--------------------------------------------------------------------------------
/src/validate/proxy.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const proxySchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | enabled: { type: 'boolean', enum: [true, false] },
28 | host: { type: 'string' },
29 | port: { type: 'string' },
30 | protocol: { type: 'string' },
31 | username: { type: 'string' },
32 | password: { type: 'string' },
33 | },
34 | required: ['enabled', 'host', 'port', 'protocol'],
35 | ...isNotEmpty('enabled', 'host', 'port', 'protocol'),
36 | };
37 |
--------------------------------------------------------------------------------
/src/validate/settings.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const settingsSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | rejectCall: { type: 'boolean' },
28 | msgCall: { type: 'string' },
29 | groupsIgnore: { type: 'boolean' },
30 | alwaysOnline: { type: 'boolean' },
31 | readMessages: { type: 'boolean' },
32 | readStatus: { type: 'boolean' },
33 | syncFullHistory: { type: 'boolean' },
34 | wavoipToken: { type: 'string' },
35 | },
36 | required: ['rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'],
37 | ...isNotEmpty('rejectCall', 'groupsIgnore', 'alwaysOnline', 'readMessages', 'readStatus', 'syncFullHistory'),
38 | };
39 |
--------------------------------------------------------------------------------
/src/validate/template.schema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema7 } from 'json-schema';
2 | import { v4 } from 'uuid';
3 |
4 | const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
5 | const properties = {};
6 | propertyNames.forEach(
7 | (property) =>
8 | (properties[property] = {
9 | minLength: 1,
10 | description: `The "${property}" cannot be empty`,
11 | }),
12 | );
13 | return {
14 | if: {
15 | propertyNames: {
16 | enum: [...propertyNames],
17 | },
18 | },
19 | then: { properties },
20 | };
21 | };
22 |
23 | export const templateSchema: JSONSchema7 = {
24 | $id: v4(),
25 | type: 'object',
26 | properties: {
27 | name: { type: 'string' },
28 | category: { type: 'string', enum: ['AUTHENTICATION', 'MARKETING', 'UTILITY'] },
29 | allowCategoryChange: { type: 'boolean' },
30 | language: { type: 'string' },
31 | components: { type: 'array' },
32 | webhookUrl: { type: 'string' },
33 | },
34 | required: ['name', 'category', 'language', 'components'],
35 | ...isNotEmpty('name', 'category', 'language', 'components'),
36 | };
37 |
--------------------------------------------------------------------------------
/src/validate/validate.schema.ts:
--------------------------------------------------------------------------------
1 | // Integrations Schema
2 | export * from './chat.schema';
3 | export * from './group.schema';
4 | export * from './instance.schema';
5 | export * from './label.schema';
6 | export * from './message.schema';
7 | export * from './proxy.schema';
8 | export * from './settings.schema';
9 | export * from './template.schema';
10 | export * from '@api/integrations/chatbot/chatbot.schema';
11 | export * from '@api/integrations/event/event.schema';
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true,
4 | "emitDecoratorMetadata": true,
5 | "declaration": true,
6 | "target": "es2020",
7 | "module": "CommonJS",
8 | "rootDir": "./",
9 | "resolveJsonModule": true,
10 | "removeComments": true,
11 | "outDir": "./dist",
12 | "noEmitOnError": true,
13 | "esModuleInterop": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "strict": false,
16 | "skipLibCheck": true,
17 | "strictNullChecks": false,
18 | "incremental": true,
19 | "noImplicitAny": false,
20 | "baseUrl": ".",
21 | "paths": {
22 | "@api/*": ["./src/api/*"],
23 | "@cache/*": ["./src/cache/*"],
24 | "@config/*": ["./src/config/*"],
25 | "@exceptions": ["./src/exceptions"],
26 | "@libs/*": ["./src/libs/*"],
27 | "@utils/*": ["./src/utils/*"],
28 | "@validate/*": ["./src/validate/*"]
29 | },
30 | "moduleResolution": "Node"
31 | },
32 | "exclude": ["node_modules", "./test", "./dist", "./prisma"],
33 | "include": [
34 | "src/**/*",
35 | "src/**/*.json"
36 | ]
37 | }
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { cpSync } from 'node:fs';
2 |
3 | import { defineConfig } from 'tsup';
4 |
5 | export default defineConfig({
6 | entry: ['src'],
7 | outDir: 'dist',
8 | splitting: false,
9 | sourcemap: true,
10 | clean: true,
11 | minify: true,
12 | format: ['cjs', 'esm'],
13 | onSuccess: async () => {
14 | cpSync('src/utils/translations', 'dist/translations', { recursive: true });
15 | },
16 | loader: {
17 | '.json': 'file',
18 | },
19 | });
20 |
--------------------------------------------------------------------------------