diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0c0dff6..31d90ae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,14 +1,41 @@ name: Continuous Integration + on: pull_request: branches: - main + jobs: - build: - name: Build + build-docker: + name: Build Docker runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + - name: Build service run: docker build . + + build-lint-test: + name: Build, Lint, and Test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: package.json + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Lint + run: npm run lint + + - name: Test + run: npm run test:run diff --git a/.github/workflows/release-please-config.json b/.github/workflows/release-please-config.json new file mode 100644 index 0000000..9c02db9 --- /dev/null +++ b/.github/workflows/release-please-config.json @@ -0,0 +1,8 @@ +{ + "packages": { + ".": { + "release-type": "simple", + "package-name": "zap2xml" + } + } +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d3d04ec..9c05102 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,13 +14,10 @@ jobs: uses: actions/checkout@v4 - name: Setup release please - uses: google-github-actions/release-please-action@v2 + uses: googleapis/release-please-action@v4 id: release with: - token: ${{ secrets.GITHUB_TOKEN }} - release-type: simple - changelog-path: CHANGELOG.md - package-name: zap2xml + config-file: .github/workflows/release-please-config.json - name: Login into GitHub Container Registry if: ${{ steps.release.outputs.release_created }} diff --git a/.gitignore b/.gitignore index fade825..dd2d7eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -.zap2xmlrc +build/ +node_modules/ -config/ -xmltv/ +compose.yaml + +xmltv.xml diff --git a/.zap2xmlrc-example b/.zap2xmlrc-example deleted file mode 100644 index 6263e8e..0000000 --- a/.zap2xmlrc-example +++ /dev/null @@ -1,57 +0,0 @@ -# Basic settings -# start: Number of days to offset from today for the start date (default: 0, cmd: -s) -start=0 -# days: Number of days of program data to fetch (default: 7, cmd: -d) -days=7 -# retries: Number of connection retries before failure (default: 3, max: 20, cmd: -r) -retries=3 - -# Authentication -# user: Username/email for Zap2it account (default: empty, cmd: -u) -user=myemail@example.com -# pass: Password for Zap2it account (default: empty, cmd: -p) -pass=mypassword - -# Cache settings -# cache: Directory to store cached data files (default: cache, cmd: -c) -cache=/config/cache -# ncdays: Number of days from the end to not cache (default: 0, cmd: -n) -ncdays=0 -# ncsdays: Number of days from the start to not cache (default: 0, cmd: -N) -ncsdays=0 -# ncmday: Specific day number to not cache, 1-based relative to start (default: -1, cmd: -B) -ncmday=-1 - -# Output settings -# outfile: Output XML file path (default: xmltv.xml or xtvd.xml, cmd: -o) -outfile=/xmltv/xmltv.xml -# outformat: Output format - xmltv or xtvd (default: xmltv, cmd: -x forces xtvd) -outformat=xmltv - -# Language -# lang: Language code for program data (default: en, cmd: -l) -lang=en - -# Media directories -# icon: Directory to store channel icons (default: disabled, cmd: -i) -icon=/config/icons -# trailer: Directory to store movie trailers (default: disabled, cmd: -t) -trailer=/config/trailers - -# Network -# proxy: HTTP proxy server URL (default: none, cmd: -P) -proxy=http://localhost:8080 - -# XTVD format settings (only used when outformat=xtvd) -# lineuptype: Type of lineup - Cable/CableDigital/Satellite/LocalBroadcast (default: none) -lineuptype=Cable -# lineupname: Name of the lineup (default: none) -lineupname=My Cable Provider -# lineuplocation: Location of the lineup (default: none) -lineuplocation=New York, NY - -# Alternative authentication (TV Guide) -# lineupid: Lineup ID for TV Guide, alternative to username/password (default: none, cmd: -Y) -lineupid=X:80000 -# postalcode: Postal code for TV Guide lineup lookup (default: none, cmd: -Z) -postalcode=01010 diff --git a/Dockerfile b/Dockerfile index 6871720..cc30967 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,18 @@ -FROM alpine:3.13.5 +FROM node:22.17.1-alpine3.22 -ENV SLEEPTIME=43200 +WORKDIR /app -RUN apk add --no-cache \ - perl \ - perl-http-cookies \ - perl-lwp-useragent-determined \ - perl-json \ - perl-json-xs \ - perl-lwp-protocol-https \ - perl-gd +COPY package.json package.json +COPY package-lock.json package-lock.json -WORKDIR /opt +RUN npm ci -COPY zap2xml.pl zap2xml.pl +COPY tsconfig.json tsconfig.json COPY entrypoint.sh entrypoint.sh +COPY src/ src/ -ENTRYPOINT ["./entrypoint.sh"] +RUN npm run build + +RUN ls -l /app + +ENTRYPOINT ["/bin/sh", "-c", "/app/entrypoint.sh"] diff --git a/README.md b/README.md index 57ab0fc..441f4d0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,30 @@ # zap2xml -See [zap2xml](https://web.archive.org/web/20200426004001/zap2xml.awardspace.info/) for original Perl script and guidance for the configuration file. +See [zap2xml](https://web.archive.org/web/20200426004001/zap2xml.awardspace.info/) for original Perl script and guidance +for the configuration file. -## Docker +## How to use + +### Retrieving your Lineup ID + +Visit the [Retrieving Lineup ID](https://github.com/jef/zap2xml/wiki/Retrieving-Lineup-ID) in the Wiki. + +### Node.js + +```bash +npm i && npm run dev +``` + +See [Command line arguments](#command-line-arguments) for configuration options. + +### Docker | Tag | Description | | ------- | ----------------------- | | latest | Stable zap2xml releases | | nightly | HEAD zap2xml release | -### docker-compose (recommended) +#### docker-compose ```yaml services: @@ -17,74 +32,37 @@ services: container_name: zap2xml image: ghcr.io/jef/zap2xml:latest environment: - OPT_ARGS: >- - -I -D -C /config/.zap2xmlrc -o /xmltv/xmltv.xml - TZ: America/New_York # Consider using your timezone + OUTPUT_FILE: /xmltv/xmltv.xml volumes: - - /path/to/zap2xml/config:/config - - /path/to/xmltv:/xmltv # nice for mapping other drives to this that may use xmltv.xml + - ./xmltv:/xmltv restart: unless-stopped ``` +See [Environment variables](#environment-variables) for configuration options. + ## Configuration -### Optional environment variables +### Environment variables -| Variable | Description | Type | Default | -| ------------ | ---------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------- | -| `USER_AGENT` | Custom user agent string for HTTP requests. | String | `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36` | -| `SLEEPTIME` | Number of seconds to sleep between runs (useful for scheduling in Docker or cron). | Integer | `43200` | -| `TZ` | Timezone for program times (affects output XML and Perl's time calculations). | String | System default | +| Variable | Description | Type | Default | +| ------------- | --------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------- | +| `LINEUP_ID` | Lineup ID; You can find this at https://tvlistings.gracenote.com/grid-affiliates.html?aid=orbebb | String | `USA-lineupId-DEFAULT` (Attenna) | +| `TIMESPAN` | Either 3 or 6 hours of shows | Integer | 3 | +| `PREF` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | String | (empty) | +| `POSTAL_CODE` | Postal code of where shows are available. | Integer | 30309 | +| `USER_AGENT` | Custom user agent string for HTTP requests. | String | Uses random if not specified | +| `TZ` | Timezone | String | System default | +| `SLEEP_TIME` | Sleep time before next run in seconds (default: 10800, Only used with Docker.) | Integer | 10800 | +| `OUTPUT_FILE` | Output file name (default: xmltv.xml) | String | xmltv.xml | -### Optional run configurations +### Command line arguments -| Option | Type | Default | Description | Config File | Command Line | -| ---------------- | --------- | ----------- | ------------------------------------------------------ | ----------------------------- | ------------ | -| `start` | Integer | `0` | Number of days to offset from today for the start date | `start=1` | `-s` | -| `days` | Integer | `7` | Number of days of program data to fetch | `days=14` | `-d` | -| `retries` | Integer | `3` | Number of connection retries before failure (max 20) | `retries=5` | `-r` | -| `user` | String | (empty) | Username/email for Zap2it account | `user=myemail@example.com` | `-u` | -| `pass` | String | (empty) | Password for Zap2it account | `pass=mypassword` | `-p` | -| `cache` | Directory | `cache` | Directory to store cached data files | `cache=/config/cache` | `-c` | -| `ncdays` | Integer | `0` | Number of days from the end to not cache | `ncdays=2` | `-n` | -| `ncsdays` | Integer | `0` | Number of days from the start to not cache | `ncsdays=1` | `-N` | -| `ncmday` | Integer | `-1` | Specific day number to not cache (1-based) | `ncmday=3` | `-B` | -| `outfile` | File path | `xmltv.xml` | Output XML file path | `outfile=/xmltv/xmltv.xml` | `-o` | -| `outformat` | String | `xmltv` | Output format (xmltv/xtvd) | `outformat=xtvd` | `-x` | -| `lang` | String | `en` | Language code for program data | `lang=es` | `-l` | -| `icon` | Directory | (disabled) | Directory to store channel icons | `icon=/config/icons` | `-i` | -| `trailer` | Directory | (disabled) | Directory to store movie trailers | `trailer=/config/trailers` | `-t` | -| `proxy` | URL | (none) | HTTP proxy server URL | `proxy=http://localhost:8080` | `-P` | -| `lineuptype` | String | (none) | Type of lineup (XTVD only) | `lineuptype=Cable` | - | -| `lineupname` | String | (none) | Name of the lineup (XTVD only) | `lineupname=My Provider` | - | -| `lineuplocation` | String | (none) | Location of the lineup (XTVD only) | `lineuplocation=New York, NY` | - | -| `lineupid` | String | (none) | Lineup ID for TV Guide | `lineupid=X:80000` | `-Y` | -| `postalcode` | String | (none) | Postal code for TV Guide | `postalcode=01010` | `-Z` | -| `shiftMinutes` | Integer | `0` | Offset program times by minutes | - | `-m` | -| `sleeptime` | Integer | `0` | Sleep between requests (seconds) | - | `-S` | -| `allChan` | Boolean | `false` | Output all channels (not just favorites) | - | `-a` | -| `outputXTVD` | Boolean | `false` | Force XTVD output format | - | `-x` | -| `includeDetails` | Boolean | `false` | Include program details (extra requests) | - | `-D` | -| `includeIcons` | Boolean | `false` | Include program icons (extra requests) | - | `-I` | -| `retainOrder` | Boolean | `false` | Retain website channel order | - | `-b` | -| `quiet` | Boolean | `false` | Quiet mode (no status output) | - | `-q` | -| `wait` | Boolean | `false` | Wait on exit (require keypress) | - | `-w` | -| `hexEncode` | Boolean | `false` | Hex encode HTML entities | - | `-e` | -| `utf8` | Boolean | `false` | UTF-8 encoding (default: ISO-8859-1) | - | `-U` | -| `liveTag` | Boolean | `false` | Output `` tag | - | `-L` | -| `noTBA` | Boolean | `false` | Don't cache files with "TBA" titles | - | `-T` | -| `channelFirst` | Boolean | `false` | Output channel names first | - | `-F` | -| `oldStyle` | Boolean | `false` | Use old tv_grab_na style channel IDs | - | `-O` | -| `appendFlags` | String | (none) | Append flags to program titles | - | `-A` | -| `copyYear` | Boolean | `false` | Copy movie_year to sub-title tags | - | `-M` | -| `addSeries` | Boolean | `false` | Add "series" category to non-movies | - | `-j` | -| `includeXMLTV` | File | (none) | Include XMLTV file in output | - | `-J` | -| `useTVGuide` | Boolean | `false` | Use tvguide.com instead of gracenote.com | - | `-z` | - -### Notes - -- Configuration file values can be overridden by command line options -- The configuration file supports comments (lines starting with `#`) -- Empty lines are ignored -- Values are trimmed of leading/trailing whitespace -- Boolean options (like `outformat=xtvd`) are case-insensitive +| Argument | Description | Type | Default | +| -------------- | --------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------- | +| `--lineupId` | Lineup ID; You can find this at https://tvlistings.gracenote.com/grid-affiliates.html?aid=orbebb | String | `USA-lineupId-DEFAULT` (Attenna) | +| `--timespan` | Either 3 or 6 hours of shows | Integer | 3 | +| `--pref` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | String | (empty) | +| `--postalCode` | Postal code of where shows are available. | Integer | 30309 | +| `--userAgent` | Custom user agent string for HTTP requests. | String | Uses random if not specified | +| `--timezone` | Timezone | String | System default | +| `--outputFile` | Output file name (default: xmltv.xml) | String | xmltv.xml | diff --git a/entrypoint.sh b/entrypoint.sh index f9edb40..63b1ae5 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,9 +1,11 @@ #!/bin/sh +SLEEP_TIME=${SLEEP_TIME:-10800} + while true; do - DATE=$(date) - eval /opt/zap2xml.pl "$OPT_ARGS" - echo "Last run time: $DATE" - echo "Will run in $SLEEPTIME seconds" - sleep "$SLEEPTIME" + DATE=$(date) + node build/src/index.js + echo "Last run time: $DATE" + echo "Will run in $((SLEEP_TIME / 60)) minutes" + sleep "$SLEEP_TIME" done diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1da28ec --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,15 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import prettierConfig from "eslint-plugin-prettier/recommended"; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.strict, + tseslint.configs.stylistic, + prettierConfig, + { + ignores: ["build"], + }, +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ce3dce4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3373 @@ +{ + "name": "@jef/zap2xml", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@jef/zap2xml", + "version": "1.0.0", + "dependencies": { + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@eslint/js": "^9.31.0", + "@types/node": "^24.0.14", + "eslint": "^9.31.0", + "eslint-plugin-prettier": "^5.5.3", + "prettier": "^3.6.2", + "tsx": "^4.20.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.37.0", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", + "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", + "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", + "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", + "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", + "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", + "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", + "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", + "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", + "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", + "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", + "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", + "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", + "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", + "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", + "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", + "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", + "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", + "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", + "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", + "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", + "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", + "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", + "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", + "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", + "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", + "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "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/eslint-utils/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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.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": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.4.tgz", + "integrity": "sha512-MrfbnMgfKexic6mxC4xrSBVQHSvmvhz7qtSDG5cHg4xn8kHXkPltUY44R5u4ghYf+1rXpOvC2drbMcE1rZ3a2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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, + "license": "MIT", + "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-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "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/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "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==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", + "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.6", + "@esbuild/android-arm": "0.25.6", + "@esbuild/android-arm64": "0.25.6", + "@esbuild/android-x64": "0.25.6", + "@esbuild/darwin-arm64": "0.25.6", + "@esbuild/darwin-x64": "0.25.6", + "@esbuild/freebsd-arm64": "0.25.6", + "@esbuild/freebsd-x64": "0.25.6", + "@esbuild/linux-arm": "0.25.6", + "@esbuild/linux-arm64": "0.25.6", + "@esbuild/linux-ia32": "0.25.6", + "@esbuild/linux-loong64": "0.25.6", + "@esbuild/linux-mips64el": "0.25.6", + "@esbuild/linux-ppc64": "0.25.6", + "@esbuild/linux-riscv64": "0.25.6", + "@esbuild/linux-s390x": "0.25.6", + "@esbuild/linux-x64": "0.25.6", + "@esbuild/netbsd-arm64": "0.25.6", + "@esbuild/netbsd-x64": "0.25.6", + "@esbuild/openbsd-arm64": "0.25.6", + "@esbuild/openbsd-x64": "0.25.6", + "@esbuild/openharmony-arm64": "0.25.6", + "@esbuild/sunos-x64": "0.25.6", + "@esbuild/win32-arm64": "0.25.6", + "@esbuild/win32-ia32": "0.25.6", + "@esbuild/win32-x64": "0.25.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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.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, + "license": "BSD-3-Clause", + "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, + "license": "BSD-2-Clause", + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.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, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "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, + "license": "ISC" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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/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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "license": "MIT", + "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" + } + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.10.tgz", + "integrity": "sha512-n6Ze5AGOURWdQ9Kg/wqI1//4OBw9V1zuOTj7uQlpAjtpe2bhgPBpmSFXvapbP3KxcRoqo996J28kdT2ly4w9UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsx": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", + "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", + "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9d68877 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "@jef/zap2xml", + "version": "1.0.0", + "description": "JavaScript implementation of zap2xml", + "exports": "./src/index.ts", + "type": "module", + "engines": { + "node": ">=18" + }, + "scripts": { + "test": "vitest", + "test:run": "vitest run", + "build": "tsc", + "dev": "tsx src/index.ts", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "devDependencies": { + "@eslint/js": "^9.31.0", + "@types/node": "^24.0.14", + "eslint": "^9.31.0", + "eslint-plugin-prettier": "^5.5.3", + "prettier": "^3.6.2", + "tsx": "^4.20.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.37.0", + "vitest": "^3.2.4" + }, + "dependencies": { + "node-fetch": "^3.3.2" + }, + "volta": { + "node": "22.17.1" + } +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..61e7758 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,34 @@ +import { UserAgent } from "./useragents.js"; + +export const config = { + baseUrl: "https://tvlistings.gracenote.com/api/grid", + lineupId: + process.env.LINEUP_ID || + process.argv.find((arg) => arg.startsWith("--lineupId="))?.split("=")[1] || + "USA-lineupId-DEFAULT", + timespan: + process.env.TIMESPAN || + process.argv.find((arg) => arg.startsWith("--timespan="))?.split("=")[1] || + "3", + postalCode: + process.env.POSTAL_CODE || + process.argv + .find((arg) => arg.startsWith("--postalCode=")) + ?.split("=")[1] || + "30309", + pref: + process.env.PREF || + process.argv.find((arg) => arg.startsWith("--pref="))?.split("=")[1] || + "", + timezone: process.env.TZ || "America/New_York", + userAgent: + process.env.USER_AGENT || + process.argv.find((arg) => arg.startsWith("--userAgent="))?.split("=")[1] || + UserAgent, + outputFile: + process.env.OUTPUT_FILE || + process.argv + .find((arg) => arg.startsWith("--outputFile=")) + ?.split("=")[1] || + "xmltv.xml", +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c06425a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,38 @@ +import { writeFileSync } from "node:fs"; +import { getTVListings } from "./tvlistings.js"; +import { buildXmltv } from "./xmltv.js"; +import { config } from "./config.js"; + +function isHelp() { + if (process.argv.includes("--help")) { + console.log(` +Usage: node build/index.js [options] + +Options: +--help Show this help message +--lineupId=ID Lineup ID (default: USA-lineupId-DEFAULT) +--timespan=NUM Timespan in hours (default: 3) +--pref=LIST User preferences, comma separated. Can be m, p, and h (default: empty) +--postalCode=ZIP Postal code (default: 30309) +--userAgent=UA Custom user agent string (default: Uses random if not specified) +--timezone=TZ Timezone (default: America/New_York) +`); + process.exit(0); + } +} + +async function main() { + try { + isHelp(); + + const data = await getTVListings(); + const xml = buildXmltv(data); + + console.log("Writing XMLTV file"); + writeFileSync(config.outputFile, xml, { encoding: "utf-8" }); + } catch (err) { + console.error("Error fetching GridApiResponse:", err); + } +} + +void main(); diff --git a/src/tvlistings.test.ts b/src/tvlistings.test.ts new file mode 100644 index 0000000..f167317 --- /dev/null +++ b/src/tvlistings.test.ts @@ -0,0 +1,237 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { GridApiResponse } from "./tvlistings.js"; +import { getTVListings } from "./tvlistings.js"; + +// Mock fetch globally +const mockFetch = vi.fn(); +global.fetch = mockFetch; + +const mockGridApiResponse: GridApiResponse = { + channels: [ + { + callSign: "KOMODT", + affiliateName: "AMERICAN BROADCASTING COMPANY", + affiliateCallSign: null, + channelId: "19629", + channelNo: "4.1", + events: [ + { + callSign: "KOMODT", + duration: "60", + startTime: "2025-07-18T19:00:00Z", + endTime: "2025-07-18T20:00:00Z", + thumbnail: "p30687311_b_v13_aa", + channelNo: "4.1", + filter: ["filter-news"], + seriesId: "SH05918266", + rating: "TV-PG", + flag: ["New"], + tags: ["Stereo", "CC"], + program: { + title: "GMA3", + id: "EP059182660025", + tmsId: "EP059182660025", + shortDesc: + "BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula.", + season: "5", + releaseYear: null, + episode: "217", + episodeTitle: "Special Episode", + seriesId: "SH05918266", + isGeneric: "0", + }, + }, + ], + id: "196290", + stationGenres: [false], + stationFilters: ["filter-news", "filter-talk"], + thumbnail: + "//zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png?w=55", + }, + ], +}; + +describe("getTVListings", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should successfully fetch TV listings", async () => { + // Mock successful response + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockGridApiResponse, + }); + + const result = await getTVListings(); + + expect(result).toEqual(mockGridApiResponse); + expect(result.channels).toHaveLength(1); + expect(result.channels[0].callSign).toBe("KOMODT"); + }); + + it("should include a User-Agent header in the request", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockGridApiResponse, + }); + + await getTVListings(); + + const callArgs = mockFetch.mock.calls[0]; + const headers = callArgs[1].headers; + expect(headers["User-Agent"]).toBeDefined(); + expect(typeof headers["User-Agent"]).toBe("string"); + expect(headers["User-Agent"].length).toBeGreaterThan(0); + }); + + it("should use a random User-Agent from the predefined list", async () => { + const expectedUserAgents = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", + "Mozilla/5.0 (Linux; Android 13; SM-G991U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", + ]; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockGridApiResponse, + }); + + await getTVListings(); + + const callArgs = mockFetch.mock.calls[0]; + const userAgent = callArgs[1].headers["User-Agent"]; + expect(expectedUserAgents).toContain(userAgent); + }); + + it("should throw an error when response is not ok (4xx status)", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 404, + statusText: "Not Found", + }); + + await expect(getTVListings()).rejects.toThrow( + "Failed to fetch: 404 Not Found", + ); + }); + + it("should throw an error when response is not ok (5xx status)", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 500, + statusText: "Internal Server Error", + }); + + await expect(getTVListings()).rejects.toThrow( + "Failed to fetch: 500 Internal Server Error", + ); + }); + + it("should throw an error when response is not ok (3xx status)", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 301, + statusText: "Moved Permanently", + }); + + await expect(getTVListings()).rejects.toThrow( + "Failed to fetch: 301 Moved Permanently", + ); + }); + + it("should handle empty channels array", async () => { + const emptyResponse: GridApiResponse = { + channels: [], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => emptyResponse, + }); + + const result = await getTVListings(); + expect(result).toEqual(emptyResponse); + expect(result.channels).toHaveLength(0); + }); + + it("should handle multiple channels in response", async () => { + const multiChannelResponse: GridApiResponse = { + channels: [ + { + callSign: "KOMODT", + affiliateName: "AMERICAN BROADCASTING COMPANY", + affiliateCallSign: null, + channelId: "19629", + channelNo: "4.1", + events: [], + id: "196290", + stationGenres: [], + stationFilters: [], + thumbnail: "", + }, + { + callSign: "KOMODT2", + affiliateName: "AMERICAN BROADCASTING COMPANY", + affiliateCallSign: null, + channelId: "19630", + channelNo: "4.2", + events: [], + id: "196300", + stationGenres: [], + stationFilters: [], + thumbnail: "", + }, + ], + }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => multiChannelResponse, + }); + + const result = await getTVListings(); + expect(result.channels).toHaveLength(2); + expect(result.channels[0].callSign).toBe("KOMODT"); + expect(result.channels[1].callSign).toBe("KOMODT2"); + }); + + it("should handle network errors", async () => { + const networkError = new Error("Network error"); + mockFetch.mockRejectedValueOnce(networkError); + + await expect(getTVListings()).rejects.toThrow("Network error"); + }); + + it("should handle JSON parsing errors", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => { + throw new Error("Invalid JSON"); + }, + }); + + await expect(getTVListings()).rejects.toThrow("Invalid JSON"); + }); + + it("should handle malformed JSON response", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => { + throw new SyntaxError("Unexpected token < in JSON at position 0"); + }, + }); + + await expect(getTVListings()).rejects.toThrow( + "Unexpected token < in JSON at position 0", + ); + }); +}); diff --git a/src/tvlistings.ts b/src/tvlistings.ts new file mode 100644 index 0000000..09779da --- /dev/null +++ b/src/tvlistings.ts @@ -0,0 +1,117 @@ +import { config } from "./config.js"; + +export interface Program { + /** "title": "GMA3" */ + title: string; + /** "id": "EP059182660025" */ + id: string; + /** "tmsId": "EP059182660025" */ + tmsId: string; + /** "shortDesc": "BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula." */ + shortDesc: string; + /** "season": "5" */ + season: string; + /** "releaseYear": null */ + releaseYear: string | null; + /** "episode": "217" */ + episode: string; + /** "episodeTitle": null */ + episodeTitle: string | null; + /** "seriesId": "SH05918266" */ + seriesId: string; + /** "isGeneric": "0" */ + isGeneric: string; +} + +export interface Event { + /** "callSign": "KOMODT" */ + callSign: string; + /** "duration": "60" */ + duration: string; + /** "startTime": "2025-07-18T19:00:00Z" */ + startTime: string; + /** "endTime": "2025-07-18T20:00:00Z" */ + endTime: string; + /** "thumbnail": "p30687311_b_v13_aa" */ + thumbnail: string; + /** "channelNo": "4.1" */ + channelNo: string; + /** "filter": ["filter-news"] */ + filter: string[]; + /** "seriesId": "SH05918266" */ + seriesId: string; + /** "rating": "TV-PG" */ + rating: string; + /** "flag": ["New"] */ + flag: string[]; + /** "tags": ["Stereo", "CC"] */ + tags: string[]; + /** "program": {...} */ + program: Program; +} + +export interface Channel { + /** "callSign": "KOMODT" */ + callSign: string; + /** "affiliateName": "AMERICAN BROADCASTING COMPANY" */ + affiliateName: string; + /** "affiliateCallSign": "null" */ + affiliateCallSign: string | null; + /** "channelId": "19629" */ + channelId: string; + /** "channelNo": "4.1" */ + channelNo: string; + /** "events": [...] */ + events: Event[]; + /** "id": "196290" */ + id: string; + /** "stationGenres": [false] */ + stationGenres: boolean[]; + /** "stationFilters": ["filter-news", "filter-talk"] */ + stationFilters: string[]; + /** "thumbnail": "//zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png?w=55" */ + thumbnail: string; +} + +export interface GridApiResponse { + /** "channels": [...] */ + channels: Channel[]; +} + +function buildUrl() { + const params = { + lineupId: config.lineupId, + timespan: config.timespan, + headendId: "lineupId", + country: "USA", + timezone: config.timezone, + postalCode: config.postalCode, + isOverride: "true", + pref: config.pref + "16,128" || "16,128", + aid: "orbebb", + languagecode: "en-us", + time: Math.floor(Date.now() / 1000).toString(), + }; + + const urlParams = new URLSearchParams(params).toString(); + + return `${config.baseUrl}?${urlParams}`; +} + +export async function getTVListings(): Promise { + console.log("Fetching TV listings"); + + const response = await fetch(buildUrl(), { + headers: { + "User-Agent": config.userAgent, + }, + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch: ${response.status} ${response.statusText}`, + ); + } + + return (await response.json()) as GridApiResponse; +} diff --git a/src/useragents.ts b/src/useragents.ts new file mode 100644 index 0000000..fccae0e --- /dev/null +++ b/src/useragents.ts @@ -0,0 +1,12 @@ +const userAgents = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", + "Mozilla/5.0 (Linux; Android 13; SM-G991U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", +]; + +export const UserAgent = + userAgents[Math.floor(Math.random() * userAgents.length)]; diff --git a/src/xmltv.test.ts b/src/xmltv.test.ts new file mode 100644 index 0000000..8687461 --- /dev/null +++ b/src/xmltv.test.ts @@ -0,0 +1,333 @@ +import { describe, expect, it } from "vitest"; +import type { GridApiResponse } from "./tvlistings.js"; +import { + buildChannelsXml, + buildProgrammesXml, + buildXmltv, + escapeXml, + formatDate, +} from "./xmltv.js"; + +const mockData: GridApiResponse = { + channels: [ + { + callSign: "KOMODT", + affiliateName: "AMERICAN BROADCASTING COMPANY", + affiliateCallSign: null, + channelId: "19629", + channelNo: "4.1", + events: [ + { + callSign: "KOMODT", + duration: "60", + startTime: "2025-07-18T19:00:00Z", + endTime: "2025-07-18T20:00:00Z", + thumbnail: "p30687311_b_v13_aa", + channelNo: "4.1", + filter: ["filter-news"], + seriesId: "SH05918266", + rating: "TV-PG", + flag: ["New"], + tags: ["Stereo", "CC"], + program: { + title: "GMA3", + id: "EP059182660025", + tmsId: "EP059182660025", + shortDesc: + "BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula.", + season: "5", + releaseYear: null, + episode: "217", + episodeTitle: "Special Episode", + seriesId: "SH05918266", + isGeneric: "0", + }, + }, + ], + id: "196290", + stationGenres: [false], + stationFilters: ["filter-news", "filter-talk"], + thumbnail: + "//zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png?w=55", + }, + ], +}; + +describe("buildXmltv", () => { + it("should generate valid XML structure", () => { + const result = buildXmltv(mockData); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(""); + }); + + it("should include channel information", () => { + const result = buildXmltv(mockData); + expect(result).toContain(''); + expect(result).toContain("KOMODT"); + expect(result).toContain( + "AMERICAN BROADCASTING COMPANY", + ); + expect(result).toContain("4.1"); + }); + + it("should include programme information", () => { + const result = buildXmltv(mockData); + expect(result).toContain( + '', + ); + expect(result).toContain("GMA3"); + expect(result).toContain("Special Episode"); + expect(result).toContain( + "BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula.", + ); + }); + + it("should include rating information", () => { + const result = buildXmltv(mockData); + expect(result).toContain("TV-PG"); + }); + + it("should include categories from flags and tags", () => { + const result = buildXmltv(mockData); + expect(result).toContain("New"); + expect(result).toContain("Stereo"); + expect(result).toContain("CC"); + }); + + it("should include episode information", () => { + const result = buildXmltv(mockData); + expect(result).toContain('5'); + expect(result).toContain('217'); + expect(result).toContain( + 'SH05918266', + ); + }); + + it("should handle empty data gracefully", () => { + const emptyData: GridApiResponse = { channels: [] }; + const result = buildXmltv(emptyData); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(""); + expect(result).not.toContain(" { + const minimalData: GridApiResponse = { + channels: [ + { + callSign: "TEST", + affiliateName: "", + affiliateCallSign: null, + channelId: "123", + channelNo: "", + events: [ + { + callSign: "TEST", + duration: "30", + startTime: "2025-07-18T19:00:00Z", + endTime: "2025-07-18T19:30:00Z", + thumbnail: "", + channelNo: "", + filter: [], + seriesId: "", + rating: "", + flag: [], + tags: [], + program: { + title: "Test Show", + id: "TEST123", + tmsId: "TEST123", + shortDesc: "", + season: "", + releaseYear: null, + episode: "", + episodeTitle: null, + seriesId: "", + isGeneric: "0", + }, + }, + ], + id: "123", + stationGenres: [], + stationFilters: [], + thumbnail: "", + }, + ], + }; + const result = buildXmltv(minimalData); + expect(result).toContain(''); + expect(result).toContain("TEST"); + expect(result).toContain( + '', + ); + expect(result).toContain("Test Show"); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain(" { + it("should escape XML special characters", () => { + expect(escapeXml("&")).toBe("&"); + expect(escapeXml("<")).toBe("<"); + expect(escapeXml(">")).toBe(">"); + expect(escapeXml('"')).toBe("""); + expect(escapeXml("'")).toBe("'"); + }); + + it("should handle text with multiple special characters", () => { + expect(escapeXml("A & B < C > D \"E\" 'F'")).toBe( + "A & B < C > D "E" 'F'", + ); + }); + + it("should handle normal text without special characters", () => { + expect(escapeXml("Normal text")).toBe("Normal text"); + }); + + it("should handle empty string", () => { + expect(escapeXml("")).toBe(""); + }); +}); + +describe("formatDate", () => { + it("should format ISO date string correctly", () => { + expect(formatDate("2025-07-18T19:00:00Z")).toBe("20250718190000 +0000"); + expect(formatDate("2025-12-31T23:59:59Z")).toBe("20251231235959 +0000"); + }); + + it("should handle different times", () => { + expect(formatDate("2025-01-01T00:00:00Z")).toBe("20250101000000 +0000"); + expect(formatDate("2025-06-15T12:30:45Z")).toBe("20250615123045 +0000"); + }); + + it("should handle edge cases", () => { + expect(formatDate("2025-02-28T23:59:59Z")).toBe("20250228235959 +0000"); + expect(formatDate("2025-03-01T00:00:00Z")).toBe("20250301000000 +0000"); + }); +}); + +describe("buildChannelsXml", () => { + it("should build channel XML correctly", () => { + const result = buildChannelsXml(mockData); + expect(result).toContain(''); + expect(result).toContain("KOMODT"); + expect(result).toContain( + "AMERICAN BROADCASTING COMPANY", + ); + expect(result).toContain("4.1"); + expect(result).toContain( + '', + ); + }); + + it("should handle channels without optional fields", () => { + const minimalChannel: GridApiResponse = { + channels: [ + { + callSign: "TEST", + affiliateName: "", + affiliateCallSign: null, + channelId: "123", + channelNo: "", + events: [], + id: "123", + stationGenres: [], + stationFilters: [], + thumbnail: "", + }, + ], + }; + const result = buildChannelsXml(minimalChannel); + expect(result).toContain(''); + expect(result).toContain("TEST"); + expect(result).not.toContain(" { + it("should build programme XML correctly", () => { + const result = buildProgrammesXml(mockData); + expect(result).toContain( + '', + ); + expect(result).toContain("GMA3"); + expect(result).toContain("Special Episode"); + expect(result).toContain( + "BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula.", + ); + expect(result).toContain("TV-PG"); + expect(result).toContain("New"); + expect(result).toContain("Stereo"); + expect(result).toContain("CC"); + expect(result).toContain('5'); + expect(result).toContain('217'); + expect(result).toContain( + 'SH05918266', + ); + expect(result).toContain(''); + }); + + it("should handle programmes without optional fields", () => { + const minimalProgramme: GridApiResponse = { + channels: [ + { + callSign: "TEST", + affiliateName: "", + affiliateCallSign: null, + channelId: "123", + channelNo: "", + events: [ + { + callSign: "TEST", + duration: "30", + startTime: "2025-07-18T19:00:00Z", + endTime: "2025-07-18T19:30:00Z", + thumbnail: "", + channelNo: "", + filter: [], + seriesId: "", + rating: "", + flag: [], + tags: [], + program: { + title: "Test Show", + id: "TEST123", + tmsId: "TEST123", + shortDesc: "", + season: "", + releaseYear: null, + episode: "", + episodeTitle: null, + seriesId: "", + isGeneric: "0", + }, + }, + ], + id: "123", + stationGenres: [], + stationFilters: [], + thumbnail: "", + }, + ], + }; + const result = buildProgrammesXml(minimalProgramme); + expect(result).toContain( + '', + ); + expect(result).toContain("Test Show"); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain(""); + expect(result).not.toContain("/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +export function formatDate(dateStr: string): string { + // Input: "2025-07-18T19:00:00Z" + // Output: "20250718190000 +0000" + const d = new Date(dateStr); + const pad = (n: number) => n.toString().padStart(2, "0"); + const YYYY = d.getUTCFullYear(); + const MM = pad(d.getUTCMonth() + 1); + const DD = pad(d.getUTCDate()); + const hh = pad(d.getUTCHours()); + const mm = pad(d.getUTCMinutes()); + const ss = pad(d.getUTCSeconds()); + return `${YYYY}${MM}${DD}${hh}${mm}${ss} +0000`; +} + +export function buildChannelsXml(data: GridApiResponse): string { + let xml = ""; + + for (const channel of data.channels) { + xml += ` \n`; + xml += ` ${escapeXml(channel.callSign)}\n`; + + if (channel.affiliateName) { + xml += ` ${escapeXml( + channel.affiliateName, + )}\n`; + } + + if (channel.channelNo) { + xml += ` ${escapeXml( + channel.channelNo, + )}\n`; + } + + if (channel.thumbnail) { + xml += ` \n`; + } + + xml += " \n"; + } + return xml; +} + +export function buildProgrammesXml(data: GridApiResponse): string { + let xml = ""; + + for (const channel of data.channels) { + for (const event of channel.events) { + xml += ` \n`; + + xml += ` ${escapeXml(event.program.title)}\n`; + + if (event.program.episodeTitle) { + xml += ` ${escapeXml( + event.program.episodeTitle, + )}\n`; + } + + if (event.program.shortDesc) { + xml += ` ${escapeXml(event.program.shortDesc)}\n`; + } + + if (event.rating) { + xml += ` ${escapeXml( + event.rating, + )}\n`; + } + + if (event.flag && event.flag.length > 0) { + for (const flag of event.flag) { + xml += ` ${escapeXml(flag)}\n`; + } + } + + if (event.tags && event.tags.length > 0) { + for (const tag of event.tags) { + xml += ` ${escapeXml(tag)}\n`; + } + } + + if (event.program.season) { + xml += ` ${escapeXml( + event.program.season, + )}\n`; + } + + if (event.program.episode) { + xml += ` ${escapeXml( + event.program.episode, + )}\n`; + } + + if (event.program.seriesId) { + xml += ` ${escapeXml( + event.program.seriesId, + )}\n`; + } + + if (event.thumbnail) { + xml += ` \n`; + } + + xml += " \n"; + } + } + + return xml; +} + +export function buildXmltv(data: GridApiResponse): string { + console.log("Building XMLTV file"); + + let xml = + '\n\n'; + xml += buildChannelsXml(data); + xml += buildProgrammesXml(data); + xml += "\n"; + + return xml; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3421744 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "module": "Node16", + "moduleResolution": "Node16", + "target": "ESNext", + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.test.ts" + ] +} diff --git a/zap2xml.pl b/zap2xml.pl deleted file mode 100755 index 937c83d..0000000 --- a/zap2xml.pl +++ /dev/null @@ -1,1596 +0,0 @@ -#!/usr/bin/env perl - -BEGIN { $SIG{__DIE__} = sub { - return if $^S; - my $msg = join(" ", @_); - print STDERR "$msg"; - if ($msg =~ /can't locate/i) { - print "\nSee homepage for tips on installing missing modules (example: \"perl -MCPAN -e shell\")\n"; - if ($^O eq 'MSWin32') { - print "Use \"ppm install\" on windows\n"; - } - } - if ($^O eq 'MSWin32') { - if ($msg =~ /uri.pm/i && $msg =~ /temp/i) { - print "\nIf your scanner deleted the perl URI.pm file see the homepage for tips\n"; - if ($msg =~ /(\ .\:.+?par-.+?\\)/) { - print "(Delete the $1 folder and retry)\n"; - } - } - sleep(5); - } - exit 1; -}} - -use Compress::Zlib; -use Encode; -use File::Basename; -use File::Copy; -use Getopt::Std; -use HTTP::Cookies; -use URI; -use URI::Escape; -use LWP::UserAgent; -use LWP::ConnCache; -use POSIX; -use Time::Local; -use Time::Piece; -use JSON; - -no warnings 'utf8'; - -STDOUT->autoflush(1); -STDERR->autoflush(1); - -$VERSION = "2018-12-01"; -print "zap2xml ($VERSION)\nCommand line: $0 " . join(" ",@ARGV) . "\n"; - -%options=(); -getopts("?aA:bB:c:C:d:DeE:Fgi:IjJ:l:Lm:Mn:N:o:Op:P:qRr:s:S:t:Tu:UwWxY:zZ:89",\%options); - -$homeDir = $ENV{HOME}; -$homeDir = $ENV{USERPROFILE} if !defined($homeDir); -$homeDir = '.' if !defined($homeDir); -$confFile = $homeDir . '/.zap2xmlrc'; - -# Defaults -$start = 0; -$days = 7; -$ncdays = 0; -$ncsdays = 0; -$ncmday = -1; -$retries = 3; -$outFile = 'xmltv.xml'; -$outFile = 'xtvd.xml' if defined $options{x}; -$cacheDir = 'cache'; -$lang = 'en'; -$userEmail = ''; -$password = ''; -$proxy; -$postalcode; -$country; -$lineupId; -$device; -$sleeptime = 0; -$allChan = 0; -$shiftMinutes = 0; - -$userAgent = $ENV{USER_AGENT} || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; - -$outputXTVD = 0; -$lineuptype; -$lineupname; -$lineuplocation; - -$zapToken; -$zapPref='-'; -%zapFavorites=(); -%sidCache=(); - -$sTBA = "\\bTBA\\b|To Be Announced"; - -%tvgfavs=(); - -&HELP_MESSAGE() if defined $options{'?'}; - -$confFile = $options{C} if defined $options{C}; -# read config file -if (open (CONF, $confFile)) -{ - &pout("Reading config file: $confFile\n"); - while () - { - s/#.*//; # comments - if (/^\s*$/i) { } - elsif (/^\s*start\s*=\s*(\d+)/i) { $start = $1; } - elsif (/^\s*days\s*=\s*(\d+)/i) { $days = $1; } - elsif (/^\s*ncdays\s*=\s*(\d+)/i) { $ncdays = $1; } - elsif (/^\s*ncsdays\s*=\s*(\d+)/i) { $ncsdays = $1; } - elsif (/^\s*ncmday\s*=\s*(\d+)/i) { $ncmday = $1; } - elsif (/^\s*retries\s*=\s*(\d+)/i) { $retries = $1; } - elsif (/^\s*user[\w\s]*=\s*(.+)/i) { $userEmail = &rtrim($1); } - elsif (/^\s*pass[\w\s]*=\s*(.+)/i) { $password = &rtrim($1); } - elsif (/^\s*cache\s*=\s*(.+)/i) { $cacheDir = &rtrim($1); } - elsif (/^\s*icon\s*=\s*(.+)/i) { $iconDir = &rtrim($1); } - elsif (/^\s*trailer\s*=\s*(.+)/i) { $trailerDir = &rtrim($1); } - elsif (/^\s*lang\s*=\s*(.+)/i) { $lang = &rtrim($1); } - elsif (/^\s*outfile\s*=\s*(.+)/i) { $outFile = &rtrim($1); } - elsif (/^\s*proxy\s*=\s*(.+)/i) { $proxy = &rtrim($1); } - elsif (/^\s*outformat\s*=\s*(.+)/i) { $outputXTVD = 1 if $1 =~ /xtvd/i; } - elsif (/^\s*lineupid\s*=\s*(.+)/i) { $lineupId = &rtrim($1); } - elsif (/^\s*lineupname\s*=\s*(.+)/i) { $lineupname = &rtrim($1); } - elsif (/^\s*lineuptype\s*=\s*(.+)/i) { $lineuptype = &rtrim($1); } - elsif (/^\s*lineuplocation\s*=\s*(.+)/i) { $lineuplocation = &rtrim($1); } - elsif (/^\s*postalcode\s*=\s*(.+)/i) { $postalcode = &rtrim($1); } - else - { - die "Oddline in config file \"$confFile\".\n\t$_"; - } - } - close (CONF); -} -&HELP_MESSAGE() if !(%options) && $userEmail eq ''; - -$cacheDir = $options{c} if defined $options{c}; -$days = $options{d} if defined $options{d}; -$ncdays = $options{n} if defined $options{n}; -$ncsdays = $options{N} if defined $options{N}; -$ncmday = $options{B} if defined $options{B}; -$start = $options{s} if defined $options{s}; -$retries = $options{r} if defined $options{r}; -$iconDir = $options{i} if defined $options{i}; -$trailerDir = $options{t} if defined $options{t}; -$lang = $options{l} if defined $options{l}; -$outFile = $options{o} if defined $options{o}; -$password = $options{p} if defined $options{p}; -$userEmail = $options{u} if defined $options{u}; -$proxy = $options{P} if defined $options{P}; -$zlineupId = $options{Y} if defined $options{Y}; -$zipcode = $options{Z} if defined $options{Z}; -$includeXMLTV = $options{J} if defined $options{J} && -e $options{J}; -$outputXTVD = 1 if defined $options{x}; -$allChan = 1 if defined($options{a}); -$allChan = 1 if defined($zipcode) && defined($zlineupId); -$sleeptime = $options{S} if defined $options{S}; -$shiftMinutes = $options{m} if defined $options{m}; -$ncdays = $days - $ncdays; # make relative to the end -$urlRoot = 'https://tvlistings.gracenote.com/'; -$urlAssets = 'https://zap2it.tmsimg.com/assets/'; -$tvgurlRoot = 'http://mobilelistings.tvguide.com/'; -$tvgMapiRoot = 'http://mapi.tvguide.com/'; -$tvgurl = 'https://www.tvguide.com/'; -$tvgspritesurl = 'http://static.tvgcdn.net/sprites/'; -$retries = 20 if $retries > 20; # Too many - -my %programs = (); -my $cp; -my %stations = (); -my $cs; -my $rcs; -my %schedule = (); -my $sch; -my %logos = (); - -my $coNum = 0; -my $tb = 0; -my $treq = 0; -my $tsocks = (); -my $expired = 0; -my $ua; -my $tba = 0; -my $exp = 0; -my @fh = (); - -my $XTVD_startTime; -my $XTVD_endTime; - -if (! -d $cacheDir) { - mkdir($cacheDir) or die "Can't mkdir: $!\n"; -} else { - opendir (DIR, "$cacheDir/"); - @cacheFiles = grep(/\.html|\.js/,readdir(DIR)); - closedir (DIR); - foreach $cacheFile (@cacheFiles) { - $fn = "$cacheDir/$cacheFile"; - $atime = (stat($fn))[8]; - if ($atime + ( ($days + 2) * 86400) < time) { - &pout("Deleting old cached file: $fn\n"); - &unf($fn); - } - } -} - -my $s1 = time(); -if (defined($options{z})) { - - &login() if !defined($options{a}); # get favorites - &parseTVGIcons() if defined($iconDir); - $gridHours = 3; - $maxCount = $days * (24 / $gridHours); - $offset = $start * 3600 * 24 * 1000; - $ms = &hourToMillis() + $offset; - - for ($count=0; $count < $maxCount; $count++) { - $curday = int($count / (24/$gridHours)) + 1; - if ($count == 0) { - $XTVD_startTime = $ms; - } elsif ($count == $maxCount - 1) { - $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; - } - - $fn = "$cacheDir/$ms\.js\.gz"; - if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { - &login() if !defined($zlineupId); - my $duration = $gridHours * 60; - my $tvgstart = substr($ms, 0, -3); - $rs = &getURL($tvgurlRoot . "Listingsweb/ws/rest/schedules/$zlineupId/start/$tvgstart/duration/$duration", 1); - last if ($rs eq ''); - $rc = Encode::encode('utf8', $rs); - &wbf($fn, Compress::Zlib::memGzip($rc)); - } - &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); - &parseTVGGrid($fn); - - if (defined($options{T}) && $tba) { - &pout("Deleting: $fn (contains \"$sTBA\")\n"); - &unf($fn); - } - if ($exp) { - &pout("Deleting: $fn (expired)\n"); - &unf($fn); - } - $exp = 0; - $tba = 0; - $ms += ($gridHours * 3600 * 1000); - } - -} else { - - &login() if !defined($options{a}); # get favorites - $gridHours = 3; - $maxCount = $days * (24 / $gridHours); - $offset = $start * 3600 * 24 * 1000; - $ms = &hourToMillis() + $offset; - for ($count=0; $count < $maxCount; $count++) { - $curday = int($count / (24/$gridHours)) + 1; - if ($count == 0) { - $XTVD_startTime = $ms; - } elsif ($count == $maxCount - 1) { - $XTVD_endTime = $ms + ($gridHours * 3600000) - 1; - } - - $fn = "$cacheDir/$ms\.js\.gz"; - if (! -e $fn || $curday > $ncdays || $curday <= $ncsdays || $curday == $ncmday) { - my $zstart = substr($ms, 0, -3); - $params = "?time=$zstart×pan=$gridHours&pref=$zapPref&"; - $params .= &getZapGParams(); - $params .= '&TMSID=&AffiliateID=lat&FromPage=TV%20Grid'; - $params .= '&ActivityID=1&OVDID=&isOverride=true'; - $rs = &getURL($urlRoot . "api/grid$params",1); - last if ($rs eq ''); - $rc = Encode::encode('utf8', $rs); - &wbf($fn, Compress::Zlib::memGzip($rc)); - } - - &pout("[" . ($count+1) . "/" . "$maxCount] Parsing: $fn\n"); - &parseJSON($fn); - - if (defined($options{T}) && $tba) { - &pout("Deleting: $fn (contains \"$sTBA\")\n"); - &unf($fn); - } - if ($exp) { - &pout("Deleting: $fn (expired)\n"); - &unf($fn); - } - $exp = 0; - $tba = 0; - $ms += ($gridHours * 3600 * 1000); - } - -} -my $s2 = time(); -my $tsockt = scalar(keys %tsocks); -&pout("Downloaded " . &pl($tb, "byte") - . " in " . &pl($treq, "http request") - . " using " . &pl($tsockt > 0 ? $tsockt : $treq, "socket") . ".\n") if $tb > 0; -&pout("Expired programs: $expired\n") if $expired > 0; -&pout("Writing XML file: $outFile\n"); -open($FH, ">$outFile"); -my $enc = 'ISO-8859-1'; -if (defined($options{U})) { - $enc = 'UTF-8'; -} -if ($outputXTVD) { - &printHeaderXTVD($FH, $enc); - &printStationsXTVD($FH); - &printLineupsXTVD($FH); - &printSchedulesXTVD($FH); - &printProgramsXTVD($FH); - &printGenresXTVD($FH); - &printFooterXTVD($FH); -} else { - &printHeader($FH, $enc); - &printChannels($FH); - if (defined($includeXMLTV)) { - &pout("Reading XML file: $includeXMLTV\n"); - &incXML("; -} else { - sleep(3) if ($^O eq 'MSWin32'); -} - -exit 0; - -sub incXML { - my ($st, $en, $FH) = @_; - open($XF, "<$includeXMLTV"); - while (<$XF>) { - if (/^\s*$st/../^\s*$en/) { - print $FH $_ unless /^\s*$en/ - } - } - close($XF); -} - -sub pl { - my($i, $s) = @_; - my $r = "$i $s"; - return $i == 1 ? $r : $r . "s"; -} - -sub pout { - print @_ if !defined $options{q}; -} - -sub perr { - warn @_; -} - -sub rtrim { - my $s = shift; - $s =~ s/\s+$//; - return $s; -} - -sub trim { - my $s = shift; - $s =~ s/^\s+//; - $s =~ s/\s+$//; - return $s; -} - -sub trim2 { - my $s = &trim(shift); - $s =~ s/[^\w\s\(\)\,]//gsi; - $s =~ s/\s+/ /gsi; - return $s; -} - -sub _rtrim3 { - my $s = shift; - return substr($s, 0, length($s)-3); -} - -sub convTime { - my $t = shift; - $t += $shiftMinutes * 60 * 1000; - return strftime "%Y%m%d%H%M%S", localtime(&_rtrim3($t)); -} - -sub convTimeXTVD { - my $t = shift; - $t += $shiftMinutes * 60 * 1000; - return strftime "%Y-%m-%dT%H:%M:%SZ", gmtime(&_rtrim3($t)); -} - -sub convOAD { - return strftime "%Y%m%d", gmtime(&_rtrim3(shift)); -} - -sub convOADXTVD { - return strftime "%Y-%m-%d", gmtime(&_rtrim3(shift)); -} - -sub convDurationXTVD { - my $duration = shift; - my $hour = int($duration / 3600000); - my $minutes = int(($duration - ($hour * 3600000)) / 60000); - return sprintf("PT%02dH%02dM", $hour, $minutes); -} - -sub appendAsterisk { - my ($title, $station, $s) = @_; - if (defined($options{A})) { - if (($options{A} =~ "new" && defined($schedule{$station}{$s}{new})) - || ($options{A} =~ "live" && defined($schedule{$station}{$s}{live}))) { - $title .= " *"; - } - } - return $title; -} - -sub stationToChannel { - my $s = shift; - if (defined($options{z})) { - return sprintf("I%s.%s.tvguide.com", $stations{$s}{number},$stations{$s}{stnNum}); - } elsif (defined($options{O})) { - return sprintf("C%s%s.gracenote.com",$stations{$s}{number},lc($stations{$s}{name})); - } elsif (defined($options{9})) { - return sprintf("I%s.labs.gracenote.com",$stations{$s}{stnNum}); - } - return sprintf("I%s.%s.gracenote.com", $stations{$s}{number},$stations{$s}{stnNum}); -} - -sub sortChan { - if (defined($stations{$a}{order}) && defined($stations{$b}{order})) { - my $c = $stations{$a}{order} <=> $stations{$b}{order}; - if ($c == 0) { return $stations{$a}{stnNum} <=> $stations{$b}{stnNum} } - else { return $c }; - } else { - return $stations{$a}{name} cmp $stations{$b}{name}; - } -} - -sub enc { - my $t = shift; - if (!defined($options{U})) {$t = Encode::decode('utf8', $t);} - if (!defined($options{E}) || $options{E} =~ /amp/) {$t =~ s/&/&/gs;} - if (!defined($options{E}) || $options{E} =~ /quot/) {$t =~ s/"/"/gs;} - if (!defined($options{E}) || $options{E} =~ /apos/) {$t =~ s/'/'/gs;} - if (!defined($options{E}) || $options{E} =~ /lt/) {$t =~ s//>/gs;} - if (defined($options{e})) { - $t =~ s/([^\x20-\x7F])/'&#' . ord($1) . ';'/gse; - } - return $t; -} - -sub printHeader { - my ($FH, $enc) = @_; - print $FH "\n"; - print $FH "\n\n"; - if (defined($options{z})) { - print $FH "\n"; -} - -sub printFooter { - my $FH = shift; - print $FH "\n"; -} - -sub printChannels { - my $FH = shift; - for my $key ( sort sortChan keys %stations ) { - $sname = &enc($stations{$key}{name}); - $fname = &enc($stations{$key}{fullname}); - $snum = $stations{$key}{number}; - print $FH "\t\n"; - print $FH "\t\t" . $sname . "\n" if defined($options{F}) && defined($sname); - if (defined($snum)) { - ©Logo($key); - print $FH "\t\t" . $snum . " " . $sname . "\n" if ($snum ne ''); - print $FH "\t\t" . $snum . "\n" if ($snum ne ''); - } - print $FH "\t\t" . $sname . "\n" if !defined($options{F}) && defined($sname); - print $FH "\t\t" . $fname . "\n" if (defined($fname)); - if (defined($stations{$key}{logoURL})) { - print $FH "\t\t\n"; - } - print $FH "\t\n"; - } -} - -sub printProgrammes { - my $FH = shift; - for my $station ( sort sortChan keys %stations ) { - my $i = 0; - my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; - foreach $s (@keyArray) { - if ($#keyArray <= $i && !defined($schedule{$station}{$s}{endtime})) { - delete $schedule{$station}{$s}; - next; - } - my $p = $schedule{$station}{$s}{program}; - my $startTime = &convTime($schedule{$station}{$s}{time}); - my $startTZ = &timezone($schedule{$station}{$s}{time}); - my $endTime; - if (defined($schedule{$station}{$s}{endtime})) { - $endTime = $schedule{$station}{$s}{endtime}; - } else { - $endTime = $schedule{$station}{$keyArray[$i+1]}{time}; - } - - my $stopTime = &convTime($endTime); - my $stopTZ = &timezone($endTime); - - print $FH "\t\n"; - if (defined($programs{$p}{title})) { - my $title = &enc($programs{$p}{title}); - $title = &appendAsterisk($title, $station, $s); - print $FH "\t\t" . $title . "\n"; - } - - if (defined($programs{$p}{episode}) || (defined($options{M}) && defined($programs{$p}{movie_year}))) { - print $FH "\t\t"; - if (defined($programs{$p}{episode})) { - print $FH &enc($programs{$p}{episode}); - } else { - print $FH "Movie (" . $programs{$p}{movie_year} . ")"; - } - print $FH "\n" - } - - print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); - - if (defined($programs{$p}{actor}) - || defined($programs{$p}{director}) - || defined($programs{$p}{writer}) - || defined($programs{$p}{producer}) - || defined($programs{$p}{preseter}) - ) { - print $FH "\t\t\n"; - &printCredits($FH, $p, "director"); - foreach my $g (sort { $programs{$p}{actor}{$a} <=> $programs{$p}{actor}{$b} } keys %{$programs{$p}{actor}} ) { - print $FH "\t\t\t" . &enc($g) . "\n"; - } - &printCredits($FH, $p, "writer"); - &printCredits($FH, $p, "producer"); - &printCredits($FH, $p, "presenter"); - print $FH "\t\t\n"; - } - - my $date; - if (defined($programs{$p}{movie_year})) { - $date = $programs{$p}{movie_year}; - } elsif (defined($programs{$p}{originalAirDate}) && $p =~ /^EP|^\d/) { - $date = &convOAD($programs{$p}{originalAirDate}); - } - print $FH "\t\t$date\n" if defined($date); - - if (defined($programs{$p}{genres})) { - foreach my $g (sort { $programs{$p}{genres}{$a} <=> $programs{$p}{genres}{$b} or $a cmp $b } keys %{$programs{$p}{genres}} ) { - print $FH "\t\t" . &enc(ucfirst($g)) . "\n"; - } - } - - print $FH "\t\t" . $programs{$p}{duration} . "\n" if defined($programs{$p}{duration}); - - if (defined($programs{$p}{imageUrl})) { - print $FH "\t\t\n"; - } - - if (defined($programs{$p}{url})) { - print $FH "\t\t" . &enc($programs{$p}{url}) . "\n"; - } - - my $xs; - my $xe; - - if (defined($programs{$p}{seasonNum}) && defined($programs{$p}{episodeNum})) { - my $s = $programs{$p}{seasonNum}; - my $sf = sprintf("S%0*d", &max(2, length($s)), $s); - my $e = $programs{$p}{episodeNum}; - my $ef = sprintf("E%0*d", &max(2, length($e)), $e); - - $xs = int($s) - 1; - $xe = int($e) - 1; - - if ($s > 0 || $e > 0) { - print $FH "\t\t" . $sf . $ef . "\n"; - } - } - - $dd_prog_id = $p; - if ( $dd_prog_id =~ /^(..\d{8})(\d{4})/ ) { - $dd_prog_id = sprintf("%s.%s",$1,$2); - print $FH "\t\t" . $dd_prog_id . "\n"; - } - - if (defined($xs) && defined($xe) && $xs >= 0 && $xe >= 0) { - print $FH "\t\t" . $xs . "." . $xe . ".\n"; - } - - if (defined($schedule{$station}{$s}{quality})) { - print $FH "\t\t\n"; - } - my $new = defined($schedule{$station}{$s}{new}); - my $live = defined($schedule{$station}{$s}{live}); - my $cc = defined($schedule{$station}{$s}{cc}); - - if (! $new && ! $live && $p =~ /^EP|^SH|^\d/) { - print $FH "\t\t\n"; - } - - if (defined($schedule{$station}{$s}{premiere})) { - print $FH "\t\t" . $schedule{$station}{$s}{premiere} . "\n"; - } - - if (defined($schedule{$station}{$s}{finale})) { - print $FH "\t\t" . $schedule{$station}{$s}{finale} . "\n"; - } - - print $FH "\t\t\n" if $new; - # not part of XMLTV format yet? - print $FH "\t\t\n" if (defined($options{L}) && $live); - print $FH "\t\t\n" if $cc; - - if (defined($programs{$p}{rating})) { - print $FH "\t\t\n\t\t\t" . $programs{$p}{rating} . "\n\t\t\n" - } - - if (defined($programs{$p}{starRating})) { - print $FH "\t\t\n\t\t\t" . $programs{$p}{starRating} . "/4\n\t\t\n"; - } - print $FH "\t\n"; - $i++; - } - } -} - -sub printHeaderXTVD { - my ($FH, $enc) = @_; - print $FH "\n"; - print $FH "\n"; -} - -sub printCredits { - my ($FH, $p, $s) = @_; - foreach my $g (sort { $programs{$p}{$s}{$a} <=> $programs{$p}{$s}{$b} } keys %{$programs{$p}{$s}} ) { - print $FH "\t\t\t<$s>" . &enc($g) . "\n"; - } -} - -sub printFooterXTVD { - my $FH = shift; - print $FH "\n"; -} - -sub printStationsXTVD { - my $FH = shift; - print $FH "\n"; - for my $key ( sort sortChan keys %stations ) { - print $FH "\t\n"; - if (defined($stations{$key}{number})) { - $sname = &enc($stations{$key}{name}); - print $FH "\t\t" . $sname . "\n"; - print $FH "\t\t" . $sname . "\n"; - print $FH "\t\t" . $stations{$key}{number} . "\n"; - ©Logo($key); - } - print $FH "\t\n"; - } - print $FH "\n"; -} - -sub printLineupsXTVD { - my $FH = shift; - print $FH "\n"; - print $FH "\t\n"; - for my $key ( sort sortChan keys %stations ) { - if (defined($stations{$key}{number})) { - print $FH "\t\n"; - } - } - print $FH "\t\n"; - print $FH "\n"; -} - -sub printSchedulesXTVD { - my $FH = shift; - print $FH "\n"; - for my $station ( sort sortChan keys %stations ) { - my $i = 0; - my @keyArray = sort { $schedule{$station}{$a}{time} cmp $schedule{$station}{$b}{time} } keys %{$schedule{$station}}; - foreach $s (@keyArray) { - if ($#keyArray <= $i) { - delete $schedule{$station}{$s}; - next; - } - my $p = $schedule{$station}{$s}{program}; - my $startTime = &convTimeXTVD($schedule{$station}{$s}{time}); - my $stopTime = &convTimeXTVD($schedule{$station}{$keyArray[$i+1]}{time}); - my $duration = &convDurationXTVD($schedule{$station}{$keyArray[$i+1]}{time} - $schedule{$station}{$s}{time}); - - print $FH "\t\n"; - $i++; - } - } - print $FH "\n"; -} - -sub printProgramsXTVD { - my $FH = shift; - print $FH "\n"; - foreach $p (keys %programs) { - print $FH "\t\n"; - print $FH "\t\t" . &enc($programs{$p}{title}) . "\n" if defined($programs{$p}{title}); - print $FH "\t\t" . &enc($programs{$p}{episode}) . "\n" if defined($programs{$p}{episode}); - print $FH "\t\t" . &enc($programs{$p}{description}) . "\n" if defined($programs{$p}{description}); - - if (defined($programs{$p}{movie_year})) { - print $FH "\t\t" . $programs{$p}{movie_year} . "\n"; - } else { #Guess - my $showType = "Series"; - if ($programs{$p}{title} =~ /Paid Programming/i) { - $showType = "Paid Programming"; - } - print $FH "\t\t$showType\n"; - print $FH "\t\tEP" . substr($p,2,8) . "\n"; - print $FH "\t\t" . &convOADXTVD($programs{$p}{originalAirDate}) . "\n" if defined($programs{$p}{originalAirDate}); - } - print $FH "\t\n"; - } - print $FH "\n"; -} - -sub printGenresXTVD { - my $FH = shift; - print $FH "\n"; - foreach $p (keys %programs) { - if (defined($programs{$p}{genres}) && $programs{$p}{genres}{movie} != 1) { - print $FH "\t\n"; - foreach my $g (keys %{$programs{$p}{genres}}) { - print $FH "\t\t\n"; - print $FH "\t\t\t" . &enc(ucfirst($g)) . "\n"; - print $FH "\t\t\t0\n"; - print $FH "\t\t\n"; - } - print $FH "\t\n"; - } - } - print $FH "\n"; -} - -sub loginTVG { - my $r = &ua_get($tvgurl . 'signin/'); - if ($r->is_success) { - my $str = $r->decoded_content; - if ($str =~ / $token, - email => $userEmail, - password => $password, - }, 'X-Requested-With' => 'XMLHttpRequest' - ); - - $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); - if ($dc =~ /success/) { - $ua->cookie_jar->scan(sub { if ($_[1] eq "ServiceID") { $zlineupId = $_[2]; }; }); - if (!defined($options{a})) { - my $r = &ua_get($tvgurl . "user/favorites/?provider=$zlineupId",'X-Requested-With' => 'XMLHttpRequest'); - $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); - if ($dc =~ /\{\"code\":200/) { - &parseTVGFavs($dc); - } - } - return $dc; - } else { - &pout("[Attempt $rc] " . $r->status_line . ":" . $dc . "\n"); - sleep ($sleeptime + 1); - } - } - die "Failed to login within $retries retries.\n"; - } - } else { - die "Login token not found\n"; - } - } -} - -sub loginZAP { - my $rc = 0; - while ($rc++ < $retries) { - my $r = &ua_post($urlRoot . 'api/user/login', - { - emailid => $userEmail, password => $password, - usertype => '0', facebookuser =>'false', - } - ); - - $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); - if ($r->is_success) { - my $t = decode_json($dc); - $zapToken = $t->{'token'}; - $zapPref = ''; - $zapPref .= "m" if ($t->{isMusic}); - $zapPref .= "p" if ($t->{isPPV}); - $zapPref .= "h" if ($t->{isHD}); - if ($zapPref eq '') { $zapPref = '-' } - else { - $zapPref = join(",", split(//, $zapPref)); - } - - my $prs = $t->{'properties'}; - $postalcode = $prs->{2002}; - $country = $prs->{2003}; - ($lineupId, $device) = split(/:/, $prs->{2004}); - if (!defined($options{a})) { - my $r = &ua_post($urlRoot . "api/user/favorites", { token => $zapToken }, 'X-Requested-With' => 'XMLHttpRequest'); - $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); - if ($r->is_success) { - &parseZFavs($dc); - } else { - &perr("FF" . $r->status_line . ": $dc\n"); - } - } - return $dc; - } else { - &pout("[Attempt $rc] " . $dc . "\n"); - sleep ($sleeptime + 1); - } - } - die "Failed to login within $retries retries.\n"; -} - -sub getZapGParams { - my %hash = &getZapParams(); - $hash{country} = delete $hash{countryCode}; - return join("&", map { "$_=$hash{$_}" } keys %hash); -} - -sub getZapPParams { - my %hash = &getZapParams(); - delete $hash{lineupId}; - return %hash; -} - -sub getZapParams { - my %phash = (); - if (defined($zlineupId) || defined($zipcode)) { - $postalcode = $zipcode; - $country = "USA"; - $country = "CAN" if ($zipcode =~ /[A-z]/); - if ($zlineupId =~ /:/) { - ($lineupId, $device) = split(/:/, $zlineupId); - } else { - $lineupId = $zlineupId; - $device = "-"; - } - $phash{postalCode} = $postalcode; - } else { - $phash{token} = &getZToken(); - } - $phash{lineupId} = "$country-$lineupId-DEFAULT"; - $phash{postalCode} = $postalcode; - $phash{countryCode} = $country; - $phash{headendId} = $lineupId; - $phash{device} = $device; - $phash{aid} = 'lat'; - return %phash; -} - -sub login { - if (!defined($userEmail) || $userEmail eq '' || !defined($password) || $password eq '') { - if (!defined($zlineupId)) { - die "Unable to login: Unspecified username or password.\n" - } - } - - if (!defined($ua)) { - $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 }); # WIN - $ua->conn_cache(LWP::ConnCache->new( total_capacity => undef )); - $ua->cookie_jar(HTTP::Cookies->new); - $ua->proxy(['http', 'https'], $proxy) if defined($proxy); - $ua->agent($userAgent); - $ua->default_headers->push_header('Accept-Encoding' => 'gzip, deflate'); - } - - if ($userEmail ne '' && $password ne '') { - &pout("Logging in as \"$userEmail\" (" . localtime . ")\n"); - if (defined($options{z})) { - &loginTVG(); - } else { - &loginZAP(); - } - } else { - &pout("Connecting with lineupId \"$zlineupId\" (" . localtime . ")\n"); - } -} - -sub ua_stats { - my ($s, @p) = @_; - my $r; - if ($s eq 'POST') { - $r = $ua->post(@p); - } else { - $r = $ua->get(@p); - } - my $cc = $ua->conn_cache; - if (defined($cc)) { - my @cxns = $cc->get_connections(); - foreach (@cxns) { - $tsocks{$_} = 1; - } - } - $treq++; - $tb += length($r->content); - return $r; -} - -sub ua_get { return &ua_stats('GET', @_); } -sub ua_post { return &ua_stats('POST', @_); } - -sub getURL { - my $url = shift; - my $er = shift; - &login() if !defined($ua); - - my $rc = 0; - while ($rc++ < $retries) { - &pout("[$treq] Getting: $url\n"); - sleep $sleeptime; # do these rapid requests flood servers? - my $r = &ua_get($url); - my $cl = length($r->content); - my $dc = $r->decoded_content( raise_error => 1 ); - if ($r->is_success && $cl) { - return $dc; - } elsif ($r->code == 500 && $dc =~ /Could not load details/) { - &pout("$dc\n"); - return ""; - } else { - &perr("[Attempt $rc] $cl:" . $r->status_line . "\n"); - &perr($r->content . "\n"); - sleep ($sleeptime + 2); - } - } - &perr("Failed to download within $retries retries.\n"); - if ($er) { - &perr("Server out of data? Temporary server error? Normal exit anyway.\n"); - return ""; - }; - die; -} - -sub wbf { - my($f, $s) = @_; - open(FO, ">$f") or die "Failed to open '$f': $!"; - binmode(FO); - print FO $s; - close(FO); -} - -sub unf { - my $f = shift; - unlink($f) or &perr("Failed to delete '$f': $!"); -} - -sub copyLogo { - my $key = shift; - my $cid = $key; - if (!defined($logos{$cid}{logo})) { - $cid = substr($key, rindex($key, ".")+1); - } - if (defined($iconDir) && defined($logos{$cid}{logo})) { - my $num = $stations{$key}{number}; - my $src = "$iconDir/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; - my $dest1 = "$iconDir/$num" . $logos{$cid}{logoExt}; - my $dest2 = "$iconDir/$num " . $stations{$key}{name} . $logos{$cid}{logoExt}; - my $dest3 = "$iconDir/$num\_" . $stations{$key}{name} . $logos{$cid}{logoExt}; - copy($src, $dest1); - copy($src, $dest2); - copy($src, $dest3); - } -} - -sub handleLogo { - my $url = shift; - if (! -d $iconDir) { - mkdir($iconDir) or die "Can't mkdir: $!\n"; - } - my $n; my $s; ($n,$_,$s) = fileparse($url, qr"\..*"); - $stations{$cs}{logoURL} = $url; - $logos{$cs}{logo} = $n; - $logos{$cs}{logoExt} = $s; - my $f = $iconDir . "/" . $n . $s; - if (! -e $f) { &wbf($f, &getURL($url,0)); } -} - -sub setOriginalAirDate { - if (substr($cp,10,4) ne '0000') { - if (!defined($programs{$cp}{originalAirDate}) - || ($schedule{$cs}{$sch}{time} < $programs{$cp}{originalAirDate})) { - $programs{$cp}{originalAirDate} = $schedule{$cs}{$sch}{time}; - } - } -} - -sub getZToken { - &login() if (!defined($zapToken)); - return $zapToken; -} - -sub parseZFavs { - my $buffer = shift; - my $t = decode_json($buffer); - if (defined($t->{'channels'})) { - my $m = $t->{'channels'}; - foreach my $f (@{$m}) { - if ($options{R}) { - my $r = &ua_post($urlRoot . "api/user/ChannelAddtofav", { token => $zapToken, prgsvcid => $f, addToFav => "false" }, 'X-Requested-With' => 'XMLHttpRequest'); - if ($r->is_success) { - &pout("Removed favorite $f\n"); - } else { - &perr("RF" . $r->status_line . "\n"); - } - } else { - $zapFavorites{$f} = 1; - } - } - if ($options{R}) { - &pout("Removed favorites, exiting\n"); - exit; - }; - &pout("Lineup favorites: " . (keys %zapFavorites) . "\n"); - } -} - -sub parseTVGFavs { - my $buffer = shift; - my $t = decode_json($buffer); - if (defined($t->{'message'})) { - my $m = $t->{'message'}; - foreach my $f (@{$m}) { - my $source = $f->{"source"}; - my $channel = $f->{"channel"}; - $tvgfavs{"$channel.$source"} = 1; - } - &pout("Lineup $zlineupId favorites: " . (keys %tvgfavs) . "\n"); - } -} - -sub parseTVGIcons { - require GD; - $rc = Encode::encode('utf8', &getURL($tvgspritesurl . "$zlineupId\.css",0) ); - if ($rc =~ /background-image:.+?url\((.+?)\)/) { - my $url = $tvgspritesurl . $1; - - if (! -d $iconDir) { - mkdir($iconDir) or die "Can't mkdir: $!\n"; - } - - ($n,$_,$s) = fileparse($url, qr"\..*"); - $f = $iconDir . "/sprites-" . $n . $s; - &wbf($f, &getURL($url,0)); - - GD::Image->trueColor(1); - $im = new GD::Image->new($f); - - my $iconw = 30; - my $iconh = 20; - while ($rc =~ /listings-channel-icon-(.+?)\{.+?position:.*?\-(\d+).+?(\d+).*?\}/isg) { - my $cid = $1; - my $iconx = $2; - my $icony = $3; - - my $icon = new GD::Image($iconw,$iconh); - $icon->alphaBlending(0); - $icon->saveAlpha(1); - $icon->copy($im, 0, 0, $iconx, $icony, $iconw, $iconh); - - $logos{$cid}{logo} = "sprite-" . $cid; - $logos{$cid}{logoExt} = $s; - - my $ifn = $iconDir . "/" . $logos{$cid}{logo} . $logos{$cid}{logoExt}; - &wbf($ifn, $icon->png); - } - } -} - -sub parseTVGD { - my $gz = gzopen(shift, "rb"); - my $buffer; - $buffer .= $b while $gz->gzread($b, 65535) > 0; - $gz->gzclose(); - my $t = decode_json($buffer); - - if (defined($t->{'program'})) { - my $prog = $t->{'program'}; - if (defined($prog->{'release_year'})) { - $programs{$cp}{movie_year} = $prog->{'release_year'}; - } - if (defined($prog->{'rating'}) && !defined($programs{$cp}{rating})) { - $programs{$cp}{rating} = $prog->{'rating'} if $prog->{'rating'} ne 'NR'; - } - } - - if (defined($t->{'tvobject'})) { - my $tvo = $t->{'tvobject'}; - if (defined($tvo->{'photos'})) { - my $photos = $tvo->{'photos'}; - my %phash; - foreach $ph (@{$photos}) { - my $w = $ph->{'width'} * $ph->{'height'}; - my $u = $ph->{'url'}; - $phash{$w} = $u; - } - my $big = (sort {$b <=> $a} keys %phash)[0]; - $programs{$cp}{imageUrl} = $phash{$big}; - } - } -} - -sub parseTVGGrid { - my $gz = gzopen(shift, "rb"); - my $buffer; - $buffer .= $b while $gz->gzread($b, 65535) > 0; - $gz->gzclose(); - my $t = decode_json($buffer); - - foreach my $e (@{$t}) { - my $cjs = $e->{'Channel'}; - my $src = $cjs->{'SourceId'}; - my $num = $cjs->{'Number'}; - - $cs = "$num.$src"; - - if (%tvgfavs) { - next if (!$tvgfavs{$cs}); - } - - if (!defined($stations{$cs}{stnNum})) { - $stations{$cs}{stnNum} = $src; - $stations{$cs}{number} = $num; - $stations{$cs}{name} = $cjs->{'Name'}; - if (defined($cjs->{'FullName'}) && $cjs->{'FullName'} ne $cjs->{'Name'}) { - if ($cjs->{'FullName'} ne '') { - $stations{$cs}{fullname} = $cjs->{'FullName'}; - } - } - - if (!defined($stations{$cs}{order})) { - if (defined($options{b})) { - $stations{$cs}{order} = $coNum++; - } else { - $stations{$cs}{order} = $stations{$cs}{number}; - } - } - } - - my $cps = $e->{'ProgramSchedules'}; - foreach my $pe (@{$cps}) { - next if (!defined($pe->{'ProgramId'})); - $cp = $pe->{'ProgramId'}; - my $catid = $pe->{'CatId'}; - - if ($catid == 1) { $programs{$cp}{genres}{movie} = 1 } - elsif ($catid == 2) { $programs{$cp}{genres}{sports} = 1 } - elsif ($catid == 3) { $programs{$cp}{genres}{family} = 1 } - elsif ($catid == 4) { $programs{$cp}{genres}{news} = 1 } - # 5 - 10? - # my $subcatid = $pe->{'SubCatId'}; - - my $ppid = $pe->{'ParentProgramId'}; - if ((defined($ppid) && $ppid != 0) - || (defined($options{j}) && $catid != 1)) { - $programs{$cp}{genres}{series} = 99; - } - - $programs{$cp}{title} = $pe->{'Title'}; - $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; - - if (defined($pe->{'EpisodeTitle'}) && $pe->{'EpisodeTitle'} ne '') { - $programs{$cp}{episode} = $pe->{'EpisodeTitle'}; - $tba = 1 if $programs{$cp}{episode} =~ /$sTBA/i; - } - - $programs{$cp}{description} = $pe->{'CopyText'} if defined($pe->{'CopyText'}) && $pe->{'CopyText'} ne ''; - $programs{$cp}{rating} = $pe->{'Rating'} if defined($pe->{'Rating'}) && $pe->{'Rating'} ne ''; - - my $sch = $pe->{'StartTime'} * 1000; - $schedule{$cs}{$sch}{time} = $sch; - $schedule{$cs}{$sch}{endtime} = $pe->{'EndTime'} * 1000; - $schedule{$cs}{$sch}{program} = $cp; - $schedule{$cs}{$sch}{station} = $cs; - - my $airat = $pe->{'AiringAttrib'}; - if ($airat & 1) { $schedule{$cs}{$sch}{live} = 1 } - elsif ($airat & 4) { $schedule{$cs}{$sch}{new} = 1 } - # other bits? - - my $tvo = $pe->{'TVObject'}; - if (defined($tvo)) { - if (defined($tvo->{'SeasonNumber'}) && $tvo->{'SeasonNumber'} != 0) { - $programs{$cp}{seasonNum} = $tvo->{'SeasonNumber'}; - if (defined($tvo->{'EpisodeNumber'}) && $tvo->{'EpisodeNumber'} != 0) { - $programs{$cp}{episodeNum} = $tvo->{'EpisodeNumber'}; - } - } - if (defined($tvo->{'EpisodeAirDate'})) { - my $eaid = $tvo->{'EpisodeAirDate'}; # GMT @ 00:00:00 - $eaid =~ tr/\-0-9//cd; - $programs{$cp}{originalAirDate} = $eaid if ($eaid ne ''); - } - my $url; - if (defined($tvo->{'EpisodeSEOUrl'}) && $tvo->{'EpisodeSEOUrl'} ne '') { - $url = $tvo->{'EpisodeSEOUrl'}; - } elsif(defined($tvo->{'SEOUrl'}) && $tvo->{'SEOUrl'} ne '') { - $url = $tvo->{'SEOUrl'}; - $url = "/movies$url" if ($catid == 1 && $url !~ /movies/); - } - $programs{$cp}{url} = substr($tvgurl, 0, -1) . $url if defined($url); - } - - if (defined($options{I}) - || (defined($options{D}) && $programs{$cp}{genres}{movie}) - || (defined($options{W}) && $programs{$cp}{genres}{movie}) ) { - &getDetails(\&parseTVGD, $cp, $tvgMapiRoot . "listings/details?program=$cp", ""); - } - } - } -} - -sub getDetails { - my ($func, $cp, $url, $prefix) = @_; - my $fn = "$cacheDir/$prefix$cp\.js\.gz"; - if (! -e $fn) { - my $rs = &getURL($url,1); - if (length($rs)) { - $rc = Encode::encode('utf8', $rs); - &wbf($fn, Compress::Zlib::memGzip($rc)); - } - } - if (-e $fn) { - my $l = length($prefix) ? $prefix : "D"; - &pout("[$l] Parsing: $cp\n"); - $func->($fn); - } else { - &pout("Skipping: $cp\n"); - } -} - -sub parseJSON { - my $gz = gzopen(shift, "rb"); - my $buffer; - $buffer .= $b while $gz->gzread($b, 65535) > 0; - $gz->gzclose(); - my $t = decode_json($buffer); - - my $sts = $t->{'channels'}; - my %zapStarred=(); - foreach $s (@$sts) { - - if (defined($s->{'channelId'})) { - if (!$allChan && scalar(keys %zapFavorites)) { - if ($zapFavorites{$s->{channelId}}) { - if ($options{8}) { - next if $zapStarred{$s->{channelId}}; - $zapStarred{$s->{channelId}} = 1; - } - } else { - next; - } - } - # id (uniq) vs channelId, but id not nec consistent in cache - $cs = $s->{channelNo} . "." . $s->{channelId}; - $stations{$cs}{stnNum} = $s->{channelId}; - $stations{$cs}{name} = $s->{'callSign'}; - $stations{$cs}{number} = $s->{'channelNo'}; - $stations{$cs}{number} =~ s/^0+//g; - - if (!defined($stations{$cs}{order})) { - if (defined($options{b})) { - $stations{$cs}{order} = $coNum++; - } else { - $stations{$cs}{order} = $stations{$cs}{number}; - } - } - - if ($s->{'thumbnail'} ne '') { - my $url = $s->{'thumbnail'}; - $url =~ s/\?.*//; # remove size - if ($url !~ /^http/) { - $url = "https:" . $url; - } - $stations{$cs}{logoURL} = $url; - &handleLogo($url) if defined($iconDir); - } - - my $events = $s->{'events'}; - foreach $e (@$events) { - my $program = $e->{'program'}; - $cp = $program->{'id'}; - $programs{$cp}{title} = $program->{'title'}; - $tba = 1 if $programs{$cp}{title} =~ /$sTBA/i; - $programs{$cp}{episode} = $program->{'episodeTitle'} if ($program->{'episodeTitle'} ne ''); - $programs{$cp}{description} = $program->{'shortDesc'} if ($program->{'shortDesc'} ne ''); - $programs{$cp}{duration} = $e->{duration} if ($e->{duration} > 0); - $programs{$cp}{movie_year} = $program->{releaseYear} if ($program->{releaseYear} ne ''); - $programs{$cp}{seasonNum} = $program->{season} if ($program->{'season'} ne ''); - if ($program->{'episode'} ne '') { - $programs{$cp}{episodeNum} = $program->{episode}; - } - if ($e->{'thumbnail'} ne '') { - my $turl = $urlAssets; - $turl .= $e->{'thumbnail'} . ".jpg"; - $programs{$cp}{imageUrl} = $turl; - } - if ($program->{'seriesId'} ne '' && $program->{'tmsId'} ne '') { - $programs{$cp}{url} = $urlRoot . "overview-affiliates.html?programSeriesId=" - . $program->{seriesId} . "&tmsId=" . $program->{tmsId}; - } - - $sch = str2time1($e->{'startTime'}) * 1000; - $schedule{$cs}{$sch}{time} = $sch; - $schedule{$cs}{$sch}{endTime} = str2time1($e->{'endTime'}) * 1000; - $schedule{$cs}{$sch}{program} = $cp; - $schedule{$cs}{$sch}{station} = $cs; - - if ($e->{'filter'}) { - my $genres = $e->{'filter'}; - my $i = 1; - foreach $g (@{$genres}) { - $g =~ s/filter-//i; - ${$programs{$cp}{genres}}{lc($g)} = $i++; - } - } - - $programs{$cp}{rating} = $e->{rating} if ($e->{rating} ne ''); - - if ($e->{'tags'}) { - my $tags = $e->{'tags'}; - if (grep $_ eq 'CC', @{$tags}) { - $schedule{$cs}{$sch}{cc} = 1 - } - } - - if ($e->{'flag'}) { - my $flags = $e->{'flag'}; - if (grep $_ eq 'New', @{$flags}) { - $schedule{$cs}{$sch}{new} = 'New' - &setOriginalAirDate(); - } - if (grep $_ eq 'Live', @{$flags}) { - $schedule{$cs}{$sch}{live} = 'Live' - &setOriginalAirDate(); # live to tape? - } - if (grep $_ eq 'Premiere', @{$flags}) { - $schedule{$cs}{$sch}{premiere} = 'Premiere'; - } - if (grep $_ eq 'Finale', @{$flags}) { - $schedule{$cs}{$sch}{finale} = 'Finale'; - } - } - - if ($options{D} && !$program->{isGeneric}) { - &postJSONO($cp, $program->{seriesId}); - } - if (defined($options{j}) && $cp !~ /^MV/) { - $programs{$cp}{genres}{series} = 99; - } - } - } - } - return 0; -} - -sub postJSONO { - my ($cp, $sid) = @_; - my $fn = "$cacheDir/O$cp\.js\.gz"; - - if (! -e $fn && defined($sidCache{$sid}) && -e $sidCache{$sid}) { - copy($sidCache{$sid}, $fn); - } - if (! -e $fn) { - my $url = $urlRoot . 'api/program/overviewDetails'; - &pout("[$treq] Post $sid: $url\n"); - sleep $sleeptime; # do these rapid requests flood servers? - my %phash = &getZapPParams(); - $phash{programSeriesID} = $sid; - $phash{'clickstream[FromPage]'} = 'TV%20Grid'; - my $r = &ua_post($url, \%phash, 'X-Requested-With' => 'XMLHttpRequest'); - if ($r->is_success) { - $dc = Encode::encode('utf8', $r->decoded_content( raise_error => 1 )); - &wbf($fn, Compress::Zlib::memGzip($dc)); - $sidCache{$sid} = $fn; - } else { - &perr($id . " :" . $r->status_line); - } - } - if (-e $fn) { - &pout("[D] Parsing: $cp\n"); - my $gz = gzopen($fn, "rb"); - my $buffer; - $buffer .= $b while $gz->gzread($b, 65535) > 0; - $gz->gzclose(); - my $t = decode_json($buffer); - - if ($t->{seriesGenres} ne '') { - my $i = 2; - my %gh = %{$programs{$cp}{genres}}; - if (keys %gh) { - my @genArr = sort { $gh{$a} <=> $gh{$b} } keys %gh; - my $max = $genArr[-1]; - $i = $gh{$max} + 1; - } - foreach my $sg (split(/\|/, lc($t->{seriesGenres}))) { - if (!${$programs{$cp}{genres}}{$sg}) { - ${$programs{$cp}{genres}}{$sg} = $i++; - } - } - } - - my $i = 1; - foreach my $c (@{$t->{'overviewTab'}->{cast}}) { - my $n = $c->{name}; - my $cn = $c->{characterName}; - my $cr = lc($c->{role}); - - if ($cr eq 'host') { - ${$programs{$cp}{presenter}}{$n} = $i++; - } else { - ${$programs{$cp}{actor}}{$n} = $i++; - ${$programs{$cp}{role}}{$n} = $cn if length($cn); - } - } - $i = 1; - foreach my $c (@{$t->{'overviewTab'}->{crew}}) { - my $n = $c->{name}; - my $cr = lc($c->{role}); - if ($cr =~ /producer/) { - ${$programs{$cp}{producer}}{$n} = $i++; - } elsif ($cr =~ /director/) { - ${$programs{$cp}{director}}{$n} = $i++; - } elsif ($cr =~ /writer/) { - ${$programs{$cp}{writer}}{$n} = $i++; - } - } - if (!defined($programs{$cp}{imageUrl}) && $t->{seriesImage} ne '') { - my $turl = $urlAssets; - $turl .= $t->{seriesImage} . ".jpg"; - $programs{$cp}{imageUrl} = $turl; - } - if ($cp =~ /^MV|^SH/ && length($t->{seriesDescription}) > length($programs{$cp}{description})) { - $programs{$cp}{description} = $t->{seriesDescription}; - } - if ($cp =~ /^EP/) { # GMT @ 00:00:00 - my $ue = $t->{overviewTab}->{upcomingEpisode}; - if (defined($ue) && lc($ue->{tmsID}) eq lc($cp) - && $ue->{originalAirDate} ne '' - && $ue->{originalAirDate} ne '1000-01-01T00:00Z') { - $oad = str2time2($ue->{originalAirDate}) ; - $oad *= 1000; - $programs{$cp}{originalAirDate} = $oad; - } else { - foreach my $ue (@{$t->{upcomingEpisodeTab}}) { - if (lc($ue->{tmsID}) eq lc($cp) - && $ue->{originalAirDate} ne '' - && $ue->{originalAirDate} ne '1000-01-01T00:00Z' - ) { - $oad = str2time2($ue->{originalAirDate}) ; - $oad *= 1000; - $programs{$cp}{originalAirDate} = $oad; - last; - } - } - } - } - } else { - &pout("Skipping: $sid\n"); - } -} - -sub str2time1 { - my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%M:%SZ'); - return $t->epoch(); -} - -sub str2time2 { - my $t = Time::Piece->strptime(shift, '%Y-%m-%dT%H:%MZ'); - return $t->epoch(); -} - -sub hourToMillis { - ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); - if ($start == 0) { - $hour = int($hour/$gridHours) * $gridHours; - } else { - $hour = 0; - } - $t = timegm(0,0,$hour,$mday,$mon,$year); - $t = $t - (&tz_offset * 3600) if !defined($options{g}); - ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t); - $t = timegm($sec, $min, $hour,$mday,$mon,$year); - return $t . "000"; -} - -sub tz_offset { - my $n = defined $_[0] ? $_[0] : time; - my ($lm, $lh, $ly, $lyd) = (localtime $n)[1, 2, 5, 7]; - my ($gm, $gh, $gy, $gyd) = (gmtime $n)[1, 2, 5, 7]; - ($lm - $gm)/60 + $lh - $gh + 24 * ($ly - $gy || $lyd - $gyd) -} - -sub timezone { - my $tztime = defined $_[0] ? &_rtrim3(shift) : time; - my $os = sprintf "%.1f", (timegm(localtime($tztime)) - $tztime) / 3600; - my $mins = sprintf "%02d", abs( $os - int($os) ) * 60; - return sprintf("%+03d", int($os)) . $mins; -} - -sub max ($$) { $_[$_[0] < $_[1]] } -sub min ($$) { $_[$_[0] > $_[1]] } - -sub HELP_MESSAGE { -print < ($VERSION) - -u - -p - -d <# of days> (default = $days) - -n <# of no-cache days> (from end) (default = $ncdays) - -N <# of no-cache days> (from start) (default = $ncsdays) - -B - -s (default = $start) - -o (default = "$outFile") - -c (default = "$cacheDir") - -l (default = "$lang") - -i (default = don't download channel icons) - -m <#> = offset program times by # minutes (better to use TZ env var) - -b = retain website channel order - -x = output XTVD xml file format (default = XMLTV) - -w = wait on exit (require keypress before exiting) - -q = quiet (no status output) - -r <# of connection retries before failure> (default = $retries, max 20) - -e = hex encode entities (html special characters like accents) - -E "amp apos quot lt gt" = selectively encode standard XML entities - -F = output channel names first (rather than "number name") - -O = use old tv_grab_na style channel ids (C###nnnn.gracenote.com) - -A "new live" = append " *" to program titles that are "new" and/or "live" - -M = copy movie_year to empty movie sub-title tags - -U = UTF-8 encoding (default = "ISO-8859-1") - -L = output "" tag (not part of xmltv.dtd) - -T = don't cache files containing programs with "$sTBA" titles - -P = to use an http proxy - -C (default = "$confFile") - -S <#seconds> = sleep between requests to prevent flooding of server - -D = include details = 1 extra http request per program! - -I = include icons (image URLs) - 1 extra http request per program! - -J = include xmltv file in output - -Y (if not using username/password) - -Z (if not using username/password) - -z = use tvguide.com instead of gracenote.com - -a = output all channels (not just favorites) - -j = add "series" category to all non-movie programs -END -sleep(5) if ($^O eq 'MSWin32'); -exit 0; -}