From 8a301bf1ab1e80fa0559a33b8ee9e1c6fd5f277d Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:18:03 -0300 Subject: [PATCH 1/7] dockerized --- .devcontainer/devcontainer.json | 45 ++++++++++++++++++++++++++++++++ .devcontainer/docker-compose.yml | 26 ++++++++++++++++++ .github/dependabot.yml | 12 +++++++++ docker-compose.dev.yml | 39 +++++++++++++++++++++++++++ tsconfig.json | 1 + 5 files changed, 123 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .github/dependabot.yml create mode 100644 docker-compose.dev.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..176358c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose +{ + "name": "Existing Docker Compose (Extend)", + + // Update the 'dockerComposeFile' list if you have more compose files or use different names. + // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + "dockerComposeFile": [ + "../docker-compose.dev.yml", + "docker-compose.yml" + ], + + // The 'service' property is the name of the service for the container that VS Code should + // use. Update this value and .devcontainer/docker-compose.yml to the real service name. + "service": "baileys-api", + + // The optional 'workspaceFolder' property is the path VS Code should open by default when + // connected. This is typically a file mount in .devcontainer/docker-compose.yml + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/devcontainers-contrib/features/prisma:2": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..3b22f0f --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' +services: + # Update this to the name of the service you want to work with in your docker-compose.yml file + baileys-api: + # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer + # folder. Note that the path of the Dockerfile and context is relative to the *primary* + # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" + # array). The sample below assumes your primary file is in the root of your project. + # + # build: + # context: . + # dockerfile: .devcontainer/Dockerfile + + volumes: + # Update this to wherever you want VS Code to mount the folder of your project + - ..:/workspaces:cached + + # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Overrides default command so things don't shut down after the process ends. + command: /bin/sh -c "while sleep 1000; do :; done" + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..8a1a470 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,39 @@ +version: '3.8' + +services: + baileys-db: + image: postgres + container_name: baileys-db + environment: + POSTGRES_PASSWORD: 2024pass + POSTGRES_DB: bailey + ports: + - "5432:5432" # Exportado apenas para demonstração, remova em produção + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - wpp-net + baileys-api: + container_name: baileys-api + build: + context: . + dockerfile: ./docker/Dockerfile.dev + volumes: + - '.:/app' + - /app/node_modules + - prisma:/prisma + ports: + - "3000:3000" + environment: + DATABASE_URL: postgres://postgres:2024pass@baileys-db:5432/bailey + depends_on: + - baileys-db + networks: + - wpp-net + +volumes: + postgres_data: + prisma: +networks: + wpp-net: + driver: bridge diff --git a/tsconfig.json b/tsconfig.json index 1892c19..96a003b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "lib": ["ESNext"], "outDir": "dist/", "baseUrl": ".", + "rootDir": "src", "paths": { "@/*": ["src/*"] } From fea4b103c421b40dd538732c192875178b0fa149 Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:18:17 -0300 Subject: [PATCH 2/7] chore update bailey --- package-lock.json | 205 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 194 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4157fb7..6fd45f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "baileys-api", - "version": "1.1.2", + "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "baileys-api", - "version": "1.1.2", + "version": "1.1.4", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@hapi/boom": "^10.0.1", "@prisma/client": "^4.16.2", - "@whiskeysockets/baileys": "^6.6.0", + "@whiskeysockets/baileys": "^6.7.5", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -19,6 +20,7 @@ "link-preview-js": "^3.0.5", "long": "^5.2.3", "pino": "^7.11.0", + "pino-pretty": "^11.0.0", "prisma": "^4.16.2", "qrcode": "^1.5.3", "qrcode-terminal": "^0.12.0", @@ -961,12 +963,14 @@ } }, "node_modules/@whiskeysockets/baileys": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-6.6.0.tgz", - "integrity": "sha512-4aIPHztdLZP24Qac7mudZTMR7qIsEDQxlpCBJE4atYHFAa5tlwCGPcUy249q3aaayxYtQAwheVG42L6AkRxAwg==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-6.7.5.tgz", + "integrity": "sha512-5kDwir6wT1BY0aIdsJXDAHoq7Gy3oYwRXj3qFoS6MOC0udzF4e2D4vTbA/LVLg7a54dtDxXj+iEnrWcaE9jQZA==", + "license": "MIT", "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", "@hapi/boom": "^9.1.3", + "async-lock": "^1.4.1", "audio-decode": "^2.1.3", "axios": "^1.3.3", "cache-manager": "4.0.1", @@ -977,7 +981,7 @@ "node-cache": "^5.1.2", "pino": "^7.0.0", "protobufjs": "^7.2.4", - "uuid": "^9.0.0", + "uuid": "^10.0.0", "ws": "^8.13.0" }, "peerDependencies": { @@ -1221,6 +1225,12 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1320,6 +1330,25 @@ "node": ">=0.10.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1394,6 +1423,29 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1654,6 +1706,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2027,6 +2084,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2522,6 +2587,14 @@ "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -2732,6 +2805,11 @@ "node": ">=0.10.0" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2786,6 +2864,11 @@ "node": ">=6" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -3323,6 +3406,11 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "node_modules/hosted-git-info": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", @@ -3665,6 +3753,14 @@ "node": ">=0.10.0" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4028,7 +4124,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4687,6 +4782,70 @@ "split2": "^4.0.0" } }, + "node_modules/pino-pretty": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.2.tgz", + "integrity": "sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/pino-std-serializers": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", @@ -4769,6 +4928,14 @@ "node": ">=14.17" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-warning": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", @@ -4820,6 +4987,15 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5279,6 +5455,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -5816,7 +5997,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -6561,13 +6741,14 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 47cbf88..7095314 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "@hapi/boom": "^10.0.1", "@prisma/client": "^4.16.2", - "@whiskeysockets/baileys": "^6.6.0", + "@whiskeysockets/baileys": "^6.7.5", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", From b4a3b84f465fb7632db21a6e62d2b83cb2c7d7d8 Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:18:41 -0300 Subject: [PATCH 3/7] add dockerfile --- docker/Dockerfile.dev | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docker/Dockerfile.dev diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev new file mode 100644 index 0000000..e7925ca --- /dev/null +++ b/docker/Dockerfile.dev @@ -0,0 +1,17 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package*.json . + +RUN npm install --quiet + +RUN npx prisma migrate + +COPY . . + +EXPOSE 3000 + +CMD [ "npm", "run", "dev" ] + + From dcbad79ca3aff25b16acd0cd0f5b8126a1a4e426 Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:19:03 -0300 Subject: [PATCH 4/7] fix: unique map label --- prisma/schema.prisma | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index edf5cec..3cd9d58 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,7 +6,7 @@ generator client { } datasource db { - provider = "mysql" + provider = "postgres" url = env("DATABASE_URL") } @@ -58,7 +58,7 @@ model Chat { lastMessageRecvTimestamp Int? commentsCount Int? - @@unique([sessionId, id], map: "unique_id_per_session_id") + @@unique([sessionId, id], map: "unique_id_per_session_id_1") @@index([sessionId]) } @@ -72,7 +72,7 @@ model Contact { imgUrl String? @db.VarChar(255) status String? @db.VarChar(128) - @@unique([sessionId, id], map: "unique_id_per_session_id") + @@unique([sessionId, id], map: "unique_id_per_session_id_2") @@index([sessionId]) } @@ -95,7 +95,7 @@ model GroupMetadata { ephemeralDuration Int? inviteCode String? @db.VarChar(255) - @@unique([sessionId, id], map: "unique_id_per_session_id") + @@unique([sessionId, id], map: "unique_id_per_session_id_3") @@index([sessionId]) } @@ -160,6 +160,6 @@ model Session { id String @db.VarChar(255) data String @db.Text - @@unique([sessionId, id], map: "unique_id_per_session_id") + @@unique([sessionId, id], map: "unique_id_per_session_id_4") @@index([sessionId]) } From 538c97413d42c12f34c4b3032b52057720a6e563 Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:21:26 -0300 Subject: [PATCH 5/7] add search query params, status based in waconnection --- src/controllers/contact.ts | 11 +++++++++-- src/controllers/group.ts | 11 +++++++++-- src/controllers/session.ts | 7 +++++++ src/routes/sessions.ts | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/controllers/contact.ts b/src/controllers/contact.ts index 8f9c009..b174c31 100644 --- a/src/controllers/contact.ts +++ b/src/controllers/contact.ts @@ -7,12 +7,19 @@ import { prisma } from "@/db"; export const list: RequestHandler = async (req, res) => { try { const { sessionId } = req.params; - const { cursor = undefined, limit = 25 } = req.query; + const { cursor = undefined, limit = 25, search } = req.query; const contacts = await prisma.contact.findMany({ cursor: cursor ? { pkId: Number(cursor) } : undefined, take: Number(limit), skip: cursor ? 1 : 0, - where: { id: { endsWith: "s.whatsapp.net" }, sessionId }, + where: { + id: { endsWith: "s.whatsapp.net" }, + sessionId, + name: { + contains: String(search), + mode: 'insensitive', + } + }, }); res.status(200).json({ diff --git a/src/controllers/group.ts b/src/controllers/group.ts index c78e99b..14704a3 100644 --- a/src/controllers/group.ts +++ b/src/controllers/group.ts @@ -7,12 +7,19 @@ import { prisma } from "@/db"; export const list: RequestHandler = async (req, res) => { try { const { sessionId } = req.params; - const { cursor = undefined, limit = 25 } = req.query; + const { cursor = undefined, limit = 25, search } = req.query; const groups = await prisma.contact.findMany({ cursor: cursor ? { pkId: Number(cursor) } : undefined, take: Number(limit), skip: cursor ? 1 : 0, - where: { id: { endsWith: "g.us" }, sessionId }, + where: { + id: { endsWith: "g.us" }, + sessionId, + name: { + contains: String(search), + mode: 'insensitive', + } + }, }); res.status(200).json({ diff --git a/src/controllers/session.ts b/src/controllers/session.ts index ec7bbaa..c2c81a8 100644 --- a/src/controllers/session.ts +++ b/src/controllers/session.ts @@ -27,6 +27,13 @@ export const add: RequestHandler = async (req, res) => { createSession({ sessionId, res, readIncomingMessages, socketConfig }); }; +export const reload: RequestHandler = async (req, res) => { + const { sessionId, readIncomingMessages, ...socketConfig } = req.body; + + if (!sessionExists(sessionId)) return res.status(400).json({ error: "Session not exists" }); + createSession({ sessionId, res, readIncomingMessages, socketConfig }); +}; + export const addSSE: RequestHandler = async (req, res) => { const { sessionId } = req.params; res.writeHead(200, { diff --git a/src/routes/sessions.ts b/src/routes/sessions.ts index df2c99d..35ed77c 100644 --- a/src/routes/sessions.ts +++ b/src/routes/sessions.ts @@ -10,6 +10,7 @@ router.get("/", apiKeyValidator, session.list); router.get("/:sessionId", apiKeyValidator, sessionValidator, session.find); router.get("/:sessionId/status", apiKeyValidator, sessionValidator, session.status); router.post("/add", body("sessionId").isString().notEmpty(), apiKeyValidator, requestValidator, session.add); +router.post("/reload", body("sessionId").isString().notEmpty(), apiKeyValidator, requestValidator, session.add); router.get("/:sessionId/add-sse", apiKeyValidatorParam, session.addSSE); router.delete("/:sessionId", apiKeyValidator, sessionValidator, session.del); From 308396e5002f9cb1a5df0c9e9cf336e5b218395a Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:22:42 -0300 Subject: [PATCH 6/7] fix: throw when without participants --- src/store/handlers/group-meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/handlers/group-meta.ts b/src/store/handlers/group-meta.ts index 24a3746..46e3829 100644 --- a/src/store/handlers/group-meta.ts +++ b/src/store/handlers/group-meta.ts @@ -81,7 +81,7 @@ export default function groupMetadataHandler(sessionId: string, event: BaileysEv } break; case "remove": - metadata.participants = metadata.participants.filter((p) => !participants.includes(p.id)); + metadata.participants = metadata.participants?.filter((p) => !participants.includes(p.id)); break; } From 0dcde8e1a9207251e3830bd71f24eb0406a21fb5 Mon Sep 17 00:00:00 2001 From: lucas mota Date: Wed, 31 Jul 2024 16:24:03 -0300 Subject: [PATCH 7/7] fix: contacts come with several patches of N contacts, deleting those that are not in this patch ends up deleting those received in the previous patch --- src/index.ts | 1 - src/store/handlers/contact.ts | 41 ++++--- src/whatsapp.ts | 195 +++++++++++++++++++++++++++++++--- 3 files changed, 209 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index 44beb09..b1a3d18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,6 @@ const app = express(); app.use(cors()); app.use(express.json()); app.use("/", routes); - app.all("*", (_: Request, res: Response) => res.status(404).json({ error: "URL not found" })); const host = process.env.HOST || "0.0.0.0"; diff --git a/src/store/handlers/contact.ts b/src/store/handlers/contact.ts index cc15980..8083c3f 100644 --- a/src/store/handlers/contact.ts +++ b/src/store/handlers/contact.ts @@ -4,6 +4,7 @@ import { transformPrisma } from "@/store/utils"; import { prisma } from "@/db"; import { logger } from "@/shared"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; +import { PrismaClient } from "@prisma/client"; export default function contactHandler(sessionId: string, event: BaileysEventEmitter) { let listening = false; @@ -31,7 +32,8 @@ export default function contactHandler(sessionId: string, event: BaileysEventEmi await Promise.any([ ...upsertPromises, - prisma.contact.deleteMany({ where: { id: { in: deletedOldContactIds }, sessionId } }), + //danger: contacts come with several patches of N contacts, deleting those that are not in this patch ends up deleting those received in the previous patch + //prisma.contact.deleteMany({ where: { id: { in: deletedOldContactIds }, sessionId } }), ]); logger.info( { deletedContacts: deletedOldContactIds.length, newContacts: contacts.length }, @@ -44,22 +46,31 @@ export default function contactHandler(sessionId: string, event: BaileysEventEmi const upsert: BaileysEventHandler<"contacts.upsert"> = async (contacts) => { try { - await Promise.any( - contacts - .map((c) => transformPrisma(c)) - .map((data) => - prisma.contact.upsert({ - select: { pkId: true }, - create: { ...data, sessionId }, - update: data, - where: { sessionId_id: { id: data.id, sessionId } }, - }), - ), - ); - } catch (e) { - logger.error(e, "An error occured during contacts upsert"); + console.info(`Received ${contacts.length} contacts for upsert.`); // Informative message + console.info(contacts[0]); // Informative message + + if (contacts.length === 0) { + return; + } + + const transformedContacts = await Promise.all( + contacts.map((contact) => transformPrisma(contact)) + ); + console.log(transformedContacts) + await prisma.contact.createMany({ + data: transformedContacts.map((data) => ({ + ...data, + sessionId, + })), + skipDuplicates: true, // Prevent duplicate inserts + }); + + } catch (error) { + logger.error("An unexpected error occurred during contacts upsert", error); } }; + + const update: BaileysEventHandler<"contacts.update"> = async (updates) => { for (const update of updates) { diff --git a/src/whatsapp.ts b/src/whatsapp.ts index 6c1b413..8f5c6ac 100644 --- a/src/whatsapp.ts +++ b/src/whatsapp.ts @@ -6,7 +6,8 @@ import makeWASocket, { import type { ConnectionState, SocketConfig, WASocket, proto } from "@whiskeysockets/baileys"; import { Store, useSession } from "./store"; import { prisma } from "./db"; -import type { WebSocket } from "ws"; +import type { WebSocket as WebSocketType } from "ws"; +import WebSocket from "ws"; import { logger } from "./shared"; import type { Boom } from "@hapi/boom"; import type { Response } from "express"; @@ -14,11 +15,16 @@ import { toDataURL } from "qrcode"; import { delay } from "./utils"; import dotenv from "dotenv"; +const channel = new WebSocket.Server({port: 3001}) + dotenv.config(); +type WaStatus = 'unknown' | 'wait_for_qrcode_auth' | 'authenticated' | 'pulling_wa_data' | 'connected' | 'disconected'; + type Session = WASocket & { destroy: () => Promise; store: Store; + waStatus?: WaStatus }; const sessions = new Map(); @@ -29,16 +35,23 @@ const RECONNECT_INTERVAL = Number(process.env.RECONNECT_INTERVAL || 0); const MAX_RECONNECT_RETRIES = Number(process.env.MAX_RECONNECT_RETRIES || 5); const SSE_MAX_QR_GENERATION = Number(process.env.SSE_MAX_QR_GENERATION || 5); const SESSION_CONFIG_ID = "session-config"; - export async function init() { const sessions = await prisma.session.findMany({ select: { sessionId: true, data: true }, where: { id: { startsWith: SESSION_CONFIG_ID } }, }); - for (const { sessionId, data } of sessions) { - const { readIncomingMessages, ...socketConfig } = JSON.parse(data); - createSession({ sessionId, readIncomingMessages, socketConfig }); + const { readIncomingMessages, ...socketConfig } = JSON.parse(data); + createSession({ sessionId, readIncomingMessages, socketConfig }); + + } +} + +export function updateWaStatus(sessionId: string, waStatus: WaStatus){ + if(sessions.has(sessionId)){ + const _session = sessions.get(sessionId)! + console.warn(waStatus) + sessions.set(sessionId, {..._session, waStatus}); } } @@ -70,9 +83,9 @@ export async function createSession(options: createSessionOptions) { try { await Promise.all([ logout && socket.logout(), - prisma.chat.deleteMany({ where: { sessionId } }), + // prisma.chat.deleteMany({ where: { sessionId } }), prisma.contact.deleteMany({ where: { sessionId } }), - prisma.message.deleteMany({ where: { sessionId } }), + prisma.message.deleteMany({ where: { sessionId } }), prisma.groupMetadata.deleteMany({ where: { sessionId } }), prisma.session.deleteMany({ where: { sessionId } }), ]); @@ -89,6 +102,8 @@ export async function createSession(options: createSessionOptions) { const restartRequired = code === DisconnectReason.restartRequired; const doNotReconnect = !shouldReconnect(sessionId); + updateWaStatus(sessionId, "disconected"); + if (code === DisconnectReason.loggedOut || doNotReconnect) { if (res) { !SSE && !res.headersSent && res.status(500).json({ error: "Unable to create session" }); @@ -109,6 +124,7 @@ export async function createSession(options: createSessionOptions) { if (res && !res.headersSent) { try { const qr = await toDataURL(connectionState.qr); + updateWaStatus(sessionId, "wait_for_qrcode_auth"); res.status(200).json({ qr }); return; } catch (e) { @@ -124,6 +140,7 @@ export async function createSession(options: createSessionOptions) { let qr: string | undefined = undefined; if (connectionState.qr?.length) { try { + updateWaStatus(sessionId, "wait_for_qrcode_auth") qr = await toDataURL(connectionState.qr); } catch (e) { logger.error(e, "An error occured during QR generation"); @@ -165,18 +182,170 @@ export async function createSession(options: createSessionOptions) { }); const store = new Store(sessionId, socket.ev); - sessions.set(sessionId, { ...socket, destroy, store }); + + sessions.set(sessionId, { ...socket, destroy, store, waStatus: 'unknown' }); socket.ev.on("creds.update", saveCreds); + socket.ev.on("connection.update", (update) => { connectionState = update; const { connection } = update; if (connection === "open") { + updateWaStatus(sessionId, update.isNewLogin ? "authenticated" : "connected") + retries.delete(sessionId); + SSEQRGenerations.delete(sessionId); + } + if (connection === "close") handleConnectionClose(); + handleConnectionUpdate(); + }); + + if (readIncomingMessages) { + socket.ev.on("messages.upsert", async (m) => { + const message = m.messages[0]; + if (message.key.fromMe || m.type !== "notify") return; + + await delay(1000); + await socket.readMessages([message.key]); + }); + } + + + + // Debug events + // socket.ev.on("messaging-history.set", (data) => dump("messaging-history.set", data)); + // socket.ev.on("chats.upsert", (data) => dump("chats.upsert", data)); + // socket.ev.on("contacts.update", (data) => dump("contacts.update", data)); + // socket.ev.on("groups.upsert", (data) => dump("groups.upsert", data)); + + await prisma.session.upsert({ + create: { + id: configID, + sessionId, + data: JSON.stringify({ readIncomingMessages, ...socketConfig }), + }, + update: {}, + where: { sessionId_id: { id: configID, sessionId } }, + }); +} +export async function reloadDataFromSession(options: createSessionOptions) { + const { sessionId, res, SSE = false, readIncomingMessages = false, socketConfig } = options; + const configID = `${SESSION_CONFIG_ID}-${sessionId}`; + let connectionState: Partial = { connection: "close" }; + + const destroy = async (logout = true) => { + try { + await Promise.all([ + logout && socket.logout(), + // prisma.chat.deleteMany({ where: { sessionId } }), + prisma.contact.deleteMany({ where: { sessionId } }), + // prisma.message.deleteMany({ where: { sessionId } }), + prisma.groupMetadata.deleteMany({ where: { sessionId } }), + prisma.session.deleteMany({ where: { sessionId } }), + ]); + logger.info({ session: sessionId }, "Session destroyed"); + } catch (e) { + logger.error(e, "An error occured during session destroy"); + } finally { + sessions.delete(sessionId); + } + }; + + const handleConnectionClose = () => { + const code = (connectionState.lastDisconnect?.error as Boom)?.output?.statusCode; + const restartRequired = code === DisconnectReason.restartRequired; + const doNotReconnect = !shouldReconnect(sessionId); + + if (code === DisconnectReason.loggedOut || doNotReconnect) { + if (res) { + !SSE && !res.headersSent && res.status(500).json({ error: "Unable to create session" }); + res.end(); + } + destroy(doNotReconnect); + return; + } + + if (!restartRequired) { + logger.info({ attempts: retries.get(sessionId) ?? 1, sessionId }, "Reconnecting..."); + } + setTimeout(() => createSession(options), restartRequired ? 0 : RECONNECT_INTERVAL); + }; + + const handleNormalConnectionUpdate = async () => { + if (connectionState.qr?.length) { + if (res && !res.headersSent) { + try { + const qr = await toDataURL(connectionState.qr); + res.status(200).json({ qr }); + return; + } catch (e) { + logger.error(e, "An error occured during QR generation"); + res.status(500).json({ error: "Unable to generate QR" }); + } + } + destroy(); + } + }; + + const handleSSEConnectionUpdate = async () => { + let qr: string | undefined = undefined; + if (connectionState.qr?.length) { + try { + qr = await toDataURL(connectionState.qr); + } catch (e) { + logger.error(e, "An error occured during QR generation"); + } + } + + const currentGenerations = SSEQRGenerations.get(sessionId) ?? 0; + if (!res || res.writableEnded || (qr && currentGenerations >= SSE_MAX_QR_GENERATION)) { + res && !res.writableEnded && res.end(); + destroy(); + return; + } + + const data = { ...connectionState, qr }; + if (qr) SSEQRGenerations.set(sessionId, currentGenerations + 1); + res.write(`data: ${JSON.stringify(data)}\n\n`); + }; + + const handleConnectionUpdate = SSE ? handleSSEConnectionUpdate : handleNormalConnectionUpdate; + const { state, saveCreds } = await useSession(sessionId); + const socket = makeWASocket({ + printQRInTerminal: true, + browser: [process.env.NAME_BOT_BROWSER || "Whatsapp Bot", "Chrome", "3.0"], + generateHighQualityLinkPreview: true, + ...socketConfig, + auth: { + creds: state.creds, + keys: makeCacheableSignalKeyStore(state.keys, logger), + }, + version: [2, 2413, 1], + logger, + shouldIgnoreJid: (jid) => isJidBroadcast(jid), + getMessage: async (key) => { + const data = await prisma.message.findFirst({ + where: { remoteJid: key.remoteJid!, id: key.id!, sessionId }, + }); + return (data?.message || undefined) as proto.IMessage | undefined; + }, + }); + + const store = new Store(sessionId, socket.ev); + sessions.set(sessionId, { ...socket, destroy, store }); + + socket.ev.on("creds.update", saveCreds); + socket.ev.on("connection.update", (update) => { + connectionState = update; + const { connection } = update; + if (connection === "open") { + updateWaStatus(sessionId, update.isNewLogin ? 'authenticated' : 'connected'); + retries.delete(sessionId); SSEQRGenerations.delete(sessionId); } if (connection === "close") handleConnectionClose(); + if (connection === "connecting") updateWaStatus(sessionId, 'pulling_wa_data'); handleConnectionUpdate(); }); @@ -190,6 +359,8 @@ export async function createSession(options: createSessionOptions) { }); } + + // Debug events // socket.ev.on("messaging-history.set", (data) => dump("messaging-history.set", data)); // socket.ev.on("chats.upsert", (data) => dump("chats.upsert", data)); @@ -208,10 +379,10 @@ export async function createSession(options: createSessionOptions) { } export function getSessionStatus(session: Session) { - const state = ["CONNECTING", "CONNECTED", "DISCONNECTING", "DISCONNECTED"]; - let status = state[(session.ws as WebSocket).readyState]; - status = session.user ? "AUTHENTICATED" : status; - return status; + // const state = ["CONNECTING", "CONNECTED", "DISCONNECTING", "DISCONNECTED"]; + // let status = state[(session.ws as WebSocketType).readyState]; + // // status = session.user ? "AUTHENTICATED" : status; + return session.waStatus; } export function listSessions() {