commit c58ed95370148dd5e66a19db411af4a2039d6446 Author: debudebuye Date: Thu Jan 1 15:22:54 2026 +0300 Initial commit: Yaltipia Telegram Bot with API integration diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..04d2e5f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,50 @@ +# Node modules +node_modules +npm-debug.log* + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production + +# Git +.git +.gitignore + +# Documentation +*.md +docs/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# Docker files +Dockerfile* +docker-compose* +.dockerignore + +# Temporary files +tmp/ +temp/ \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..043c697 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +TELEGRAM_BOT_TOKEN=your_bot_token_here +API_BASE_URL=your_backend_api_url_here +WEBSITE_URL=https://yaltipia.com/listings \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c336e81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Dependencies +/node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Docker files (keep in git but ignore in docker) +# Dockerfile* +# docker-compose* +# .dockerignore + +# Documentation (optional - remove if you want to track them) +BACKEND_API_INTEGRATION.md +ERROR_HANDLING_IMPROVEMENTS.md +WEBSITE_INTEGRATION.md +SECURITY_AUDIT_REPORT.md + +# Temporary files +tmp/ +temp/ + +# User data (will be mounted as volume in Docker) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7b24bf3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Use official Node.js runtime as base image +FROM node:18-alpine + +# Set working directory in container +WORKDIR /app + +# Copy package files first (for better caching) +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY src/ ./src/ + +# Create data directory for user storage +RUN mkdir -p ./src/data + +# Create non-root user for security +RUN addgroup -g 1001 -S nodejs +RUN adduser -S botuser -u 1001 + +# Change ownership of app directory +RUN chown -R botuser:nodejs /app +USER botuser + +# Expose port (if needed for health checks) +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "console.log('Bot is running')" || exit 1 + +# Start the bot +CMD ["node", "src/bot.js"] \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..0488353 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,34 @@ +# Development Dockerfile with hot reload +FROM node:18-alpine + +# Set working directory +WORKDIR /app + +# Install nodemon globally for development +RUN npm install -g nodemon + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including dev dependencies) +RUN npm install + +# Copy source code +COPY src/ ./src/ + +# Create data directory +RUN mkdir -p ./src/data + +# Create non-root user +RUN addgroup -g 1001 -S nodejs +RUN adduser -S botuser -u 1001 + +# Change ownership +RUN chown -R botuser:nodejs /app +USER botuser + +# Expose debug port +EXPOSE 9229 + +# Start with nodemon for hot reload +CMD ["nodemon", "--inspect=0.0.0.0:9229", "src/bot.js"] \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..b2b1992 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,31 @@ +version: '3.8' + +services: + yaltipia-telegram-bot-dev: + build: + context: . + dockerfile: Dockerfile.dev + container_name: yaltipia-telegram-bot-dev + restart: unless-stopped + environment: + - NODE_ENV=development + env_file: + - .env + volumes: + # Mount source code for development (hot reload) + - ./src:/app/src + - ./package.json:/app/package.json + - ./package-lock.json:/app/package-lock.json + # Mount logs directory + - ./logs:/app/logs + # Exclude node_modules from host + - /app/node_modules + networks: + - yaltipia-dev-network + ports: + # Optional: expose port for debugging + - "9229:9229" + +networks: + yaltipia-dev-network: + driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c8de0d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +version: '3.8' + +services: + yaltipia-telegram-bot: + build: . + container_name: yaltipia-telegram-bot + restart: unless-stopped + environment: + - NODE_ENV=production + env_file: + - .env + volumes: + # Persist user data + - ./src/data:/app/src/data + # Mount logs directory (optional) + - ./logs:/app/logs + networks: + - yaltipia-network + depends_on: + - backend-api + healthcheck: + test: ["CMD", "node", "-e", "console.log('Bot is running')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Optional: If you want to run your backend API in the same compose + backend-api: + # Replace with your backend image or build context + image: your-backend-api:latest + container_name: yaltipia-backend-api + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + networks: + - yaltipia-network + # Add your backend-specific configuration here + +networks: + yaltipia-network: + driver: bridge \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c8b97fb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3356 @@ +{ + "name": "yaltipia-telegram-bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yaltipia-telegram-bot", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0", + "bcrypt": "^5.1.1", + "crypto": "^1.0.1", + "dotenv": "^16.3.1", + "node-telegram-bot-api": "^0.67.0", + "validator": "^13.11.0" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request-promise": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@cypress/request-promise/-/request-promise-5.0.0.tgz", + "integrity": "sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==", + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "@cypress/request": "^3.0.0" + } + }, + "node_modules/@cypress/request-promise/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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.prototype.findindex": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.2.4.tgz", + "integrity": "sha512-LLm4mhxa9v8j0A/RPnpQAP4svXToJFh+Hp1pNYl5ZD5qpB4zdx/D4YjpVcETkhFbUKWO3iGMVLvrOnnmkAJT6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "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/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "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" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "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" + }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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" + }, + "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/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "peer": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT", + "peer": true + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT", + "peer": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-telegram-bot-api": { + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.67.0.tgz", + "integrity": "sha512-gO7o/dPcdFSUuFuPiAYBxV7bcN+vQL3pfaHN8P4z8fPtFstN05xVmhMFOth57DANGDKBpzW3rMRo7debOlUI1w==", + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.8", + "@cypress/request-promise": "^5.0.0", + "array.prototype.findindex": "^2.0.2", + "bl": "^1.2.3", + "debug": "^3.2.7", + "eventemitter3": "^3.0.0", + "file-type": "^3.9.0", + "mime": "^1.6.0", + "pump": "^2.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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" + }, + "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/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "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", + "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/readable-stream/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" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "license": "ISC", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "license": "ISC", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/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" + }, + "node_modules/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", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, + "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/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fc046ed --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "yaltipia-telegram-bot", + "version": "1.0.0", + "description": "Telegram bot for Yaltipia home clients to manage property notifications", + "main": "src/bot.js", + "scripts": { + "start": "node src/bot.js", + "dev": "nodemon src/bot.js" + }, + "dependencies": { + "axios": "^1.6.0", + "bcrypt": "^5.1.1", + "crypto": "^1.0.1", + "dotenv": "^16.3.1", + "node-telegram-bot-api": "^0.67.0", + "validator": "^13.11.0" + }, + "devDependencies": { + "nodemon": "^3.0.2" + }, + "keywords": [ + "telegram", + "bot", + "real-estate", + "notifications" + ], + "author": "Yaltipia", + "license": "MIT" +} diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh new file mode 100644 index 0000000..bae71bf --- /dev/null +++ b/scripts/docker-build.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Docker build script for Yaltipia Telegram Bot + +echo "🐳 Building Yaltipia Telegram Bot Docker image..." + +# Build production image +docker build -t yaltipia-telegram-bot:latest . + +# Build development image +docker build -f Dockerfile.dev -t yaltipia-telegram-bot:dev . + +echo "āœ… Docker images built successfully!" +echo "" +echo "Available images:" +docker images | grep yaltipia-telegram-bot + +echo "" +echo "šŸš€ To run the bot:" +echo "Production: docker-compose up -d" +echo "Development: docker-compose -f docker-compose.dev.yml up -d" \ No newline at end of file diff --git a/scripts/docker-run.sh b/scripts/docker-run.sh new file mode 100644 index 0000000..e69de29 diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..97decc6 --- /dev/null +++ b/src/api.js @@ -0,0 +1,465 @@ +const axios = require('axios'); +const ErrorHandler = require('./utils/errorHandler'); + +class ApiClient { + constructor() { + this.baseURL = process.env.API_BASE_URL || 'http://localhost:3000/api'; + this.client = axios.create({ + baseURL: this.baseURL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } + }); + + // Store user tokens for authenticated requests + this.userTokens = new Map(); // telegramId -> token + } + + // Set authentication token for a user + setUserToken(telegramId, token) { + if (token === null || token === undefined) { + this.userTokens.delete(telegramId); + } else { + this.userTokens.set(telegramId, token); + } + } + + // Get authentication token for a user + getUserToken(telegramId) { + return this.userTokens.get(telegramId); + } + + // Create authenticated headers for a user + getAuthHeaders(telegramId) { + const token = this.getUserToken(telegramId); + if (token) { + return { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }; + } + return { + 'Content-Type': 'application/json' + }; + } + + async registerUser(userData, telegramUserId) { + try { + console.log('Attempting telegram registration with data:', { + name: userData.name, + email: userData.email, + phone: userData.phone, + telegramUserId: telegramUserId + }); + + if (!userData.email) { + return { + success: false, + error: 'Email is required for registration. Please provide an email address.' + }; + } + + const response = await this.client.post('/telegram-auth/telegram-register', { + name: userData.name, + email: userData.email, + phone: userData.phone, + password: userData.password, + telegramUserId: telegramUserId.toString() + }); + + console.log('Telegram registration successful for:', userData.phone); + console.log('Registration response structure:', Object.keys(response.data)); + + // Handle different possible response structures + const user = response.data.user || response.data.data || response.data; + const token = response.data.token || response.data.accessToken || response.data.access_token; + + if (token) { + console.log('Token received from registration'); + } else { + console.log('No token in registration response'); + } + + return { + success: true, + data: response.data, + user: user, + token: token + }; + } catch (error) { + ErrorHandler.logError(error, 'telegram_registration'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'register') + }; + } + } + + async loginUser(phone, password, telegramUserId) { + try { + console.log('Attempting telegram login with phone:', phone); + + const response = await this.client.post('/telegram-auth/telegram-login', { + phone: phone, + password: password, + telegramUserId: telegramUserId.toString() + }); + + console.log('Telegram login successful for phone:', phone); + console.log('Login response structure:', Object.keys(response.data)); + + // Handle different possible response structures + const user = response.data.user || response.data.data || response.data; + const token = response.data.token || response.data.accessToken || response.data.access_token; + + if (token) { + console.log('Token received from login'); + } else { + console.log('No token in login response'); + } + + return { + success: true, + data: response.data, + user: user, + token: token + }; + } catch (error) { + ErrorHandler.logError(error, 'telegram_login'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'login') + }; + } + } + + async getUserByPhone(phone) { + try { + console.log('Checking user existence for phone:', phone); + + // Use the new telegram-auth endpoint to check phone existence + const response = await this.client.get(`/telegram-auth/phone/${encodeURIComponent(phone)}/check`); + + console.log('Phone check response:', response.data); + + // Check for hasAccount instead of exists (based on actual API response) + if (response.data.hasAccount === true || response.data.flow === 'login') { + console.log('User exists in database'); + + return { + success: true, + user: { + phone: phone, + name: response.data.user?.name || 'User', + email: response.data.user?.email, + id: response.data.user?.id + } + }; + } else if (response.data.hasAccount === false || response.data.flow === 'register') { + console.log('User does not exist in database'); + return { + success: false, + error: 'User not found' + }; + } else { + // Fallback for unexpected response format + console.log('Unexpected response format, treating as user not found'); + return { + success: false, + error: 'User not found' + }; + } + } catch (error) { + ErrorHandler.logError(error, 'phone_check'); + + if (error.response?.status === 404) { + return { + success: false, + error: 'User not found' + }; + } + + if (error.response?.status === 429) { + return { + success: false, + error: 'Too many requests. Please try again later.' + }; + } + + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'phone_check') + }; + } + } + + // Alternative method to check user existence by attempting login with a dummy password + async checkUserExistsByLogin(phone) { + try { + console.log('Checking user existence by attempting login for phone:', phone); + + // Try login with a dummy password to see if user exists + const response = await this.client.post('/auth/login', { + identifier: phone, + password: 'dummy_password_to_check_existence' + }); + + // If we get here, user exists but password was wrong (shouldn't happen) + return { + success: true, + userExists: true + }; + } catch (error) { + if (error.response?.status === 401) { + // Unauthorized - user exists but password is wrong + console.log('User exists (got 401 unauthorized)'); + return { + success: true, + userExists: true + }; + } else if (error.response?.status === 404 || + (error.response?.data?.message && + error.response.data.message.toLowerCase().includes('user not found'))) { + // User not found + console.log('User does not exist'); + return { + success: true, + userExists: false + }; + } + + console.log('Could not determine user existence:', error.response?.data || error.message); + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + async validateEmail(email) { + try { + console.log('Validating email:', email); + + const response = await this.client.get(`/telegram-auth/validate-email/${encodeURIComponent(email)}`); + + console.log('Email validation response:', response.data); + + return { + success: true, + valid: response.data.valid, + available: response.data.available, + message: response.data.message + }; + } catch (error) { + ErrorHandler.logError(error, 'email_validation'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'email_validation') + }; + } + } + + async createNotification(telegramId, userId, notificationData) { + try { + console.log('Creating notification via API for user:', userId); + + const response = await this.client.post('/telegram-notifications', { + name: notificationData.name, + type: notificationData.type, + status: notificationData.status, + subcity: notificationData.subcity, + houseType: notificationData.houseType, + minPrice: notificationData.minPrice, + maxPrice: notificationData.maxPrice, + telegramUserId: telegramId.toString() + }, { + headers: this.getAuthHeaders(telegramId) + }); + + console.log('Notification created successfully'); + + return { + success: true, + data: response.data + }; + } catch (error) { + ErrorHandler.logError(error, 'create_notification'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'create_notification') + }; + } + } + + async getUserNotifications(telegramId, userId) { + try { + console.log('Getting user notifications via API for user:', userId); + + const response = await this.client.get('/telegram-notifications/my-notifications', { + headers: this.getAuthHeaders(telegramId) + }); + + console.log('Retrieved notifications successfully'); + console.log('API response structure:', Object.keys(response.data)); + + // Handle different possible response structures + let notifications = []; + if (response.data.notifications) { + notifications = response.data.notifications; + } else if (response.data.data && Array.isArray(response.data.data)) { + notifications = response.data.data; + } else if (Array.isArray(response.data)) { + notifications = response.data; + } + + console.log('Parsed notifications count:', notifications.length); + + return { + success: true, + notifications: notifications + }; + } catch (error) { + ErrorHandler.logError(error, 'get_notifications'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'get_notifications') + }; + } + } + + async getNotificationMatches(telegramId, notificationId) { + try { + console.log('Getting notification matches for notification:', notificationId); + + const response = await this.client.get(`/telegram-notifications/${notificationId}/matches`, { + headers: this.getAuthHeaders(telegramId) + }); + + console.log('Retrieved matches successfully'); + + return { + success: true, + listings: response.data + }; + } catch (error) { + ErrorHandler.logError(error, 'get_matches'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'get_matches') + }; + } + } + + async deleteNotification(telegramId, notificationId) { + try { + console.log('Deleting notification:', notificationId); + + await this.client.delete(`/telegram-notifications/${notificationId}`, { + headers: this.getAuthHeaders(telegramId) + }); + + console.log('Notification deleted successfully'); + + return { + success: true + }; + } catch (error) { + ErrorHandler.logError(error, 'delete_notification'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'delete_notification') + }; + } + } + + async getNotificationsByTelegramId(telegramUserId) { + try { + console.log('Getting notifications by telegram ID:', telegramUserId); + + // Use the public endpoint that doesn't require authentication + const response = await this.client.get(`/telegram-notifications/telegram/${telegramUserId}`); + + console.log('Retrieved notifications by telegram ID successfully'); + + return { + success: true, + notifications: response.data + }; + } catch (error) { + ErrorHandler.logError(error, 'get_notifications_by_telegram_id'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'get_notifications') + }; + } + } + + async getListings(telegramId = null, filters = {}) { + try { + const params = new URLSearchParams(); + + // Add filters to query parameters (only if they have values) + if (filters.type) params.append('type', filters.type); + if (filters.status) params.append('status', filters.status); + if (filters.minPrice && filters.minPrice > 0) params.append('minPrice', filters.minPrice); + if (filters.maxPrice && filters.maxPrice > 0) params.append('maxPrice', filters.maxPrice); + if (filters.subcity && filters.subcity.toLowerCase() !== 'any') params.append('subcity', filters.subcity); + if (filters.houseType && filters.houseType.toLowerCase() !== 'any') params.append('houseType', filters.houseType); + + // Use auth headers if telegramId is provided + const config = telegramId ? { headers: this.getAuthHeaders(telegramId) } : {}; + + console.log('API call with filters:', Object.fromEntries(params)); + + const response = await this.client.get(`/listings?${params.toString()}`, config); + + return { + success: true, + listings: response.data + }; + } catch (error) { + ErrorHandler.logError(error, 'get_listings'); + return { + success: false, + error: ErrorHandler.getUserFriendlyMessage(error, 'get_listings') + }; + } + } + + async checkMatchingListings(telegramId, notificationFilters) { + // Use the regular getListings method for matching + return this.getListings(telegramId, notificationFilters); + } + + async updateNotification(telegramId, notificationId, updateData) { + try { + console.log('Updating notification via API:', notificationId, updateData); + + // Use PATCH for partial updates (more semantically correct) + const response = await this.client.patch(`/telegram-notifications/${notificationId}`, updateData, { + headers: this.getAuthHeaders(telegramId) + }); + + console.log('Notification updated successfully'); + + return { + success: true, + data: response.data + }; + } catch (error) { + console.error('Update notification error:', error.response?.data || error.message); + return { + success: false, + error: error.response?.data?.message || error.message + }; + } + } + + // Generate a random password for users (since they register via Telegram) + generatePassword() { + return Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8); + } +} + +module.exports = ApiClient; \ No newline at end of file diff --git a/src/bot.js b/src/bot.js new file mode 100644 index 0000000..2466ae0 --- /dev/null +++ b/src/bot.js @@ -0,0 +1,299 @@ +require('dotenv').config(); +const TelegramBot = require('node-telegram-bot-api'); +const ApiClient = require('./api'); +const NotificationService = require('./services/notificationService'); +const ErrorHandler = require('./utils/errorHandler'); + +// Import feature modules +const AuthFeature = require('./features/auth'); +const NotificationFeature = require('./features/notifications'); +const SearchFeature = require('./features/search'); +const MenuFeature = require('./features/menu'); + +class YaltipiaBot { + constructor() { + // Configure bot with better error handling + this.bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { + polling: { + interval: 1000, + autoStart: true, + params: { + timeout: 10 + } + } + }); + + this.api = new ApiClient(); + this.notificationService = new NotificationService(this.api); + this.userStates = new Map(); // Store user conversation states + this.userSessions = new Map(); // Store user session data + + console.log('Bot initialized with notification service:', !!this.notificationService); + + // Initialize features + this.auth = new AuthFeature(this.bot, this.api, this.userStates, this.userSessions, this.notificationService); + this.notifications = new NotificationFeature(this.bot, this.api, this.userStates, this.userSessions, this.notificationService); + this.search = new SearchFeature(this.bot, this.api, this.userStates, this.userSessions); + this.menu = new MenuFeature(this.bot, this.userStates); + + console.log('Features initialized. Notification feature has service:', !!this.notifications.notificationService); + + this.setupHandlers(); + this.setupErrorHandlers(); + } + + setupErrorHandlers() { + // Handle polling errors + this.bot.on('polling_error', (error) => { + console.error('Polling error:', error.message); + + // Don't restart on network errors, just log them + if (error.code === 'EFATAL' || error.message.includes('ECONNRESET') || error.message.includes('ETIMEDOUT')) { + console.log('Network error detected, continuing...'); + return; + } + + // For other errors, you might want to restart + console.error('Critical polling error:', error); + }); + + // Handle webhook errors + this.bot.on('webhook_error', (error) => { + console.error('Webhook error:', error); + }); + + // Handle unhandled promise rejections + process.on('unhandledRejection', (reason, promise) => { + ErrorHandler.logError(reason, 'unhandled_rejection'); + // Don't exit the process, just log the error + }); + + // Handle uncaught exceptions + process.on('uncaughtException', (error) => { + ErrorHandler.logError(error, 'uncaught_exception'); + console.error('Critical error occurred, but continuing...'); + }); + } + + setupHandlers() { + // Start command + this.bot.onText(/\/start/, (msg) => { + this.auth.handleStart(msg); + }); + + // Logout command + this.bot.onText(/\/logout/, (msg) => { + this.auth.handleLogout(msg); + }); + + // Login command + this.bot.onText(/\/login/, (msg) => { + this.auth.handleLogin(msg); + }); + + // Session status command (for debugging) + this.bot.onText(/\/session/, (msg) => { + const telegramId = msg.from.id; + const chatId = msg.chat.id; + const sessionInfo = this.auth.getSessionInfo(telegramId); + + if (sessionInfo.exists) { + this.bot.sendMessage(chatId, + `šŸ” Session Status:\n\n` + + `āœ… Logged in: Yes\n` + + `šŸ‘¤ User: ${sessionInfo.userName}\n` + + `šŸ“± Phone: ${sessionInfo.phone}\n` + + `šŸ• Login time: ${sessionInfo.loginTime}\n` + + `šŸ†” User ID: ${sessionInfo.userId}` + ); + } else { + this.bot.sendMessage(chatId, + `šŸ” Session Status:\n\n` + + `āŒ Logged in: No\n\n` + + `Use /start to login or register.` + ); + } + }); + + // Handle contact sharing + this.bot.on('contact', (msg) => { + this.auth.handleContact(msg); + }); + + // Handle text messages + this.bot.on('message', (msg) => { + if (msg.text && !msg.text.startsWith('/') && !msg.contact) { + this.handleTextMessage(msg); + } + }); + + // Handle callback queries (inline keyboard buttons) + this.bot.on('callback_query', (query) => { + this.handleCallbackQuery(query); + }); + } + + async handleTextMessage(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + + try { + // Try each feature's text handler in order + const handled = + await this.auth.handleRegistrationText(msg) || + await this.notifications.handleNotificationText(msg) || + await this.notifications.handleEditText(msg) || // Add edit text handler + await this.search.handleSearchText(msg); + + if (!handled) { + // If no feature handled the message and user is authenticated, show menu + if (this.auth.isAuthenticated(telegramId)) { + this.menu.showMainMenu(chatId); + } else { + this.bot.sendMessage(chatId, 'Please start with /start to register or login.'); + } + } + } catch (error) { + ErrorHandler.logError(error, 'text_message_handler'); + this.bot.sendMessage(chatId, + 'āŒ Something went wrong. Please try again or use /start to restart.' + ); + } + } + + async handleCallbackQuery(query) { + const chatId = query.message.chat.id; + const telegramId = query.from.id; + const data = query.data; + + try { + let handled = false; + + // Route callback queries to appropriate features + switch (data) { + // Auth callbacks + case 'skip_email': + handled = await this.auth.handleSkipEmail(chatId, telegramId); + break; + + // Menu callbacks + case 'create_notification': + await this.notifications.startNotificationCreation(chatId, telegramId); + handled = true; + break; + + case 'view_notifications': + await this.notifications.showUserNotifications(chatId, telegramId); + handled = true; + break; + + case 'back_to_menu': + handled = await this.menu.handleBackToMenu(chatId, telegramId); + break; + + case 'logout': + handled = await this.auth.handleLogout({ chat: { id: chatId }, from: { id: telegramId } }); + break; + + // Notification callbacks + case 'preview_matches': + handled = await this.notifications.previewMatches(chatId, telegramId); + break; + + case 'save_notification': + handled = await this.notifications.saveNotification(chatId, telegramId); + if (handled) { + this.menu.showMainMenu(chatId); + } + break; + + // Dynamic callbacks (type_, status_, edit_, delete_) + default: + if (data.startsWith('type_')) { + const type = data.replace('type_', ''); + handled = + await this.notifications.setNotificationType(chatId, telegramId, type) || + await this.search.setSearchType(chatId, telegramId, type); + } else if (data.startsWith('status_')) { + const status = data.replace('status_', ''); + handled = + await this.notifications.setNotificationStatus(chatId, telegramId, status) || + await this.search.setSearchStatus(chatId, telegramId, status); + } else if (data.startsWith('edit_notification_')) { + const notificationId = data.replace('edit_notification_', ''); + handled = await this.notifications.editNotification(chatId, telegramId, notificationId); + } else if (data.startsWith('delete_notification_')) { + const notificationId = data.replace('delete_notification_', ''); + handled = await this.notifications.deleteNotification(chatId, telegramId, notificationId); + } else if (data.startsWith('confirm_delete_')) { + const notificationId = data.replace('confirm_delete_', ''); + handled = await this.notifications.confirmDeleteNotification(chatId, telegramId, notificationId); + } else if (data.startsWith('edit_name_')) { + const notificationId = data.replace('edit_name_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'name'); + } else if (data.startsWith('edit_type_')) { + const notificationId = data.replace('edit_type_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'type'); + } else if (data.startsWith('edit_status_')) { + const notificationId = data.replace('edit_status_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'status'); + } else if (data.startsWith('edit_area_')) { + const notificationId = data.replace('edit_area_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'area'); + } else if (data.startsWith('edit_house_type_')) { + const notificationId = data.replace('edit_house_type_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'house_type'); + } else if (data.startsWith('edit_price_')) { + const notificationId = data.replace('edit_price_', ''); + handled = await this.notifications.startEditField(chatId, telegramId, notificationId, 'price'); + } else if (data.startsWith('update_type_')) { + const parts = data.replace('update_type_', '').split('_'); + const type = parts[0]; + const notificationId = parts[1]; + handled = await this.notifications.updateNotificationDirectly(chatId, telegramId, notificationId, { type }); + } else if (data.startsWith('update_status_')) { + const parts = data.replace('update_status_', '').split('_'); + const status = parts[0]; + const notificationId = parts[1]; + handled = await this.notifications.updateNotificationDirectly(chatId, telegramId, notificationId, { status }); + } + break; + } + + if (!handled) { + console.log(`Unhandled callback query: ${data}`); + // Try to show main menu as fallback + if (this.auth.isAuthenticated(telegramId)) { + this.menu.showMainMenu(chatId); + } + } + + this.bot.answerCallbackQuery(query.id); + } catch (error) { + ErrorHandler.logError(error, 'callback_query_handler'); + + // Send user-friendly error message + this.bot.sendMessage(chatId, + 'āŒ Something went wrong. Please try again or return to the main menu.' + ); + + // Try to show main menu as fallback + if (this.auth.isAuthenticated(telegramId)) { + this.menu.showMainMenu(chatId); + } + + this.bot.answerCallbackQuery(query.id, { text: 'Error occurred' }); + } + } +} + +// Start the bot +const bot = new YaltipiaBot(); + +console.log('šŸ¤– Yaltipia Telegram Bot is running...'); + +// Graceful shutdown +process.on('SIGINT', () => { + console.log('Shutting down bot...'); + process.exit(0); +}); \ No newline at end of file diff --git a/src/features/auth.js b/src/features/auth.js new file mode 100644 index 0000000..8f5f575 --- /dev/null +++ b/src/features/auth.js @@ -0,0 +1,592 @@ +class AuthFeature { + constructor(bot, api, userStates, userSessions, notificationService = null) { + this.bot = bot; + this.api = api; + this.userStates = userStates; + this.userSessions = userSessions; + this.notificationService = notificationService; + } + + async handleStart(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + + try { + // Check if user is already logged in + const existingSession = this.userSessions.get(telegramId); + if (existingSession && existingSession.user) { + this.showMainMenu(chatId); + return; + } + + // Always start with phone number request + this.userStates.set(telegramId, { step: 'waiting_phone' }); + + const keyboard = { + keyboard: [[{ + text: 'šŸ“± Share Phone Number', + request_contact: true + }]], + resize_keyboard: true, + one_time_keyboard: true + }; + + this.bot.sendMessage(chatId, + 'šŸ  Welcome to Yaltipia Home Bot!\n\n' + + 'To get started, please share your phone number by clicking the button below:', + { reply_markup: keyboard } + ); + } catch (error) { + console.error('Error in handleStart:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong. Please try again.'); + } + } + + async handleLogin(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + + try { + // Check if user is already logged in + const existingSession = this.userSessions.get(telegramId); + + if (existingSession && existingSession.user) { + this.bot.sendMessage(chatId, + `āœ… You are already logged in as ${existingSession.user.name || 'User'}!` + ); + this.showMainMenu(chatId); + return; + } + + // User not found - direct them to register + this.bot.sendMessage(chatId, + 'āŒ Please use /start to login with your phone number and password.' + ); + } catch (error) { + console.error('Error in handleLogin:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong. Please try again.'); + } + } + + async handleContact(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + const userState = this.userStates.get(telegramId); + + if (!userState || userState.step !== 'waiting_phone') { + return; + } + + const phoneNumber = msg.contact.phone_number; + console.log('Checking user existence for phone:', phoneNumber); + + try { + // Check if user exists in backend database + const existingUser = await this.api.getUserByPhone(phoneNumber); + + console.log('User existence check result:', existingUser); + + if (existingUser.success) { + // User exists in backend, ask for password to login + console.log('User found in database'); + + this.userStates.set(telegramId, { + step: 'waiting_password', + userData: { + phone: phoneNumber, + telegramId: telegramId, + // We might not have the name yet if using /exists endpoint + name: existingUser.user.name || 'User' + } + }); + + const welcomeMessage = existingUser.user.name && existingUser.user.name !== 'User' + ? `Welcome back, ${existingUser.user.name}!` + : 'Welcome back!'; + + this.bot.sendMessage(chatId, + `āœ… Phone number recognized!\n\n` + + `${welcomeMessage}\n` + + 'šŸ” Please enter your password to login:', + { reply_markup: { remove_keyboard: true } } + ); + return; + } + + // User not found in backend, continue with registration + console.log('User not found in database, proceeding with registration'); + + userState.phoneNumber = phoneNumber; + userState.step = 'waiting_name'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'āœ… Phone number received!\n\n' + + 'Now, please enter your full name:', + { reply_markup: { remove_keyboard: true } } + ); + } catch (error) { + console.error('Error checking existing user:', error); + + // Handle rate limiting specifically + if (error.response?.status === 429 || existingUser?.error?.includes('Too many requests')) { + console.log('Rate limit detected, asking user to try again later'); + this.bot.sendMessage(chatId, + 'ā³ Server is busy right now. Please wait a moment and try again.\n\n' + + 'Click /start to try again in a few seconds.', + { reply_markup: { remove_keyboard: true } } + ); + return; + } + + // For other errors, proceed with registration flow + // and handle conflicts during registration if user already exists + console.log('Error occurred, proceeding with registration flow'); + + userState.phoneNumber = phoneNumber; + userState.step = 'waiting_name'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'āœ… Phone number received!\n\n' + + 'Now, please enter your full name:', + { reply_markup: { remove_keyboard: true } } + ); + } + } + + async handleRegistrationText(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + const userState = this.userStates.get(telegramId); + + if (!userState) { + return false; // Not handling this message + } + + try { + switch (userState.step) { + case 'waiting_name': + userState.name = msg.text.trim(); + userState.step = 'waiting_email'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'āœ… Name received!\n\n' + + 'šŸ“§ Please enter your email address (required):' + ); + return true; + + case 'waiting_email': + const email = msg.text.trim(); + + // Validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + this.bot.sendMessage(chatId, + 'āŒ Please enter a valid email address:' + ); + return true; + } + + // Check if email already exists using the new validation endpoint + const emailCheck = await this.api.validateEmail(email); + if (emailCheck.success && !emailCheck.available) { + this.bot.sendMessage(chatId, + 'āŒ This email address is already registered.\n\n' + + 'This means you might already have an account. Please try one of these options:\n\n' + + '1ļøāƒ£ Enter a different email address\n' + + '2ļøāƒ£ Use /start to login with your existing account\n\n' + + 'Please enter a different email address or use /start to login:' + ); + return true; + } + + userState.email = email; + userState.step = 'waiting_password'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'āœ… Email received!\n\n' + + 'šŸ” Now, please create a password for your account:\n' + + '(Password should be at least 6 characters)' + ); + return true; + + case 'waiting_password': + const password = msg.text.trim(); + + // Check if this is for login or registration + if (userState.userData) { + // This is a login attempt + return await this.handlePasswordLogin(chatId, telegramId, password, userState); + } else { + // This is password creation during registration + if (password.length < 6) { + this.bot.sendMessage(chatId, + 'āŒ Password must be at least 6 characters long. Please try again:' + ); + return true; + } + + userState.password = password; + userState.step = 'waiting_password_confirm'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'šŸ” Please confirm your password by typing it again:' + ); + return true; + } + + case 'waiting_password_confirm': + const confirmPassword = msg.text.trim(); + + if (confirmPassword !== userState.password) { + this.bot.sendMessage(chatId, + 'āŒ Passwords do not match. Please enter your password again:' + ); + userState.step = 'waiting_password'; + this.userStates.set(telegramId, userState); + return true; + } + + await this.completeRegistration(chatId, telegramId, userState); + return true; + } + } catch (error) { + console.error('Error in handleRegistrationText:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong. Please try again.'); + } + + return false; + } + + async handlePasswordLogin(chatId, telegramId, password, userState) { + try { + const userData = userState.userData; + + // Login to backend with phone, password, and telegramUserId + const loginResult = await this.api.loginUser(userData.phone, password, telegramId); + + console.log('Login result success:', loginResult.success); + console.log('Login result token:', !!loginResult.token); + console.log('Login result user:', !!loginResult.user); + + if (loginResult.success) { + let token = loginResult.token; + let user = loginResult.user; + + if (!token) { + console.log('No token from login, but login was successful'); + // Continue without token - user is authenticated but may need token for some operations + } + + // Create session with proper structure + this.userSessions.set(telegramId, { + user: { + id: user.id, + name: user.name || userData.name || 'User', + email: user.email, + phone: userData.phone + }, + phoneNumber: userData.phone, + password: password, + loginTime: new Date().toISOString(), + telegramId: telegramId + }); + + // Store authentication token if we have one + if (token) { + this.api.setUserToken(telegramId, token); + } + + this.userStates.delete(telegramId); + + const userName = user.name || userData.name || 'User'; + this.bot.sendMessage(chatId, + `āœ… Welcome back, ${userName}!\n\n` + + 'You are now logged in.' + ); + + this.showMainMenu(chatId); + return true; + } else { + this.bot.sendMessage(chatId, + 'āŒ Incorrect password. Please try again:\n\n' + + 'šŸ’” Tip: Make sure you entered the correct password for your account.' + ); + return true; + } + } catch (error) { + console.error('Error in password login:', error); + + // Check if it's a 401 Unauthorized error (wrong password) + if (error.response && error.response.status === 401) { + this.bot.sendMessage(chatId, + 'āŒ Incorrect password. Please try again:\n\n' + + 'šŸ’” Tip: Make sure you entered the correct password for your account.' + ); + } else { + this.bot.sendMessage(chatId, + 'āŒ Login failed due to a server error. Please try again later.\n\n' + + 'If the problem persists, please contact support.' + ); + } + return true; + } + } + + async handleSkipEmail(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + if (userState && userState.step === 'waiting_email') { + // Since backend requires email, we can't skip it + this.bot.sendMessage(chatId, + 'āŒ Email is required for registration.\n\n' + + 'Please enter your email address:' + ); + return true; + } + return false; + } + + async completeRegistration(chatId, telegramId, userState) { + try { + // Register with backend, passing telegramUserId + const registrationResult = await this.api.registerUser({ + name: userState.name, + email: userState.email, + phone: userState.phoneNumber, + password: userState.password + }, telegramId); // Pass telegramUserId as second parameter + + if (!registrationResult.success) { + // Handle specific error cases + if (registrationResult.error.includes('email already exists')) { + this.bot.sendMessage(chatId, + `āŒ Registration failed: An account with this email already exists.\n\n` + + 'šŸ”„ Please try with a different email address.\n\n' + + 'Please enter a different email address:' + ); + + // Go back to email step + userState.step = 'waiting_email'; + this.userStates.set(telegramId, userState); + return; + } else if (registrationResult.error.includes('phone already exists') || + registrationResult.error.includes('phone number already exists') || + registrationResult.error.includes('User with this phone number already exists')) { + // This can happen if we couldn't check phone due to rate limiting + this.bot.sendMessage(chatId, + `āŒ Registration failed: This phone number is already registered.\n\n` + + 'šŸ” It seems you already have an account. Please use /start and enter your password when prompted.\n\n' + + 'If you forgot your password, please contact support.' + ); + this.userStates.delete(telegramId); + return; + } else { + // Other registration errors + this.bot.sendMessage(chatId, + `āŒ Registration failed: ${registrationResult.error}\n\n` + + 'Please try again with /start' + ); + this.userStates.delete(telegramId); + return; + } + } + + // Registration successful, but check if we got a token + let token = registrationResult.token; + let user = registrationResult.user; + + if (!token) { + console.log('No token from registration, attempting login to get token...'); + + // Try to login to get a token + const loginResult = await this.api.loginUser(userState.phoneNumber, userState.password, telegramId); + + if (loginResult.success && loginResult.token) { + token = loginResult.token; + user = loginResult.user; + console.log('Login after registration successful, token obtained'); + } else { + console.log('Login after registration failed:', loginResult.error); + // Continue without token - user is registered but may need to login manually later + } + } + + // Create session with proper structure + this.userSessions.set(telegramId, { + user: { + id: user.id || registrationResult.data?.id, + name: userState.name, + email: userState.email, + phone: userState.phoneNumber + }, + phoneNumber: userState.phoneNumber, + password: userState.password, + loginTime: new Date().toISOString(), + telegramId: telegramId + }); + + // Store authentication token if we have one + if (token) { + console.log('Storing token for user:', telegramId); + this.api.setUserToken(telegramId, token); + } else { + console.log('No token available - user may need to login later for authenticated requests'); + } + + this.userStates.delete(telegramId); + + this.bot.sendMessage(chatId, + 'šŸŽ‰ Registration completed successfully!\n\n' + + `Name: ${userState.name}\n` + + `Phone: ${userState.phoneNumber}\n` + + `Email: ${userState.email}\n\n` + + 'āœ… Your account has been created and you are now logged in!\n' + + 'You can now create property notifications!' + ); + + this.showMainMenu(chatId); + } catch (error) { + console.error('Error completing registration:', error); + this.bot.sendMessage(chatId, 'Sorry, registration failed due to a server error. Please try again with /start'); + } + } + + async handleLogout(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + + try { + // Check if user is logged in + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, + 'āŒ You are not logged in.\n\n' + + 'Use /start to register or login.' + ); + return true; + } + + const userName = userSession.user.name || 'User'; + + // Clear user session and token + this.userSessions.delete(telegramId); + this.userStates.delete(telegramId); + + // Remove authentication token + this.api.setUserToken(telegramId, null); + + // Clear user notifications if notification service is available + if (this.notificationService && userSession.user.id) { + this.notificationService.clearUserNotifications(userSession.user.id); + } + + this.bot.sendMessage(chatId, + `āœ… Goodbye ${userName}!\n\n` + + 'You have been logged out successfully.\n\n' + + 'Use /start to login again when you want to use the bot.' + ); + + return true; + } catch (error) { + console.error('Error in handleLogout:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong during logout. Please try again.'); + return false; + } + } + + showMainMenu(chatId, telegramId = null) { + // If telegramId is provided, validate session first + if (telegramId && !this.validateSession(telegramId)) { + this.bot.sendMessage(chatId, + 'āŒ Your session has expired. Please login again with /start' + ); + return; + } + + const websiteUrl = process.env.WEBSITE_URL || 'https://yaltipia.com/listings'; + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ”” Create Notification', callback_data: 'create_notification' }], + [{ text: 'šŸ“‹ View My Notifications', callback_data: 'view_notifications' }], + [{ text: '🌐 Browse All Listings', url: websiteUrl }], + [{ text: '🚪 Logout', callback_data: 'logout' }] + ] + }; + + this.bot.sendMessage(chatId, + 'šŸ  Yaltipia Home Bot - Main Menu\n\n' + + 'What would you like to do?', + { reply_markup: keyboard } + ); + } + + // Validate and refresh session if needed + validateSession(telegramId) { + const session = this.userSessions.get(telegramId); + + if (!session || !session.user || !session.user.id) { + return false; + } + + // Check if session is too old (optional - 24 hours) + if (session.loginTime) { + const loginTime = new Date(session.loginTime); + const now = new Date(); + const hoursDiff = (now - loginTime) / (1000 * 60 * 60); + + if (hoursDiff > 24) { + console.log('Session expired for user:', telegramId); + this.userSessions.delete(telegramId); + this.api.setUserToken(telegramId, null); + return false; + } + } + + return true; + } + + // Get session info for debugging + getSessionInfo(telegramId) { + const session = this.userSessions.get(telegramId); + if (!session) { + return { exists: false }; + } + + return { + exists: true, + hasUser: !!session.user, + userId: session.user?.id, + userName: session.user?.name, + phone: session.phoneNumber, + loginTime: session.loginTime + }; + } + + isAuthenticated(telegramId) { + return this.validateSession(telegramId); + } + + getUser(telegramId) { + if (!this.validateSession(telegramId)) { + return null; + } + const session = this.userSessions.get(telegramId); + return session ? session.user : null; + } + + // Generate a consistent password based on phone number + generateUserPassword(phoneNumber) { + // Create a consistent password based on phone number + // This ensures the same password is generated for the same phone number + const crypto = require('crypto'); + return crypto.createHash('md5').update(phoneNumber + 'yaltipia_salt').digest('hex').substring(0, 12); + } +} + +module.exports = AuthFeature; \ No newline at end of file diff --git a/src/features/menu.js b/src/features/menu.js new file mode 100644 index 0000000..80e8f35 --- /dev/null +++ b/src/features/menu.js @@ -0,0 +1,35 @@ +class MenuFeature { + constructor(bot, userStates) { + this.bot = bot; + this.userStates = userStates; + } + + showMainMenu(chatId, telegramId = null) { + // If telegramId is provided, we should validate session in auth feature + // For now, just show the menu + const websiteUrl = process.env.WEBSITE_URL || 'https://yaltipia.com/listings'; + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ”” Create Notification', callback_data: 'create_notification' }], + [{ text: 'šŸ“‹ View My Notifications', callback_data: 'view_notifications' }], + [{ text: '🌐 Browse All Listings', url: websiteUrl }], + [{ text: '🚪 Logout', callback_data: 'logout' }] + ] + }; + + this.bot.sendMessage(chatId, + 'šŸ  Yaltipia Home Bot - Main Menu\n\n' + + 'What would you like to do?', + { reply_markup: keyboard } + ); + } + + async handleBackToMenu(chatId, telegramId) { + this.userStates.delete(telegramId); + this.showMainMenu(chatId); + return true; + } +} + +module.exports = MenuFeature; \ No newline at end of file diff --git a/src/features/notifications.js b/src/features/notifications.js new file mode 100644 index 0000000..e52b058 --- /dev/null +++ b/src/features/notifications.js @@ -0,0 +1,736 @@ +class NotificationFeature { + constructor(bot, api, userStates, userSessions, notificationService) { + this.bot = bot; + this.api = api; + this.userStates = userStates; + this.userSessions = userSessions; + this.notificationService = notificationService; + } + + async startNotificationCreation(chatId, telegramId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Please register first by sending /start'); + return; + } + + const userState = this.userStates.get(telegramId) || {}; + userState.step = 'notification_name'; + userState.notificationData = {}; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'šŸ”” Create New Property Notification\n\n' + + 'Please enter a name for this notification (e.g., "Downtown Apartment"):' + ); + } + + async handleNotificationText(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + const userState = this.userStates.get(telegramId); + + if (!userState || !userState.step?.startsWith('notification_')) { + return false; + } + + try { + switch (userState.step) { + case 'notification_name': + userState.notificationData.name = msg.text.trim(); + this.askPropertyType(chatId, telegramId); + return true; + + case 'notification_subcity': + const subcityText = msg.text.trim().toLowerCase(); + if (subcityText === 'any' || subcityText === '') { + userState.notificationData.subcity = null; + } else { + userState.notificationData.subcity = msg.text.trim(); + } + this.askHouseType(chatId, telegramId); + return true; + + case 'notification_house_type': + const houseTypeText = msg.text.trim().toLowerCase(); + if (houseTypeText === 'any' || houseTypeText === '') { + userState.notificationData.houseType = null; + } else { + userState.notificationData.houseType = msg.text.trim(); + } + this.askMinPrice(chatId, telegramId); + return true; + + case 'notification_min_price': + const minPriceText = msg.text.trim().toLowerCase(); + if (minPriceText === 'skip' || minPriceText === '0' || minPriceText === '') { + userState.notificationData.minPrice = null; + } else { + const minPrice = parseInt(msg.text.trim()); + if (isNaN(minPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number, "0", or "skip":'); + return true; + } + userState.notificationData.minPrice = minPrice; + } + this.askMaxPrice(chatId, telegramId); + return true; + + case 'notification_max_price': + const maxPriceText = msg.text.trim().toLowerCase(); + if (maxPriceText === 'skip' || maxPriceText === '0' || maxPriceText === '') { + userState.notificationData.maxPrice = null; + } else { + const maxPrice = parseInt(msg.text.trim()); + if (isNaN(maxPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number, "0", or "skip":'); + return true; + } + userState.notificationData.maxPrice = maxPrice; + } + await this.showNotificationSummary(chatId, telegramId, userState); + return true; + } + } catch (error) { + console.error('Error in handleNotificationText:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong. Please try again.'); + } + + return false; + } + + askPropertyType(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'notification_type'; + this.userStates.set(telegramId, userState); + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ  Rent', callback_data: 'type_RENT' }], + [{ text: 'šŸ’° Sell', callback_data: 'type_SELL' }] + ] + }; + + this.bot.sendMessage(chatId, + 'What type of property are you looking for?', + { reply_markup: keyboard } + ); + } + + async setNotificationType(chatId, telegramId, type) { + const userState = this.userStates.get(telegramId); + if (!userState || userState.step !== 'notification_type') { + return false; + } + + userState.notificationData.type = type; + userState.step = 'notification_status'; + this.userStates.set(telegramId, userState); + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ“ Draft', callback_data: 'status_DRAFT' }], + [{ text: 'āœ… Active', callback_data: 'status_ACTIVE' }], + [{ text: 'šŸ  Rented', callback_data: 'status_RENTED' }], + [{ text: 'šŸ’° For Sale', callback_data: 'status_FOR_SALE' }], + [{ text: 'āœ”ļø Sold', callback_data: 'status_SOLD' }], + [{ text: 'āŒ Inactive', callback_data: 'status_INACTIVE' }] + ] + }; + + this.bot.sendMessage(chatId, + 'What status are you interested in?', + { reply_markup: keyboard } + ); + return true; + } + + async setNotificationStatus(chatId, telegramId, status) { + const userState = this.userStates.get(telegramId); + if (!userState || userState.step !== 'notification_status') { + return false; + } + + userState.notificationData.status = status; + userState.step = 'notification_subcity'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the subcity/area you\'re interested in:\n\n' + + 'šŸ’” Examples: Bole, Kirkos, Addis Ketema\n' + + 'šŸ’” Tip: Send "any" to match all areas' + ); + return true; + } + + askHouseType(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'notification_house_type'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the house type:\n\n' + + 'šŸ’” Examples: Apartment, Villa, Studio, Condominium\n' + + 'šŸ’” Tip: Send "any" to match all house types' + ); + } + + askMinPrice(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'notification_min_price'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the minimum price:\n\n' + + 'šŸ’” Tip: Leave empty (send "0" or "skip") for no minimum limit' + ); + } + + askMaxPrice(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'notification_max_price'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the maximum price:\n\n' + + 'šŸ’” Tip: Leave empty (send "0" or "skip") for no maximum limit' + ); + } + + async showNotificationSummary(chatId, telegramId, userState) { + const data = userState.notificationData; + + const summaryMessage = + 'šŸ“‹ Notification Summary\n\n' + + `šŸ“ Name: ${data.name}\n` + + `šŸ  Type: ${data.type}\n` + + `šŸ“Š Status: ${data.status}\n` + + `šŸ“ Area: ${data.subcity || 'Any area'}\n` + + `šŸ” House Type: ${data.houseType || 'Any type'}\n` + + `šŸ’° Price Range: ${data.minPrice || 'No min'} - ${data.maxPrice || 'No max'}\n\n` + + 'What would you like to do?'; + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ” Preview Matching Listings', callback_data: 'preview_matches' }], + [{ text: 'āœ… Save Notification', callback_data: 'save_notification' }], + [{ text: 'šŸ”™ Back to Main Menu', callback_data: 'back_to_menu' }] + ] + }; + + this.bot.sendMessage(chatId, summaryMessage, { reply_markup: keyboard }); + } + + async saveNotification(chatId, telegramId) { + console.log('saveNotification called for user:', telegramId); + + const userState = this.userStates.get(telegramId); + const userSession = this.userSessions.get(telegramId); + + console.log('User state exists:', !!userState); + console.log('User session exists:', !!userSession); + console.log('Notification service exists:', !!this.notificationService); + + if (!userState || !userState.notificationData) { + this.bot.sendMessage(chatId, 'No notification data found.'); + return false; + } + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Session expired. Please start again with /start'); + return false; + } + + try { + console.log('Creating notification with data:', userState.notificationData); + + // Use backend API instead of local service + const notificationResult = await this.notificationService.createNotification( + userSession.user.id, + userState.notificationData, + telegramId // Pass telegramId for API authentication + ); + + console.log('Notification result:', notificationResult); + + if (!notificationResult.success) { + this.bot.sendMessage(chatId, + `āŒ Failed to create notification: ${notificationResult.error}` + ); + return false; + } + + this.userStates.delete(telegramId); + + const data = userState.notificationData; + this.bot.sendMessage(chatId, + 'āœ… Notification created successfully!\n\n' + + `šŸ“ Name: ${data.name}\n` + + `šŸ  Type: ${data.type}\n` + + `šŸ“Š Status: ${data.status}\n` + + `šŸ“ Area: ${data.subcity || 'Any area'}\n` + + `šŸ” House Type: ${data.houseType || 'Any type'}\n` + + `šŸ’° Price Range: ${data.minPrice || 'No min'} - ${data.maxPrice || 'No max'}\n\n` + + 'You will receive notifications when matching properties are listed!' + ); + + return true; + } catch (error) { + console.error('Error saving notification:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to save notification. Please try again.'); + return false; + } + } + + async showUserNotifications(chatId, telegramId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Session expired. Please start again with /start'); + return; + } + + try { + // Use backend API instead of local service + const notificationsResult = await this.notificationService.getUserNotifications(userSession.user.id, telegramId); + + if (!notificationsResult.success) { + this.bot.sendMessage(chatId, + `āŒ Failed to load notifications: ${notificationsResult.error}` + ); + return; + } + + const notifications = notificationsResult.notifications; + + console.log('Notifications received:', notifications); + console.log('Is notifications an array?', Array.isArray(notifications)); + console.log('Notifications type:', typeof notifications); + + if (!notifications || !Array.isArray(notifications) || notifications.length === 0) { + this.bot.sendMessage(chatId, + 'šŸ“‹ You don\'t have any active notifications yet.\n\n' + + 'Create your first notification to start receiving property updates!' + ); + return; + } + + // Show notifications with management buttons + let message = 'šŸ“‹ Your Active Notifications:\n\n'; + + notifications.forEach((notif, index) => { + message += `${index + 1}. šŸ“ ${notif.name || notif.type}\n`; + message += ` šŸ“Š Status: ${notif.status}\n`; + message += ` šŸ“ Area: ${notif.subcity || 'Any'}\n`; + message += ` šŸ” Type: ${notif.houseType || 'Any'}\n`; + message += ` šŸ’° Price: ${notif.minPrice || 0} - ${notif.maxPrice || 'āˆž'}\n\n`; + }); + + // Create inline keyboard with management options for each notification + const keyboard = { + inline_keyboard: [] + }; + + // Add buttons for each notification + notifications.forEach((notif, index) => { + keyboard.inline_keyboard.push([ + { + text: `šŸ“ Edit "${notif.name}"`, + callback_data: `edit_notification_${notif.id}` + }, + { + text: `šŸ—‘ļø Delete "${notif.name}"`, + callback_data: `delete_notification_${notif.id}` + } + ]); + }); + + // Add general action buttons + keyboard.inline_keyboard.push([ + { text: 'šŸ”” Create New Notification', callback_data: 'create_notification' } + ]); + keyboard.inline_keyboard.push([ + { text: 'šŸ”™ Back to Main Menu', callback_data: 'back_to_menu' } + ]); + + this.bot.sendMessage(chatId, message, { reply_markup: keyboard }); + } catch (error) { + console.error('Error showing notifications:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to load notifications.'); + } + } + + async previewMatches(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + + if (!userState || !userState.notificationData) { + this.bot.sendMessage(chatId, 'No notification data found. Please create a notification first.'); + return false; + } + + try { + this.bot.sendMessage(chatId, 'šŸ” Checking for matching listings...'); + + // Use the notification service to check matches + const matchResult = await this.notificationService.checkMatches(telegramId, userState.notificationData); + + if (!matchResult.success) { + this.bot.sendMessage(chatId, + `āŒ Preview failed: ${matchResult.error}` + ); + return false; + } + + const matches = matchResult.listings; + + if (!matches || matches.length === 0) { + const keyboard = { + inline_keyboard: [ + [{ text: 'āœ… Save Notification Anyway', callback_data: 'save_notification' }], + [{ text: '🌐 Browse All Listings', url: process.env.WEBSITE_URL || 'https://yaltipia.com/listings' }], + [{ text: 'šŸ”™ Back to Main Menu', callback_data: 'back_to_menu' }] + ] + }; + + this.bot.sendMessage(chatId, + 'šŸ” No current listings match your criteria.\n\n' + + 'You can still save this notification to get alerts when matching properties are listed, or browse all listings on our website.', + { reply_markup: keyboard } + ); + return true; + } + + let message = `šŸŽÆ Found ${matches.length} matching listings!\n\n`; + + // Show first 3 matches + const displayMatches = matches.slice(0, 3); + + displayMatches.forEach((listing, index) => { + message += `${index + 1}. šŸ  ${listing.title || 'Property'}\n`; + message += ` šŸ“Š Status: ${listing.status}\n`; + message += ` šŸ“ Location: ${listing.subcity || 'N/A'}\n`; + message += ` šŸ” Type: ${listing.houseType || 'N/A'}\n`; + message += ` šŸ’° Price: ${listing.price || 'N/A'}\n\n`; + }); + + if (matches.length > 3) { + message += `... and ${matches.length - 3} more matches\n\n`; + } + + const keyboard = { + inline_keyboard: [ + [{ text: 'āœ… Save Notification', callback_data: 'save_notification' }], + [{ text: 'šŸ”™ Back to Main Menu', callback_data: 'back_to_menu' }] + ] + }; + + this.bot.sendMessage(chatId, message, { reply_markup: keyboard }); + return true; + + } catch (error) { + console.error('Error previewing matches:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to preview matches. Please try again.'); + return false; + } + } + + async deleteNotification(chatId, telegramId, notificationId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Session expired. Please start again with /start'); + return false; + } + + try { + // Show confirmation dialog + const keyboard = { + inline_keyboard: [ + [ + { text: 'āœ… Yes, Delete', callback_data: `confirm_delete_${notificationId}` }, + { text: 'āŒ Cancel', callback_data: 'view_notifications' } + ] + ] + }; + + this.bot.sendMessage(chatId, + 'āš ļø Are you sure you want to delete this notification?\n\n' + + 'This action cannot be undone.', + { reply_markup: keyboard } + ); + + return true; + } catch (error) { + console.error('Error showing delete confirmation:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to show delete confirmation.'); + return false; + } + } + + async confirmDeleteNotification(chatId, telegramId, notificationId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Session expired. Please start again with /start'); + return false; + } + + try { + console.log('Deleting notification:', notificationId); + + const deleteResult = await this.notificationService.deleteNotification(notificationId, telegramId); + + if (!deleteResult.success) { + this.bot.sendMessage(chatId, + `āŒ Failed to delete notification: ${deleteResult.error}` + ); + return false; + } + + this.bot.sendMessage(chatId, 'āœ… Notification deleted successfully!'); + + // Show updated notifications list + await this.showUserNotifications(chatId, telegramId); + + return true; + } catch (error) { + console.error('Error deleting notification:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to delete notification.'); + return false; + } + } + + async editNotification(chatId, telegramId, notificationId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Session expired. Please start again with /start'); + return false; + } + + try { + // Get the specific notification details first + // For now, we'll show edit options - you can implement getNotificationById API later + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ“ Edit Name', callback_data: `edit_name_${notificationId}` }], + [{ text: 'šŸ  Edit Type', callback_data: `edit_type_${notificationId}` }], + [{ text: 'šŸ“Š Edit Status', callback_data: `edit_status_${notificationId}` }], + [{ text: 'šŸ“ Edit Area', callback_data: `edit_area_${notificationId}` }], + [{ text: 'šŸ” Edit House Type', callback_data: `edit_house_type_${notificationId}` }], + [{ text: 'šŸ’° Edit Price Range', callback_data: `edit_price_${notificationId}` }], + [{ text: 'šŸ”™ Back to Notifications', callback_data: 'view_notifications' }] + ] + }; + + this.bot.sendMessage(chatId, + 'āœļø What would you like to edit?\n\n' + + 'Choose an option below:', + { reply_markup: keyboard } + ); + + return true; + } catch (error) { + console.error('Error showing edit options:', error); + this.bot.sendMessage(chatId, 'Sorry, failed to show edit options.'); + return false; + } + } + + async startEditField(chatId, telegramId, notificationId, field) { + const userState = this.userStates.get(telegramId) || {}; + userState.step = `edit_${field}`; + userState.editingNotificationId = notificationId; + userState.editingField = field; + this.userStates.set(telegramId, userState); + + let message = ''; + let keyboard = null; + + switch (field) { + case 'name': + message = 'šŸ“ Enter the new name for this notification:'; + break; + case 'type': + message = 'šŸ  Select the new property type:'; + keyboard = { + inline_keyboard: [ + [{ text: 'šŸ  Rent', callback_data: `update_type_RENT_${notificationId}` }], + [{ text: 'šŸ’° Sell', callback_data: `update_type_SELL_${notificationId}` }], + [{ text: 'šŸ”™ Back to Edit Menu', callback_data: `edit_notification_${notificationId}` }] + ] + }; + break; + case 'status': + message = 'šŸ“Š Select the new status:'; + keyboard = { + inline_keyboard: [ + [{ text: 'šŸ“ Draft', callback_data: `update_status_DRAFT_${notificationId}` }], + [{ text: 'āœ… Active', callback_data: `update_status_ACTIVE_${notificationId}` }], + [{ text: 'šŸ  Rented', callback_data: `update_status_RENTED_${notificationId}` }], + [{ text: 'šŸ’° For Sale', callback_data: `update_status_FOR_SALE_${notificationId}` }], + [{ text: 'āœ”ļø Sold', callback_data: `update_status_SOLD_${notificationId}` }], + [{ text: 'āŒ Inactive', callback_data: `update_status_INACTIVE_${notificationId}` }], + [{ text: 'šŸ”™ Back to Edit Menu', callback_data: `edit_notification_${notificationId}` }] + ] + }; + break; + case 'area': + message = 'šŸ“ Enter the new area/subcity:\n\nšŸ’” Tip: Send "any" to match all areas'; + break; + case 'house_type': + message = 'šŸ” Enter the new house type:\n\nšŸ’” Examples: Apartment, Villa, Studio\nšŸ’” Tip: Send "any" to match all types'; + break; + case 'price': + message = 'šŸ’° Enter the new minimum price:\n\nšŸ’” Tip: Send "0" or "skip" for no minimum'; + userState.step = 'edit_min_price'; + break; + default: + message = `Enter the new ${field}:`; + } + + if (keyboard) { + this.bot.sendMessage(chatId, message, { reply_markup: keyboard }); + } else { + this.bot.sendMessage(chatId, message); + } + return true; + } + + async updateNotificationDirectly(chatId, telegramId, notificationId, updateData) { + try { + console.log('Updating notification directly:', notificationId, updateData); + + const updateResult = await this.api.updateNotification(telegramId, notificationId, updateData); + + if (!updateResult.success) { + this.bot.sendMessage(chatId, + `āŒ Failed to update notification: ${updateResult.error}` + ); + return false; + } + + this.userStates.delete(telegramId); + + this.bot.sendMessage(chatId, 'āœ… Notification updated successfully!'); + + // Show updated notifications list + await this.showUserNotifications(chatId, telegramId); + + return true; + } catch (error) { + console.error('Error updating notification directly:', error); + this.bot.sendMessage(chatId, + `āŒ Failed to update notification: ${error.message}` + ); + return false; + } + } + + async handleEditText(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + const userState = this.userStates.get(telegramId); + + if (!userState || !userState.step?.startsWith('edit_')) { + return false; + } + + try { + const notificationId = userState.editingNotificationId; + const newValue = msg.text.trim(); + + // Handle different field types + let updateData = {}; + + switch (userState.step) { + case 'edit_name': + updateData.name = newValue; + await this.updateNotificationField(chatId, telegramId, notificationId, updateData); + return true; + + case 'edit_area': + updateData.subcity = newValue.toLowerCase() === 'any' ? null : newValue; + await this.updateNotificationField(chatId, telegramId, notificationId, updateData); + return true; + + case 'edit_house_type': + updateData.houseType = newValue.toLowerCase() === 'any' ? null : newValue; + await this.updateNotificationField(chatId, telegramId, notificationId, updateData); + return true; + + case 'edit_min_price': + const minPriceText = newValue.toLowerCase(); + if (minPriceText === 'skip' || minPriceText === '0') { + userState.editMinPrice = null; + } else { + const minPrice = parseInt(newValue); + if (isNaN(minPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number, "0", or "skip":'); + return true; + } + userState.editMinPrice = minPrice; + } + + userState.step = 'edit_max_price'; + this.bot.sendMessage(chatId, 'šŸ’° Enter the new maximum price:\n\nšŸ’” Tip: Send "0" or "skip" for no maximum'); + return true; + + case 'edit_max_price': + const maxPriceText = newValue.toLowerCase(); + let maxPrice = null; + + if (maxPriceText !== 'skip' && maxPriceText !== '0') { + maxPrice = parseInt(newValue); + if (isNaN(maxPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number, "0", or "skip":'); + return true; + } + } + + updateData.minPrice = userState.editMinPrice; + updateData.maxPrice = maxPrice; + await this.updateNotificationField(chatId, telegramId, notificationId, updateData); + return true; + } + + return false; + } catch (error) { + console.error('Error handling edit text:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong while editing.'); + return false; + } + } + + async updateNotificationField(chatId, telegramId, notificationId, updateData) { + try { + console.log('Updating notification field:', notificationId, updateData); + + // Use the API client method instead of direct axios call + const updateResult = await this.api.updateNotification(telegramId, notificationId, updateData); + + if (!updateResult.success) { + this.bot.sendMessage(chatId, + `āŒ Failed to update notification: ${updateResult.error}` + ); + return; + } + + this.userStates.delete(telegramId); + + this.bot.sendMessage(chatId, 'āœ… Notification updated successfully!'); + + // Show updated notifications list + await this.showUserNotifications(chatId, telegramId); + + } catch (error) { + console.error('Error updating notification:', error); + this.bot.sendMessage(chatId, + `āŒ Failed to update notification: ${error.message}` + ); + } + } +} + +module.exports = NotificationFeature; \ No newline at end of file diff --git a/src/features/search.js b/src/features/search.js new file mode 100644 index 0000000..e05201f --- /dev/null +++ b/src/features/search.js @@ -0,0 +1,241 @@ +class SearchFeature { + constructor(bot, api, userStates, userSessions) { + this.bot = bot; + this.api = api; + this.userStates = userStates; + this.userSessions = userSessions; + } + + async startListingSearch(chatId, telegramId) { + const userSession = this.userSessions.get(telegramId); + + if (!userSession || !userSession.user) { + this.bot.sendMessage(chatId, 'Please register first by sending /start'); + return; + } + + const userState = this.userStates.get(telegramId) || {}; + userState.step = 'search_name'; + userState.searchData = {}; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'šŸ” Search Current Listings\n\n' + + 'Please enter a name for this search (e.g., "Downtown Search"):' + ); + } + + async handleSearchText(msg) { + const chatId = msg.chat.id; + const telegramId = msg.from.id; + const userState = this.userStates.get(telegramId); + + if (!userState || !userState.step?.startsWith('search_')) { + return false; + } + + try { + switch (userState.step) { + case 'search_name': + userState.searchData.name = msg.text.trim(); + this.askSearchPropertyType(chatId, telegramId); + return true; + + case 'search_subcity': + userState.searchData.subcity = msg.text.trim(); + this.askSearchHouseType(chatId, telegramId); + return true; + + case 'search_house_type': + userState.searchData.houseType = msg.text.trim(); + this.askSearchMinPrice(chatId, telegramId); + return true; + + case 'search_min_price': + const searchMinPrice = parseInt(msg.text.trim()); + if (isNaN(searchMinPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number for minimum price:'); + return true; + } + userState.searchData.minPrice = searchMinPrice; + this.askSearchMaxPrice(chatId, telegramId); + return true; + + case 'search_max_price': + const searchMaxPrice = parseInt(msg.text.trim()); + if (isNaN(searchMaxPrice)) { + this.bot.sendMessage(chatId, 'Please enter a valid number for maximum price:'); + return true; + } + userState.searchData.maxPrice = searchMaxPrice; + await this.executeSearch(chatId, telegramId, userState); + return true; + } + } catch (error) { + console.error('Error in handleSearchText:', error); + this.bot.sendMessage(chatId, 'Sorry, something went wrong. Please try again.'); + } + + return false; + } + + askSearchPropertyType(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'search_type'; + this.userStates.set(telegramId, userState); + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ  Rent', callback_data: 'type_RENT' }], + [{ text: 'šŸ’° Sell', callback_data: 'type_SELL' }], + [{ text: 'šŸ”„ Any', callback_data: 'type_ANY' }] + ] + }; + + this.bot.sendMessage(chatId, + 'What type of property are you looking for?', + { reply_markup: keyboard } + ); + } + + async setSearchType(chatId, telegramId, type) { + const userState = this.userStates.get(telegramId); + if (!userState || userState.step !== 'search_type') { + return false; + } + + userState.searchData.type = type === 'ANY' ? null : type; + userState.step = 'search_status'; + this.userStates.set(telegramId, userState); + + const keyboard = { + inline_keyboard: [ + [{ text: 'šŸ“ Draft', callback_data: 'status_DRAFT' }], + [{ text: 'āœ… Active', callback_data: 'status_ACTIVE' }], + [{ text: 'šŸ  Rented', callback_data: 'status_RENTED' }], + [{ text: 'šŸ’° For Sale', callback_data: 'status_FOR_SALE' }], + [{ text: 'āœ”ļø Sold', callback_data: 'status_SOLD' }], + [{ text: 'āŒ Inactive', callback_data: 'status_INACTIVE' }], + [{ text: 'šŸ”„ Any', callback_data: 'status_ANY' }] + ] + }; + + this.bot.sendMessage(chatId, + 'What status are you interested in?', + { reply_markup: keyboard } + ); + return true; + } + + async setSearchStatus(chatId, telegramId, status) { + const userState = this.userStates.get(telegramId); + if (!userState || userState.step !== 'search_status') { + return false; + } + + userState.searchData.status = status === 'ANY' ? null : status; + userState.step = 'search_subcity'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the subcity/area you\'re interested in (or type "any" for all areas):' + ); + return true; + } + + askSearchHouseType(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'search_house_type'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the house type (e.g., Apartment, Villa, Studio) or type "any" for all types:' + ); + } + + askSearchMinPrice(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'search_min_price'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the minimum price (or 0 for no minimum):' + ); + } + + askSearchMaxPrice(chatId, telegramId) { + const userState = this.userStates.get(telegramId); + userState.step = 'search_max_price'; + this.userStates.set(telegramId, userState); + + this.bot.sendMessage(chatId, + 'Please enter the maximum price (or 0 for no maximum):' + ); + } + + async executeSearch(chatId, telegramId, userState) { + try { + this.bot.sendMessage(chatId, 'šŸ” Searching for listings...'); + + const searchFilters = { + type: userState.searchData.type, + status: userState.searchData.status, + minPrice: userState.searchData.minPrice || undefined, + maxPrice: userState.searchData.maxPrice || undefined, + subcity: userState.searchData.subcity === 'any' ? undefined : userState.searchData.subcity, + houseType: userState.searchData.houseType === 'any' ? undefined : userState.searchData.houseType + }; + + const searchResult = await this.api.getListings(searchFilters); + + if (!searchResult.success) { + this.bot.sendMessage(chatId, + `āŒ Search failed: ${searchResult.error}` + ); + return; + } + + this.userStates.delete(telegramId); + await this.displaySearchResults(chatId, searchResult.listings, userState.searchData); + + } catch (error) { + console.error('Error executing search:', error); + this.bot.sendMessage(chatId, 'Sorry, search failed. Please try again.'); + } + } + + async displaySearchResults(chatId, listings, searchData) { + if (!listings || listings.length === 0) { + this.bot.sendMessage(chatId, + 'šŸ” No listings found matching your criteria.\n\n' + + 'Try adjusting your search filters.' + ); + return; + } + + let message = `šŸ” Search Results (${listings.length} found)\n\n`; + + // Show first 5 results + const displayListings = listings.slice(0, 5); + + displayListings.forEach((listing, index) => { + message += `${index + 1}. šŸ  ${listing.title || 'Property'}\n`; + message += ` šŸ“Š Status: ${listing.status}\n`; + message += ` šŸ“ Location: ${listing.subcity || 'N/A'}\n`; + message += ` šŸ” Type: ${listing.houseType || 'N/A'}\n`; + message += ` šŸ’° Price: ${listing.price || 'N/A'}\n`; + if (listing.description) { + message += ` šŸ“ ${listing.description.substring(0, 50)}...\n`; + } + message += '\n'; + }); + + if (listings.length > 5) { + message += `... and ${listings.length - 5} more results\n\n`; + } + + this.bot.sendMessage(chatId, message); + } +} + +module.exports = SearchFeature; \ No newline at end of file diff --git a/src/services/notificationService.js b/src/services/notificationService.js new file mode 100644 index 0000000..29a891b --- /dev/null +++ b/src/services/notificationService.js @@ -0,0 +1,64 @@ +class NotificationService { + constructor(api) { + this.api = api; + // Remove local storage - now using backend API + } + + // Create a new notification via API + createNotification(userId, notificationData, telegramId) { + console.log('Creating notification via backend API'); + return this.api.createNotification(telegramId, userId, notificationData); + } + + // Get user notifications via API + getUserNotifications(userId, telegramId) { + console.log('Getting user notifications via backend API'); + return this.api.getUserNotifications(telegramId, userId); + } + + // Delete a notification via API + deleteNotification(notificationId, telegramId) { + console.log('Deleting notification via backend API'); + return this.api.deleteNotification(telegramId, notificationId); + } + + // Check for matching listings for a specific notification + async checkMatches(telegramId, notificationData) { + try { + // For preview, we still use the listings API with filters + const filters = { + type: notificationData.type, + status: notificationData.status, + minPrice: notificationData.minPrice, + maxPrice: notificationData.maxPrice, + subcity: notificationData.subcity, + houseType: notificationData.houseType + }; + + return await this.api.getListings(telegramId, filters); + } catch (error) { + console.error('Error checking matches for notification:', error); + return { success: false, error: error.message }; + } + } + + // Check matches for existing notification by ID + async checkNotificationMatches(telegramId, notificationId) { + console.log('Getting notification matches via backend API'); + return this.api.getNotificationMatches(telegramId, notificationId); + } + + // Get notifications by telegram user ID (public endpoint) + async getNotificationsByTelegramId(telegramUserId) { + console.log('Getting notifications by telegram ID via backend API'); + return this.api.getNotificationsByTelegramId(telegramUserId); + } + + // Clear user notifications (for logout) - now just a placeholder + clearUserNotifications(userId) { + console.log('User logged out - notifications remain in backend'); + // Notifications persist in backend, no need to clear + } +} + +module.exports = NotificationService; \ No newline at end of file diff --git a/src/utils/envValidator.js b/src/utils/envValidator.js new file mode 100644 index 0000000..136a63e --- /dev/null +++ b/src/utils/envValidator.js @@ -0,0 +1,89 @@ +class EnvironmentValidator { + static validateEnvironment() { + const requiredEnvVars = [ + 'TELEGRAM_BOT_TOKEN', + 'API_BASE_URL' + ]; + + const missingVars = []; + const invalidVars = []; + + for (const envVar of requiredEnvVars) { + const value = process.env[envVar]; + + if (!value) { + missingVars.push(envVar); + continue; + } + + // Validate specific environment variables + switch (envVar) { + case 'TELEGRAM_BOT_TOKEN': + if (!this.validateTelegramToken(value)) { + invalidVars.push(`${envVar}: Invalid token format`); + } + break; + + case 'API_BASE_URL': + if (!this.validateApiUrl(value)) { + invalidVars.push(`${envVar}: Invalid URL format`); + } + break; + } + } + + if (missingVars.length > 0) { + console.error('āŒ Missing required environment variables:'); + missingVars.forEach(varName => { + console.error(` - ${varName}`); + }); + console.error('\nPlease check your .env file and ensure all required variables are set.'); + return false; + } + + if (invalidVars.length > 0) { + console.error('āŒ Invalid environment variables:'); + invalidVars.forEach(error => { + console.error(` - ${error}`); + }); + return false; + } + + console.log('āœ… Environment validation passed'); + return true; + } + + static validateTelegramToken(token) { + // Telegram bot tokens have a specific format: number:string + const tokenRegex = /^\d+:[A-Za-z0-9_-]{35}$/; + return tokenRegex.test(token); + } + + static validateApiUrl(url) { + try { + const parsedUrl = new URL(url); + + // Ensure HTTPS in production (allow HTTP for development) + if (process.env.NODE_ENV === 'production' && parsedUrl.protocol !== 'https:') { + console.warn('āš ļø WARNING: Using HTTP in production is not secure'); + return false; + } + + return ['http:', 'https:'].includes(parsedUrl.protocol); + } catch (error) { + return false; + } + } + + static getSecureConfig() { + return { + telegramBotToken: process.env.TELEGRAM_BOT_TOKEN, + apiBaseUrl: process.env.API_BASE_URL, + websiteUrl: process.env.WEBSITE_URL || 'https://yaltipia.com/listings', + nodeEnv: process.env.NODE_ENV || 'development', + isProduction: process.env.NODE_ENV === 'production' + }; + } +} + +module.exports = EnvironmentValidator; \ No newline at end of file diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js new file mode 100644 index 0000000..db9491f --- /dev/null +++ b/src/utils/errorHandler.js @@ -0,0 +1,99 @@ +class ErrorHandler { + static getUserFriendlyMessage(error, operation = 'operation') { + // Don't expose technical details to users + const friendlyMessages = { + // Network/Connection errors + 'ECONNREFUSED': 'Service temporarily unavailable. Please try again later.', + 'ETIMEDOUT': 'Request timed out. Please check your connection and try again.', + 'ENOTFOUND': 'Service unavailable. Please try again later.', + 'ECONNRESET': 'Connection lost. Please try again.', + + // HTTP Status codes + 400: 'Invalid request. Please check your input and try again.', + 401: 'Authentication failed. Please login again with /start', + 403: 'Access denied. You don\'t have permission for this action.', + 404: 'Resource not found. The item you\'re looking for doesn\'t exist.', + 409: 'Conflict occurred. The item might already exist.', + 422: 'Invalid data provided. Please check your input.', + 429: 'Too many requests. Please wait a moment and try again.', + 500: 'Server error occurred. Please try again later.', + 502: 'Service temporarily unavailable. Please try again later.', + 503: 'Service maintenance in progress. Please try again later.', + 504: 'Request timed out. Please try again later.' + }; + + // Operation-specific messages + const operationMessages = { + 'create_notification': 'Failed to create notification', + 'get_notifications': 'Failed to load notifications', + 'update_notification': 'Failed to update notification', + 'delete_notification': 'Failed to delete notification', + 'get_matches': 'Failed to find matching listings', + 'login': 'Login failed', + 'register': 'Registration failed', + 'phone_check': 'Failed to verify phone number' + }; + + let message = operationMessages[operation] || `Failed to complete ${operation}`; + + // Check for specific error types + if (error.code && friendlyMessages[error.code]) { + return `${message}: ${friendlyMessages[error.code]}`; + } + + if (error.response?.status && friendlyMessages[error.response.status]) { + return `${message}: ${friendlyMessages[error.response.status]}`; + } + + // Check for common error patterns + if (error.message) { + const msg = error.message.toLowerCase(); + + if (msg.includes('network') || msg.includes('connection')) { + return `${message}: Connection problem. Please check your internet and try again.`; + } + + if (msg.includes('timeout')) { + return `${message}: Request timed out. Please try again.`; + } + + if (msg.includes('unauthorized') || msg.includes('authentication')) { + return `${message}: Please login again with /start`; + } + + if (msg.includes('not found')) { + return `${message}: Item not found.`; + } + + if (msg.includes('already exists')) { + return `${message}: Item already exists.`; + } + } + + // Generic fallback message + return `${message}. Please try again or contact support if the problem persists.`; + } + + static logError(error, context = '') { + // Log technical details for developers (not shown to users) + console.error(`[ERROR] ${context}:`, { + message: error.message, + status: error.response?.status, + statusText: error.response?.statusText, + data: error.response?.data, + code: error.code, + stack: error.stack + }); + } + + static handleApiError(error, operation, chatId, bot) { + // Log technical details + this.logError(error, operation); + + // Send user-friendly message + const userMessage = this.getUserFriendlyMessage(error, operation); + bot.sendMessage(chatId, `āŒ ${userMessage}`); + } +} + +module.exports = ErrorHandler; \ No newline at end of file diff --git a/src/utils/inputValidator.js b/src/utils/inputValidator.js new file mode 100644 index 0000000..1994f59 --- /dev/null +++ b/src/utils/inputValidator.js @@ -0,0 +1,144 @@ +const validator = require('validator'); + +class InputValidator { + static sanitizeText(input) { + if (!input || typeof input !== 'string') { + return ''; + } + + // Remove dangerous characters and trim + return validator.escape(input.trim()); + } + + static validateEmail(email) { + if (!email || typeof email !== 'string') { + return { valid: false, message: 'Email is required' }; + } + + const trimmedEmail = email.trim().toLowerCase(); + + if (!validator.isEmail(trimmedEmail)) { + return { valid: false, message: 'Please enter a valid email address' }; + } + + if (trimmedEmail.length > 254) { + return { valid: false, message: 'Email address is too long' }; + } + + return { valid: true, email: trimmedEmail }; + } + + static validatePhone(phone) { + if (!phone || typeof phone !== 'string') { + return { valid: false, message: 'Phone number is required' }; + } + + const cleanPhone = phone.replace(/\s+/g, ''); + + // Basic phone validation (international format) + if (!validator.isMobilePhone(cleanPhone, 'any', { strictMode: false })) { + return { valid: false, message: 'Please enter a valid phone number' }; + } + + return { valid: true, phone: cleanPhone }; + } + + static validateName(name) { + if (!name || typeof name !== 'string') { + return { valid: false, message: 'Name is required' }; + } + + const trimmedName = name.trim(); + + if (trimmedName.length < 1) { + return { valid: false, message: 'Name cannot be empty' }; + } + + if (trimmedName.length > 100) { + return { valid: false, message: 'Name is too long (max 100 characters)' }; + } + + // Check for potentially dangerous characters + if (/ 255) { + return { valid: false, message: 'Notification name is too long (max 255 characters)' }; + } + + return { valid: true, name: this.sanitizeText(trimmedName) }; + } + + static validatePrice(price) { + if (price === null || price === undefined || price === '') { + return { valid: true, price: null }; // Allow null prices + } + + const numPrice = parseInt(price); + + if (isNaN(numPrice)) { + return { valid: false, message: 'Please enter a valid number' }; + } + + if (numPrice < 0) { + return { valid: false, message: 'Price cannot be negative' }; + } + + if (numPrice > 999999999) { + return { valid: false, message: 'Price is too high' }; + } + + return { valid: true, price: numPrice }; + } + + static validatePropertyType(type) { + const validTypes = ['RENT', 'SELL']; + + if (!type || !validTypes.includes(type.toUpperCase())) { + return { valid: false, message: 'Invalid property type' }; + } + + return { valid: true, type: type.toUpperCase() }; + } + + static validatePropertyStatus(status) { + const validStatuses = ['DRAFT', 'ACTIVE', 'RENTED', 'FOR_SALE', 'SOLD', 'INACTIVE']; + + if (!status || !validStatuses.includes(status.toUpperCase())) { + return { valid: false, message: 'Invalid property status' }; + } + + return { valid: true, status: status.toUpperCase() }; + } + + static validateTelegramId(telegramId) { + if (!telegramId) { + return { valid: false, message: 'Telegram ID is required' }; + } + + const numId = parseInt(telegramId); + + if (isNaN(numId) || numId <= 0) { + return { valid: false, message: 'Invalid Telegram ID' }; + } + + return { valid: true, telegramId: numId }; + } +} + +module.exports = InputValidator; \ No newline at end of file diff --git a/src/utils/passwordUtils.js b/src/utils/passwordUtils.js new file mode 100644 index 0000000..4fc9ad6 --- /dev/null +++ b/src/utils/passwordUtils.js @@ -0,0 +1,55 @@ +const bcrypt = require('bcrypt'); + +class PasswordUtils { + static async hashPassword(password) { + try { + // Use salt rounds of 12 for strong security + const saltRounds = 12; + const hashedPassword = await bcrypt.hash(password, saltRounds); + return hashedPassword; + } catch (error) { + console.error('Error hashing password'); + throw new Error('Password hashing failed'); + } + } + + static async verifyPassword(password, hashedPassword) { + try { + const isValid = await bcrypt.compare(password, hashedPassword); + return isValid; + } catch (error) { + console.error('Error verifying password'); + return false; + } + } + + static validatePasswordStrength(password) { + if (!password || typeof password !== 'string') { + return { valid: false, message: 'Password is required' }; + } + + if (password.length < 6) { + return { valid: false, message: 'Password must be at least 6 characters long' }; + } + + if (password.length > 128) { + return { valid: false, message: 'Password is too long (max 128 characters)' }; + } + + // Check for at least one letter and one number (optional but recommended) + const hasLetter = /[a-zA-Z]/.test(password); + const hasNumber = /\d/.test(password); + + if (!hasLetter || !hasNumber) { + return { + valid: true, + message: 'Password is valid but consider adding both letters and numbers for better security', + weak: true + }; + } + + return { valid: true, message: 'Password is strong' }; + } +} + +module.exports = PasswordUtils; \ No newline at end of file diff --git a/src/utils/secureLogger.js b/src/utils/secureLogger.js new file mode 100644 index 0000000..9d2203e --- /dev/null +++ b/src/utils/secureLogger.js @@ -0,0 +1,114 @@ +class SecureLogger { + static log(level, message, context = {}) { + const timestamp = new Date().toISOString(); + const sanitizedContext = this.sanitizeLogData(context); + + const logEntry = { + timestamp, + level: level.toUpperCase(), + message, + ...sanitizedContext + }; + + switch (level.toLowerCase()) { + case 'error': + console.error(`[${timestamp}] ERROR: ${message}`, sanitizedContext); + break; + case 'warn': + console.warn(`[${timestamp}] WARN: ${message}`, sanitizedContext); + break; + case 'info': + console.info(`[${timestamp}] INFO: ${message}`, sanitizedContext); + break; + case 'debug': + if (process.env.NODE_ENV !== 'production') { + console.log(`[${timestamp}] DEBUG: ${message}`, sanitizedContext); + } + break; + default: + console.log(`[${timestamp}] ${message}`, sanitizedContext); + } + } + + static sanitizeLogData(data) { + if (!data || typeof data !== 'object') { + return {}; + } + + const sensitiveFields = [ + 'password', 'token', 'authorization', 'auth', 'secret', 'key', + 'phone', 'email', 'telegramId', 'userId', 'sessionId' + ]; + + const sanitized = {}; + + for (const [key, value] of Object.entries(data)) { + const lowerKey = key.toLowerCase(); + + if (sensitiveFields.some(field => lowerKey.includes(field))) { + sanitized[key] = this.maskSensitiveData(value); + } else if (typeof value === 'object' && value !== null) { + sanitized[key] = this.sanitizeLogData(value); + } else { + sanitized[key] = value; + } + } + + return sanitized; + } + + static maskSensitiveData(value) { + if (!value) return '[EMPTY]'; + + const str = String(value); + if (str.length <= 4) { + return '[MASKED]'; + } + + // Show first 2 and last 2 characters, mask the middle + return str.substring(0, 2) + '*'.repeat(str.length - 4) + str.substring(str.length - 2); + } + + static info(message, context = {}) { + this.log('info', message, context); + } + + static warn(message, context = {}) { + this.log('warn', message, context); + } + + static error(message, context = {}) { + this.log('error', message, context); + } + + static debug(message, context = {}) { + this.log('debug', message, context); + } + + // Safe logging methods for common operations + static logUserAction(action, telegramId, success = true) { + this.info(`User action: ${action}`, { + telegramId: this.maskSensitiveData(telegramId), + success, + action + }); + } + + static logApiCall(endpoint, success = true, statusCode = null) { + this.info(`API call: ${endpoint}`, { + endpoint, + success, + statusCode + }); + } + + static logAuthAttempt(phone, success = true, reason = null) { + this.info(`Authentication attempt`, { + phone: this.maskSensitiveData(phone), + success, + reason + }); + } +} + +module.exports = SecureLogger; \ No newline at end of file