diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml deleted file mode 100644 index c5a3996edb..0000000000 --- a/.github/workflows/publish_docker_image.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build Docker image - -on: - push: - tags: - - "*.*.*" - -jobs: - build_deploy: - name: Build and Deploy - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: evoapicloud/evolution-api - tags: type=semver,pattern=v{{version}} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file diff --git a/.github/workflows/publish_docker_image_homolog.yml b/.github/workflows/publish_docker_image_homolog.yml deleted file mode 100644 index a76e1008be..0000000000 --- a/.github/workflows/publish_docker_image_homolog.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build Docker image - -on: - push: - branches: - - develop - -jobs: - build_deploy: - name: Build and Deploy - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: evoapicloud/evolution-api - tags: homolog - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/publish_docker_image_latest.yml b/.github/workflows/publish_docker_image_latest.yml deleted file mode 100644 index f73fe80e4e..0000000000 --- a/.github/workflows/publish_docker_image_latest.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build Docker image - -on: - push: - branches: - - main - -jobs: - build_deploy: - name: Build and Deploy - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: evoapicloud/evolution-api - tags: latest - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/publish_ghcr.yml b/.github/workflows/publish_ghcr.yml new file mode 100644 index 0000000000..5702b039ee --- /dev/null +++ b/.github/workflows/publish_ghcr.yml @@ -0,0 +1,68 @@ +name: Build and Publish to GHCR + +on: + push: + branches: + - main + - 'release/**' + tags: + - 'v*.*.*' + pull_request: + branches: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + name: Build and Publish + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix= + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # Disable cache to ensure fresh build (cache was returning stale compiled JS) + no-cache: true + + - name: Image digest + if: github.event_name != 'pull_request' + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/Dockerfile b/Dockerfile index 24c4e3bc7f..f2d638989c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,16 @@ FROM node:24-alpine AS builder RUN apk update && \ - apk add --no-cache git ffmpeg wget curl bash openssl + apk add --no-cache git ffmpeg wget curl bash openssl \ + chromium nss freetype harfbuzz ttf-freefont -LABEL version="2.3.1" description="Api to control whatsapp features through http requests." -LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" -LABEL contact="contato@evolution-api.com" +LABEL version="2.3.7-catalog-browser" description="Api to control whatsapp features through http requests. Adds browser-based catalog fetch provider." +LABEL maintainer="Kelvin Yuli Andrian" git="https://github.com/kelvinzer0/evolution-api" +LABEL contact="kelvinzer0@users.noreply.github.com" + +# Tell Puppeteer to use system Chromium instead of downloading its own +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser WORKDIR /evolution @@ -33,11 +38,22 @@ RUN npm run build FROM node:24-alpine AS final RUN apk update && \ - apk add tzdata ffmpeg bash openssl + apk add --no-cache tzdata ffmpeg bash openssl \ + chromium nss freetype harfbuzz ttf-freefont font-noto-emoji ENV TZ=America/Sao_Paulo ENV DOCKER_ENV=true +# Puppeteer config — use system Chromium +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser + +# Catalog Browser Service config (overridable at runtime) +ENV CATALOG_BROWSER_ENABLED=false +ENV CATALOG_BROWSER_IDLE_TIMEOUT_MS=600000 +ENV CATALOG_BROWSER_MAX_SESSIONS=5 +ENV CATALOG_BROWSER_HEADLESS=true + WORKDIR /evolution COPY --from=builder /evolution/package.json ./package.json diff --git a/docs/catalog-browser-provider.md b/docs/catalog-browser-provider.md new file mode 100644 index 0000000000..85f17c7097 --- /dev/null +++ b/docs/catalog-browser-provider.md @@ -0,0 +1,364 @@ +# Browser-Based Catalog Provider + +> Fetch full WhatsApp Business catalog & collections via web.whatsapp.com +> browser automation, bypassing Baileys' protocol-level truncation. + +--- + +## Why This Exists + +WhatsApp's anti-bot/anti-scraping on the **protocol level** (Baileys) is +much stricter than on the **browser level** (web.whatsapp.com). As a +result, Baileys' `getCatalog()` may return truncated results even when +the catalog has more products. + +The browser-based provider launches a Puppeteer-controlled Chromium +session, navigates to web.whatsapp.com, and uses WhatsApp Web's own +internal `window.WPP.whatsapp.functions.queryCatalog` API — the same +code path WhatsApp's frontend uses. This returns the full catalog +without protocol-level truncation. + +**Reference implementation**: ported from +[kelvinzer0/bedones-whatsapp](https://github.com/kelvinzer0/bedones-whatsapp) +which uses `whatsapp-web.js` + this same 4-layer fetch approach. + +--- + +## How to Enable + +### 1. Set environment variables + +In your `docker-compose.yml` or env file: + +```yaml +environment: + CATALOG_BROWSER_ENABLED: 'true' + # Optional tunables (with defaults): + CATALOG_BROWSER_IDLE_TIMEOUT_MS: '600000' # 10 minutes + CATALOG_BROWSER_MAX_SESSIONS: '5' + CATALOG_BROWSER_HEADLESS: 'true' # true | false | shell + # PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium-browser # auto-set in Docker +``` + +### 2. Rebuild Docker image + +The Dockerfile already installs Chromium + required fonts. Just rebuild: + +```bash +docker compose build evolution-api +docker compose up -d evolution-api +``` + +**Image size increase**: ~200 MB (Chromium + fonts). + +### 3. RAM requirements + +Each active browser session uses ~500 MB RAM. The browser is killed +after `CATALOG_BROWSER_IDLE_TIMEOUT_MS` of inactivity, freeing memory. + +**Recommendation**: ensure your Docker host has at least 1 GB free RAM +beyond existing workloads before enabling. For hosts with limited RAM +(< 4 GB total), set `CATALOG_BROWSER_MAX_SESSIONS=1` and a shorter idle +timeout (e.g. `CATALOG_BROWSER_IDLE_TIMEOUT_MS=120000` for 2 minutes). + +--- + +## How to Use + +### Fetch catalog via browser + +```bash +curl -X POST "https://your-evolution-host/business/getCatalog/{instanceName}" \ + -H "Content-Type: application/json" \ + -H "apikey: YOUR_API_KEY" \ + -d '{ + "provider": "browser" + }' +``` + +### Fetch collections via browser + +```bash +curl -X POST "https://your-evolution-host/business/getCollections/{instanceName}" \ + -H "Content-Type: application/json" \ + -H "apikey: YOUR_API_KEY" \ + -d '{ + "provider": "browser" + }' +``` + +### Backward compatible + +If you don't pass `provider`, the existing Baileys path is used — no +behavior change for existing clients (n8n, Odoo, etc.). + +--- + +## First-Time Setup: QR Scan + +The first time you call the browser provider for a given instance, the +response will include a `qrCode` field (a `data:image/png;base64,...` URL) +because the browser session isn't authenticated yet. + +**Response shape (pending auth)**: +```json +{ + "wuid": "1234567890@s.whatsapp.net", + "numberExists": true, + "isBusiness": true, + "catalogLength": 0, + "catalog": [], + "truncated": false, + "nextCursor": null, + "source": "browser", + "qrCode": "data:image/png;base64,..." +} +``` + +**To authenticate**: +1. Render the `qrCode` data URL as an image (any browser, or `qrencode -t ANSIUTF8`) +2. On your phone, open WhatsApp → Settings → Linked Devices → Link a Device +3. Scan the QR code +4. Wait 5–10 seconds for WA Web to log in +5. Call the endpoint again — you'll get the full catalog + +The session is persisted at `instances/{instanceName}/browser-session/` +and survives browser restarts, container restarts, and host reboots (as +long as the volume `evolution-instances` is preserved). + +**You only need to scan the QR code ONCE per WhatsApp account.** The +browser session is independent from the Baileys session — you'll have +two linked devices on your phone (one for Baileys messaging, one for +browser catalog fetch). + +--- + +## How It Works (Internal) + +### Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Evolution API │ +│ │ +│ ┌──────────────────┐ ┌──────────────────────────┐ │ +│ │ BaileysStartup │ │ BrowserCatalogService │ │ +│ │ Service │───►│ (singleton, lazy-launch) │ │ +│ │ │ │ │ │ +│ │ fetchCatalog() │ │ - Puppeteer Chromium │ │ +│ │ if provider= │ │ - One Browser per JID │ │ +│ │ 'browser' │ │ - Idle kill (10 min) │ │ +│ │ → delegate │ │ - Persistent session │ │ +│ └──────────────────┘ └──────────────────────────┘ │ +│ │ +│ Session files: instances/{name}/browser-session/ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 4-Layer Catalog Fetch + +Inside the browser page context, the service tries 4 strategies in +order, deduplicating products by ID: + +1. **`WPP.whatsapp.functions.queryCatalog(userId, afterToken)`** — paginated + cursor loop (most reliable, primary path) +2. **`WPP.whatsapp.CatalogStore.findQuery(userId)`** — direct store access +3. **`WPP.catalog.getMyCatalog()`** — high-level wrapper +4. **`WPP.catalog.getProducts(userId, 999)`** — last-resort hardcoded cap + +### Collections Fetch + +Uses `WPP.catalog.getCollections(userId)` with +`CollectionStore.findQuery` fallback. + +> **⚠️ Known limitation**: wa-js `getCollections` returns **metadata only** +> (`totalItemsCount: 0` for all collections) — it does NOT include the +> products inside each collection. The wa-js wrapper doesn't expose the +> nested `` elements that WhatsApp's protocol returns. +> +> **Solution**: When `provider: "browser"` is used, `fetchCollections` +> runs in **hybrid mode** — see [Hybrid Mode](#hybrid-mode) below. + +### Hybrid Mode + +When you call `getCollections` with `provider: "browser"`, the endpoint +runs **both** paths in parallel and merges the results: + +| Path | Mechanism | Returns | +|---|---|---| +| **Browser (wa-js)** | Puppeteer + `WPP.catalog.getCollections(userId)` | Collection metadata only (id, name, status, totalItemsCount) | +| **Baileys (protocol)** | IQ stanza `` with `collection_limit` and `item_limit` | Collections + nested products per collection | + +The Baileys path uses the **same open WhatsApp socket** that already +powers the instance's messaging — no new connection needed. It sends +the protocol-level IQ stanza directly: + +```xml + + + {limit} + {limit} + 100 + 100 + + +``` + +WhatsApp responds with each `` containing nested `` +elements, which Baileys parses via `parseCollectionsNode()`: + +```json +{ + "collections": [ + { + "id": "...", + "name": "Makanan", + "products": [ + { "id": "prod_xxx", "name": "Nasi Goreng", "price": 25000, "currency": "IDR", ... }, + ... + ], + "status": { "status": "APPROVED", "canAppeal": false } + } + ] +} +``` + +#### Hybrid Response Shape + +```json +{ + "wuid": "6285733556953@s.whatsapp.net", + "name": null, + "numberExists": true, + "isBusiness": true, + "collectionsLength": 9, + "collections": [ + { + "id": "...", + "name": "Makanan", + "products": [...], + "productsLength": 12, + "totalItemsCount": 12 + } + ], + "source": "hybrid", + "baileysCollectionsCount": 9, + "baileysProductsCount": 95, + "baileysOk": true +} +``` + +#### Why Hybrid Works + +- **`getCatalog` via Baileys fails** (anti-bot truncates result, returns + `isBusiness: false` and 0 products) +- **`getCollections` via Baileys succeeds** (different IQ stanza — + WhatsApp server treats collection metadata queries more leniently) +- **`getCatalog` via browser succeeds** (wa-js lazy-loads all pages via + `findNextProductPage()`, returns full 143-product catalog) +- **`getCollections` via browser returns metadata only** (wa-js wrapper + limitation — no products in response) + +Hybrid = browser catalog (full products) + Baileys collections (with +product mapping). Best of both worlds. + +#### Fallback Behavior + +If Baileys fails (e.g. anti-bot kicks in, or instance not yet business-verified), +the response still succeeds but with: +- `baileysOk: false` +- `collections[].products: []` (empty arrays) +- `baileysProductsCount: 0` + +Callers should check `baileysOk` and fall back to keyword matching or +manual categorization if `false`. + +### Resource Management + +- One `Browser` instance per JID, lazy-started on first request +- Browser is killed after `CATALOG_BROWSER_IDLE_TIMEOUT_MS` (default 10 min) +- Max concurrent sessions capped at `CATALOG_BROWSER_MAX_SESSIONS` (default 5) +- When limit reached, oldest idle browser is evicted +- All browsers killed on app shutdown + +--- + +## Files Changed + +| File | Purpose | +|---|---| +| `src/api/integrations/channel/whatsapp/catalog-browser.service.ts` | Core service — 4-layer fetch, browser pool, idle timeout | +| `src/api/integrations/channel/whatsapp/catalog-browser.types.ts` | Type definitions | +| `src/api/integrations/channel/whatsapp/catalog-browser.module.ts` | NestJS module wiring | +| `src/api/integrations/channel/whatsapp/session-store.browser.ts` | Persistent session storage | +| `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts` | Routing: `provider=browser` delegates to BrowserCatalogService | +| `src/api/dto/business.dto.ts` | Added `provider?: 'baileys' \| 'browser'` field | +| `src/validate/business.schema.ts` | JSON Schema for the new field | +| `src/api/server.module.ts` | Bootstrap singleton instance of BrowserCatalogService | +| `Dockerfile` | Install Chromium + fonts, set PUPPETEER env vars | +| `package.json` | Added `puppeteer-core@^23.11.1` dependency | +| `env.example` | Documented new env vars | + +--- + +## Troubleshooting + +### `Browser catalog service is disabled` + +Set `CATALOG_BROWSER_ENABLED=true` and restart the container. + +### `Browser catalog service is not initialized` + +The service constructor failed. Check logs for the initialization +error. Likely cause: missing Chromium binary — verify +`PUPPETEER_EXECUTABLE_PATH` points to a real Chromium binary +(`ls -la /usr/bin/chromium-browser` inside the container). + +### QR code never appears + +Make sure `web.whatsapp.com` is reachable from inside the container. +Try `curl -I https://web.whatsapp.com/` from inside the container. + +### Browser OOM kills + +Check container memory usage: +```bash +docker stats evolution_api +``` + +If the container hits the host memory limit, either: +- Increase host RAM +- Reduce `CATALOG_BROWSER_MAX_SESSIONS` to 1 +- Reduce `CATALOG_BROWSER_IDLE_TIMEOUT_MS` to 60000 (1 minute) + +### Session expired + +If WA Web session expires (rare, happens after ~30 days inactive), +delete the session dir and scan QR again: + +```bash +docker exec evolution_api rm -rf /evolution/instances/{instanceName}/browser-session +``` + +Then call the endpoint again to get a fresh QR code. + +--- + +## Limitations + +- **First call is slow**: Browser launch + WA Web load = 10–30 seconds. + Subsequent calls within the idle window are fast (~2 seconds). +- **QR scan required once per WhatsApp account**: Browser session is + independent from Baileys session. You'll see two linked devices on + your phone. +- **Memory heavy**: Each active browser = ~500 MB RAM. Plan capacity + accordingly. +- **Compliance**: Browser automation is against WhatsApp ToS in + principle, but enforcement is rare for legitimate catalog reads. + Use at your own risk. + +--- + +*Added in evolution-api v2.3.7-catalog-browser* +*Hybrid collections (Baileys + browser) added in v2.3.8* +*Branch: `feature/catalog-browser-provider`* diff --git a/env.example b/env.example index 5fe448b82c..bb3486aafe 100644 --- a/env.example +++ b/env.example @@ -300,3 +300,16 @@ PROVIDER_ENABLED=false PROVIDER_HOST= PROVIDER_PORT=5656 PROVIDER_PREFIX=evolution + +# === Catalog Browser Service (NEW) === +# Browser-based catalog fetch via web.whatsapp.com (bypasses Baileys protocol limits) +# Set to true to enable. Adds ~500MB RAM per active browser session. +CATALOG_BROWSER_ENABLED=false +# Idle timeout before killing idle browser (ms, default 10 minutes) +CATALOG_BROWSER_IDLE_TIMEOUT_MS=600000 +# Max concurrent browser sessions (default 5) +CATALOG_BROWSER_MAX_SESSIONS=5 +# Headless mode: true (default) | false | shell +CATALOG_BROWSER_HEADLESS=true +# Override Chromium executable path (auto-detected in Docker image) +# PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000000..5fc8561dc2 --- /dev/null +++ b/openapi.json @@ -0,0 +1,150 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Evolution API", + "version": "2.3.7", + "description": "WhatsApp API - OpenAPI Documentation", + "contact": { + "name": "Evolution API", + "url": "https://github.com/EvolutionAPI/evolution-api" + } + }, + "servers": [ + { + "url": "/v1", + "description": "API v1" + } + ], + "components": { + "securitySchemes": { + "apikey": { + "type": "apiKey", + "name": "apikey", + "in": "header", + "description": "API key for authentication" + } + } + }, + "security": [ + { + "apikey": [] + } + ], + "paths": { + "/business/getCatalog/{instanceName}": { + "post": { + "tags": [ + "Business" + ], + "summary": "Get WhatsApp Business catalog", + "description": "Fetches all products from a WhatsApp Business catalog with automatic pagination", + "security": [ + { + "apikey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "instanceName", + "required": true, + "schema": { + "type": "string" + }, + "description": "Instance name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CatalogRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Catalog retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CatalogResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/business/getCollections/{instanceName}": { + "post": { + "tags": [ + "Business" + ], + "summary": "Get WhatsApp Business collections", + "description": "Fetches all collections with their products from a WhatsApp Business account", + "security": [ + { + "apikey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "instanceName", + "required": true, + "schema": { + "type": "string" + }, + "description": "Instance name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Collections retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionsResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c45e8fef38..09733e6ea8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,11 @@ "@prisma/client": "^6.16.2", "@sentry/node": "^10.12.0", "@types/uuid": "^10.0.0", + "@wppconnect/wa-js": "^3.22.1", "amqplib": "^0.10.5", "audio-decode": "^2.2.3", "axios": "^1.7.9", - "baileys": "7.0.0-rc.9", + "baileys": "^7.0.0-rc13", "class-validator": "^0.14.1", "compression": "^1.7.5", "cors": "^2.8.5", @@ -68,7 +69,8 @@ "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5", "undici": "^7.16.0", - "uuid": "^13.0.0" + "uuid": "^13.0.0", + "whatsapp-web.js": "^1.34.7" }, "devDependencies": { "@commitlint/cli": "^19.8.1", @@ -95,6 +97,7 @@ "husky": "^9.1.7", "lint-staged": "^16.1.6", "prettier": "^3.4.2", + "swagger-jsdoc": "^6.3.0", "tsconfig-paths": "^4.2.0", "tsx": "^4.20.5", "typescript": "^5.7.2" @@ -106,6 +109,58 @@ "integrity": "sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw==", "license": "MIT" }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", + "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz", + "integrity": "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "14.0.1", + "@apidevtools/openapi-schemas": "^2.1.0", + "@apidevtools/swagger-methods": "^3.0.2", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.2" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@apm-js-collab/code-transformer": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", @@ -798,7 +853,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -813,7 +867,6 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -829,9 +882,9 @@ } }, "node_modules/@borewit/text-codec": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.0.tgz", - "integrity": "sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", "license": "MIT", "funding": { "type": "github", @@ -2371,18 +2424,28 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@jimp/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", - "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.1.tgz", + "integrity": "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==", "license": "MIT", "dependencies": { - "@jimp/file-ops": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/file-ops": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", - "file-type": "^16.0.0", + "file-type": "^21.3.3", "mime": "3" }, "engines": { @@ -2402,14 +2465,14 @@ } }, "node_modules/@jimp/diff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", - "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.1.tgz", + "integrity": "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==", "license": "MIT", "dependencies": { - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "pixelmatch": "^5.3.0" }, "engines": { @@ -2417,23 +2480,23 @@ } }, "node_modules/@jimp/file-ops": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", - "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.1.tgz", + "integrity": "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@jimp/js-bmp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", - "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.1.tgz", + "integrity": "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "bmp-ts": "^1.0.9" }, "engines": { @@ -2441,13 +2504,13 @@ } }, "node_modules/@jimp/js-gif": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", - "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.1.tgz", + "integrity": "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "gifwrap": "^0.10.1", "omggif": "^1.0.10" }, @@ -2456,13 +2519,13 @@ } }, "node_modules/@jimp/js-jpeg": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", - "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.1.tgz", + "integrity": "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "jpeg-js": "^0.4.4" }, "engines": { @@ -2470,13 +2533,13 @@ } }, "node_modules/@jimp/js-png": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", - "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.1.tgz", + "integrity": "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "pngjs": "^7.0.0" }, "engines": { @@ -2484,13 +2547,13 @@ } }, "node_modules/@jimp/js-tiff": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", - "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.1.tgz", + "integrity": "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "utif2": "^4.1.0" }, "engines": { @@ -2498,13 +2561,13 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", - "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.1.tgz", + "integrity": "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2512,25 +2575,25 @@ } }, "node_modules/@jimp/plugin-blur": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", - "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.1.tgz", + "integrity": "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/utils": "1.6.0" + "@jimp/core": "1.6.1", + "@jimp/utils": "1.6.1" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-circle": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", - "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.1.tgz", + "integrity": "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2538,14 +2601,14 @@ } }, "node_modules/@jimp/plugin-color": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", - "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.1.tgz", + "integrity": "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "tinycolor2": "^1.6.0", "zod": "^3.23.8" }, @@ -2554,16 +2617,16 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", - "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.1.tgz", + "integrity": "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2571,15 +2634,15 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", - "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.1.tgz", + "integrity": "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2587,14 +2650,14 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", - "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.1.tgz", + "integrity": "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2602,13 +2665,13 @@ } }, "node_modules/@jimp/plugin-displace": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", - "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.1.tgz", + "integrity": "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2616,25 +2679,25 @@ } }, "node_modules/@jimp/plugin-dither": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", - "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.1.tgz", + "integrity": "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0" + "@jimp/types": "1.6.1" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", - "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.1.tgz", + "integrity": "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2642,12 +2705,12 @@ } }, "node_modules/@jimp/plugin-flip": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", - "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.1.tgz", + "integrity": "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2655,20 +2718,20 @@ } }, "node_modules/@jimp/plugin-hash": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", - "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.1.tgz", + "integrity": "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "any-base": "^1.1.0" }, "engines": { @@ -2676,12 +2739,12 @@ } }, "node_modules/@jimp/plugin-mask": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", - "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.1.tgz", + "integrity": "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2689,16 +2752,16 @@ } }, "node_modules/@jimp/plugin-print": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", - "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.1.tgz", + "integrity": "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/types": "1.6.1", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", @@ -2710,9 +2773,9 @@ } }, "node_modules/@jimp/plugin-quantize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", - "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.1.tgz", + "integrity": "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==", "license": "MIT", "dependencies": { "image-q": "^4.0.0", @@ -2723,13 +2786,13 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", - "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.1.tgz", + "integrity": "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/types": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/types": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2737,16 +2800,16 @@ } }, "node_modules/@jimp/plugin-rotate": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", - "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.1.tgz", + "integrity": "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2754,16 +2817,16 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", - "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.1.tgz", + "integrity": "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==", "license": "MIT", "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0", + "@jimp/core": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1", "zod": "^3.23.8" }, "engines": { @@ -2771,9 +2834,9 @@ } }, "node_modules/@jimp/types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", - "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.1.tgz", + "integrity": "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==", "license": "MIT", "dependencies": { "zod": "^3.23.8" @@ -2783,12 +2846,12 @@ } }, "node_modules/@jimp/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==", "license": "MIT", "dependencies": { - "@jimp/types": "1.6.0", + "@jimp/types": "1.6.1", "tinycolor2": "^1.6.0" }, "engines": { @@ -3429,6 +3492,16 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -3578,25 +3651,24 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { @@ -3605,12 +3677,6 @@ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -3624,9 +3690,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, "node_modules/@redis/bloom": { @@ -4758,40 +4824,18 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@tokenizer/inflate/node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/inflate/node_modules/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -4895,12 +4939,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-4.0.0.tgz", @@ -5072,6 +5110,16 @@ "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", @@ -5365,6 +5413,15 @@ "url": "https://github.com/sponsors/eshaz" } }, + "node_modules/@wppconnect/wa-js": { + "version": "3.23.4", + "resolved": "https://registry.npmjs.org/@wppconnect/wa-js/-/wa-js-3.23.4.tgz", + "integrity": "sha512-YAVMxz7n3DXqMq469OQSEKW5Nfjb9/ai1vcrlIweJb0RUgkfiTYJT9os+bcZ6QP8lmlNX8wLBJM/04hgtxZJEA==", + "license": "Apache-2.0", + "engines": { + "whatsapp-web": ">=2.2326.10-beta" + } + }, "node_modules/@zxing/text-encoding": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", @@ -5475,6 +5532,21 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/amqplib": { "version": "0.10.9", "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz", @@ -5556,109 +5628,389 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "license": "MIT", + "optional": true, "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "license": "MIT", + "optional": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", + "node_modules/archiver-utils/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "optional": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, + "node_modules/archiver-utils/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, + "optional": true, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", + "node_modules/archiver-utils/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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT", + "optional": true + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "optional": true + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "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/archiver-utils/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/archiver-utils/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT", + "optional": true + }, + "node_modules/archiver/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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, @@ -5698,6 +6050,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", @@ -5813,70 +6177,166 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/baileys": { - "version": "7.0.0-rc.9", - "resolved": "https://registry.npmjs.org/baileys/-/baileys-7.0.0-rc.9.tgz", - "integrity": "sha512-Txd2dZ9MHbojvsHckeuCnAKPO/bQjKxua/0tQSJwOKXffK5vpS82k4eA/Nb46K0cK0Bx+fyY0zhnQHYMBriQcw==", + "version": "7.0.0-rc13", + "resolved": "https://registry.npmjs.org/baileys/-/baileys-7.0.0-rc13.tgz", + "integrity": "sha512-v8k74K8B5R7WNYGa26MyJAYEu3Wc4BSuK01QaK8lr30lhE8Nga31nWNu8KN0NDDt+Fsvkq4SQFFI8Q13ghjKmA==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@cacheable/node-cache": "^1.4.0", "@hapi/boom": "^9.1.3", "async-mutex": "^0.5.0", - "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git", + "libsignal": "^6.0.0", "lru-cache": "^11.1.0", - "music-metadata": "^11.7.0", + "music-metadata": "^11.12.3", "p-queue": "^9.0.0", "pino": "^9.6", - "protobufjs": "^7.2.4", + "protobufjs": "^7.5.6", + "whatsapp-rust-bridge": "0.5.4", "ws": "^8.13.0" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { - "audio-decode": "^2.1.3", - "jimp": "^1.6.0", - "link-preview-js": "^3.0.0", - "sharp": "*" + "audio-decode": "^2.1.3", + "jimp": "^1.6.1", + "link-preview-js": "^3.0.0", + "sharp": "*" + }, + "peerDependenciesMeta": { + "audio-decode": { + "optional": true + }, + "jimp": { + "optional": true + }, + "link-preview-js": { + "optional": true + } + } + }, + "node_modules/baileys/node_modules/@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "9.x.x" + } + }, + "node_modules/baileys/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" }, "peerDependenciesMeta": { - "audio-decode": { + "bare-abort-controller": { "optional": true }, - "jimp": { + "bare-buffer": { "optional": true }, - "link-preview-js": { + "bare-events": { "optional": true } } }, - "node_modules/baileys/node_modules/@hapi/boom": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", - "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", - "license": "BSD-3-Clause", + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "license": "Apache-2.0", "dependencies": { - "@hapi/hoek": "9.x.x" + "bare-path": "^3.0.0" } }, - "node_modules/baileys/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, "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==", + "devOptional": true, "funding": [ { "type": "github", @@ -5902,6 +6362,15 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -5923,6 +6392,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT", + "optional": true + }, "node_modules/bmp-ts": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", @@ -6202,11 +6678,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6424,7 +6906,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -6557,6 +7038,65 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "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/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -6727,6 +7267,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT", + "optional": true + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -6744,7 +7291,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -6785,11 +7331,80 @@ "typescript": ">=5" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "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/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6906,6 +7521,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -7088,6 +7712,20 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7264,6 +7902,63 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "optional": true + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "optional": true + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT", + "optional": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -7313,6 +8008,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", @@ -7456,7 +8160,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7479,7 +8182,6 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -7699,6 +8401,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -8260,6 +8993,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -8290,7 +9036,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -8300,7 +9045,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -8341,10 +9085,20 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", @@ -8454,6 +9208,26 @@ "node": ">=4" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", @@ -8490,6 +9264,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -8579,6 +9359,15 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fetch-socks": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fetch-socks/-/fetch-socks-1.3.2.tgz", @@ -8629,17 +9418,18 @@ } }, "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -8856,6 +9646,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -9068,6 +9888,21 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -9099,6 +9934,20 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/gifwrap": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", @@ -9320,7 +10169,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/graphemer": { @@ -9486,6 +10335,19 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -9608,7 +10470,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -9625,7 +10486,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9866,7 +10726,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-async-function": { @@ -10198,6 +11057,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -10350,39 +11222,55 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jimp": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", - "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", - "license": "MIT", - "dependencies": { - "@jimp/core": "1.6.0", - "@jimp/diff": "1.6.0", - "@jimp/js-bmp": "1.6.0", - "@jimp/js-gif": "1.6.0", - "@jimp/js-jpeg": "1.6.0", - "@jimp/js-png": "1.6.0", - "@jimp/js-tiff": "1.6.0", - "@jimp/plugin-blit": "1.6.0", - "@jimp/plugin-blur": "1.6.0", - "@jimp/plugin-circle": "1.6.0", - "@jimp/plugin-color": "1.6.0", - "@jimp/plugin-contain": "1.6.0", - "@jimp/plugin-cover": "1.6.0", - "@jimp/plugin-crop": "1.6.0", - "@jimp/plugin-displace": "1.6.0", - "@jimp/plugin-dither": "1.6.0", - "@jimp/plugin-fisheye": "1.6.0", - "@jimp/plugin-flip": "1.6.0", - "@jimp/plugin-hash": "1.6.0", - "@jimp/plugin-mask": "1.6.0", - "@jimp/plugin-print": "1.6.0", - "@jimp/plugin-quantize": "1.6.0", - "@jimp/plugin-resize": "1.6.0", - "@jimp/plugin-rotate": "1.6.0", - "@jimp/plugin-threshold": "1.6.0", - "@jimp/types": "1.6.0", - "@jimp/utils": "1.6.0" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.1.tgz", + "integrity": "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.1", + "@jimp/diff": "1.6.1", + "@jimp/js-bmp": "1.6.1", + "@jimp/js-gif": "1.6.1", + "@jimp/js-jpeg": "1.6.1", + "@jimp/js-png": "1.6.1", + "@jimp/js-tiff": "1.6.1", + "@jimp/plugin-blit": "1.6.1", + "@jimp/plugin-blur": "1.6.1", + "@jimp/plugin-circle": "1.6.1", + "@jimp/plugin-color": "1.6.1", + "@jimp/plugin-contain": "1.6.1", + "@jimp/plugin-cover": "1.6.1", + "@jimp/plugin-crop": "1.6.1", + "@jimp/plugin-displace": "1.6.1", + "@jimp/plugin-dither": "1.6.1", + "@jimp/plugin-fisheye": "1.6.1", + "@jimp/plugin-flip": "1.6.1", + "@jimp/plugin-hash": "1.6.1", + "@jimp/plugin-mask": "1.6.1", + "@jimp/plugin-print": "1.6.1", + "@jimp/plugin-quantize": "1.6.1", + "@jimp/plugin-resize": "1.6.1", + "@jimp/plugin-rotate": "1.6.1", + "@jimp/plugin-threshold": "1.6.1", + "@jimp/types": "1.6.1", + "@jimp/utils": "1.6.1" }, "engines": { "node": ">=18" @@ -10416,14 +11304,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10443,7 +11329,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema": { @@ -10483,7 +11368,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -10589,6 +11474,59 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "optional": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "optional": true + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "optional": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10610,51 +11548,13 @@ "license": "MIT" }, "node_modules/libsignal": { - "name": "@whiskeysockets/libsignal-node", - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/libsignal/-/libsignal-6.0.0.tgz", + "integrity": "sha512-d/5V3YFtDljbFMufz4ncyUYGYhJl+vzAe+c2EFFBQ6bz1h8Q3IOMEGXYMzlibU60I+e8GagMMpji18iez3P1hA==", "license": "GPL-3.0", "dependencies": { "curve25519-js": "^0.0.4", - "protobufjs": "6.8.8" - } - }, - "node_modules/libsignal/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "license": "MIT" - }, - "node_modules/libsignal/node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "license": "Apache-2.0" - }, - "node_modules/libsignal/node_modules/protobufjs": { - "version": "6.8.8", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", - "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "protobufjs": "^7.5.5" } }, "node_modules/lilconfig": { @@ -11232,12 +12132,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-2.0.0.tgz", + "integrity": "sha512-kOy3OxT2HH39N70UnKgu4NWDZjLOz8W/mfyvniHjRH/DrL3f2pOfvWQ4p60offbbtDAnXWp0v9LfMIqMec269Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mediainfo.js": { @@ -11571,6 +12475,22 @@ ], "license": "MIT" }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "devOptional": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -11656,9 +12576,9 @@ } }, "node_modules/music-metadata": { - "version": "11.10.3", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.10.3.tgz", - "integrity": "sha512-j0g/x4cNNZW6I5gdcPAY+GFkJY9WHTpkFDMBJKQLxJQyvSfQbXm57fTE3haGFFuOzCgtsTd4Plwc49Sn9RacDQ==", + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.13.0.tgz", + "integrity": "sha512-uXRaov9dfjSpQufXIU7sMxVZnh+FilCQv2mXn+K5EJ/decP3dTWrgvPYa5r6MtRbieNSCE708Da4J0u1UGfQIw==", "funding": [ { "type": "github", @@ -11671,80 +12591,32 @@ ], "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.2.0", + "@borewit/text-codec": "^0.2.2", "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "file-type": "^21.1.1", - "media-typer": "^1.1.0", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.5.0" + "file-type": "^21.3.4", + "media-typer": "^2.0.0", + "strtok3": "^10.3.5", + "token-types": "^6.1.2", + "uint8array-extras": "^1.5.0", + "win-guid": "^0.2.1" }, "engines": { "node": ">=18" } }, - "node_modules/music-metadata/node_modules/file-type": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", - "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", - "license": "MIT", - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/music-metadata/node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "node_modules/music-metadata/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, "engines": { "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata/node_modules/token-types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", - "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", - "license": "MIT", - "dependencies": { - "@borewit/text-codec": "^0.1.0", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/music-metadata/node_modules/token-types/node_modules/@borewit/text-codec": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", - "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mute-stream": { @@ -11806,6 +12678,15 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/nkeys.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nkeys.js/-/nkeys.js-1.1.0.tgz", @@ -11897,6 +12778,13 @@ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", "license": "MIT" }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT", + "optional": true + }, "node_modules/node-wav": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/node-wav/-/node-wav-0.0.2.tgz", @@ -11906,6 +12794,22 @@ "node": ">=4.4.0" } }, + "node_modules/node-webpmux": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-webpmux/-/node-webpmux-3.2.1.tgz", + "integrity": "sha512-MKgpq9nFgo44pIVNx/umD3nkqb2E8oqQTfmstVsfNdx9uV4cX7a4LqA+d8AZd3v5tgJXwENKUFsXNP3bRLP8nQ==", + "license": "LGPL-3.0-or-later" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -12104,7 +13008,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -12171,6 +13074,14 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -12399,6 +13310,45 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "devOptional": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -12409,7 +13359,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -12457,7 +13406,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -12552,7 +13500,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12564,6 +13512,23 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -12576,18 +13541,11 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" }, "node_modules/perfect-debounce": { "version": "1.0.0", @@ -12962,10 +13920,18 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT", + "optional": true + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -12982,25 +13948,33 @@ ], "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -13028,18 +14002,135 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", "license": "MIT" }, + "node_modules/puppeteer": { + "version": "24.38.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.38.0.tgz", + "integrity": "sha512-abnJOBVoL9PQTLKSbYGm9mjNFyIPaTVj77J/6cS370dIQtcZMpx8wyZoAuBzR71Aoon6yvI71NEVFUsl3JU82g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.0", + "chromium-bidi": "14.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1581282", + "puppeteer-core": "24.38.0", + "typed-query-selector": "^2.12.1" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer/node_modules/@puppeteer/browsers": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", + "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer/node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1581282", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz", + "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==", + "license": "BSD-3-Clause" + }, + "node_modules/puppeteer/node_modules/puppeteer-core": { + "version": "24.38.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.38.0.tgz", + "integrity": "sha512-zB3S/tksIhgi2gZRndUe07AudBz5SXOB7hqG0kEa9/YXWrGwlVlYm3tZtwKgfRftBzbmLQl5iwHkQQl04n/mWw==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.0", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1581282", + "typed-query-selector": "^2.12.1", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -13411,60 +14502,27 @@ "node": ">= 6" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", - "license": "MIT", - "dependencies": { - "readable-stream": "^4.7.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/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" - } - ], - "license": "MIT", + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "minimatch": "^5.1.0" } }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "optional": true, "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" } }, "node_modules/readdirp": { @@ -13877,9 +14935,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14071,7 +15129,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -14084,7 +15142,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14176,9 +15234,9 @@ "license": "ISC" }, "node_modules/simple-xml-to-json": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", - "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", "license": "MIT", "engines": { "node": ">=20.12.2" @@ -14476,6 +15534,17 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -14518,6 +15587,39 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -14604,6 +15706,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -14640,16 +15756,15 @@ "license": "MIT" }, "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", "license": "MIT", "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "@tokenizer/token": "^0.3.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "type": "github", @@ -14687,29 +15802,134 @@ "node": ">= 6" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.3.0.tgz", + "integrity": "sha512-I+iQjVGV3t28pOkQUJv2MncthvOtkEactOn8R76SvSYhxgtIn7FoqfDHwQaN+GBnQdXQLrhgDXseKitmJcHMsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "^12.1.0", + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "11.1.0", + "lodash.mergewith": "^4.6.2", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/swagger-jsdoc/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "has-flag": "^3.0.0" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=4" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", + "node_modules/swagger-jsdoc/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, "engines": { - "node": ">= 0.4" + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" } }, "node_modules/swagger-ui-dist": { @@ -14752,6 +15972,50 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -14914,16 +16178,17 @@ } }, "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "type": "github", @@ -15080,7 +16345,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15097,7 +16361,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15114,7 +16377,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15131,7 +16393,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15148,7 +16409,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15165,7 +16425,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15182,7 +16441,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15199,7 +16457,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15216,7 +16473,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15233,7 +16489,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15250,7 +16505,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15267,7 +16521,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15284,7 +16537,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15301,7 +16553,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15318,7 +16569,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15335,7 +16585,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15352,7 +16601,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15369,7 +16617,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15386,7 +16633,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15403,7 +16649,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15420,7 +16665,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15437,7 +16681,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15454,7 +16697,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15471,7 +16713,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15488,7 +16729,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15505,7 +16745,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15695,6 +16934,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "license": "MIT" + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -15784,7 +17029,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -15799,6 +17044,35 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -15938,12 +17212,72 @@ "node": ">= 14" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatsapp-rust-bridge": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/whatsapp-rust-bridge/-/whatsapp-rust-bridge-0.5.4.tgz", + "integrity": "sha512-yYO1qSs0Fe7tGtnxOFHomocUD6IZtoAgmA4oDFyGIRZ67D3QZk3w7swA6XXFXNQngiyrg2k7tul6IrM3eUFh7A==", + "license": "MIT" + }, + "node_modules/whatsapp-web.js": { + "version": "1.34.7", + "resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.34.7.tgz", + "integrity": "sha512-CscRtB32OnozLj+cuG9Q5f7IhnNV2EU4RGRJYeYF7wwhN6acQ0efabnFpetSEh5Y8OL4YqBj7nSQbUrTZYLDGA==", + "license": "Apache-2.0", + "dependencies": { + "fluent-ffmpeg": "2.1.3", + "mime": "3.0.0", + "node-fetch": "2.7.0", + "node-webpmux": "3.2.1", + "puppeteer": "24.38.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "archiver": "7.0.1", + "fs-extra": "11.3.4", + "unzipper": "0.12.3" + } + }, + "node_modules/whatsapp-web.js/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/whatsapp-web.js/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -15958,7 +17292,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -16064,6 +17398,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/win-guid": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz", + "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==", + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -16078,7 +17418,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -16092,11 +17431,65 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "optional": true + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -16112,7 +17505,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -16125,20 +17517,18 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -16233,7 +17623,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -16252,12 +17641,30 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yauzl/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/yocto-queue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", @@ -16271,6 +17678,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "optional": true, + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/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" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "optional": true, + "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/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 56e32fcc8a..30b74aed7d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "db:studio": "node runWithProvider.js \"npx prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"", "db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\"", "db:migrate:dev:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate dev --schema prisma\\DATABASE_PROVIDER-schema.prisma\"", + "openapi:generate": "tsx ./scripts/generate-openapi.ts", "prepare": "husky" }, "repository": { @@ -74,10 +75,11 @@ "@prisma/client": "^6.16.2", "@sentry/node": "^10.12.0", "@types/uuid": "^10.0.0", + "@wppconnect/wa-js": "^3.22.1", "amqplib": "^0.10.5", "audio-decode": "^2.2.3", "axios": "^1.7.9", - "baileys": "7.0.0-rc.9", + "baileys": "^7.0.0-rc13", "class-validator": "^0.14.1", "compression": "^1.7.5", "cors": "^2.8.5", @@ -87,10 +89,10 @@ "eventemitter2": "^6.4.9", "express": "^4.21.2", "express-async-errors": "^3.1.1", + "fetch-socks": "^1.3.2", "fluent-ffmpeg": "^2.1.3", "form-data": "^4.0.1", "https-proxy-agent": "^7.0.6", - "fetch-socks": "^1.3.2", "i18next": "^23.7.19", "jimp": "^1.6.0", "json-schema": "^0.4.0", @@ -124,7 +126,8 @@ "swagger-ui-express": "^5.0.1", "tsup": "^8.3.5", "undici": "^7.16.0", - "uuid": "^13.0.0" + "uuid": "^13.0.0", + "whatsapp-web.js": "^1.34.7" }, "devDependencies": { "@commitlint/cli": "^19.8.1", @@ -151,6 +154,7 @@ "husky": "^9.1.7", "lint-staged": "^16.1.6", "prettier": "^3.4.2", + "swagger-jsdoc": "^6.3.0", "tsconfig-paths": "^4.2.0", "tsx": "^4.20.5", "typescript": "^5.7.2" diff --git a/public/catalog-pairing.html b/public/catalog-pairing.html new file mode 100644 index 0000000000..db53f2a15d --- /dev/null +++ b/public/catalog-pairing.html @@ -0,0 +1,405 @@ + + + + + + Catalog Browser Auth — Evolution API + + + +
+

Catalog Browser Auth

+
WhatsApp Web session authentication for browser-based catalog fetch
+ +
+
+ + +
+
+ + +
+
+ + + + + + + +
+ + +
+ +
+ + + +
+ +
+ How to authenticate (choose one method): +
    +
  1. Pairing Code (recommended): Enter your phone number above (international format, e.g. 6285733556953), click "Request Pairing Code". An 8-character code will appear. On your phone: WhatsApp → Settings → Linked Devices → Link a Device → "Link with phone number instead" → Enter the code.
  2. +
  3. QR Code: Click "Show QR Code". A QR will appear. On your phone: WhatsApp → Settings → Linked Devices → Link a Device → Scan the QR code with your camera.
  4. +
  5. After successful auth, this page will auto-detect and show "Authenticated". You can then use POST /business/getCatalog/{instance} with {"provider":"browser"} to fetch full catalog.
  6. +
+
+ +
Ready.
+
+ + + + diff --git a/scripts/generate-openapi.ts b/scripts/generate-openapi.ts new file mode 100644 index 0000000000..060ed23a06 --- /dev/null +++ b/scripts/generate-openapi.ts @@ -0,0 +1,47 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import swaggerJsdoc from 'swagger-jsdoc'; + +const packageJson = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8')); +const { version } = packageJson; + +const options: swaggerJsdoc.Options = { + definition: { + openapi: '3.0.0', + info: { + title: 'Evolution API', + version, + description: 'WhatsApp API - OpenAPI Documentation', + contact: { + name: 'Evolution API', + url: 'https://github.com/EvolutionAPI/evolution-api', + }, + }, + servers: [ + { + url: '/v1', + description: 'API v1', + }, + ], + components: { + securitySchemes: { + apikey: { + type: 'apiKey', + name: 'apikey', + in: 'header', + description: 'API key for authentication', + }, + }, + }, + security: [ + { + apikey: [], + }, + ], + }, + apis: ['./src/api/routes/*.ts', './src/api/routes/**/*.ts'], +}; + +const spec = swaggerJsdoc(options); +writeFileSync('./openapi.json', JSON.stringify(spec, null, 2)); +console.log('OpenAPI spec generated at ./openapi.json'); diff --git a/src/api/controllers/business.controller.ts b/src/api/controllers/business.controller.ts index 3c7f166cca..54d9b32392 100644 --- a/src/api/controllers/business.controller.ts +++ b/src/api/controllers/business.controller.ts @@ -1,5 +1,6 @@ import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto'; import { InstanceDto } from '@api/dto/instance.dto'; +import { BrowserCatalogService } from '@api/integrations/channel/whatsapp/catalog-browser.service'; import { WAMonitoringService } from '@api/services/monitor.service'; export class BusinessController { @@ -12,4 +13,65 @@ export class BusinessController { public async fetchCollections({ instanceName }: InstanceDto, data: getCollectionsDto) { return await this.waMonitor.waInstances[instanceName].fetchCollections(instanceName, data); } + + /** + * Request a phone-number pairing code for browser-based catalog session. + * Returns 8-character code (e.g. "ABCD1234") the user enters on their phone + * in WhatsApp → Linked Devices → Link with phone number instead. + * + * Phone format: international, digits only (e.g. "6285733556953" for Indonesia). + */ + public async requestPairingCode({ instanceName }: InstanceDto, data: { phone: string }) { + const svc = BrowserCatalogService.getInstance(); + if (!svc) { + throw new Error('Browser catalog service is not initialized. Set CATALOG_BROWSER_ENABLED=true.'); + } + const pairingCode = await svc.requestPairingCode(instanceName, data.phone); + return { + instance: instanceName, + phone: data.phone, + pairingCode, + expiresIn: 60, + instructions: + 'Open WhatsApp on your phone → Settings → Linked Devices → Link a Device → ' + + 'Link with phone number instead → Enter the 8-character code', + }; + } + + /** + * Get current auth state of the browser session for an instance. + * Used by the pairing UI to poll for: QR code, pairing code, or authenticated status. + */ + public async getAuthState({ instanceName }: InstanceDto) { + const svc = BrowserCatalogService.getInstance(); + if (!svc) { + return { + instance: instanceName, + enabled: false, + message: 'Browser catalog service is not initialized. Set CATALOG_BROWSER_ENABLED=true.', + }; + } + return { + instance: instanceName, + enabled: true, + ...svc.getAuthState(instanceName), + }; + } + + /** + * Logout / delete the browser session for an instance. + * User will need to re-scan QR or re-pair on next catalog fetch. + */ + public async logoutBrowser({ instanceName }: InstanceDto) { + const svc = BrowserCatalogService.getInstance(); + if (!svc) { + throw new Error('Browser catalog service is not initialized.'); + } + await svc.logout(instanceName); + return { + instance: instanceName, + loggedOut: true, + message: 'Browser session deleted. Next catalog fetch will require new auth.', + }; + } } diff --git a/src/api/dto/business.dto.ts b/src/api/dto/business.dto.ts index d29b3cf97c..8680e64bfc 100644 --- a/src/api/dto/business.dto.ts +++ b/src/api/dto/business.dto.ts @@ -6,9 +6,28 @@ export class getCatalogDto { number?: string; limit?: number; cursor?: string; + /** + * Which fetch backend to use. + * - `baileys` (default): protocol-level fetch via Baileys library + * - `browser`: launches a Puppeteer browser session to fetch via + * web.whatsapp.com's internal API. Returns full catalog without + * WhatsApp's protocol-level truncation. Requires `CATALOG_BROWSER_ENABLED=true` + * and the user must complete a one-time QR scan or pairing code for the browser session. + */ + provider?: 'baileys' | 'browser'; } export class getCollectionsDto { number?: string; limit?: number; + cursor?: string; + /** + * Which fetch backend to use. See `getCatalogDto.provider`. + */ + provider?: 'baileys' | 'browser'; +} + +export class requestPairingCodeDto { + /** Phone number in international format, digits only (e.g. "6285733556953" for Indonesia) */ + phone: string; } diff --git a/src/api/integrations/channel/whatsapp/catalog-browser.service.ts b/src/api/integrations/channel/whatsapp/catalog-browser.service.ts new file mode 100644 index 0000000000..1a7dd38512 --- /dev/null +++ b/src/api/integrations/channel/whatsapp/catalog-browser.service.ts @@ -0,0 +1,1479 @@ +/** + * BrowserCatalogService + * --------------------------------------------------------------------- + * Singleton service that uses whatsapp-web.js to fetch catalog & collections + * via web.whatsapp.com, bypassing Baileys' protocol-level truncation. + * + * Why this exists: + * WhatsApp's anti-bot/anti-scraping on the protocol level (Baileys) is + * very strict and causes `getCatalog()` to truncate results. The same + * catalog fetched via web.whatsapp.com (browser automation) returns the + * full list because WhatsApp's own frontend code handles pagination. + * + * Implementation: + * Uses whatsapp-web.js (same library as bedones-whatsapp, proven working). + * - LocalAuth strategy for session persistence per instance + * - Event-driven: 'qr', 'authenticated', 'ready', 'code_received' (pairing) + * - Catalog fetch uses window.WPP API (auto-injected by whatsapp-web.js) + * + * Ported logic from: + * bedones-whatsapp/apps/whatsapp-connector/src/catalog/catalog.service.ts + */ + +import { Logger } from '@config/logger.config'; +import { INSTANCE_DIR } from '@config/path.config'; +import { BadRequestException } from '@exceptions'; +import axios from 'axios'; +import { existsSync, mkdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { Client, LocalAuth } from 'whatsapp-web.js'; + +import { + BrowserCatalogConfig, + BrowserCatalogOptions, + BrowserCatalogResult, + BrowserCollectionsOptions, + BrowserCollectionsResult, +} from './catalog-browser.types'; + +// Per-instance client state +interface InstanceClientState { + client: Client; + ready: boolean; + readyPromise: Promise; + qrCode: string | null; + pairingCode: string | null; + lastActivity: number; + idleTimer?: NodeJS.Timeout; +} + +const SESSION_SUBDIR = 'browser-session'; + +// (No NestJS — Evolution API uses plain classes. The @Injectable decorator +// below is a no-op kept only for documentation purposes; remove if it causes +// issues. This class is constructed manually in server.module.ts.) + +export class BrowserCatalogService { + private readonly logger = new Logger(BrowserCatalogService.name); + private readonly config: BrowserCatalogConfig; + + // Per-instance state map (key = instance name) + private readonly clients = new Map(); + + private static instance: BrowserCatalogService | null = null; + + static getInstance(): BrowserCatalogService | null { + return BrowserCatalogService.instance; + } + + static setInstance(svc: BrowserCatalogService): void { + BrowserCatalogService.instance = svc; + } + + constructor() { + this.config = this.loadConfig(); + if (this.config.enabled) { + this.logger.log( + `Browser catalog service enabled (maxSessions=${this.config.maxSessions}, idleTimeoutMs=${this.config.idleTimeoutMs})`, + ); + } + BrowserCatalogService.setInstance(this); + } + + static async fetchCatalogOrThrow(options: BrowserCatalogOptions): Promise { + const svc = BrowserCatalogService.getInstance(); + if (!svc) { + throw new BadRequestException( + 'Browser catalog service is not initialized. Set CATALOG_BROWSER_ENABLED=true to enable.', + ); + } + return svc.fetchCatalog(options); + } + + static async fetchCollectionsOrThrow(options: BrowserCollectionsOptions): Promise { + const svc = BrowserCatalogService.getInstance(); + if (!svc) { + throw new BadRequestException( + 'Browser catalog service is not initialized. Set CATALOG_BROWSER_ENABLED=true to enable.', + ); + } + return svc.fetchCollections(options); + } + + private loadConfig(): BrowserCatalogConfig { + const enabled = (process.env.CATALOG_BROWSER_ENABLED || 'false').toLowerCase() === 'true'; + const idleTimeoutMs = parseInt(process.env.CATALOG_BROWSER_IDLE_TIMEOUT_MS || '600000', 10); + const maxSessions = parseInt(process.env.CATALOG_BROWSER_MAX_SESSIONS || '5', 10); + const headlessEnv = (process.env.CATALOG_BROWSER_HEADLESS || 'true').toLowerCase(); + const headless: boolean | 'shell' = headlessEnv === 'shell' ? 'shell' : headlessEnv === 'false' ? false : true; + const executablePath = + process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROMIUM_PATH || '/usr/bin/chromium-browser'; + + return { + enabled, + idleTimeoutMs, + maxSessions, + headless, + executablePath, + extraArgs: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-gpu', + '--disable-software-rasterizer', + '--disable-features=VizDisplayCompositor,Vulkan', + '--disable-vulkan', + '--memory-pressure-off', + '--no-first-run', + '--no-default-browser-check', + '--disable-extensions', + '--disable-background-networking', + '--disable-sync', + '--disable-translate', + '--disable-default-apps', + '--disable-component-update', + '--disable-blink-features=AutomationControlled', + ], + }; + } + + /** + * Get the user-data directory for an instance's WhatsApp Web session. + */ + private userDataDir(instanceName: string): string { + return join(INSTANCE_DIR, instanceName, SESSION_SUBDIR); + } + + /** + * Sanitize an instance name into a valid LocalAuth clientId. + * + * whatsapp-web.js LocalAuth requires clientId to be alphanumeric + underscore + * + hyphen only. Instance names like "Warung Lakku" (with space) are rejected + * with "Invalid clientId" error. + * + * Strategy: replace any non-alphanumeric char with a hyphen, collapse + * consecutive hyphens, and trim leading/trailing hyphens. + * + * Examples: + * "Warung Lakku" → "Warung-Lakku" + * "kelvincruv" → "kelvincruv" + * "Hobi Haus" → "Hobi-Haus" + * "My Instance #1!" → "My-Instance-1" + */ + private sanitizeClientId(instanceName: string): string { + return instanceName + .replace(/[^a-zA-Z0-9_-]/g, '-') + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, ''); + } + + /** + * Clean stale Chromium lock files left by previous crashed sessions. + */ + private cleanStaleLocks(dir: string): void { + for (const lockFile of ['SingletonLock', 'SingletonCookie', 'SingletonSocket']) { + const p = join(dir, lockFile); + if (existsSync(p)) { + try { + unlinkSync(p); + this.logger.log(`[browser] Removed stale lock: ${lockFile}`); + } catch (err) { + this.logger.warn(`[browser] Failed to remove ${lockFile}: ${(err as Error).message}`); + } + } + } + } + + /** + * Inject @wppconnect/wa-js into the WhatsApp Web page. + * + * whatsapp-web.js does NOT auto-inject wa-js. Without it, window.WPP is + * undefined and all catalog/collection fetch logic fails silently. + * + * This reads the wa-js bundle from node_modules and evaluates it in the + * page context, then waits for WPP.isReady. + * + * (Ported from bedones-whatsapp's injectWPPIntoPageInternal) + */ + private async injectWaJs(page: any): Promise { + const wppExists = await page.evaluate(() => typeof (window as any).WPP !== 'undefined'); + if (wppExists) { + this.logger.log('[browser] WPP already loaded in page'); + // Still ensure it's ready + await this.waitForWppReady(page); + return; + } + + this.logger.log('[browser] Injecting @wppconnect/wa-js into page...'); + // require.resolve is needed because @wppconnect/wa-js doesn't export a path + // eslint-disable-next-line @typescript-eslint/no-var-requires + const waJsPath = require.resolve('@wppconnect/wa-js'); + const waJsCode = readFileSync(waJsPath, 'utf8'); + await page.evaluate(waJsCode); + this.logger.log(`[browser] wa-js injected (${waJsCode.length} chars)`); + + // Wait for window.WPP to be defined (the library object) + try { + await page.waitForFunction(() => typeof (window as any).WPP !== 'undefined', { timeout: 10000 }); + this.logger.log('[browser] window.WPP is defined'); + } catch { + this.logger.warn('[browser] window.WPP not defined after 10s — wa-js injection may have failed'); + throw new BadRequestException('Failed to inject wa-js: window.WPP not defined'); + } + + // Wait for WPP to be fully ready (webpack modules loaded) + await this.waitForWppReady(page); + } + + /** + * Wait for WPP.isReady === true using Puppeteer's waitForFunction polling. + * + * Verified working in production: isReady becomes true within ~3s of + * wa-js injection. The onFullReady/onReady callbacks from wa-js are + * NOT available on window.WPP object (confirmed via debug script), + * so polling via waitForFunction is the most reliable approach. + * + * Without waiting, calling WPP.conn.getMyUserId() crashes with + * "Cannot read properties of undefined (reading 'm')" because the + * underlying webpack module isn't loaded yet. + */ + private async waitForWppReady(page: any): Promise { + this.logger.log('[browser] Waiting for WPP.isReady...'); + + try { + await page.waitForFunction( + () => { + const wpp = (window as any).WPP; + return wpp && (wpp.isReady === true || wpp.isFullReady === true); + }, + { timeout: 60000, polling: 500 }, + ); + this.logger.log('[browser] WPP.isReady = true'); + } catch { + // Check final state for debugging + const state = await page + .evaluate(() => { + const wpp = (window as any).WPP; + return { + exists: !!wpp, + isReady: wpp?.isReady, + isFullReady: wpp?.isFullReady, + }; + }) + .catch(() => ({ exists: false, isReady: false, isFullReady: false })); + + this.logger.warn( + `[browser] WPP NOT ready after 60s (exists=${state.exists}, isReady=${state.isReady}, isFullReady=${state.isFullReady})`, + ); + throw new BadRequestException( + 'WPP library not ready after 60s. WhatsApp Web may have changed its internal structure.', + ); + } + } + + /** + * Get or create a Client for the given instance. + * Returns once the client is fully ready (authenticated + WA Web loaded). + */ + private async getReadyClient(instanceName: string): Promise { + let state = this.clients.get(instanceName); + if (state) { + // Reset idle timer + this.resetIdleTimer(instanceName); + await state.readyPromise; + return state; + } + + // Evict oldest if at capacity + if (this.clients.size >= this.config.maxSessions) { + const oldest = Array.from(this.clients.entries()).sort((a, b) => a[1].lastActivity - b[1].lastActivity)[0]; + if (oldest) { + this.logger.warn(`[browser] Max sessions reached, evicting: ${oldest[0]}`); + await this.killClient(oldest[0]); + } + } + + state = this.launchClient(instanceName); + this.clients.set(instanceName, state); + this.resetIdleTimer(instanceName); + await state.readyPromise; + return state; + } + + /** + * Launch a new whatsapp-web.js Client for the instance. + * Sets up event listeners for qr / authenticated / ready / disconnected. + */ + private launchClient(instanceName: string): InstanceClientState { + const userDataDir = this.userDataDir(instanceName); + mkdirSync(userDataDir, { recursive: true }); + this.cleanStaleLocks(userDataDir); + + this.logger.log(`[browser] Launching Client for instance=${instanceName}`); + + const state: InstanceClientState = { + client: null as any, + ready: false, + readyPromise: null as any, + qrCode: null, + pairingCode: null, + lastActivity: Date.now(), + }; + + const client = new Client({ + authStrategy: new LocalAuth({ + clientId: this.sanitizeClientId(instanceName), + dataPath: userDataDir, + }), + puppeteer: { + executablePath: this.config.executablePath, + headless: this.config.headless, + args: this.config.extraArgs, + bypassCSP: true, + }, + }); + state.client = client; + + // Set up event listeners + client.on('qr', (qr: string) => { + this.logger.log(`[browser] QR received for instance=${instanceName}`); + state.qrCode = qr; + state.pairingCode = null; + }); + + client.on('authenticated', () => { + this.logger.log(`[browser] Authenticated for instance=${instanceName}`); + state.qrCode = null; + state.pairingCode = null; + }); + + client.on('ready', () => { + this.logger.log(`[browser] Client ready for instance=${instanceName}`); + state.ready = true; + state.qrCode = null; + state.pairingCode = null; + // Note: wa-js injection is done lazily in fetchCatalog/fetchCollections, + // NOT here. Calling injectWaJs here causes a race condition with the + // injectWaJs call in fetchCatalog — two concurrent page.evaluate(waJsCode) + // calls corrupt the page state, resulting in window.WPP = undefined. + }); + + client.on('auth_failure', (msg: string) => { + this.logger.error(`[browser] Auth failure for instance=${instanceName}: ${msg}`); + }); + + client.on('disconnected', (reason: string) => { + this.logger.warn(`[browser] Disconnected for instance=${instanceName}: ${reason}`); + this.clients.delete(instanceName); + }); + + // Create readyPromise that resolves when EITHER 'qr' OR 'ready' event fires. + // - 'qr' means client needs authentication (return QR to caller) + // - 'ready' means client is authenticated and operational + // Either way, the caller can proceed (either show QR or fetch catalog). + // Timeout: 120s — if neither fires, something is wrong (network issue etc.) + state.readyPromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Client initialization timed out after 120s for instance=${instanceName}`)); + }, 120000); + + client.once('qr', () => { + clearTimeout(timeout); + // Don't resolve immediately — wait a tick to ensure state.qrCode is set + // in the 'qr' event handler above before resolving. + setTimeout(() => resolve(), 100); + }); + client.once('ready', () => { + clearTimeout(timeout); + resolve(); + }); + client.once('auth_failure', (msg: string) => { + clearTimeout(timeout); + reject(new Error(`Auth failure: ${msg}`)); + }); + }); + + // Initialize (async, but don't await — readyPromise will resolve when ready) + client.initialize().catch((err: Error) => { + this.logger.error(`[browser] Initialize failed for instance=${instanceName}: ${err.message}`); + }); + + return state; + } + + /** + * Public entry: fetch catalog via browser. + * If session is not authenticated, returns qrCode in the result for the + * caller to surface to the user. + */ + async fetchCatalog(options: BrowserCatalogOptions): Promise { + if (!this.config.enabled) { + throw new BadRequestException('Browser catalog service is disabled. Set CATALOG_BROWSER_ENABLED=true to enable.'); + } + + const { instanceName } = options; + this.logger.log(`[browser] fetchCatalog instance=${instanceName}`); + + let state: InstanceClientState; + try { + state = await this.getReadyClient(instanceName); + } catch (err) { + // If init failed (e.g. not authenticated within timeout), + // return current QR/pairing state if available + const currentState = this.clients.get(instanceName); + if (currentState?.qrCode) { + return this.buildAuthPendingResult(options.jid, currentState); + } + throw err; + } + + if (!state.ready || state.qrCode) { + return this.buildAuthPendingResult(options.jid, state); + } + + // Client is ready — fetch catalog via window.WPP API + const page = await state.client.pupPage; + if (!page) { + throw new BadRequestException('WhatsApp Web page not available'); + } + + // Ensure wa-js is injected before fetching catalog. + // (injectWaJs is idempotent — skips if WPP already loaded. + // This fixes a race condition where readyPromise resolves before + // the 'ready' event handler's injectWaJs call completes.) + await this.injectWaJs(page); + + // Get the authenticated user's WhatsApp ID (needed for catalog API calls) + const wppUserId = await page.evaluate(() => { + const wpp = (window as any).WPP; + const myUser = wpp?.conn?.getMyUserId ? wpp.conn.getMyUserId() : null; + return myUser ? myUser._serialized : null; + }); + if (!wppUserId) { + throw new BadRequestException('Could not determine WhatsApp user ID'); + } + + const result = await page.evaluate(async (userId: string): Promise => { + const wpp = (window as any).WPP; + if (!wpp) return { catalog: [], message: 'WPP not available' }; + + const whatsappApi = wpp.whatsapp; + const productsById = new Map(); + + const extractProductsFromCatalog = (catalogEntry: any): any[] => { + if (!catalogEntry) return []; + const productIndex = catalogEntry.productCollection?._index; + if (!productIndex || typeof productIndex !== 'object') return []; + return Object.keys(productIndex) + .map((productId) => productIndex[productId]?.attributes) + .filter(Boolean); + }; + + // Catalog fetch strategy: + // queryCatalog errors with CatalogUnknownError for own catalog (only works + // for other businesses' catalogs). Use these methods in order: + // + // 1. CatalogStore.findQuery — direct store access (most reliable) + // 2. WPP.catalog.getMyCatalog — wrapper around catalog store + // 3. WPP.catalog.getProducts(uid, 20) — note: count=20 returns 20, + // but count=999 returns only 10 (WhatsApp quirk). Use 20. + // Then try count=10 for any additional products not in first batch. + // + // All products are serialized immediately via JSON.stringify to avoid + // Puppeteer serialization crashes with non-serializable prototypes. + + // Catalog fetch with pagination: + // + // WhatsApp Web uses lazy-loading (cache). CatalogStore only has the first + // page (~20 products). To get ALL products, must call findNextProductPage() + // in a loop — like scrolling down in the UI. Each call fetches the next + // batch from the server and appends to the store. + // + // Flow: + // 1. CatalogStore.findQuery(uid) → get catalog model + first page + // 2. Loop: CatalogStore.findNextProductPage(catalog.id) → load next page + // 3. Extract all products from catalog.productCollection._index + // 4. Repeat until findNextProductPage returns 0 new products + + const serializeProduct = (p: any): any | null => { + if (!p?.id) return null; + try { + return JSON.parse(JSON.stringify(p, (_k: string, v: any) => (typeof v === 'function' ? undefined : v))); + } catch { + return { id: p.id, name: p.name, priceAmount1000: p.priceAmount1000, currency: p.currency }; + } + }; + + // Step 1: Get catalog model from CatalogStore + if (whatsappApi?.CatalogStore?.findQuery) { + try { + const results: any[] = await whatsappApi.CatalogStore.findQuery(userId); + if (Array.isArray(results) && results.length > 0) { + const catalogModel = results[0]; + + // Extract initial products + const extractAndAdd = () => { + const products = extractProductsFromCatalog(catalogModel); + for (const product of products) { + const plain = serializeProduct(product); + if (plain && !productsById.has(plain.id)) { + productsById.set(plain.id, plain); + } + } + }; + extractAndAdd(); + + // Step 2: Paginate — call findNextProductPage in a loop + if (whatsappApi.CatalogStore.findNextProductPage) { + let pageCount = 0; + const maxPages = 100; // Safety limit + while (pageCount < maxPages) { + const beforeCount = productsById.size; + try { + // findNextProductPage(catalogWid) fetches next batch from server + // and appends to catalogModel.productCollection._index + await whatsappApi.CatalogStore.findNextProductPage(catalogModel.id); + } catch (e: any) { + console.log(`findNextProductPage error (page ${pageCount}):`, e?.message); + break; + } + // Re-extract products after loading more + extractAndAdd(); + const afterCount = productsById.size; + pageCount++; + + console.log(`Page ${pageCount}: ${afterCount - beforeCount} new products (total: ${afterCount})`); + + // If no new products, we've reached the end + if (afterCount === beforeCount) { + break; + } + + // Small delay between pages (mimic scroll behavior) + await new Promise((r) => setTimeout(r, 500)); + } + } + } + } catch (error: any) { + console.log('CatalogStore.findQuery error:', error?.message); + } + } + + // Fallback: WPP.catalog.getMyCatalog + if (productsById.size === 0) { + try { + const myCatalog: any = await wpp.catalog?.getMyCatalog?.(); + const fallbackProducts = extractProductsFromCatalog(myCatalog); + for (const product of fallbackProducts) { + const plain = serializeProduct(product); + if (plain && !productsById.has(plain.id)) { + productsById.set(plain.id, plain); + } + } + } catch (error: any) { + console.log('getMyCatalog error:', error?.message); + } + } + + // Fallback: WPP.catalog.getProducts (max 20 per call) + if (productsById.size === 0) { + try { + const products: any[] = await wpp.catalog?.getProducts?.(userId, 20); + if (Array.isArray(products)) { + for (const product of products) { + const plain = serializeProduct(product); + if (plain && !productsById.has(plain.id)) { + productsById.set(plain.id, plain); + } + } + } + } catch (error: any) { + console.log('getProducts error:', error?.message); + } + } + + // Products are already serialized (plain objects) — return directly + const catalog = Array.from(productsById.values()); + + // Add computed 'price' field from priceAmount1000 (WhatsApp stores + // prices in 1/1000 units: 3000000 = Rp 3.000) + for (const p of catalog) { + if (p.priceAmount1000 != null && p.price === undefined) { + p.price = String(Math.floor(p.priceAmount1000 / 1000)); + } + } + + return { catalog }; + }, wppUserId); + + // Null check — page.evaluate can return undefined if the page closes + // or the evaluation times out + if (!result || !result.catalog) { + this.logger.warn(`[browser] fetchCatalog: page.evaluate returned no result`); + throw new BadRequestException( + 'Catalog fetch failed: WhatsApp Web page returned no data. ' + 'Try again in a few seconds.', + ); + } + + this.logger.log(`[browser] fetchCatalog got ${result.catalog.length} products`); + + return { + wuid: options.jid, + numberExists: true, + isBusiness: true, + catalogLength: result.catalog.length, + catalog: result.catalog, + truncated: false, + nextCursor: null, + source: 'browser', + }; + } + + /** + * Public entry: fetch collections via browser. + */ + async fetchCollections(options: BrowserCollectionsOptions): Promise { + if (!this.config.enabled) { + throw new BadRequestException('Browser catalog service is disabled. Set CATALOG_BROWSER_ENABLED=true to enable.'); + } + + const { instanceName } = options; + this.logger.log(`[browser] fetchCollections instance=${instanceName}`); + + let state: InstanceClientState; + try { + state = await this.getReadyClient(instanceName); + } catch (err) { + const currentState = this.clients.get(instanceName); + if (currentState?.qrCode) { + return this.buildAuthPendingCollectionsResult(options.jid, currentState); + } + throw err; + } + + if (!state.ready || state.qrCode) { + return this.buildAuthPendingCollectionsResult(options.jid, state); + } + + const page = await state.client.pupPage; + if (!page) { + throw new BadRequestException('WhatsApp Web page not available'); + } + + await this.injectWaJs(page); + + const wppUserId = await page.evaluate(() => { + const wpp = (window as any).WPP; + const myUser = wpp?.conn?.getMyUserId ? wpp.conn.getMyUserId() : null; + return myUser ? myUser._serialized : null; + }); + if (!wppUserId) { + throw new BadRequestException('Could not determine WhatsApp user ID'); + } + + // === Pure wa-js collection fetch with product mapping === + // + // Architecture (mirrors getCatalog's store-based approach): + // 1. WPP.catalog.getCollections(userId, limit, productsCount) → metadata + // This sends IQ stanza via queryCollectionsIQ and populates: + // - CatalogStore with collection metadata + // - CatalogModel.collections with ProductCollCollection instance + // 2. CatalogStore.findQuery(userId) → CatalogModel + // 3. catalogModel.collections → ProductCollCollection instance + // 4. For each collection: + // a. collections.findCollectionProducts(collectionId, limit) → products + // b. Fallback: CatalogStore.findCollectionMembership(productId) per product + // c. Fallback: scan catalogModel.productCollection._index for collectionId field + // + // This is the same pattern that made getCatalog work (findNextProductPage + // loop on CatalogStore). Now applied to collections. + const limit = options.limit || 100; + + // Set a longer timeout for page.evaluate — default is 30s which is too + // short for catalog operations. We need up to 3 minutes for slow connections. + try { + await page.setDefaultTimeout(180000); + } catch { + // setDefaultTimeout may not exist in all puppeteer versions — ignore + } + + const result = await page + .evaluate( + async (userId: string, colLimit: number): Promise => { + const wpp = (window as any).WPP; + if (!wpp) return { collections: [], error: 'WPP not available' }; + + const wa = wpp.whatsapp; + if (!wa) return { collections: [], error: 'WPP.whatsapp not available' }; + + // Helper: create Wid from string + const makeWid = (id: string): any => { + try { + if (wa.WidFactory?.createWid) return wa.WidFactory.createWid(id); + if (wa.WidFactory?.createWidFromWidLike) return wa.WidFactory.createWidFromWidLike(id); + } catch (e: any) { + console.log(`makeWid(${id}) failed:`, e?.message); + } + return { _serialized: id, id, toString: () => id }; + }; + + // Helper: serialize WhatsApp model to plain object + const serializeProduct = (p: any): any | null => { + if (!p) return null; + const attrs = p?.attributes || p; + if (!attrs?.id) return null; + try { + return JSON.parse( + JSON.stringify(attrs, (_k: string, v: any) => (typeof v === 'function' ? undefined : v)), + ); + } catch { + return { + id: attrs.id, + name: attrs.name, + priceAmount1000: attrs.priceAmount1000, + currency: attrs.currency, + }; + } + }; + + // Diagnostic info + const diag: any = { + catalogStoreExists: !!wa.CatalogStore, + catalogStoreMethods: wa.CatalogStore + ? Object.getOwnPropertyNames(Object.getPrototypeOf(wa.CatalogStore) || {}) + .concat(Object.getOwnPropertyNames(wa.CatalogStore)) + .filter((m: string) => typeof wa.CatalogStore[m] === 'function' && !m.startsWith('_')) + : [], + productCollCollectionExists: !!wa.ProductCollCollection, + }; + + // Step 1: Get collection metadata via WPP.catalog.getCollections + // This also populates CatalogStore and CatalogModel.collections + const collections: any[] = []; + try { + // productsCount=colLimit — request up to colLimit products per collection + const cols = await wpp.catalog.getCollections(userId, colLimit, colLimit); + if (Array.isArray(cols)) { + // Diagnostic: dump ALL attributes of first collection model + if (cols.length > 0) { + const firstCol = cols[0]; + const firstAttrs = firstCol?.attributes || firstCol; + diag.firstCollectionModelKeys = Object.keys(firstAttrs).slice(0, 40); + diag.firstCollectionModelSample = {}; + for (const k of Object.keys(firstAttrs).slice(0, 40)) { + const v = firstAttrs[k]; + if ( + v === null || + v === undefined || + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' + ) { + diag.firstCollectionModelSample[k] = v; + } else if (Array.isArray(v)) { + diag.firstCollectionModelSample[k] = `Array(${v.length})`; + } else if (typeof v === 'object') { + diag.firstCollectionModelSample[k] = `Object(${Object.keys(v).slice(0, 5).join(',')})`; + } else { + diag.firstCollectionModelSample[k] = typeof v; + } + } + // Check if collection model has products field/method + diag.firstCollectionHasProductsField = 'products' in firstAttrs; + diag.firstCollectionHasProductsMethod = typeof firstCol?.products === 'function'; + diag.firstCollectionHasGetProducts = typeof firstCol?.getProducts === 'function'; + diag.firstCollectionProtoMethods = Object.getOwnPropertyNames( + Object.getPrototypeOf(firstCol) || {}, + ).filter((m: string) => !m.startsWith('_') && typeof firstCol[m] === 'function'); + } + for (const c of cols) { + const a = c?.attributes || c; + if (!a?.id) continue; + // Products are embedded in the model's attributes (Backbone.Model pattern). + // Diagnostic confirmed: firstCollectionModelSample shows "products": "Array(55)" + // — the products array lives at a.products (attributes level), NOT c.products + // (model level, which returns undefined for Backbone getters). + // Try multiple access patterns to be safe: + // 1. a.products — attributes.products (Backbone standard) + // 2. c.get('products') — Backbone model getter + // 3. c.products — direct property (if plain object, not Model) + let embeddedProducts: any = a?.products; + if (!Array.isArray(embeddedProducts) && typeof (c as any)?.get === 'function') { + try { + embeddedProducts = (c as any).get('products'); + } catch { + // ignore + } + } + if (!Array.isArray(embeddedProducts)) { + embeddedProducts = (c as any)?.products; + } + + // Diagnostic for first collection: inspect embedded products structure + if (collections.length === 0 && Array.isArray(embeddedProducts) && embeddedProducts.length > 0) { + const firstProd = embeddedProducts[0]; + diag.firstEmbeddedProductType = typeof firstProd; + diag.firstEmbeddedProductIsModel = firstProd?.constructor?.name || 'n/a'; + diag.firstEmbeddedProductKeys = firstProd + ? Object.keys(firstProd.attributes || firstProd).slice(0, 20) + : []; + diag.firstEmbeddedProductHasId = !!(firstProd?.id || firstProd?.attributes?.id); + // Try serialize and check if it works + const testSerialized = serializeProduct(firstProd); + diag.firstEmbeddedProductSerializeResult = testSerialized + ? `OK (${Object.keys(testSerialized).length} keys)` + : 'null/failed'; + if (testSerialized) { + diag.firstEmbeddedProductSampleId = testSerialized.id; + diag.firstEmbeddedProductSampleName = testSerialized.name; + } + } + + const serializedProducts = Array.isArray(embeddedProducts) + ? embeddedProducts.map(serializeProduct).filter(Boolean) + : []; + collections.push({ + id: String(a.id), + name: a.name || '', + products: serializedProducts, + status: a.reviewStatus || a.status, + totalItemsCount: a.totalItemsCount || serializedProducts.length, + }); + } + } + diag.getCollectionsCount = collections.length; + diag.collectionsWithEmbeddedProducts = collections.filter((c) => c.products.length > 0).length; + } catch (error: any) { + diag.getCollectionsError = error?.message; + console.log('getCollections error:', error?.message); + } + + if (collections.length === 0) { + return { collections: [], diag, error: 'No collections returned by getCollections' }; + } + + // Step 2: Get CatalogModel via CatalogStore.findQuery + // This gives us access to catalogModel.collections (ProductCollCollection instance) + let catalogModel: any = null; + let productCollCollection: any = null; + try { + const userWid = makeWid(userId); + const catalogModels = wa.CatalogStore?.findQuery ? await wa.CatalogStore.findQuery(userWid) : null; + if (Array.isArray(catalogModels) && catalogModels.length > 0) { + catalogModel = catalogModels[0]; + diag.catalogModelKeys = Object.keys(catalogModel.attributes || catalogModel).slice(0, 30); + diag.catalogModelHasProductCollection = !!catalogModel.productCollection; + diag.catalogModelHasCollections = !!catalogModel.collections; + diag.catalogModelCollectionsType = typeof catalogModel.collections; + diag.catalogModelCollectionsIsArray = Array.isArray(catalogModel.collections); + + // Step 3: Access catalogModel.collections → ProductCollCollection instance + if (catalogModel.collections) { + productCollCollection = catalogModel.collections; + const pccProto = Object.getPrototypeOf(productCollCollection) || {}; + diag.productCollCollectionMethods = Object.getOwnPropertyNames(pccProto) + .concat(Object.getOwnPropertyNames(productCollCollection)) + .filter((m: string) => typeof productCollCollection[m] === 'function' && !m.startsWith('_')); + diag.productCollCollectionHas = { + findCollectionProducts: typeof productCollCollection.findCollectionProducts === 'function', + getCollectionModels: typeof productCollCollection.getCollectionModels === 'function', + findCollectionsList: typeof productCollCollection.findCollectionsList === 'function', + }; + // Also check if it's an array-like with length + diag.productCollCollectionLength = productCollCollection.length; + } + } + } catch (error: any) { + diag.catalogModelError = error?.message; + console.log('CatalogStore.findQuery error:', error?.message); + } + + // Step 4: For each collection, try to get products + // Build a map: collectionId → products[] + const productsByCollectionId = new Map(); + const perCollectionDebug: any[] = []; + + // === Pre-step: Populate ProductCollCollection store === + // Diagnostic showed productCollCollectionLength: 0 — store is EMPTY. + // getCollections only returns metadata, doesn't populate products. + // We need to call findCollectionsList to fetch collection products. + if (productCollCollection?.findCollectionsList) { + try { + const userWid = makeWid(userId); + diag.findCollectionsListCalled = true; + await productCollCollection.findCollectionsList(userWid, limit, limit); + diag.productCollCollectionLengthAfter = productCollCollection.length; + // Re-check methods availability + diag.productCollCollectionHasAfter = { + findCollectionProducts: typeof productCollCollection.findCollectionProducts === 'function', + getCollectionModels: typeof productCollCollection.getCollectionModels === 'function', + findCollectionsList: typeof productCollCollection.findCollectionsList === 'function', + }; + } catch (e: any) { + diag.findCollectionsListError = e?.message; + } + } + + // === Pre-step: Check catalogModel._products field === + // Check actual value, not just truthiness — _products might be + // undefined, null, empty Map, empty object, etc. + let catalogModelProducts: any = null; + if (catalogModel) { + const _prods = (catalogModel as any)._products; + diag.catalogModelProductsRawValue = + _prods === undefined + ? 'undefined' + : _prods === null + ? 'null' + : Array.isArray(_prods) + ? `Array(${_prods.length})` + : _prods instanceof Map + ? `Map(${_prods.size})` + : typeof _prods === 'object' + ? `Object(${Object.keys(_prods).slice(0, 5).join(',')})` + : String(_prods); + if (_prods) { + catalogModelProducts = _prods; + diag.catalogModelProductsType = typeof catalogModelProducts; + diag.catalogModelProductsIsArray = Array.isArray(catalogModelProducts); + diag.catalogModelProductsKeys = Object.keys(catalogModelProducts).slice(0, 20); + diag.catalogModelProductsLength = catalogModelProducts.length; + if (catalogModelProducts instanceof Map) { + diag.catalogModelProductsIsMap = true; + diag.catalogModelProductsMapSize = catalogModelProducts.size; + diag.catalogModelProductsMapKeys = Array.from(catalogModelProducts.keys()).slice(0, 10); + } + } + } + + for (const col of collections) { + const colId = col.id; + const colName = col.name; + let products: any[] = []; + let method = 'none'; + const attempts: any[] = []; + + // NOTE: findCollectionMembership with collectionWid FAILS because + // collection IDs are numeric Facebook-style IDs, NOT WhatsApp Wids. + // WidFactory.createWid("2066810427554462") throws "wid error: invalid wid" + // Skipping these methods — they will always fail. + + // Method A: findCollectionMembership(productId, collectionId) — TWO args + // - Try with raw collectionId string (not Wid) + // - First arg might be productId or catalogWid + if (wa.CatalogStore?.findCollectionMembership) { + try { + const userWid = makeWid(userId); + attempts.push({ method: 'findCollectionMembership(catalogWid, colId-string)', collectionId: colId }); + const result: any = await wa.CatalogStore.findCollectionMembership(userWid, colId); + attempts[attempts.length - 1].result = Array.isArray(result) + ? `${result.length} items` + : `type: ${typeof result}`; + if (Array.isArray(result) && result.length > 0) { + products = result.map(serializeProduct).filter(Boolean); + method = 'CatalogStore.findCollectionMembership(catalogWid, colId-string)'; + } + } catch (e: any) { + attempts.push({ method: 'findCollectionMembership(catalogWid, colId-string)', error: e?.message }); + } + } + + // Method A2: findCollectionMembership(colId-string) — SINGLE string arg + if (products.length === 0 && wa.CatalogStore?.findCollectionMembership) { + try { + attempts.push({ method: 'findCollectionMembership(colId-string)', collectionId: colId }); + const result: any = await wa.CatalogStore.findCollectionMembership(colId); + attempts[attempts.length - 1].result = Array.isArray(result) + ? `${result.length} items` + : `type: ${typeof result}`; + if (Array.isArray(result) && result.length > 0) { + products = result.map(serializeProduct).filter(Boolean); + method = 'CatalogStore.findCollectionMembership(colId-string)'; + } + } catch (e: any) { + attempts.push({ method: 'findCollectionMembership(colId-string)', error: e?.message }); + } + } + + // Method A3: getCollectionModels with raw string (not Wid) + if (products.length === 0 && productCollCollection?.getCollectionModels) { + try { + attempts.push({ method: 'getCollectionModels(string)', collectionId: colId }); + const result: any = await productCollCollection.getCollectionModels(colId, colLimit); + attempts[attempts.length - 1].result = Array.isArray(result) + ? `${result.length} items` + : `type: ${typeof result}`; + if (Array.isArray(result) && result.length > 0) { + products = result.map(serializeProduct).filter(Boolean); + method = 'ProductCollCollection.getCollectionModels(string)'; + } + } catch (e: any) { + attempts.push({ method: 'getCollectionModels(string)', error: e?.message }); + } + } + + // Method B: Access catalogModel._products directly + // - If _products is a Map, get products for this collection + // - This is synchronous — no async calls + if (products.length === 0 && catalogModelProducts) { + try { + let colProducts: any = null; + if (catalogModelProducts instanceof Map) { + colProducts = catalogModelProducts.get(colId); + } else if (typeof catalogModelProducts === 'object') { + colProducts = catalogModelProducts[colId]; + } + attempts.push({ + method: 'catalogModel._products', + hasProducts: !!colProducts, + productsType: typeof colProducts, + productsIsArray: Array.isArray(colProducts), + }); + if (Array.isArray(colProducts) && colProducts.length > 0) { + products = colProducts.map(serializeProduct).filter(Boolean); + method = 'catalogModel._products'; + } else if (colProducts && typeof colProducts === 'object') { + // Might be a Collection — try to convert to array + const arr = Array.isArray(colProducts) + ? colProducts + : colProducts._index + ? Object.values(colProducts._index) + : []; + if (arr.length > 0) { + products = arr.map(serializeProduct).filter(Boolean); + method = 'catalogModel._products(collection)'; + } + } + } catch (e: any) { + attempts.push({ method: 'catalogModel._products', error: e?.message }); + } + } + + // Method B2: Check productCollCollection._index AFTER findCollectionsList + // - findCollectionsList should have populated the store with collection models + // - Each collection model might have products attached + if (products.length === 0 && productCollCollection?._index) { + try { + const idx = productCollCollection._index; + const idxKeys = Object.keys(idx); + const idxValues = Object.values(idx); + attempts.push({ + method: 'productCollCollection._index', + indexSize: idxKeys.length, + indexKeys: idxKeys.slice(0, 10), + }); + // Check if any collection model in the index matches our colId + for (const cm of idxValues) { + const cmAttrs = (cm as any)?.attributes || cm; + if (String(cmAttrs?.id) === colId) { + // Found the collection model — check if it has products + const cmProducts = (cm as any)?.products || cmAttrs?.products; + if (Array.isArray(cmProducts) && cmProducts.length > 0) { + products = cmProducts.map(serializeProduct).filter(Boolean); + method = 'productCollCollection._index'; + attempts[attempts.length - 1].matchedProducts = products.length; + break; + } + } + } + } catch (e: any) { + attempts.push({ method: 'productCollCollection._index', error: e?.message }); + } + } + + // Method C: Scan catalogModel.productCollection for collectionId field + // - Check if products have collectionId/collectionIds in their attributes + // - This is a SINGLE PASS over all products — fast, no async calls + if (products.length === 0 && catalogModel?.productCollection?._index) { + try { + const allProducts = Object.values(catalogModel.productCollection._index); + // Log sample product keys for diagnostics + if (allProducts.length > 0) { + const sample = (allProducts[0] as any)?.attributes || allProducts[0]; + attempts.push({ + method: 'scanByCollectionId', + totalProducts: allProducts.length, + sampleProductKeys: Object.keys(sample).slice(0, 30), + }); + } + const matching = allProducts + .map((p: any) => p?.attributes || p) + .filter((p: any) => { + return ( + String(p?.collectionId) === colId || + String(p?.collection_id) === colId || + (Array.isArray(p?.collectionIds) && p.collectionIds.some((c: any) => String(c) === colId)) || + (Array.isArray(p?.collection_ids) && p.collection_ids.some((c: any) => String(c) === colId)) || + String(p?.collectionWid?._serialized) === colId || + String(p?.collectionWid) === colId + ); + }) + .map(serializeProduct) + .filter(Boolean) + .slice(0, colLimit); + if (attempts[attempts.length - 1]) { + attempts[attempts.length - 1].matchedProducts = matching.length; + } + if (matching.length > 0) { + products = matching; + method = 'CatalogStore.scanByCollectionId'; + } + } catch (e: any) { + attempts.push({ method: 'scanByCollectionId', error: e?.message }); + } + } + + // Store products for this collection + productsByCollectionId.set(colId, products); + perCollectionDebug.push({ + collectionId: colId, + collectionName: colName, + productCount: products.length, + method, + attempts: attempts.length > 0 ? attempts : undefined, + }); + } + + // Attach products to collections + // IMPORTANT: Only overwrite if the collection doesn't already have + // embedded products from Step 1 (getCollections response). The + // productsByCollectionId Map is populated by Methods A/B/C which + // all failed — so it's empty. Overwriting would destroy the + // embedded products that were already correctly set in Step 1. + for (const col of collections) { + if (Array.isArray(col.products) && col.products.length > 0) { + // Already has embedded products — don't overwrite, just update counts + col.productsLength = col.products.length; + col.totalItemsCount = col.products.length; + } else { + // No embedded products — use productsByCollectionId (may also be empty) + const prods = productsByCollectionId.get(col.id) || []; + col.products = prods; + col.productsLength = prods.length; + col.totalItemsCount = prods.length; + } + } + + const totalProducts = collections.reduce( + (sum: number, c: any) => sum + (Array.isArray(c.products) ? c.products.length : 0), + 0, + ); + + return { + collections, + diag, + perCollectionDebug, + totalProducts, + }; + }, + wppUserId, + limit, + ) + .catch((err: Error) => { + // Catch "Target closed" / Protocol errors — these happen when the + // browser page closes mid-evaluate (OOM, WhatsApp Web reload, etc.) + // Return a partial result with the error so the caller can see what happened. + this.logger.error(`[browser] fetchCollections page.evaluate failed: ${err?.message}`); + return { + collections: [], + diag: { pageEvaluateError: err?.message }, + perCollectionDebug: [], + totalProducts: 0, + error: err?.message, + }; + }); + + this.logger.log( + `[browser] fetchCollections: ${result.collections.length} collections, ` + + `${result.totalProducts || 0} products mapped`, + ); + if (result.diag) { + this.logger.log(`[browser] Diagnostic: ${JSON.stringify(result.diag).slice(0, 500)}`); + } + + return { + wuid: options.jid, + name: null, + numberExists: true, + isBusiness: true, + collectionsLength: result.collections.length, + collections: await this.downloadCollectionImages(result.collections, instanceName), + source: 'browser', + diagnostic: result.diag, + perCollectionDebug: result.perCollectionDebug, + totalProductsMapped: result.totalProducts || 0, + }; + } + + /** + * Download product images from WhatsApp CDN to local storage. + * + * WhatsApp CDN URLs contain time-limited tokens (oh=, oe= parameters) + * that expire after ~24 hours. If n8n or other consumers try to download + * images later, they get 403 Forbidden. + * + * This method downloads images at fetch time and stores them in + * public/catalog-images/{instanceName}/{productId}.jpg + * + * The collections response replaces image_cdn_urls with local URLs + * like: http://evolution-api:8080/catalog-images/{instance}/{file}.jpg + * + * This ensures images are always accessible for Odoo sync. + */ + private async downloadCollectionImages(collections: any[], instanceName: string): Promise { + const imagesDir = join(process.cwd(), 'public', 'catalog-images', this.sanitizeClientId(instanceName)); + mkdirSync(imagesDir, { recursive: true }); + + const baseUrl = process.env.SERVER_URL || `http://0.0.0.0:${process.env.PORT || 8080}`; + let downloadedCount = 0; + let skippedCount = 0; + let failedCount = 0; + + for (const col of collections) { + if (!Array.isArray(col.products)) continue; + + for (const product of col.products) { + const productId = String(product.id || ''); + if (!productId) continue; + + // Get the 'full' image URL (high-res, not thumbnail) + let imageUrl: string | null = null; + const cdnUrls = product.image_cdn_urls || []; + if (Array.isArray(cdnUrls)) { + // Prefer 'full' key, then 'original', then 'requested' + const sorted = [...cdnUrls].sort((a: any, b: any) => { + const priority = (k: string) => (k === 'full' ? 0 : k === 'original' ? 1 : k === 'requested' ? 3 : 2); + return priority(a?.key) - priority(b?.key); + }); + for (const img of sorted) { + if (img?.value) { + imageUrl = img.value; + break; + } + } + } + + if (!imageUrl) { + // No image URL — keep product as-is + skippedCount++; + continue; + } + + // Local file path: {productId}.jpg + const localFile = `${productId}.jpg`; + const localPath = join(imagesDir, localFile); + const localUrl = `${baseUrl}/catalog-images/${this.sanitizeClientId(instanceName)}/${localFile}`; + + // Check if already downloaded (skip re-download to save bandwidth) + try { + if (existsSync(localPath)) { + const stats = statSync(localPath); + if (stats.size > 1000) { + // File exists and is > 1KB — assume valid, skip download + product.image_cdn_urls = [{ key: 'local', value: localUrl }]; + if (product.additional_image_cdn_urls?.[0]?.[0]) { + product.additional_image_cdn_urls = []; + } + skippedCount++; + continue; + } + } + } catch { + // stat failed — proceed with download + } + + // Download image + try { + const response = await axios.get(imageUrl, { + responseType: 'arraybuffer', + timeout: 30000, + maxRedirects: 5, + }); + + const buffer = Buffer.from(response.data); + if (buffer.length < 100) { + // Too small — probably error page + failedCount++; + continue; + } + + // Write to file + writeFileSync(localPath, buffer); + + // Replace CDN URLs with local URL + product.image_cdn_urls = [{ key: 'local', value: localUrl }]; + // Clear additional images (they'd need separate download logic) + if (product.additional_image_cdn_urls) { + product.additional_image_cdn_urls = []; + } + + downloadedCount++; + } catch (err) { + this.logger.warn(`[browser] Failed to download image for product ${productId}: ${(err as Error)?.message}`); + failedCount++; + // Keep original CDN URL as fallback + } + } + } + + this.logger.log( + `[browser] Image download: ${downloadedCount} downloaded, ${skippedCount} cached, ${failedCount} failed`, + ); + + return collections; + } + + async requestPairingCode(instanceName: string, phoneNumber: string): Promise { + if (!this.config.enabled) { + throw new BadRequestException('Browser catalog service is disabled. Set CATALOG_BROWSER_ENABLED=true to enable.'); + } + + // Validate phone format: international, digits only (no +, no spaces) + if (!phoneNumber || !/^\d{6,15}$/.test(phoneNumber)) { + throw new BadRequestException( + 'Invalid phone number. Must be international format, digits only (e.g. "6285733556953" for Indonesia). ' + + 'No "+", spaces, or hyphens.', + ); + } + + const state = await this.getReadyClient(instanceName); + + // If already authenticated, no need for pairing code + if (state.ready) { + throw new BadRequestException( + `Instance "${instanceName}" browser session is already authenticated. No pairing code needed.`, + ); + } + + // requestPairingCode requires the client to be in UNPAIRED state (socket connected + // but not yet authenticated). The 'qr' event guarantees this state. + // getReadyClient resolves on 'qr' or 'ready', so we should be safe here. + const code = await state.client.requestPairingCode(phoneNumber); + state.pairingCode = code; + this.logger.log(`[browser] Pairing code requested for instance=${instanceName} phone=${phoneNumber}: ${code}`); + return code; + } + + /** + * Get current auth state for an instance (qrCode, pairingCode, ready). + */ + getAuthState(instanceName: string): { + ready: boolean; + qrCode: string | null; + pairingCode: string | null; + userId: string | null; + } { + const state = this.clients.get(instanceName); + if (!state) { + return { ready: false, qrCode: null, pairingCode: null, userId: null }; + } + // Extract userId from client.info if available (set after 'ready' event) + let userId: string | null = null; + try { + if (state.ready && state.client?.info?.wid) { + userId = state.client.info.wid._serialized || null; + } + } catch { + // client.info may not be available yet + } + return { + ready: state.ready, + qrCode: state.qrCode, + pairingCode: state.pairingCode, + userId, + }; + } + + /** + * Logout: kill client + delete session for an instance. + */ + async logout(instanceName: string): Promise { + await this.killClient(instanceName); + const userDataDir = this.userDataDir(instanceName); + if (existsSync(userDataDir)) { + rmSync(userDataDir, { recursive: true, force: true }); + this.logger.log(`[browser] Deleted session for instance=${instanceName}`); + } + } + + /** + * Shutdown all clients (for graceful app close). + */ + async shutdownAll(): Promise { + const names = Array.from(this.clients.keys()); + await Promise.all(names.map((n) => this.killClient(n))); + this.logger.log(`[browser] Shutdown ${names.length} client(s)`); + } + + private buildAuthPendingResult(jid: string, state: InstanceClientState): BrowserCatalogResult { + const result: any = { + wuid: jid, + numberExists: true, + isBusiness: true, + catalogLength: 0, + catalog: [], + truncated: false, + nextCursor: null, + source: 'browser', + status: 'auth_required', + }; + if (state.qrCode) result.qrCode = state.qrCode; + if (state.pairingCode) result.pairingCode = state.pairingCode; + return result; + } + + private buildAuthPendingCollectionsResult(jid: string, state: InstanceClientState): BrowserCollectionsResult { + const result: any = { + wuid: jid, + name: null, + numberExists: true, + isBusiness: true, + collectionsLength: 0, + collections: [], + source: 'browser', + status: 'auth_required', + }; + if (state.qrCode) result.qrCode = state.qrCode; + if (state.pairingCode) result.pairingCode = state.pairingCode; + return result; + } + + /** + * Reset the idle timer for an instance. When timer fires, the client + * is killed to free memory. + */ + private resetIdleTimer(instanceName: string): void { + const state = this.clients.get(instanceName); + if (!state) return; + + if (state.idleTimer) clearTimeout(state.idleTimer); + + state.idleTimer = setTimeout(() => { + this.logger.log(`[browser] Idle timeout for instance=${instanceName}, killing client`); + this.killClient(instanceName).catch((err) => { + this.logger.error(`[browser] Failed to kill idle client: ${(err as Error).message}`); + }); + }, this.config.idleTimeoutMs); + } + + /** + * Kill the client for an instance and clean up timers. + */ + private async killClient(instanceName: string): Promise { + const state = this.clients.get(instanceName); + if (!state) return; + + if (state.idleTimer) { + clearTimeout(state.idleTimer); + } + + try { + await state.client.destroy(); + } catch (err) { + this.logger.warn(`[browser] Error closing client: ${(err as Error).message}`); + } + + this.clients.delete(instanceName); + } +} +// rebuild trigger 1783939519 diff --git a/src/api/integrations/channel/whatsapp/catalog-browser.types.ts b/src/api/integrations/channel/whatsapp/catalog-browser.types.ts new file mode 100644 index 0000000000..9bb450b54b --- /dev/null +++ b/src/api/integrations/channel/whatsapp/catalog-browser.types.ts @@ -0,0 +1,168 @@ +/** + * Type definitions for the browser-based catalog service. + * + * The BrowserCatalogService is a singleton that lazily launches a Puppeteer + * browser per WhatsApp account (identified by JID) to fetch catalog & + * collections via the internal `window.WPP.whatsapp.functions.queryCatalog` + * API of web.whatsapp.com — bypassing the limitations of Baileys' protocol + * level `getCatalog()` implementation. + * + * Why: WhatsApp's anti-bot/anti-scraping is very strict on the protocol + * level (Baileys), but relaxed on the browser level (whatsapp-web.js / + * direct WA Web access). Bedones-whatsapp proved this approach works for + * fetching full catalogs. + */ + +/** + * Catalog product as returned by the browser fetch path. + * Shape mirrors Baileys' `Product` type for API response compatibility. + */ +export interface BrowserProduct { + id: string; + name?: string; + description?: string; + currency?: string; + price?: number; + imageCdnUrl?: string; + image_cdn_url?: string; + image_cdn_urls?: Array<{ key: string; value: string }>; + additionalImageCdnUrl?: string[]; + additional_image_cdn_urls?: Array>; + retailerId?: string; + retailer_id?: string; + url?: string; + isHidden?: boolean; + availability?: string; + [key: string]: unknown; +} + +/** + * Catalog collection as returned by the browser fetch path. + * Shape mirrors Baileys' `CatalogCollection` type. + */ +export interface BrowserCollection { + id: string; + name: string; + products: BrowserProduct[]; + status?: string; + [key: string]: unknown; +} + +/** + * Options for fetching catalog via the browser path. + */ +export interface BrowserCatalogOptions { + /** JID of the WhatsApp Business account whose catalog to fetch */ + jid: string; + /** Instance name (used for session file path isolation) */ + instanceName: string; + /** Optional: max number of products per pagination call (default 50) */ + pageSize?: number; + /** Optional: max number of pagination loops before stopping (default 200) */ + maxLoops?: number; +} + +/** + * Options for fetching collections via the browser path. + */ +export interface BrowserCollectionsOptions { + /** JID of the WhatsApp Business account */ + jid: string; + /** Instance name */ + instanceName: string; + /** Optional: limit */ + limit?: number; +} + +/** + * Result of a catalog fetch operation. + */ +export interface BrowserCatalogResult { + wuid: string; + numberExists: boolean; + isBusiness: boolean; + catalogLength: number; + catalog: BrowserProduct[]; + truncated: boolean; + nextCursor: string | null; + source: 'browser'; +} + +/** + * Result of a collections fetch operation. + */ +export interface BrowserCollectionsResult { + wuid: string; + name: string | null; + numberExists: boolean; + isBusiness: boolean; + collectionsLength: number; + collections: BrowserCollection[]; + source: 'browser'; + /** Diagnostic info about wa-js webpack modules available */ + diagnostic?: any; + /** Per-collection debug info: which method worked, product count, attempts */ + perCollectionDebug?: Array<{ + collectionId: string; + collectionName?: string; + productCount: number; + method: string; + attempts?: any[]; + }>; + /** Total products mapped across all collections */ + totalProductsMapped?: number; +} + +/** + * Result of the hybrid collections fetch (browser metadata + Baileys product mapping). + * + * Each collection's `products` array is populated from Baileys' protocol-level + * `getCollections` IQ stanza (which returns products nested in each collection). + * If Baileys fails (anti-bot / not-business), `products` arrays will be empty + * and `baileysOk` will be false — caller should fall back to keyword matching. + */ +export interface HybridCollectionsResult extends Omit { + source: 'hybrid'; + /** Number of collections Baileys returned (may be 0 if anti-bot blocked it) */ + baileysCollectionsCount: number; + /** Total number of products across all collections from Baileys */ + baileysProductsCount: number; + /** Whether the Baileys protocol query succeeded */ + baileysOk: boolean; +} + +/** + * Internal state of a browser session. + */ +export interface BrowserSessionState { + /** JID of the WhatsApp account */ + jid: string; + /** Instance name */ + instanceName: string; + /** Whether the browser is authenticated */ + authenticated: boolean; + /** QR code data URL, if pending authentication */ + qrCode?: string; + /** Timestamp of last activity (ms epoch) */ + lastActivity: number; + /** Timestamp of session creation */ + createdAt: number; +} + +/** + * Configuration for the BrowserCatalogService. + */ +export interface BrowserCatalogConfig { + /** Master toggle — if false, all browser calls fall back to Baileys */ + enabled: boolean; + /** Idle timeout before killing browser (ms) */ + idleTimeoutMs: number; + /** Max concurrent browser sessions */ + maxSessions: number; + /** Headless mode (true | false | 'shell' in Puppeteer v23+) */ + headless: boolean | 'shell'; + /** Custom executable path (overrides PUPPETEER_EXECUTABLE_PATH env) */ + executablePath?: string; + /** Extra Puppeteer launch args */ + extraArgs?: string[]; +} diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 60e857fcc1..5f33bdbf2f 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1,4 +1,4 @@ -import { getCollectionsDto } from '@api/dto/business.dto'; +import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto'; import { OfferCallDto } from '@api/dto/call.dto'; import { ArchiveChatDto, @@ -153,6 +153,7 @@ import { PassThrough, Readable } from 'stream'; import { v4 } from 'uuid'; import { BaileysMessageProcessor } from './baileysMessage.processor'; +import { BrowserCatalogService } from './catalog-browser.service'; import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys'; export interface ExtendedIMessageKey extends proto.IMessageKey { @@ -4899,10 +4900,22 @@ export class BaileysStartupService extends ChannelStartupService { } //Business Controller - public async fetchCatalog(instanceName: string, data: getCollectionsDto) { + public async fetchCatalog(instanceName: string, data: getCatalogDto) { + // === Provider routing: browser fallback for full catalog === + if (data.provider === 'browser') { + const jid = data.number ? createJid(data.number) : this.client?.user?.id; + return BrowserCatalogService.fetchCatalogOrThrow({ + jid, + instanceName, + pageSize: Number(data.limit) || 50, + }); + } + // === End provider routing === + const jid = data.number ? createJid(data.number) : this.client?.user?.id; - const limit = data.limit || 10; - const cursor = null; + const limit = Number(data.limit) || 50; + // Tetap hormati cursor dari caller (untuk resume manual jika diperlukan) + let cursor = data.cursor || null; const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4912,40 +4925,59 @@ export class BaileysStartupService extends ChannelStartupService { try { const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); - const business = await this.fetchBusinessProfile(info?.jid); - let catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); - let nextPageCursor = catalog.nextPageCursor; - let nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; - let pagination = nextPageCursorJson?.pagination_cursor - ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) - : null; - let fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; + let isBusiness = false; + try { + const business = await this.fetchBusinessProfile(info?.jid); + isBusiness = business?.isBusiness || false; + } catch (profileError) { + console.log('fetchBusinessProfile failed, continuing catalog fetch:', profileError?.message); + } - let productsCatalog = catalog.products || []; + let productsCatalog: Product[] = []; + let fetcherHasMore = true; let countLoops = 0; - while (fetcherHasMore && countLoops < 4) { - catalog = await this.getCatalog({ jid: info?.jid, limit, cursor: nextPageCursor }); - nextPageCursor = catalog.nextPageCursor; - nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; - pagination = nextPageCursorJson?.pagination_cursor + const MAX_LOOPS = 200; // ~10.000 produk @50/halaman — cukup besar untuk hampir semua kasus nyata + let truncated = false; + + while (fetcherHasMore) { + const catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); + + productsCatalog = [...productsCatalog, ...(catalog.products || [])]; + + const nextPageCursor = catalog.nextPageCursor; + const nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; + const pagination = nextPageCursorJson?.pagination_cursor ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) : null; - fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; - productsCatalog = [...productsCatalog, ...catalog.products]; + + fetcherHasMore = pagination?.fetcher_has_more === true; + cursor = nextPageCursor; countLoops++; + + // Safety valve: hindari infinite loop kalau WhatsApp API berkelakuan aneh + if (countLoops >= MAX_LOOPS) { + truncated = fetcherHasMore; // hanya benar2 "truncated" kalau memang masih ada sisa + this.logger.warn( + `fetchCatalog: mencapai MAX_LOOPS (${MAX_LOOPS}) untuk ${info?.jid}, ` + + `hasil mungkin belum lengkap. Gunakan 'cursor' di response untuk lanjut.`, + ); + break; + } } return { wuid: info?.jid || jid, numberExists: info?.exists, - isBusiness: business.isBusiness, + isBusiness, catalogLength: productsCatalog.length, catalog: productsCatalog, + truncated, // <-- penanda eksplisit ke caller + nextCursor: truncated ? cursor : null, // <-- supaya caller bisa lanjut manual }; } catch (error) { console.log(error); - return { wuid: jid, name: null, isBusiness: false }; + return { wuid: jid, name: null, isBusiness: false, catalog: [], truncated: true }; } } @@ -4970,8 +5002,33 @@ export class BaileysStartupService extends ChannelStartupService { } public async fetchCollections(instanceName: string, data: getCollectionsDto) { + // === Provider routing: pure browser (wa-js) === + // + // User instruction: "jangan hybrid, full research dari wa-js" + // Drop the Baileys protocol path entirely. Use wa-js (web.whatsapp.com + // webpack modules) for BOTH metadata AND product→collection mapping. + // + // Architecture (mirrors getCatalog's store-based approach): + // 1. WPP.catalog.getCollections(userId, limit, productsCount) → metadata + // 2. CatalogStore.findQuery(userId) → CatalogModel + // 3. catalogModel.collections → ProductCollCollection instance + // 4. collections.findCollectionProducts(collectionId, limit) → products + // 5. Fallback: CatalogStore.findCollectionMembership per product + // + // This is the same pattern that made getCatalog work (findNextProductPage + // loop on CatalogStore). Now applied to collections. + if (data.provider === 'browser') { + const jid = data.number ? createJid(data.number) : this.client?.user?.id; + return BrowserCatalogService.fetchCollectionsOrThrow({ + jid, + instanceName, + limit: Number(data.limit) || 100, + }); + } + // === End browser routing === + const jid = data.number ? createJid(data.number) : this.client?.user?.id; - const limit = data.limit <= 20 ? data.limit : 20; //(tem esse limite, não sei porque) + const limit = Number(data.limit) || 100; const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4981,14 +5038,22 @@ export class BaileysStartupService extends ChannelStartupService { try { const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); - const business = await this.fetchBusinessProfile(info?.jid); + + let isBusiness = false; + try { + const business = await this.fetchBusinessProfile(info?.jid); + isBusiness = business?.isBusiness || false; + } catch (profileError) { + console.log('fetchBusinessProfile failed, continuing collections fetch:', profileError?.message); + } + const collections = await this.getCollections(info?.jid, limit); return { wuid: info?.jid || jid, name: info?.name, numberExists: info?.exists, - isBusiness: business.isBusiness, + isBusiness: isBusiness, collectionsLength: collections?.length, collections: collections, }; diff --git a/src/api/routes/business.router.ts b/src/api/routes/business.router.ts index faca7b33f3..f2b105ec21 100644 --- a/src/api/routes/business.router.ts +++ b/src/api/routes/business.router.ts @@ -1,22 +1,63 @@ import { RouterBroker } from '@api/abstract/abstract.router'; -import { NumberDto } from '@api/dto/chat.dto'; +import { getCatalogDto, getCollectionsDto, requestPairingCodeDto } from '@api/dto/business.dto'; +import { InstanceDto } from '@api/dto/instance.dto'; import { businessController } from '@api/server.module'; import { createMetaErrorResponse } from '@utils/errorResponse'; -import { catalogSchema, collectionsSchema } from '@validate/validate.schema'; +import { catalogSchema, collectionsSchema, pairingCodeSchema } from '@validate/validate.schema'; import { RequestHandler, Router } from 'express'; import { HttpStatus } from './index.router'; +/** + * Business Router - Handles WhatsApp Business catalog operations + * @tags Business + */ export class BusinessRouter extends RouterBroker { constructor(...guards: RequestHandler[]) { super(); this.router + /** + * @swagger + * /business/getCatalog/{instanceName}: + * post: + * tags: [Business] + * summary: Get WhatsApp Business catalog + * description: Fetches all products from a WhatsApp Business catalog with automatic pagination. Use `provider: "browser"` to bypass Baileys protocol-level truncation (requires CATALOG_BROWSER_ENABLED=true and one-time QR/pairing auth). + * security: + * - apikey: [] + * parameters: + * - in: path + * name: instanceName + * required: true + * schema: + * type: string + * description: Instance name + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CatalogRequest' + * responses: + * 200: + * description: Catalog retrieved successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CatalogResponse' + * 400: + * description: Bad request + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ .post(this.routerPath('getCatalog'), ...guards, async (req, res) => { try { - const response = await this.dataValidate({ + const response = await this.dataValidate({ request: req, schema: catalogSchema, - ClassRef: NumberDto, + ClassRef: getCatalogDto, execute: (instance, data) => businessController.fetchCatalog(instance, data), }); @@ -31,24 +72,215 @@ export class BusinessRouter extends RouterBroker { } }) + /** + * @swagger + * /business/getCollections/{instanceName}: + * post: + * tags: [Business] + * summary: Get WhatsApp Business collections + * description: Fetches all collections with their products from a WhatsApp Business account. Use `provider: "browser"` to bypass Baileys protocol-level truncation. + * security: + * - apikey: [] + * parameters: + * - in: path + * name: instanceName + * required: true + * schema: + * type: string + * description: Instance name + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CollectionsRequest' + * responses: + * 200: + * description: Collections retrieved successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CollectionsResponse' + * 400: + * description: Bad request + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ .post(this.routerPath('getCollections'), ...guards, async (req, res) => { try { - const response = await this.dataValidate({ + const response = await this.dataValidate({ request: req, schema: collectionsSchema, - ClassRef: NumberDto, + ClassRef: getCollectionsDto, execute: (instance, data) => businessController.fetchCollections(instance, data), }); return res.status(HttpStatus.OK).json(response); } catch (error) { - // Log error for debugging console.error('Business collections error:', error); - - // Use utility function to create standardized error response const errorResponse = createMetaErrorResponse(error, 'business_collections'); return res.status(errorResponse.status).json(errorResponse); } + }) + + /** + * @swagger + * /business/requestPairingCode/{instanceName}: + * post: + * tags: [Business] + * summary: Request phone-number pairing code for browser catalog session + * description: Generates an 8-character pairing code that the user enters on their phone in WhatsApp → Linked Devices → Link a Device → Link with phone number instead. Requires CATALOG_BROWSER_ENABLED=true. + * security: + * - apikey: [] + * parameters: + * - in: path + * name: instanceName + * required: true + * schema: + * type: string + * description: Instance name + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [phone] + * properties: + * phone: + * type: string + * description: Phone number in international format, digits only (e.g. "6285733556953" for Indonesia) + * example: "6285733556953" + * responses: + * 200: + * description: Pairing code generated + * content: + * application/json: + * schema: + * type: object + * properties: + * instance: + * type: string + * phone: + * type: string + * pairingCode: + * type: string + * description: 8-character code (e.g. "ABCD1234") + * expiresIn: + * type: number + * description: Code validity in seconds (60) + * instructions: + * type: string + */ + .post(this.routerPath('requestPairingCode'), ...guards, async (req, res) => { + try { + const response = await this.dataValidate({ + request: req, + schema: pairingCodeSchema, + ClassRef: requestPairingCodeDto, + execute: (instance, data) => businessController.requestPairingCode(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + } catch (error) { + console.error('Pairing code error:', error); + const errorResponse = createMetaErrorResponse(error, 'pairing_code'); + return res.status(errorResponse.status).json(errorResponse); + } + }) + + /** + * @swagger + * /business/getAuthState/{instanceName}: + * get: + * tags: [Business] + * summary: Get browser catalog session auth state + * description: Returns the current authentication state of the browser-based catalog session (qrCode, pairingCode, ready). Use this to poll for auth status from the pairing UI. + * security: + * - apikey: [] + * parameters: + * - in: path + * name: instanceName + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Auth state + * content: + * application/json: + * schema: + * type: object + * properties: + * instance: + * type: string + * enabled: + * type: boolean + * ready: + * type: boolean + * description: True if browser session is authenticated and ready + * qrCode: + * type: string + * nullable: true + * description: QR code data URL (present when session needs QR scan) + * pairingCode: + * type: string + * nullable: true + * description: 8-character pairing code (present after requestPairingCode call) + */ + .get(this.routerPath('getAuthState'), ...guards, async (req, res) => { + try { + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => businessController.getAuthState(instance), + }); + + return res.status(HttpStatus.OK).json(response); + } catch (error) { + console.error('Auth state error:', error); + const errorResponse = createMetaErrorResponse(error, 'auth_state'); + return res.status(errorResponse.status).json(errorResponse); + } + }) + + /** + * @swagger + * /business/logoutBrowser/{instanceName}: + * delete: + * tags: [Business] + * summary: Logout browser catalog session + * description: Kills the browser session and deletes persisted auth data. Next catalog fetch will require new QR scan or pairing code. + * security: + * - apikey: [] + * parameters: + * - in: path + * name: instanceName + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Logged out + */ + .delete(this.routerPath('logoutBrowser'), ...guards, async (req, res) => { + try { + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => businessController.logoutBrowser(instance), + }); + + return res.status(HttpStatus.OK).json(response); + } catch (error) { + console.error('Logout browser error:', error); + const errorResponse = createMetaErrorResponse(error, 'logout_browser'); + return res.status(errorResponse.status).json(errorResponse); + } }); } diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 668b9e2722..6f17d919d8 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -17,6 +17,7 @@ import { ChannelController } from './integrations/channel/channel.controller'; import { EvolutionController } from './integrations/channel/evolution/evolution.controller'; import { MetaController } from './integrations/channel/meta/meta.controller'; import { BaileysController } from './integrations/channel/whatsapp/baileys.controller'; +import { BrowserCatalogService } from './integrations/channel/whatsapp/catalog-browser.service'; import { ChatbotController } from './integrations/chatbot/chatbot.controller'; import { ChatwootController } from './integrations/chatbot/chatwoot/controllers/chatwoot.controller'; import { ChatwootService } from './integrations/chatbot/chatwoot/services/chatwoot.service'; @@ -107,6 +108,11 @@ export const businessController = new BusinessController(waMonitor); export const groupController = new GroupController(waMonitor); export const labelController = new LabelController(waMonitor); +// Browser-based catalog service (singleton, lazy-initialized) +// Construction auto-registers itself with BrowserCatalogService.setInstance() +// so BaileysStartupService can access it via BrowserCatalogService.getInstance() +export const browserCatalogService = new BrowserCatalogService(); + export const eventManager = new EventManager(prismaRepository, waMonitor); export const chatbotController = new ChatbotController(prismaRepository, waMonitor); export const channelController = new ChannelController(prismaRepository, waMonitor); diff --git a/src/config/swagger.config.ts b/src/config/swagger.config.ts new file mode 100644 index 0000000000..47a4ea92b6 --- /dev/null +++ b/src/config/swagger.config.ts @@ -0,0 +1,232 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; +import swaggerJsdoc from 'swagger-jsdoc'; + +const packageJson = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8')); +const { version } = packageJson; + +const options: swaggerJsdoc.Options = { + definition: { + openapi: '3.0.0', + info: { + title: 'Evolution API', + version, + description: 'WhatsApp API - OpenAPI Documentation', + contact: { + name: 'Evolution API', + url: 'https://github.com/EvolutionAPI/evolution-api', + }, + }, + servers: [ + { + url: '/v1', + description: 'API v1', + }, + ], + components: { + securitySchemes: { + apikey: { + type: 'apiKey', + name: 'apikey', + in: 'header', + description: 'API key for authentication', + }, + }, + schemas: { + InstanceDto: { + type: 'object', + properties: { + instanceName: { + type: 'string', + description: 'Instance name', + }, + }, + }, + NumberDto: { + type: 'object', + properties: { + number: { + type: 'string', + description: 'Phone number (e.g., 5511999999999)', + }, + }, + }, + CatalogRequest: { + type: 'object', + properties: { + number: { + type: 'string', + description: 'Phone number of the business account', + }, + limit: { + type: 'number', + description: 'Number of products to fetch (default: 50)', + default: 50, + }, + cursor: { + type: 'string', + description: 'Pagination cursor for next page', + }, + }, + }, + CollectionsRequest: { + type: 'object', + properties: { + number: { + type: 'string', + description: 'Phone number of the business account', + }, + limit: { + type: 'number', + description: 'Number of collections to fetch (default: 100)', + default: 100, + }, + cursor: { + type: 'string', + description: 'Pagination cursor for next page', + }, + }, + }, + Product: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Product ID', + }, + name: { + type: 'string', + description: 'Product name', + }, + description: { + type: 'string', + description: 'Product description', + }, + price: { + type: 'number', + description: 'Product price', + }, + currency: { + type: 'string', + description: 'Currency code', + }, + availability: { + type: 'string', + enum: ['in stock', 'out of stock', 'preorder'], + description: 'Product availability status', + }, + image: { + type: 'array', + items: { + type: 'string', + }, + description: 'Product image URLs', + }, + }, + }, + CatalogCollection: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Collection ID', + }, + name: { + type: 'string', + description: 'Collection name', + }, + products: { + type: 'array', + items: { + $ref: '#/components/schemas/Product', + }, + }, + }, + }, + CatalogResponse: { + type: 'object', + properties: { + wuid: { + type: 'string', + description: 'WhatsApp user ID', + }, + numberExists: { + type: 'boolean', + description: 'Whether the number exists on WhatsApp', + }, + isBusiness: { + type: 'boolean', + description: 'Whether the account is a business account', + }, + catalogLength: { + type: 'number', + description: 'Total number of products fetched', + }, + catalog: { + type: 'array', + items: { + $ref: '#/components/schemas/Product', + }, + }, + }, + }, + CollectionsResponse: { + type: 'object', + properties: { + wuid: { + type: 'string', + description: 'WhatsApp user ID', + }, + name: { + type: 'string', + description: 'Business name', + }, + numberExists: { + type: 'boolean', + description: 'Whether the number exists on WhatsApp', + }, + isBusiness: { + type: 'boolean', + description: 'Whether the account is a business account', + }, + collectionsLength: { + type: 'number', + description: 'Total number of collections', + }, + collections: { + type: 'array', + items: { + $ref: '#/components/schemas/CatalogCollection', + }, + }, + }, + }, + ErrorResponse: { + type: 'object', + properties: { + status: { + type: 'number', + description: 'HTTP status code', + }, + error: { + type: 'string', + description: 'Error message', + }, + message: { + type: 'string', + description: 'Detailed error message', + }, + }, + }, + }, + }, + security: [ + { + apikey: [], + }, + ], + }, + apis: ['./src/api/routes/*.ts', './src/api/routes/**/*.ts'], +}; + +export const swaggerSpec = swaggerJsdoc(options); diff --git a/src/main.ts b/src/main.ts index f1f00ba9ae..a66202b922 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,7 @@ import { import { onUnexpectedError } from '@config/error.config'; import { Logger } from '@config/logger.config'; import { ROOT_DIR } from '@config/path.config'; +import { swaggerSpec } from '@config/swagger.config'; import * as Sentry from '@sentry/node'; import { ServerUP } from '@utils/server-up'; import axios from 'axios'; @@ -25,6 +26,7 @@ import compression from 'compression'; import cors from 'cors'; import express, { json, NextFunction, Request, Response, urlencoded } from 'express'; import { join } from 'path'; +import swaggerUi from 'swagger-ui-express'; async function initWA() { await waMonitor.loadInstance(); @@ -70,6 +72,22 @@ async function bootstrap() { app.use('/store', express.static(join(ROOT_DIR, 'store'))); + // Swagger UI + app.use( + '/docs', + swaggerUi.serve, + swaggerUi.setup(swaggerSpec, { + customCss: '.swagger-ui .topbar { display: none }', + customSiteTitle: 'Evolution API Documentation', + }), + ); + + // OpenAPI JSON endpoint + app.get('/openapi.json', (req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.send(swaggerSpec); + }); + app.use('/', router); app.use( diff --git a/src/validate/business.schema.ts b/src/validate/business.schema.ts index 91ad17b203..e13b30bab5 100644 --- a/src/validate/business.schema.ts +++ b/src/validate/business.schema.ts @@ -1,10 +1,22 @@ import { JSONSchema7 } from 'json-schema'; +const providerProperty: JSONSchema7 = { + type: 'string', + enum: ['baileys', 'browser'], + description: + "Fetch backend. 'baileys' (default) uses Baileys protocol-level API. " + + "'browser' launches a Puppeteer session that fetches via web.whatsapp.com — " + + 'returns full catalog without protocol-level truncation. ' + + 'Requires CATALOG_BROWSER_ENABLED=true and one-time QR/pairing auth per instance.', +}; + export const catalogSchema: JSONSchema7 = { type: 'object', properties: { number: { type: 'string' }, - limit: { type: 'number' }, + limit: { type: ['number', 'string'] }, + cursor: { type: 'string' }, + provider: providerProperty, }, }; @@ -12,6 +24,21 @@ export const collectionsSchema: JSONSchema7 = { type: 'object', properties: { number: { type: 'string' }, - limit: { type: 'number' }, + limit: { type: ['number', 'string'] }, + cursor: { type: 'string' }, + provider: providerProperty, + }, +}; + +export const pairingCodeSchema: JSONSchema7 = { + type: 'object', + required: ['phone'], + properties: { + phone: { + type: 'string', + pattern: '^[0-9]+$', + description: 'Phone number in international format, digits only (e.g. "6285733556953")', + examples: ['6285733556953'], + }, }, };