diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index b221152..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = { - 'env': { - 'es2021': true, - 'node': true - }, - 'extends': [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended' - ], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { - 'ecmaVersion': 12, - 'sourceType': 'module' - }, - 'plugins': [ - '@typescript-eslint' - ], - 'rules': { - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'never' - ], - '@typescript-eslint/no-explicit-any': - ['warn', { - fixToUnknown: true // This line is optional and only relevant if you are using TypeScript - }], - 'comma-dangle': 'off', - '@typescript-eslint/comma-dangle': 'error', - 'prefer-arrow-callback': 'error' - // Add any other rules you want to enforce here - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4046dc1..faaa9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ sessions/ dist/ +.dev/ node_modules/ -accounts.json -notes +src/accounts.json +src/config.json +note accounts.dev.json accounts.main.json .DS_Store -.playwright-chromium-installed +.playwright-chromium-installed \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7a6d25d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -############################################################################### -# Stage 1: Builder -############################################################################### -FROM node:22-slim AS builder - -WORKDIR /usr/src/microsoft-rewards-script - -ENV PLAYWRIGHT_BROWSERS_PATH=0 - -# Copy package files -COPY package.json package-lock.json tsconfig.json ./ - -# Install all dependencies required to build the script -RUN npm ci --ignore-scripts - -# Copy source and build -COPY . . -RUN npm run build - -# Remove build dependencies, and reinstall only runtime dependencies -RUN rm -rf node_modules \ - && npm ci --omit=dev --ignore-scripts \ - && npm cache clean --force - -# Install Chromium Headless Shell, and cleanup -RUN npx playwright install --with-deps --only-shell chromium \ - && rm -rf /root/.cache /tmp/* /var/tmp/* - -############################################################################### -# Stage 2: Runtime -############################################################################### -FROM node:22-slim AS runtime - -WORKDIR /usr/src/microsoft-rewards-script - -# Set production environment variables -ENV NODE_ENV=production \ - TZ=UTC \ - PLAYWRIGHT_BROWSERS_PATH=0 \ - FORCE_HEADLESS=1 - -# Install minimal system libraries required for Chromium headless to run -RUN apt-get update && apt-get install -y --no-install-recommends \ - cron \ - gettext-base \ - tzdata \ - ca-certificates \ - libglib2.0-0 \ - libdbus-1-3 \ - libexpat1 \ - libfontconfig1 \ - libgtk-3-0 \ - libnspr4 \ - libnss3 \ - libasound2 \ - libflac12 \ - libatk1.0-0 \ - libatspi2.0-0 \ - libdrm2 \ - libgbm1 \ - libdav1d6 \ - libx11-6 \ - libx11-xcb1 \ - libxcomposite1 \ - libxcursor1 \ - libxdamage1 \ - libxext6 \ - libxfixes3 \ - libxi6 \ - libxrandr2 \ - libxrender1 \ - libxss1 \ - libxtst6 \ - libdouble-conversion3 \ - && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* - -# Copy compiled application and dependencies from builder stage -COPY --from=builder /usr/src/microsoft-rewards-script/dist ./dist -COPY --from=builder /usr/src/microsoft-rewards-script/package*.json ./ -COPY --from=builder /usr/src/microsoft-rewards-script/node_modules ./node_modules - -# Copy runtime scripts with proper permissions from the start -COPY --chmod=755 src/run_daily.sh ./src/run_daily.sh -COPY --chmod=644 src/crontab.template /etc/cron.d/microsoft-rewards-cron.template -COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh - -# Entrypoint handles TZ, initial run toggle, cron templating & launch -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] -CMD ["sh", "-c", "echo 'Container started; cron is running.'"] diff --git a/README.md b/README.md index ab1d9e7..8c95f80 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,7 @@ [![Discord](https://img.shields.io/badge/Join%20Our%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/8BxYbV4pkj) --- - -## Table of Contents -- [Setup](#setup) - - [1. Clone the Repository](#1-clone-the-repository) - - [2. Copy Configuration Files](#2-copy-configuration-files) - - [3. Install Dependencies and Prepare the Browser](#3-install-dependencies-and-prepare-the-browser) - - [4. Build and Run](#4-build-and-run) -- [Nix Users](#nix-setup) -- [Docker Setup](#docker-setup) - - [Before Starting](#before-starting) - - [Quick Start](#quick-start) - - [Example compose.yaml](#example-composeyaml) -- [Configuration Reference](#configuration-reference) -- [Account Configuration](#account-configuration) -- [Features Overview](#features-overview) -- [Disclaimer](#disclaimer) - ---- - -## Setup - -**Requirements:** Node.js ≥ 20 and Git -Works on Windows, Linux, macOS, and WSL. - ---- - -### 1. Clone the Repository -**All systems:** -```bash -git clone https://github.com/TheNetsky/Microsoft-Rewards-Script.git -cd Microsoft-Rewards-Script -``` -Or download the latest release ZIP and extract it. - ---- - -### 2. Copy Configuration Files - -**Windows:** -Rename manually: -``` -src/accounts.example.json → src/accounts.json -``` - -**Linux / macOS / WSL:** -```bash -cp src/accounts.example.json src/accounts.json -``` - -Then edit: -- `src/accounts.json` — fill in your Microsoft account credentials. -- `src/config.json` — review or customize options. - ---- - -### 3. Install Dependencies and Prepare the Browser - -**All systems:** -```bash -npm run pre-build -``` - -This command: -- Installs all dependencies -- Clears old builds (`dist/`) -- Installs Playwright Chromium (required browser) - ---- - -### 4. Build and Run - -**All systems:** -```bash -npm run build -npm run start -``` - ---- - -## Nix Setup - -If using Nix: - -1. Run the pre-build step first: - ```bash - npm run pre-build - ``` - -2. Then start the script: - ```bash - ./run.sh - ``` - -This will launch the script headlessly using `xvfb-run`. - ---- - -## Docker Setup - -### Before Starting -- Remove local `/node_modules` and `/dist` if previously built. -- Remove old Docker volumes if upgrading from older versions. -- You can reuse your existing `accounts.json`. - ---- - -### Quick Start -1. Clone the repository and configure your `accounts.json`. -2. Ensure `config.json` has `"headless": true`. -3. Edit `compose.yaml`: - - Set your timezone (`TZ`) - - Set the cron schedule (`CRON_SCHEDULE`) - - Optionally enable `RUN_ON_START=true` -4. Start the container: - ```bash - docker compose up -d - ``` -5. Monitor logs: - ```bash - docker logs microsoft-rewards-script - ``` - -The container includes a randomized delay (about 5–50 minutes by default) -before each scheduled run to appear more natural. This can be configured or disabled via environment variables. - ---- - -### Example compose.yaml - -```yaml -services: - microsoft-rewards-script: - image: ghcr.io/your-org/microsoft-rewards-script:latest - container_name: microsoft-rewards-script - restart: unless-stopped - - volumes: - - ./src/accounts.json:/usr/src/microsoft-rewards-script/dist/accounts.json:ro - - ./src/config.json:/usr/src/microsoft-rewards-script/dist/config.json:ro - - ./sessions:/usr/src/microsoft-rewards-script/dist/sessions - - environment: - TZ: "Europe/Amsterdam" - NODE_ENV: "production" - CRON_SCHEDULE: "0 7,16,20 * * *" - RUN_ON_START: "true" - # MIN_SLEEP_MINUTES: "5" - # MAX_SLEEP_MINUTES: "50" - # SKIP_RANDOM: "true" - - deploy: - resources: - limits: - cpus: "1.0" - memory: "1g" -``` - -#### compose.yaml Notes -- **volumes** - - `accounts.json` and `config.json` are mounted read-only to prevent accidental edits. - - `sessions` persists login sessions and fingerprints across runs. - - If `jobState.enabled` is used, mount its directory as a volume. -- **CRON_SCHEDULE** - - Uses standard crontab syntax (e.g., via [crontab.guru](https://crontab.guru/)). - - Schedule is evaluated inside the container using the configured `TZ`. -- **RUN_ON_START** - - Runs the script once immediately on startup, then continues on schedule. -- **Randomization** - - Default delay: 5–50 minutes. - - Adjustable via `MIN_SLEEP_MINUTES` and `MAX_SLEEP_MINUTES`, or disable with `SKIP_RANDOM`. - ---- - -## Configuration Reference - -Edit `src/config.json` to customize behavior. -Below is a summary of available options (matches the latest version in the repository). - -### Core -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `baseURL` | string | `"https://rewards.bing.com"` | Microsoft Rewards base URL | -| `sessionPath` | string | `"sessions"` | Directory to store browser sessions | -| `headless` | boolean | `false` | Run browser invisibly | -| `parallel` | boolean | `false` | Run desktop and mobile simultaneously | -| `runOnZeroPoints` | boolean | `false` | Run even when no points are available | -| `clusters` | number | `1` | Number of concurrent account clusters | -| `globalTimeout` | string | `"30s"` | Timeout for all actions | -| `searchOnBingLocalQueries` | boolean | `false` | Use local query list | - -### Fingerprinting -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `saveFingerprint.mobile` | boolean | `false` | Reuse mobile fingerprint | -| `saveFingerprint.desktop` | boolean | `false` | Reuse desktop fingerprint | - -### Workers -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `doDailySet` | boolean | `true` | Complete daily set | -| `doMorePromotions` | boolean | `true` | Complete more promotions | -| `doPunchCards` | boolean | `true` | Complete punchcards | -| `doDesktopSearch` | boolean | `true` | Perform desktop searches | -| `doMobileSearch` | boolean | `true` | Perform mobile searches | -| `doDailyCheckIn` | boolean | `true` | Complete daily check-in | -| `doReadToEarn` | boolean | `true` | Complete Read-to-Earn | - -### Search -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `searchSettings.useGeoLocaleQueries` | boolean | `false` | Use region-based queries | -| `searchSettings.scrollRandomResults` | boolean | `true` | Scroll randomly on results | -| `searchSettings.clickRandomResults` | boolean | `true` | Click random links | -| `searchSettings.searchDelay.min` | string | `"3min"` | Minimum delay between searches | -| `searchSettings.searchDelay.max` | string | `"5min"` | Maximum delay between searches | -| `searchSettings.retryMobileSearchAmount` | number | `2` | Retry mobile searches amount | - -### Logging -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `logExcludeFunc` | string[] | `["SEARCH-CLOSE-TABS"]` | Exclude from console logs | -| `webhookLogExcludeFunc` | string[] | `["SEARCH-CLOSE-TABS"]` | Exclude from webhook logs | - -### Proxy -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `proxy.proxyGoogleTrends` | boolean | `true` | Proxy Google Trends requests | -| `proxy.proxyBingTerms` | boolean | `true` | Proxy Bing term requests | - -### Webhooks -| Setting | Type | Default | Description | -|----------|------|----------|-------------| -| `webhook.enabled` | boolean | `false` | Enable Discord webhook | -| `webhook.url` | string | `""` | Webhook URL | -| `conclusionWebhook.enabled` | boolean | `false` | Enable summary webhook | -| `conclusionWebhook.url` | string | `""` | Summary webhook URL | - ---- - -## Account Configuration - -Edit `src/accounts.json` — the file is an **array** of accounts: - -```json -[ - { - "email": "email_1", - "password": "password_1", - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "email": "email_2", - "password": "password_2", - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - } -] -``` - -**Notes** -- The file is a **flat array** — not `{ "accounts": [ ... ] }`. -- Only `email`, `password`, and `proxy` are supported. -- `proxyAxios` enables Axios-level proxying for API requests. - ---- - -## Features Overview - -- Multi-account and session handling -- Persistent browser fingerprints -- Parallel task execution -- Proxy and retry support -- Human-like behavior simulation -- Full daily set automation -- Mobile and desktop search support -- Vacation and risk protection -- Webhook notifications -- Docker scheduling support - ---- +TODO ## Disclaimer diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index 0002100..0000000 --- a/compose.yaml +++ /dev/null @@ -1,42 +0,0 @@ -services: - microsoft-rewards-script: - build: . - container_name: microsoft-rewards-script - restart: unless-stopped - - # Volume mounts: Specify a location where you want to save the files on your local machine. - volumes: - - ./src/accounts.json:/usr/src/microsoft-rewards-script/dist/accounts.json:ro - - ./src/config.json:/usr/src/microsoft-rewards-script/dist/config.json:ro - - ./sessions:/usr/src/microsoft-rewards-script/dist/browser/sessions # Optional, saves your login session - - environment: - TZ: "America/Toronto" # Set your timezone for proper scheduling - NODE_ENV: "production" - CRON_SCHEDULE: "0 7,16,20 * * *" # Customize your schedule, use crontab.guru for formatting - RUN_ON_START: "true" # Runs the script immediately on container startup - - # Add scheduled start-time randomization (uncomment to customize or disable, default: enabled) - #MIN_SLEEP_MINUTES: "5" - #MAX_SLEEP_MINUTES: "50" - SKIP_RANDOM_SLEEP: "false" - - # Optionally set how long to wait before killing a stuck script run (prevents blocking future runs, default: 8 hours) - #STUCK_PROCESS_TIMEOUT_HOURS: "8" - - # Optional resource limits for the container - mem_limit: 4g - cpus: 2 - - # Health check - monitors if cron daemon is running to ensure scheduled jobs can execute - # Container marked unhealthy if cron process dies - healthcheck: - test: ["CMD", "sh", "-c", "pgrep cron > /dev/null || exit 1"] - interval: 60s - timeout: 10s - retries: 3 - start_period: 30s - - # Security hardening - security_opt: - - no-new-privileges:true \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 22d53b8..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Ensure Playwright uses preinstalled browsers -export PLAYWRIGHT_BROWSERS_PATH=0 - -# 1. Timezone: default to UTC if not provided -: "${TZ:=UTC}" -ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime -echo "$TZ" > /etc/timezone -dpkg-reconfigure -f noninteractive tzdata - -# 2. Validate CRON_SCHEDULE -if [ -z "${CRON_SCHEDULE:-}" ]; then - echo "ERROR: CRON_SCHEDULE environment variable is not set." >&2 - echo "Please set CRON_SCHEDULE (e.g., \"0 2 * * *\")." >&2 - exit 1 -fi - -# 3. Initial run without sleep if RUN_ON_START=true -if [ "${RUN_ON_START:-false}" = "true" ]; then - echo "[entrypoint] Starting initial run in background at $(date)" - ( - cd /usr/src/microsoft-rewards-script || { - echo "[entrypoint-bg] ERROR: Unable to cd to /usr/src/microsoft-rewards-script" >&2 - exit 1 - } - # Skip random sleep for initial run, but preserve setting for cron jobs - SKIP_RANDOM_SLEEP=true src/run_daily.sh - echo "[entrypoint-bg] Initial run completed at $(date)" - ) & - echo "[entrypoint] Background process started (PID: $!)" -fi - -# 4. Template and register cron file with explicit timezone export -if [ ! -f /etc/cron.d/microsoft-rewards-cron.template ]; then - echo "ERROR: Cron template /etc/cron.d/microsoft-rewards-cron.template not found." >&2 - exit 1 -fi - -# Export TZ for envsubst to use -export TZ -envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron -chmod 0644 /etc/cron.d/microsoft-rewards-cron -crontab /etc/cron.d/microsoft-rewards-cron - -echo "[entrypoint] Cron configured with schedule: $CRON_SCHEDULE and timezone: $TZ; starting cron at $(date)" - -# 5. Start cron in foreground (PID 1) -exec cron -f \ No newline at end of file diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 27bbe28..0000000 --- a/flake.lock +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1749727998, - "narHash": "sha256-mHv/yeUbmL91/TvV95p+mBVahm9mdQMJoqaTVTALaFw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "fd487183437963a59ba763c0cc4f27e3447dd6dd", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-25.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index e327534..0000000 --- a/flake.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; - flake-utils = { - url = "github:numtide/flake-utils"; - }; - }; - - outputs = - { nixpkgs, flake-utils, ... }: - flake-utils.lib.eachDefaultSystem ( - system: - let - pkgs = import nixpkgs { - inherit system; - }; - in - { - devShell = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ - nodejs - playwright-driver.browsers - typescript - playwright-test - - # fixes "waiting until load" issue compared to - # setting headless in config.json - xvfb-run - ]; - - shellHook = '' - export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} - export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true - npm i - npm run build - ''; - }; - } - ); -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e4b8253..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3126 +0,0 @@ -{ - "name": "microsoft-rewards-script", - "version": "1.5.3", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "microsoft-rewards-script", - "version": "1.5.3", - "license": "ISC", - "dependencies": { - "axios": "^1.8.4", - "chalk": "^4.1.2", - "cheerio": "^1.0.0", - "fingerprint-generator": "^2.1.66", - "fingerprint-injector": "^2.1.66", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "ms": "^2.1.3", - "playwright": "1.52.0", - "rebrowser-playwright": "1.52.0", - "socks-proxy-agent": "^8.0.5", - "ts-node": "^10.9.2" - }, - "devDependencies": { - "@types/ms": "^0.7.34", - "@types/node": "^20.14.11", - "@typescript-eslint/eslint-plugin": "^7.17.0", - "eslint": "^8.57.0", - "eslint-plugin-modules-newline": "^0.0.6", - "rimraf": "^6.0.1", - "typescript": "^5.5.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" - }, - "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", - "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", - "engines": { - "node": ">=12.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": 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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", - "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==", - "dev": true - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", - "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "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, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "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==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001750", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", - "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "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==", - "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==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "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==", - "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==", - "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==", - "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==", - "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/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-modules-newline": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-modules-newline/-/eslint-plugin-modules-newline-0.0.6.tgz", - "integrity": "sha512-69NpBr68U6pmXL+y+KHl/64PwRarceC3/sCNUVxRbe0gPI32SIw8AtdpkqNiJYCa2yMd4lRrkrnU09Yio7KVzA==", - "dev": true, - "dependencies": { - "requireindex": "~1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.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, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fingerprint-generator": { - "version": "2.1.75", - "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.75.tgz", - "integrity": "sha512-3dJr2d0GktMAupjRigYs6sXDeZ56zATPcsYfBcp9n4bvhxl3NnWAgdocEdiezi4vKXacoeeh3jNs+i5ReTjgvw==", - "dependencies": { - "generative-bayesian-network": "^2.1.75", - "header-generator": "^2.1.75", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fingerprint-injector": { - "version": "2.1.75", - "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.75.tgz", - "integrity": "sha512-TDylxXZV805MtZzKBFWWzqFCzNq4rmAVaHZf8TztgSix6Arc3l8hfyg9fcOMx8rpF4odjVuFIRzEH7rscLPQew==", - "dependencies": { - "fingerprint-generator": "^2.1.75", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "playwright": "^1.22.2", - "puppeteer": ">= 9.x" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - }, - "puppeteer": { - "optional": true - } - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/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==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/flat-cache/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", - "dev": true, - "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/flat-cache/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/flat-cache/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", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true - }, - "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" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "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.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "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==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generative-bayesian-network": { - "version": "2.1.75", - "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.75.tgz", - "integrity": "sha512-B8B6l7B1tFYvbPNq65vMibLhAfUqyBCGPx3+9VHf2V0hc95LuZIXSlhMOy/zHkfFualU5HJSTi0Nv6EZ2Lq6fA==", - "dependencies": { - "adm-zip": "^0.5.9", - "tslib": "^2.4.0" - } - }, - "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==", - "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==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dev": true, - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "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==", - "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==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/header-generator": { - "version": "2.1.75", - "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.75.tgz", - "integrity": "sha512-IUyoYkL1eMt788nahD3tiw+A4AZ1mhVIWorHiXnS4rBMIeJ4C4HwoOvSKRkElKIvpKqwpkNO6BGvkPuVsKYRYw==", - "dependencies": { - "browserslist": "^4.21.1", - "generative-bayesian-network": "^2.1.75", - "ow": "^0.28.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "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.", - "dev": true, - "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==", - "dev": true - }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "engines": { - "node": ">= 12" - } - }, - "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, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "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==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "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==", - "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==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "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==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "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==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ow": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", - "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", - "dependencies": { - "@sindresorhus/is": "^4.2.0", - "callsites": "^3.1.0", - "dot-prop": "^6.0.1", - "lodash.isequal": "^4.5.0", - "vali-date": "^1.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", - "dependencies": { - "playwright-core": "1.52.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "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==" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/rebrowser-playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/rebrowser-playwright/-/rebrowser-playwright-1.52.0.tgz", - "integrity": "sha512-UjpqfwmF9+XtOuCCxGQ2ZlLeuSaSv//4Z6ZQgYPsJovz3d7nWodCd2hSRQigAswAUnsPmVwnQUpSn+TLKaKV+A==", - "dependencies": { - "playwright-core": "npm:rebrowser-playwright-core@~1.52.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/requireindex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", - "integrity": "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==", - "dev": true, - "engines": { - "node": ">=0.10.5" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "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==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "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, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.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==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" - }, - "node_modules/vali-date": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", - "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/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==", - "dev": true - }, - "node_modules/wrap-ansi-cjs/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index e74dc08..0000000 --- a/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "microsoft-rewards-script", - "version": "1.5.3", - "description": "Automatically do tasks for Microsoft Rewards but in TS!", - "main": "index.js", - "engines": { - "node": ">=18.0.0" - }, - "scripts": { - "pre-build": "npm i && rimraf dist && npx playwright install chromium", - "build": "tsc", - "start": "node ./dist/index.js", - "ts-start": "ts-node ./src/index.ts", - "dev": "ts-node ./src/index.ts -dev", - "kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"", - "create-docker": "docker build -t microsoft-rewards-script-docker ." - }, - "keywords": [ - "Bing Rewards", - "Microsoft Rewards", - "Bot", - "Script", - "TypeScript", - "Playwright", - "Cheerio" - ], - "author": "Netsky", - "license": "ISC", - "devDependencies": { - "@types/node": "^20.14.11", - "@types/ms": "^0.7.34", - "@typescript-eslint/eslint-plugin": "^7.17.0", - "eslint": "^8.57.0", - "eslint-plugin-modules-newline": "^0.0.6", - "rimraf": "^6.0.1", - "typescript": "^5.5.4" - }, - "dependencies": { - "axios": "^1.8.4", - "chalk": "^4.1.2", - "cheerio": "^1.0.0", - "fingerprint-generator": "^2.1.66", - "fingerprint-injector": "^2.1.66", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "ms": "^2.1.3", - "playwright": "1.52.0", - "rebrowser-playwright": "1.52.0", - "socks-proxy-agent": "^8.0.5", - "ts-node": "^10.9.2" - } -} diff --git a/run.sh b/run.sh deleted file mode 100755 index cffeb43..0000000 --- a/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -nix develop --command bash -c "xvfb-run npm run start" diff --git a/src/accounts.example.json b/src/accounts.example.json deleted file mode 100644 index ef7718a..0000000 --- a/src/accounts.example.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "email": "email_1", - "password": "password_1", - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - }, - { - "email": "email_2", - "password": "password_2", - "proxy": { - "proxyAxios": true, - "url": "", - "port": 0, - "username": "", - "password": "" - } - } -] \ No newline at end of file diff --git a/src/browser/Browser.ts b/src/browser/Browser.ts deleted file mode 100644 index 1aeac5f..0000000 --- a/src/browser/Browser.ts +++ /dev/null @@ -1,96 +0,0 @@ -import playwright, { BrowserContext } from 'rebrowser-playwright' - -import { newInjectedContext } from 'fingerprint-injector' -import { FingerprintGenerator } from 'fingerprint-generator' - -import { MicrosoftRewardsBot } from '../index' -import { loadSessionData, saveFingerprintData } from '../util/Load' -import { updateFingerprintUserAgent } from '../util/UserAgent' - -import { AccountProxy } from '../interface/Account' - -/* Test Stuff -https://abrahamjuliot.github.io/creepjs/ -https://botcheck.luminati.io/ -https://fv.pro/ -https://pixelscan.net/ -https://www.browserscan.net/ -*/ - -class Browser { - private bot: MicrosoftRewardsBot - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - async createBrowser(proxy: AccountProxy, email: string): Promise { - // Optional automatic browser installation (set AUTO_INSTALL_BROWSERS=1) - if (process.env.AUTO_INSTALL_BROWSERS === '1') { - try { - // Dynamically import child_process to avoid overhead otherwise - const { execSync } = await import('child_process') as any - execSync('npx playwright install chromium', { stdio: 'ignore' }) - } catch { /* silent */ } - } - - let browser: any - try { - browser = await playwright.chromium.launch({ - //channel: 'msedge', // Uses Edge instead of chrome - headless: this.bot.config.headless, - ...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }), - args: [ - '--no-sandbox', - '--mute-audio', - '--disable-setuid-sandbox', - '--ignore-certificate-errors', - '--ignore-certificate-errors-spki-list', - '--ignore-ssl-errors' - ] - }) - } catch (e: any) { - const msg = (e instanceof Error ? e.message : String(e)) - // Common missing browser executable guidance - if (/Executable doesn't exist/i.test(msg)) { - this.bot.log(this.bot.isMobile, 'BROWSER', 'Chromium not installed for Playwright. Run: "npx playwright install chromium" (or set AUTO_INSTALL_BROWSERS=1 to auto attempt).', 'error') - } else { - this.bot.log(this.bot.isMobile, 'BROWSER', 'Failed to launch browser: ' + msg, 'error') - } - throw e - } - - const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint) - - const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint() - - const context = await newInjectedContext(browser as any, { fingerprint: fingerprint }) - - // Set timeout to preferred amount - context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30000)) - - await context.addCookies(sessionData.cookies) - - if (this.bot.config.saveFingerprint) { - await saveFingerprintData(this.bot.config.sessionPath, email, this.bot.isMobile, fingerprint) - } - - this.bot.log(this.bot.isMobile, 'BROWSER', `Created browser with User-Agent: "${fingerprint.fingerprint.navigator.userAgent}"`) - - return context as BrowserContext - } - - async generateFingerprint() { - const fingerPrintData = new FingerprintGenerator().getFingerprint({ - devices: this.bot.isMobile ? ['mobile'] : ['desktop'], - operatingSystems: this.bot.isMobile ? ['android'] : ['windows'], - browsers: [{ name: 'edge' }] - }) - - const updatedFingerPrintData = await updateFingerprintUserAgent(fingerPrintData, this.bot.isMobile) - - return updatedFingerPrintData - } -} - -export default Browser \ No newline at end of file diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts deleted file mode 100644 index f60bf05..0000000 --- a/src/browser/BrowserFunc.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { BrowserContext, Page } from 'rebrowser-playwright' -import { CheerioAPI, load } from 'cheerio' -import { AxiosRequestConfig } from 'axios' - -import { MicrosoftRewardsBot } from '../index' -import { saveSessionData } from '../util/Load' - -import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData' -import { QuizData } from './../interface/QuizData' -import { AppUserData } from '../interface/AppUserData' -import { EarnablePoints } from '../interface/Points' - - -export default class BrowserFunc { - private bot: MicrosoftRewardsBot - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - - /** - * Navigate the provided page to rewards homepage - * @param {Page} page Playwright page - */ - async goHome(page: Page) { - - try { - const dashboardURL = new URL(this.bot.config.baseURL) - - if (page.url() === dashboardURL.href) { - return - } - - await page.goto(this.bot.config.baseURL) - - const maxIterations = 5 // Maximum iterations set to 5 - - for (let iteration = 1; iteration <= maxIterations; iteration++) { - await this.bot.utils.wait(3000) - await this.bot.browser.utils.tryDismissAllMessages(page) - - // Check if account is suspended - const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false) - if (isSuspended) { - this.bot.log(this.bot.isMobile, 'GO-HOME', 'This account is suspended!', 'error') - throw new Error('Account has been suspended!') - } - - try { - // If activities are found, exit the loop - await page.waitForSelector('#more-activities', { timeout: 1000 }) - this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully') - break - - } catch (error) { - // Continue if element is not found - } - - // Below runs if the homepage was unable to be visited - const currentURL = new URL(page.url()) - - if (currentURL.hostname !== dashboardURL.hostname) { - await this.bot.browser.utils.tryDismissAllMessages(page) - - await this.bot.utils.wait(2000) - await page.goto(this.bot.config.baseURL) - } else { - this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully') - break - } - - await this.bot.utils.wait(5000) - } - - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GO-HOME', 'An error occurred:' + error, 'error') - } - } - - /** - * Fetch user dashboard data - * @returns {DashboardData} Object of user bing rewards dashboard data - */ - async getDashboardData(): Promise { - const dashboardURL = new URL(this.bot.config.baseURL) - const currentURL = new URL(this.bot.homePage.url()) - - try { - // Should never happen since tasks are opened in a new tab! - if (currentURL.hostname !== dashboardURL.hostname) { - this.bot.log(this.bot.isMobile, 'DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page') - await this.goHome(this.bot.homePage) - } - let lastError: any = null - for (let attempt = 1; attempt <= 2; attempt++) { - try { - // Reload the page to get new data - await this.bot.homePage.reload({ waitUntil: 'domcontentloaded' }) - lastError = null - break - } catch (re) { - lastError = re - const msg = (re instanceof Error ? re.message : String(re)) - this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Reload failed attempt ${attempt}: ${msg}`, 'warn') - // If page/context closed => bail early after first retry - if (msg.includes('has been closed')) { - if (attempt === 1) { - this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Page appears closed; trying one navigation fallback', 'warn') - try { - await this.goHome(this.bot.homePage) - } catch {/* ignore */} - } else { - break - } - } - if (attempt === 2 && lastError) throw lastError - await this.bot.utils.wait(1000) - } - } - - const scriptContent = await this.bot.homePage.evaluate(() => { - const scripts = Array.from(document.querySelectorAll('script')) - const targetScript = scripts.find(script => script.innerText.includes('var dashboard')) - - return targetScript?.innerText ? targetScript.innerText : null - }) - - if (!scriptContent) { - throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error') - } - - // Extract the dashboard object from the script content - const dashboardData = await this.bot.homePage.evaluate((scriptContent: string) => { - // Extract the dashboard object using regex - const regex = /var dashboard = (\{.*?\});/s - const match = regex.exec(scriptContent) - - if (match && match[1]) { - return JSON.parse(match[1]) - } - - }, scriptContent) - - if (!dashboardData) { - throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error') - } - - return dashboardData - - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Error fetching dashboard data: ${error}`, 'error') - } - - } - - /** - * Get search point counters - * @returns {Counters} Object of search counter data - */ - async getSearchPoints(): Promise { - const dashboardData = await this.getDashboardData() // Always fetch newest data - - return dashboardData.userStatus.counters - } - - /** - * Get total earnable points with web browser - * @returns {number} Total earnable points - */ - async getBrowserEarnablePoints(): Promise { - try { - let desktopSearchPoints = 0 - let mobileSearchPoints = 0 - let dailySetPoints = 0 - let morePromotionsPoints = 0 - - const data = await this.getDashboardData() - - // Desktop Search Points - if (data.userStatus.counters.pcSearch?.length) { - data.userStatus.counters.pcSearch.forEach(x => desktopSearchPoints += (x.pointProgressMax - x.pointProgress)) - } - - // Mobile Search Points - if (data.userStatus.counters.mobileSearch?.length) { - data.userStatus.counters.mobileSearch.forEach(x => mobileSearchPoints += (x.pointProgressMax - x.pointProgress)) - } - - // Daily Set - data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => dailySetPoints += (x.pointProgressMax - x.pointProgress)) - - // More Promotions - if (data.morePromotions?.length) { - data.morePromotions.forEach(x => { - // Only count points from supported activities - if (['quiz', 'urlreward'].includes(x.promotionType) && x.exclusiveLockedFeatureStatus !== 'locked') { - morePromotionsPoints += (x.pointProgressMax - x.pointProgress) - } - }) - } - - const totalEarnablePoints = desktopSearchPoints + mobileSearchPoints + dailySetPoints + morePromotionsPoints - - return { - dailySetPoints, - morePromotionsPoints, - desktopSearchPoints, - mobileSearchPoints, - totalEarnablePoints - } - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error') - } - } - - /** - * Get total earnable points with mobile app - * @returns {number} Total earnable points - */ - async getAppEarnablePoints(accessToken: string) { - try { - const points = { - readToEarn: 0, - checkIn: 0, - totalEarnablePoints: 0 - } - - const eligibleOffers = [ - 'ENUS_readarticle3_30points', - 'Gamification_Sapphire_DailyCheckIn' - ] - - const data = await this.getDashboardData() - let geoLocale = data.userProfile.attributes.country - geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' - - const userDataRequest: AxiosRequestConfig = { - url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613', - method: 'GET', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'X-Rewards-Country': geoLocale, - 'X-Rewards-Language': 'en' - } - } - - const userDataResponse: AppUserData = (await this.bot.axios.request(userDataRequest)).data - const userData = userDataResponse.response - const eligibleActivities = userData.promotions.filter((x) => eligibleOffers.includes(x.attributes.offerid ?? '')) - - for (const item of eligibleActivities) { - if (item.attributes.type === 'msnreadearn') { - points.readToEarn = parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '') - break - } else if (item.attributes.type === 'checkin') { - const checkInDay = parseInt(item.attributes.progress ?? '') % 7 - - if (checkInDay < 6 && (new Date()).getDate() != (new Date(item.attributes.last_updated ?? '')).getDate()) { - points.checkIn = parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '') - } - break - } - } - - points.totalEarnablePoints = points.readToEarn + points.checkIn - - return points - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error') - } - } - - /** - * Get current point amount - * @returns {number} Current total point amount - */ - async getCurrentPoints(): Promise { - try { - const data = await this.getDashboardData() - - return data.userStatus.availablePoints - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', 'An error occurred:' + error, 'error') - } - } - - /** - * Parse quiz data from provided page - * @param {Page} page Playwright page - * @returns {QuizData} Quiz data object - */ - async getQuizData(page: Page): Promise { - try { - const html = await page.content() - const $ = load(html) - - const scriptContent = $('script').filter((index: number, element: any) => { - return $(element).text().includes('_w.rewardsQuizRenderInfo') - }).text() - - if (scriptContent) { - const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s - const match = regex.exec(scriptContent) - - if (match && match[1]) { - const quizData = JSON.parse(match[1]) - return quizData - } else { - throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Quiz data not found within script', 'error') - } - } else { - throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Script containing quiz data not found', 'error') - } - - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'An error occurred:' + error, 'error') - } - - } - - async waitForQuizRefresh(page: Page): Promise { - try { - await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10000 }) - await this.bot.utils.wait(2000) - - return true - } catch (error) { - this.bot.log(this.bot.isMobile, 'QUIZ-REFRESH', 'An error occurred:' + error, 'error') - return false - } - } - - async checkQuizCompleted(page: Page): Promise { - try { - await page.waitForSelector('#quizCompleteContainer', { state: 'visible', timeout: 2000 }) - await this.bot.utils.wait(2000) - - return true - } catch (error) { - return false - } - } - - async loadInCheerio(page: Page): Promise { - const html = await page.content() - const $ = load(html) - - return $ - } - - async getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise { - let selector = '' - try { - const html = await page.content() - const $ = load(html) - - const element = $('.offer-cta').toArray().find((x: any) => x.attribs.href?.includes(activity.offerId)) - if (element) { - selector = `a[href*="${element.attribs.href}"]` - } - } catch (error) { - this.bot.log(this.bot.isMobile, 'GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error') - } - - return selector - } - - async closeBrowser(browser: BrowserContext, email: string) { - try { - // Save cookies - await saveSessionData(this.bot.config.sessionPath, browser, email, this.bot.isMobile) - - await this.bot.utils.wait(2000) - - // Close browser - await browser.close() - this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'Browser closed cleanly!') - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'An error occurred:' + error, 'error') - } - } -} \ No newline at end of file diff --git a/src/browser/BrowserUtil.ts b/src/browser/BrowserUtil.ts deleted file mode 100644 index 7952e23..0000000 --- a/src/browser/BrowserUtil.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Page } from 'rebrowser-playwright' -import { load } from 'cheerio' - -import { MicrosoftRewardsBot } from '../index' - - -export default class BrowserUtil { - private bot: MicrosoftRewardsBot - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - async tryDismissAllMessages(page: Page): Promise { - const buttons = [ - { selector: '#acceptButton', label: 'AcceptButton' }, - { selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' }, - { selector: '#iLandingViewAction', label: 'iLandingViewAction' }, - { selector: '#iShowSkip', label: 'iShowSkip' }, - { selector: '#iNext', label: 'iNext' }, - { selector: '#iLooksGood', label: 'iLooksGood' }, - { selector: '#idSIButton9', label: 'idSIButton9' }, - { selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' }, - { selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' }, - { selector: '.maybe-later', label: 'Mobile Rewards App Banner' }, - { selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container', isXPath: true }, - { selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' }, - { selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' } - ] - - for (const button of buttons) { - try { - const element = button.isXPath ? page.locator(`xpath=${button.selector}`) : page.locator(button.selector) - await element.first().click({ timeout: 500 }) - await page.waitForTimeout(500) - - this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`) - - } catch (error) { - // Silent fail - } - } - - // Handle blocking Bing privacy overlay intercepting clicks (#bnp_overlay_wrapper) - try { - const overlay = await page.locator('#bnp_overlay_wrapper').first() - if (await overlay.isVisible({ timeout: 500 }).catch(()=>false)) { - // Try common dismiss buttons inside overlay - const rejectBtn = await page.locator('#bnp_btn_reject, button[aria-label*="Reject" i]').first() - const acceptBtn = await page.locator('#bnp_btn_accept').first() - if (await rejectBtn.isVisible().catch(()=>false)) { - await rejectBtn.click({ timeout: 500 }).catch(()=>{}) - this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', 'Dismissed: Bing Overlay Reject') - } else if (await acceptBtn.isVisible().catch(()=>false)) { - await acceptBtn.click({ timeout: 500 }).catch(()=>{}) - this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', 'Dismissed: Bing Overlay Accept (fallback)') - } - await page.waitForTimeout(300) - } - } catch { /* ignore */ } - } - - async getLatestTab(page: Page): Promise { - try { - await this.bot.utils.wait(1000) - - const browser = page.context() - const pages = browser.pages() - const newTab = pages[pages.length - 1] - - if (newTab) { - return newTab - } - - throw this.bot.log(this.bot.isMobile, 'GET-NEW-TAB', 'Unable to get latest tab', 'error') - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-NEW-TAB', 'An error occurred:' + error, 'error') - } - } - - async getTabs(page: Page) { - try { - const browser = page.context() - const pages = browser.pages() - - const homeTab = pages[1] - let homeTabURL: URL - - if (!homeTab) { - throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Home tab could not be found!', 'error') - - } else { - homeTabURL = new URL(homeTab.url()) - - if (homeTabURL.hostname !== 'rewards.bing.com') { - throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error') - } - } - - const workerTab = pages[2] - if (!workerTab) { - throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Worker tab could not be found!', 'error') - } - - return { - homeTab: homeTab, - workerTab: workerTab - } - - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'An error occurred:' + error, 'error') - } - } - - async reloadBadPage(page: Page): Promise { - try { - const html = await page.content().catch(() => '') - const $ = load(html) - - const isNetworkError = $('body.neterror').length - - if (isNetworkError) { - this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'Bad page detected, reloading!') - await page.reload() - } - - } catch (error) { - throw this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/config.json b/src/config.json deleted file mode 100644 index daf334f..0000000 --- a/src/config.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "baseURL": "https://rewards.bing.com", - "sessionPath": "sessions", - "headless": false, - "parallel": false, - "runOnZeroPoints": false, - "clusters": 1, - "saveFingerprint": { - "mobile": false, - "desktop": false - }, - "workers": { - "doDailySet": true, - "doMorePromotions": true, - "doPunchCards": true, - "doDesktopSearch": true, - "doMobileSearch": true, - "doDailyCheckIn": true, - "doReadToEarn": true - }, - "searchOnBingLocalQueries": false, - "globalTimeout": "30s", - "searchSettings": { - "useGeoLocaleQueries": false, - "scrollRandomResults": true, - "clickRandomResults": true, - "searchDelay": { - "min": "3min", - "max": "5min" - }, - "retryMobileSearchAmount": 2 - }, - "logExcludeFunc": [ - "SEARCH-CLOSE-TABS" - ], - "webhookLogExcludeFunc": [ - "SEARCH-CLOSE-TABS" - ], - "proxy": { - "proxyGoogleTrends": true, - "proxyBingTerms": true - }, - "webhook": { - "enabled": false, - "url": "" - }, - "conclusionWebhook": { - "enabled": false, - "url": "" - } -} \ No newline at end of file diff --git a/src/crontab.template b/src/crontab.template deleted file mode 100644 index 5576966..0000000 --- a/src/crontab.template +++ /dev/null @@ -1,2 +0,0 @@ -# Run automation according to CRON_SCHEDULE; redirect both stdout & stderr to Docker logs -${CRON_SCHEDULE} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /proc/1/fd/1 2>&1 diff --git a/src/functions/Activities.ts b/src/functions/Activities.ts deleted file mode 100644 index 74dd166..0000000 --- a/src/functions/Activities.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { MicrosoftRewardsBot } from '../index' - -import { Search } from './activities/Search' -import { ABC } from './activities/ABC' -import { Poll } from './activities/Poll' -import { Quiz } from './activities/Quiz' -import { ThisOrThat } from './activities/ThisOrThat' -import { UrlReward } from './activities/UrlReward' -import { SearchOnBing } from './activities/SearchOnBing' -import { ReadToEarn } from './activities/ReadToEarn' -import { DailyCheckIn } from './activities/DailyCheckIn' - -import { DashboardData, MorePromotion, PromotionalItem } from '../interface/DashboardData' - - -export default class Activities { - private bot: MicrosoftRewardsBot - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - doSearch = async (page: Page, data: DashboardData): Promise => { - const search = new Search(this.bot) - await search.doSearch(page, data) - } - - doABC = async (page: Page): Promise => { - const abc = new ABC(this.bot) - await abc.doABC(page) - } - - doPoll = async (page: Page): Promise => { - const poll = new Poll(this.bot) - await poll.doPoll(page) - } - - doThisOrThat = async (page: Page): Promise => { - const thisOrThat = new ThisOrThat(this.bot) - await thisOrThat.doThisOrThat(page) - } - - doQuiz = async (page: Page): Promise => { - const quiz = new Quiz(this.bot) - await quiz.doQuiz(page) - } - - doUrlReward = async (page: Page): Promise => { - const urlReward = new UrlReward(this.bot) - await urlReward.doUrlReward(page) - } - - doSearchOnBing = async (page: Page, activity: MorePromotion | PromotionalItem): Promise => { - const searchOnBing = new SearchOnBing(this.bot) - await searchOnBing.doSearchOnBing(page, activity) - } - - doReadToEarn = async (accessToken: string, data: DashboardData): Promise => { - const readToEarn = new ReadToEarn(this.bot) - await readToEarn.doReadToEarn(accessToken, data) - } - - doDailyCheckIn = async (accessToken: string, data: DashboardData): Promise => { - const dailyCheckIn = new DailyCheckIn(this.bot) - await dailyCheckIn.doDailyCheckIn(accessToken, data) - } - -} \ No newline at end of file diff --git a/src/functions/Login.ts b/src/functions/Login.ts deleted file mode 100644 index 09698d5..0000000 --- a/src/functions/Login.ts +++ /dev/null @@ -1,562 +0,0 @@ -import type { Page } from 'playwright' -import readline from 'readline' -import * as crypto from 'crypto' -import { AxiosRequestConfig } from 'axios' - -import { MicrosoftRewardsBot } from '../index' -import { saveSessionData } from '../util/Load' - -import { OAuth } from '../interface/OAuth' - - -const rl = readline.createInterface({ - // Use as any to avoid strict typing issues with our minimal process shim - input: (process as any).stdin, - output: (process as any).stdout -}) - -export class Login { - private bot: MicrosoftRewardsBot - private clientId: string = '0000000040170455' - private authBaseUrl: string = 'https://login.live.com/oauth20_authorize.srf' - private redirectUrl: string = 'https://login.live.com/oauth20_desktop.srf' - private tokenUrl: string = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token' - private scope: string = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL' - // Flag to prevent spamming passkey logs after first handling - private passkeyHandled: boolean = false - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - async login(page: Page, email: string, password: string) { - - try { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Starting login process!') - - // Navigate to the Bing login page - await page.goto('https://www.bing.com/rewards/dashboard') - - // Disable FIDO support in login request - await page.route('**/GetCredentialType.srf*', (route: any) => { - const body = JSON.parse(route.request().postData() || '{}') - body.isFidoSupported = false - route.continue({ postData: JSON.stringify(body) }) - }) - - await page.waitForLoadState('domcontentloaded').catch(() => { }) - - await this.bot.browser.utils.reloadBadPage(page) - - // Check if account is locked - await this.checkAccountLocked(page) - - const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 }).then(() => true).catch(() => false) - - if (!isLoggedIn) { - await this.execLogin(page, email, password) - this.bot.log(this.bot.isMobile, 'LOGIN', 'Logged into Microsoft successfully') - } else { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Already logged in') - - // Check if account is locked - await this.checkAccountLocked(page) - } - - // Check if logged in to bing - await this.checkBingLogin(page) - - // Save session - await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile) - - // We're done logging in - this.bot.log(this.bot.isMobile, 'LOGIN', 'Logged in successfully, saved login session!') - - } catch (error) { - // Throw and don't continue - throw this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred:' + error, 'error') - } - } - - private async execLogin(page: Page, email: string, password: string) { - try { - await this.enterEmail(page, email) - await this.bot.utils.wait(2000) - await this.bot.browser.utils.reloadBadPage(page) - await this.bot.utils.wait(2000) - await this.enterPassword(page, password) - await this.bot.utils.wait(2000) - - // Check if account is locked - await this.checkAccountLocked(page) - - await this.bot.browser.utils.reloadBadPage(page) - await this.checkLoggedIn(page) - } catch (error) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred: ' + error, 'error') - } - } - - private async enterEmail(page: Page, email: string) { - const emailInputSelector = 'input[type="email"]' - - try { - // Wait for email field - const emailField = await page.waitForSelector(emailInputSelector, { state: 'visible', timeout: 2000 }).catch(() => null) - if (!emailField) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Email field not found', 'warn') - return - } - - await this.bot.utils.wait(1000) - - // Check if email is prefilled - const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 5000 }).catch(() => null) - if (emailPrefilled) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft') - } else { - // Else clear and fill email - await page.fill(emailInputSelector, '') - await this.bot.utils.wait(500) - await page.fill(emailInputSelector, email) - await this.bot.utils.wait(1000) - } - - const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null) - if (nextButton) { - await nextButton.click() - await this.bot.utils.wait(2000) - this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully') - } else { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after email entry', 'warn') - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'LOGIN', `Email entry failed: ${error}`, 'error') - } - } - - private async enterPassword(page: Page, password: string) { - const passwordInputSelector = 'input[type="password"]' - const skip2FASelector = '#idA_PWD_SwitchToPassword'; - try { - const skip2FAButton = await page.waitForSelector(skip2FASelector, { timeout: 2000 }).catch(() => null) - if (skip2FAButton) { - await skip2FAButton.click() - await this.bot.utils.wait(2000) - this.bot.log(this.bot.isMobile, 'LOGIN', 'Skipped 2FA') - } else { - this.bot.log(this.bot.isMobile, 'LOGIN', 'No 2FA skip button found, proceeding with password entry') - } - const viewFooter = await page.waitForSelector('#view > div > span:nth-child(6)', { timeout: 2000 }).catch(() => null) - const passwordField1 = await page.waitForSelector(passwordInputSelector, { timeout: 5000 }).catch(() => null) - if (viewFooter && !passwordField1) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Page "Get a code to sign in" found by "viewFooter"') - - const otherWaysButton = await viewFooter.$('span[role="button"]') - if (otherWaysButton) { - await otherWaysButton.click() - await this.bot.utils.wait(5000) - - const secondListItem = page.locator('[role="listitem"]').nth(1) - if (await secondListItem.isVisible()) { - await secondListItem.click() - } - } - } - - // Wait for password field - const passwordField = await page.waitForSelector(passwordInputSelector, { state: 'visible', timeout: 5000 }).catch(() => null) - if (!passwordField) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Password field not found, possibly 2FA required', 'warn') - await this.handle2FA(page) - return - } - - await this.bot.utils.wait(1000) - - // Clear and fill password - await page.fill(passwordInputSelector, '') - await this.bot.utils.wait(500) - await page.fill(passwordInputSelector, password) - await this.bot.utils.wait(1000) - - const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null) - if (nextButton) { - await nextButton.click() - await this.bot.utils.wait(2000) - this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully') - } else { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after password entry', 'warn') - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'LOGIN', `Password entry failed: ${error}`, 'error') - await this.handle2FA(page) - } - } - - private async handle2FA(page: Page) { - try { - const numberToPress = await this.get2FACode(page) - if (numberToPress) { - // Authenticator App verification - await this.authAppVerification(page, numberToPress) - } else { - // SMS verification - await this.authSMSVerification(page) - } - } catch (error) { - this.bot.log(this.bot.isMobile, 'LOGIN', `2FA handling failed: ${error}`) - } - } - - private async get2FACode(page: Page): Promise { - try { - const element = await page.waitForSelector('#displaySign, div[data-testid="displaySign"]>span', { state: 'visible', timeout: 2000 }) - return await element.textContent() - } catch { - if (this.bot.config.parallel) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Script running in parallel, can only send 1 2FA request per account at a time!', 'log', 'yellow') - this.bot.log(this.bot.isMobile, 'LOGIN', 'Trying again in 60 seconds! Please wait...', 'log', 'yellow') - - // eslint-disable-next-line no-constant-condition - while (true) { - const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null) - if (button) { - await this.bot.utils.wait(60000) - await button.click() - - continue - } else { - break - } - } - } - - await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => { }) - await this.bot.utils.wait(2000) - const element = await page.waitForSelector('#displaySign, div[data-testid="displaySign"]>span', { state: 'visible', timeout: 2000 }) - return await element.textContent() - } - } - - private async authAppVerification(page: Page, numberToPress: string | null) { - // eslint-disable-next-line no-constant-condition - while (true) { - try { - this.bot.log(this.bot.isMobile, 'LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`) - this.bot.log(this.bot.isMobile, 'LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds') - - await page.waitForSelector('form[name="f1"]', { state: 'detached', timeout: 60000 }) - - this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!') - break - } catch { - this.bot.log(this.bot.isMobile, 'LOGIN', 'The code is expired. Trying to get a new code...') - // await page.click('button[aria-describedby="pushNotificationsTitle errorDescription"]') - const primaryButton = await page.waitForSelector('button[data-testid="primaryButton"]', { state: 'visible', timeout: 5000 }).catch(() => null) - if (primaryButton) { - await primaryButton.click() - } - numberToPress = await this.get2FACode(page) - } - } - } - - private async authSMSVerification(page: Page) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'SMS 2FA code required. Waiting for user input...') - - const code = await new Promise((resolve) => { - rl.question('Enter 2FA code:\n', (input: string) => { - rl.close() - resolve(input) - }) - }) - - await page.fill('input[name="otc"]', code) - await page.keyboard.press('Enter') - this.bot.log(this.bot.isMobile, 'LOGIN', '2FA code entered successfully') - } - - async getMobileAccessToken(page: Page, email: string) { - const authorizeUrl = new URL(this.authBaseUrl) - - authorizeUrl.searchParams.append('response_type', 'code') - authorizeUrl.searchParams.append('client_id', this.clientId) - authorizeUrl.searchParams.append('redirect_uri', this.redirectUrl) - authorizeUrl.searchParams.append('scope', this.scope) - authorizeUrl.searchParams.append('state', crypto.randomBytes(16).toString('hex')) - authorizeUrl.searchParams.append('access_type', 'offline_access') - authorizeUrl.searchParams.append('login_hint', email) - - // Disable FIDO for OAuth flow as well (reduces passkey prompts resurfacing) - await page.route('**/GetCredentialType.srf*', (route: any) => { - const body = JSON.parse(route.request().postData() || '{}') - body.isFidoSupported = false - route.continue({ postData: JSON.stringify(body) }) - }).catch(()=>{}) - - await page.goto(authorizeUrl.href) - - let currentUrl = new URL(page.url()) - let code: string - - const authStart = Date.now() - this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'Waiting for authorization...') - // eslint-disable-next-line no-constant-condition - while (true) { - // Attempt to dismiss passkey/passkey-like screens quickly (non-blocking) - await this.tryDismissPasskeyPrompt(page) - if (currentUrl.hostname === 'login.live.com' && currentUrl.pathname === '/oauth20_desktop.srf') { - code = currentUrl.searchParams.get('code')! - break - } - - currentUrl = new URL(page.url()) - // Shorter wait to react faster to passkey prompt - await this.bot.utils.wait(1000) - } - - const body = new URLSearchParams() - body.append('grant_type', 'authorization_code') - body.append('client_id', this.clientId) - body.append('code', code) - body.append('redirect_uri', this.redirectUrl) - - const tokenRequest: AxiosRequestConfig = { - url: this.tokenUrl, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data: body.toString() - } - - const tokenResponse = await this.bot.axios.request(tokenRequest) - const tokenData: OAuth = await tokenResponse.data - - const authDuration = Date.now() - authStart - this.bot.log(this.bot.isMobile, 'LOGIN-APP', `Successfully authorized in ${Math.round(authDuration/1000)}s`) - return tokenData.access_token - } - - // Utils - - private async checkLoggedIn(page: Page) { - const targetHostname = 'rewards.bing.com' - const targetPathname = '/' - - const start = Date.now() - const maxWaitMs = Number(process.env.LOGIN_MAX_WAIT_MS || 180000) // default 3 minutes - let guidanceLogged = false - - // eslint-disable-next-line no-constant-condition - while (true) { - await this.dismissLoginMessages(page) - const currentURL = new URL(page.url()) - if (currentURL.hostname === targetHostname && currentURL.pathname === targetPathname) { - break - } - - // If we keep looping without prompts for too long, advise and fail fast - const elapsed = Date.now() - start - if (elapsed > maxWaitMs) { - if (!guidanceLogged) { - this.bot.log(this.bot.isMobile, 'LOGIN-GUIDE', 'Login taking too long without prompts.') - this.bot.log(this.bot.isMobile, 'LOGIN-GUIDE', 'Tip: Enable passwordless sign-in (Microsoft Authenticator "number match") or add a TOTP secret in accounts.json to auto-fill OTP.') - this.bot.log(this.bot.isMobile, 'LOGIN-GUIDE', 'You can also set LOGIN_MAX_WAIT_MS to increase this timeout if needed.') - guidanceLogged = true - } - throw this.bot.log(this.bot.isMobile, 'LOGIN-TIMEOUT', `Login timed out after ${Math.round(elapsed/1000)}s without completing`, 'error') - } - } - - // Wait for login to complete - await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 }) - this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal') - } - - private lastNoPromptLog: number = 0 - private noPromptIterations: number = 0 - private async dismissLoginMessages(page: Page) { - let didSomething = false - - // PASSKEY / Windows Hello / Sign in faster - const passkeyVideo = await page.waitForSelector('[data-testid="biometricVideo"]', { timeout: 1000 }).catch(() => null) - if (passkeyVideo) { - const skipButton = await page.$('button[data-testid="secondaryButton"]') - if (skipButton) { - await skipButton.click().catch(()=>{}) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', 'Passkey dialog detected (video heuristic) -> clicked "Skip for now"') - } - this.passkeyHandled = true - await page.waitForTimeout(300) - didSomething = true - } - } - if (!didSomething) { - const titleEl = await page.waitForSelector('[data-testid="title"]', { timeout: 800 }).catch(() => null) - const titleText = (titleEl ? (await titleEl.textContent()) : '')?.trim() || '' - const looksLikePasskey = /sign in faster|passkey|fingerprint|face|pin/i.test(titleText) - const secondaryBtn = await page.waitForSelector('button[data-testid="secondaryButton"]', { timeout: 500 }).catch(() => null) - const primaryBtn = await page.waitForSelector('button[data-testid="primaryButton"]', { timeout: 500 }).catch(() => null) - if (looksLikePasskey && secondaryBtn) { - await secondaryBtn.click().catch(()=>{}) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', `Passkey dialog detected (title: "${titleText}") -> clicked secondary`) - } - this.passkeyHandled = true - await page.waitForTimeout(300) - didSomething = true - } else if (!didSomething && secondaryBtn && primaryBtn) { - const secText = (await secondaryBtn.textContent() || '').trim() - if (/skip for now/i.test(secText)) { - await secondaryBtn.click().catch(()=>{}) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', 'Passkey dialog (pair heuristic) -> clicked secondary (Skip for now)') - } - this.passkeyHandled = true - await page.waitForTimeout(300) - didSomething = true - } - } - if (!didSomething) { - const skipByText = await page.locator('xpath=//button[contains(normalize-space(.), "Skip for now")]').first() - if (await skipByText.isVisible().catch(()=>false)) { - await skipByText.click().catch(()=>{}) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', 'Passkey dialog (text fallback) -> clicked "Skip for now"') - } - this.passkeyHandled = true - await page.waitForTimeout(300) - didSomething = true - } - } - if (!didSomething) { - const closeBtn = await page.$('#close-button') - if (closeBtn) { - await closeBtn.click().catch(()=>{}) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', 'Attempted close button on potential passkey modal') - } - this.passkeyHandled = true - await page.waitForTimeout(300) - } - } - } - - // KMSI (Keep me signed in) prompt - const kmsi = await page.waitForSelector('[data-testid="kmsiVideo"]', { timeout: 800 }).catch(()=>null) - if (kmsi) { - const yesButton = await page.$('button[data-testid="primaryButton"]') - if (yesButton) { - await yesButton.click().catch(()=>{}) - this.bot.log(this.bot.isMobile, 'LOGIN-KMSI', 'KMSI dialog detected -> accepted (Yes)') - await page.waitForTimeout(300) - didSomething = true - } - } - - if (!didSomething) { - this.noPromptIterations++ - const now = Date.now() - if (this.noPromptIterations === 1 || (now - this.lastNoPromptLog) > 10000) { - this.lastNoPromptLog = now - this.bot.log(this.bot.isMobile, 'LOGIN-NO-PROMPT', `No dialogs (x${this.noPromptIterations})`) - // Reset counter if it grows large to keep number meaningful - if (this.noPromptIterations > 50) this.noPromptIterations = 0 - } - } else { - // Reset counters after an interaction - this.noPromptIterations = 0 - } - } - - /** Lightweight passkey prompt dismissal used in mobile OAuth loop */ - private async tryDismissPasskeyPrompt(page: Page) { - try { - // Fast existence checks with very small timeouts to avoid slowing the loop - const titleEl = await page.waitForSelector('[data-testid="title"]', { timeout: 500 }).catch(() => null) - const secondaryBtn = await page.waitForSelector('button[data-testid="secondaryButton"]', { timeout: 500 }).catch(() => null) - // Direct text locator fallback (sometimes data-testid changes) - const textSkip = secondaryBtn ? null : await page.locator('xpath=//button[contains(normalize-space(.), "Skip for now")]').first().isVisible().catch(()=>false) - if (secondaryBtn) { - // Heuristic: if title indicates passkey or both primary/secondary exist with typical text - let shouldClick = false - let titleText = '' - if (titleEl) { - titleText = (await titleEl.textContent() || '').trim() - if (/sign in faster|passkey|fingerprint|face|pin/i.test(titleText)) { - shouldClick = true - } - } - if (!shouldClick && textSkip) { - shouldClick = true - } - if (!shouldClick) { - // Fallback text probe on the secondary button itself - const btnText = (await secondaryBtn.textContent() || '').trim() - if (/skip for now/i.test(btnText)) { - shouldClick = true - } - } - if (shouldClick) { - await secondaryBtn.click().catch(() => { }) - if (!this.passkeyHandled) { - this.bot.log(this.bot.isMobile, 'LOGIN-PASSKEY', `Passkey prompt (loop) -> clicked skip${titleText ? ` (title: ${titleText})` : ''}`) - } - this.passkeyHandled = true - await this.bot.utils.wait(500) - } - } - } catch { /* ignore minor errors */ } - } - - private async checkBingLogin(page: Page): Promise { - try { - this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Verifying Bing login') - await page.goto('https://www.bing.com/fd/auth/signin?action=interactive&provider=windows_live_id&return_url=https%3A%2F%2Fwww.bing.com%2F') - - const maxIterations = 5 - - for (let iteration = 1; iteration <= maxIterations; iteration++) { - const currentUrl = new URL(page.url()) - - if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') { - await this.bot.browser.utils.tryDismissAllMessages(page) - - const loggedIn = await this.checkBingLoginStatus(page) - // If mobile browser, skip this step - if (loggedIn || this.bot.isMobile) { - this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing login verification passed!') - break - } - } - - await this.bot.utils.wait(1000) - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'An error occurred:' + error, 'error') - } - } - - private async checkBingLoginStatus(page: Page): Promise { - try { - await page.waitForSelector('#id_n', { timeout: 5000 }) - return true - } catch (error) { - return false - } - } - - private async checkAccountLocked(page: Page) { - await this.bot.utils.wait(2000) - const isLocked = await page.waitForSelector('#serviceAbuseLandingTitle', { state: 'visible', timeout: 1000 }).then(() => true).catch(() => false) - if (isLocked) { - throw this.bot.log(this.bot.isMobile, 'CHECK-LOCKED', 'This account has been locked! Remove the account from "accounts.json" and restart!', 'error') - } - } -} diff --git a/src/functions/Workers.ts b/src/functions/Workers.ts deleted file mode 100644 index efe5da5..0000000 --- a/src/functions/Workers.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData' - -import { MicrosoftRewardsBot } from '../index' - -export class Workers { - public bot: MicrosoftRewardsBot - - constructor(bot: MicrosoftRewardsBot) { - this.bot = bot - } - - // Daily Set - async doDailySet(page: Page, data: DashboardData) { - const todayData = data.dailySetPromotions[this.bot.utils.getFormattedDate()] - - const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? [] - - if (!activitiesUncompleted.length) { - this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All Daily Set" items have already been completed') - return - } - - // Solve Activities - this.bot.log(this.bot.isMobile, 'DAILY-SET', 'Started solving "Daily Set" items') - - await this.solveActivities(page, activitiesUncompleted) - - page = await this.bot.browser.utils.getLatestTab(page) - - // Always return to the homepage if not already - await this.bot.browser.func.goHome(page) - - this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have been completed') - } - - // Punch Card - async doPunchCard(page: Page, data: DashboardData) { - - const punchCardsUncompleted = data.punchCards?.filter(x => x.parentPromotion && !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards - - if (!punchCardsUncompleted.length) { - this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Cards" have already been completed') - return - } - - for (const punchCard of punchCardsUncompleted) { - - // Ensure parentPromotion exists before proceeding - if (!punchCard.parentPromotion?.title) { - this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Skipped punchcard "${punchCard.name}" | Reason: Parent promotion is missing!`, 'warn') - continue - } - - // Get latest page for each card - page = await this.bot.browser.utils.getLatestTab(page) - - const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities - - // Solve Activities - this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`) - - // Got to punch card index page in a new tab - await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL }) - - // Wait for new page to load, max 10 seconds, however try regardless in case of error - await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { }) - - await this.solveActivities(page, activitiesUncompleted, punchCard) - - page = await this.bot.browser.utils.getLatestTab(page) - - const pages = page.context().pages() - - if (pages.length > 3) { - await page.close() - } else { - await this.bot.browser.func.goHome(page) - } - - this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`) - } - - this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Card" items have been completed') - } - - // More Promotions - async doMorePromotions(page: Page, data: DashboardData) { - const morePromotions = data.morePromotions - - // Check if there is a promotional item - if (data.promotionalItem) { // Convert and add the promotional item to the array - morePromotions.push(data.promotionalItem as unknown as MorePromotion) - } - - const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0 && x.exclusiveLockedFeatureStatus !== 'locked') ?? [] - - if (!activitiesUncompleted.length) { - this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have already been completed') - return - } - - // Solve Activities - this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'Started solving "More Promotions" items') - - page = await this.bot.browser.utils.getLatestTab(page) - - await this.solveActivities(page, activitiesUncompleted) - - page = await this.bot.browser.utils.getLatestTab(page) - - // Always return to the homepage if not already - await this.bot.browser.func.goHome(page) - - this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have been completed') - } - - // Solve all the different types of activities - private async solveActivities(activityPage: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) { - const activityInitial = activityPage.url() // Homepage for Daily/More and Index for promotions - - for (const activity of activities) { - try { - // Reselect the worker page - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - - const pages = activityPage.context().pages() - if (pages.length > 3) { - await activityPage.close() - - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - } - - await this.bot.utils.wait(1000) - - if (activityPage.url() !== activityInitial) { - await activityPage.goto(activityInitial) - } - - - let selector = `[data-bi-id^="${activity.offerId}"] .pointLink:not(.contentContainer .pointLink)` - - if (punchCard) { - selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity) - - } else if (activity.name.toLowerCase().includes('membercenter') || activity.name.toLowerCase().includes('exploreonbing')) { - selector = `[data-bi-id^="${activity.name}"] .pointLink:not(.contentContainer .pointLink)` - } - - // Wait for the new tab to fully load, ignore error. - /* - Due to common false timeout on this function, we're ignoring the error regardless, if it worked then it's faster, - if it didn't then it gave enough time for the page to load. - */ - await activityPage.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { }) - await this.bot.utils.wait(2000) - - switch (activity.promotionType) { - // Quiz (Poll, Quiz or ABC) - case 'quiz': - switch (activity.pointProgressMax) { - // Poll or ABC (Usually 10 points) - case 10: - // Normal poll - if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) { - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doPoll(activityPage) - } else { // ABC - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doABC(activityPage) - } - break - - // This Or That Quiz (Usually 50 points) - case 50: - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doThisOrThat(activityPage) - break - - // Quizzes are usually 30-40 points - default: - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doQuiz(activityPage) - break - } - break - - // UrlReward (Visit) - case 'urlreward': - // Search on Bing are subtypes of "urlreward" - if (activity.name.toLowerCase().includes('exploreonbing')) { - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "SearchOnBing" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doSearchOnBing(activityPage, activity) - - } else { - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`) - await activityPage.click(selector) - activityPage = await this.bot.browser.utils.getLatestTab(activityPage) - await this.bot.activities.doUrlReward(activityPage) - } - break - - // Unsupported types - default: - this.bot.log(this.bot.isMobile, 'ACTIVITY', `Skipped activity "${activity.title}" | Reason: Unsupported type: "${activity.promotionType}"!`, 'warn') - break - } - - // Cooldown - await this.bot.utils.wait(2000) - - } catch (error) { - this.bot.log(this.bot.isMobile, 'ACTIVITY', 'An error occurred:' + error, 'error') - } - - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/ABC.ts b/src/functions/activities/ABC.ts deleted file mode 100644 index 72e61d7..0000000 --- a/src/functions/activities/ABC.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { Workers } from '../Workers' - - -export class ABC extends Workers { - - async doABC(page: Page) { - this.bot.log(this.bot.isMobile, 'ABC', 'Trying to complete poll') - - try { - let $ = await this.bot.browser.func.loadInCheerio(page) - - // Don't loop more than 15 in case unable to solve, would lock otherwise - const maxIterations = 15 - let i - for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) { - await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10000 }) - - const answers = $('.wk_OptionClickClass') - const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id'] - - await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10000 }) - - await this.bot.utils.wait(2000) - await page.click(`#${answer}`) // Click answer - - await this.bot.utils.wait(4000) - await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10000 }) - await page.click('div.wk_button') // Click next question button - - page = await this.bot.browser.utils.getLatestTab(page) - $ = await this.bot.browser.func.loadInCheerio(page) - await this.bot.utils.wait(1000) - } - - await this.bot.utils.wait(4000) - await page.close() - - if (i === maxIterations) { - this.bot.log(this.bot.isMobile, 'ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn') - } else { - this.bot.log(this.bot.isMobile, 'ABC', 'Completed the ABC successfully') - } - - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'ABC', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/DailyCheckIn.ts b/src/functions/activities/DailyCheckIn.ts deleted file mode 100644 index 23d821a..0000000 --- a/src/functions/activities/DailyCheckIn.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { randomBytes } from 'crypto' -import { AxiosRequestConfig } from 'axios' - -import { Workers } from '../Workers' - -import { DashboardData } from '../../interface/DashboardData' - - -export class DailyCheckIn extends Workers { - public async doDailyCheckIn(accessToken: string, data: DashboardData) { - this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'Starting Daily Check In') - - try { - let geoLocale = data.userProfile.attributes.country - geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' - - const jsonData = { - amount: 1, - country: geoLocale, - id: randomBytes(64).toString('hex'), - type: 101, - attributes: { - offerid: 'Gamification_Sapphire_DailyCheckIn' - } - } - - const claimRequest: AxiosRequestConfig = { - url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities', - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'X-Rewards-Country': geoLocale, - 'X-Rewards-Language': 'en' - }, - data: JSON.stringify(jsonData) - } - - const claimResponse = await this.bot.axios.request(claimRequest) - const claimedPoint = parseInt((await claimResponse.data).response?.activity?.p) ?? 0 - - this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today') - } catch (error) { - this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/Poll.ts b/src/functions/activities/Poll.ts deleted file mode 100644 index 526a2e0..0000000 --- a/src/functions/activities/Poll.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { Workers } from '../Workers' - - -export class Poll extends Workers { - - async doPoll(page: Page) { - this.bot.log(this.bot.isMobile, 'POLL', 'Trying to complete poll') - - try { - const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}` - - await page.waitForSelector(buttonId, { state: 'visible', timeout: 10000 }).catch(() => { }) // We're gonna click regardless or not - await this.bot.utils.wait(2000) - - await page.click(buttonId) - - await this.bot.utils.wait(4000) - await page.close() - - this.bot.log(this.bot.isMobile, 'POLL', 'Completed the poll successfully') - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'POLL', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts deleted file mode 100644 index e0a15a7..0000000 --- a/src/functions/activities/Quiz.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { Workers } from '../Workers' - - -export class Quiz extends Workers { - - async doQuiz(page: Page) { - this.bot.log(this.bot.isMobile, 'QUIZ', 'Trying to complete quiz') - - try { - // Check if the quiz has been started or not - const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false) - if (quizNotStarted) { - await page.click('#rqStartQuiz') - } else { - this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz has already been started, trying to finish it') - } - - await this.bot.utils.wait(2000) - - let quizData = await this.bot.browser.func.getQuizData(page) - const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining - - // All questions - for (let question = 0; question < questionsRemaining; question++) { - - if (quizData.numberOfOptions === 8) { - const answers: string[] = [] - - for (let i = 0; i < quizData.numberOfOptions; i++) { - const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 }) - const answerAttribute = await answerSelector?.evaluate((el: any) => el.getAttribute('iscorrectoption')) - - if (answerAttribute && answerAttribute.toLowerCase() === 'true') { - answers.push(`#rqAnswerOption${i}`) - } - } - - // Click the answers - for (const answer of answers) { - await page.waitForSelector(answer, { state: 'visible', timeout: 2000 }) - - // Click the answer on page - await page.click(answer) - - const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page) - if (!refreshSuccess) { - await page.close() - this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error') - return - } - } - - // Other type quiz, lightspeed - } else if ([2, 3, 4].includes(quizData.numberOfOptions)) { - quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data - const correctOption = quizData.correctAnswer - - for (let i = 0; i < quizData.numberOfOptions; i++) { - - const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 }) - const dataOption = await answerSelector?.evaluate((el: any) => el.getAttribute('data-option')) - - if (dataOption === correctOption) { - // Click the answer on page - await page.click(`#rqAnswerOption${i}`) - - const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page) - if (!refreshSuccess) { - await page.close() - this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error') - return - } - } - } - await this.bot.utils.wait(2000) - } - } - - // Done with - await this.bot.utils.wait(2000) - await page.close() - - this.bot.log(this.bot.isMobile, 'QUIZ', 'Completed the quiz successfully') - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/ReadToEarn.ts b/src/functions/activities/ReadToEarn.ts deleted file mode 100644 index 65c8862..0000000 --- a/src/functions/activities/ReadToEarn.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { randomBytes } from 'crypto' -import { AxiosRequestConfig } from 'axios' - -import { Workers } from '../Workers' - -import { DashboardData } from '../../interface/DashboardData' - - -export class ReadToEarn extends Workers { - public async doReadToEarn(accessToken: string, data: DashboardData) { - this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Starting Read to Earn') - - try { - let geoLocale = data.userProfile.attributes.country - geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us' - - const userDataRequest: AxiosRequestConfig = { - url: 'https://prod.rewardsplatform.microsoft.com/dapi/me', - method: 'GET', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'X-Rewards-Country': geoLocale, - 'X-Rewards-Language': 'en' - } - } - const userDataResponse = await this.bot.axios.request(userDataRequest) - const userData = (await userDataResponse.data).response - let userBalance = userData.balance - - const jsonData = { - amount: 1, - country: geoLocale, - id: '1', - type: 101, - attributes: { - offerid: 'ENUS_readarticle3_30points' - } - } - - const articleCount = 10 - for (let i = 0; i < articleCount; ++i) { - jsonData.id = randomBytes(64).toString('hex') - const claimRequest = { - url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities', - method: 'POST', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'X-Rewards-Country': geoLocale, - 'X-Rewards-Language': 'en' - }, - data: JSON.stringify(jsonData) - } - - const claimResponse = await this.bot.axios.request(claimRequest) - const newBalance = (await claimResponse.data).response.balance - - if (newBalance == userBalance) { - this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Read all available articles') - break - } else { - this.bot.log(this.bot.isMobile, 'READ-TO-EARN', `Read article ${i + 1} of ${articleCount} max | Gained ${newBalance - userBalance} Points`) - userBalance = newBalance - await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max)))) - } - } - - this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Completed Read to Earn') - } catch (error) { - this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'An error occurred:' + error, 'error') - } - } -} \ No newline at end of file diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts deleted file mode 100644 index c96c2e5..0000000 --- a/src/functions/activities/Search.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { Page } from 'rebrowser-playwright' -import { platform } from 'os' - -import { Workers } from '../Workers' - -import { Counters, DashboardData } from '../../interface/DashboardData' -import { GoogleSearch } from '../../interface/Search' -import { AxiosRequestConfig } from 'axios' - -type GoogleTrendsResponse = [ - string, - [ - string, - ...null[], - [string, ...string[]] - ][] -]; - -export class Search extends Workers { - private bingHome = 'https://bing.com' - private searchPageURL = '' - - public async doSearch(page: Page, data: DashboardData) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Starting Bing searches') - - page = await this.bot.browser.utils.getLatestTab(page) - - let searchCounters: Counters = await this.bot.browser.func.getSearchPoints() - let missingPoints = this.calculatePoints(searchCounters) - - if (missingPoints === 0) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Bing searches have already been completed') - return - } - - // Generate search queries - let googleSearchQueries = await this.getGoogleTrends(this.bot.config.searchSettings.useGeoLocaleQueries ? data.userProfile.attributes.country : 'US') - googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries) - - // Deduplicate the search terms - googleSearchQueries = [...new Set(googleSearchQueries)] - - // Go to bing - await page.goto(this.searchPageURL ? this.searchPageURL : this.bingHome) - - await this.bot.utils.wait(2000) - - await this.bot.browser.utils.tryDismissAllMessages(page) - - let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it doesn't continue after 5 more searches with alternative queries, abort search - - const queries: string[] = [] - // Mobile search doesn't seem to like related queries? - googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : queries.push(x.topic, ...x.related) }) - - // Loop over Google search queries - for (let i = 0; i < queries.length; i++) { - const query = queries[i] as string - - this.bot.log(this.bot.isMobile, 'SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query}`) - - searchCounters = await this.bingSearch(page, query) - const newMissingPoints = this.calculatePoints(searchCounters) - - // If the new point amount is the same as before - if (newMissingPoints == missingPoints) { - maxLoop++ // Add to max loop - } else { // There has been a change in points - maxLoop = 0 // Reset the loop - } - - missingPoints = newMissingPoints - - if (missingPoints === 0) { - break - } - - // Only for mobile searches - if (maxLoop > 5 && this.bot.isMobile) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 5 iterations, likely bad User-Agent', 'warn') - break - } - - // If we didn't gain points for 10 iterations, assume it's stuck - if (maxLoop > 10) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn') - maxLoop = 0 // Reset to 0 so we can retry with related searches below - break - } - } - - // Only for mobile searches - if (missingPoints > 0 && this.bot.isMobile) { - return - } - - // If we still got remaining search queries, generate extra ones - if (missingPoints > 0) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`) - - let i = 0 - while (missingPoints > 0) { - const query = googleSearchQueries[i++] as GoogleSearch - - // Get related search terms to the Google search queries - const relatedTerms = await this.getRelatedTerms(query?.topic) - if (relatedTerms.length > 3) { - // Search for the first 2 related terms - for (const term of relatedTerms.slice(1, 3)) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term}`) - - searchCounters = await this.bingSearch(page, term) - const newMissingPoints = this.calculatePoints(searchCounters) - - // If the new point amount is the same as before - if (newMissingPoints == missingPoints) { - maxLoop++ // Add to max loop - } else { // There has been a change in points - maxLoop = 0 // Reset the loop - } - - missingPoints = newMissingPoints - - // If we satisfied the searches - if (missingPoints === 0) { - break - } - - // Try 5 more times, then we tried a total of 15 times, fair to say it's stuck - if (maxLoop > 5) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn') - return - } - } - } - } - } - - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Completed searches') - } - - private async bingSearch(searchPage: Page, query: string) { - const platformControlKey = platform() === 'darwin' ? 'Meta' : 'Control' - - // Try a max of 5 times - for (let i = 0; i < 5; i++) { - try { - // This page had already been set to the Bing.com page or the previous search listing, we just need to select it - searchPage = await this.bot.browser.utils.getLatestTab(searchPage) - - // Go to top of the page - await searchPage.evaluate(() => { - window.scrollTo(0, 0) - }) - - await this.bot.utils.wait(500) - - const searchBar = '#sb_form_q' - await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10000 }) - await searchPage.click(searchBar) // Focus on the textarea - await this.bot.utils.wait(500) - await searchPage.keyboard.down(platformControlKey) - await searchPage.keyboard.press('A') - await searchPage.keyboard.press('Backspace') - await searchPage.keyboard.up(platformControlKey) - await searchPage.keyboard.type(query) - await searchPage.keyboard.press('Enter') - - await this.bot.utils.wait(3000) - - // Bing.com in Chrome opens a new tab when searching - const resultPage = await this.bot.browser.utils.getLatestTab(searchPage) - this.searchPageURL = new URL(resultPage.url()).href // Set the results page - - await this.bot.browser.utils.reloadBadPage(resultPage) - - if (this.bot.config.searchSettings.scrollRandomResults) { - await this.bot.utils.wait(2000) - await this.randomScroll(resultPage) - } - - if (this.bot.config.searchSettings.clickRandomResults) { - await this.bot.utils.wait(2000) - await this.clickRandomLink(resultPage) - } - - // Delay between searches - await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max)))) - - return await this.bot.browser.func.getSearchPoints() - - } catch (error) { - if (i === 5) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error') - break - - } - - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search failed, An error occurred:' + error, 'error') - this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn') - - // Reset the tabs - const lastTab = await this.bot.browser.utils.getLatestTab(searchPage) - await this.closeTabs(lastTab) - - await this.bot.utils.wait(4000) - } - } - - this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search failed after 5 retries, ending', 'error') - return await this.bot.browser.func.getSearchPoints() - } - - private async getGoogleTrends(geoLocale: string = 'US'): Promise { - const queryTerms: GoogleSearch[] = [] - this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`) - - try { - const request: AxiosRequestConfig = { - url: 'https://trends.google.com/_/TrendsUi/data/batchexecute', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' - }, - data: `f.req=[[[i0OFE,"[null, null, \\"${geoLocale.toUpperCase()}\\", 0, null, 48]"]]]` - } - - const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyGoogleTrends) - const rawText = response.data - - const trendsData = this.extractJsonFromResponse(rawText) - if (!trendsData) { - throw this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Failed to parse Google Trends response', 'error') - } - - const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)]) - if (mappedTrendsData.length < 90) { - this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Insufficient search queries, falling back to US', 'warn') - return this.getGoogleTrends() - } - - for (const [topic, relatedQueries] of mappedTrendsData) { - queryTerms.push({ - topic: topic as string, - related: relatedQueries as string[] - }) - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error') - } - - return queryTerms - } - - private extractJsonFromResponse(text: string): GoogleTrendsResponse[1] | null { - const lines = text.split('\n') - for (const line of lines) { - const trimmed = line.trim() - if (trimmed.startsWith('[') && trimmed.endsWith(']')) { - try { - return JSON.parse(JSON.parse(trimmed)[0][2])[1] - } catch { - continue - } - } - } - - return null - } - - private async getRelatedTerms(term: string): Promise { - try { - const request = { - url: `https://api.bing.com/osjson.aspx?query=${term}`, - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - } - - const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyBingTerms) - - return response.data[1] as string[] - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-BING-RELATED', 'An error occurred:' + error, 'error') - } - - return [] - } - - private async randomScroll(page: Page) { - try { - const viewportHeight = await page.evaluate(() => window.innerHeight) - const totalHeight = await page.evaluate(() => document.body.scrollHeight) - const randomScrollPosition = Math.floor(Math.random() * (totalHeight - viewportHeight)) - - await page.evaluate((scrollPos: number) => { - window.scrollTo(0, scrollPos) - }, randomScrollPosition) - - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error') - } - } - - private async clickRandomLink(page: Page) { - try { - await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { }) // Since we don't really care if it did it or not - - // Only used if the browser is not the edge browser (continue on Edge popup) - await this.closeContinuePopup(page) - - // Stay for 10 seconds for page to load and "visit" - await this.bot.utils.wait(10000) - - // Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click - let lastTab = await this.bot.browser.utils.getLatestTab(page) - - let lastTabURL = new URL(lastTab.url()) // Get new tab info, this is the website we're visiting - - // Check if the URL is different from the original one, don't loop more than 5 times. - let i = 0 - while (lastTabURL.href !== this.searchPageURL && i < 5) { - - await this.closeTabs(lastTab) - - // End of loop, refresh lastPage - lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again - lastTabURL = new URL(lastTab.url()) // Get new tab info - i++ - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error') - } - } - - private async closeTabs(lastTab: Page) { - const browser = lastTab.context() - const tabs = browser.pages() - - try { - if (tabs.length > 2) { - // If more than 2 tabs are open, close the last tab - - await lastTab.close() - this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', `More than 2 were open, closed the last tab: "${new URL(lastTab.url()).host}"`) - - } else if (tabs.length === 1) { - // If only 1 tab is open, open a new one to search in - - const newPage = await browser.newPage() - await this.bot.utils.wait(1000) - - await newPage.goto(this.bingHome) - await this.bot.utils.wait(3000) - this.searchPageURL = newPage.url() - - this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'There was only 1 tab open, crated a new one') - } else { - // Else reset the last tab back to the search listing or Bing.com - - lastTab = await this.bot.browser.utils.getLatestTab(lastTab) - await lastTab.goto(this.searchPageURL ? this.searchPageURL : this.bingHome) - } - - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'An error occurred:' + error, 'error') - } - - } - - private calculatePoints(counters: Counters) { - const mobileData = counters.mobileSearch?.[0] // Mobile searches - const genericData = counters.pcSearch?.[0] // Normal searches - const edgeData = counters.pcSearch?.[1] // Edge searches - - const missingPoints = (this.bot.isMobile && mobileData) - ? mobileData.pointProgressMax - mobileData.pointProgress - : (edgeData ? edgeData.pointProgressMax - edgeData.pointProgress : 0) - + (genericData ? genericData.pointProgressMax - genericData.pointProgress : 0) - - return missingPoints - } - - private async closeContinuePopup(page: Page) { - try { - await page.waitForSelector('#sacs_close', { timeout: 1000 }) - const continueButton = await page.$('#sacs_close') - - if (continueButton) { - await continueButton.click() - } - } catch (error) { - // Continue if element is not found or other error occurs - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/SearchOnBing.ts b/src/functions/activities/SearchOnBing.ts deleted file mode 100644 index 1a8e91c..0000000 --- a/src/functions/activities/SearchOnBing.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Page } from 'playwright' -import * as fs from 'fs' -import path from 'path' - -import { Workers } from '../Workers' - -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' - - -export class SearchOnBing extends Workers { - - async doSearchOnBing(page: Page, activity: MorePromotion | PromotionalItem) { - this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Trying to complete SearchOnBing') - - try { - await this.bot.utils.wait(5000) - - await this.bot.browser.utils.tryDismissAllMessages(page) - - const query = await this.getSearchQuery(activity.title) - - const searchBar = '#sb_form_q' - await page.waitForSelector(searchBar, { state: 'visible', timeout: 10000 }) - await this.safeClick(page, searchBar) - await this.bot.utils.wait(500) - await page.keyboard.type(query) - await page.keyboard.press('Enter') - await this.bot.utils.wait(3000) - - await page.close() - - this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Completed the SearchOnBing successfully') - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'An error occurred:' + error, 'error') - } - } - - private async safeClick(page: Page, selector: string) { - try { - await page.click(selector, { timeout: 5000 }) - } catch (e: any) { - const msg = (e?.message || '') - if (/Timeout.*click/i.test(msg) || /intercepts pointer events/i.test(msg)) { - // Try to dismiss overlays then retry once - await this.bot.browser.utils.tryDismissAllMessages(page) - await this.bot.utils.wait(500) - await page.click(selector, { timeout: 5000 }) - } else { - throw e - } - } - } - - private async getSearchQuery(title: string): Promise { - interface Queries { - title: string; - queries: string[] - } - - let queries: Queries[] = [] - - try { - if (this.bot.config.searchOnBingLocalQueries) { - const data = fs.readFileSync(path.join(__dirname, '../queries.json'), 'utf8') - queries = JSON.parse(data) - } else { - // Fetch from the repo directly so the user doesn't need to redownload the script for the new activities - const response = await this.bot.axios.request({ - method: 'GET', - url: 'https://raw.githubusercontent.com/TheNetsky/Microsoft-Rewards-Script/refs/heads/main/src/functions/queries.json' - }) - queries = response.data - } - - const answers = queries.find(x => this.normalizeString(x.title) === this.normalizeString(title)) - const answer = answers ? this.bot.utils.shuffleArray(answers?.queries)[0] as string : title - - this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', `Fetched answer: ${answer} | question: ${title}`) - return answer - - } catch (error) { - this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', 'An error occurred:' + error, 'error') - return title - } - } - - private normalizeString(string: string): string { - return string.normalize('NFD').trim().toLowerCase().replace(/[^\x20-\x7E]/g, '').replace(/[?!]/g, '') - } -} \ No newline at end of file diff --git a/src/functions/activities/ThisOrThat.ts b/src/functions/activities/ThisOrThat.ts deleted file mode 100644 index f6e6edb..0000000 --- a/src/functions/activities/ThisOrThat.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { Workers } from '../Workers' - - -export class ThisOrThat extends Workers { - - async doThisOrThat(page: Page) { - this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Trying to complete ThisOrThat') - - - try { - // Check if the quiz has been started or not - const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false) - if (quizNotStarted) { - await page.click('#rqStartQuiz') - } else { - this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it') - } - - await this.bot.utils.wait(2000) - - // Solving - const quizData = await this.bot.browser.func.getQuizData(page) - const questionsRemaining = quizData.maxQuestions - (quizData.currentQuestionNumber - 1) // Amount of questions remaining - - for (let question = 0; question < questionsRemaining; question++) { - // Since there's no solving logic yet, randomly guess to complete - const buttonId = `#rqAnswerOption${Math.floor(this.bot.utils.randomNumber(0, 1))}` - await page.click(buttonId) - - const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page) - if (!refreshSuccess) { - await page.close() - this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error') - return - } - } - - this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Completed the ThisOrThat successfully') - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/activities/UrlReward.ts b/src/functions/activities/UrlReward.ts deleted file mode 100644 index b5c310e..0000000 --- a/src/functions/activities/UrlReward.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Page } from 'rebrowser-playwright' - -import { Workers } from '../Workers' - - -export class UrlReward extends Workers { - - async doUrlReward(page: Page) { - this.bot.log(this.bot.isMobile, 'URL-REWARD', 'Trying to complete UrlReward') - - try { - this.bot.utils.wait(2000) - - await page.close() - - this.bot.log(this.bot.isMobile, 'URL-REWARD', 'Completed the UrlReward successfully') - } catch (error) { - await page.close() - this.bot.log(this.bot.isMobile, 'URL-REWARD', 'An error occurred:' + error, 'error') - } - } - -} \ No newline at end of file diff --git a/src/functions/queries.json b/src/functions/queries.json deleted file mode 100644 index 6a8c4d0..0000000 --- a/src/functions/queries.json +++ /dev/null @@ -1,289 +0,0 @@ -[ - { - "title": "Houses near you", - "queries": [ - "Houses near me" - ] - }, - { - "title": "Feeling symptoms?", - "queries": [ - "Rash on forearm", - "Stuffy nose", - "Tickling cough" - ] - }, - { - "title": "Get your shopping done faster", - "queries": [ - "Buy PS5", - "Buy Xbox", - "Chair deals" - ] - }, - { - "title": "Translate anything", - "queries": [ - "Translate welcome home to Korean", - "Translate welcome home to Japanese", - "Translate goodbye to Japanese" - ] - }, - { - "title": "Search the lyrics of a song", - "queries": [ - "Debarge rhythm of the night lyrics" - ] - }, - { - "title": "Let's watch that movie again!", - "queries": [ - "Alien movie", - "Aliens movie", - "Alien 3 movie", - "Predator movie" - ] - }, - { - "title": "Plan a quick getaway", - "queries": [ - "Flights Amsterdam to Tokyo", - "Flights New York to Tokyo" - ] - }, - { - "title": "Discover open job roles", - "queries": [ - "jobs at Microsoft", - "Microsoft Job Openings", - "Jobs near me", - "jobs at Boeing worked" - ] - }, - { - "title": "You can track your package", - "queries": [ - "USPS tracking" - ] - }, - { - "title": "Find somewhere new to explore", - "queries": [ - "Directions to Berlin", - "Directions to Tokyo", - "Directions to New York" - ] - }, - { - "title": "Too tired to cook tonight?", - "queries": [ - "KFC near me", - "Burger King near me", - "McDonalds near me" - ] - }, - { - "title": "Quickly convert your money", - "queries": [ - "convert 250 USD to yen", - "convert 500 USD to yen" - ] - }, - { - "title": "Learn to cook a new recipe", - "queries": [ - "How to cook ratatouille", - "How to cook lasagna" - ] - }, - { - "title": "Find places to stay!", - "queries": [ - "Hotels Berlin Germany", - "Hotels Amsterdam Netherlands" - ] - }, - { - "title": "How's the economy?", - "queries": [ - "sp 500" - ] - }, - { - "title": "Who won?", - "queries": [ - "braves score" - ] - }, - { - "title": "Gaming time", - "queries": [ - "Overwatch video game", - "Call of duty video game" - ] - }, - { - "title": "Expand your vocabulary", - "queries": [ - "definition definition" - ] - }, - { - "title": "What time is it?", - "queries": [ - "Japan time", - "New York time" - ] - }, - - { - "title": "Maisons près de chez vous", - "queries": [ - "Maisons près de chez moi" - ] - }, - { - "title": "Vous ressentez des symptômes ?", - "queries": [ - "Éruption cutanée sur l'avant-bras", - "Nez bouché", - "Toux chatouilleuse" - ] - }, - { - "title": "Faites vos achats plus vite", - "queries": [ - "Acheter une PS5", - "Acheter une Xbox", - "Offres sur les chaises" - ] - }, - { - "title": "Traduisez tout !", - "queries": [ - "Traduction bienvenue à la maison en coréen", - "Traduction bienvenue à la maison en japonais", - "Traduction au revoir en japonais" - ] - }, - { - "title": "Rechercher paroles de chanson", - "queries": [ - "Paroles de Debarge rhythm of the night" - ] - }, - { - "title": "Et si nous regardions ce film une nouvelle fois?", - "queries": [ - "Alien film", - "Film Aliens", - "Film Alien 3", - "Film Predator" - ] - }, - { - "title": "Planifiez une petite escapade", - "queries": [ - "Vols Amsterdam-Tokyo", - "Vols New York-Tokyo" - ] - }, - { - "title": "Consulter postes à pourvoir", - "queries": [ - "emplois chez Microsoft", - "Offres d'emploi Microsoft", - "Emplois près de chez moi", - "emplois chez Boeing" - ] - }, - { - "title": "Vous pouvez suivre votre colis", - "queries": [ - "Suivi Chronopost" - ] - }, - { - "title": "Trouver un endroit à découvrir", - "queries": [ - "Itinéraire vers Berlin", - "Itinéraire vers Tokyo", - "Itinéraire vers New York" - ] - }, - { - "title": "Trop fatigué pour cuisiner ce soir ?", - "queries": [ - "KFC près de chez moi", - "Burger King près de chez moi", - "McDonalds près de chez moi" - ] - }, - { - "title": "Convertissez rapidement votre argent", - "queries": [ - "convertir 250 EUR en yen", - "convertir 500 EUR en yen" - ] - }, - { - "title": "Apprenez à cuisiner une nouvelle recette", - "queries": [ - "Comment faire cuire la ratatouille", - "Comment faire cuire les lasagnes" - ] - }, - { - "title": "Trouvez des emplacements pour rester!", - "queries": [ - "Hôtels Berlin Allemagne", - "Hôtels Amsterdam Pays-Bas" - ] - }, - { - "title": "Comment se porte l'économie ?", - "queries": [ - "CAC 40" - ] - }, - { - "title": "Qui a gagné ?", - "queries": [ - "score du Paris Saint-Germain" - ] - }, - { - "title": "Temps de jeu", - "queries": [ - "Jeu vidéo Overwatch", - "Jeu vidéo Call of Duty" - ] - }, - { - "title": "Enrichissez votre vocabulaire", - "queries": [ - "definition definition" - ] - }, - { - "title": "Quelle heure est-il ?", - "queries": [ - "Heure du Japon", - "Heure de New York" - ] - }, - { - "title": "Vérifier la météo", - "queries": [ - "Météo de Paris", - "Météo de la France" - ] - }, - { - "title": "Tenez-vous informé des sujets d'actualité", - "queries": [ - "Augmentation Impots", - "Mort célébrité" - ] - } -] diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 1585591..0000000 --- a/src/index.ts +++ /dev/null @@ -1,548 +0,0 @@ -import cluster from 'cluster' -// Use Page type from playwright for typings; at runtime rebrowser-playwright extends playwright -import type { Page } from 'playwright' - -import Browser from './browser/Browser' -import BrowserFunc from './browser/BrowserFunc' -import BrowserUtil from './browser/BrowserUtil' - -import { log } from './util/Logger' -import Util from './util/Utils' -import { loadAccounts, loadConfig, saveSessionData } from './util/Load' - -import { Login } from './functions/Login' -import { Workers } from './functions/Workers' -import Activities from './functions/Activities' - -import { Account } from './interface/Account' -import Axios from './util/Axios' -import fs from 'fs' -import path from 'path' - - -// Main bot class -export class MicrosoftRewardsBot { - public log: typeof log - public config - public utils: Util - public activities: Activities = new Activities(this) - public browser: { - func: BrowserFunc, - utils: BrowserUtil - } - public isMobile: boolean - public homePage!: Page - - private pointsCanCollect: number = 0 - private pointsInitial: number = 0 - - private activeWorkers: number - private mobileRetryAttempts: number - private browserFactory: Browser = new Browser(this) - private accounts: Account[] - private workers: Workers - private login = new Login(this) - private accessToken: string = '' - - // Summary collection (per process) - private accountSummaries: AccountSummary[] = [] - - //@ts-expect-error Will be initialized later - public axios: Axios - - constructor(isMobile: boolean) { - this.isMobile = isMobile - this.log = log - - this.accounts = [] - this.utils = new Util() - this.workers = new Workers(this) - this.browser = { - func: new BrowserFunc(this), - utils: new BrowserUtil(this) - } - this.config = loadConfig() - this.activeWorkers = this.config.clusters - this.mobileRetryAttempts = 0 - } - - async initialize() { - this.accounts = loadAccounts() - } - - async run() { - this.printBanner() - log('main', 'MAIN', `Bot started with ${this.config.clusters} clusters`) - - // Only cluster when there's more than 1 cluster demanded - if (this.config.clusters > 1) { - if (cluster.isPrimary) { - this.runMaster() - } else { - this.runWorker() - } - } else { - await this.runTasks(this.accounts) - } - } - - private printBanner() { - // Only print once (primary process or single cluster execution) - if (this.config.clusters > 1 && !cluster.isPrimary) return - try { - const pkgPath = path.join(__dirname, '../', 'package.json') - let version = 'unknown' - if (fs.existsSync(pkgPath)) { - const raw = fs.readFileSync(pkgPath, 'utf-8') - const pkg = JSON.parse(raw) - version = pkg.version || version - } - const banner = [ - ' __ __ _____ _____ _ ', - ' | \/ |/ ____| | __ \\ | | ', - ' | \ / | (___ ______| |__) |_____ ____ _ _ __ __| |___ ', - ' | |\/| |\\___ \\______| _ // _ \\ \\ /\\ / / _` | \'__/ _` / __|', - ' | | | |____) | | | \\ \\ __/ \\ V V / (_| | | | (_| \\__ \\', - ' |_| |_|_____/ |_| \\_\\___| \\_/\\_/ \\__,_|_| \\__,_|___/', - '', - ` Version: v${version}`, - '' - ].join('\n') - console.log(banner) - } catch { /* ignore banner errors */ } - } - - // Return summaries (used when clusters==1) - public getSummaries() { - return this.accountSummaries - } - - private runMaster() { - log('main', 'MAIN-PRIMARY', 'Primary process started') - - const accountChunks = this.utils.chunkArray(this.accounts, this.config.clusters) - - for (let i = 0; i < accountChunks.length; i++) { - const worker = cluster.fork() - const chunk = accountChunks[i] - ;(worker as any).send?.({ chunk }) - // Collect summaries from workers - worker.on('message', (msg: any) => { - if (msg && msg.type === 'summary' && Array.isArray(msg.data)) { - this.accountSummaries.push(...msg.data) - } - }) - } - - cluster.on('exit', (worker: any, code: number) => { - this.activeWorkers -= 1 - - log('main', 'MAIN-WORKER', `Worker ${worker.process.pid} destroyed | Code: ${code} | Active workers: ${this.activeWorkers}`, 'warn') - - // Check if all workers have exited - if (this.activeWorkers === 0) { - // All workers done -> send conclusion (if enabled) then exit - this.sendConclusion(this.accountSummaries).finally(() => { - log('main', 'MAIN-WORKER', 'All workers destroyed. Exiting main process!', 'warn') - process.exit(0) - }) - } - }) - } - - private runWorker() { - log('main', 'MAIN-WORKER', `Worker ${process.pid} spawned`) - // Receive the chunk of accounts from the master - ;(process as any).on('message', async ({ chunk }: { chunk: Account[] }) => { - await this.runTasks(chunk) - }) - } - - private async runTasks(accounts: Account[]) { - for (const account of accounts) { - log('main', 'MAIN-WORKER', `Started tasks for account ${account.email}`) - - const accountStart = Date.now() - let desktopInitial = 0 - let mobileInitial = 0 - let desktopCollected = 0 - let mobileCollected = 0 - const errors: string[] = [] - - this.axios = new Axios(account.proxy) - const verbose = process.env.DEBUG_REWARDS_VERBOSE === '1' - const formatFullErr = (label: string, e: any) => { - const base = shortErr(e) - if (verbose && e instanceof Error) { - return `${label}:${base} :: ${e.stack?.split('\n').slice(0,4).join(' | ')}` - } - return `${label}:${base}` - } - - if (this.config.parallel) { - const mobileInstance = new MicrosoftRewardsBot(true) - mobileInstance.axios = this.axios - // Run both and capture results with detailed logging - const desktopPromise = this.Desktop(account).catch(e => { - log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error') - errors.push(formatFullErr('desktop', e)); return null - }) - const mobilePromise = mobileInstance.Mobile(account).catch(e => { - log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error') - errors.push(formatFullErr('mobile', e)); return null - }) - const [desktopResult, mobileResult] = await Promise.all([desktopPromise, mobilePromise]) - if (desktopResult) { - desktopInitial = desktopResult.initialPoints - desktopCollected = desktopResult.collectedPoints - } - if (mobileResult) { - mobileInitial = mobileResult.initialPoints - mobileCollected = mobileResult.collectedPoints - } - } else { - this.isMobile = false - const desktopResult = await this.Desktop(account).catch(e => { - log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error') - errors.push(formatFullErr('desktop', e)); return null - }) - if (desktopResult) { - desktopInitial = desktopResult.initialPoints - desktopCollected = desktopResult.collectedPoints - } - - this.isMobile = true - const mobileResult = await this.Mobile(account).catch(e => { - log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error') - errors.push(formatFullErr('mobile', e)); return null - }) - if (mobileResult) { - mobileInitial = mobileResult.initialPoints - mobileCollected = mobileResult.collectedPoints - } - } - - const accountEnd = Date.now() - const durationMs = accountEnd - accountStart - const totalCollected = desktopCollected + mobileCollected - const initialTotal = (desktopInitial || 0) + (mobileInitial || 0) - this.accountSummaries.push({ - email: account.email, - durationMs, - desktopCollected, - mobileCollected, - totalCollected, - initialTotal, - endTotal: initialTotal + totalCollected, - errors - }) - - log('main', 'MAIN-WORKER', `Completed tasks for account ${account.email}`, 'log', 'green') - } - - log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green') - // Extra diagnostic summary when verbose - if (process.env.DEBUG_REWARDS_VERBOSE === '1') { - for (const summary of this.accountSummaries) { - log('main','SUMMARY-DEBUG',`Account ${summary.email} collected D:${summary.desktopCollected} M:${summary.mobileCollected} TOTAL:${summary.totalCollected} ERRORS:${summary.errors.length ? summary.errors.join(';') : 'none'}`) - } - } - // If in worker mode (clusters>1) send summaries to primary - if (this.config.clusters > 1 && !cluster.isPrimary) { - if (process.send) { - process.send({ type: 'summary', data: this.accountSummaries }) - } - } else { - // Single process mode -> build and send conclusion directly - await this.sendConclusion(this.accountSummaries) - } - process.exit() - } - - // Desktop - async Desktop(account: Account) { - log(false,'FLOW','Desktop() invoked') - const browser = await this.browserFactory.createBrowser(account.proxy, account.email) - this.homePage = await browser.newPage() - - log(this.isMobile, 'MAIN', 'Starting browser') - - // Login into MS Rewards, then go to rewards homepage - await this.login.login(this.homePage, account.email, account.password) - - await this.browser.func.goHome(this.homePage) - - const data = await this.browser.func.getDashboardData() - - this.pointsInitial = data.userStatus.availablePoints - const initial = this.pointsInitial - - log(this.isMobile, 'MAIN-POINTS', `Current point count: ${this.pointsInitial}`) - - const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints() - - // Tally all the desktop points - this.pointsCanCollect = browserEnarablePoints.dailySetPoints + - browserEnarablePoints.desktopSearchPoints - + browserEnarablePoints.morePromotionsPoints - - log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today`) - - // If runOnZeroPoints is false and 0 points to earn, don't continue - if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) { - log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow') - - // Close desktop browser - await this.browser.func.closeBrowser(browser, account.email) - return - } - - // Open a new tab to where the tasks are going to be completed - const workerPage = await browser.newPage() - - // Go to homepage on worker page - await this.browser.func.goHome(workerPage) - - // Complete daily set - if (this.config.workers.doDailySet) { - await this.workers.doDailySet(workerPage, data) - } - - // Complete more promotions - if (this.config.workers.doMorePromotions) { - await this.workers.doMorePromotions(workerPage, data) - } - - // Complete punch cards - if (this.config.workers.doPunchCards) { - await this.workers.doPunchCard(workerPage, data) - } - - // Do desktop searches - if (this.config.workers.doDesktopSearch) { - await this.activities.doSearch(workerPage, data) - } - - // Save cookies - await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile) - - // Fetch points BEFORE closing (avoid page closed reload error) - const after = await this.browser.func.getCurrentPoints().catch(()=>initial) - // Close desktop browser - await this.browser.func.closeBrowser(browser, account.email) - return { - initialPoints: initial, - collectedPoints: (after - initial) || 0 - } - } - - // Mobile - async Mobile(account: Account) { - log(true,'FLOW','Mobile() invoked') - const browser = await this.browserFactory.createBrowser(account.proxy, account.email) - this.homePage = await browser.newPage() - - log(this.isMobile, 'MAIN', 'Starting browser') - - // Login into MS Rewards, then go to rewards homepage - await this.login.login(this.homePage, account.email, account.password) - this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email) - - await this.browser.func.goHome(this.homePage) - - const data = await this.browser.func.getDashboardData() - const initialPoints = data.userStatus.availablePoints || this.pointsInitial || 0 - - const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints() - const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken) - - this.pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints - - log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`) - - // If runOnZeroPoints is false and 0 points to earn, don't continue - if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) { - log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow') - - // Close mobile browser - await this.browser.func.closeBrowser(browser, account.email) - return { - initialPoints: initialPoints, - collectedPoints: 0 - } - } - // Do daily check in - if (this.config.workers.doDailyCheckIn) { - await this.activities.doDailyCheckIn(this.accessToken, data) - } - - // Do read to earn - if (this.config.workers.doReadToEarn) { - await this.activities.doReadToEarn(this.accessToken, data) - } - - // Do mobile searches - if (this.config.workers.doMobileSearch) { - // If no mobile searches data found, stop (Does not always exist on new accounts) - if (data.userStatus.counters.mobileSearch) { - // Open a new tab to where the tasks are going to be completed - const workerPage = await browser.newPage() - - // Go to homepage on worker page - await this.browser.func.goHome(workerPage) - - await this.activities.doSearch(workerPage, data) - - // Fetch current search points - const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0] - - if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) { - // Increment retry count - this.mobileRetryAttempts++ - } - - // Exit if retries are exhausted - if (this.mobileRetryAttempts > this.config.searchSettings.retryMobileSearchAmount) { - log(this.isMobile, 'MAIN', `Max retry limit of ${this.config.searchSettings.retryMobileSearchAmount} reached. Exiting retry loop`, 'warn') - } else if (this.mobileRetryAttempts !== 0) { - log(this.isMobile, 'MAIN', `Attempt ${this.mobileRetryAttempts}/${this.config.searchSettings.retryMobileSearchAmount}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow') - - // Close mobile browser - await this.browser.func.closeBrowser(browser, account.email) - - // Create a new browser and try - await this.Mobile(account) - return - } - } else { - log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn') - } - } - - const afterPointAmount = await this.browser.func.getCurrentPoints() - - log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - initialPoints} points today`) - - // Close mobile browser - await this.browser.func.closeBrowser(browser, account.email) - return { - initialPoints: initialPoints, - collectedPoints: (afterPointAmount - initialPoints) || 0 - } - } - - private async sendConclusion(summaries: AccountSummary[]) { - const { ConclusionWebhook } = await import('./util/ConclusionWebhook') - const cfg = this.config - if (!cfg.conclusionWebhook || !cfg.conclusionWebhook.enabled) return - - const totalAccounts = summaries.length - if (totalAccounts === 0) return - - let totalCollected = 0 - let totalInitial = 0 - let totalEnd = 0 - let totalDuration = 0 - let accountsWithErrors = 0 - - const accountFields: any[] = [] - for (const s of summaries) { - totalCollected += s.totalCollected - totalInitial += s.initialTotal - totalEnd += s.endTotal - totalDuration += s.durationMs - if (s.errors.length) accountsWithErrors++ - - const statusEmoji = s.errors.length ? '⚠️' : '✅' - const diff = s.totalCollected - const duration = formatDuration(s.durationMs) - const valueLines: string[] = [ - `Points: ${s.initialTotal} → ${s.endTotal} ( +${diff} )`, - `Breakdown: 🖥️ ${s.desktopCollected} | 📱 ${s.mobileCollected}`, - `Duration: ⏱️ ${duration}` - ] - if (s.errors.length) { - valueLines.push(`Errors: ${s.errors.slice(0,2).join(' | ')}`) - } - accountFields.push({ - name: `${statusEmoji} ${s.email}`.substring(0, 256), - value: valueLines.join('\n').substring(0, 1024), - inline: false - }) - } - - const avgDuration = totalDuration / totalAccounts - const embed = { - title: '🎯 Microsoft Rewards Summary', - description: `Processed **${totalAccounts}** account(s)${accountsWithErrors ? ` • ${accountsWithErrors} with issues` : ''}`, - color: accountsWithErrors ? 0xFFAA00 : 0x32CD32, - fields: [ - { - name: 'Global Totals', - value: [ - `Total Points: ${totalInitial} → ${totalEnd} ( +${totalCollected} )`, - `Average Duration: ${formatDuration(avgDuration)}`, - `Cumulative Runtime: ${formatDuration(totalDuration)}` - ].join('\n') - }, - ...accountFields - ].slice(0, 25), // Discord max 25 fields - timestamp: new Date().toISOString(), - footer: { - text: 'Script conclusion webhook' - } - } - - // Fallback plain text (rare) & embed send - const fallback = `Microsoft Rewards Summary\nAccounts: ${totalAccounts}\nTotal: ${totalInitial} -> ${totalEnd} (+${totalCollected})\nRuntime: ${formatDuration(totalDuration)}` - await ConclusionWebhook(cfg, fallback, { embeds: [embed] }) - } -} - -interface AccountSummary { - email: string - durationMs: number - desktopCollected: number - mobileCollected: number - totalCollected: number - initialTotal: number - endTotal: number - errors: string[] -} - -function shortErr(e: any): string { - if (!e) return 'unknown' - if (e instanceof Error) return e.message.substring(0, 120) - const s = String(e) - return s.substring(0, 120) -} - -function formatDuration(ms: number): string { - if (!ms || ms < 1000) return `${ms}ms` - const sec = Math.floor(ms / 1000) - const h = Math.floor(sec / 3600) - const m = Math.floor((sec % 3600) / 60) - const s = sec % 60 - const parts: string[] = [] - if (h) parts.push(`${h}h`) - if (m) parts.push(`${m}m`) - if (s) parts.push(`${s}s`) - return parts.join(' ') || `${ms}ms` -} - -async function main() { - const rewardsBot = new MicrosoftRewardsBot(false) - - try { - await rewardsBot.initialize() - await rewardsBot.run() - } catch (error) { - log(false, 'MAIN-ERROR', `Error running desktop bot: ${error}`, 'error') - } -} - -// Start the bots -main().catch(error => { - log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error') - process.exit(1) -}) \ No newline at end of file diff --git a/src/interface/Account.ts b/src/interface/Account.ts deleted file mode 100644 index 0847f99..0000000 --- a/src/interface/Account.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Account { - email: string; - password: string; - proxy: AccountProxy; -} - -export interface AccountProxy { - proxyAxios: boolean; - url: string; - port: number; - password: string; - username: string; -} \ No newline at end of file diff --git a/src/interface/AppUserData.ts b/src/interface/AppUserData.ts deleted file mode 100644 index f4f2b0e..0000000 --- a/src/interface/AppUserData.ts +++ /dev/null @@ -1,226 +0,0 @@ -export interface AppUserData { - response: Response; - correlationId: string; - code: number; -} - -export interface Response { - profile: Profile; - balance: number; - counters: null; - promotions: Promotion[]; - catalog: null; - goal_item: GoalItem; - activities: null; - cashback: null; - orders: Order[]; - rebateProfile: null; - rebatePayouts: null; - giveProfile: GiveProfile; - autoRedeemProfile: null; - autoRedeemItem: null; - thirdPartyProfile: null; - notifications: null; - waitlist: null; - autoOpenFlyout: null; - coupons: null; - recommendedAffordableCatalog: null; -} - -export interface GiveProfile { - give_user: string; - give_organization: { [key: string]: GiveOrganization | null }; - first_give_optin: string; - last_give_optout: string; - give_lifetime_balance: string; - give_lifetime_donation_balance: string; - give_balance: string; - form: null; -} - -export interface GiveOrganization { - give_organization_donation_points: number; - give_organization_donation_point_to_currency_ratio: number; - give_organization_donation_currency: number; -} - -export interface GoalItem { - name: string; - provider: string; - price: number; - attributes: GoalItemAttributes; - config: GoalItemConfig; -} - -export interface GoalItemAttributes { - category: string; - CategoryDescription: string; - 'desc.group_text': string; - 'desc.legal_text'?: string; - 'desc.sc_description': string; - 'desc.sc_title': string; - display_order: string; - ExtraLargeImage: string; - group: string; - group_image: string; - group_sc_image: string; - group_title: string; - hidden?: string; - large_image: string; - large_sc_image: string; - medium_image: string; - MobileImage: string; - original_price: string; - Remarks?: string; - ShortText?: string; - showcase?: string; - small_image: string; - title: string; - cimsid: string; - user_defined_goal?: string; - disable_bot_redemptions?: string; - 'desc.large_text'?: string; - english_title?: string; - etid?: string; - sku?: string; - coupon_discount?: string; -} - -export interface GoalItemConfig { - amount: string; - currencyCode: string; - isHidden: string; - PointToCurrencyConversionRatio: string; -} - -export interface Order { - id: string; - t: Date; - sku: string; - item_snapshot: ItemSnapshot; - p: number; - s: S; - a: A; - child_redemption: null; - third_party_partner: null; - log: Log[]; -} - -export interface A { - form?: string; - OrderId: string; - CorrelationId: string; - Channel: string; - Language: string; - Country: string; - EvaluationId: string; - provider?: string; - referenceOrderID?: string; - externalRefID?: string; - denomination?: string; - rewardName?: string; - sendEmail?: string; - status?: string; - createdAt?: Date; - bal_before_deduct?: string; - bal_after_deduct?: string; -} - -export interface ItemSnapshot { - name: string; - provider: string; - price: number; - attributes: GoalItemAttributes; - config: ItemSnapshotConfig; -} - -export interface ItemSnapshotConfig { - amount: string; - countryCode: string; - currencyCode: string; - sku: string; -} - -export interface Log { - time: Date; - from: From; - to: S; - reason: string; -} - -export enum From { - Created = 'Created', - RiskApproved = 'RiskApproved', - RiskReview = 'RiskReview' -} - -export enum S { - Cancelled = 'Cancelled', - RiskApproved = 'RiskApproved', - RiskReview = 'RiskReview', - Shipped = 'Shipped' -} - -export interface Profile { - ruid: string; - attributes: ProfileAttributes; - offline_attributes: OfflineAttributes; -} - -export interface ProfileAttributes { - publisher: string; - publisher_upd: Date; - creative: string; - creative_upd: Date; - program: string; - program_upd: Date; - country: string; - country_upd: Date; - referrerhash: string; - referrerhash_upd: Date; - optout_upd: Date; - language: string; - language_upd: Date; - target: string; - target_upd: Date; - created: Date; - created_upd: Date; - epuid: string; - epuid_upd: Date; - goal: string; - goal_upd: Date; - waitlistattributes: string; - waitlistattributes_upd: Date; - serpbotscore_upd: Date; - iscashbackeligible: string; - cbedc: string; - rlscpct_upd: Date; - give_user: string; - rebcpc_upd: Date; - SerpBotScore_upd: Date; - AdsBotScore_upd: Date; - dbs_upd: Date; - rbs: string; - rbs_upd: Date; - iris_segmentation: string; - iris_segmentation_upd: Date; -} - -export interface OfflineAttributes { -} - -export interface Promotion { - name: string; - priority: number; - attributes: { [key: string]: string }; - tags: Tag[]; -} - -export enum Tag { - AllowTrialUser = 'allow_trial_user', - ExcludeGivePcparent = 'exclude_give_pcparent', - ExcludeGlobalConfig = 'exclude_global_config', - ExcludeHidden = 'exclude_hidden', - LOCString = 'locString', - NonGlobalConfig = 'non_global_config' -} diff --git a/src/interface/Config.ts b/src/interface/Config.ts deleted file mode 100644 index 0194f7c..0000000 --- a/src/interface/Config.ts +++ /dev/null @@ -1,56 +0,0 @@ -export interface Config { - baseURL: string; - sessionPath: string; - headless: boolean; - parallel: boolean; - runOnZeroPoints: boolean; - clusters: number; - saveFingerprint: ConfigSaveFingerprint; - workers: ConfigWorkers; - searchOnBingLocalQueries: boolean; - globalTimeout: number | string; - searchSettings: ConfigSearchSettings; - logExcludeFunc: string[]; - webhookLogExcludeFunc: string[]; - proxy: ConfigProxy; - webhook: ConfigWebhook; - conclusionWebhook?: ConfigWebhook; // Optional secondary webhook for final summary -} - -export interface ConfigSaveFingerprint { - mobile: boolean; - desktop: boolean; -} - -export interface ConfigSearchSettings { - useGeoLocaleQueries: boolean; - scrollRandomResults: boolean; - clickRandomResults: boolean; - searchDelay: ConfigSearchDelay; - retryMobileSearchAmount: number; -} - -export interface ConfigSearchDelay { - min: number | string; - max: number | string; -} - -export interface ConfigWebhook { - enabled: boolean; - url: string; -} - -export interface ConfigProxy { - proxyGoogleTrends: boolean; - proxyBingTerms: boolean; -} - -export interface ConfigWorkers { - doDailySet: boolean; - doMorePromotions: boolean; - doPunchCards: boolean; - doDesktopSearch: boolean; - doMobileSearch: boolean; - doDailyCheckIn: boolean; - doReadToEarn: boolean; -} diff --git a/src/interface/DashboardData.ts b/src/interface/DashboardData.ts deleted file mode 100644 index bb4c380..0000000 --- a/src/interface/DashboardData.ts +++ /dev/null @@ -1,701 +0,0 @@ -export interface DashboardData { - userStatus: UserStatus; - promotionalItem: PromotionalItem; - dailySetPromotions: { [key: string]: PromotionalItem[] }; - streakPromotion: StreakPromotion; - streakBonusPromotions: StreakBonusPromotion[]; - punchCards: PunchCard[]; - dashboardFlights: DashboardFlights; - morePromotions: MorePromotion[]; - suggestedRewards: AutoRedeemItem[]; - coachMarks: CoachMarks; - welcomeTour: WelcomeTour; - userInterests: UserInterests; - isVisualParityTest: boolean; - mbingFlight: null; - langCountryMismatchPromo: null; - machineTranslationPromo: MachineTranslationPromo; - autoRedeemItem: AutoRedeemItem; - userProfile: UserProfile; -} - -export interface AutoRedeemItem { - name: null | string; - price: number; - provider: null | string; - disabled: boolean; - category: string; - title: string; - variableGoalSpecificTitle: string; - smallImageUrl: string; - mediumImageUrl: string; - largeImageUrl: string; - largeShowcaseImageUrl: string; - description: Description; - showcase: boolean; - showcaseInAllCategory: boolean; - originalPrice: number; - discountedPrice: number; - popular: boolean; - isTestOnly: boolean; - groupId: string; - inGroup: boolean; - isDefaultItemInGroup: boolean; - groupTitle: string; - groupImageUrl: string; - groupShowcaseImageUrl: string; - instantWinGameId: string; - instantWinPlayAgainSku: string; - isLowInStock: boolean; - isOutOfStock: boolean; - getCodeMessage: string; - disableEmail: boolean; - stockMessage: string; - comingSoonFlag: boolean; - isGenericDonation: boolean; - isVariableRedemptionItem: boolean; - variableRedemptionItemCurrencySymbol: null; - variableRedemptionItemMin: number; - variableRedemptionItemMax: number; - variableItemConfigPointsToCurrencyConversionRatio: number; - isAutoRedeem: boolean; -} - -export interface Description { - itemGroupText: string; - smallText: string; - largeText: string; - legalText: string; - showcaseTitle: string; - showcaseDescription: string; -} - -export interface CoachMarks { - streaks: WelcomeTour; -} - -export interface WelcomeTour { - promotion: DashboardImpression; - slides: Slide[]; -} - -export interface DashboardImpression { - name: null | string; - priority: number; - attributes: { [key: string]: string } | null; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; - benefits?: Benefit[]; - supportedLevelKeys?: string[]; - supportedLevelTitles?: string[]; - supportedLevelTitlesMobile?: string[]; - activeLevel?: string; - isCodexAutoJoinUser?: boolean; -} - -export interface Benefit { - key: string; - text: string; - url: null | string; - helpText: null | string; - supportedLevels: SupportedLevels; -} - -export interface SupportedLevels { - level1?: string; - level2: string; - level2XBoxGold: string; -} - -export interface Slide { - slideType: null; - slideShowTourId: string; - id: number; - title: string; - subtitle: null; - subtitle1: null; - description: string; - description1: null; - imageTitle: null; - image2Title: null | string; - image3Title: null | string; - image4Title: null | string; - imageDescription: null; - image2Description: null | string; - image3Description: null | string; - image4Description: null | string; - imageUrl: null | string; - darkImageUrl: null; - image2Url: null | string; - image3Url: null | string; - image4Url: null | string; - layout: null | string; - actionButtonText: null | string; - actionButtonUrl: null | string; - foregroundImageUrl: null; - backLink: null; - nextLink: CloseLink; - closeLink: CloseLink; - footnote: null | string; - termsText: null; - termsUrl: null; - privacyText: null; - privacyUrl: null; - taggedItem: null | string; - slideVisited: boolean; - aboutPageLinkText: null; - aboutPageLink: null; - redeemLink: null; - rewardsLink: null; - quizLinks?: string[]; - quizCorrectAnswerTitle?: string; - quizWrongAnswerTitle?: string; - quizAnswerDescription?: string; -} - -export interface CloseLink { - text: null | string; - url: null | string; -} - -export interface PromotionalItem { - name: string; - priority: number; - attributes: PromotionalItemAttributes; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: Type; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; -} - -export interface PromotionalItemAttributes { - animated_icon?: string; - bg_image: string; - complete: GiveEligible; - daily_set_date?: string; - description: string; - destination: string; - icon: string; - image: string; - link_text: string; - max: string; - offerid: string; - progress: string; - sc_bg_image: string; - sc_bg_large_image: string; - small_image: string; - state: State; - title: string; - type: Type; - give_eligible: GiveEligible; - activity_max?: string; - activity_progress?: string; - is_wot?: GiveEligible; - offer_counter?: string; - promotional?: GiveEligible; - parentPunchcards?: string; - 'classification.DescriptionText'?: string; - 'classification.PunchcardChildrenCount'?: string; - 'classification.PunchcardEndDate'?: Date; - 'classification.Template'?: string; - 'classification.TitleText'?: string; -} - -export enum GiveEligible { - False = 'False', - True = 'True' -} - -export enum State { - Default = 'Default' -} - -export enum Type { - Quiz = 'quiz', - Urlreward = 'urlreward', - UrlrewardUrlrewardUrlrewardUrlrewardUrlreward = 'urlreward,urlreward,urlreward,urlreward,urlreward' -} - -export interface DashboardFlights { - dashboardbannernav: string; - togglegiveuser: string; - spotifyRedirect: string; - give_eligible: GiveEligible; - destination: string; -} - -export interface MachineTranslationPromo { -} - -export interface MorePromotion { - name: string; - priority: number; - attributes: { [key: string]: string }; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; - exclusiveLockedFeatureType: string; - exclusiveLockedFeatureStatus: string; -} - -export interface PunchCard { - name: string; - parentPromotion?: PromotionalItem; - childPromotions: PromotionalItem[]; -} - -export interface StreakBonusPromotion { - name: string; - priority: number; - attributes: StreakBonusPromotionAttributes; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; -} - -export interface StreakBonusPromotionAttributes { - hidden: GiveEligible; - type: string; - title: string; - description: string; - description_localizedkey: string; - image: string; - animated_icon: string; - activity_progress: string; - activity_max: string; - bonus_earned: string; - break_description: string; - give_eligible: GiveEligible; - destination: string; -} - -export interface StreakPromotion { - lastUpdatedDate: Date; - breakImageUrl: string; - lifetimeMaxValue: number; - bonusPointsEarned: number; - name: string; - priority: number; - attributes: StreakPromotionAttributes; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; -} - -export interface StreakPromotionAttributes { - hidden: GiveEligible; - type: string; - title: string; - image: string; - activity_progress: string; - last_updated: Date; - break_image: string; - lifetime_max: string; - bonus_points: string; - give_eligible: GiveEligible; - destination: string; -} - -export interface UserInterests { - name: string; - priority: number; - attributes: UserInterestsAttributes; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; -} - -export interface UserInterestsAttributes { - hidden: GiveEligible; - give_eligible: GiveEligible; - destination: string; -} - -export interface UserProfile { - ruid: string; - attributes: UserProfileAttributes; -} - -export interface UserProfileAttributes { - publisher: string; - publisher_upd: Date; - creative: string; - creative_upd: Date; - program: string; - program_upd: Date; - country: string; - country_upd: Date; - referrerhash: string; - referrerhash_upd: Date; - optout_upd: Date; - language: string; - language_upd: Date; - target: string; - target_upd: Date; - created: Date; - created_upd: Date; - epuid: string; - epuid_upd: Date; - waitlistattributes: string; - waitlistattributes_upd: Date; - cbedc: GiveEligible; - iscashbackeligible: GiveEligible; - give_user: GiveEligible; -} - -export interface UserStatus { - levelInfo: LevelInfo; - availablePoints: number; - lifetimePoints: number; - lifetimePointsRedeemed: number; - ePuid: string; - redeemGoal: AutoRedeemItem; - counters: Counters; - lastOrder: LastOrder; - dashboardImpression: DashboardImpression; - referrerProgressInfo: ReferrerProgressInfo; - isGiveModeOn: boolean; - giveBalance: number; - firstTimeGiveModeOptIn: null; - giveOrganizationName: string; - lifetimeGivingPoints: number; - isRewardsUser: boolean; - isMuidTrialUser: boolean; -} - -export interface Counters { - pcSearch: DashboardImpression[]; - mobileSearch?: DashboardImpression[]; - shopAndEarn: DashboardImpression[]; - activityAndQuiz: ActivityAndQuiz[]; - dailyPoint: DashboardImpression[]; -} - -export interface ActivityAndQuiz { - name: string; - priority: number; - attributes: ActivityAndQuizAttributes; - offerId: string; - complete: boolean; - counter: number; - activityProgress: number; - activityProgressMax: number; - pointProgressMax: number; - pointProgress: number; - promotionType: string; - promotionSubtype: string; - title: string; - extBannerTitle: string; - titleStyle: string; - theme: string; - description: string; - extBannerDescription: string; - descriptionStyle: string; - showcaseTitle: string; - showcaseDescription: string; - imageUrl: string; - dynamicImage: string; - smallImageUrl: string; - backgroundImageUrl: string; - showcaseBackgroundImageUrl: string; - showcaseBackgroundLargeImageUrl: string; - promotionBackgroundLeft: string; - promotionBackgroundRight: string; - iconUrl: string; - animatedIconUrl: string; - animatedLargeBackgroundImageUrl: string; - destinationUrl: string; - linkText: string; - hash: string; - activityType: string; - isRecurring: boolean; - isHidden: boolean; - isTestOnly: boolean; - isGiveEligible: boolean; - level: string; - slidesCount: number; - legalText: string; - legalLinkText: string; - deviceType: string; -} - -export interface ActivityAndQuizAttributes { - type: string; - title: string; - link_text: string; - description: string; - foreground_color: string; - image: string; - recurring: string; - destination: string; - 'classification.ShowProgress': GiveEligible; - hidden: GiveEligible; - give_eligible: GiveEligible; -} - -export interface LastOrder { - id: null; - price: number; - status: null; - sku: null; - timestamp: Date; - catalogItem: null; -} - -export interface LevelInfo { - activeLevel: string; - activeLevelName: string; - progress: number; - progressMax: number; - levels: Level[]; - benefitsPromotion: DashboardImpression; -} - -export interface Level { - key: string; - active: boolean; - name: string; - tasks: CloseLink[]; - privileges: CloseLink[]; -} - -export interface ReferrerProgressInfo { - pointsEarned: number; - pointsMax: number; - isComplete: boolean; - promotions: string[]; -} diff --git a/src/interface/GoogleDailyTrends.ts b/src/interface/GoogleDailyTrends.ts deleted file mode 100644 index e86fa50..0000000 --- a/src/interface/GoogleDailyTrends.ts +++ /dev/null @@ -1,44 +0,0 @@ -export interface GoogleTrends { - default: Default; -} - -export interface Default { - trendingSearchesDays: TrendingSearchesDay[]; - endDateForNextRequest: string; - rssFeedPageUrl: string; -} - -export interface TrendingSearchesDay { - date: string; - formattedDate: string; - trendingSearches: TrendingSearch[]; -} - -export interface TrendingSearch { - title: Title; - formattedTraffic: string; - relatedQueries: Title[]; - image: Image; - articles: Article[]; - shareUrl: string; -} - -export interface Article { - title: string; - timeAgo: string; - source: string; - image?: Image; - url: string; - snippet: string; -} - -export interface Image { - newsUrl: string; - source: string; - imageUrl: string; -} - -export interface Title { - query: string; - exploreLink: string; -} diff --git a/src/interface/OAuth.ts b/src/interface/OAuth.ts deleted file mode 100644 index 6f1d669..0000000 --- a/src/interface/OAuth.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface OAuth { - access_token: string; - refresh_token: string; - scope: string; - expires_in: number; - ext_expires_in: number; - foci: string; - token_type: string; -} \ No newline at end of file diff --git a/src/interface/Points.ts b/src/interface/Points.ts deleted file mode 100644 index 2cfe28f..0000000 --- a/src/interface/Points.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface EarnablePoints { - desktopSearchPoints: number - mobileSearchPoints: number - dailySetPoints: number - morePromotionsPoints: number - totalEarnablePoints: number -} \ No newline at end of file diff --git a/src/interface/QuizData.ts b/src/interface/QuizData.ts deleted file mode 100644 index 0c3665d..0000000 --- a/src/interface/QuizData.ts +++ /dev/null @@ -1,50 +0,0 @@ -export interface QuizData { - offerId: string; - quizId: string; - quizCategory: string; - IsCurrentQuestionCompleted: boolean; - quizRenderSummaryPage: boolean; - resetQuiz: boolean; - userClickedOnHint: boolean; - isDemoEnabled: boolean; - correctAnswer: string; - isMultiChoiceQuizType: boolean; - isPutInOrderQuizType: boolean; - isListicleQuizType: boolean; - isWOTQuizType: boolean; - isBugsForRewardsQuizType: boolean; - currentQuestionNumber: number; - maxQuestions: number; - resetTrackingCounters: boolean; - showWelcomePanel: boolean; - isAjaxCall: boolean; - showHint: boolean; - numberOfOptions: number; - isMobile: boolean; - inRewardsMode: boolean; - enableDailySetWelcomePane: boolean; - enableDailySetNonWelcomePane: boolean; - isDailySetUrlOffer: boolean; - isDailySetFlightEnabled: boolean; - dailySetUrlOfferId: string; - earnedCredits: number; - maxCredits: number; - creditsPerQuestion: number; - userAlreadyClickedOptions: number; - hasUserClickedOnOption: boolean; - recentAnswerChoice: string; - sessionTimerSeconds: string; - isOverlayMinimized: number; - ScreenReaderMsgOnMove: string; - ScreenReaderMsgOnDrop: string; - IsPartialPointsEnabled: boolean; - PrioritizeUrlOverCookies: boolean; - UseNewReportActivityAPI: boolean; - CorrectlyAnsweredQuestionCount: number; - showJoinRewardsPage: boolean; - CorrectOptionAnswer_WOT: string; - WrongOptionAnswer_WOT: string; - enableSlideAnimation: boolean; - ariaLoggingEnabled: boolean; - UseQuestionIndexInActivityId: boolean; -} diff --git a/src/interface/Search.ts b/src/interface/Search.ts deleted file mode 100644 index 5e9371f..0000000 --- a/src/interface/Search.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface GoogleSearch { - topic: string; - related: string[]; -} \ No newline at end of file diff --git a/src/interface/UserAgentUtil.ts b/src/interface/UserAgentUtil.ts deleted file mode 100644 index e5d024b..0000000 --- a/src/interface/UserAgentUtil.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Chrome Product Data -export interface ChromeVersion { - timestamp: Date; - channels: Channels; -} - -export interface Channels { - Stable: Beta; - Beta: Beta; - Dev: Beta; - Canary: Beta; -} - -export interface Beta { - channel: string; - version: string; - revision: string; -} - -// Edge Product Data -export interface EdgeVersion { - Product: string; - Releases: Release[]; -} - -export interface Release { - ReleaseId: number; - Platform: Platform; - Architecture: Architecture; - CVEs: string[]; - ProductVersion: string; - Artifacts: Artifact[]; - PublishedTime: Date; - ExpectedExpiryDate: Date; -} - -export enum Architecture { - Arm64 = 'arm64', - Universal = 'universal', - X64 = 'x64', - X86 = 'x86' -} - -export interface Artifact { - ArtifactName: string; - Location: string; - Hash: string; - HashAlgorithm: HashAlgorithm; - SizeInBytes: number; -} - -export enum HashAlgorithm { - Sha256 = 'SHA256' -} - -export enum Platform { - Android = 'Android', - IOS = 'iOS', - Linux = 'Linux', - MACOS = 'MacOS', - Windows = 'Windows' -} \ No newline at end of file diff --git a/src/run_daily.sh b/src/run_daily.sh deleted file mode 100755 index 6024c6f..0000000 --- a/src/run_daily.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -export PATH="/usr/local/bin:/usr/bin:/bin" -export PLAYWRIGHT_BROWSERS_PATH=0 -export TZ="${TZ:-UTC}" - -cd /usr/src/microsoft-rewards-script - -LOCKFILE=/tmp/run_daily.lock - -# ------------------------------- -# Function: Check and fix lockfile integrity -# ------------------------------- -self_heal_lockfile() { - # If lockfile exists but is empty → remove it - if [ -f "$LOCKFILE" ]; then - local lock_content - lock_content=$(<"$LOCKFILE" || echo "") - - if [[ -z "$lock_content" ]]; then - echo "[$(date)] [run_daily.sh] Found empty lockfile → removing." - rm -f "$LOCKFILE" - return - fi - - # If lockfile contains non-numeric PID → remove it - if ! [[ "$lock_content" =~ ^[0-9]+$ ]]; then - echo "[$(date)] [run_daily.sh] Found corrupted lockfile content ('$lock_content') → removing." - rm -f "$LOCKFILE" - return - fi - - # If lockfile contains PID but process is dead → remove it - if ! kill -0 "$lock_content" 2>/dev/null; then - echo "[$(date)] [run_daily.sh] Lockfile PID $lock_content is dead → removing stale lock." - rm -f "$LOCKFILE" - return - fi - fi -} - -# ------------------------------- -# Function: Acquire lock -# ------------------------------- -acquire_lock() { - local max_attempts=5 - local attempt=0 - local timeout_hours=${STUCK_PROCESS_TIMEOUT_HOURS:-8} - local timeout_seconds=$((timeout_hours * 3600)) - - while [ $attempt -lt $max_attempts ]; do - # Try to create lock with current PID - if (set -C; echo "$$" > "$LOCKFILE") 2>/dev/null; then - echo "[$(date)] [run_daily.sh] Lock acquired successfully (PID: $$)" - return 0 - fi - - # Lock exists, validate it - if [ -f "$LOCKFILE" ]; then - local existing_pid - existing_pid=$(<"$LOCKFILE" || echo "") - - echo "[$(date)] [run_daily.sh] Lock file exists with PID: '$existing_pid'" - - # If lockfile content is invalid → delete and retry - if [[ -z "$existing_pid" || ! "$existing_pid" =~ ^[0-9]+$ ]]; then - echo "[$(date)] [run_daily.sh] Removing invalid lockfile → retrying..." - rm -f "$LOCKFILE" - continue - fi - - # If process is dead → delete and retry - if ! kill -0 "$existing_pid" 2>/dev/null; then - echo "[$(date)] [run_daily.sh] Removing stale lock (dead PID: $existing_pid)" - rm -f "$LOCKFILE" - continue - fi - - # Check process runtime → kill if exceeded timeout - local process_age - if process_age=$(ps -o etimes= -p "$existing_pid" 2>/dev/null | tr -d ' '); then - if [ "$process_age" -gt "$timeout_seconds" ]; then - echo "[$(date)] [run_daily.sh] Killing stuck process $existing_pid (${process_age}s > ${timeout_hours}h)" - kill -TERM "$existing_pid" 2>/dev/null || true - sleep 5 - kill -KILL "$existing_pid" 2>/dev/null || true - rm -f "$LOCKFILE" - continue - fi - fi - fi - - echo "[$(date)] [run_daily.sh] Lock held by PID $existing_pid, attempt $((attempt + 1))/$max_attempts" - sleep 2 - ((attempt++)) - done - - echo "[$(date)] [run_daily.sh] Could not acquire lock after $max_attempts attempts; exiting." - return 1 -} - -# ------------------------------- -# Function: Release lock -# ------------------------------- -release_lock() { - if [ -f "$LOCKFILE" ]; then - local lock_pid - lock_pid=$(<"$LOCKFILE") - if [ "$lock_pid" = "$$" ]; then - rm -f "$LOCKFILE" - echo "[$(date)] [run_daily.sh] Lock released (PID: $$)" - fi - fi -} - -# Always release lock on exit — but only if we acquired it -trap 'release_lock' EXIT INT TERM - -# ------------------------------- -# MAIN EXECUTION FLOW -# ------------------------------- -echo "[$(date)] [run_daily.sh] Current process PID: $$" - -# Self-heal any broken or empty locks before proceeding -self_heal_lockfile - -# Attempt to acquire the lock safely -if ! acquire_lock; then - exit 0 -fi - -# Random sleep between MIN and MAX to spread execution -MINWAIT=${MIN_SLEEP_MINUTES:-5} -MAXWAIT=${MAX_SLEEP_MINUTES:-50} -MINWAIT_SEC=$((MINWAIT*60)) -MAXWAIT_SEC=$((MAXWAIT*60)) - -if [ "${SKIP_RANDOM_SLEEP:-false}" != "true" ]; then - SLEEPTIME=$(( MINWAIT_SEC + RANDOM % (MAXWAIT_SEC - MINWAIT_SEC) )) - echo "[$(date)] [run_daily.sh] Sleeping for $((SLEEPTIME/60)) minutes ($SLEEPTIME seconds)" - sleep "$SLEEPTIME" -else - echo "[$(date)] [run_daily.sh] Skipping random sleep" -fi - -# Start the actual script -echo "[$(date)] [run_daily.sh] Starting script..." -if npm start; then - echo "[$(date)] [run_daily.sh] Script completed successfully." -else - echo "[$(date)] [run_daily.sh] ERROR: Script failed!" >&2 -fi - -echo "[$(date)] [run_daily.sh] Script finished" -# Lock is released automatically via trap diff --git a/src/util/Axios.ts b/src/util/Axios.ts deleted file mode 100644 index 98c9b5b..0000000 --- a/src/util/Axios.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' -import { HttpProxyAgent } from 'http-proxy-agent' -import { HttpsProxyAgent } from 'https-proxy-agent' -import { SocksProxyAgent } from 'socks-proxy-agent' -import { AccountProxy } from '../interface/Account' - -class AxiosClient { - private instance: AxiosInstance - private account: AccountProxy - - constructor(account: AccountProxy) { - this.account = account - this.instance = axios.create() - - // If a proxy configuration is provided, set up the agent - if (this.account.url && this.account.proxyAxios) { - const agent = this.getAgentForProxy(this.account) - this.instance.defaults.httpAgent = agent - this.instance.defaults.httpsAgent = agent - } - } - - private getAgentForProxy(proxyConfig: AccountProxy): HttpProxyAgent | HttpsProxyAgent | SocksProxyAgent { - const { url, port } = proxyConfig - - switch (true) { - case proxyConfig.url.startsWith('http'): - return new HttpProxyAgent(`${url}:${port}`) - case proxyConfig.url.startsWith('https'): - return new HttpsProxyAgent(`${url}:${port}`) - case proxyConfig.url.startsWith('socks'): - return new SocksProxyAgent(`${url}:${port}`) - default: - throw new Error(`Unsupported proxy protocol: ${url}`) - } - } - - // Generic method to make any Axios request - public async request(config: AxiosRequestConfig, bypassProxy = false): Promise { - if (bypassProxy) { - const bypassInstance = axios.create() - return bypassInstance.request(config) - } - - return this.instance.request(config) - } -} - -export default AxiosClient \ No newline at end of file diff --git a/src/util/ConclusionWebhook.ts b/src/util/ConclusionWebhook.ts deleted file mode 100644 index 67a4390..0000000 --- a/src/util/ConclusionWebhook.ts +++ /dev/null @@ -1,32 +0,0 @@ -import axios from 'axios' - -import { Config } from '../interface/Config' - -interface ConclusionPayload { - content?: string - embeds?: any[] -} - -/** - * Send a final structured summary to the dedicated conclusion webhook (if enabled), - * otherwise do nothing. Does NOT fallback to the normal logging webhook to avoid spam. - */ -export async function ConclusionWebhook(configData: Config, content: string, embed?: ConclusionPayload) { - const webhook = configData.conclusionWebhook - - if (!webhook || !webhook.enabled || webhook.url.length < 10) return - - const body: ConclusionPayload = embed?.embeds ? { embeds: embed.embeds } : { content } - if (content && !body.content && !body.embeds) body.content = content - - const request = { - method: 'POST', - url: webhook.url, - headers: { - 'Content-Type': 'application/json' - }, - data: body - } - - await axios(request).catch(() => { }) -} diff --git a/src/util/Load.ts b/src/util/Load.ts deleted file mode 100644 index 9667274..0000000 --- a/src/util/Load.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { BrowserContext, Cookie } from 'rebrowser-playwright' -import { BrowserFingerprintWithHeaders } from 'fingerprint-generator' -import fs from 'fs' -import path from 'path' - - -import { Account } from '../interface/Account' -import { Config, ConfigSaveFingerprint } from '../interface/Config' - -let configCache: Config - -export function loadAccounts(): Account[] { - try { - let file = 'accounts.json' - - // If dev mode, use dev account(s) - if (process.argv.includes('-dev')) { - file = 'accounts.dev.json' - } - - const accountDir = path.join(__dirname, '../', file) - const accounts = fs.readFileSync(accountDir, 'utf-8') - - return JSON.parse(accounts) - } catch (error) { - throw new Error(error as string) - } -} - -export function loadConfig(): Config { - try { - if (configCache) { - return configCache - } - - const configDir = path.join(__dirname, '../', 'config.json') - const config = fs.readFileSync(configDir, 'utf-8') - - const configData = JSON.parse(config) - configCache = configData // Set as cache - - return configData - } catch (error) { - throw new Error(error as string) - } -} - -export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, saveFingerprint: ConfigSaveFingerprint) { - try { - // Fetch cookie file - const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`) - - let cookies: Cookie[] = [] - if (fs.existsSync(cookieFile)) { - const cookiesData = await fs.promises.readFile(cookieFile, 'utf-8') - cookies = JSON.parse(cookiesData) - } - - // Fetch fingerprint file - const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`) - - let fingerprint!: BrowserFingerprintWithHeaders - if (((saveFingerprint.desktop && !isMobile) || (saveFingerprint.mobile && isMobile)) && fs.existsSync(fingerprintFile)) { - const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8') - fingerprint = JSON.parse(fingerprintData) - } - - return { - cookies: cookies, - fingerprint: fingerprint - } - - } catch (error) { - throw new Error(error as string) - } -} - -export async function saveSessionData(sessionPath: string, browser: BrowserContext, email: string, isMobile: boolean): Promise { - try { - const cookies = await browser.cookies() - - // Fetch path - const sessionDir = path.join(__dirname, '../browser/', sessionPath, email) - - // Create session dir - if (!fs.existsSync(sessionDir)) { - await fs.promises.mkdir(sessionDir, { recursive: true }) - } - - // Save cookies to a file - await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`), JSON.stringify(cookies)) - - return sessionDir - } catch (error) { - throw new Error(error as string) - } -} - -export async function saveFingerprintData(sessionPath: string, email: string, isMobile: boolean, fingerpint: BrowserFingerprintWithHeaders): Promise { - try { - // Fetch path - const sessionDir = path.join(__dirname, '../browser/', sessionPath, email) - - // Create session dir - if (!fs.existsSync(sessionDir)) { - await fs.promises.mkdir(sessionDir, { recursive: true }) - } - - // Save fingerprint to a file - await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`), JSON.stringify(fingerpint)) - - return sessionDir - } catch (error) { - throw new Error(error as string) - } -} \ No newline at end of file diff --git a/src/util/Logger.ts b/src/util/Logger.ts deleted file mode 100644 index ecbbe4f..0000000 --- a/src/util/Logger.ts +++ /dev/null @@ -1,45 +0,0 @@ -import chalk from 'chalk' - -import { Webhook } from './Webhook' -import { loadConfig } from './Load' - - -export function log(isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof chalk): void { - const configData = loadConfig() - - if (configData.logExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) { - return - } - - const currentTime = new Date().toLocaleString() - const platformText = isMobile === 'main' ? 'MAIN' : isMobile ? 'MOBILE' : 'DESKTOP' - const chalkedPlatform = isMobile === 'main' ? chalk.bgCyan('MAIN') : isMobile ? chalk.bgBlue('MOBILE') : chalk.bgMagenta('DESKTOP') - - // Clean string for the Webhook (no chalk) - const cleanStr = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}` - - // Send the clean string to the Webhook - if (!configData.webhookLogExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) { - Webhook(configData, cleanStr) - } - - // Formatted string with chalk for terminal logging - const str = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${chalkedPlatform} [${title}] ${message}` - - const applyChalk = color && typeof chalk[color] === 'function' ? chalk[color] as (msg: string) => string : null - - // Log based on the type - switch (type) { - case 'warn': - applyChalk ? console.warn(applyChalk(str)) : console.warn(str) - break - - case 'error': - applyChalk ? console.error(applyChalk(str)) : console.error(str) - break - - default: - applyChalk ? console.log(applyChalk(str)) : console.log(str) - break - } -} diff --git a/src/util/UserAgent.ts b/src/util/UserAgent.ts deleted file mode 100644 index cbf0bdb..0000000 --- a/src/util/UserAgent.ts +++ /dev/null @@ -1,143 +0,0 @@ -import axios from 'axios' -import { BrowserFingerprintWithHeaders } from 'fingerprint-generator' - -import { log } from './Logger' - -import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil' - -const NOT_A_BRAND_VERSION = '99' - -export async function getUserAgent(isMobile: boolean) { - const system = getSystemComponents(isMobile) - const app = await getAppComponents(isMobile) - - const uaTemplate = isMobile ? - `Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Mobile Safari/537.36 EdgA/${app.edge_version}` : - `Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Safari/537.36 Edg/${app.edge_version}` - - const platformVersion = `${isMobile ? Math.floor(Math.random() * 5) + 9 : Math.floor(Math.random() * 15) + 1}.0.0` - - const uaMetadata = { - isMobile, - platform: isMobile ? 'Android' : 'Windows', - fullVersionList: [ - { brand: 'Not/A)Brand', version: `${NOT_A_BRAND_VERSION}.0.0.0` }, - { brand: 'Microsoft Edge', version: app['edge_version'] }, - { brand: 'Chromium', version: app['chrome_version'] } - ], - brands: [ - { brand: 'Not/A)Brand', version: NOT_A_BRAND_VERSION }, - { brand: 'Microsoft Edge', version: app['edge_major_version'] }, - { brand: 'Chromium', version: app['chrome_major_version'] } - ], - platformVersion, - architecture: isMobile ? '' : 'x86', - bitness: isMobile ? '' : '64', - model: '' - } - - return { userAgent: uaTemplate, userAgentMetadata: uaMetadata } -} - -export async function getChromeVersion(isMobile: boolean): Promise { - try { - const request = { - url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json', - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - } - - const response = await axios(request) - const data: ChromeVersion = response.data - return data.channels.Stable.version - - } catch (error) { - throw log(isMobile, 'USERAGENT-CHROME-VERSION', 'An error occurred:' + error, 'error') - } -} - -export async function getEdgeVersions(isMobile: boolean) { - try { - const request = { - url: 'https://edgeupdates.microsoft.com/api/products', - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - } - - const response = await axios(request) - const data: EdgeVersion[] = response.data - const stable = data.find(x => x.Product == 'Stable') as EdgeVersion - return { - android: stable.Releases.find(x => x.Platform == 'Android')?.ProductVersion, - windows: stable.Releases.find(x => (x.Platform == 'Windows' && x.Architecture == 'x64'))?.ProductVersion - } - - - } catch (error) { - throw log(isMobile, 'USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error') - } -} - -export function getSystemComponents(mobile: boolean): string { - const osId: string = mobile ? 'Linux' : 'Windows NT 10.0' - const uaPlatform: string = mobile ? `Android 1${Math.floor(Math.random() * 5)}` : 'Win64; x64' - - if (mobile) { - return `${uaPlatform}; ${osId}; K` - } - - return `${uaPlatform}; ${osId}` -} - -export async function getAppComponents(isMobile: boolean) { - const versions = await getEdgeVersions(isMobile) - const edgeVersion = isMobile ? versions.android : versions.windows as string - const edgeMajorVersion = edgeVersion?.split('.')[0] - - const chromeVersion = await getChromeVersion(isMobile) - const chromeMajorVersion = chromeVersion?.split('.')[0] - const chromeReducedVersion = `${chromeMajorVersion}.0.0.0` - - return { - not_a_brand_version: `${NOT_A_BRAND_VERSION}.0.0.0`, - not_a_brand_major_version: NOT_A_BRAND_VERSION, - edge_version: edgeVersion as string, - edge_major_version: edgeMajorVersion as string, - chrome_version: chromeVersion as string, - chrome_major_version: chromeMajorVersion as string, - chrome_reduced_version: chromeReducedVersion as string - } -} - -export async function updateFingerprintUserAgent(fingerprint: BrowserFingerprintWithHeaders, isMobile: boolean): Promise { - try { - const userAgentData = await getUserAgent(isMobile) - const componentData = await getAppComponents(isMobile) - - //@ts-expect-error Errors due it not exactly matching - fingerprint.fingerprint.navigator.userAgentData = userAgentData.userAgentMetadata - fingerprint.fingerprint.navigator.userAgent = userAgentData.userAgent - fingerprint.fingerprint.navigator.appVersion = userAgentData.userAgent.replace(`${fingerprint.fingerprint.navigator.appCodeName}/`, '') - - fingerprint.headers['user-agent'] = userAgentData.userAgent - fingerprint.headers['sec-ch-ua'] = `"Microsoft Edge";v="${componentData.edge_major_version}", "Not=A?Brand";v="${componentData.not_a_brand_major_version}", "Chromium";v="${componentData.chrome_major_version}"` - fingerprint.headers['sec-ch-ua-full-version-list'] = `"Microsoft Edge";v="${componentData.edge_version}", "Not=A?Brand";v="${componentData.not_a_brand_version}", "Chromium";v="${componentData.chrome_version}"` - - /* - Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36 EdgA/129.0.0.0 - sec-ch-ua-full-version-list: "Microsoft Edge";v="129.0.2792.84", "Not=A?Brand";v="8.0.0.0", "Chromium";v="129.0.6668.90" - sec-ch-ua: "Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129" - - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 - "Google Chrome";v="129.0.6668.90", "Not=A?Brand";v="8.0.0.0", "Chromium";v="129.0.6668.90" - */ - - return fingerprint - } catch (error) { - throw log(isMobile, 'USER-AGENT-UPDATE', 'An error occurred:' + error, 'error') - } -} \ No newline at end of file diff --git a/src/util/Utils.ts b/src/util/Utils.ts deleted file mode 100644 index aacf661..0000000 --- a/src/util/Utils.ts +++ /dev/null @@ -1,50 +0,0 @@ -import ms from 'ms' - -export default class Util { - - async wait(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) - } - - getFormattedDate(ms = Date.now()): string { - const today = new Date(ms) - const month = String(today.getMonth() + 1).padStart(2, '0') // January is 0 - const day = String(today.getDate()).padStart(2, '0') - const year = today.getFullYear() - - return `${month}/${day}/${year}` - } - - shuffleArray(array: T[]): T[] { - return array.map(value => ({ value, sort: Math.random() })) - .sort((a, b) => a.sort - b.sort) - .map(({ value }) => value) - } - - randomNumber(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1)) + min - } - - chunkArray(arr: T[], numChunks: number): T[][] { - const chunkSize = Math.ceil(arr.length / numChunks) - const chunks: T[][] = [] - - for (let i = 0; i < arr.length; i += chunkSize) { - const chunk = arr.slice(i, i + chunkSize) - chunks.push(chunk) - } - - return chunks - } - - stringToMs(input: string | number): number { - const milisec = ms(input.toString()) - if (!milisec) { - throw new Error('The string provided cannot be parsed to a valid time! Use a format like "1 min", "1m" or "1 minutes"') - } - return milisec - } - -} \ No newline at end of file diff --git a/src/util/Webhook.ts b/src/util/Webhook.ts deleted file mode 100644 index 050f8f3..0000000 --- a/src/util/Webhook.ts +++ /dev/null @@ -1,23 +0,0 @@ -import axios from 'axios' - - -import { Config } from '../interface/Config' - -export async function Webhook(configData: Config, content: string) { - const webhook = configData.webhook - - if (!webhook.enabled || webhook.url.length < 10) return - - const request = { - method: 'POST', - url: webhook.url, - headers: { - 'Content-Type': 'application/json' - }, - data: { - 'content': content - } - } - - await axios(request).catch(() => { }) -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 8785759..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Basic Options */ - "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ - "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* Enable strict null checks. */ - "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ - /* Module Resolution Options */ - "moduleResolution":"node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "types": ["node"], - "typeRoots": ["./node_modules/@types"], - // Keep explicit typeRoots to ensure resolution in environments that don't auto-detect before full install. - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ - "resolveJsonModule": true - }, - "include": [ - "src/**/*.ts", - "src/accounts.json", - "src/config.json", - "src/functions/queries.json" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file