diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcadb2c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/.github/workflows/auto-update-country.yml b/.github/workflows/auto-update-country.yml index 1f217e9..36afd0d 100644 --- a/.github/workflows/auto-update-country.yml +++ b/.github/workflows/auto-update-country.yml @@ -1,31 +1,41 @@ -name: Auto database update browser-country +name: Auto Update Country Packages + on: schedule: - cron: '42 19 * * *' workflow_dispatch: + jobs: release: - name: Auto database update browser-country + if: github.repository == 'sapics/ip-location-api' runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v4 - - name: setup Node - uses: actions/setup-node@v4 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version-file: package.json + cache: pnpm registry-url: 'https://registry.npmjs.org' - - name: install dependencies - run: npm i - - name: update - run: npm run updatedb-browser-country - - name: test - run: npm run test - - name: commit - run: script/auto-update-country.sh + + - name: Install dependencies + run: pnpm i + + - name: Build + run: pnpm -r --workspace-concurrency=1 --filter "@iplookup/country*..." build + + - name: Bump version + shell: bash + run: | + //* Increments patch version and appends current date (YYYYMMDD) for all country packages + for pkg in packages/country*; do + [ -f "$pkg/package.json" ] \ + && cd $pkg \ + && npm pkg set version="$(pnpm exec semver $(node -p "require('./package.json').version") -i patch)-$(date +'%Y%m%d')" \ + && cd ../.. + done + + - name: Publish + run: pnpm -r --filter "@iplookup/country*" publish --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{secrets.GTOKEN}} diff --git a/.github/workflows/auto-update-geocode.yml b/.github/workflows/auto-update-geocode.yml index c1fa751..97b232a 100644 --- a/.github/workflows/auto-update-geocode.yml +++ b/.github/workflows/auto-update-geocode.yml @@ -1,31 +1,41 @@ -name: Auto database update browser-geocode +name: Auto Update Geocode Packages + on: schedule: - cron: '2 0 1,2,3 * *' workflow_dispatch: + jobs: release: - name: Auto database update browser-geocode + if: github.repository == 'sapics/ip-location-api' runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v4 - - name: setup Node - uses: actions/setup-node@v4 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version-file: package.json + cache: pnpm registry-url: 'https://registry.npmjs.org' - - name: install dependencies - run: npm i - - name: update - run: npm run updatedb-browser-geocode - - name: test - run: npm run test - - name: commit - run: script/auto-update-geocode.sh + + - name: Install dependencies + run: pnpm i + + - name: Build + run: pnpm -r --workspace-concurrency=1 --filter "@iplookup/geocode*..." build + + - name: Bump version + shell: bash + run: | + //* Increments patch version and appends current date (YYYYMMDD) for all geocode packages + for pkg in packages/geocode*; do + [ -f "$pkg/package.json" ] \ + && cd $pkg \ + && npm pkg set version="$(pnpm exec semver $(node -p "require('./package.json').version") -i patch)-$(date +'%Y%m%d')" \ + && cd ../.. + done + + - name: Publish + run: pnpm -r --filter "@iplookup/geocode*" publish --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{secrets.GTOKEN}} diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml deleted file mode 100644 index a17d8ae..0000000 --- a/.github/workflows/auto-update.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Auto database update -on: - workflow_dispatch: -jobs: - release: - name: latest version - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v4 - - name: setup Node - uses: actions/setup-node@v4 - with: - node-version: 20.x - registry-url: 'https://registry.npmjs.org' - - name: install dependencies - run: npm i - - name: update - run: npm run updatedb - - name: test - run: npm run test - - name: commit - run: script/auto-update.sh - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: push changes - uses: ad-m/github-push-action@master - with: - github_token: ${{secrets.GTOKEN}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9ab09ef --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: Build + +on: + workflow_call: + pull_request: + types: + - opened + - synchronize + - closed + +jobs: + build: + name: Build with Node.js ${{ matrix.node-version }} + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18, 20, 22] + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + + - name: Install dependencies + run: pnpm i + + - name: Lint + run: pnpm lint + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..99b22d1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Build + +on: + push: + branches: + - main + +jobs: + build: + uses: ./.github/workflows/build.yml + + release: + if: github.repository == 'sapics/ip-location-api' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: pnpm + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm i + + - name: Build + run: pnpm build + + - name: Publish + run: pnpm -r publish --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index cac1308..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Test - -on: - workflow_dispatch: - -jobs: - test-esm: - name: Test ESM & CJS - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14, 16, 18, 20, 22] - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - name: npm install - run: npm install - - name: run test ESM - run: | - npm run updatedb - npx jasmine spec/*.mjs - - name: run test CJS - run: | - npm run updatedb-cjs - npx jasmine spec/*.cjs diff --git a/.gitignore b/.gitignore index 5ec5b4f..ae62023 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,13 @@ -node_modules -/tmp -/data -/browser/country/4 -/browser/country/6 -/browser/country/*.idx -/browser/country-extra/4 -/browser/country-extra/6 -/browser/country-extra/*.idx -/browser/geocode/4 -/browser/geocode/6 -/browser/geocode/*.idx -/browser/geocode-extra/4 -/browser/geocode-extra/6 -/browser/geocode-extra/*.idx +dist +indexes +.DS_Store +coverage +*.lcov +node_modules/ +*.tsbuildinfo +.npm +.eslintcache +*.tgz +.env +tmp +data \ No newline at end of file diff --git a/.release-it.json b/.release-it.json deleted file mode 100644 index c32c43f..0000000 --- a/.release-it.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://unpkg.com/release-it/schema/release-it.json", - "github": { - "release": true - } -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c484f42 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,49 @@ +{ + // Disable the default formatter, use eslint instead + "prettier.enable": false, + "editor.formatOnSave": false, + + // Auto fix + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + + // Silent the stylistic rules in you IDE, but still auto fix them + "eslint.rules.customizations": [ + { "rule": "style/*", "severity": "off", "fixable": true }, + { "rule": "format/*", "severity": "off", "fixable": true }, + { "rule": "*-indent", "severity": "off", "fixable": true }, + { "rule": "*-spacing", "severity": "off", "fixable": true }, + { "rule": "*-spaces", "severity": "off", "fixable": true }, + { "rule": "*-order", "severity": "off", "fixable": true }, + { "rule": "*-dangle", "severity": "off", "fixable": true }, + { "rule": "*-newline", "severity": "off", "fixable": true }, + { "rule": "*quotes", "severity": "off", "fixable": true }, + { "rule": "*semi", "severity": "off", "fixable": true } + ], + + // Enable eslint for all supported languages + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "yaml", + "toml", + "xml", + "gql", + "graphql", + "astro", + "css", + "less", + "scss", + "pcss", + "postcss" + ] +} diff --git a/README.md b/README.md index 431c966..47e58a9 100644 --- a/README.md +++ b/README.md @@ -1,277 +1,29 @@ -# ip-location-api [![npm version](https://img.shields.io/npm/v/ip-location-api?color=success&style=flat-square&label=npm)](https://www.npmjs.com/package/ip-location-api) +## 💻 Development -Fast and customizable nodejs api to get geolocation information from ip address. -`ip-location-api` make a fast lookup by using in-memory database. +- Clone this repository +- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10) +- Install dependencies using `pnpm install` +- Build packages using `pnpm build` +- Build packages in watch mode using `pnpm build:watch` (will watch for changes and rebuild) +- Run interactive tests using `pnpm test:ui` -This api is created for server-side javascript like Node.js. -If you need client-side javascript which works in **BROWSER**, please try to use [@iplookup/country](https://github.com/sapics/ip-location-api/tree/main/browser/country) or [@iplookup/geocode](https://github.com/sapics/ip-location-api/tree/main/browser/geocode). +## 📦 Packages +- `ip-location-api`: Core package with full functionality +- `@iplookup/country`: Lightweight package for country lookups (Web-compatible) +- `@iplookup/country-extra`: Enhanced country data with additional information (Web-compatible) +- `@iplookup/geocode`: Basic geocoding functionality (Web-compatible) +- `@iplookup/geocode-extra`: Enhanced geocoding with additional location data (Web-compatible) +- `@iplookup/util`: Utility functions for updating the database (Private) -## Synopsis +## 📝 Commit Convention -```javascript -import { lookup } from 'ip-location-api' -// or CJS format -// const { lookup } = require('ip-location-api') +This repository follows [Conventional Commits](https://www.conventionalcommits.org/). All commit messages must be structured as follows: -var ip = "207.97.227.239" -var location = lookup(ip) -// If you use Asynchronouns version which is configured with smallMemory=true, -// var location = await lookup(ip) - -console.log(location) -{ - country: 'FR', - region1: 'NOR', - region1_name: 'Normandy', - region2: '27', - region2_name: 'Eure', - city: 'Heudicourt', - // metro: Defined only in US (Aug.2024) - timezone: 'Europe/Paris', - eu: 1, - latitude: 49.3335, - longitude: 1.6566, - area: 5, - postcode: 27860, - country_name: 'France', - country_native: 'France', - phone: [ 33 ], - continent: 'EU', - capital: 'Paris', - currency: [ 'EUR' ], - languages: [ 'fr' ], - continent_name: 'Europe' -} -``` - -## Benchmark - -I make a benchmark for making comparison with intel 12700 (2.1GHz), SSD, nodejs v20. -You can change the memory usage or lookup time, by customizing location information. - -| benchmark | type | in-memory db | startup | lookup ipv4 | lookup ipv6 | -| ---- | ---- | ---- | ---- | ---- | ---- | -| ip-location-api
(default) | country | 6.9 MB | 3 ms | 0.362 μs/ip | 0.708 μs/ip | -| ip-location-api
(async) | country | 2.9 MB | 2 ms | 243 μs/ip | 255 μs/ip | -| ip-location-api | city | 62.9 MB | 14 ms | 0.751 μs/ip | 1.064 μs/ip | -| ip-location-api
(async) | city | 15.6 MB | 5 ms | 267 μs/ip | 271 μs/ip | -| [geoip-lite](https://github.com/geoip-lite/node-geoip) | city | 136 MB | 54 ms | 1.616 μs/ip | 3.890 μs/ip | -| [fast-geoip](https://github.com/Doc999tor/fast-geoip)
(async) | city | 0MB | 4 ms | 1714 μs/ip | cannot lookup | - - -## Installation - -```bash -$ npm i ip-location-api -``` - - -## API - -ip-location-api has two modes which are synchronous and asynchronous. -Synchronouns one load all data in-memory at startup time, thus it makes fast lookup. -Asynchronouns one load smaller data in-memory at startup time, and the other data is loaded from the hard drive for each lookup. - -| type | memory usage | startup | lookup | -| ---- | ---- | ---- | ---- | -| Synchronouns | Large | Slow | Fast | -| Asynchronouns | Small | Fast | Slow | - -If you have a enough memory, I recommend to use synchronouns one because lookup is over 300 times faster than asynchronouns one. - - -## Field description - -Note that as far as possible, the same field names as in `geoip-lite` are used, but some different field names are used. - -| `ip-location-api` | `geoip-lite` | database |description | -| ---- | ---- | ---- | ---- | -| country | country | MaxMind | "2 letter" country code defined at [ISO-3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1) | -| region1 | region | MaxMind | region code which is short code for region1_name [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) | -| region1_name | ❌️ | MaxMind | first sub division name (multi language) | -| region2 | ❌️ | MaxMind | region code which is short code for region2_name [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) | -| region2_name | ❌️ | MaxMind | second sub division name (multi language) | -| city | city |MaxMind | city name (multi language) | -| metro | metro |MaxMind | Geolocation target code from Google | -| eu | eu | MaxMind | true: the member state of the European Union, undefined: for the other countries. This needs "country" field. | -| timezone | timezone | MaxMind | time zone associated with location | -| latitude | ll[0] | MaxMind | approximate [WGS84](https://en.wikipedia.org/wiki/World_Geodetic_System) latitude | -| longitude | ll[1] | MaxMind | approximate [WGS84](https://en.wikipedia.org/wiki/World_Geodetic_System) longitude | -| area | area | MaxMind | The radius in kilometers around the specified location where the IP address is likely to be. [maxmind blog](https://blog.maxmind.com/2022/06/using-maxminds-accuracy-radius/) | -| postcode | ❌️ | MaxMind | region-specific postal code near the IP address | -| ❌️ | range | MaxMind | We removes range information for optimization | -| country_name | ❌️ | Countries| country name| -| country_native | ❌️ | Countries| country name in native language| -| continent | ❌️ | Countries| continent short code| -| continent_name | ❌️ | Countries| continent name| -| capital | ❌️ | Countries | capital name | -| phone | ❌️ | Countries| international country calling codes | -| currency | ❌️ | Countries | list of commonly used currencies | -| languages | ❌️ | Countries | list of commonly used languages | - - -## Setup the configuration - -You can configure the api by 3 way. -- CLI parameters: `ILA_FIELDS=latitude,longitude` -- Environment variables: `ILA_FIELDS=latitude,longitude` -- Javascript: `await reload({fields: 'latitude,longitude'})` . - -The name of CLI prameter and environment variables are same. - - -Conf key in `reload(conf)` is named with "LOWER CAMEL", CLI or ENV parameter is named with "SNAKE" with adding "ILA_" (come from Ip-Location-Api). - -| `reload(conf)` | CLI or ENV | default | description | -| ---- | ---- | ---- | ---- | -| fields | ILA_FIELDS | country | You can change the fields to be retrived from [MaxMind](https://www.maxmind.com/). When you set "all", all fields are displayed. | -| addCountryInfo | ILA_ADD_COUNTRY_INFO | false | "true" make to add the country information from [Countries](https://github.com/annexare/Countries). This needs "country" field. | -| dataDir | ILA_DATA_DIR | ../data | Directory for database file | -| tmpDataDir | ILA_TMP_DATA_DIR | ../tmp | Directory for temporary file | -| apiDir | ILA_API_DIR | .. | Directory for ip-location-api | -| smallMemory | ILA_SMALL_MEMORY | false | false: synchronouns, ture: asynchronouns | -| smallMemoryFileSize | ILA_SMALL_MEMORY_FILE_SIZE | 4096 | Max file size for asynchronouns data (no change is recommended) | -| licenseKey | ILA_LICENSE_KEY | redist | By setting [MaxMind](https://www.maxmind.com/) License key, you can download latest version of database from [MaxMind](https://www.maxmind.com/) server. By setting to "redist", you can download the database from [node-geolite2-redist](https://github.com/sapics/node-geolite2-redist) repository which re-distribute the GeoLite2 database. | -| ipLocationDb | ILA_IP_LOCATION_DB | | When you need only "country" field, you can use [ip-location-db](https://github.com/sapics/ip-location-db) data | -| downloadType | ILA_DOWNLOAD_TYPE | reuse | By setting to "false", "tmpDataDir" directory is deleted every update. "reuse" dose not delete "tmpDataDir" and re-use "tmpDataDir"'s database if the database file dose not update. | -| autoUpdate | ILA_AUTO_UPDATE | default | By setting to "false", it dose not update automatically. "default" updates twice weekly. You can set CRON PATTERN FORMAT which is provided by [cron](https://github.com/kelektiv/node-cron) with UTC timezone (For example, ILA_AUTO_UPDATE="0 1 * * *" for daily update). | -| multiDbDir | ILA_MULTI_DB_DIR | false | If you use multiple "dataDir", please make this value to "true" | -| series | ILA_SERIES | GeoLite2 | By setting to "GeoIP2", you can use premium database "GeoIP2" | -| language | ILA_LANGUAGE | en | You can choose "de", "en", "es", "fr", "ja", "pt-BR", "ru", "zh-CN". By changing, the language of "region1_name", "region2_name", "city" fields are changed | -| silent | ILA_SILENT | false | true: deactivate unnecessary console.log | - - -## Update database - -```bash -npm run updatedb ``` +[optional scope]: -or +[optional body] -```javascript -import { updateDb } from 'ip-location-api' -await updateDb(setting) +[optional footer] ``` - - -There are three database update way. -- ILA_LICENSE_KEY=redist -- ILA_LICENSE_KEY=YOUR_GEOLITE2_LICENSE_KEY -- ILA_IP_LOCATION_DB=YOUR_CHOOSEN_DATABSE - -When you set "ILA_LICENSE_KEY=redist" which is the dafault setting, it downloads GeoLite2 database from the redistribution repository [node-geolite2-redist](https://github.com/sapics/node-geolite2-redist). - -When you set "ILA_LICENSE_KEY=YOUR_GEOLITE2_LICENSE_KEY", it downloads GeoLite2 dastabase from the MaxMind provided server. -`YOUR_GEOLITE2_LICENSE_KEY` should be replaced by a valid GeoLite2 license key. Please [follow instructions](https://dev.maxmind.com/geoip/geoip2/geolite2/) provided by MaxMind to obtain a license key. - -When you set "ILA_IP_LOCATION_DB=YOUR_CHOOSEN_DATABSE", it downloads from the [ip-location-db](https://github.com/sapics/ip-location-db) (country type only). -You can "YOUR_CHOOSEN_DATABSE" from [ip-location-db](https://github.com/sapics/ip-location-db) with country type. For example, "geolite2-geo-whois-asn" is wider IP range country database which is equivalent to GeoLite2 database result for GeoLite2 country covered IP range and geo-whois-asn-country for the other IP range. -The other example, "geo-whois-asn" is [CC0 licensed database](https://github.com/sapics/ip-location-db/tree/main/geo-asn-country), if you are unable to apply the GeoLite2 License. - - -After v2.0, the database is created automatically at initial startup, and updated automatically by setting `ILA_AUTO_UPDATE` which updates twice weekly with default setting. - - -## How to use with an example - -When you need only geographic coordinates, please set "ILA_FIELDS=latitude,longitude". -You need to create a database for each configuration. -After v2.0.0, the database is created at initial running (which takes some seconds), and auto update with `ILA_AUTO_UPDATE` which update twice weekly with default setting. - -The database is created by following CLI - -```bash -$ npm run updatedb ILA_FIELDS=latitude,longitude -``` - -or - -```bash -$ ILA_FIELDS=latitude,longitude # set environment variable -$ npm run updatedb -``` - -or you can create database with 'create.js' which includes the following. - -```javascript -await updateDb({fields:['latitude', 'longitude']}) -``` - - -The CLI command for using `app.js` which uses `ip-location-api` is necessary to start with following CLI parameter - -```bash -$ node app.js ILA_FIELDS=latitude,longitude -``` - -or environment variable - -```bash -$ ILA_FIELDS=latitude,longitude # set environment variable -$ node app.js -``` - -or you can write down configuration in `reload` function of app.js as - -```javascript -await reload({fields:['latitude', 'longitude']}) -// or await reload({fields:'latitude,longitude'}) -``` - - -If you need all the data in above field table, setting "ILA_FIELDS=all" and "ILA_ADD_COUNTRY_INFO=true" is the one. - - -| benchmark | in-memory db | startup | lookup ipv4 | lookup ipv6 | -| ---- | ---- | ---- | ---- | ---- | -| longitude,latitude | 46.5 MB | 10 ms | 0.428 μs/ip | 0.776 μs/ip | -| all | 76.4 MB | 18 ms | 1.054 μs/ip | 1.348 μs/ip | - - -## For module bundler (webpack, vite, next.js, etc) - -Some module bundlers cannot work with original database system. -If module bundlers could not work with `ip-location-api`, please try to import module as following. -It works almost same as original module. - -```js -import { lookup } from 'ip-location-api/pack' -``` - - -It would be better to set directories for database files which have write permission. -Without write permission directories, you cannot use this module. - -```bash -ILA_DATA_DIR=/your_database_directory -ILA_TMP_DATA_DIR=/your_tmporary_directory_for_database -``` - - -## Node.js version - -This library supports Node.js >= 14 for ESM and CJS. - - -## License and EULA - -There are multiple licenses in this library, one for the software library, and the others for the datadata. -Please read the LICENSE and EULA files for details. - - -The license for the software itself is published under MIT License by [sapics](https://github.com/sapics). - - -The GeoLite2 database comes with certain restrictions and obligations, most notably: - - You cannot prevent the library from updating the databases. - - You cannot use the GeoLite2 data: - - for FCRA purposes, - - to identify specific households or individuals. - -You can read [the latest version of GeoLite2 EULA](https://www.maxmind.com/en/geolite2/eula). -GeoLite2 database is provided under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) by [MaxMind](https://www.maxmind.com/), so, you need to create attribusion to [MaxMind](https://www.maxmind.com/) for using GeoLite2 database. - - -The database of [Countries](https://github.com/annexare/Countries) is published under MIT license by [Annexare Studio](https://annexare.com/). diff --git a/browser/country-extra/README.md b/browser/country-extra/README.md deleted file mode 100644 index 71e7237..0000000 --- a/browser/country-extra/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# @iplookup/country-extra [![npm version](https://img.shields.io/npm/v/@iplookup/country-extra?color=success&style=flat-square&label=npm)](https://www.npmjs.com/package/@iplookup/country-extra) - -This is an API created to make [ip-location-api](https://github.com/sapics/ip-location-api) available for browsers. -The database itself is large at 7MB, so it is splitted into over 2000 pieces for fast downloading in a browser. - - -## Synopsis - -```html - - -``` - -#### ESM - -```javascript -import IpLookup from '@iplookup/country-extra' -await IpLookup("2402:b801:ea8b:23c0::") -``` - -#### CJS - -```javascript -const IpLookup = require('@iplookup/country-extra') -await IpLookup("207.97.227.239") -``` - -If you do not need extra information about country, try to use [@iplookup/country](https://github.com/sapics/ip-location-api/tree/main/browser/country). - - -## License - -Since each user download a partial database, we use the CC0 Licensed database [geo-whois-asn-country](https://github.com/sapics/ip-location-db/tree/main/geo-whois-asn-country) for ip to country mapping to avoid license problem. - -To get extra information about country, we use [Countries](https://github.com/annexare/Countries) which is published under MIT license by [Annexare Studio](https://annexare.com/). - -The software itself is published under MIT license by [sapics](https://github.com/sapics). \ No newline at end of file diff --git a/browser/country-extra/iplookup.cjs b/browser/country-extra/iplookup.cjs deleted file mode 100644 index cba7a94..0000000 --- a/browser/country-extra/iplookup.cjs +++ /dev/null @@ -1,187 +0,0 @@ -'use strict'; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 2 ; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null -}; - -/*! countries-list v3.1.1 by Annexare | MIT */ -var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - -var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata -}; - -module.exports = browserExtra; diff --git a/browser/country-extra/iplookup.js b/browser/country-extra/iplookup.js deleted file mode 100644 index 150ae28..0000000 --- a/browser/country-extra/iplookup.js +++ /dev/null @@ -1,168 +0,0 @@ -var IpLookup = (function () { - 'use strict'; - - const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 - }; - - const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r - }; - - const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num - }; - const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) - }; - - const TOP_URL = document.currentScript.src.split('/').slice(0, -1).join('/') + '/'; - const MAIN_RECORD_SIZE = 2 ; - const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - - const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) - }; - - const downloadIdx = downloadArrayBuffer; - - const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; - const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[6] = new BigUint64Array(buf) - }) - }; - - var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { - // console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { - // console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null - }; - - /*! countries-list v3.1.1 by Annexare | MIT */ - var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - - var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata - }; - - return browserExtra; - -})(); diff --git a/browser/country-extra/iplookup.min.js b/browser/country-extra/iplookup.min.js deleted file mode 100644 index eaa2860..0000000 --- a/browser/country-extra/iplookup.min.js +++ /dev/null @@ -1,2 +0,0 @@ -var IpLookup=function(){"use strict";const n=n=>((n=n.split(/\./))[0]<<24|n[1]<<16|n[2]<<8|n[3])>>>0,a=document.currentScript.src.split("/").slice(0,-1).join("/")+"/",e=async(n,a=3)=>fetch(n,{cache:"no-cache"}).then((async t=>{return t.ok?t.arrayBuffer():404===t.status?null:a?(await(i=100*(4-a)*(4-a),new Promise((n=>setTimeout(n,i)))),e(n,a-1)):null;var i})),t=e,i={},c={4:a,6:a},r={4:t(a+"4.idx").then((n=>{if(n)return i[4]=new Uint32Array(n)})),6:t(a+"4.idx").then((n=>{if(n)return i[6]=new BigUint64Array(n)}))};var o=async a=>{var t,o,u=!0;a.includes(":")?(t=(a=>{if(a.includes("."))return n(a.split(":").pop());var e,t=0n;if((a=a.split(/:/)).length-1<7){const n=a.indexOf("");if(n<4){const t=8-a.length,i=n+t;for(e=7;e>=n;e--)a[e]=e>i?a[e-t]:0}}for(e=0;e<4;e++)a[e]&&(t+=BigInt(parseInt(a[e],16))<=l[0]))return null;for(var g,s=0,p=l.length-1;;)if(t>1]){if(p-s<2)return null;p=g-1}else{if(s===g){p>g&&t>=l[p]&&(g=p);break}s=g}const m=((n,a)=>n.length>a?n:"_".repeat(a-n.length)+n)(g.toString(36),2);const A=await e(c[o]+o+"/"+m);if(!A)return null;const h=2*(o-2),y=2+2*h,S=A.byteLength/y,v=u?new Uint32Array(A.slice(0,4*S)):new BigUint64Array(A.slice(0,8*S));for(s=0,p=S-1;;)if(t>1]){if(p-s<2)return null;p=g-1}else{if(s===g){p>g&&t>=v[p]&&(g=p);break}s=g}const M=u?new Uint32Array(A.slice((S+g)*h,(S+g+1)*h))[0]:new BigUint64Array(A.slice((S+g)*h,(S+g+1)*h))[0];if(t>=v[g]&&t<=M){const n=new Uint16Array(A.slice(S*h*2+2*g,S*h*2+2*(g+1)))[0];return{country:String.fromCharCode(255&n,n>>8)}}return null},u={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"دولة الإمارات العربية المتحدة",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"افغانستان",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqipëria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"Հայաստան",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"Österreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"Åland",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Azərbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"België",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"България",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"‏البحرين",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"Bénin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barthélemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Brasília",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"ʼbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvetøya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"Белару́сь",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"République démocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"Ködörösêse tî Bêafrîka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"République du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"Côte d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaoundé",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"中国",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogotá",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San José",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Curaçao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"Κύπρος",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"Česká republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"República Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"الجزائر",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"مصر‎",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"الصحراء الغربية",phone:[212],continent:"AF",capital:"El Aaiún",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"ኤርትራ",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"España",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"ኢትዮጵያ",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"Føroyar",phone:[298],continent:"EU",capital:"Tórshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"საქართველო",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane française",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guinée",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"Ελλάδα",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hagåtña",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guiné-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"香港",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Haïti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarország",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"Éire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"יִשְׂרָאֵל",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"भारत",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"العراق",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"ایران",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"Ísland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"الأردن",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"日本",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"Кыргызстан",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"Kâmpŭchéa",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"북한",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"대한민국",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"الكويت",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"Қазақстан",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"ສປປລາວ",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"لبنان",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"śrī laṃkāva",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"‏ليبيا",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"المغرب",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chișinău",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"Црна Гора",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M̧ajeļ",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"Северна Македонија",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"မြန်မာ",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"Монгол улс",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"澳門",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"موريتانيا",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Malé",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"México",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Moçambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Calédonie",phone:[687],continent:"OC",capital:"Nouméa",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"नेपाल",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niuē",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"عمان",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panamá",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Perú",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polynésie française",phone:[689],continent:"OC",capital:"Papeetē",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"فلسطين",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunción",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"قطر",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La Réunion",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"România",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"Србија",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"Россия",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"العربية السعودية",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"السودان",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"Sénégal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"São Tomé e Príncipe",phone:[239],continent:"AF",capital:"São Tomé",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"سوريا",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Français",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lomé",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"ประเทศไทย",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"Тоҷикистон",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"Türkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"تونس",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"Türkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"臺灣",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"Україна",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Việt Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosovës",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"اليَمَن",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}},l={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"}; -/*! countries-list v3.1.1 by Annexare | MIT */Object.keys(u).map((n=>(n=>({...u[n],iso2:n,iso3:l[n]}))(n)));return async n=>{const a=await o(n);if(a&&u[a.country]){const n=u[a.country];a.country_name=n.name,a.country_native=n.native,a.continent=n.continent,a.capital=n.capital,a.phone=n.phone,a.currency=n.currency,a.languages=n.languages}return a}}(); diff --git a/browser/country-extra/iplookup.mjs b/browser/country-extra/iplookup.mjs deleted file mode 100644 index 6d33c40..0000000 --- a/browser/country-extra/iplookup.mjs +++ /dev/null @@ -1,185 +0,0 @@ -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 2 ; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null -}; - -/*! countries-list v3.1.1 by Annexare | MIT */ -var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - -var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata -}; - -export { browserExtra as default }; diff --git a/browser/country-extra/package.json b/browser/country-extra/package.json deleted file mode 100644 index 1368036..0000000 --- a/browser/country-extra/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@iplookup/country-extra", - "version": "1.0.20241112", - "description": "Browser api to lookup country from IP address", - "keywords": [ - "location", - "iplookup", - "lookup", - "geo", - "geoip", - "ip", - "ipv4", - "ipv6", - "ip-location-db", - "country" - ], - "author": "sapics", - "homepage": "https://github.com/sapics/ip-location-api", - "repository": { - "type": "git", - "url": "git://github.com/sapics/ip-location-api.git" - }, - "publishConfig": { - "access": "public" - }, - "license": "MIT", - "main": "iplookup.cjs", - "exports": { - "import": "./iplookup.mjs", - "require": "./iplookup.cjs" - } -} \ No newline at end of file diff --git a/browser/country-offline.html b/browser/country-offline.html deleted file mode 100644 index 6a7071c..0000000 --- a/browser/country-offline.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - TEST - - - -

TEST

- - -

EXPORT INFORMATION

-
- - - diff --git a/browser/country-online.html b/browser/country-online.html deleted file mode 100644 index 490be32..0000000 --- a/browser/country-online.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - TEST - - - -

TEST

- - -

EXPORT INFORMATION

-
- - - diff --git a/browser/country/README.md b/browser/country/README.md deleted file mode 100644 index ac8fc93..0000000 --- a/browser/country/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# @iplookup/country [![npm version](https://img.shields.io/npm/v/@iplookup/country?color=success&style=flat-square&label=npm)](https://www.npmjs.com/package/@iplookup/country) - - -This is an API created to make [ip-location-api](https://github.com/sapics/ip-location-api) available for browsers. -The database itself is large at 7MB, so it is splitted into over 2000 pieces for fast downloading in a browser. - - -## Synopsis - -```html - - -``` - -#### ESM - -```javascript -import IpLookup from '@iplookup/country' -await IpLookup("2402:b801:ea8b:23c0::") -``` - -#### CJS - -```javascript -const IpLookup = require('@iplookup/country') -await IpLookup("207.97.227.239") -``` - -If you need extra information about country, try to use [@iplookup/country-extra](https://github.com/sapics/ip-location-api/tree/main/browser/country-extra). - - -## License - -Since each user download a partial database, we use the CC0 Licensed database [geo-whois-asn-country](https://github.com/sapics/ip-location-db/tree/main/geo-whois-asn-country) for ip to country mapping to avoid license problem. - -The software itself is published under MIT License by [sapics](https://github.com/sapics). \ No newline at end of file diff --git a/browser/country/iplookup.cjs b/browser/country/iplookup.cjs deleted file mode 100644 index 168c89a..0000000 --- a/browser/country/iplookup.cjs +++ /dev/null @@ -1,169 +0,0 @@ -'use strict'; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 2 ; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null -}; - -module.exports = browser; diff --git a/browser/country/iplookup.js b/browser/country/iplookup.js deleted file mode 100644 index 3abadec..0000000 --- a/browser/country/iplookup.js +++ /dev/null @@ -1,150 +0,0 @@ -var IpLookup = (function () { - 'use strict'; - - const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 - }; - - const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r - }; - - const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num - }; - const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) - }; - - const TOP_URL = document.currentScript.src.split('/').slice(0, -1).join('/') + '/'; - const MAIN_RECORD_SIZE = 2 ; - const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - - const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) - }; - - const downloadIdx = downloadArrayBuffer; - - const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; - const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[6] = new BigUint64Array(buf) - }) - }; - - var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { - // console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { - // console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null - }; - - return browser; - -})(); diff --git a/browser/country/iplookup.min.js b/browser/country/iplookup.min.js deleted file mode 100644 index a88f5c9..0000000 --- a/browser/country/iplookup.min.js +++ /dev/null @@ -1 +0,0 @@ -var IpLookup=function(){"use strict";const n=n=>((n=n.split(/\./))[0]<<24|n[1]<<16|n[2]<<8|n[3])>>>0,r=document.currentScript.src.split("/").slice(0,-1).join("/")+"/",t=async(n,r=3)=>fetch(n,{cache:"no-cache"}).then((async e=>{return e.ok?e.arrayBuffer():404===e.status?null:r?(await(i=100*(4-r)*(4-r),new Promise((n=>setTimeout(n,i)))),t(n,r-1)):null;var i})),e=t,i={},l={4:r,6:r},s={4:e(r+"4.idx").then((n=>{if(n)return i[4]=new Uint32Array(n)})),6:e(r+"4.idx").then((n=>{if(n)return i[6]=new BigUint64Array(n)}))};return async r=>{var e,c,u=!0;r.includes(":")?6===(c=(e=(r=>{if(r.includes("."))return n(r.split(":").pop());var t,e=0n;if((r=r.split(/:/)).length-1<7){const n=r.indexOf("");if(n<4){const e=8-r.length,i=n+e;for(t=7;t>=n;t--)r[t]=t>i?r[t-e]:0}}for(t=0;t<4;t++)r[t]&&(e+=BigInt(parseInt(r[t],16))<=a[0]))return null;for(var o,f=0,g=a.length-1;;)if(e>1]){if(g-f<2)return null;g=o-1}else{if(f===o){g>o&&e>=a[g]&&(o=g);break}f=o}const h=((n,r)=>n.length>r?n:"_".repeat(r-n.length)+n)(o.toString(36),2);const y=await t(l[c]+c+"/"+h);if(!y)return null;const p=2*(c-2),w=2+2*p,d=y.byteLength/w,A=u?new Uint32Array(y.slice(0,4*d)):new BigUint64Array(y.slice(0,8*d));for(f=0,g=d-1;;)if(e>1]){if(g-f<2)return null;g=o-1}else{if(f===o){g>o&&e>=A[g]&&(o=g);break}f=o}const B=u?new Uint32Array(y.slice((d+o)*p,(d+o+1)*p))[0]:new BigUint64Array(y.slice((d+o)*p,(d+o+1)*p))[0];if(e>=A[o]&&e<=B){const n=new Uint16Array(y.slice(d*p*2+2*o,d*p*2+2*(o+1)))[0];return{country:String.fromCharCode(255&n,n>>8)}}return null}}(); diff --git a/browser/country/iplookup.mjs b/browser/country/iplookup.mjs deleted file mode 100644 index f7be8e7..0000000 --- a/browser/country/iplookup.mjs +++ /dev/null @@ -1,167 +0,0 @@ -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 2 ; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0]; - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } - } - return null -}; - -export { browser as default }; diff --git a/browser/country/package.json b/browser/country/package.json deleted file mode 100644 index 2049270..0000000 --- a/browser/country/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@iplookup/country", - "version": "1.0.20241112", - "description": "Browser api to lookup country from IP address", - "keywords": [ - "location", - "iplookup", - "lookup", - "geo", - "geoip", - "ip", - "ipv4", - "ipv6", - "ip-location-db", - "country" - ], - "author": "sapics", - "homepage": "https://github.com/sapics/ip-location-api", - "repository": { - "type": "git", - "url": "git://github.com/sapics/ip-location-api.git" - }, - "publishConfig": { - "access": "public" - }, - "license": "MIT", - "main": "iplookup.cjs", - "exports": { - "import": "./iplookup.mjs", - "require": "./iplookup.cjs" - } -} \ No newline at end of file diff --git a/browser/geocode-extra/README.md b/browser/geocode-extra/README.md deleted file mode 100644 index 38e17ee..0000000 --- a/browser/geocode-extra/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# @iplookup/geocode-extra [![npm version](https://img.shields.io/npm/v/@iplookup/geocode-extra?color=success&style=flat-square&label=npm)](https://www.npmjs.com/package/@iplookup/geocode-extra) - -This is an API created to make [ip-location-api](https://github.com/sapics/ip-location-api) available for browsers. -The database itself is large at 132MB, so it is splitted into over 4000 pieces for fast downloading in a browser. - - -## Synopsis - -```html - - -``` - -#### ESM - -```javascript -import IpLookup from '@iplookup/geocode-extra' -await IpLookup("2402:b801:ea8b:23c0::") -``` - -#### CJS - -```javascript -const IpLookup = require('@iplookup/geocode-extra') -await IpLookup("207.97.227.239") -``` - -If you do not need extra information about country, try to use [@iplookup/geocode](https://github.com/sapics/ip-location-api/tree/main/browser/geocode). - - -## License - -The database for mapping ip to geocode is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by [DB-IP](https://db-ip.com/db/download/ip-to-city-lite). - -To get extra information about country, we use [Countries](https://github.com/annexare/Countries) which is published under MIT license by [Annexare Studio](https://annexare.com/). - -The software itself is published under MIT license by [sapics](https://github.com/sapics). \ No newline at end of file diff --git a/browser/geocode-extra/iplookup.cjs b/browser/geocode-extra/iplookup.cjs deleted file mode 100644 index ccaf614..0000000 --- a/browser/geocode-extra/iplookup.cjs +++ /dev/null @@ -1,192 +0,0 @@ -'use strict'; - -const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -}; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 8; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null -}; - -/*! countries-list v3.1.1 by Annexare | MIT */ -var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - -var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata -}; - -module.exports = browserExtra; diff --git a/browser/geocode-extra/iplookup.js b/browser/geocode-extra/iplookup.js deleted file mode 100644 index 7388b41..0000000 --- a/browser/geocode-extra/iplookup.js +++ /dev/null @@ -1,173 +0,0 @@ -var IpLookup = (function () { - 'use strict'; - - const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) - }; - - const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 - }; - - const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r - }; - - const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num - }; - const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) - }; - - const TOP_URL = document.currentScript.src.split('/').slice(0, -1).join('/') + '/'; - const MAIN_RECORD_SIZE = 8; - const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - - const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) - }; - - const downloadIdx = downloadArrayBuffer; - - const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; - const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[6] = new BigUint64Array(buf) - }) - }; - - var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { - // console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { - // console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null - }; - - /*! countries-list v3.1.1 by Annexare | MIT */ - var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - - var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata - }; - - return browserExtra; - -})(); diff --git a/browser/geocode-extra/iplookup.min.js b/browser/geocode-extra/iplookup.min.js deleted file mode 100644 index e1512be..0000000 --- a/browser/geocode-extra/iplookup.min.js +++ /dev/null @@ -1,2 +0,0 @@ -var IpLookup=function(){"use strict";const n=n=>((n=n.split(/\./))[0]<<24|n[1]<<16|n[2]<<8|n[3])>>>0,a=document.currentScript.src.split("/").slice(0,-1).join("/")+"/",e=async(n,a=3)=>fetch(n,{cache:"no-cache"}).then((async t=>{return t.ok?t.arrayBuffer():404===t.status?null:a?(await(i=100*(4-a)*(4-a),new Promise((n=>setTimeout(n,i)))),e(n,a-1)):null;var i})),t=e,i={},c={4:a,6:a},r={4:t(a+"4.idx").then((n=>{if(n)return i[4]=new Uint32Array(n)})),6:t(a+"4.idx").then((n=>{if(n)return i[6]=new BigUint64Array(n)}))};var o=async a=>{var t,o,u=!0;a.includes(":")?(t=(a=>{if(a.includes("."))return n(a.split(":").pop());var e,t=0n;if((a=a.split(/:/)).length-1<7){const n=a.indexOf("");if(n<4){const t=8-a.length,i=n+t;for(e=7;e>=n;e--)a[e]=e>i?a[e-t]:0}}for(e=0;e<4;e++)a[e]&&(t+=BigInt(parseInt(a[e],16))<=l[0]))return null;for(var g,s=0,p=l.length-1;;)if(t>1]){if(p-s<2)return null;p=g-1}else{if(s===g){p>g&&t>=l[p]&&(g=p);break}s=g}const m=((n,a)=>n.length>a?n:"_".repeat(a-n.length)+n)(g.toString(36),2);const A=await e(c[o]+o+"/"+m);if(!A)return null;const h=2*(o-2),y=8+2*h,S=A.byteLength/y,v=u?new Uint32Array(A.slice(0,4*S)):new BigUint64Array(A.slice(0,8*S));for(s=0,p=S-1;;)if(t>1]){if(p-s<2)return null;p=g-1}else{if(s===g){p>g&&t>=v[p]&&(g=p);break}s=g}const M=u?new Uint32Array(A.slice((S+g)*h,(S+g+1)*h))[0]:new BigUint64Array(A.slice((S+g)*h,(S+g+1)*h))[0];if(t>=v[g]&&t<=M){const n=new Int32Array(A.slice(S*h*2+8*g,S*h*2+8*(g+1))),a=(n=>String.fromCharCode(65+(n/26|0),n%26+65))(1023&n[0]);return{latitude:(n[0]>>10)/1e4,longitude:n[1]/1e4,country:a}}return null},u={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"دولة الإمارات العربية المتحدة",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"افغانستان",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqipëria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"Հայաստան",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"Österreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"Åland",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Azərbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"België",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"България",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"‏البحرين",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"Bénin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barthélemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Brasília",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"ʼbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvetøya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"Белару́сь",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"République démocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"Ködörösêse tî Bêafrîka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"République du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"Côte d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaoundé",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"中国",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogotá",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San José",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Curaçao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"Κύπρος",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"Česká republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"República Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"الجزائر",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"مصر‎",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"الصحراء الغربية",phone:[212],continent:"AF",capital:"El Aaiún",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"ኤርትራ",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"España",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"ኢትዮጵያ",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"Føroyar",phone:[298],continent:"EU",capital:"Tórshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"საქართველო",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane française",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guinée",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"Ελλάδα",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hagåtña",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guiné-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"香港",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Haïti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarország",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"Éire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"יִשְׂרָאֵל",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"भारत",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"العراق",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"ایران",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"Ísland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"الأردن",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"日本",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"Кыргызстан",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"Kâmpŭchéa",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"북한",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"대한민국",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"الكويت",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"Қазақстан",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"ສປປລາວ",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"لبنان",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"śrī laṃkāva",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"‏ليبيا",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"المغرب",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chișinău",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"Црна Гора",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M̧ajeļ",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"Северна Македонија",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"မြန်မာ",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"Монгол улс",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"澳門",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"موريتانيا",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Malé",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"México",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Moçambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Calédonie",phone:[687],continent:"OC",capital:"Nouméa",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"नेपाल",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niuē",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"عمان",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panamá",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Perú",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polynésie française",phone:[689],continent:"OC",capital:"Papeetē",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"فلسطين",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunción",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"قطر",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La Réunion",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"România",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"Србија",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"Россия",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"العربية السعودية",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"السودان",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"Sénégal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"São Tomé e Príncipe",phone:[239],continent:"AF",capital:"São Tomé",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"سوريا",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Français",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lomé",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"ประเทศไทย",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"Тоҷикистон",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"Türkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"تونس",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"Türkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"臺灣",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"Україна",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Việt Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosovës",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"اليَمَن",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}},l={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"}; -/*! countries-list v3.1.1 by Annexare | MIT */Object.keys(u).map((n=>(n=>({...u[n],iso2:n,iso3:l[n]}))(n)));return async n=>{const a=await o(n);if(a&&u[a.country]){const n=u[a.country];a.country_name=n.name,a.country_native=n.native,a.continent=n.continent,a.capital=n.capital,a.phone=n.phone,a.currency=n.currency,a.languages=n.languages}return a}}(); diff --git a/browser/geocode-extra/iplookup.mjs b/browser/geocode-extra/iplookup.mjs deleted file mode 100644 index 2d780a1..0000000 --- a/browser/geocode-extra/iplookup.mjs +++ /dev/null @@ -1,190 +0,0 @@ -const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -}; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 8; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var ip_lookup = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null -}; - -/*! countries-list v3.1.1 by Annexare | MIT */ -var a={AD:{name:"Andorra",native:"Andorra",phone:[376],continent:"EU",capital:"Andorra la Vella",currency:["EUR"],languages:["ca"]},AE:{name:"United Arab Emirates",native:"\u062F\u0648\u0644\u0629 \u0627\u0644\u0625\u0645\u0627\u0631\u0627\u062A \u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0645\u062A\u062D\u062F\u0629",phone:[971],continent:"AS",capital:"Abu Dhabi",currency:["AED"],languages:["ar"]},AF:{name:"Afghanistan",native:"\u0627\u0641\u063A\u0627\u0646\u0633\u062A\u0627\u0646",phone:[93],continent:"AS",capital:"Kabul",currency:["AFN"],languages:["ps","uz","tk"]},AG:{name:"Antigua and Barbuda",native:"Antigua and Barbuda",phone:[1268],continent:"NA",capital:"Saint John's",currency:["XCD"],languages:["en"]},AI:{name:"Anguilla",native:"Anguilla",phone:[1264],continent:"NA",capital:"The Valley",currency:["XCD"],languages:["en"]},AL:{name:"Albania",native:"Shqip\xEBria",phone:[355],continent:"EU",capital:"Tirana",currency:["ALL"],languages:["sq"]},AM:{name:"Armenia",native:"\u0540\u0561\u0575\u0561\u057D\u057F\u0561\u0576",phone:[374],continent:"AS",capital:"Yerevan",currency:["AMD"],languages:["hy","ru"]},AO:{name:"Angola",native:"Angola",phone:[244],continent:"AF",capital:"Luanda",currency:["AOA"],languages:["pt"]},AQ:{name:"Antarctica",native:"Antarctica",phone:[672],continent:"AN",capital:"",currency:[],languages:[]},AR:{name:"Argentina",native:"Argentina",phone:[54],continent:"SA",capital:"Buenos Aires",currency:["ARS"],languages:["es","gn"]},AS:{name:"American Samoa",native:"American Samoa",phone:[1684],continent:"OC",capital:"Pago Pago",currency:["USD"],languages:["en","sm"]},AT:{name:"Austria",native:"\xD6sterreich",phone:[43],continent:"EU",capital:"Vienna",currency:["EUR"],languages:["de"]},AU:{name:"Australia",native:"Australia",phone:[61],continent:"OC",capital:"Canberra",currency:["AUD"],languages:["en"]},AW:{name:"Aruba",native:"Aruba",phone:[297],continent:"NA",capital:"Oranjestad",currency:["AWG"],languages:["nl","pa"]},AX:{name:"Aland",native:"\xC5land",phone:[358],continent:"EU",capital:"Mariehamn",currency:["EUR"],languages:["sv"],partOf:"FI"},AZ:{name:"Azerbaijan",native:"Az\u0259rbaycan",phone:[994],continent:"AS",continents:["AS","EU"],capital:"Baku",currency:["AZN"],languages:["az"]},BA:{name:"Bosnia and Herzegovina",native:"Bosna i Hercegovina",phone:[387],continent:"EU",capital:"Sarajevo",currency:["BAM"],languages:["bs","hr","sr"]},BB:{name:"Barbados",native:"Barbados",phone:[1246],continent:"NA",capital:"Bridgetown",currency:["BBD"],languages:["en"]},BD:{name:"Bangladesh",native:"Bangladesh",phone:[880],continent:"AS",capital:"Dhaka",currency:["BDT"],languages:["bn"]},BE:{name:"Belgium",native:"Belgi\xEB",phone:[32],continent:"EU",capital:"Brussels",currency:["EUR"],languages:["nl","fr","de"]},BF:{name:"Burkina Faso",native:"Burkina Faso",phone:[226],continent:"AF",capital:"Ouagadougou",currency:["XOF"],languages:["fr","ff"]},BG:{name:"Bulgaria",native:"\u0411\u044A\u043B\u0433\u0430\u0440\u0438\u044F",phone:[359],continent:"EU",capital:"Sofia",currency:["BGN"],languages:["bg"]},BH:{name:"Bahrain",native:"\u200F\u0627\u0644\u0628\u062D\u0631\u064A\u0646",phone:[973],continent:"AS",capital:"Manama",currency:["BHD"],languages:["ar"]},BI:{name:"Burundi",native:"Burundi",phone:[257],continent:"AF",capital:"Bujumbura",currency:["BIF"],languages:["fr","rn"]},BJ:{name:"Benin",native:"B\xE9nin",phone:[229],continent:"AF",capital:"Porto-Novo",currency:["XOF"],languages:["fr"]},BL:{name:"Saint Barthelemy",native:"Saint-Barth\xE9lemy",phone:[590],continent:"NA",capital:"Gustavia",currency:["EUR"],languages:["fr"]},BM:{name:"Bermuda",native:"Bermuda",phone:[1441],continent:"NA",capital:"Hamilton",currency:["BMD"],languages:["en"]},BN:{name:"Brunei",native:"Negara Brunei Darussalam",phone:[673],continent:"AS",capital:"Bandar Seri Begawan",currency:["BND"],languages:["ms"]},BO:{name:"Bolivia",native:"Bolivia",phone:[591],continent:"SA",capital:"Sucre",currency:["BOB","BOV"],languages:["es","ay","qu"]},BQ:{name:"Bonaire",native:"Bonaire",phone:[5997],continent:"NA",capital:"Kralendijk",currency:["USD"],languages:["nl"]},BR:{name:"Brazil",native:"Brasil",phone:[55],continent:"SA",capital:"Bras\xEDlia",currency:["BRL"],languages:["pt"]},BS:{name:"Bahamas",native:"Bahamas",phone:[1242],continent:"NA",capital:"Nassau",currency:["BSD"],languages:["en"]},BT:{name:"Bhutan",native:"\u02BCbrug-yul",phone:[975],continent:"AS",capital:"Thimphu",currency:["BTN","INR"],languages:["dz"]},BV:{name:"Bouvet Island",native:"Bouvet\xF8ya",phone:[47],continent:"AN",capital:"",currency:["NOK"],languages:["no","nb","nn"]},BW:{name:"Botswana",native:"Botswana",phone:[267],continent:"AF",capital:"Gaborone",currency:["BWP"],languages:["en","tn"]},BY:{name:"Belarus",native:"\u0411\u0435\u043B\u0430\u0440\u0443\u0301\u0441\u044C",phone:[375],continent:"EU",capital:"Minsk",currency:["BYN"],languages:["be","ru"]},BZ:{name:"Belize",native:"Belize",phone:[501],continent:"NA",capital:"Belmopan",currency:["BZD"],languages:["en","es"]},CA:{name:"Canada",native:"Canada",phone:[1],continent:"NA",capital:"Ottawa",currency:["CAD"],languages:["en","fr"]},CC:{name:"Cocos (Keeling) Islands",native:"Cocos (Keeling) Islands",phone:[61],continent:"AS",capital:"West Island",currency:["AUD"],languages:["en"]},CD:{name:"Democratic Republic of the Congo",native:"R\xE9publique d\xE9mocratique du Congo",phone:[243],continent:"AF",capital:"Kinshasa",currency:["CDF"],languages:["fr","ln","kg","sw","lu"]},CF:{name:"Central African Republic",native:"K\xF6d\xF6r\xF6s\xEAse t\xEE B\xEAafr\xEEka",phone:[236],continent:"AF",capital:"Bangui",currency:["XAF"],languages:["fr","sg"]},CG:{name:"Republic of the Congo",native:"R\xE9publique du Congo",phone:[242],continent:"AF",capital:"Brazzaville",currency:["XAF"],languages:["fr","ln"]},CH:{name:"Switzerland",native:"Schweiz",phone:[41],continent:"EU",capital:"Bern",currency:["CHE","CHF","CHW"],languages:["de","fr","it"]},CI:{name:"Ivory Coast",native:"C\xF4te d'Ivoire",phone:[225],continent:"AF",capital:"Yamoussoukro",currency:["XOF"],languages:["fr"]},CK:{name:"Cook Islands",native:"Cook Islands",phone:[682],continent:"OC",capital:"Avarua",currency:["NZD"],languages:["en"]},CL:{name:"Chile",native:"Chile",phone:[56],continent:"SA",capital:"Santiago",currency:["CLF","CLP"],languages:["es"]},CM:{name:"Cameroon",native:"Cameroon",phone:[237],continent:"AF",capital:"Yaound\xE9",currency:["XAF"],languages:["en","fr"]},CN:{name:"China",native:"\u4E2D\u56FD",phone:[86],continent:"AS",capital:"Beijing",currency:["CNY"],languages:["zh"]},CO:{name:"Colombia",native:"Colombia",phone:[57],continent:"SA",capital:"Bogot\xE1",currency:["COP"],languages:["es"]},CR:{name:"Costa Rica",native:"Costa Rica",phone:[506],continent:"NA",capital:"San Jos\xE9",currency:["CRC"],languages:["es"]},CU:{name:"Cuba",native:"Cuba",phone:[53],continent:"NA",capital:"Havana",currency:["CUC","CUP"],languages:["es"]},CV:{name:"Cape Verde",native:"Cabo Verde",phone:[238],continent:"AF",capital:"Praia",currency:["CVE"],languages:["pt"]},CW:{name:"Curacao",native:"Cura\xE7ao",phone:[5999],continent:"NA",capital:"Willemstad",currency:["ANG"],languages:["nl","pa","en"]},CX:{name:"Christmas Island",native:"Christmas Island",phone:[61],continent:"AS",capital:"Flying Fish Cove",currency:["AUD"],languages:["en"]},CY:{name:"Cyprus",native:"\u039A\u03CD\u03C0\u03C1\u03BF\u03C2",phone:[357],continent:"EU",capital:"Nicosia",currency:["EUR"],languages:["el","tr","hy"]},CZ:{name:"Czech Republic",native:"\u010Cesk\xE1 republika",phone:[420],continent:"EU",capital:"Prague",currency:["CZK"],languages:["cs"]},DE:{name:"Germany",native:"Deutschland",phone:[49],continent:"EU",capital:"Berlin",currency:["EUR"],languages:["de"]},DJ:{name:"Djibouti",native:"Djibouti",phone:[253],continent:"AF",capital:"Djibouti",currency:["DJF"],languages:["fr","ar"]},DK:{name:"Denmark",native:"Danmark",phone:[45],continent:"EU",continents:["EU","NA"],capital:"Copenhagen",currency:["DKK"],languages:["da"]},DM:{name:"Dominica",native:"Dominica",phone:[1767],continent:"NA",capital:"Roseau",currency:["XCD"],languages:["en"]},DO:{name:"Dominican Republic",native:"Rep\xFAblica Dominicana",phone:[1809,1829,1849],continent:"NA",capital:"Santo Domingo",currency:["DOP"],languages:["es"]},DZ:{name:"Algeria",native:"\u0627\u0644\u062C\u0632\u0627\u0626\u0631",phone:[213],continent:"AF",capital:"Algiers",currency:["DZD"],languages:["ar"]},EC:{name:"Ecuador",native:"Ecuador",phone:[593],continent:"SA",capital:"Quito",currency:["USD"],languages:["es"]},EE:{name:"Estonia",native:"Eesti",phone:[372],continent:"EU",capital:"Tallinn",currency:["EUR"],languages:["et"]},EG:{name:"Egypt",native:"\u0645\u0635\u0631\u200E",phone:[20],continent:"AF",continents:["AF","AS"],capital:"Cairo",currency:["EGP"],languages:["ar"]},EH:{name:"Western Sahara",native:"\u0627\u0644\u0635\u062D\u0631\u0627\u0621 \u0627\u0644\u063A\u0631\u0628\u064A\u0629",phone:[212],continent:"AF",capital:"El Aai\xFAn",currency:["MAD","DZD","MRU"],languages:["es"]},ER:{name:"Eritrea",native:"\u12A4\u122D\u1275\u122B",phone:[291],continent:"AF",capital:"Asmara",currency:["ERN"],languages:["ti","ar","en"]},ES:{name:"Spain",native:"Espa\xF1a",phone:[34],continent:"EU",capital:"Madrid",currency:["EUR"],languages:["es","eu","ca","gl","oc"]},ET:{name:"Ethiopia",native:"\u12A2\u1275\u12EE\u1335\u12EB",phone:[251],continent:"AF",capital:"Addis Ababa",currency:["ETB"],languages:["am"]},FI:{name:"Finland",native:"Suomi",phone:[358],continent:"EU",capital:"Helsinki",currency:["EUR"],languages:["fi","sv"]},FJ:{name:"Fiji",native:"Fiji",phone:[679],continent:"OC",capital:"Suva",currency:["FJD"],languages:["en","fj","hi","ur"]},FK:{name:"Falkland Islands",native:"Falkland Islands",phone:[500],continent:"SA",capital:"Stanley",currency:["FKP"],languages:["en"]},FM:{name:"Micronesia",native:"Micronesia",phone:[691],continent:"OC",capital:"Palikir",currency:["USD"],languages:["en"]},FO:{name:"Faroe Islands",native:"F\xF8royar",phone:[298],continent:"EU",capital:"T\xF3rshavn",currency:["DKK"],languages:["fo"]},FR:{name:"France",native:"France",phone:[33],continent:"EU",capital:"Paris",currency:["EUR"],languages:["fr"]},GA:{name:"Gabon",native:"Gabon",phone:[241],continent:"AF",capital:"Libreville",currency:["XAF"],languages:["fr"]},GB:{name:"United Kingdom",native:"United Kingdom",phone:[44],continent:"EU",capital:"London",currency:["GBP"],languages:["en"]},GD:{name:"Grenada",native:"Grenada",phone:[1473],continent:"NA",capital:"St. George's",currency:["XCD"],languages:["en"]},GE:{name:"Georgia",native:"\u10E1\u10D0\u10E5\u10D0\u10E0\u10D7\u10D5\u10D4\u10DA\u10DD",phone:[995],continent:"AS",continents:["AS","EU"],capital:"Tbilisi",currency:["GEL"],languages:["ka"]},GF:{name:"French Guiana",native:"Guyane fran\xE7aise",phone:[594],continent:"SA",capital:"Cayenne",currency:["EUR"],languages:["fr"]},GG:{name:"Guernsey",native:"Guernsey",phone:[44],continent:"EU",capital:"St. Peter Port",currency:["GBP"],languages:["en","fr"]},GH:{name:"Ghana",native:"Ghana",phone:[233],continent:"AF",capital:"Accra",currency:["GHS"],languages:["en"]},GI:{name:"Gibraltar",native:"Gibraltar",phone:[350],continent:"EU",capital:"Gibraltar",currency:["GIP"],languages:["en"]},GL:{name:"Greenland",native:"Kalaallit Nunaat",phone:[299],continent:"NA",capital:"Nuuk",currency:["DKK"],languages:["kl"]},GM:{name:"Gambia",native:"Gambia",phone:[220],continent:"AF",capital:"Banjul",currency:["GMD"],languages:["en"]},GN:{name:"Guinea",native:"Guin\xE9e",phone:[224],continent:"AF",capital:"Conakry",currency:["GNF"],languages:["fr","ff"]},GP:{name:"Guadeloupe",native:"Guadeloupe",phone:[590],continent:"NA",capital:"Basse-Terre",currency:["EUR"],languages:["fr"]},GQ:{name:"Equatorial Guinea",native:"Guinea Ecuatorial",phone:[240],continent:"AF",capital:"Malabo",currency:["XAF"],languages:["es","fr"]},GR:{name:"Greece",native:"\u0395\u03BB\u03BB\u03AC\u03B4\u03B1",phone:[30],continent:"EU",capital:"Athens",currency:["EUR"],languages:["el"]},GS:{name:"South Georgia and the South Sandwich Islands",native:"South Georgia",phone:[500],continent:"AN",capital:"King Edward Point",currency:["GBP"],languages:["en"]},GT:{name:"Guatemala",native:"Guatemala",phone:[502],continent:"NA",capital:"Guatemala City",currency:["GTQ"],languages:["es"]},GU:{name:"Guam",native:"Guam",phone:[1671],continent:"OC",capital:"Hag\xE5t\xF1a",currency:["USD"],languages:["en","ch","es"]},GW:{name:"Guinea-Bissau",native:"Guin\xE9-Bissau",phone:[245],continent:"AF",capital:"Bissau",currency:["XOF"],languages:["pt"]},GY:{name:"Guyana",native:"Guyana",phone:[592],continent:"SA",capital:"Georgetown",currency:["GYD"],languages:["en"]},HK:{name:"Hong Kong",native:"\u9999\u6E2F",phone:[852],continent:"AS",capital:"City of Victoria",currency:["HKD"],languages:["zh","en"]},HM:{name:"Heard Island and McDonald Islands",native:"Heard Island and McDonald Islands",phone:[61],continent:"AN",capital:"",currency:["AUD"],languages:["en"]},HN:{name:"Honduras",native:"Honduras",phone:[504],continent:"NA",capital:"Tegucigalpa",currency:["HNL"],languages:["es"]},HR:{name:"Croatia",native:"Hrvatska",phone:[385],continent:"EU",capital:"Zagreb",currency:["EUR"],languages:["hr"]},HT:{name:"Haiti",native:"Ha\xEFti",phone:[509],continent:"NA",capital:"Port-au-Prince",currency:["HTG","USD"],languages:["fr","ht"]},HU:{name:"Hungary",native:"Magyarorsz\xE1g",phone:[36],continent:"EU",capital:"Budapest",currency:["HUF"],languages:["hu"]},ID:{name:"Indonesia",native:"Indonesia",phone:[62],continent:"AS",capital:"Jakarta",currency:["IDR"],languages:["id"]},IE:{name:"Ireland",native:"\xC9ire",phone:[353],continent:"EU",capital:"Dublin",currency:["EUR"],languages:["ga","en"]},IL:{name:"Israel",native:"\u05D9\u05B4\u05E9\u05B0\u05C2\u05E8\u05B8\u05D0\u05B5\u05DC",phone:[972],continent:"AS",capital:"Jerusalem",currency:["ILS"],languages:["he","ar"]},IM:{name:"Isle of Man",native:"Isle of Man",phone:[44],continent:"EU",capital:"Douglas",currency:["GBP"],languages:["en","gv"]},IN:{name:"India",native:"\u092D\u093E\u0930\u0924",phone:[91],continent:"AS",capital:"New Delhi",currency:["INR"],languages:["hi","en"]},IO:{name:"British Indian Ocean Territory",native:"British Indian Ocean Territory",phone:[246],continent:"AS",capital:"Diego Garcia",currency:["USD"],languages:["en"]},IQ:{name:"Iraq",native:"\u0627\u0644\u0639\u0631\u0627\u0642",phone:[964],continent:"AS",capital:"Baghdad",currency:["IQD"],languages:["ar","ku"]},IR:{name:"Iran",native:"\u0627\u06CC\u0631\u0627\u0646",phone:[98],continent:"AS",capital:"Tehran",currency:["IRR"],languages:["fa"]},IS:{name:"Iceland",native:"\xCDsland",phone:[354],continent:"EU",capital:"Reykjavik",currency:["ISK"],languages:["is"]},IT:{name:"Italy",native:"Italia",phone:[39],continent:"EU",capital:"Rome",currency:["EUR"],languages:["it"]},JE:{name:"Jersey",native:"Jersey",phone:[44],continent:"EU",capital:"Saint Helier",currency:["GBP"],languages:["en","fr"]},JM:{name:"Jamaica",native:"Jamaica",phone:[1876],continent:"NA",capital:"Kingston",currency:["JMD"],languages:["en"]},JO:{name:"Jordan",native:"\u0627\u0644\u0623\u0631\u062F\u0646",phone:[962],continent:"AS",capital:"Amman",currency:["JOD"],languages:["ar"]},JP:{name:"Japan",native:"\u65E5\u672C",phone:[81],continent:"AS",capital:"Tokyo",currency:["JPY"],languages:["ja"]},KE:{name:"Kenya",native:"Kenya",phone:[254],continent:"AF",capital:"Nairobi",currency:["KES"],languages:["en","sw"]},KG:{name:"Kyrgyzstan",native:"\u041A\u044B\u0440\u0433\u044B\u0437\u0441\u0442\u0430\u043D",phone:[996],continent:"AS",capital:"Bishkek",currency:["KGS"],languages:["ky","ru"]},KH:{name:"Cambodia",native:"K\xE2mp\u016Dch\xE9a",phone:[855],continent:"AS",capital:"Phnom Penh",currency:["KHR"],languages:["km"]},KI:{name:"Kiribati",native:"Kiribati",phone:[686],continent:"OC",capital:"South Tarawa",currency:["AUD"],languages:["en"]},KM:{name:"Comoros",native:"Komori",phone:[269],continent:"AF",capital:"Moroni",currency:["KMF"],languages:["ar","fr"]},KN:{name:"Saint Kitts and Nevis",native:"Saint Kitts and Nevis",phone:[1869],continent:"NA",capital:"Basseterre",currency:["XCD"],languages:["en"]},KP:{name:"North Korea",native:"\uBD81\uD55C",phone:[850],continent:"AS",capital:"Pyongyang",currency:["KPW"],languages:["ko"]},KR:{name:"South Korea",native:"\uB300\uD55C\uBBFC\uAD6D",phone:[82],continent:"AS",capital:"Seoul",currency:["KRW"],languages:["ko"]},KW:{name:"Kuwait",native:"\u0627\u0644\u0643\u0648\u064A\u062A",phone:[965],continent:"AS",capital:"Kuwait City",currency:["KWD"],languages:["ar"]},KY:{name:"Cayman Islands",native:"Cayman Islands",phone:[1345],continent:"NA",capital:"George Town",currency:["KYD"],languages:["en"]},KZ:{name:"Kazakhstan",native:"\u049A\u0430\u0437\u0430\u049B\u0441\u0442\u0430\u043D",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Astana",currency:["KZT"],languages:["kk","ru"]},LA:{name:"Laos",native:"\u0EAA\u0E9B\u0E9B\u0EA5\u0EB2\u0EA7",phone:[856],continent:"AS",capital:"Vientiane",currency:["LAK"],languages:["lo"]},LB:{name:"Lebanon",native:"\u0644\u0628\u0646\u0627\u0646",phone:[961],continent:"AS",capital:"Beirut",currency:["LBP"],languages:["ar","fr"]},LC:{name:"Saint Lucia",native:"Saint Lucia",phone:[1758],continent:"NA",capital:"Castries",currency:["XCD"],languages:["en"]},LI:{name:"Liechtenstein",native:"Liechtenstein",phone:[423],continent:"EU",capital:"Vaduz",currency:["CHF"],languages:["de"]},LK:{name:"Sri Lanka",native:"\u015Br\u012B la\u1E43k\u0101va",phone:[94],continent:"AS",capital:"Colombo",currency:["LKR"],languages:["si","ta"]},LR:{name:"Liberia",native:"Liberia",phone:[231],continent:"AF",capital:"Monrovia",currency:["LRD"],languages:["en"]},LS:{name:"Lesotho",native:"Lesotho",phone:[266],continent:"AF",capital:"Maseru",currency:["LSL","ZAR"],languages:["en","st"]},LT:{name:"Lithuania",native:"Lietuva",phone:[370],continent:"EU",capital:"Vilnius",currency:["EUR"],languages:["lt"]},LU:{name:"Luxembourg",native:"Luxembourg",phone:[352],continent:"EU",capital:"Luxembourg",currency:["EUR"],languages:["fr","de","lb"]},LV:{name:"Latvia",native:"Latvija",phone:[371],continent:"EU",capital:"Riga",currency:["EUR"],languages:["lv"]},LY:{name:"Libya",native:"\u200F\u0644\u064A\u0628\u064A\u0627",phone:[218],continent:"AF",capital:"Tripoli",currency:["LYD"],languages:["ar"]},MA:{name:"Morocco",native:"\u0627\u0644\u0645\u063A\u0631\u0628",phone:[212],continent:"AF",capital:"Rabat",currency:["MAD"],languages:["ar"]},MC:{name:"Monaco",native:"Monaco",phone:[377],continent:"EU",capital:"Monaco",currency:["EUR"],languages:["fr"]},MD:{name:"Moldova",native:"Moldova",phone:[373],continent:"EU",capital:"Chi\u0219in\u0103u",currency:["MDL"],languages:["ro"]},ME:{name:"Montenegro",native:"\u0426\u0440\u043D\u0430 \u0413\u043E\u0440\u0430",phone:[382],continent:"EU",capital:"Podgorica",currency:["EUR"],languages:["sr","bs","sq","hr"]},MF:{name:"Saint Martin",native:"Saint-Martin",phone:[590],continent:"NA",capital:"Marigot",currency:["EUR"],languages:["en","fr","nl"]},MG:{name:"Madagascar",native:"Madagasikara",phone:[261],continent:"AF",capital:"Antananarivo",currency:["MGA"],languages:["fr","mg"]},MH:{name:"Marshall Islands",native:"M\u0327aje\u013C",phone:[692],continent:"OC",capital:"Majuro",currency:["USD"],languages:["en","mh"]},MK:{name:"North Macedonia",native:"\u0421\u0435\u0432\u0435\u0440\u043D\u0430 \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u0458\u0430",phone:[389],continent:"EU",capital:"Skopje",currency:["MKD"],languages:["mk"]},ML:{name:"Mali",native:"Mali",phone:[223],continent:"AF",capital:"Bamako",currency:["XOF"],languages:["fr"]},MM:{name:"Myanmar (Burma)",native:"\u1019\u103C\u1014\u103A\u1019\u102C",phone:[95],continent:"AS",capital:"Naypyidaw",currency:["MMK"],languages:["my"]},MN:{name:"Mongolia",native:"\u041C\u043E\u043D\u0433\u043E\u043B \u0443\u043B\u0441",phone:[976],continent:"AS",capital:"Ulan Bator",currency:["MNT"],languages:["mn"]},MO:{name:"Macao",native:"\u6FB3\u9580",phone:[853],continent:"AS",capital:"",currency:["MOP"],languages:["zh","pt"]},MP:{name:"Northern Mariana Islands",native:"Northern Mariana Islands",phone:[1670],continent:"OC",capital:"Saipan",currency:["USD"],languages:["en","ch"]},MQ:{name:"Martinique",native:"Martinique",phone:[596],continent:"NA",capital:"Fort-de-France",currency:["EUR"],languages:["fr"]},MR:{name:"Mauritania",native:"\u0645\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u0627",phone:[222],continent:"AF",capital:"Nouakchott",currency:["MRU"],languages:["ar"]},MS:{name:"Montserrat",native:"Montserrat",phone:[1664],continent:"NA",capital:"Plymouth",currency:["XCD"],languages:["en"]},MT:{name:"Malta",native:"Malta",phone:[356],continent:"EU",capital:"Valletta",currency:["EUR"],languages:["mt","en"]},MU:{name:"Mauritius",native:"Maurice",phone:[230],continent:"AF",capital:"Port Louis",currency:["MUR"],languages:["en"]},MV:{name:"Maldives",native:"Maldives",phone:[960],continent:"AS",capital:"Mal\xE9",currency:["MVR"],languages:["dv"]},MW:{name:"Malawi",native:"Malawi",phone:[265],continent:"AF",capital:"Lilongwe",currency:["MWK"],languages:["en","ny"]},MX:{name:"Mexico",native:"M\xE9xico",phone:[52],continent:"NA",capital:"Mexico City",currency:["MXN"],languages:["es"]},MY:{name:"Malaysia",native:"Malaysia",phone:[60],continent:"AS",capital:"Kuala Lumpur",currency:["MYR"],languages:["ms"]},MZ:{name:"Mozambique",native:"Mo\xE7ambique",phone:[258],continent:"AF",capital:"Maputo",currency:["MZN"],languages:["pt"]},NA:{name:"Namibia",native:"Namibia",phone:[264],continent:"AF",capital:"Windhoek",currency:["NAD","ZAR"],languages:["en","af"]},NC:{name:"New Caledonia",native:"Nouvelle-Cal\xE9donie",phone:[687],continent:"OC",capital:"Noum\xE9a",currency:["XPF"],languages:["fr"]},NE:{name:"Niger",native:"Niger",phone:[227],continent:"AF",capital:"Niamey",currency:["XOF"],languages:["fr"]},NF:{name:"Norfolk Island",native:"Norfolk Island",phone:[672],continent:"OC",capital:"Kingston",currency:["AUD"],languages:["en"]},NG:{name:"Nigeria",native:"Nigeria",phone:[234],continent:"AF",capital:"Abuja",currency:["NGN"],languages:["en"]},NI:{name:"Nicaragua",native:"Nicaragua",phone:[505],continent:"NA",capital:"Managua",currency:["NIO"],languages:["es"]},NL:{name:"Netherlands",native:"Nederland",phone:[31],continent:"EU",capital:"Amsterdam",currency:["EUR"],languages:["nl"]},NO:{name:"Norway",native:"Norge",phone:[47],continent:"EU",capital:"Oslo",currency:["NOK"],languages:["no","nb","nn"]},NP:{name:"Nepal",native:"\u0928\u0947\u092A\u093E\u0932",phone:[977],continent:"AS",capital:"Kathmandu",currency:["NPR"],languages:["ne"]},NR:{name:"Nauru",native:"Nauru",phone:[674],continent:"OC",capital:"Yaren",currency:["AUD"],languages:["en","na"]},NU:{name:"Niue",native:"Niu\u0113",phone:[683],continent:"OC",capital:"Alofi",currency:["NZD"],languages:["en"]},NZ:{name:"New Zealand",native:"New Zealand",phone:[64],continent:"OC",capital:"Wellington",currency:["NZD"],languages:["en","mi"]},OM:{name:"Oman",native:"\u0639\u0645\u0627\u0646",phone:[968],continent:"AS",capital:"Muscat",currency:["OMR"],languages:["ar"]},PA:{name:"Panama",native:"Panam\xE1",phone:[507],continent:"NA",capital:"Panama City",currency:["PAB","USD"],languages:["es"]},PE:{name:"Peru",native:"Per\xFA",phone:[51],continent:"SA",capital:"Lima",currency:["PEN"],languages:["es"]},PF:{name:"French Polynesia",native:"Polyn\xE9sie fran\xE7aise",phone:[689],continent:"OC",capital:"Papeet\u0113",currency:["XPF"],languages:["fr"]},PG:{name:"Papua New Guinea",native:"Papua Niugini",phone:[675],continent:"OC",capital:"Port Moresby",currency:["PGK"],languages:["en"]},PH:{name:"Philippines",native:"Pilipinas",phone:[63],continent:"AS",capital:"Manila",currency:["PHP"],languages:["en"]},PK:{name:"Pakistan",native:"Pakistan",phone:[92],continent:"AS",capital:"Islamabad",currency:["PKR"],languages:["en","ur"]},PL:{name:"Poland",native:"Polska",phone:[48],continent:"EU",capital:"Warsaw",currency:["PLN"],languages:["pl"]},PM:{name:"Saint Pierre and Miquelon",native:"Saint-Pierre-et-Miquelon",phone:[508],continent:"NA",capital:"Saint-Pierre",currency:["EUR"],languages:["fr"]},PN:{name:"Pitcairn Islands",native:"Pitcairn Islands",phone:[64],continent:"OC",capital:"Adamstown",currency:["NZD"],languages:["en"]},PR:{name:"Puerto Rico",native:"Puerto Rico",phone:[1787,1939],continent:"NA",capital:"San Juan",currency:["USD"],languages:["es","en"]},PS:{name:"Palestine",native:"\u0641\u0644\u0633\u0637\u064A\u0646",phone:[970],continent:"AS",capital:"Ramallah",currency:["ILS"],languages:["ar"]},PT:{name:"Portugal",native:"Portugal",phone:[351],continent:"EU",capital:"Lisbon",currency:["EUR"],languages:["pt"]},PW:{name:"Palau",native:"Palau",phone:[680],continent:"OC",capital:"Ngerulmud",currency:["USD"],languages:["en"]},PY:{name:"Paraguay",native:"Paraguay",phone:[595],continent:"SA",capital:"Asunci\xF3n",currency:["PYG"],languages:["es","gn"]},QA:{name:"Qatar",native:"\u0642\u0637\u0631",phone:[974],continent:"AS",capital:"Doha",currency:["QAR"],languages:["ar"]},RE:{name:"Reunion",native:"La R\xE9union",phone:[262],continent:"AF",capital:"Saint-Denis",currency:["EUR"],languages:["fr"]},RO:{name:"Romania",native:"Rom\xE2nia",phone:[40],continent:"EU",capital:"Bucharest",currency:["RON"],languages:["ro"]},RS:{name:"Serbia",native:"\u0421\u0440\u0431\u0438\u0458\u0430",phone:[381],continent:"EU",capital:"Belgrade",currency:["RSD"],languages:["sr"]},RU:{name:"Russia",native:"\u0420\u043E\u0441\u0441\u0438\u044F",phone:[7],continent:"AS",continents:["AS","EU"],capital:"Moscow",currency:["RUB"],languages:["ru"]},RW:{name:"Rwanda",native:"Rwanda",phone:[250],continent:"AF",capital:"Kigali",currency:["RWF"],languages:["rw","en","fr"]},SA:{name:"Saudi Arabia",native:"\u0627\u0644\u0639\u0631\u0628\u064A\u0629 \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629",phone:[966],continent:"AS",capital:"Riyadh",currency:["SAR"],languages:["ar"]},SB:{name:"Solomon Islands",native:"Solomon Islands",phone:[677],continent:"OC",capital:"Honiara",currency:["SBD"],languages:["en"]},SC:{name:"Seychelles",native:"Seychelles",phone:[248],continent:"AF",capital:"Victoria",currency:["SCR"],languages:["fr","en"]},SD:{name:"Sudan",native:"\u0627\u0644\u0633\u0648\u062F\u0627\u0646",phone:[249],continent:"AF",capital:"Khartoum",currency:["SDG"],languages:["ar","en"]},SE:{name:"Sweden",native:"Sverige",phone:[46],continent:"EU",capital:"Stockholm",currency:["SEK"],languages:["sv"]},SG:{name:"Singapore",native:"Singapore",phone:[65],continent:"AS",capital:"Singapore",currency:["SGD"],languages:["en","ms","ta","zh"]},SH:{name:"Saint Helena",native:"Saint Helena",phone:[290],continent:"AF",capital:"Jamestown",currency:["SHP"],languages:["en"]},SI:{name:"Slovenia",native:"Slovenija",phone:[386],continent:"EU",capital:"Ljubljana",currency:["EUR"],languages:["sl"]},SJ:{name:"Svalbard and Jan Mayen",native:"Svalbard og Jan Mayen",phone:[4779],continent:"EU",capital:"Longyearbyen",currency:["NOK"],languages:["no"]},SK:{name:"Slovakia",native:"Slovensko",phone:[421],continent:"EU",capital:"Bratislava",currency:["EUR"],languages:["sk"]},SL:{name:"Sierra Leone",native:"Sierra Leone",phone:[232],continent:"AF",capital:"Freetown",currency:["SLL"],languages:["en"]},SM:{name:"San Marino",native:"San Marino",phone:[378],continent:"EU",capital:"City of San Marino",currency:["EUR"],languages:["it"]},SN:{name:"Senegal",native:"S\xE9n\xE9gal",phone:[221],continent:"AF",capital:"Dakar",currency:["XOF"],languages:["fr"]},SO:{name:"Somalia",native:"Soomaaliya",phone:[252],continent:"AF",capital:"Mogadishu",currency:["SOS"],languages:["so","ar"]},SR:{name:"Suriname",native:"Suriname",phone:[597],continent:"SA",capital:"Paramaribo",currency:["SRD"],languages:["nl"]},SS:{name:"South Sudan",native:"South Sudan",phone:[211],continent:"AF",capital:"Juba",currency:["SSP"],languages:["en"]},ST:{name:"Sao Tome and Principe",native:"S\xE3o Tom\xE9 e Pr\xEDncipe",phone:[239],continent:"AF",capital:"S\xE3o Tom\xE9",currency:["STN"],languages:["pt"]},SV:{name:"El Salvador",native:"El Salvador",phone:[503],continent:"NA",capital:"San Salvador",currency:["SVC","USD"],languages:["es"]},SX:{name:"Sint Maarten",native:"Sint Maarten",phone:[1721],continent:"NA",capital:"Philipsburg",currency:["ANG"],languages:["nl","en"]},SY:{name:"Syria",native:"\u0633\u0648\u0631\u064A\u0627",phone:[963],continent:"AS",capital:"Damascus",currency:["SYP"],languages:["ar"]},SZ:{name:"Eswatini",native:"Eswatini",phone:[268],continent:"AF",capital:"Lobamba",currency:["SZL"],languages:["en","ss"]},TC:{name:"Turks and Caicos Islands",native:"Turks and Caicos Islands",phone:[1649],continent:"NA",capital:"Cockburn Town",currency:["USD"],languages:["en"]},TD:{name:"Chad",native:"Tchad",phone:[235],continent:"AF",capital:"N'Djamena",currency:["XAF"],languages:["fr","ar"]},TF:{name:"French Southern Territories",native:"Territoire des Terres australes et antarctiques fr",phone:[262],continent:"AN",capital:"Port-aux-Fran\xE7ais",currency:["EUR"],languages:["fr"]},TG:{name:"Togo",native:"Togo",phone:[228],continent:"AF",capital:"Lom\xE9",currency:["XOF"],languages:["fr"]},TH:{name:"Thailand",native:"\u0E1B\u0E23\u0E30\u0E40\u0E17\u0E28\u0E44\u0E17\u0E22",phone:[66],continent:"AS",capital:"Bangkok",currency:["THB"],languages:["th"]},TJ:{name:"Tajikistan",native:"\u0422\u043E\u04B7\u0438\u043A\u0438\u0441\u0442\u043E\u043D",phone:[992],continent:"AS",capital:"Dushanbe",currency:["TJS"],languages:["tg","ru"]},TK:{name:"Tokelau",native:"Tokelau",phone:[690],continent:"OC",capital:"Fakaofo",currency:["NZD"],languages:["en"]},TL:{name:"East Timor",native:"Timor-Leste",phone:[670],continent:"OC",capital:"Dili",currency:["USD"],languages:["pt"]},TM:{name:"Turkmenistan",native:"T\xFCrkmenistan",phone:[993],continent:"AS",capital:"Ashgabat",currency:["TMT"],languages:["tk","ru"]},TN:{name:"Tunisia",native:"\u062A\u0648\u0646\u0633",phone:[216],continent:"AF",capital:"Tunis",currency:["TND"],languages:["ar"]},TO:{name:"Tonga",native:"Tonga",phone:[676],continent:"OC",capital:"Nuku'alofa",currency:["TOP"],languages:["en","to"]},TR:{name:"Turkey",native:"T\xFCrkiye",phone:[90],continent:"AS",continents:["AS","EU"],capital:"Ankara",currency:["TRY"],languages:["tr"]},TT:{name:"Trinidad and Tobago",native:"Trinidad and Tobago",phone:[1868],continent:"NA",capital:"Port of Spain",currency:["TTD"],languages:["en"]},TV:{name:"Tuvalu",native:"Tuvalu",phone:[688],continent:"OC",capital:"Funafuti",currency:["AUD"],languages:["en"]},TW:{name:"Taiwan",native:"\u81FA\u7063",phone:[886],continent:"AS",capital:"Taipei",currency:["TWD"],languages:["zh"]},TZ:{name:"Tanzania",native:"Tanzania",phone:[255],continent:"AF",capital:"Dodoma",currency:["TZS"],languages:["sw","en"]},UA:{name:"Ukraine",native:"\u0423\u043A\u0440\u0430\u0457\u043D\u0430",phone:[380],continent:"EU",capital:"Kyiv",currency:["UAH"],languages:["uk"]},UG:{name:"Uganda",native:"Uganda",phone:[256],continent:"AF",capital:"Kampala",currency:["UGX"],languages:["en","sw"]},UM:{name:"U.S. Minor Outlying Islands",native:"United States Minor Outlying Islands",phone:[1],continent:"OC",capital:"",currency:["USD"],languages:["en"]},US:{name:"United States",native:"United States",phone:[1],continent:"NA",capital:"Washington D.C.",currency:["USD","USN","USS"],languages:["en"]},UY:{name:"Uruguay",native:"Uruguay",phone:[598],continent:"SA",capital:"Montevideo",currency:["UYI","UYU"],languages:["es"]},UZ:{name:"Uzbekistan",native:"O'zbekiston",phone:[998],continent:"AS",capital:"Tashkent",currency:["UZS"],languages:["uz","ru"]},VA:{name:"Vatican City",native:"Vaticano",phone:[379],continent:"EU",capital:"Vatican City",currency:["EUR"],languages:["it","la"]},VC:{name:"Saint Vincent and the Grenadines",native:"Saint Vincent and the Grenadines",phone:[1784],continent:"NA",capital:"Kingstown",currency:["XCD"],languages:["en"]},VE:{name:"Venezuela",native:"Venezuela",phone:[58],continent:"SA",capital:"Caracas",currency:["VES"],languages:["es"]},VG:{name:"British Virgin Islands",native:"British Virgin Islands",phone:[1284],continent:"NA",capital:"Road Town",currency:["USD"],languages:["en"]},VI:{name:"U.S. Virgin Islands",native:"United States Virgin Islands",phone:[1340],continent:"NA",capital:"Charlotte Amalie",currency:["USD"],languages:["en"]},VN:{name:"Vietnam",native:"Vi\u1EC7t Nam",phone:[84],continent:"AS",capital:"Hanoi",currency:["VND"],languages:["vi"]},VU:{name:"Vanuatu",native:"Vanuatu",phone:[678],continent:"OC",capital:"Port Vila",currency:["VUV"],languages:["bi","en","fr"]},WF:{name:"Wallis and Futuna",native:"Wallis et Futuna",phone:[681],continent:"OC",capital:"Mata-Utu",currency:["XPF"],languages:["fr"]},WS:{name:"Samoa",native:"Samoa",phone:[685],continent:"OC",capital:"Apia",currency:["WST"],languages:["sm","en"]},XK:{name:"Kosovo",native:"Republika e Kosov\xEBs",phone:[377,381,383,386],continent:"EU",capital:"Pristina",currency:["EUR"],languages:["sq","sr"],userAssigned:!0},YE:{name:"Yemen",native:"\u0627\u0644\u064A\u064E\u0645\u064E\u0646",phone:[967],continent:"AS",capital:"Sana'a",currency:["YER"],languages:["ar"]},YT:{name:"Mayotte",native:"Mayotte",phone:[262],continent:"AF",capital:"Mamoudzou",currency:["EUR"],languages:["fr"]},ZA:{name:"South Africa",native:"South Africa",phone:[27],continent:"AF",capital:"Pretoria",currency:["ZAR"],languages:["af","en","nr","st","ss","tn","ts","ve","xh","zu"]},ZM:{name:"Zambia",native:"Zambia",phone:[260],continent:"AF",capital:"Lusaka",currency:["ZMW"],languages:["en"]},ZW:{name:"Zimbabwe",native:"Zimbabwe",phone:[263],continent:"AF",capital:"Harare",currency:["USD","ZAR","BWP","GBP","AUD","CNY","INR","JPY"],languages:["en","sn","nd"]}};var r={AD:"AND",AE:"ARE",AF:"AFG",AG:"ATG",AI:"AIA",AL:"ALB",AM:"ARM",AO:"AGO",AQ:"ATA",AR:"ARG",AS:"ASM",AT:"AUT",AU:"AUS",AW:"ABW",AX:"ALA",AZ:"AZE",BA:"BIH",BB:"BRB",BD:"BGD",BE:"BEL",BF:"BFA",BG:"BGR",BH:"BHR",BI:"BDI",BJ:"BEN",BL:"BLM",BM:"BMU",BN:"BRN",BO:"BOL",BQ:"BES",BR:"BRA",BS:"BHS",BT:"BTN",BV:"BVT",BW:"BWA",BY:"BLR",BZ:"BLZ",CA:"CAN",CC:"CCK",CD:"COD",CF:"CAF",CG:"COG",CH:"CHE",CI:"CIV",CK:"COK",CL:"CHL",CM:"CMR",CN:"CHN",CO:"COL",CR:"CRI",CU:"CUB",CV:"CPV",CW:"CUW",CX:"CXR",CY:"CYP",CZ:"CZE",DE:"DEU",DJ:"DJI",DK:"DNK",DM:"DMA",DO:"DOM",DZ:"DZA",EC:"ECU",EE:"EST",EG:"EGY",EH:"ESH",ER:"ERI",ES:"ESP",ET:"ETH",FI:"FIN",FJ:"FJI",FK:"FLK",FM:"FSM",FO:"FRO",FR:"FRA",GA:"GAB",GB:"GBR",GD:"GRD",GE:"GEO",GF:"GUF",GG:"GGY",GH:"GHA",GI:"GIB",GL:"GRL",GM:"GMB",GN:"GIN",GP:"GLP",GQ:"GNQ",GR:"GRC",GS:"SGS",GT:"GTM",GU:"GUM",GW:"GNB",GY:"GUY",HK:"HKG",HM:"HMD",HN:"HND",HR:"HRV",HT:"HTI",HU:"HUN",ID:"IDN",IE:"IRL",IL:"ISR",IM:"IMN",IN:"IND",IO:"IOT",IQ:"IRQ",IR:"IRN",IS:"ISL",IT:"ITA",JE:"JEY",JM:"JAM",JO:"JOR",JP:"JPN",KE:"KEN",KG:"KGZ",KH:"KHM",KI:"KIR",KM:"COM",KN:"KNA",KP:"PRK",KR:"KOR",KW:"KWT",KY:"CYM",KZ:"KAZ",LA:"LAO",LB:"LBN",LC:"LCA",LI:"LIE",LK:"LKA",LR:"LBR",LS:"LSO",LT:"LTU",LU:"LUX",LV:"LVA",LY:"LBY",MA:"MAR",MC:"MCO",MD:"MDA",ME:"MNE",MF:"MAF",MG:"MDG",MH:"MHL",MK:"MKD",ML:"MLI",MM:"MMR",MN:"MNG",MO:"MAC",MP:"MNP",MQ:"MTQ",MR:"MRT",MS:"MSR",MT:"MLT",MU:"MUS",MV:"MDV",MW:"MWI",MX:"MEX",MY:"MYS",MZ:"MOZ",NA:"NAM",NC:"NCL",NE:"NER",NF:"NFK",NG:"NGA",NI:"NIC",NL:"NLD",NO:"NOR",NP:"NPL",NR:"NRU",NU:"NIU",NZ:"NZL",OM:"OMN",PA:"PAN",PE:"PER",PF:"PYF",PG:"PNG",PH:"PHL",PK:"PAK",PL:"POL",PM:"SPM",PN:"PCN",PR:"PRI",PS:"PSE",PT:"PRT",PW:"PLW",PY:"PRY",QA:"QAT",RE:"REU",RO:"ROU",RS:"SRB",RU:"RUS",RW:"RWA",SA:"SAU",SB:"SLB",SC:"SYC",SD:"SDN",SE:"SWE",SG:"SGP",SH:"SHN",SI:"SVN",SJ:"SJM",SK:"SVK",SL:"SLE",SM:"SMR",SN:"SEN",SO:"SOM",SR:"SUR",SS:"SSD",ST:"STP",SV:"SLV",SX:"SXM",SY:"SYR",SZ:"SWZ",TC:"TCA",TD:"TCD",TF:"ATF",TG:"TGO",TH:"THA",TJ:"TJK",TK:"TKL",TL:"TLS",TM:"TKM",TN:"TUN",TO:"TON",TR:"TUR",TT:"TTO",TV:"TUV",TW:"TWN",TZ:"TZA",UA:"UKR",UG:"UGA",UM:"UMI",US:"USA",UY:"URY",UZ:"UZB",VA:"VAT",VC:"VCT",VE:"VEN",VG:"VGB",VI:"VIR",VN:"VNM",VU:"VUT",WF:"WLF",WS:"WSM",XK:"XKX",YE:"YEM",YT:"MYT",ZA:"ZAF",ZM:"ZMB",ZW:"ZWE"};var c=n=>({...a[n],iso2:n,iso3:r[n]}),t=()=>Object.keys(a).map(n=>c(n));t(); - -var browserExtra = async(ip) => { - const geodata = await ip_lookup(ip); - if(geodata && a[geodata.country]){ - const h = a[geodata.country]; - geodata.country_name = h.name; - geodata.country_native = h.native; - geodata.continent = h.continent; - geodata.capital = h.capital; - geodata.phone = h.phone; - geodata.currency = h.currency; - geodata.languages = h.languages; - } - return geodata -}; - -export { browserExtra as default }; diff --git a/browser/geocode-extra/package.json b/browser/geocode-extra/package.json deleted file mode 100644 index e9e5ff1..0000000 --- a/browser/geocode-extra/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@iplookup/geocode-extra", - "version": "1.0.20241103", - "description": "Browser api to lookup geocode from IP address", - "keywords": [ - "location", - "iplookup", - "lookup", - "geo", - "geocode", - "geoip", - "ip", - "ipv4", - "ipv6", - "ip-location-db", - "country" - ], - "author": "sapics", - "homepage": "https://github.com/sapics/ip-location-api", - "repository": { - "type": "git", - "url": "git://github.com/sapics/ip-location-api.git" - }, - "publishConfig": { - "access": "public" - }, - "license": "CC BY 4.0 and MIT", - "main": "iplookup.cjs", - "exports": { - "import": "./iplookup.mjs", - "require": "./iplookup.cjs" - } -} \ No newline at end of file diff --git a/browser/geocode-offline.html b/browser/geocode-offline.html deleted file mode 100644 index c7f50c6..0000000 --- a/browser/geocode-offline.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - TEST - - - -

TEST

- - -

EXPORT INFORMATION

-
- - - diff --git a/browser/geocode-online.html b/browser/geocode-online.html deleted file mode 100644 index 42703f7..0000000 --- a/browser/geocode-online.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - TEST - - - -

TEST

- - -

EXPORT INFORMATION

-
- - - diff --git a/browser/geocode/README.md b/browser/geocode/README.md deleted file mode 100644 index 9d9a220..0000000 --- a/browser/geocode/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# @iplookup/geocode [![npm version](https://img.shields.io/npm/v/@iplookup/geocode?color=success&style=flat-square&label=npm)](https://www.npmjs.com/package/@iplookup/geocode) - -This is an API created to make [ip-location-api](https://github.com/sapics/ip-location-api) available for browsers. -The database itself is large at 132MB, so it is splitted into over 4000 pieces for fast downloading in a browser. - - -## Synopsis - -```html - - -``` - -#### ESM - -```javascript -import IpLookup from '@iplookup/geocode' -await IpLookup("2402:b801:ea8b:23c0::") -``` - -#### CJS - -```javascript -const IpLookup = require('@iplookup/geocode') -await IpLookup("207.97.227.239") -``` - -If you need extra information about country, try to use [@iplookup/geocode-extra](https://github.com/sapics/ip-location-api/tree/main/browser/geocode-extra). - - -## License - -The database is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by [DB-IP](https://db-ip.com/db/download/ip-to-city-lite). - -The software itself is published under MIT license by [sapics](https://github.com/sapics). \ No newline at end of file diff --git a/browser/geocode/iplookup.cjs b/browser/geocode/iplookup.cjs deleted file mode 100644 index 93cea7c..0000000 --- a/browser/geocode/iplookup.cjs +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -}; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 8; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null -}; - -module.exports = browser; diff --git a/browser/geocode/iplookup.js b/browser/geocode/iplookup.js deleted file mode 100644 index 428991c..0000000 --- a/browser/geocode/iplookup.js +++ /dev/null @@ -1,155 +0,0 @@ -var IpLookup = (function () { - 'use strict'; - - const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) - }; - - const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 - }; - - const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r - }; - - const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num - }; - const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) - }; - - const TOP_URL = document.currentScript.src.split('/').slice(0, -1).join('/') + '/'; - const MAIN_RECORD_SIZE = 8; - const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - - const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) - }; - - const downloadIdx = downloadArrayBuffer; - - const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; - const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ - // console.log('ipv6 file cannot download') - return - } - return Idx[6] = new BigUint64Array(buf) - }) - }; - - var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { - // console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { - // console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null - }; - - return browser; - -})(); diff --git a/browser/geocode/iplookup.min.js b/browser/geocode/iplookup.min.js deleted file mode 100644 index b180cee..0000000 --- a/browser/geocode/iplookup.min.js +++ /dev/null @@ -1 +0,0 @@ -var IpLookup=function(){"use strict";const n=n=>((n=n.split(/\./))[0]<<24|n[1]<<16|n[2]<<8|n[3])>>>0,t=document.currentScript.src.split("/").slice(0,-1).join("/")+"/",r=async(n,t=3)=>fetch(n,{cache:"no-cache"}).then((async e=>{return e.ok?e.arrayBuffer():404===e.status?null:t?(await(i=100*(4-t)*(4-t),new Promise((n=>setTimeout(n,i)))),r(n,t-1)):null;var i})),e=r,i={},l={4:t,6:t},s={4:e(t+"4.idx").then((n=>{if(n)return i[4]=new Uint32Array(n)})),6:e(t+"4.idx").then((n=>{if(n)return i[6]=new BigUint64Array(n)}))};return async t=>{var e,u,c=!0;t.includes(":")?6===(u=(e=(t=>{if(t.includes("."))return n(t.split(":").pop());var r,e=0n;if((t=t.split(/:/)).length-1<7){const n=t.indexOf("");if(n<4){const e=8-t.length,i=n+e;for(r=7;r>=n;r--)t[r]=r>i?t[r-e]:0}}for(r=0;r<4;r++)t[r]&&(e+=BigInt(parseInt(t[r],16))<=a[0]))return null;for(var o,f=0,g=a.length-1;;)if(e>1]){if(g-f<2)return null;g=o-1}else{if(f===o){g>o&&e>=a[g]&&(o=g);break}f=o}const h=((n,t)=>n.length>t?n:"_".repeat(t-n.length)+n)(o.toString(36),2);const y=await r(l[u]+u+"/"+h);if(!y)return null;const p=2*(u-2),w=8+2*p,d=y.byteLength/w,A=c?new Uint32Array(y.slice(0,4*d)):new BigUint64Array(y.slice(0,8*d));for(f=0,g=d-1;;)if(e>1]){if(g-f<2)return null;g=o-1}else{if(f===o){g>o&&e>=A[g]&&(o=g);break}f=o}const B=c?new Uint32Array(y.slice((d+o)*p,(d+o+1)*p))[0]:new BigUint64Array(y.slice((d+o)*p,(d+o+1)*p))[0];if(e>=A[o]&&e<=B){const n=new Int32Array(y.slice(d*p*2+8*o,d*p*2+8*(o+1))),t=(n=>String.fromCharCode(65+(n/26|0),n%26+65))(1023&n[0]);return{latitude:(n[0]>>10)/1e4,longitude:n[1]/1e4,country:t}}return null}}(); diff --git a/browser/geocode/iplookup.mjs b/browser/geocode/iplookup.mjs deleted file mode 100644 index b38d526..0000000 --- a/browser/geocode/iplookup.mjs +++ /dev/null @@ -1,172 +0,0 @@ -const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -}; - -const aton4 = (a) => { - a = a.split(/\./); - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -}; - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/); - const l = a.length - 1; - var i, r = 0n; - if (l < 7) { - const omitStart = a.indexOf(''); - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted; - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0; - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)); - } - return r -}; - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -}; -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -}; - -const TOP_URL = "https://cdn.jsdelivr.net/npm/@iplookup/country/"; -const MAIN_RECORD_SIZE = 8; -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -}; - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)); - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -}; - -const downloadIdx = downloadVersionArrayBuffer ; - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL}; -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/'); - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -}; - -var browser = async (ipString) => { - var ip, version, isv4 = true; - if(ipString.includes(':')) { - ip = aton6Start(ipString); - version = ip.constructor === BigInt ? 6 : 4; - if(version === 6) isv4 = false; - } else { - ip = aton4(ipString); - version = 4; - } - - const ipIndexes = Idx[version] || (await Preload[version]); - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line; - for(;;){ - line = (fline + cline) >> 1; - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - - const fileName = numberToDir(line); - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName); - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2; - const recordSize = MAIN_RECORD_SIZE + ipSize * 2; - const recordCount = dataBuffer.byteLength / recordSize; - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)); - fline = 0, cline = recordCount - 1; - for(;;){ - line = fline + cline >> 1; - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1; - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline; - } - break; - } - fline = line; - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0]; - if(ip >= startList[line] && ip <= endIp){ - { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)); - const ccCode = numToCountryCode(arr[0] & 1023); - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null -}; - -export { browser as default }; diff --git a/browser/geocode/package.json b/browser/geocode/package.json deleted file mode 100644 index 968a211..0000000 --- a/browser/geocode/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@iplookup/geocode", - "version": "1.0.20241103", - "description": "Browser api to lookup geocode from IP address", - "keywords": [ - "location", - "iplookup", - "lookup", - "geo", - "geocode", - "geoip", - "ip", - "ipv4", - "ipv6", - "ip-location-db", - "country" - ], - "author": "sapics", - "homepage": "https://github.com/sapics/ip-location-api", - "repository": { - "type": "git", - "url": "git://github.com/sapics/ip-location-api.git" - }, - "publishConfig": { - "access": "public" - }, - "license": "CC BY 4.0 and MIT", - "main": "iplookup.cjs", - "exports": { - "import": "./iplookup.mjs", - "require": "./iplookup.cjs" - } -} \ No newline at end of file diff --git a/cjs/db.cjs b/cjs/db.cjs deleted file mode 100644 index cc65e37..0000000 --- a/cjs/db.cjs +++ /dev/null @@ -1,853 +0,0 @@ - -const fs = require('fs/promises') -const fsSync = require('fs') -const path = require('path') -const { fileURLToPath } = require('url') -const { createHash } = require('crypto') -const { pipeline } = require('stream/promises') - -const axios = require('axios') -const { parse } = require('@fast-csv/parse') -const { Address4, Address6 } = require('ip-address') -const dayjs = require('dayjs') - -const { setting, consoleLog, consoleWarn } = require('./setting.cjs') -const { getPostcodeDatabase, strToNum37, aton4, aton6, getSmallMemoryFile, numberToDir, countryCodeToNum } = require('./utils.cjs') - - -const rimraf = (dir) => { - if(fs.rm){ - return fs.rm(dir, {recursive: true, force: true, maxRetries: 3}) - } - return fs.rmdir(dir, {recursive: true, maxRetries: 3}) -} - - - -const DownloadServer = 'https://download.maxmind.com/app/geoip_download' -const yauzl = require('yauzl') -const update = async () => { - var srcList, refreshTmpDir = setting.downloadType !== 'reuse' - if(refreshTmpDir || !fsSync.existsSync(setting.tmpDataDir)){ - - await rimraf(setting.tmpDataDir) - await fs.mkdir(setting.tmpDataDir, {recursive: true}) - } - if (!fsSync.existsSync(setting.fieldDir)){ - await fs.mkdir(setting.fieldDir, {recursive: true}) - } - - consoleLog('Downloading database') - if(setting.browserType === 'geocode'){ - await dbipLocation() - return createBrowserIndex(setting.browserType) - } - - if(setting.ipLocationDb){ - srcList = await ipLocationDb(setting.ipLocationDb.replace(/-country$/, '')) - } else { - srcList = await downloadZip() - } - - if(!srcList){ - return console.log('ERROR TO UPDATE') - } - consoleLog(srcList) - if(srcList === 'NO NEED TO UPDATE') { - return - } - - consoleLog('Creating database for ip-location-api') - await createData(srcList) - consoleLog('Database update completed!!') - - if(refreshTmpDir){ - await rimraf(setting.tmpDataDir, {recursive: true, force: true}) - } - if(SHA256_RESULT){ - - await fs.writeFile(path.join(setting.fieldDir, setting.series + '-' + setting.dataType + '-CSV.zip.sha256'), SHA256_RESULT) - } - - var tmpFiles = fsSync.readdirSync(setting.fieldDir).filter(file => file.endsWith('.tmp')) - for(var tmpFile of tmpFiles){ - await fs.rename(path.join(setting.fieldDir, tmpFile), path.join(setting.fieldDir, tmpFile.replace('.tmp', ''))) - } - if(setting.smallMemory && !setting.runningUpdate){ - await fs.cp(path.join(setting.fieldDir, 'v4-tmp'), path.join(setting.fieldDir, 'v4'), {recursive: true, force: true}) - await fs.cp(path.join(setting.fieldDir, 'v6-tmp'), path.join(setting.fieldDir, 'v6'), {recursive: true, force: true}) - rimraf(path.join(setting.fieldDir, 'v4-tmp')).catch(consoleWarn) - rimraf(path.join(setting.fieldDir, 'v6-tmp')).catch(consoleWarn) - } - - if(setting.browserType){ - await createBrowserIndex(setting.browserType) - } - - console.log('SUCCESS TO UPDATE') -} - -const ipLocationDb = async (db) => { - var preUrl = 'https://cdn.jsdelivr.net/npm/@ip-location-db/'+db+'-country/'+db+'-country' - var urls = [preUrl+'-ipv4.csv', preUrl+'-ipv6.csv'], fileNames = [] - for(var url of urls){ - fileNames.push(await _ipLocationDb(url)) - } - return fileNames -} - -const _ipLocationDb = async (url) => { - var fileEnd = url.split('-').pop() - return axios({ - method: 'get', - url: url, - responseType: 'stream' - }).then(res => { - return new Promise((resolve, reject) => { - var fileName = setting.ipLocationDb + '-Blocks-' + fileEnd - const ws = fsSync.createWriteStream(path.join(setting.tmpDataDir, fileName)) - ws.write('network1,network2,cc\n') - res.data.pipe(ws) - ws.on('finish', () => { - resolve(fileName) - }) - ws.on('error', reject) - }) - }) -} - -const dbipLocation = async () => { - const address = "https://download.db-ip.com/free/dbip-city-lite-" + dayjs().format('YYYY-MM') + ".csv.gz" - const res = await fetch(address) - const tmpFile = path.join(setting.tmpDataDir, 'dbip-city-lite.csv') - const ws = fsSync.createWriteStream(tmpFile) - await pipeline(res.body.pipeThrough(new DecompressionStream('gzip')), ws) - return new Promise((resolve, reject) => { - const v4 = [], v6 = [] - var preData - fsSync.createReadStream(tmpFile).pipe(parse()) - .on('error', reject) - .on('end', () => { - var v4Buf1 = Buffer.alloc(v4.length * 4) - var v4Buf2 = Buffer.alloc(v4.length * 4) - var v4Buf3 = Buffer.alloc(v4.length * 8) - for(var i = 0; i < v4.length; ++i){ - v4Buf1.writeUInt32LE(v4[i][0], i * 4) - v4Buf2.writeUInt32LE(v4[i][1], i * 4) - v4Buf3.writeInt32LE(v4[i][2], i * 8) - v4Buf3.writeInt32LE(v4[i][3], i * 8 + 4) - } - fsSync.writeFileSync(path.join(setting.fieldDir, '4-1.dat'), v4Buf1) - fsSync.writeFileSync(path.join(setting.fieldDir, '4-2.dat'), v4Buf2) - fsSync.writeFileSync(path.join(setting.fieldDir, '4-3.dat'), v4Buf3) - - var v6Buf1 = Buffer.alloc(v6.length * 8) - var v6Buf2 = Buffer.alloc(v6.length * 8) - var v6Buf3 = Buffer.alloc(v6.length * 8) - for(var i = 0; i < v6.length; ++i){ - v6Buf1.writeBigUInt64LE(v6[i][0], i * 8) - v6Buf2.writeBigUInt64LE(v6[i][1], i * 8) - v6Buf3.writeInt32LE(v6[i][2], i * 8) - v6Buf3.writeInt32LE(v6[i][3], i * 8 + 4) - } - fsSync.writeFileSync(path.join(setting.fieldDir, '6-1.dat'), v6Buf1) - fsSync.writeFileSync(path.join(setting.fieldDir, '6-2.dat'), v6Buf2) - fsSync.writeFileSync(path.join(setting.fieldDir, '6-3.dat'), v6Buf3) - resolve() - }) - .on('data', arr => { - if(!arr[2] || arr[3] === 'ZZ' || arr[3] === 'EU') return; - var latitude = Math.round((parseFloat(arr[6])) * 10000) // -90 ~ 90 -> 10 ~ 190 - - var longitude = Math.round((parseFloat(arr[7])) * 10000)// -180 ~ 180 -> 20 ~ 220 - - var countryCodeNum = countryCodeToNum(arr[3]) // 0 ~ 675 - latitude = (latitude) << 10 | countryCodeNum - if(arr[0].includes(':')){ - var start = aton6(arr[0]) - if(preData[1].constructor !== BigInt) preData = null - if(preData && preData[1] + 1n === start && preData[2] === latitude && preData[3] === longitude){ - preData[1] = aton6(arr[1]) - return - } - v6.push(preData = [aton6(arr[0]), aton6(arr[1]), latitude, longitude]) - } else { - var start = aton4(arr[0]) - if(preData && preData[1] + 1 === start && preData[2] === latitude && preData[3] === longitude){ - preData[1] = aton4(arr[1]) - return - } - v4.push(preData = [aton4(arr[0]), aton4(arr[1]), latitude, longitude]) - } - }) - }) -} - -const createBrowserIndex = async (type) => { - const exportDir = path.join(setting.fieldDir, type) - await fs.rm(path.join(exportDir, '4'), {recursive: true, force: true}) - await fs.mkdir(path.join(exportDir, '4'), {recursive: true}) - await fs.rm(path.join(exportDir, '6'), {recursive: true, force: true}) - await fs.mkdir(path.join(exportDir, '6'), {recursive: true}) - - const IndexSize = type === 'country' ? 1024 : 2048 - - var startBuf = await fs.readFile(path.join(setting.fieldDir, '4-1.dat')) - var startList = new Uint32Array(startBuf.buffer) - var len = startList.length, indexList = new Uint32Array(IndexSize) - var i, j, k - var endBuf = await fs.readFile(path.join(setting.fieldDir, '4-2.dat')) - var endList = new Uint32Array(endBuf.buffer) - var dbInfo = await fs.readFile(path.join(setting.fieldDir, '4-3.dat')) - var dbList = type === 'country' ? new Uint16Array(dbInfo.buffer) : new Int32Array(dbInfo.buffer) - var recordSize = setting.mainRecordSize + 8 - for(i = 0; i < IndexSize; ++i){ - var index = len * i / IndexSize | 0 - indexList[i] = startList[index] - var nextIndex = len * (i + 1) / IndexSize | 0 - var count = nextIndex - index - var exportBuf = Buffer.alloc(recordSize * count) - for(j = index, k = 0; j < nextIndex; ++j){ - exportBuf.writeUInt32LE(startList[j], k * 4) - exportBuf.writeUInt32LE(endList[j], 4 * count + k * 4) - if(type === 'country'){ - exportBuf.writeUInt16LE(dbList[j], 8 * count + k * setting.mainRecordSize) - } else { - exportBuf.writeInt32LE(dbList[2*j], 8 * count + k * setting.mainRecordSize) - exportBuf.writeInt32LE(dbList[2*j+1], 8 * count + k * setting.mainRecordSize + 4) - } - ++k - } - await fs.writeFile(path.join(exportDir, '4', numberToDir(i)), exportBuf) - } - await fs.writeFile(path.join(exportDir, '4.idx'), Buffer.from(indexList.buffer)) - - startBuf = await fs.readFile(path.join(setting.fieldDir, '6-1.dat')) - startList = new BigUint64Array(startBuf.buffer) - len = startList.length - indexList = new BigUint64Array(IndexSize) - endBuf = await fs.readFile(path.join(setting.fieldDir, '6-2.dat')) - endList = new BigUint64Array(endBuf.buffer) - dbInfo = await fs.readFile(path.join(setting.fieldDir, '6-3.dat')) - dbList = type === 'country' ? new Uint16Array(dbInfo.buffer) : new Int32Array(dbInfo.buffer) - recordSize = setting.mainRecordSize + 16 - for(i = 0; i < IndexSize; ++i){ - var index = len * i / IndexSize | 0 - indexList[i] = startList[index] - var nextIndex = len * (i + 1) / IndexSize | 0 - var exportBuf = Buffer.alloc(recordSize * (nextIndex - index)) - var count = nextIndex - index - for(j = index, k = 0; j < nextIndex; ++j){ - exportBuf.writeBigUInt64LE(startList[j], k * 8) - exportBuf.writeBigUInt64LE(endList[j], 8 * count + k * 8) - if(type === 'country'){ - exportBuf.writeUInt16LE(dbList[j], 16 * count + k * setting.mainRecordSize) - } else { - exportBuf.writeInt32LE(dbList[2*j], 16 * count + k * setting.mainRecordSize) - exportBuf.writeInt32LE(dbList[2*j+1], 16 * count + k * setting.mainRecordSize + 4) - } - ++k - } - await fs.writeFile(path.join(exportDir, '6', numberToDir(i)), exportBuf) - } - await fs.writeFile(path.join(exportDir, '6.idx'), Buffer.from(indexList.buffer)) - - var exPath = path.join(__dirname, '..', 'browser', type) - await fs.rm(path.join(exPath, '4'), {recursive: true, force: true}) - await fs.rm(path.join(exPath, '6'), {recursive: true, force: true}) - await fs.cp(exportDir, exPath, {recursive: true}) - exPath = path.join(__dirname, '..', 'browser', type + '-extra') - await fs.rm(path.join(exPath, '4'), {recursive: true, force: true}) - await fs.rm(path.join(exPath, '6'), {recursive: true, force: true}) - await fs.cp(exportDir, exPath, {recursive: true}) - await fs.rm(exportDir, {recursive: true, force: true}) -} - -var SHA256_RESULT -const downloadZip = async () => { - SHA256_RESULT = false - var name = setting.dataType[0].toUpperCase() + setting.dataType.slice(1) - const database = { - type: setting.dataType, - edition: setting.series + '-' + name + '-CSV', - suffix: 'zip.sha256', - src: [ - setting.series + '-' + name + '-Locations-en.csv', - setting.series + '-' + name + '-Blocks-IPv4.csv', - setting.series + '-' + name + '-Blocks-IPv6.csv' - ], - } - if(setting.language !== 'en' && setting.isCity){ - database.src.push(setting.series + '-' + name + '-Locations-' + setting.language + '.csv') - } - if(!setting.licenseKey) { - return consoleWarn('Please set your license key') - } - var url = DownloadServer + '?edition_id=' + database.edition + '&suffix=' + database.suffix + "&license_key=" + setting.licenseKey - if(setting.licenseKey === 'redist'){ - url = 'https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/' - url += database.edition + '.' + database.suffix - } - var text = await axios.get(url) - var reg = /\w{50,}/, r = reg.exec(text.data) - if(!r) { - return consoleWarn('Cannot download sha256') - } - var sha256 = r[0], data = '' - try{ - data = await fs.readFile(path.join(setting.fieldDir, database.edition + '.zip.sha256'), 'utf8') - }catch(e){ - data = '' - } - - const zipPath = path.join(setting.tmpDataDir, database.edition + '.zip') - if(data === sha256){ - if(fsSync.existsSync(zipPath)){ - const zipHash = await sha256Hash(zipPath) - if(zipHash === sha256){ - if(!setting.multiDbDir) { - if(setting.sameDbSetting) return 'NO NEED TO UPDATE' - if(setting.language === 'en') return database.src - } - } - } else if(!setting.multiDbDir){ - if(setting.sameDbSetting) return 'NO NEED TO UPDATE' - } - } - - SHA256_RESULT = sha256 - url = DownloadServer + '?edition_id=' + database.edition + '&suffix=' + database.suffix.replace('.sha256', '') + "&license_key=" + setting.licenseKey - if(setting.licenseKey === 'redist'){ - url = 'https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/' - url += database.edition + '.' + database.suffix.replace('.sha256', '') - } - return axios({ - method: 'get', - url: url, - responseType: 'stream' - }).then(res => { - const dest = fsSync.createWriteStream(zipPath) - return new Promise((resolve, reject) => { - consoleLog('Decompressing', database.edition + '.zip') - res.data.pipe(dest) - res.data.on('end', () => { - yauzl.open(zipPath, {lazyEntries: true}, (err, zipfile) => { - if(err) return reject(err) - zipfile.readEntry() - zipfile.on('entry', entry => { - for(var src of database.src){ - if(!entry.fileName.endsWith(src)) continue; - consoleLog('Extracting', entry.fileName) - return (function(src){ - zipfile.openReadStream(entry, (err, readStream) => { - if(err) return reject(err) - readStream.pipe(fsSync.createWriteStream(path.join(setting.tmpDataDir, src))) - readStream.on('end', () => { - zipfile.readEntry() - }) - }) - })(src) - } - zipfile.readEntry() - }) - zipfile.on('end', () => resolve(database.src)) - }) - }) - res.data.on('error', reject) - }) - }) -} - -const sha256Hash = async (file) => { - return new Promise((resolve, reject) => { - const stream = fsSync.createReadStream(file) - const hash = createHash('sha256') - hash.once('finish', () => resolve(hash.digest('hex'))) - stream.on('error', reject) - stream.pipe(hash) - }) -} - -const createData = async (src) => { - var mapDatas = [] - var locationSrc = src.filter(file => file.includes('Locations')) - locationSrc.sort((a,b) => { - - if(a.endsWith('-en.csv')) return -1 - if(b.endsWith('-en.csv')) return 1 - }) - for(var file of locationSrc){ - mapDatas.push(await getMapData(file)) - } - if(setting.locFile){ - minifyMapData(mapDatas) - } - var blockSrc = src.filter(file => file.includes('Blocks')) - mapDatas.push([]) - for(var file of blockSrc){ - await createMainData(file, mapDatas) - } - if(setting.locFile){ - await createMapData(mapDatas) - } -} - - -const createSmallMemoryFile = (ws, ipv4, line, buffer2, buffer3) => { - const [ _dir, file, offset ] = getSmallMemoryFile(line, ipv4 ? setting.v4 : setting.v6, true) - if(offset === 0){ - const dir = path.join(setting.fieldDir, _dir) - if(ws) ws.end() - if(file === '_0' && !fsSync.existsSync(dir)){ - fsSync.mkdirSync(dir, {recursive: true}) - } - if(setting.smallMemoryFileSize <= buffer2.length + buffer3.length){ - var buf = Buffer.alloc(buffer2.length + buffer3.length) - buffer2.copy(buf) - buffer3.copy(buf, buffer2.length) - fsSync.writeFile(path.join(dir, file), buf, () => {}) - return - } - ws = fsSync.createWriteStream(path.join(dir, file)) - } - ws.write(buffer2) - ws.write(buffer3) - return ws -} - -const createMainData = async (file, mapDatas) => { - var ipv4 = file.endsWith('v4.csv') - var ipv = ipv4 ? 4 : 6 - var rs = fsSync.createReadStream(path.join(setting.tmpDataDir, file)) - var ws1 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-1.dat.tmp'), {highWaterMark: 1024*1024}) - if(!setting.smallMemory){ - var ws2 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-2.dat.tmp'), {highWaterMark: 1024*1024}) - var ws3 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-3.dat.tmp'), {highWaterMark: 1024*1024}) - } else { - var ws = null - var dir = path.join(setting.fieldDir, 'v' + ipv + '-tmp') - if(fsSync.existsSync(dir)){ - await fs.rm(dir, {recursive: true, force: true}) - } - } - - var preBuffer1, preBuffer2, preBuffer3, preCC, preEnd - var preLocId, preLatitude, preLongitude, preArea, prePostcode - - var preLocLocation - var mapData0 = mapDatas[0], locIdList = mapDatas[mapDatas.length - 1] - var lineCount = 0 - areaDatabase = {}, areaCount = 0 - - return new Promise((resolve, reject) => { - var checkCount = 0 - function check(){ - if(++checkCount === 3)resolve() - } - rs.pipe(parse({headers: true})) - .on('error', reject) - .on('data', row => { - var cc, buffer1, buffer2, buffer3, addr, start, end - if(setting.ipLocationDb){ - if(ipv4){ - start = aton4(row.network1) - end = aton4(row.network2) - } else { - start = aton6(row.network1) - end = aton6(row.network2) - } - } else { - if(ipv4){ - addr = new Address4(row.network) - start = aton4(addr.startAddress().correctForm()) - end = aton4(addr.endAddress().correctForm()) - } else { - addr = new Address6(row.network) - start = aton6(addr.startAddress().correctForm()) - end = aton6(addr.endAddress().correctForm()) - } - } - - if(setting.isCountry){ - if(setting.ipLocationDb){ - cc = row.cc - } else { - cc = mapData0[row.geoname_id] - } - if(!cc || cc.length !== 2) { - return;// console.warn('Invalid country code', cc, row.geoname_id) - } - if(cc === preCC && (ipv4 && preEnd + 1 === start || !ipv4 && preEnd + 1n === start)){ - if(ipv4){ - preBuffer2.writeUInt32LE(end) - } else { - preBuffer2.writeBigUInt64LE(end) - } - } else { - if(ipv4){ - buffer1 = Buffer.allocUnsafe(4) - buffer1.writeUInt32LE(start) - buffer2 = Buffer.allocUnsafe(4) - buffer2.writeUInt32LE(end) - } else { - buffer1 = Buffer.allocUnsafe(8) - buffer1.writeBigUInt64LE(start) - buffer2 = Buffer.allocUnsafe(8) - buffer2.writeBigUInt64LE(end) - } - buffer3 = Buffer.allocUnsafe(2) - buffer3.write(cc) - if(preBuffer1){ - if(!ws1.write(preBuffer1)) rs.pause() - if (setting.smallMemory) { - ws = createSmallMemoryFile(ws, ipv4, lineCount++, preBuffer2, preBuffer3) - } else { - if(!ws2.write(preBuffer2)) rs.pause() - if(!ws3.write(preBuffer3)) rs.pause() - } - } - preCC = cc - preBuffer1 = buffer1 - preBuffer2 = buffer2 - preBuffer3 = buffer3 - } - } else { - var locId = row.geoname_id - var latitude = Math.round(row.latitude * 10000) - var longitude = Math.round(row.longitude * 10000) - var area = row.accuracy_radius - var postcode = row.postal_code - - - var locLocation = mapData0[locId] && mapData0[locId].counter - - var isSame = true - if(setting.mainFieldHash.latitude && preLatitude !== latitude) isSame = false - if(setting.mainFieldHash.longitude && preLongitude !== longitude) isSame = false - if(setting.mainFieldHash.area && preArea !== area) isSame = false - if(setting.mainFieldHash.postcode && prePostcode !== postcode) isSame = false - - if( (locId === preLocId || locLocation > 0 && locLocation === preLocLocation || setting.noLocFile) - && isSame - - && (ipv4 && preEnd + 1 === start || !ipv4 && preEnd + 1n === start)){ - if(ipv4){ - preBuffer2.writeUInt32LE(parseInt(end, 10)) - } else { - preBuffer2.writeBigUInt64LE(end) - } - } else { - if(!locId){ - return; - - - - - - } - if(locId && !mapData0[locId]) { - return consoleWarn('Invalid location id', locId) - } - if(locId){ - if(!mapData0[locId].counter){ - locIdList.push(locId) - locLocation = mapData0[locId].counter = locIdList.length - } - } - if(preBuffer1){ - if(!ws1.write(preBuffer1)) rs.pause() - if(setting.smallMemory){ - ws = createSmallMemoryFile(ws, ipv4, lineCount++, preBuffer2, preBuffer3) - } else { - if(!ws2.write(preBuffer2)) rs.pause() - if(!ws3.write(preBuffer3)) rs.pause() - } - } - if(ipv4){ - buffer1 = Buffer.allocUnsafe(4) - buffer1.writeUInt32LE(parseInt(start, 10)) - buffer2 = Buffer.allocUnsafe(4) - buffer2.writeUInt32LE(parseInt(end, 10)) - } else { - buffer1 = Buffer.allocUnsafe(8) - buffer1.writeBigUInt64LE(start) - buffer2 = Buffer.allocUnsafe(8) - buffer2.writeBigUInt64LE(end) - } - - buffer3 = Buffer.alloc(setting.mainRecordSize) - - var offset = 0 - if(setting.locFile){ - buffer3.writeUInt32LE(mapData0[locId].counter) - offset += 4 - } - if(setting.mainFieldHash.latitude) { - buffer3.writeInt32LE(latitude, offset) - offset += 4 - } - if(setting.mainFieldHash.longitude) { - buffer3.writeInt32LE(longitude, offset) - offset += 4 - } - if(setting.mainFieldHash.postcode) { - var postcodeDb = getPostcodeDatabase(postcode) - buffer3.writeUInt32LE(postcodeDb[1], offset) - buffer3.writeInt8(postcodeDb[0], offset + 4) - offset += 5 - } - if(setting.mainFieldHash.area) { - buffer3.writeUInt8(makeAreaDatabase(area), offset) - } - - preLocLocation = locLocation - preLocId = locId - preLatitude = latitude - preLongitude = longitude - preArea = area - - prePostcode = postcode - preBuffer1 = buffer1 - preBuffer2 = buffer2 - preBuffer3 = buffer3 - } - } - preEnd = end - }) - .on('pause', () => { - ws1.once('drain', () => rs.resume()) - if(!setting.smallMemory){ - ws2.once('drain', () => rs.resume()) - ws3.once('drain', () => rs.resume()) - } - }) - .on('end', () => { - if(setting.smallMemory){ - ws = createSmallMemoryFile(ws, ipv4, lineCount, preBuffer2, preBuffer3) - if(ws) ws.end(check) - else ++checkCount - ++checkCount - } else { - ws2.end(preBuffer2, check) - ws3.end(preBuffer3, check) - } - ws1.end(preBuffer1, check) - }) - }) -} - -const minifyMapData = (mapDatas) => { - var mapData0 = mapDatas[0] - if(setting.language !== 'en') { - var mapData1 = mapDatas.splice(1, 1)[0] - for(var locId in mapData0){ - if(mapData1 && mapData1[locId]){ - if(mapData1[locId].city_name) mapData0[locId].city_name = mapData1[locId].city_name - if(mapData1[locId].subdivision_1_name) mapData0[locId].subdivision_1_name = mapData1[locId].subdivision_1_name - if(mapData1[locId].subdivision_2_name) mapData0[locId].subdivision_2_name = mapData1[locId].subdivision_2_name - } - } - } - - var locIds = Object.keys(mapData0), locFields = Object.keys(setting.locFieldHash).filter(v => setting.locFieldHash[v]) - locIds.sort((a,b) => a-b) - const hash = { - country: 'country_iso_code', - region1: 'subdivision_1_iso_code', - region1_name: 'subdivision_1_name', - region2: 'subdivision_2_iso_code', - region2_name: 'subdivision_2_name', - city: 'city_name', - metro: 'metro_code', - timezone: 'time_zone' - } - const ranking = { - country: 8, - region1: 7, - region1_name: 6, - region2: 4, - region2_name: 3, - city: 9, - metro: 1, - timezone: 5 - } - locFields.sort((a,b) => { return ranking[b] - ranking[a]}) - var checkFields = locFields.map(v => hash[v]) - - var best1 = checkFields.shift() - var listHash = {} - for(var locId of locIds){ - var data = mapData0[locId] - if(!listHash[data[best1]]){ - listHash[data[best1]] = [] - } - listHash[data[best1]].push(locId) - } - - var i, j, len, dataI, dataJ, locIdJ, tmpLocIds - for(var key in listHash){ - tmpLocIds = listHash[key] - for(i = 0, len = tmpLocIds.length; i < len; ++i){ - dataI = mapData0[tmpLocIds[i]] - loopj: for(j = i+1; j < len; ++j){ - dataJ = mapData0[locIdJ = tmpLocIds[j]] - for(var field of checkFields){ - if(dataI[field] !== dataJ[field]){ - continue loopj - } - } - mapData0[locIdJ] = dataI - tmpLocIds.splice(j, 1) - --j - --len - } - } - } -} - -const createMapData = async (mapDatas) => { - var locIdList = mapDatas.pop() - var mapData0 = mapDatas[0] - var ws1 = fsSync.createWriteStream(path.join(setting.fieldDir, 'location.dat.tmp')) - var ws2 = fsSync.createWriteStream(path.join(setting.fieldDir, 'name.dat.tmp')) - var cityHash = {}, euHash = {} - sub1Database = {}, sub2Database = {}, timezoneDatabase = {} - sub1Count = 0, sub2Count = 0, timezoneCount = 0 - - for(var locId of locIdList){ - var data0 = mapData0[locId] - - var cc = data0.country_iso_code - var region1 = data0.subdivision_1_iso_code - var region2 = data0.subdivision_2_iso_code - var timezone = data0.time_zone - var metro = data0.metro_code - - var region1_name = data0.subdivision_1_name - var region2_name = data0.subdivision_2_name - var city = data0.city_name - - var offset = 0 - var b = Buffer.alloc(setting.locRecordSize) - if(setting.locFieldHash.country){ - if(cc && cc.length === 2) { - b.write(cc, offset); //country code [2 bytes] - if(setting.locFieldHash.eu && data0.is_in_european_union == 1){ - euHash[cc] = true - } - } - offset += 2 - } - if(setting.locFieldHash.region1){ - if(region1) b.writeUInt16LE(strToNum37(region1), offset) // subdivision code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region1_name){ - if(region1_name) b.writeUInt16LE(makeSub1Database(region1_name), offset) // subdivision name index [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region2){ - if(region2) b.writeUInt16LE(strToNum37(region2), offset) // subdivision code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region2_name){ - if(region2_name) b.writeUInt16LE(makeSub2Database(region2_name), offset) // subdivision name index [2 bytes] - offset += 2 - } - if(setting.locFieldHash.metro){ - if(metro) b.writeUInt16LE(metro, offset) // metro code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.timezone){ - if(timezone) b.writeUInt16LE(makeTimezoneDatabase(timezone), offset)// timezone [2 byte] - offset += 2 - } - if(setting.locFieldHash.city){ - if(city){ - b.writeUInt32LE(inputBuffer(cityHash, ws2, city), offset) // cityname index [4 bytes] - } - } - ws1.write(b) - } - ws1.end() - ws2.end() - - var hash = { - region1_name: DatabaseToArray(sub1Database), - region2_name: DatabaseToArray(sub2Database), - timezone: DatabaseToArray(timezoneDatabase), - area: DatabaseToArray(areaDatabase).map(v => parseInt(v, 10)||0), - eu: euHash - } - if(!setting.locFieldHash.region1_name) delete hash.region1_name - if(!setting.locFieldHash.region2_name) delete hash.region2_name - if(!setting.locFieldHash.timezone) delete hash.timezone - if(!setting.mainFieldHash.area) delete hash.area - if(!setting.locFieldHash.eu) delete hash.eu - if(Object.keys(hash).length > 0){ - await fs.writeFile(path.join(setting.fieldDir, 'sub.json.tmp'), JSON.stringify(hash)) - } - sub1Database = sub2Database = timezoneDatabase = areaDatabase = null - mapDatas.length = 0 -} - -const DatabaseToArray = (database) => { - var arr = [''] - for(var key in database){ - arr[database[key]] = key - } - return arr -} - -const inputBuffer = (hash, dataFile, text) => { - if(hash[text]) return hash[text] - if(hash.__offsetBB === undefined) { - var b = Buffer.alloc(1) - dataFile.write(b) - hash.__offsetBB = 1 - } - var offset = hash.__offsetBB - var b = Buffer.from(text) - var n = b.length + (offset << 8) - dataFile.write(b) - hash.__offsetBB = offset + b.length - return hash[text] = n -} - -var sub1Database = {}, sub2Database = {}, timezoneDatabase = {}, areaDatabase = {} -var sub1Count = 0, sub2Count = 0, timezoneCount = 0, areaCount = 0 -const makeSub1Database = (sub1) => { - if(sub1Database[sub1]) return sub1Database[sub1] - return sub1Database[sub1] = ++sub1Count -} -const makeSub2Database = (sub2) => { - if(sub2Database[sub2]) return sub2Database[sub2] - return sub2Database[sub2] = ++sub2Count -} -const makeTimezoneDatabase = (tz) => { - if(timezoneDatabase[tz]) return timezoneDatabase[tz] - return timezoneDatabase[tz] = ++timezoneCount -} -const makeAreaDatabase = (area) => { - if(areaDatabase[area]) return areaDatabase[area] - return areaDatabase[area] = ++areaCount -} - -const getMapData = async (file) => { - const rs = fsSync.createReadStream(path.join(setting.tmpDataDir, file)) - const result = {} - return new Promise((resolve, reject) => { - rs.pipe(parse({headers: true})) - .on('error', reject) - .on('data', row => { - if(setting.isCountry){ - result[row.geoname_id] = row.country_iso_code - } else { - result[row.geoname_id] = row - } - }) - .on('end', () => resolve(result)) - }) -} - -module.exports={update:update} \ No newline at end of file diff --git a/cjs/main.cjs b/cjs/main.cjs deleted file mode 100644 index e01c37a..0000000 --- a/cjs/main.cjs +++ /dev/null @@ -1,500 +0,0 @@ - -const fs = require('fs/promises') -const fsSync = require('fs') -const path = require('path') -const { exec, execSync } = require('child_process') - -const { countries, continents } = require('countries-list') -const { CronJob } = require('cron') - -const { setting, setSetting, getSettingCmd, consoleLog, consoleWarn } = require('./setting.cjs') -const { num37ToStr, getSmallMemoryFile, getZeroFill, aton6Start, aton4 } = require('./utils.cjs') - -const v4db = setting.v4 -const v6db = setting.v6 -const locFieldHash = setting.locFieldHash -const mainFieldHash = setting.mainFieldHash - - - -/** - * lookup ip address - * @type {function} - * @param {string} ip - ipv4 or ipv6 formatted address - * @return {object|null|Promise} location information - */ -const lookup = (ip) => { - - - - var isIpv6 - if(ip.includes(':')){ - ip = aton6Start(ip) - isIpv6 = ip.constructor === BigInt - } else { - ip = aton4(ip) - isIpv6 = false - } - const db = isIpv6 ? v6db : v4db - if(!(ip >= db.firstIp)) return null - const list = db.startIps - var fline = 0, cline = db.lastLine, line - for(;;){ - line = fline + cline >> 1 - if(ip < list[line]){ - if(cline - fline < 2) return null - cline = line - 1 - } else { - if(fline === line) { - if(cline !== line && ip >= list[cline]) { - line = cline - } - break - } - fline = line - } - } - - if(setting.smallMemory){ - - return lineToFile(line, db).then(buffer => { - var endIp = isIpv6 ? buffer.readBigUInt64LE(0) : buffer.readUInt32LE(0) - if(ip > endIp) return null - if(setting.isCountry){ - return setCountryInfo({ - country: buffer.toString('latin1', isIpv6 ? 8 : 4, isIpv6 ? 10 : 6) - }) - } - return setCityRecord(buffer, {}, isIpv6 ? 8 : 4) - }) - } - if(ip > db.endIps[line]) return null - if(setting.isCountry){ - return setCountryInfo({ - country: db.mainBuffer.toString('latin1', line * db.recordSize, line * db.recordSize + 2) - }) - } - return setCityRecord(db.mainBuffer, {}, line * db.recordSize) -} - -/** - * setup database without reload - * @param {object} [_setting] - * @return {void} - */ -const setupWithoutReload = setSetting - -/** - * clear in-memory database - * @type {function} - * @return {void} - */ -const clear = () => { - v4db.startIps = v6db.startIps = v4db.endIps = v6db.endIps = v4db.mainBuffer = v6db.mainBuffer = null - Region1NameJson = Region2NameJson = TimezoneJson = LocBuffer = CityNameBuffer = EuJson = null -} - -var Region1NameJson, Region2NameJson, TimezoneJson, LocBuffer, CityNameBuffer, AreaJson, EuJson -var updateJob -/** - * reload in-memory database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [sync] - sync mode - * @param {boolean} [_runningUpdate] - if it's running update [internal use] - * @return {Promise|void} - */ -const reload = async (_setting, sync, _runningUpdate) => { - var curSetting = setting - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - curSetting = Object.assign({}, setting) - Object.assign(setting, oldSetting) - } - const dataDir = curSetting.fieldDir - const v4 = v4db, v6 = v6db - var dataFiles = { - v41: path.join(dataDir, '4-1.dat'), - v42: path.join(dataDir, '4-2.dat'), - v43: path.join(dataDir, '4-3.dat'), - v61: path.join(dataDir, '6-1.dat'), - v62: path.join(dataDir, '6-2.dat'), - v63: path.join(dataDir, '6-3.dat'), - cityLocation: path.join(dataDir, 'location.dat'), - cityName: path.join(dataDir, 'name.dat'), - citySub: path.join(dataDir, 'sub.json') - } - - var locBuffer, cityNameBuffer, subBuffer - var buffer41, buffer42, buffer43, buffer61, buffer62, buffer63 - var testDir = dataDir - if(curSetting.smallMemory){ - testDir = path.join(testDir, 'v4') - } - - if(sync){ - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - updateDb(_setting && curSetting, true, true) - consoleLog('Database created') - } - buffer41 = fsSync.readFileSync(dataFiles.v41) - buffer61 = fsSync.readFileSync(dataFiles.v61) - if(!curSetting.smallMemory){ - buffer42 = fsSync.readFileSync(dataFiles.v42) - buffer43 = fsSync.readFileSync(dataFiles.v43) - buffer62 = fsSync.readFileSync(dataFiles.v62) - buffer63 = fsSync.readFileSync(dataFiles.v63) - } - if(curSetting.locFile){ - locBuffer = fsSync.readFileSync(dataFiles.cityLocation) - if(locFieldHash.city){ - cityNameBuffer = fsSync.readFileSync(dataFiles.cityName) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - subBuffer = fsSync.readFileSync(dataFiles.citySub) - } - } - } else { - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - await updateDb(_setting && curSetting, true) - consoleLog('Database created') - } - var prs = [ - fs.readFile(dataFiles.v41).then(data => buffer41 = data), - fs.readFile(dataFiles.v61).then(data => buffer61 = data), - ] - if(!curSetting.smallMemory){ - prs.push( - fs.readFile(dataFiles.v42).then(data => buffer42 = data), - fs.readFile(dataFiles.v43).then(data => buffer43 = data), - fs.readFile(dataFiles.v62).then(data => buffer62 = data), - fs.readFile(dataFiles.v63).then(data => buffer63 = data) - ) - } - if(curSetting.locFile){ - prs.push(fs.readFile(dataFiles.cityLocation).then(data => locBuffer = data)) - if(locFieldHash.city){ - prs.push(fs.readFile(dataFiles.cityName).then(data => cityNameBuffer = data)) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - prs.push(fs.readFile(dataFiles.citySub).then(data => subBuffer = data)) - } - } - await Promise.all(prs) - } - - if(_setting){ - Object.assign(setting, curSetting) - } - - v4.startIps = new Uint32Array(buffer41.buffer, 0, buffer41.byteLength >> 2) - v6.startIps = new BigUint64Array(buffer61.buffer, 0, buffer61.byteLength >> 3) - if(!curSetting.smallMemory){ - v4.endIps = new Uint32Array(buffer42.buffer, 0, buffer42.byteLength >> 2) - v4.mainBuffer = buffer43 - v6.endIps = new BigUint64Array(buffer62.buffer, 0, buffer62.byteLength >> 3) - v6.mainBuffer = buffer63 - } - - v4.lastLine = v4.startIps.length - 1 - v6.lastLine = v6.startIps.length - 1 - v4.firstIp = v4.startIps[0] - v6.firstIp = v6.startIps[0] - if(curSetting.isCity){ - LocBuffer = locBuffer - CityNameBuffer = cityNameBuffer - if(subBuffer){ - var tmpJson = JSON.parse(subBuffer) - if(locFieldHash.region1_name) Region1NameJson = tmpJson.region1_name - if(locFieldHash.region2_name) Region2NameJson = tmpJson.region2_name - if(locFieldHash.timezone) TimezoneJson = tmpJson.timezone - if(mainFieldHash.area) AreaJson = tmpJson.area - if(locFieldHash.eu) EuJson = tmpJson.eu - } - } - - - if(setting.smallMemory && _runningUpdate){ - const rimraf = (dir) => { - if(fs.rm){ - return fs.rm(dir, {recursive: true, force: true, maxRetries: 3}) - } - return fs.rmdir(dir, {recursive: true, maxRetries: 3}) - } - fsSync.cpSync(path.join(setting.fieldDir, 'v4-tmp'), path.join(setting.fieldDir, 'v4'), {recursive: true, force: true}) - fsSync.cpSync(path.join(setting.fieldDir, 'v6-tmp'), path.join(setting.fieldDir, 'v6'), {recursive: true, force: true}) - rimraf(path.join(setting.fieldDir, 'v4-tmp')).catch(consoleWarn) - rimraf(path.join(setting.fieldDir, 'v6-tmp')).catch(consoleWarn) - } - - if(!updateJob && setting.autoUpdate){ - updateJob = new CronJob(setting.autoUpdate, () => { - updateDb().finally(() => {}) - }, null, true, 'UTC') - } else if(updateJob && !setting.autoUpdate){ - updateJob.stop() - updateJob = null - } -} - -const watchHash = {} -/** - * Watch database directory. - * When database file is updated, it reload the database automatically - * This causes error if you use ILA_SMALL_MEMORY=true - * @type {function} - * @param {string} [name] - name of watch. If you want to watch multiple directories, you can set different name for each directory - */ -const watchDb = (name = 'ILA') => { - var watchId = null - watchHash[name] = fsSync.watch(setting.fieldDir, (eventType, filename) => { - if(!filename.endsWith('.dat')) return; - if(fsSync.existsSync(path.join(setting.fieldDir, filename))) { - if(watchId) clearTimeout(watchId) - watchId = setTimeout(reload, 30 * 1000) - } - }) -} - -/** - * Stop watching database directory - * @type {function} - * @param {string} [name] - */ -const stopWatchDb = (name = 'ILA') => { - if(watchHash[name]){ - watchHash[name].close() - delete watchHash[name] - } -} - -/** - * Update database and auto reload database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [noReload] - if you don't want to reload the database after update - * @param {boolean} [sync] - if you want to update the database in sync mode - * @return {Promise} - true if database is updated, false if no need to update - */ -const updateDb = (_setting, noReload, sync) => { - - - - var arg, runningUpdate = false - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - arg = getSettingCmd() - Object.assign(setting, oldSetting) - } else { - arg = getSettingCmd() - } - - var scriptPath = path.resolve(_setting ? _setting.apiDir : setting.apiDir, 'script', 'updatedb.mjs') - if(scriptPath.includes(' ')) scriptPath = '"' + scriptPath + '"' - var cmd = 'node ' + scriptPath - if(!_setting){ - arg += ' ILA_SAME_DB_SETTING=true' - } - if(_setting && _setting.smallmemory || !_setting && setting.smallMemory){ - runningUpdate = true - arg += ' ILA_RUNNING_UPDATE=true' - } - - if(arg){ - cmd += ' ' + arg - } - if(sync){ - try{ - var stdout = execSync(cmd) - if(stdout.includes('NO NEED TO UPDATE')){ - return true - } - if(stdout.includes('SUCCESS TO UPDATE')){ - if(!noReload){ - reload(_setting, sync) - } - return true - } - return false - }catch(e){ - consoleWarn(e) - return false - } - } - return new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if(err) { - consoleWarn(err) - } - if(stderr) { - consoleWarn(stderr) - } - if(stdout) { - consoleLog(stdout) - } - if(err) { - reject(err) - } else if(stdout.includes('ERROR TO UPDATE')){ - reject(new Error('ERROR TO UPDATE')) - } else if(stdout.includes('NO NEED TO UPDATE')){ - resolve(false) - } else if(stdout.includes('SUCCESS TO UPDATE')){ - if(noReload){ - resolve(true) - } else { - reload(_setting, false, runningUpdate).then(() => { - resolve(true) - }).catch(reject) - } - } else { - consoleLog('UNKNOWN ERROR') - reject(new Error('UNKNOWN ERROR')) - } - }) - }) -} - -/* --- Remain this code for better performance check -const lineToFile = (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - return new Promise((resolve, reject) => { - - - - - - - - fs.open(path.join(dir, file), 'r').then(fd => { - const buffer = Buffer.alloc(db.recordSize) - fd.read(buffer, 0, db.recordSize, offset).then(() => { - fd.close().catch(reject) - resolve(buffer) - }).catch(reject) - }).catch(reject) - }) -} -*/ -const lineToFile = async (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - const fd = await fs.open(path.join(setting.fieldDir, dir, file), 'r') - const buffer = Buffer.alloc(db.recordSize) - await fd.read(buffer, 0, db.recordSize, offset) - fd.close().catch(consoleWarn) - return buffer -} - -const setCityRecord = (buffer, geodata, offset) => { - var locId - if(setting.locFile){ - locId = buffer.readUInt32LE(offset) - offset += 4 - } - if(mainFieldHash.latitude){ - geodata.latitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.longitude){ - geodata.longitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.postcode){ - var postcode2 = buffer.readUInt32LE(offset) - var postcode1 = buffer.readInt8(offset + 4) - if (postcode2) { - var postcode, tmp - if(postcode1 < -9){ - tmp = (-postcode1).toString() - postcode = postcode2.toString(36) - postcode = getZeroFill(postcode.slice(0, -tmp[1]), tmp[0]-0) + '-' + getZeroFill(postcode.slice(-tmp[1]), tmp[1]-0) - } else if(postcode1 < 0){ - postcode = getZeroFill(postcode2.toString(36), -postcode1) - } else if(postcode1 < 10){ - postcode = getZeroFill(postcode2.toString(10), postcode1) - } else if(postcode1 < 72){ - postcode1 = String(postcode1) - postcode = getZeroFill(postcode2.toString(10), (postcode1[0]-0) + (postcode1[1]-0)) - postcode = postcode.slice(0, postcode1[0]-0) + '-' + postcode.slice(postcode1[0]-0) - } else { - postcode = postcode1.toString(36).slice(1) + postcode2.toString(36) - } - geodata.postcode = postcode.toUpperCase() - } - offset += 5 - } - if(mainFieldHash.area){ - geodata.area = AreaJson[buffer.readUInt8(offset)] - offset += 1 - } - - if(locId){ - var locOffset = (locId-1) * setting.locRecordSize - if(locFieldHash.country){ - geodata.country = LocBuffer.toString('utf8', locOffset, locOffset += 2) - if(locFieldHash.eu){ - geodata.eu = EuJson[geodata.country] - } - } - if(locFieldHash.region1){ - var region1 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1 > 0) geodata.region1 = num37ToStr(region1) - } - if(locFieldHash.region1_name){ - var region1_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1_name > 0) geodata.region1_name = Region1NameJson[region1_name] - } - if(locFieldHash.region2){ - var region2 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2 > 0) geodata.region2 = num37ToStr(region2) - } - if(locFieldHash.region2_name){ - var region2_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2_name > 0) geodata.region2_name = Region2NameJson[region2_name] - } - if(locFieldHash.metro){ - var metro = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(metro > 0) geodata.metro = metro - } - if(locFieldHash.timezone){ - var timezone = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(timezone > 0) geodata.timezone = TimezoneJson[timezone] - } - if(locFieldHash.city){ - var city = LocBuffer.readUInt32LE(locOffset) - locOffset += 4 - if(city > 0){ - var start = city >>> 8 - geodata.city = CityNameBuffer.toString('utf8', start, start + (city & 255)) - } - } - } - return setCountryInfo(geodata) -} -const setCountryInfo = (geodata) => { - if(setting.addCountryInfo){ - var h = countries[geodata.country] - geodata.country_name = h.name - geodata.country_native = h.native - geodata.continent = h.continent - geodata.continent_name = continents[h.continent] - geodata.capital = h.capital - geodata.phone = h.phone - geodata.currency = h.currency - geodata.languages = h.languages - } - return geodata -} - reload(undefined, true) - -module.exports={lookup:lookup,setupWithoutReload:setupWithoutReload,clear:clear,reload:reload,watchDb:watchDb,stopWatchDb:stopWatchDb,updateDb:updateDb} \ No newline at end of file diff --git a/cjs/setting.cjs b/cjs/setting.cjs deleted file mode 100644 index bb8cff5..0000000 --- a/cjs/setting.cjs +++ /dev/null @@ -1,179 +0,0 @@ - -const path = require('path') - -const { fileURLToPath } = require('url') -const { getFieldsSize } = require('./utils.cjs') - -const defaultSetting = { - - fields: ['country'], - dataDir: '../data/', - tmpDataDir: '../tmp/', - apiDir: '..', - - smallMemory: false, - smallMemoryFileSize: 4096, - - addCountryInfo: false, - - licenseKey: 'redist', - ipLocationDb: '', - downloadType: 'reuse', - series: 'GeoLite2', // or GeoIP2 - language: 'en', - fakeData: false, - autoUpdate: 'default', - - sameDbSetting: false, - multiDbDir: false, - - browserType: false, - silent: false, -} - -const setting = { - v4: {ipv4: true, ipv6: false, name: 'v4'}, - v6: {ipv4: false, ipv6: true, name: 'v6'}, - mainFieldHash: {}, - locFieldHash: {}, -} - - -const mainFields = ['latitude', 'longitude', 'area', 'postcode'] -const locFields = ['country', 'region1', 'region1_name', 'region2', 'region2_name', 'metro', 'timezone', 'city', 'eu'] - -const shortNumber = { - latitude: 1, - longitude: 2, - area: 4, - postcode: 8, - country: 16, - region1: 32, - region1_name: 64, - region2: 128, - region2_name: 256, - metro: 512, - timezone: 1024, - city: 2048, - eu: 4096 -} - -const make_key = (key) => { - return 'ILA_' + key.replace(/([A-Z])/g, char => '_' + char).toUpperCase() -} - -const consoleLog = (...args) => { - if(setting.silent) return - console.log(...args) -} - -const consoleWarn = (...args) => { - if(setting.silent) return - console.warn(...args) -} - -const getSettingCmd = () => { - const ret = [] - for(const key in defaultSetting){ - if(setting[key] && setting[key] !== defaultSetting[key]){ - var value = String(setting[key]) - if(value.includes(' ')) value = '"' + value + '"' - ret.push(make_key(key) + '=' + value) - } - } - return ret.join(' ') -} - -const inputSetting = {} -var settingKeys = Object.keys(defaultSetting) -for(var env in process.env){ - for(var key of settingKeys){ - if(env.toUpperCase() === make_key(key)){ - inputSetting[key] = process.env[env] - } - } -} -for(var arg of process.argv){ - var v = arg.toUpperCase() - for(var key of settingKeys){ - if(v.includes(make_key(key) + '=')){ - inputSetting[key] = arg.split('=')[1] - } - } -} - -const NumReg = /^\d+$/ -const setSetting = (_setting = {}) => { - for(var key in _setting){ - var value = setting[key] = _setting[key] - if(value === "false") setting[key] = false - else if(value === "true") setting[key] = true - else if(NumReg.test(value)) setting[key] = parseInt(value) - } - - if(setting.autoUpdate === 'default'){ - setting.autoUpdate = Math.floor(Math.random()*59.9) + ' ' + Math.floor(Math.random()*59.9) + ' 0 * * wed,sat' - } - - const windowsDriveReg = /^[a-zA-Z]:\\/ - if(!setting.dataDir.startsWith('/') && !setting.dataDir.startsWith('\\\\') && !windowsDriveReg.test(setting.dataDir)){ - setting.dataDir = path.resolve(__dirname, setting.dataDir) - } - if(!setting.tmpDataDir.startsWith('/') && !setting.tmpDataDir.startsWith('\\\\') && !windowsDriveReg.test(setting.tmpDataDir)){ - setting.tmpDataDir = path.resolve(__dirname, setting.tmpDataDir) - } - if(!setting.apiDir.startsWith('/') && !setting.apiDir.startsWith('\\\\') && !windowsDriveReg.test(setting.apiDir)){ - setting.apiDir = path.resolve(__dirname, setting.apiDir) - } - - if(typeof setting.fields === 'string'){ - setting.fields = setting.fields.split(/\s*,\s*/) - } - if(setting.fields.includes('all')) { - setting.fields = mainFields.concat(locFields) - } else { - setting.fields = setting.fields.filter(v => mainFields.includes(v) || locFields.includes(v)) - } - - if(setting.fields.length === 1 && setting.fields[0] === 'country'){ - setting.dataType = 'country' - } else { - setting.dataType = 'city' - } - setting.isCountry = setting.dataType === 'country' - setting.isCity = !setting.isCountry - - for(var field of mainFields){ - setting.mainFieldHash[field] = setting.fields.includes(field) - } - - setting.noLocFile = true - for(var field of locFields){ - setting.locFieldHash[field] = setting.fields.includes(field) - if(setting.locFieldHash[field]){ - setting.noLocFile = false - } - } - if(setting.isCountry) setting.noLocFile = true - setting.locFile = !setting.noLocFile - - setting.fieldDir = path.join(setting.dataDir, setting.fields.reduce((sum, v) => sum + shortNumber[v], 0).toString(36)) - - var mainRecordSize = setting.isCountry ? 2 : getFieldsSize(setting.fields.filter(v => mainFields.includes(v))) - if(setting.locFile) mainRecordSize += 4 - setting.v4.recordSize = setting.v6.recordSize = setting.mainRecordSize = mainRecordSize - setting.locRecordSize = getFieldsSize(setting.fields.filter(v => locFields.includes(v))) - if(setting.smallMemory){ - setting.v4.recordSize += 4 - setting.v6.recordSize += 8 - setting.v4.fileLineMax = (setting.smallMemoryFileSize / setting.v4.recordSize | 0) || 1 - setting.v6.fileLineMax = (setting.smallMemoryFileSize / setting.v6.recordSize | 0) || 1 - setting.fileMax = 1024 - setting.v4.folderLineMax = setting.v4.fileLineMax * setting.fileMax - setting.v6.folderLineMax = setting.v6.fileLineMax * setting.fileMax - } -} - -setSetting(Object.assign({}, defaultSetting, inputSetting)) - -module.exports={setting:setting,consoleLog:consoleLog,consoleWarn:consoleWarn,getSettingCmd:getSettingCmd,setSetting:setSetting} \ No newline at end of file diff --git a/cjs/utils.cjs b/cjs/utils.cjs deleted file mode 100644 index 1d32e1a..0000000 --- a/cjs/utils.cjs +++ /dev/null @@ -1,199 +0,0 @@ - -const path = require('path') - - -const countryCodeToNum = (code) => { // 0~675 - code = code.toUpperCase() - return (code.charCodeAt(0)-65)*26 + (code.charCodeAt(1)-65) -} -const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -} - -const getFieldsSize = (types) => { - var size = 0 - for (const type of types) { - switch (type) { - case 'postcode': - size += 5 - break - case 'area': - size += 1 - break - case 'latitude': - case 'longitude': - case 'city': - size += 4 - break - case 'eu': - break - default: - size += 2 - break - } - } - return size -} - -const ntoa4 = (n) => { - return [n >>> 24, n >> 16 & 255, n >> 8 & 255, n & 255].join('.') -} - -const aton4 = (a) => { - a = a.split(/\./) - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -} - -const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/) - const l = a.length - 1 - var i, r = 0n - if (l < 7) { - const omitStart = a.indexOf('') - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0 - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)) - } - return r -} - -const aton6 = (a) => { - a = a.replace(/"/g, '').split(/:/) - - const l = a.length - 1 - var i - if (a[l] === '') a[l] = 0 - if (l < 7) { - const omitted = 8 - a.length, omitStart = a.indexOf(''), omitEnd = omitStart + omitted - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0 - } - } - - var r = 0n - for (i = 0; i < 4; i++) { - if (a[i]) { - r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)) - } - } - return r -} - -const v4MappedReg = /^(?:0:0:0:0:0|:):ffff:(\d+\.\d+\.\d+\.\d+)$/i -const v4Mapped = (addr) => { - const match = v4MappedReg.exec(addr) - return match && match[1] -} - -const PrivateIpRegList = [ - /^10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/, - /^192\.168\.([0-9]{1,3})\.([0-9]{1,3})/, - /^172\.16\.([0-9]{1,3})\.([0-9]{1,3})/, - /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/, - /^169\.254\.([0-9]{1,3})\.([0-9]{1,3})/, - /^fc00:/, - /^fe80:/ -] - -const isPrivateIP = function(addr) { - for(const reg of PrivateIpRegList){ - if(reg.test(addr)){ - return true - } - } - return false -} - -const strToNum37 = (a) => { - var num = 0 - for(var i = 0; i < a.length; i++){ - num = num * 37 + parseInt(a[i], 36) + 1 - } - return num -} - -const num37ToStr = (num) => { - var str = '' - while(num > 0){ - str = (num % 37 - 1).toString(36) + str - num = Math.floor(num / 37) - } - return str.toUpperCase() -} - -const getZeroFill = (num, len) => { - return '0'.repeat(len - num.length) + num -} - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -} -const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -} -const getSmallMemoryFile = (line, db, isTmp) => { - const dbNumber = line / db.folderLineMax | 0 - const fileNumber = (line - dbNumber * db.folderLineMax) / db.fileLineMax | 0 - const lineOffset = line - dbNumber * db.folderLineMax - fileNumber * db.fileLineMax - var dir = path.join(db.name + (isTmp ? '-tmp' : ''), getUnderberFill(dbNumber.toString(36), 2)) - return [dir, getUnderberFill(fileNumber.toString(36), 2), lineOffset * db.recordSize] -} - -const isPostNumReg = /^\d+$/ -const isPostNumReg2 = /^(\d+)[-\s](\d+)$/ -const isPostStrReg = /^([A-Z\d]+)$/ -const isPostStrReg2 = /^([A-Z\d]+)[-\s]([A-Z\d]+)$/ -const getPostcodeDatabase = (postcode) => { - if(!postcode) return [0, 0]; - - if(isPostNumReg.test(postcode)){ - return [ - postcode.length, // 1~9 - parseInt(postcode, 10) // 0~999999999 - ] - } - var r = isPostNumReg2.exec(postcode) - if(r){ - return [ - parseInt(r[1].length + '' + r[2].length, 10), // 11~66 - parseInt(r[1] + r[2], 10) // 0~999999999 - ] - } - - r = isPostStrReg.exec(postcode) - if(r){ - var num = parseInt(postcode, 36) - if(num < Math.pow(2, 32)){ - return [ - -postcode.length, // -1~-9 - num - ] - } else { - return [ - parseInt('2' + postcode.slice(0, 1), 36), // 72~107, - parseInt(postcode.slice(1), 36) // 0~2176782335 MAX: 6char ZZZZZZ - ] - } - } - - r = isPostStrReg2.exec(postcode) - if(!r){ - console.log('Invalid postcode:', postcode) - } - return [ - - parseInt(r[1].length + "" + r[2].length, 10),// -11~-55 - parseInt(r[1] + r[2], 36) // 0~2176782335 MAX: 6char ZZZZZZ - ] -} - -module.exports={countryCodeToNum:countryCodeToNum,numToCountryCode:numToCountryCode,getFieldsSize:getFieldsSize,ntoa4:ntoa4,aton4:aton4,aton6Start:aton6Start,aton6:aton6,v4Mapped:v4Mapped,isPrivateIP:isPrivateIP,strToNum37:strToNum37,num37ToStr:num37ToStr,getZeroFill:getZeroFill,numberToDir:numberToDir,getSmallMemoryFile:getSmallMemoryFile,getPostcodeDatabase:getPostcodeDatabase} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..26cd634 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,8 @@ +import antfu from '@antfu/eslint-config' + +export default antfu( + { + formatters: true, + typescript: true, + }, +) diff --git a/package.json b/package.json index 355ff05..f9e9d66 100644 --- a/package.json +++ b/package.json @@ -1,130 +1,43 @@ { - "name": "ip-location-api", - "version": "2.3.3", - "description": "Fast location lookup from IP address", - "keywords": [ - "location", - "lookup", - "geo", - "geoip", - "geolite", - "maxmind", - "ip", - "ipv4", - "ipv6", - "ip-location-db", - "country", - "city" - ], - "scripts": { - "test": "npx jasmine", - "updatedb": "node script/updatedb.mjs", - "updatedb-browser-country": "node script/updatedb.mjs ILA_BROWSER_TYPE=country ILA_IP_LOCATION_DB=geo-whois-asn", - "updatedb-browser-geocode": "node script/updatedb.mjs ILA_BROWSER_TYPE=geocode ILA_FIELDS=latitude,longitude", - "browser": "npx http-server browser -p 8321", - "cjs": "node script/convert-to-cjs.mjs", - "ts": "npx -p typescript tsc src/main.mjs cjs/main.cjs --declaration --allowJs --emitDeclarationOnly --skipLibCheck --outDir types", - "release": "release-it" - }, - "author": "sapics", - "homepage": "https://github.com/sapics/ip-location-api", - "repository": { - "type": "git", - "url": "git://github.com/sapics/ip-location-api.git" - }, - "license": "Multiple licenses", "type": "module", + "private": true, + "packageManager": "pnpm@9.11.0", "engines": { - "node": ">=14.8.0" + "node": ">=14.8.0", + "pnpm": ">=9.11.0" }, - "main": "cjs/main.cjs", - "types": "types/cjs/main.d.cts", - "module": "src/main.mjs", - "exports": { - ".": { - "import": { - "types": "./types/src/main.d.mts", - "default": "./src/main.mjs" - }, - "require": { - "types": "./types/cjs/main.d.cts", - "default": "./cjs/main.cjs" - }, - "default": { - "types": "./types/cjs/main.d.cts", - "default": "./cjs/main.cjs" - } - }, - "./pack": { - "import": { - "types": "./types/src/main.d.mts", - "default": "./src/main-pack.mjs" - }, - "default": { - "types": "./types/src/main.d.mts", - "default": "./src/main-pack.mjs" - } - }, - "./country": { - "import": "./browser/country/lookup.mjs", - "require": "./browser/country/lookup.cjs", - "default": "./browser/country/lookup.cjs" - }, - "./country-extra": { - "import": "./browser/country-extra/lookup.mjs", - "require": "./browser/country-extra/lookup.cjs", - "default": "./browser/country-extra/lookup.cjs" - }, - "./geocode": { - "import": "./browser/geocode/lookup.mjs", - "require": "./browser/geocode/lookup.cjs", - "default": "./browser/geocode/lookup.cjs" - }, - "./geocode-extra": { - "import": "./browser/geocode-extra/lookup.mjs", - "require": "./browser/geocode-extra/lookup.cjs", - "default": "./browser/geocode-extra/lookup.cjs" - } - }, - "files": [ - "src/*.mjs", - "!src/brower*.mjs", - "browser/*/*.cjs", - "browser/*/*.mjs", - "browser/*/*.js", - "cjs/*.cjs", - "spec", - "script/*.mjs", - "script/*.cjs", - "types/src/main.d.mts", - "types/cjs/main.d.cts", - "LICENSE", - "EULA", - "README.md" - ], - "dependencies": { - "axios": "^1.7.7", - "countries-list": "^3.1.1", - "cron": "^3.1.7", - "dayjs": "^1.11.13", - "fast-csv": "^5.0.1", - "ip-address": "^9.0.5", - "yauzl": "^3.1.3" + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "build": "pnpm -r --workspace-concurrency=1 build", + "build:watch": "concurrently \"npm:build:watch:*\" --kill-others", + "build:watch:country": "cd packages/country && pnpm build:watch", + "build:watch:country-extra": "cd packages/country-extra && pnpm build:watch", + "build:watch:geocode": "cd packages/geocode && pnpm build:watch", + "build:watch:geocode-extra": "cd packages/geocode-extra && pnpm build:watch", + "build:watch:ip-location-api": "cd packages/ip-location-api && pnpm build:watch", + "build:watch:util": "cd packages/util && pnpm build:watch", + "test:ui": "vitest --ui --coverage", + "test": "vitest --run --coverage", + "release:all": "pnpm install && pnpm build && pnpm -r release" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.7", - "@rollup/plugin-strip": "^3.0.4", - "@rollup/plugin-terser": "^0.4.4", - "doc999tor-fast-geoip": "^1.1.253", - "eslint": "^8.57.0", - "geoip-country": "^4.0.0", - "geoip-lite": "^1.4.10", - "globals": "^15.9.0", - "http-server": "^14.1.1", - "jasmine": "^5.2.0", - "release-it": "^17.10.0", - "rollup-plugin-ignore": "^1.0.10", - "tsc": "^2.0.4" + "@antfu/eslint-config": "^3.7.3", + "@rollup/plugin-replace": "^6.0.1", + "@total-typescript/tsconfig": "^1.0.4", + "@types/node": "^22.7.4", + "@unocss/eslint-plugin": "^0.63.0", + "@vitest/coverage-v8": "^2.1.1", + "@vitest/ui": "^2.1.1", + "bumpp": "^9.8.1", + "concurrently": "^9.0.1", + "eslint": "^9.11.1", + "eslint-plugin-format": "^0.1.2", + "semver": "^7.6.3", + "typescript": "^5.6.2", + "vite": "^5.4.8", + "vite-plugin-checker": "^0.8.0", + "vite-plugin-dts": "^4.2.2", + "vitest": "^2.1.1" } } diff --git a/packages/country-extra/README.md b/packages/country-extra/README.md new file mode 100644 index 0000000..d244602 --- /dev/null +++ b/packages/country-extra/README.md @@ -0,0 +1,57 @@ +# @iplookup/country-extra + +[![npm version](https://badge.fury.io/js/%40iplookup%2Fcountry-extra.svg)](https://badge.fury.io/js/%40iplookup%2Fcountry-extra) +[![Downloads](https://img.shields.io/npm/dm/%40iplookup%2Fcountry-extra.svg)](https://www.npmjs.com/package/%40iplookup%2Fcountry-extra) +[![Build](https://github.com/sapics/ip-location-api/actions/workflows/build.yml/badge.svg)](https://github.com/sapics/ip-location-api/actions/workflows/build.yml) + +## Usage + +### Browser + +```html + + +``` + +### ESM + +```ts +import IpLookup from '@iplookup/country-extra' + +const location = await IpLookup('207.97.227.239') +``` + +### CommonJS + +```ts +const IpLookup = require('@iplookup/country-extra') + +const location = await IpLookup('207.97.227.239') +``` + +If you do not need extra information about country, try to use [@iplookup/country](https://github.com/sapics/ip-location-api/tree/main/browser/country). + +## License + +Since each user download a partial database, we use the CC0 Licensed database [geo-whois-asn-country](https://github.com/sapics/ip-location-db/tree/main/geo-whois-asn-country) for ip to country mapping to avoid license problem. + +To get extra information about country, we use [Countries](https://github.com/annexare/Countries) which is published under MIT license by [Annexare Studio](https://annexare.com/). + +The software itself is published under MIT license by [sapics](https://github.com/sapics). diff --git a/packages/country-extra/package.json b/packages/country-extra/package.json new file mode 100644 index 0000000..a082e5b --- /dev/null +++ b/packages/country-extra/package.json @@ -0,0 +1,51 @@ +{ + "name": "@iplookup/country-extra", + "type": "module", + "version": "2.0.0", + "description": "Browser api to lookup country from IP address", + "author": "sapics", + "license": "MIT", + "homepage": "https://github.com/sapics/ip-location-api", + "repository": { + "type": "git", + "url": "git://github.com/sapics/ip-location-api.git", + "directory": "packages/country-extra" + }, + "keywords": [ + "location", + "iplookup", + "lookup", + "geo", + "geoip", + "ip", + "ipv4", + "ipv6", + "ip-location-db", + "country" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "indexes" + ], + "scripts": { + "build": "vite build", + "build:watch": "vite build --watch", + "release": "bumpp package.json --commit --push --tag" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@iplookup/util": "workspace:*", + "countries-list": "^3.1.1" + } +} diff --git a/packages/country-extra/src/index.test.ts b/packages/country-extra/src/index.test.ts new file mode 100644 index 0000000..34ed0c7 --- /dev/null +++ b/packages/country-extra/src/index.test.ts @@ -0,0 +1,71 @@ +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import IpLookup from './index' + +describe('ipLookup', () => { + it('should export the IpLookup function', () => { + expect.soft(IpLookup).toBeDefined() + expect.soft(typeof IpLookup).toBe('function') + expectTypeOf(IpLookup).toEqualTypeOf<(ipInput: string) => Promise<{ + country: string + country_name: string + country_native: string + continent: string + capital: string + phone: number[] + currency: string[] + languages: string[] + } | null>>() + }) + + it('should return the correct country', async () => { + globalThis.fetch = vi.fn().mockImplementation((url: URL) => { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Headers(), + arrayBuffer: async () => { + const data = await readFile(resolve(__dirname, `../indexes${url.pathname.split('indexes')[1]}`)) + return data.buffer + }, + }) + }) + const result = await IpLookup('207.97.227.239') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ + country: 'US', + country_name: 'United States', + country_native: 'United States', + continent: 'NA', + capital: 'Washington D.C.', + phone: [1], + currency: [ + 'USD', + 'USN', + 'USS', + ], + languages: ['en'], + }) + + const result2 = await IpLookup('2607:F8B0:4005:801::200E') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ + country: 'US', + country_name: 'United States', + country_native: 'United States', + continent: 'NA', + capital: 'Washington D.C.', + phone: [1], + currency: [ + 'USD', + 'USN', + 'USS', + ], + languages: ['en'], + }) + + await expect.soft(IpLookup('invalid')).rejects.toThrow('Invalid IPv4 address: invalid') + await expect.soft(IpLookup('0000:0000:0000:0000:0000:0000:0000:0000')).resolves.toBeNull() + }) +}) diff --git a/packages/country-extra/src/index.ts b/packages/country-extra/src/index.ts new file mode 100644 index 0000000..5ee2498 --- /dev/null +++ b/packages/country-extra/src/index.ts @@ -0,0 +1,69 @@ +import { setup } from '@iplookup/util/browser' +import { countries } from 'countries-list' + +const BaseLookup = setup<'country'>() + +/** + * Lookup country from IP address + * @param ipInput - IP address to lookup + * @returns country code, country name, country native name, continent, capital, phone, currency and languages. `null` if not found. + */ +export default async function IpLookup(ipInput: string): Promise<{ + /** + * The country of the IP address + * @example 'NL' + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country: string + /** + * The name of the country of the IP address (In English) + * @example 'Netherlands' + */ + country_name: string + /** + * The name of the country of the IP address (In the native language of the country) + * @example 'Nederland' + */ + country_native: string + /** + * The continent of the IP address (alpha-2 code) + * @example 'EU' + */ + continent: string + /** + * The capital of the country of the IP address (In English) + * @example 'Amsterdam' + */ + capital: string + /** + * The phone codes of the country of the IP address + * @example ['31'] + */ + phone: number[] + /** + * The currency of the country of the IP address + * @example ['EUR'] + */ + currency: string[] + /** + * The languages of the country of the IP address + * @example ['nl'] + */ + languages: string[] +} | null> { + const result = await BaseLookup(ipInput) + if (!result) + return null + + const country = countries[result.country as keyof typeof countries] + return { + country: result.country, + country_name: country.name, + country_native: country.native, + continent: country.continent, + capital: country.capital, + phone: country.phone, + currency: country.currency, + languages: country.languages, + } +} diff --git a/packages/country-extra/tsconfig.json b/packages/country-extra/tsconfig.json new file mode 100644 index 0000000..0f3afd7 --- /dev/null +++ b/packages/country-extra/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + } +} diff --git a/packages/country-extra/vite.config.ts b/packages/country-extra/vite.config.ts new file mode 100644 index 0000000..6402849 --- /dev/null +++ b/packages/country-extra/vite.config.ts @@ -0,0 +1,62 @@ +import type { IpLocationApiInputSettings } from '@iplookup/util' +import { resolve } from 'node:path' +import { createBrowserIndex } from '@iplookup/util/browserIndex' +import { update } from '@iplookup/util/db' +import replace from '@rollup/plugin-replace' +import { defineConfig } from 'vite' +import checker from 'vite-plugin-checker' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + emptyOutDir: true, + lib: { + entry: { + index: 'src/index.ts', + }, + formats: ['es', 'cjs', 'iife'], + fileName: (format, entryName) => { + switch (format) { + case 'es': + return `${entryName}.mjs` + case 'cjs': + return `${entryName}.cjs` + case 'iife': + return `${entryName}.min.js` + default: + return `${entryName}.js` + } + }, + name: 'IpLookup', + }, + sourcemap: true, + rollupOptions: { + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/country-extra"', + __DATA_TYPE__: '"country"', + }), + ], + }, + }, + plugins: [ + checker({ + typescript: true, + }), + dts(), + { + name: 'createBrowserIndex', + async buildStart() { + const settings: IpLocationApiInputSettings = { + dataDir: resolve('../../data/country'), + tmpDataDir: resolve('../../tmp/country'), + fields: ['country'], + silent: true, + } + await update(settings) + await createBrowserIndex('country', settings, resolve('./indexes')) + }, + }, + ], +}) diff --git a/packages/country-extra/vitest.config.ts b/packages/country-extra/vitest.config.ts new file mode 100644 index 0000000..156ef2a --- /dev/null +++ b/packages/country-extra/vitest.config.ts @@ -0,0 +1,13 @@ +import replace from '@rollup/plugin-replace' + +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/country-extra"', + __DATA_TYPE__: '"country"', + }), + ], +}) diff --git a/packages/country/README.md b/packages/country/README.md new file mode 100644 index 0000000..4d59799 --- /dev/null +++ b/packages/country/README.md @@ -0,0 +1,47 @@ +# @iplookup/country + +[![npm version](https://badge.fury.io/js/%40iplookup%2Fcountry.svg)](https://badge.fury.io/js/%40iplookup%2Fcountry) +[![Downloads](https://img.shields.io/npm/dm/%40iplookup%2Fcountry.svg)](https://www.npmjs.com/package/%40iplookup%2Fcountry) +[![Build](https://github.com/sapics/ip-location-api/actions/workflows/build.yml/badge.svg)](https://github.com/sapics/ip-location-api/actions/workflows/build.yml) + +## Usage + +### Browser + +```html + + +``` + +### ESM + +```ts +import IpLookup from '@iplookup/country' + +const location = await IpLookup('207.97.227.239') +``` + +### CommonJS + +```ts +const IpLookup = require('@iplookup/country') + +const location = await IpLookup('207.97.227.239') +``` + +If you need extra information about country, try to use [@iplookup/country-extra](https://github.com/sapics/ip-location-api/tree/main/browser/country-extra). + +## License + +Since each user download a partial database, we use the CC0 Licensed database [geo-whois-asn-country](https://github.com/sapics/ip-location-db/tree/main/geo-whois-asn-country) for ip to country mapping to avoid license problem. + +The software itself is published under MIT License by [sapics](https://github.com/sapics). diff --git a/packages/country/package.json b/packages/country/package.json new file mode 100644 index 0000000..6d0c3df --- /dev/null +++ b/packages/country/package.json @@ -0,0 +1,50 @@ +{ + "name": "@iplookup/country", + "type": "module", + "version": "2.0.0", + "description": "Browser api to lookup country from IP address", + "author": "sapics", + "license": "MIT", + "homepage": "https://github.com/sapics/ip-location-api", + "repository": { + "type": "git", + "url": "git://github.com/sapics/ip-location-api.git", + "directory": "packages/country" + }, + "keywords": [ + "location", + "iplookup", + "lookup", + "geo", + "geoip", + "ip", + "ipv4", + "ipv6", + "ip-location-db", + "country" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "indexes" + ], + "scripts": { + "build": "vite build", + "build:watch": "vite build --watch", + "release": "bumpp package.json --commit --push --tag" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@iplookup/util": "workspace:*" + } +} diff --git a/packages/country/src/index.test.ts b/packages/country/src/index.test.ts new file mode 100644 index 0000000..25d6516 --- /dev/null +++ b/packages/country/src/index.test.ts @@ -0,0 +1,36 @@ +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import IpLookup from './index' + +describe('ipLookup', () => { + it('should export the IpLookup function', () => { + expect.soft(IpLookup).toBeDefined() + expect.soft(typeof IpLookup).toBe('function') + expectTypeOf(IpLookup).toEqualTypeOf<(ipInput: string) => Promise<{ country: string } | null>>() + }) + + it('should return the correct country', async () => { + globalThis.fetch = vi.fn().mockImplementation((url: URL) => { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Headers(), + arrayBuffer: async () => { + const data = await readFile(resolve(__dirname, `../indexes${url.pathname.split('indexes')[1]}`)) + return data.buffer + }, + }) + }) + const result = await IpLookup('207.97.227.239') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ country: 'US' }) + + const result2 = await IpLookup('2607:F8B0:4005:801::200E') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ country: 'US' }) + + await expect.soft(IpLookup('invalid')).rejects.toThrow('Invalid IPv4 address: invalid') + await expect.soft(IpLookup('0000:0000:0000:0000:0000:0000:0000:0000')).resolves.toBeNull() + }) +}) diff --git a/packages/country/src/index.ts b/packages/country/src/index.ts new file mode 100644 index 0000000..f3e40b9 --- /dev/null +++ b/packages/country/src/index.ts @@ -0,0 +1,19 @@ +import { setup } from '@iplookup/util/browser' + +const BaseLookup = setup<'country'>() + +/** + * Lookup country from IP address + * @param ipInput - IP address to lookup + * @returns country code. `null` if not found. + */ +export default async function IpLookup(ipInput: string): Promise<{ + /** + * The country of the IP address + * @example 'NL' + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country: string +} | null> { + return BaseLookup(ipInput) +} diff --git a/packages/country/tsconfig.json b/packages/country/tsconfig.json new file mode 100644 index 0000000..0f3afd7 --- /dev/null +++ b/packages/country/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + } +} diff --git a/packages/country/vite.config.ts b/packages/country/vite.config.ts new file mode 100644 index 0000000..3e68fac --- /dev/null +++ b/packages/country/vite.config.ts @@ -0,0 +1,62 @@ +import type { IpLocationApiInputSettings } from '@iplookup/util' +import { resolve } from 'node:path' +import { createBrowserIndex } from '@iplookup/util/browserIndex' +import { update } from '@iplookup/util/db' +import replace from '@rollup/plugin-replace' +import { defineConfig } from 'vite' +import checker from 'vite-plugin-checker' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + emptyOutDir: true, + lib: { + entry: { + index: 'src/index.ts', + }, + formats: ['es', 'cjs', 'iife'], + fileName: (format, entryName) => { + switch (format) { + case 'es': + return `${entryName}.mjs` + case 'cjs': + return `${entryName}.cjs` + case 'iife': + return `${entryName}.min.js` + default: + return `${entryName}.js` + } + }, + name: 'IpLookup', + }, + sourcemap: true, + rollupOptions: { + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/country"', + __DATA_TYPE__: '"country"', + }), + ], + }, + }, + plugins: [ + checker({ + typescript: true, + }), + dts(), + { + name: 'createBrowserIndex', + async buildStart() { + const settings: IpLocationApiInputSettings = { + dataDir: resolve('../../data/country'), + tmpDataDir: resolve('../../tmp/country'), + fields: ['country'], + silent: true, + } + await update(settings) + await createBrowserIndex('country', settings, resolve('./indexes')) + }, + }, + ], +}) diff --git a/packages/country/vitest.config.ts b/packages/country/vitest.config.ts new file mode 100644 index 0000000..1cf82d4 --- /dev/null +++ b/packages/country/vitest.config.ts @@ -0,0 +1,13 @@ +import replace from '@rollup/plugin-replace' + +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/country"', + __DATA_TYPE__: '"country"', + }), + ], +}) diff --git a/packages/geocode-extra/README.md b/packages/geocode-extra/README.md new file mode 100644 index 0000000..ebf891c --- /dev/null +++ b/packages/geocode-extra/README.md @@ -0,0 +1,59 @@ +# @iplookup/geocode-extra + +[![npm version](https://badge.fury.io/js/%40iplookup%2Fgeocode-extra.svg)](https://badge.fury.io/js/%40iplookup%2Fgeocode-extra) +[![Downloads](https://img.shields.io/npm/dm/%40iplookup%2Fgeocode-extra.svg)](https://www.npmjs.com/package/%40iplookup%2Fgeocode-extra) +[![Build](https://github.com/sapics/ip-location-api/actions/workflows/build.yml/badge.svg)](https://github.com/sapics/ip-location-api/actions/workflows/build.yml) + +## Usage + +### Browser + +```html + + +``` + +### ESM + +```ts +import IpLookup from '@iplookup/geocode-extra' + +const location = await IpLookup('207.97.227.239') +``` + +### CommonJS + +```ts +const IpLookup = require('@iplookup/geocode-extra') + +const location = await IpLookup('207.97.227.239') +``` + +If you do not need extra information about country, try to use [@iplookup/geocode](https://github.com/sapics/ip-location-api/tree/main/browser/geocode). + +## License + +The database for mapping ip to geocode is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by [DB-IP](https://db-ip.com/db/download/ip-to-city-lite). + +To get extra information about country, we use [Countries](https://github.com/annexare/Countries) which is published under MIT license by [Annexare Studio](https://annexare.com/). + +The software itself is published under MIT license by [sapics](https://github.com/sapics). diff --git a/packages/geocode-extra/package.json b/packages/geocode-extra/package.json new file mode 100644 index 0000000..a01b8ef --- /dev/null +++ b/packages/geocode-extra/package.json @@ -0,0 +1,52 @@ +{ + "name": "@iplookup/geocode-extra", + "type": "module", + "version": "2.0.0", + "description": "Browser api to lookup geocode from IP address", + "author": "sapics", + "license": "CC BY 4.0 and MIT", + "homepage": "https://github.com/sapics/ip-location-api", + "repository": { + "type": "git", + "url": "git://github.com/sapics/ip-location-api.git", + "directory": "packages/geocode-extra" + }, + "keywords": [ + "location", + "iplookup", + "lookup", + "geo", + "geocode", + "geoip", + "ip", + "ipv4", + "ipv6", + "ip-location-db", + "country" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "indexes" + ], + "scripts": { + "build": "vite build", + "build:watch": "vite build --watch", + "release": "bumpp package.json --commit --push --tag" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@iplookup/util": "workspace:*", + "countries-list": "^3.1.1" + } +} diff --git a/packages/geocode-extra/src/index.test.ts b/packages/geocode-extra/src/index.test.ts new file mode 100644 index 0000000..71ce513 --- /dev/null +++ b/packages/geocode-extra/src/index.test.ts @@ -0,0 +1,77 @@ +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import IpLookup from './index' + +describe('ipLookup', () => { + it('should export the IpLookup function', () => { + expect.soft(IpLookup).toBeDefined() + expect.soft(typeof IpLookup).toBe('function') + expectTypeOf(IpLookup).toEqualTypeOf<(ipInput: string) => Promise<{ + latitude: number + longitude: number + country: string + country_name: string + country_native: string + continent: string + capital: string + phone: number[] + currency: string[] + languages: string[] + } | null>>() + }) + + it('should return the correct geocode', async () => { + globalThis.fetch = vi.fn().mockImplementation((url: URL) => { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Headers(), + arrayBuffer: async () => { + const data = await readFile(resolve(__dirname, `../indexes${url.pathname.split('indexes')[1]}`)) + return data.buffer + }, + }) + }) + const result = await IpLookup('207.97.227.239') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ + country: 'US', + country_name: 'United States', + country_native: 'United States', + continent: 'NA', + capital: 'Washington D.C.', + phone: [1], + currency: [ + 'USD', + 'USN', + 'USS', + ], + languages: ['en'], + latitude: 38.9072, + longitude: -77.0369, + }) + + const result2 = await IpLookup('2607:F8B0:4005:801::200E') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ + country: 'US', + country_name: 'United States', + country_native: 'United States', + continent: 'NA', + capital: 'Washington D.C.', + phone: [1], + currency: [ + 'USD', + 'USN', + 'USS', + ], + languages: ['en'], + latitude: 37.422, + longitude: -122.084, + }) + + await expect.soft(IpLookup('invalid')).rejects.toThrow('Invalid IPv4 address: invalid') + await expect.soft(IpLookup('0000:0000:0000:0000:0000:0000:0000:0000')).resolves.toBeNull() + }) +}) diff --git a/packages/geocode-extra/src/index.ts b/packages/geocode-extra/src/index.ts new file mode 100644 index 0000000..fbc3ca6 --- /dev/null +++ b/packages/geocode-extra/src/index.ts @@ -0,0 +1,83 @@ +import { setup } from '@iplookup/util/browser' +import { countries } from 'countries-list' + +const BaseLookup = setup<'geocode'>() + +/** + * Lookup geocode from IP address + * @param ipInput - IP address to lookup + * @returns latitude, longitude, country code, country name, country native name, continent, capital, phone, currency and languages. `null` if not found. + */ +export default async function IpLookup(ipInput: string): Promise<{ + /** + * The approximate WGS84 latitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + latitude: number + /** + * The approximate WGS84 longitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + longitude: number + /** + * The country of the IP address + * @example 'NL' + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country: string + /** + * The name of the country of the IP address (In English) + * @example 'Netherlands' + */ + country_name: string + /** + * The name of the country of the IP address (In the native language of the country) + * @example 'Nederland' + */ + country_native: string + /** + * The continent of the IP address (alpha-2 code) + * @example 'EU' + */ + continent: string + /** + * The capital of the country of the IP address (In English) + * @example 'Amsterdam' + */ + capital: string + /** + * The phone codes of the country of the IP address + * @example ['31'] + */ + phone: number[] + /** + * The currency of the country of the IP address + * @example ['EUR'] + */ + currency: string[] + /** + * The languages of the country of the IP address + * @example ['nl'] + */ + languages: string[] +} | null> { + const result = await BaseLookup(ipInput) + if (!result) + return null + + const country = countries[result.country as keyof typeof countries] + return { + latitude: result.latitude, + longitude: result.longitude, + country: result.country, + country_name: country.name, + country_native: country.native, + continent: country.continent, + capital: country.capital, + phone: country.phone, + currency: country.currency, + languages: country.languages, + } +} diff --git a/packages/geocode-extra/tsconfig.json b/packages/geocode-extra/tsconfig.json new file mode 100644 index 0000000..0f3afd7 --- /dev/null +++ b/packages/geocode-extra/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + } +} diff --git a/packages/geocode-extra/vite.config.ts b/packages/geocode-extra/vite.config.ts new file mode 100644 index 0000000..215a1b1 --- /dev/null +++ b/packages/geocode-extra/vite.config.ts @@ -0,0 +1,62 @@ +import type { IpLocationApiInputSettings } from '@iplookup/util' +import { resolve } from 'node:path' +import { createBrowserIndex } from '@iplookup/util/browserIndex' +import { update } from '@iplookup/util/dbIpUpdate' +import replace from '@rollup/plugin-replace' +import { defineConfig } from 'vite' +import checker from 'vite-plugin-checker' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + emptyOutDir: true, + lib: { + entry: { + index: 'src/index.ts', + }, + formats: ['es', 'cjs', 'iife'], + fileName: (format, entryName) => { + switch (format) { + case 'es': + return `${entryName}.mjs` + case 'cjs': + return `${entryName}.cjs` + case 'iife': + return `${entryName}.min.js` + default: + return `${entryName}.js` + } + }, + name: 'IpLookup', + }, + sourcemap: true, + rollupOptions: { + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/geocode-extra"', + __DATA_TYPE__: '"geocode"', + }), + ], + }, + }, + plugins: [ + checker({ + typescript: true, + }), + dts(), + { + name: 'createBrowserIndex', + async buildStart() { + const settings: IpLocationApiInputSettings = { + dataDir: resolve('../../data/geocode'), + tmpDataDir: resolve('../../tmp/geocode'), + fields: ['latitude', 'longitude'], + silent: true, + } + await update(settings) + await createBrowserIndex('geocode', settings, resolve('./indexes')) + }, + }, + ], +}) diff --git a/packages/geocode-extra/vitest.config.ts b/packages/geocode-extra/vitest.config.ts new file mode 100644 index 0000000..cc55495 --- /dev/null +++ b/packages/geocode-extra/vitest.config.ts @@ -0,0 +1,13 @@ +import replace from '@rollup/plugin-replace' + +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/geocode-extra"', + __DATA_TYPE__: '"geocode"', + }), + ], +}) diff --git a/packages/geocode/README.md b/packages/geocode/README.md new file mode 100644 index 0000000..6f04c61 --- /dev/null +++ b/packages/geocode/README.md @@ -0,0 +1,49 @@ +# @iplookup/geocode + +[![npm version](https://badge.fury.io/js/%40iplookup%2Fgeocode.svg)](https://badge.fury.io/js/%40iplookup%2Fgeocode) +[![Downloads](https://img.shields.io/npm/dm/%40iplookup%2Fgeocode.svg)](https://www.npmjs.com/package/%40iplookup%2Fgeocode) +[![Build](https://github.com/sapics/ip-location-api/actions/workflows/build.yml/badge.svg)](https://github.com/sapics/ip-location-api/actions/workflows/build.yml) + +## Usage + +### Browser + +```html + + +``` + +### ESM + +```ts +import IpLookup from '@iplookup/geocode' + +const location = await IpLookup('207.97.227.239') +``` + +### CommonJS + +```ts +const IpLookup = require('@iplookup/geocode') + +const location = await IpLookup('207.97.227.239') +``` + +If you need extra information about country, try to use [@iplookup/geocode-extra](https://github.com/sapics/ip-location-api/tree/main/browser/geocode-extra). + +## License + +The database is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by [DB-IP](https://db-ip.com/db/download/ip-to-city-lite). + +The software itself is published under MIT license by [sapics](https://github.com/sapics). diff --git a/packages/geocode/package.json b/packages/geocode/package.json new file mode 100644 index 0000000..def4989 --- /dev/null +++ b/packages/geocode/package.json @@ -0,0 +1,51 @@ +{ + "name": "@iplookup/geocode", + "type": "module", + "version": "2.0.0", + "description": "Browser api to lookup geocode from IP address", + "author": "sapics", + "license": "CC BY 4.0 and MIT", + "homepage": "https://github.com/sapics/ip-location-api", + "repository": { + "type": "git", + "url": "git://github.com/sapics/ip-location-api.git", + "directory": "packages/geocode" + }, + "keywords": [ + "location", + "iplookup", + "lookup", + "geo", + "geocode", + "geoip", + "ip", + "ipv4", + "ipv6", + "ip-location-db", + "country" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "indexes" + ], + "scripts": { + "build": "vite build", + "build:watch": "vite build --watch", + "release": "bumpp package.json --commit --push --tag" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@iplookup/util": "workspace:*" + } +} diff --git a/packages/geocode/src/index.test.ts b/packages/geocode/src/index.test.ts new file mode 100644 index 0000000..5149af4 --- /dev/null +++ b/packages/geocode/src/index.test.ts @@ -0,0 +1,48 @@ +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { describe, expect, expectTypeOf, it, vi } from 'vitest' +import IpLookup from './index' + +describe('ipLookup', () => { + it('should export the IpLookup function', () => { + expect.soft(IpLookup).toBeDefined() + expect.soft(typeof IpLookup).toBe('function') + expectTypeOf(IpLookup).toEqualTypeOf<(ipInput: string) => Promise<{ + latitude: number + longitude: number + country: string + } | null>>() + }) + + it('should return the correct geocode', async () => { + globalThis.fetch = vi.fn().mockImplementation((url: URL) => { + return Promise.resolve({ + ok: true, + status: 200, + headers: new Headers(), + arrayBuffer: async () => { + const data = await readFile(resolve(__dirname, `../indexes${url.pathname.split('indexes')[1]}`)) + return data.buffer + }, + }) + }) + const result = await IpLookup('207.97.227.239') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ + country: 'US', + latitude: 38.9072, + longitude: -77.0369, + }) + + const result2 = await IpLookup('2607:F8B0:4005:801::200E') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ + country: 'US', + latitude: 37.422, + longitude: -122.084, + }) + + await expect.soft(IpLookup('invalid')).rejects.toThrow('Invalid IPv4 address: invalid') + await expect.soft(IpLookup('0000:0000:0000:0000:0000:0000:0000:0000')).resolves.toBeNull() + }) +}) diff --git a/packages/geocode/src/index.ts b/packages/geocode/src/index.ts new file mode 100644 index 0000000..5469b69 --- /dev/null +++ b/packages/geocode/src/index.ts @@ -0,0 +1,31 @@ +import { setup } from '@iplookup/util/browser' + +const BaseLookup = setup<'geocode'>() + +/** + * Lookup geocode from IP address + * @param ipInput - IP address to lookup + * @returns country code, latitude and longitude. `null` if not found. + */ +export default async function IpLookup(ipInput: string): Promise<{ + /** + * The approximate WGS84 latitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + latitude: number + /** + * The approximate WGS84 longitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + longitude: number + /** + * The country of the IP address + * @example 'NL' + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country: string +} | null> { + return BaseLookup(ipInput) +} diff --git a/packages/geocode/tsconfig.json b/packages/geocode/tsconfig.json new file mode 100644 index 0000000..0f3afd7 --- /dev/null +++ b/packages/geocode/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + } +} diff --git a/packages/geocode/vite.config.ts b/packages/geocode/vite.config.ts new file mode 100644 index 0000000..c841844 --- /dev/null +++ b/packages/geocode/vite.config.ts @@ -0,0 +1,62 @@ +import type { IpLocationApiInputSettings } from '@iplookup/util' +import { resolve } from 'node:path' +import { createBrowserIndex } from '@iplookup/util/browserIndex' +import { update } from '@iplookup/util/dbIpUpdate' +import replace from '@rollup/plugin-replace' +import { defineConfig } from 'vite' +import checker from 'vite-plugin-checker' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + emptyOutDir: true, + lib: { + entry: { + index: 'src/index.ts', + }, + formats: ['es', 'cjs', 'iife'], + fileName: (format, entryName) => { + switch (format) { + case 'es': + return `${entryName}.mjs` + case 'cjs': + return `${entryName}.cjs` + case 'iife': + return `${entryName}.min.js` + default: + return `${entryName}.js` + } + }, + name: 'IpLookup', + }, + sourcemap: true, + rollupOptions: { + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/geocode"', + __DATA_TYPE__: '"geocode"', + }), + ], + }, + }, + plugins: [ + checker({ + typescript: true, + }), + dts(), + { + name: 'createBrowserIndex', + async buildStart() { + const settings: IpLocationApiInputSettings = { + dataDir: resolve('../../data/geocode'), + tmpDataDir: resolve('../../tmp/geocode'), + fields: ['latitude', 'longitude'], + silent: true, + } + await update(settings) + await createBrowserIndex('geocode', settings, resolve('./indexes')) + }, + }, + ], +}) diff --git a/packages/geocode/vitest.config.ts b/packages/geocode/vitest.config.ts new file mode 100644 index 0000000..0a1b2c2 --- /dev/null +++ b/packages/geocode/vitest.config.ts @@ -0,0 +1,13 @@ +import replace from '@rollup/plugin-replace' + +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [ + replace({ + preventAssignment: true, + __CDN_URL__: '"https://cdn.jsdelivr.net/npm/@iplookup/geocode"', + __DATA_TYPE__: '"geocode"', + }), + ], +}) diff --git a/packages/ip-location-api/README.md b/packages/ip-location-api/README.md new file mode 100644 index 0000000..9647246 --- /dev/null +++ b/packages/ip-location-api/README.md @@ -0,0 +1,112 @@ +# ip-location-api + +[![npm version](https://badge.fury.io/js/ip-location-api.svg)](https://badge.fury.io/js/ip-location-api) +[![Downloads](https://img.shields.io/npm/dm/ip-location-api.svg)](https://www.npmjs.com/package/ip-location-api) +[![Build](https://github.com/sapics/ip-location-api/actions/workflows/build.yml/badge.svg)](https://github.com/sapics/ip-location-api/actions/workflows/build.yml) + +## Usage + +```ts +import { clear, lookup, reload, update } from 'ip-location-api' + +await reload({ fields: ['country'] }) + +const location = await lookup('8.8.8.8') +console.log(location) // { country: 'US' } + +await update({ fields: ['country', 'city'] }) // Update the database from the remote source +await reload({ fields: ['country', 'city'] }) // Reload the in-memory database with the updated data + +const location2 = await lookup('8.8.8.8') +console.log(location2) // { country: 'US', city: 'Mountain View' } + +clear() // Clear the in-memory database + +const location3 = await lookup('8.8.8.8') +console.log(location3) // null +``` + +## Configuration + +You can configure the library in three ways: + +- Environment variables: `ILA_FIELDS=country,city` +- CLI arguments: `node index.js ILA_FIELDS=country,city` +- Configuration object: `reload({ fields: ['country', 'city'] })` + +| Option | ENV Variable | Default | Description | +| ------------------- | -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| fields | ILA_FIELDS | country | Specify which fields to retrieve from MaxMind. Use "all" to display all available fields. | +| addCountryInfo | ILA_ADD_COUNTRY_INFO | false | When set to "true", adds additional country information from the Countries database. Requires the "country" field to be selected. | +| dataDir | ILA_DATA_DIR | ../data | Directory path for the database file | +| tmpDataDir | ILA_TMP_DATA_DIR | ../tmp | Directory path for temporary files | +| smallMemory | ILA_SMALL_MEMORY | false | When true, operates in asynchronous mode. When false, operates in synchronous mode. | +| smallMemoryFileSize | ILA_SMALL_MEMORY_FILE_SIZE | 4096 | Maximum file size for asynchronous data processing (changing not recommended) | +| licenseKey | ILA_LICENSE_KEY | redist | MaxMind license key for downloading the latest database. Set to "redist" to use the redistributed database from node-geolite2-redist. | +| series | ILA_SERIES | GeoLite2 | Set to "GeoIP2" to use the premium GeoIP2 database | +| language | ILA_LANGUAGE | en | Supported languages: "de", "en", "es", "fr", "ja", "pt-BR", "ru", "zh-CN". Affects the language of region and city names. | +| silent | ILA_SILENT | false | When true, suppresses non-essential console output | + +## Operating Modes + +The library operates in two modes: + +1. **Synchronous Mode** (default): + + - Loads all data into memory at startup + - Higher memory usage + - Slower startup time + - Very fast lookups (>300x faster than async) + +2. **Asynchronous Mode** (`smallMemory: true`): + - Minimal memory footprint + - Fast startup time + - Slower lookups (loads data from disk as needed) + +For optimal performance, use synchronous mode when sufficient memory is available. + +## Available Fields + +The library aims to maintain compatibility with `geoip-lite` field names where possible, while offering additional fields: + +| Field Name | geoip-lite | Source | Description | +| -------------- | ---------- | --------- | ---------------------------------------------- | +| country | ✓ | MaxMind | Two-letter country code (ISO-3166-1 alpha-2) | +| region1 | region | MaxMind | First-level region code (ISO 3166-2) | +| region1_name | ✗ | MaxMind | First-level region name (localized) | +| region2 | ✗ | MaxMind | Second-level region code (ISO 3166-2) | +| region2_name | ✗ | MaxMind | Second-level region name (localized) | +| city | ✓ | MaxMind | City name (localized) | +| metro | ✓ | MaxMind | Google Geolocation target code | +| eu | ✓ | MaxMind | EU membership flag (true for EU member states) | +| timezone | ✓ | MaxMind | Time zone identifier | +| latitude | ll[0] | MaxMind | WGS84 latitude | +| longitude | ll[1] | MaxMind | WGS84 longitude | +| area | ✓ | MaxMind | Accuracy radius in kilometers | +| postcode | ✗ | MaxMind | Postal code | +| country_name | ✗ | Countries | Country name in English | +| country_native | ✗ | Countries | Country name in native language | +| continent | ✗ | Countries | Continent code | +| continent_name | ✗ | Countries | Continent name | +| capital | ✗ | Countries | Capital city name | +| phone | ✗ | Countries | International calling code | +| currency | ✗ | Countries | Common currencies | +| languages | ✗ | Countries | Common languages | + +## License and Terms of Use + +This project includes multiple components with different licenses: + +1. **Software Library**: MIT License (© sapics) + +2. **GeoLite2 Database**: CC BY-SA 4.0 (© MaxMind) + + - Usage restrictions: + - No FCRA-regulated uses + - No identification of specific households/individuals + - Must allow database updates + - Attribution to MaxMind required + + [View full GeoLite2 EULA](https://www.maxmind.com/en/geolite2/eula) + +3. **Countries Database**: MIT License (© Annexare Studio) diff --git a/packages/ip-location-api/package.json b/packages/ip-location-api/package.json new file mode 100644 index 0000000..805a4a5 --- /dev/null +++ b/packages/ip-location-api/package.json @@ -0,0 +1,66 @@ +{ + "name": "ip-location-api", + "type": "module", + "version": "3.0.0", + "description": "Fast location lookup from IP address", + "author": "sapics", + "license": "Multiple licenses", + "homepage": "https://github.com/sapics/ip-location-api", + "repository": { + "type": "git", + "url": "git://github.com/sapics/ip-location-api.git", + "directory": "packages/ip-location-api" + }, + "keywords": [ + "location", + "lookup", + "geo", + "geoip", + "geolite", + "maxmind", + "ip", + "ipv4", + "ipv6", + "ip-location-db", + "country", + "city" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "build:watch": "vite build --watch", + "release": "bumpp package.json --commit --push --tag" + }, + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "countries-list": "^3.1.1" + }, + "peerDependenciesMeta": { + "countries-list": { + "optional": true + } + }, + "dependencies": { + "@fast-csv/parse": "^5.0.0", + "ip-address": "^9.0.5", + "ky": "^1.7.2", + "yauzl": "^3.1.3" + }, + "devDependencies": { + "@iplookup/util": "workspace:*", + "countries-list": "^3.1.1" + } +} diff --git a/packages/ip-location-api/src/functions/lookup.ts b/packages/ip-location-api/src/functions/lookup.ts new file mode 100644 index 0000000..62978cc --- /dev/null +++ b/packages/ip-location-api/src/functions/lookup.ts @@ -0,0 +1,382 @@ +import { Buffer } from 'node:buffer' +import { open } from 'node:fs/promises' +import { join } from 'node:path' +import { binarySearch, getSmallMemoryFile, type IpLocationApiSettings, type LocalDatabase, log, number37ToString, parseIp, SAVED_SETTINGS } from '@iplookup/util' +import { LOADED_DATA } from './reload.js' + +/** + * Represents geographical data for an IP address + */ +interface GeoData { + /** + * The approximate WGS84 latitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + latitude?: number + /** + * The approximate WGS84 longitude of the IP address + * + * @see https://en.wikipedia.org/wiki/World_Geodetic_System + */ + longitude?: number + /** + * The region-specific postcode nearest to the IP address + */ + postcode?: string + /** + * The radius in kilometers of the specified location where the IP address is likely to be + */ + area?: number + /** + * The country of the IP address + * @example 'NL' + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country?: string + /** + * Whether the country is a member of the European Union + * + * @requires country + */ + eu?: boolean + /** + * The first region of the IP address + * @example 'NL-ZH' // (Netherlands, South Holland) + * @see https://en.wikipedia.org/wiki/ISO_3166-2 + */ + region1?: string + /** + * The name of the first region of the IP address (Can be aquired in multiple languages using the `language` setting) + * @example 'South Holland' + */ + region1_name?: string + /** + * The second region of the IP address + * @example 'NL-ZH' // (Netherlands, South Holland) + * @see https://en.wikipedia.org/wiki/ISO_3166-2 + */ + region2?: string + /** + * The name of the second region of the IP address (Can be aquired in multiple languages using the `language` setting) + * @example 'South Holland' + */ + region2_name?: string + /** + * The metropolitan area of the IP address (Google AdWords API) + * @example 1000 + * @see https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions + */ + metro?: number + /** + * The timezone of the IP address + * @example 'Europe/Amsterdam' + */ + timezone?: string + /** + * The city of the IP address + * @example 'Amsterdam' + */ + city?: string + /** + * The name of the country of the IP address (In English) + * @example 'Netherlands' + * @requires country + * @requires country-list (optional peer dependency) + */ + country_name?: string + /** + * The name of the country of the IP address (In the native language of the country) + * @example 'Nederland' + * @requires country + * @requires country-list (optional peer dependency) + */ + country_native?: string + /** + * The continent of the IP address (alpha-2 code) + * @example 'EU' + * @requires country + * @requires country-list (optional peer dependency) + */ + continent?: string + /** + * The name of the continent of the IP address (In English) + * @example 'Europe' + * @requires country + * @requires country-list (optional peer dependency) + */ + continent_name?: string + /** + * The capital of the country of the IP address (In English) + * @example 'Amsterdam' + * @requires country + * @requires country-list (optional peer dependency) + */ + capital?: string + /** + * The phone codes of the country of the IP address + * @example ['31'] + * @requires country + * @requires country-list (optional peer dependency) + */ + phone?: number[] + /** + * The currency of the country of the IP address + * @example ['EUR'] + * @requires country + * @requires country-list (optional peer dependency) + */ + currency?: string[] + /** + * The languages of the country of the IP address + * @example ['nl'] + */ + languages?: string[] +} + +/** + * Looks up geographical data for a given IP address + * @param ip - The IP address to look up + * @returns A Promise that resolves to GeoData or null if not found + */ +export async function lookup(ip: string): Promise { + //* We don't use net.isIP(ip) as it is slow for ipv6 + const { version, ip: ipNumber } = parseIp(ip) + + const settings = SAVED_SETTINGS + const db = version === 4 ? settings.v4 : settings.v6 + + if (!db.loadedData || !(ipNumber >= db.loadedData.firstIp)) + return null + const list = db.loadedData.startIps + const line = binarySearch(list, ipNumber) + /* c8 ignore next 2 */ //* Line should never be null + if (line === null) + return null + + if (settings.smallMemory) { + const buffer = await lineToFile(line, db, settings) + const endIp = version === 4 ? buffer.readUInt32LE(0) : buffer.readBigUInt64LE(0) + if (ipNumber > endIp) + return null + + if (settings.dataType === 'Country') { + return setCountryInfo({ + country: buffer.toString('latin1', version === 4 ? 4 : 8, version === 4 ? 6 : 10), + }, settings) + } + return setCityInfo(buffer, version === 4 ? 4 : 8, settings) + } + + const endIps = db.loadedData.endIps + if (!endIps || ipNumber > endIps[line]!) + return null + + if (settings.dataType === 'Country') { + return setCountryInfo({ + country: db.loadedData.mainBuffer!.toString('latin1', line * db.recordSize, line * db.recordSize + 2), + }, settings) + } + return setCityInfo(db.loadedData.mainBuffer!, line * db.recordSize, settings) +} + +/** + * Reads a specific line from a file in the database + * @param line - The line number to read + * @param db - The database object + * @param settings - The API settings + * @returns A Promise that resolves to a Buffer containing the line data + */ +async function lineToFile(line: number, db: LocalDatabase, settings: IpLocationApiSettings): Promise { + const [dir, file, offset] = getSmallMemoryFile(line, db) + const fd = await open(join(settings.fieldDir, dir, file), 'r') + const buffer = Buffer.alloc(db.recordSize) + await fd.read(buffer, 0, db.recordSize, offset) + fd.close().catch(() => { + log('warn', 'Failed to close file descriptor') + }) + return buffer +} + +/** + * Extracts city information from a buffer + * @param buffer - The buffer containing city data + * @param offset - The starting offset in the buffer + * @param settings - The API settings + * @returns A Promise that resolves to GeoData + */ +function setCityInfo(buffer: Buffer, offset: number, settings: IpLocationApiSettings): Promise { + let locationId: number | undefined + const geodata: GeoData = {} + + //* Read location ID if location file is available + if (settings.locationFile) { + locationId = buffer.readUInt32LE(offset) + offset += 4 + } + + //* Read latitude and longitude if included in fields + if (settings.fields.includes('latitude')) { + geodata.latitude = buffer.readInt32LE(offset) / 10000 + offset += 4 + } + if (settings.fields.includes('longitude')) { + geodata.longitude = buffer.readInt32LE(offset) / 10000 + offset += 4 + } + + //* Read and decode postcode if included in fields + if (settings.fields.includes('postcode')) { + const postcodeValue = buffer.readUInt32LE(offset) + const postcodeLength = buffer.readInt8(offset + 4) + if (postcodeValue) { + let postcode: string + if (postcodeLength < -9) { + const code = (-postcodeLength).toString() + postcode = postcodeValue.toString(36) + postcode = `${getZeroFill( + postcode.slice(0, -Number.parseInt(code[1]!)), + Number.parseInt(code[0]!) - 0, + )}-${getZeroFill(postcode.slice(-Number.parseInt(code[1]!)), Number.parseInt(code[1]!) - 0)}` + } + else if (postcodeLength < 0) { + postcode = getZeroFill(postcodeValue.toString(36), -postcodeLength) + } + else if (postcodeLength < 10) { + postcode = getZeroFill(postcodeValue.toString(10), postcodeLength) + } + else if (postcodeLength < 72) { + const code = String(postcodeLength) + postcode = getZeroFill(postcodeValue.toString(10), (Number.parseInt(code[0]!) - 0) + (Number.parseInt(code[1]!) - 0)) + postcode = `${postcode.slice(0, Number.parseInt(code[0]!) - 0)}-${postcode.slice(Number.parseInt(code[0]!) - 0)}` + /* c8 ignore next 4 */ //* Should never happen + } + else { + postcode = postcodeLength.toString(36).slice(1) + postcodeValue.toString(36) + } + geodata.postcode = postcode.toUpperCase() + } + offset += 5 + } + + //* Read area if included in fields + if (settings.fields.includes('area')) { + const areaMap = LOADED_DATA.sub?.area + if (areaMap) { + geodata.area = areaMap[buffer.readUInt8(offset)] + } + // offset += 1 + } + + //* Process location data if locationId is available + if (locationId) { + let locationOffset = (locationId - 1) * settings.locationRecordSize + const locationBuffer = LOADED_DATA.location + if (locationBuffer) { + if (settings.fields.includes('country')) { + geodata.country = locationBuffer.toString('utf8', locationOffset, locationOffset += 2) + const euMap = LOADED_DATA.sub?.eu + if (settings.fields.includes('eu') && euMap) { + geodata.eu = euMap[geodata.country] + } + } + if (settings.fields.includes('region1')) { + const region1 = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + if (region1 > 0) { + geodata.region1 = number37ToString(region1) + } + } + if (settings.fields.includes('region1_name')) { + const region1Name = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + const region1Map = LOADED_DATA.sub?.region1 + if (region1Name > 0 && region1Map) { + geodata.region1_name = region1Map[region1Name] + } + } + if (settings.fields.includes('region2')) { + const region2 = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + if (region2 > 0) { + geodata.region2 = number37ToString(region2) + } + } + if (settings.fields.includes('region2_name')) { + const region2Name = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + const region2Map = LOADED_DATA.sub?.region2 + if (region2Name > 0 && region2Map) { + geodata.region2_name = region2Map[region2Name] + } + } + if (settings.fields.includes('metro')) { + const metro = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + if (metro > 0) { + geodata.metro = metro + } + } + if (settings.fields.includes('timezone')) { + const timezone = locationBuffer.readUInt16LE(locationOffset) + locationOffset += 2 + const timezoneMap = LOADED_DATA.sub?.timezone + if (timezone > 0 && timezoneMap) { + geodata.timezone = timezoneMap[timezone] + } + } + if (settings.fields.includes('city')) { + const city = locationBuffer.readUInt32LE(locationOffset) + // locationOffset += 4 + const cityMap = LOADED_DATA.city + if (city > 0 && cityMap) { + const start = city >>> 8 + geodata.city = cityMap.toString('utf8', start, start + (city & 255)) + } + } + } + } + + return setCountryInfo(geodata, settings) +} + +/** + * Pads a string with leading zeros + * @param text - The text to pad + * @param length - The desired length of the padded string + * @returns The zero-padded string + */ +function getZeroFill(text: string, length: number) { + return '0'.repeat(length - text.length) + text +} + +/** + * Adds additional country information to the GeoData object + * @param geodata - The GeoData object to enhance + * @param settings - The API settings + * @returns A Promise that resolves to the enhanced GeoData + */ +async function setCountryInfo(geodata: GeoData, settings: IpLocationApiSettings): Promise { + if (settings.addCountryInfo && geodata.country) { + //* Import the countries-list package (optional peer dependency) + try { + const { countries, continents } = await import('countries-list') + const country = countries[geodata.country as keyof typeof countries] + + //* Enhance geodata with additional country information + geodata.country_name = country.name + geodata.country_native = country.native + geodata.continent = country.continent + geodata.continent_name = continents[country.continent] + geodata.capital = country.capital + geodata.phone = country.phone + geodata.currency = country.currency + geodata.languages = country.languages + /* c8 ignore next 5 */ //* We don't check the try-catch as it's an optional peer dependency + } + catch (error) { + log('warn', 'Error importing countries-list', error) + } + } + return geodata +} diff --git a/packages/ip-location-api/src/functions/reload.ts b/packages/ip-location-api/src/functions/reload.ts new file mode 100644 index 0000000..d13c773 --- /dev/null +++ b/packages/ip-location-api/src/functions/reload.ts @@ -0,0 +1,140 @@ +import type { IpLocationApiInputSettings } from '@iplookup/util' +import type { Buffer } from 'node:buffer' +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { getSettings, SAVED_SETTINGS } from '@iplookup/util' +import { update } from '@iplookup/util/db' + +export const LOADED_DATA: { + location?: Buffer + city?: Buffer + sub?: { + region1?: string[] + region2?: string[] + timezone?: string[] + area?: number[] + eu?: Record + } +} = {} + +/** + * Reloads the IP location data based on the provided settings. + * @param inputSettings - Optional input settings to override the default settings. + */ +export async function reload(inputSettings?: IpLocationApiInputSettings): Promise { + const settings = getSettings(inputSettings) + + //* If the data directory doesn't exist, update the database + if (!existsSync(join(settings.fieldDir, '4-1.dat'))) { + await update(settings) + } + + const buffers = { + v4: { + dat1: undefined as Buffer | undefined, + dat2: undefined as Buffer | undefined, + dat3: undefined as Buffer | undefined, + }, + v6: { + dat1: undefined as Buffer | undefined, + dat2: undefined as Buffer | undefined, + dat3: undefined as Buffer | undefined, + }, + location: undefined as Buffer | undefined, + city: undefined as Buffer | undefined, + sub: undefined as Buffer | undefined, + } + + //* Create an array of promises to read all necessary files + const promises: Promise[] = [ + readFile(join(settings.fieldDir, '4-1.dat')).then((buffer) => { buffers.v4.dat1 = buffer }), + readFile(join(settings.fieldDir, '6-1.dat')).then((buffer) => { buffers.v6.dat1 = buffer }), + ] + + //* Add additional file reading promises if not in small memory mode + if (!settings.smallMemory) { + promises.push( + readFile(join(settings.fieldDir, '4-2.dat')).then((buffer) => { buffers.v4.dat2 = buffer }), + readFile(join(settings.fieldDir, '4-3.dat')).then((buffer) => { buffers.v4.dat3 = buffer }), + readFile(join(settings.fieldDir, '6-2.dat')).then((buffer) => { buffers.v6.dat2 = buffer }), + readFile(join(settings.fieldDir, '6-3.dat')).then((buffer) => { buffers.v6.dat3 = buffer }), + ) + } + + //* Add location-related file reading promises based on settings + if (settings.locationFile) { + promises.push( + readFile(join(settings.fieldDir, 'location.dat')).then((buffer) => { buffers.location = buffer }), + ) + + if (settings.fields.includes('city')) { + promises.push( + readFile(join(settings.fieldDir, 'name.dat')).then((buffer) => { buffers.city = buffer }), + ) + } + + if (settings.fields.some(field => ['region1_name', 'region2_name', 'timezone', 'area', 'eu'].includes(field))) { + promises.push( + readFile(join(settings.fieldDir, 'sub.json')).then((buffer) => { buffers.sub = buffer }), + ) + } + } + + //* Wait for all file reading operations to complete + await Promise.all(promises) + + const v4 = settings.v4 + const v6 = settings.v6 + + //* Create typed arrays from the loaded buffer data + const v4StartIps = new Uint32Array(buffers.v4.dat1!.buffer, 0, buffers.v4.dat1!.byteLength >> 2) + const v6StartIps = new BigUint64Array(buffers.v6.dat1!.buffer, 0, buffers.v6.dat1!.byteLength >> 3) + + //* Set up v4 loaded data + v4.loadedData = { + startIps: v4StartIps, + endIps: buffers.v4.dat2 ? new Uint32Array(buffers.v4.dat2.buffer, 0, buffers.v4.dat2.byteLength >> 2) : undefined, + mainBuffer: buffers.v4.dat3, + lastLine: v4StartIps.length - 1, + firstIp: v4StartIps[0]!, + } + + //* Set up v6 loaded data + v6.loadedData = { + startIps: v6StartIps, + endIps: buffers.v6.dat2 ? new BigUint64Array(buffers.v6.dat2.buffer, 0, buffers.v6.dat2.byteLength >> 3) : undefined, + mainBuffer: buffers.v6.dat3, + lastLine: v6StartIps.length - 1, + firstIp: v6StartIps[0]!, + } + + //* Load additional data for City dataType + if (settings.dataType === 'City') { + LOADED_DATA.location = buffers.location + LOADED_DATA.city = buffers.city + if (buffers.sub) { + const subJson = JSON.parse(buffers.sub.toString()) + LOADED_DATA.sub = { + region1: subJson.region1_name, + region2: subJson.region2_name, + timezone: subJson.timezone, + area: subJson.area, + eu: subJson.eu, + } + } + } +} + +/** + * Clears the loaded IP location data from memory. + */ +export function clear(): void { + const settings = SAVED_SETTINGS + settings.v4.loadedData = undefined + settings.v6.loadedData = undefined + + LOADED_DATA.location = undefined + LOADED_DATA.city = undefined + LOADED_DATA.sub = undefined +} diff --git a/packages/ip-location-api/src/index.all.smallMemory.test.ts b/packages/ip-location-api/src/index.all.smallMemory.test.ts new file mode 100644 index 0000000..da642cf --- /dev/null +++ b/packages/ip-location-api/src/index.all.smallMemory.test.ts @@ -0,0 +1,158 @@ +import { randomUUID } from 'node:crypto' +import { rm } from 'node:fs/promises' +import { resolve } from 'node:path' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { clear, lookup, reload } from './index' + +describe('lookup (all, smallMemory)', () => { + const id = randomUUID() + + beforeAll(async () => { + await reload({ + dataDir: resolve(__dirname, '../data', id), + tmpDataDir: resolve(__dirname, '../tmp', id), + fields: 'all', + smallMemory: true, + addCountryInfo: true, + // silent: true, + }) + }, 25 * 60_000) + + afterAll(async () => { + clear() + await rm(resolve(__dirname, '../data', id), { recursive: true, force: true }) + await rm(resolve(__dirname, '../tmp', id), { recursive: true, force: true }) + }) + + it('should return all fields for a valid IP address', async ({ expect }) => { + //* Ipv4 (Metro code) + const result1 = await lookup('170.171.1.0') + expect.soft(result1).not.toBeNull() + expect.soft(result1).toEqual({ + area: 10, + capital: 'Washington D.C.', + city: 'New York', + continent: 'NA', + continent_name: 'North America', + country: 'US', + country_name: 'United States', + country_native: 'United States', + currency: [ + 'USD', + 'USN', + 'USS', + ], + eu: undefined, + languages: [ + 'en', + ], + latitude: 40.7621, + longitude: -73.9517, + metro: 501, + phone: [ + 1, + ], + postcode: '10044', + region1: 'NY', + region1_name: 'New York', + timezone: 'America/New_York', + }) + + //* Ipv4 (Region2) + const result2 = await lookup('213.189.170.238') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ + area: 50, + capital: 'Brussels', + city: 'Charleroi', + continent: 'EU', + continent_name: 'Europe', + country: 'BE', + country_name: 'Belgium', + country_native: 'België', + currency: [ + 'EUR', + ], + eu: true, + languages: [ + 'nl', + 'fr', + 'de', + ], + latitude: 50.4102, + longitude: 4.4472, + phone: [ + 32, + ], + postcode: '6000', + region1: 'WAL', + region1_name: 'Wallonia', + region2: 'WHT', + region2_name: 'Hainaut Province', + timezone: 'Europe/Brussels', + }) + + const postalCodesTest = [ + { + ip: '45.67.84.0', + postcode: 'EC1A', + }, + { + ip: '106.160.170.0', + postcode: '402-0051', + }, + { + ip: '172.94.24.0', + postcode: 'LV-1063', + }, + ] + + for (const { ip, postcode } of postalCodesTest) { + const result = await lookup(ip) + expect.soft(result).not.toBeNull() + expect.soft(result).toMatchObject({ postcode }) + } + + //* Ipv6 + const result4 = await lookup('2607:F8B0:4005:801::200E') + expect.soft(result4).not.toBeNull() + expect.soft(result4).toEqual({ + area: 1000, + capital: 'Washington D.C.', + continent: 'NA', + continent_name: 'North America', + country: 'US', + country_name: 'United States', + country_native: 'United States', + currency: [ + 'USD', + 'USN', + 'USS', + ], + eu: undefined, + languages: [ + 'en', + ], + latitude: 37.751, + longitude: -97.822, + phone: [ + 1, + ], + timezone: 'America/Chicago', + }) + + //* Invalid IP address + await expect.soft(lookup('invalid')).rejects.toThrowError('Invalid IPv4 address: invalid') + + //* Too high IP address + await expect.soft(lookup('256.256.256.256')).resolves.toBeNull() + + //* Clear data + clear() + + //* Ips should return null after data is cleared + await expect.soft(lookup('8.8.8.8')).resolves.toBeNull() + + await expect.soft(lookup('2607:F8B0:4005:801::200E')).resolves.toBeNull() + }) +}) diff --git a/packages/ip-location-api/src/index.city.test.ts b/packages/ip-location-api/src/index.city.test.ts new file mode 100644 index 0000000..e617534 --- /dev/null +++ b/packages/ip-location-api/src/index.city.test.ts @@ -0,0 +1,36 @@ +import { randomUUID } from 'node:crypto' +import { rm } from 'node:fs/promises' +import { resolve } from 'node:path' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { clear, lookup, reload } from './index' + +describe('lookup (city)', () => { + const id = randomUUID() + + beforeAll(async () => { + await reload({ + dataDir: resolve(__dirname, '../data', id), + tmpDataDir: resolve(__dirname, '../tmp', id), + fields: ['city', 'country'], + silent: true, + }) + }, 25 * 60_000) + + afterAll(async () => { + clear() + await rm(resolve(__dirname, '../data', id), { recursive: true, force: true }) + await rm(resolve(__dirname, '../tmp', id), { recursive: true, force: true }) + }) + + it('should return the city and country for a valid IP address', async ({ expect }) => { + //* Ipv4 + const result = await lookup('170.171.1.0') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ city: 'New York', country: 'US' }) + + //* Ipv6 + const result2 = await lookup('2606:2e00:8003::216:3eff:fe82:68ab') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ city: 'New York', country: 'US' }) + }) +}) diff --git a/packages/ip-location-api/src/index.country.smallMemory.test.ts b/packages/ip-location-api/src/index.country.smallMemory.test.ts new file mode 100644 index 0000000..2c04811 --- /dev/null +++ b/packages/ip-location-api/src/index.country.smallMemory.test.ts @@ -0,0 +1,55 @@ +import { randomUUID } from 'node:crypto' +import { rm } from 'node:fs/promises' +import { resolve } from 'node:path' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { clear, lookup, reload } from './index' + +describe('lookup (country, smallMemory)', () => { + const id = randomUUID() + + beforeAll(async () => { + await reload({ + dataDir: resolve(__dirname, '../data', id), + tmpDataDir: resolve(__dirname, '../tmp', id), + fields: ['country'], + smallMemory: true, + silent: true, + }) + }, 5 * 60_000) + + afterAll(async () => { + clear() + await rm(resolve(__dirname, '../data', id), { recursive: true, force: true }) + await rm(resolve(__dirname, '../tmp', id), { recursive: true, force: true }) + }) + + it('should return the country for a valid IP address', async ({ expect }) => { + //* Ipv4 + const result = await lookup('8.8.8.8') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ country: 'US' }) + + const result2 = await lookup('207.97.227.239') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ country: 'US' }) + + //* Ipv6 + const result3 = await lookup('2607:F8B0:4005:801::200E') + expect.soft(result3).not.toBeNull() + expect.soft(result3).toEqual({ country: 'US' }) + + //* Invalid IP address + await expect.soft(lookup('invalid')).rejects.toThrowError('Invalid IPv4 address: invalid') + + //* Too high IP address + await expect.soft(lookup('256.256.256.256')).resolves.toBeNull() + + //* Clear data + clear() + + //* Ips should return null after data is cleared + await expect.soft(lookup('8.8.8.8')).resolves.toBeNull() + + await expect.soft(lookup('2607:F8B0:4005:801::200E')).resolves.toBeNull() + }) +}) diff --git a/packages/ip-location-api/src/index.country.test.ts b/packages/ip-location-api/src/index.country.test.ts new file mode 100644 index 0000000..a03dc91 --- /dev/null +++ b/packages/ip-location-api/src/index.country.test.ts @@ -0,0 +1,54 @@ +import { randomUUID } from 'node:crypto' +import { rm } from 'node:fs/promises' +import { resolve } from 'node:path' +import { afterAll, beforeAll, describe, it } from 'vitest' +import { clear, lookup, reload } from './index' + +describe('lookup (country)', () => { + const id = randomUUID() + + beforeAll(async () => { + await reload({ + dataDir: resolve(__dirname, '../data', id), + tmpDataDir: resolve(__dirname, '../tmp', id), + fields: ['country'], + silent: true, + }) + }, 5 * 60_000) + + afterAll(async () => { + clear() + await rm(resolve(__dirname, '../data', id), { recursive: true, force: true }) + await rm(resolve(__dirname, '../tmp', id), { recursive: true, force: true }) + }) + + it('should return the country for a valid IP address', async ({ expect }) => { + //* Ipv4 + const result = await lookup('8.8.8.8') + expect.soft(result).not.toBeNull() + expect.soft(result).toEqual({ country: 'US' }) + + const result2 = await lookup('207.97.227.239') + expect.soft(result2).not.toBeNull() + expect.soft(result2).toEqual({ country: 'US' }) + + //* Ipv6 + const result3 = await lookup('2607:F8B0:4005:801::200E') + expect.soft(result3).not.toBeNull() + expect.soft(result3).toEqual({ country: 'US' }) + + //* Invalid IP address + await expect.soft(lookup('invalid')).rejects.toThrowError('Invalid IPv4 address: invalid') + + //* Too high IP address + await expect.soft(lookup('256.256.256.256')).resolves.toBeNull() + + //* Clear data + clear() + + //* Ips should return null after data is cleared + await expect.soft(lookup('8.8.8.8')).resolves.toBeNull() + + await expect.soft(lookup('2607:F8B0:4005:801::200E')).resolves.toBeNull() + }) +}) diff --git a/packages/ip-location-api/src/index.ts b/packages/ip-location-api/src/index.ts new file mode 100644 index 0000000..069dda6 --- /dev/null +++ b/packages/ip-location-api/src/index.ts @@ -0,0 +1,3 @@ +export { lookup } from './functions/lookup.js' +export { clear, reload } from './functions/reload.js' +export { update } from '@iplookup/util/db' diff --git a/packages/ip-location-api/tsconfig.json b/packages/ip-location-api/tsconfig.json new file mode 100644 index 0000000..0f3afd7 --- /dev/null +++ b/packages/ip-location-api/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "noEmit": true + } +} diff --git a/packages/ip-location-api/vite.config.ts b/packages/ip-location-api/vite.config.ts new file mode 100644 index 0000000..f475926 --- /dev/null +++ b/packages/ip-location-api/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite' +import checker from 'vite-plugin-checker' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + emptyOutDir: true, + lib: { + entry: { + index: 'src/index.ts', + }, + formats: ['es', 'cjs'], + name: 'IpLookup', + }, + rollupOptions: { + external: ['@fast-csv/parse', 'ip-address', 'ky', 'yauzl', 'countries-list'], + }, + sourcemap: true, + ssr: true, + }, + plugins: [ + checker({ + typescript: true, + }), + dts(), + ], +}) diff --git a/packages/util/environment.d.ts b/packages/util/environment.d.ts new file mode 100644 index 0000000..e8320e2 --- /dev/null +++ b/packages/util/environment.d.ts @@ -0,0 +1,6 @@ +declare global { + const __DATA_TYPE__: 'country' | 'geocode' + const __CDN_URL__: string +} + +export {} diff --git a/packages/util/package.json b/packages/util/package.json new file mode 100644 index 0000000..441df0b --- /dev/null +++ b/packages/util/package.json @@ -0,0 +1,31 @@ +{ + "name": "@iplookup/util", + "type": "module", + "version": "0.0.0", + "private": true, + "exports": { + ".": "./dist/index.js", + "./browser": "./dist/browser.js", + "./browserIndex": "./dist/browserIndex.js", + "./db": "./dist/db.js", + "./dbIpUpdate": "./dist/dbIpUpdate.js" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch" + }, + "dependencies": { + "@fast-csv/parse": "^5.0.0", + "ip-address": "^9.0.5", + "ky": "^1.7.2", + "yauzl": "^3.1.3" + }, + "devDependencies": { + "@types/yauzl": "^2.10.3" + } +} diff --git a/packages/util/src/browser.ts b/packages/util/src/browser.ts new file mode 100644 index 0000000..544738f --- /dev/null +++ b/packages/util/src/browser.ts @@ -0,0 +1,184 @@ +import { binarySearch } from './functions/binarySearch.js' +import { fetchArrayBuffer } from './functions/fetchArrayBuffer.js' +import { log } from './functions/log.js' +import { numberToCountryCode } from './functions/numberToCountryCode.js' +import { numberToDir } from './functions/numberToDir.js' +import { parseIp } from './functions/parseIp.js' + +/** + * Sets up an IP lookup function based on the specified data type. + * @template T - The type of data to return ('country' or 'geocode') + * @returns A function that takes an IP address and returns location data + */ +export function setup(): (ipInput: string) => Promise { + const CDN_URL = __CDN_URL__ + const MAIN_RECORD_SIZE = __DATA_TYPE__ === 'country' ? 2 : 8 + const INDEXES: { + 4?: Uint32Array + 6?: BigUint64Array + } = {} + const DATA_URL = { + 4: CDN_URL, + 6: CDN_URL, + } + + /** + * Performs an IP lookup and returns location data. + * @param ipInput - The IP address to look up + * @returns A promise that resolves to location data or null if not found + */ + return async function IpLookup(ipInput: string) { + const { version, ip } = parseIp(ipInput) + + //* Get the index for the IP version + const index = INDEXES[version] ?? (await loadIndex(version)) + if (!index) { + log('warn', 'No index found') + return null + } + + //* If the IP is less than the first index, return null + if (!(ip >= index[0]!)) { + log('warn', `IP ${ipInput} is out of range`) + return null + } + + //* Binary search to find the correct line in the index + const lineIndex = binarySearch(index, ip) + if (lineIndex === null) { + return null + } + + //* Get the data file for the line + const dataResponse = await fetchArrayBuffer( + new URL(`${DATA_URL[version]}/indexes/${version}/${numberToDir(lineIndex)}`), + ) + if (!dataResponse) { + log('warn', 'Index file not found, is it corrupted?') + return null + } + + const { buffer: dataBuffer } = dataResponse + const ipSize = (version - 2) * 2 + const recordSize = MAIN_RECORD_SIZE + ipSize * 2 + const recordCount = dataBuffer.byteLength / recordSize + const startList = version === 4 + ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) + : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)) + + const recordIndex = binarySearch(startList, ip) + if (recordIndex === null) { + return null + } + + const endIp = getEndIp(dataBuffer, version, recordCount, recordIndex, ipSize) + if (ip >= startList[recordIndex]! && ip <= endIp) { + return parseRecord(dataBuffer, recordCount, recordIndex, ipSize, MAIN_RECORD_SIZE) + } + + return null + } as (ipInput: string) => Promise + + /** + * Loads the index for the specified IP version. + * @param ipVersion - The IP version (4 or 6) + * @returns A promise that resolves to the loaded index + */ + async function loadIndex(ipVersion: 4 | 6) { + const baseUrl = getBaseUrl() + return downloadIndex(baseUrl, ipVersion) + } + + /** + * Determines the base URL for downloading the index. + * @returns The base URL as a string + */ + function getBaseUrl(): string { + //* If we are not in the DOM we just use the CDN_URL to download the index + if (typeof document === 'undefined' || !document.currentScript) { + return CDN_URL + } + + //* If we aren't in a SCRIPT element we use the CDN_URL + if (!(document.currentScript instanceof HTMLScriptElement)) { + return CDN_URL + } + + //* Extract the base URL from the script's src attribute + return document.currentScript.src.split('/').slice(0, -1).join('/') + } + + /** + * Downloads the index file for the specified IP version. + * @param baseUrl - The base URL for downloading + * @param version - The IP version (4 or 6) + * @returns A promise that resolves to the downloaded index + */ + async function downloadIndex(baseUrl: string, version: 4 | 6) { + const result = await fetchArrayBuffer( + new URL(`${baseUrl}/indexes/${version}.idx`), + ) + if (!result) { + log('warn', 'Index file not found, is it corrupted?') + return null + } + + const { versionHeader, buffer } = result + if (versionHeader) + DATA_URL[version] = `${CDN_URL}@${versionHeader}` + if (version === 4) + return (INDEXES[version] = new Uint32Array(buffer)) + return (INDEXES[version] = new BigUint64Array(buffer)) + } + + /** + * Retrieves the end IP for a given record. + * @param dataBuffer - The buffer containing IP data + * @param ipVersion - The IP version (4 or 6) + * @param recordCount - The total number of records + * @param recordIndex - The index of the current record + * @param ipSize - The size of an IP address in bytes + * @returns The end IP as a number + */ + function getEndIp(dataBuffer: ArrayBuffer, ipVersion: 4 | 6, recordCount: number, recordIndex: number, ipSize: number) { + const endIpBuffer = dataBuffer.slice( + (recordCount + recordIndex) * ipSize, + (recordCount + recordIndex + 1) * ipSize, + ) + return ipVersion === 4 + ? new Uint32Array(endIpBuffer)[0]! + : new BigUint64Array(endIpBuffer)[0]! + } + + /** + * Parses a record and returns location data. + * @param dataBuffer - The buffer containing record data + * @param recordCount - The total number of records + * @param recordIndex - The index of the current record + * @param ipSize - The size of an IP address in bytes + * @param MAIN_RECORD_SIZE - The size of the main record data + * @returns Location data object + */ + function parseRecord(dataBuffer: ArrayBuffer, recordCount: number, recordIndex: number, ipSize: number, MAIN_RECORD_SIZE: number) { + const recordBuffer = dataBuffer.slice( + recordCount * ipSize * 2 + recordIndex * MAIN_RECORD_SIZE, + recordCount * ipSize * 2 + (recordIndex + 1) * MAIN_RECORD_SIZE, + ) + + if (__DATA_TYPE__ === 'country') { + const ccCode = new Uint16Array(recordBuffer)[0]! + return { country: String.fromCharCode(ccCode & 255, ccCode >> 8) } + } + else { + const recordData = new Int32Array(recordBuffer) + const latitudeData = recordData[0]! + const longitudeData = recordData[1]! + const countryCode = numberToCountryCode(latitudeData & 1023) + return { + latitude: (latitudeData >> 10) / 10000, + longitude: longitudeData / 10000, + country: countryCode, + } + } + } +} diff --git a/packages/util/src/browserIndex.ts b/packages/util/src/browserIndex.ts new file mode 100644 index 0000000..2f0854f --- /dev/null +++ b/packages/util/src/browserIndex.ts @@ -0,0 +1,93 @@ +import { Buffer } from 'node:buffer' +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { getSettings, type IpLocationApiInputSettings, type IpLocationApiSettings, processDirectory } from './functions/getSettings.js' +import { numberToDir } from './functions/numberToDir.js' + +/** + * Creates a browser index for IP location data. + * @param type - The type of data to create an index for ('country' or 'geocode'). + * @param inputSettings - The input settings for the IP location API. + * @param directory - The directory to store the created index. + */ +export async function createBrowserIndex( + type: 'country' | 'geocode', + inputSettings: IpLocationApiInputSettings, + directory: string, +) { + const settings = getSettings(inputSettings) + const exportDir = processDirectory(directory) + await rm(exportDir, { recursive: true, force: true }) + + await processIpVersion(type, settings, exportDir, '4') + await processIpVersion(type, settings, exportDir, '6') +} + +/** + * Processes and creates index files for a specific IP version. + * @param type - The type of data to process ('country' or 'geocode'). + * @param settings - The settings for the IP location API. + * @param exportDir - The directory to export the processed data. + * @param ipVersion - The IP version to process ('4' or '6'). + */ +async function processIpVersion( + type: 'country' | 'geocode', + settings: IpLocationApiSettings, + exportDir: string, + ipVersion: '4' | '6', +) { + //* Create the directory for the IP version + await mkdir(join(exportDir, ipVersion), { recursive: true }) + + const indexSize = type === 'country' ? 1024 : 2048 + const startBuf = await readFile(join(settings.fieldDir, `${ipVersion}-1.dat`)) + const endBuf = await readFile(join(settings.fieldDir, `${ipVersion}-2.dat`)) + const dbInfo = await readFile(join(settings.fieldDir, `${ipVersion}-3.dat`)) + + //* Create typed arrays for efficient data processing + const startList = ipVersion === '4' ? new Uint32Array(startBuf.buffer) : new BigUint64Array(startBuf.buffer) + const endList = ipVersion === '4' ? new Uint32Array(endBuf.buffer) : new BigUint64Array(endBuf.buffer) + const dbList = type === 'country' ? new Uint16Array(dbInfo.buffer) : new Int32Array(dbInfo.buffer) + + const length = startList.length + const indexList = ipVersion === '4' ? new Uint32Array(indexSize) : new BigUint64Array(indexSize) + const recordSize = settings.mainRecordSize + (ipVersion === '4' ? 8 : 16) + + //* Process and write index files + for (let i = 0; i < indexSize; ++i) { + const index = length * i / indexSize | 0 + indexList[i] = startList[index] as number | bigint + const nextIndex = length * (i + 1) / indexSize | 0 + const count = nextIndex - index + + const exportBuf = Buffer.alloc(recordSize * count) + for (let j = index, k = 0; j < nextIndex; ++j) { + //* Write start and end IP addresses + if (ipVersion === '4') { + exportBuf.writeUInt32LE(startList[j] as number, k * 4) + exportBuf.writeUInt32LE(endList[j] as number, 4 * count + k * 4) + } + else { + exportBuf.writeBigUInt64LE(startList[j] as bigint, k * 8) + exportBuf.writeBigUInt64LE(endList[j] as bigint, 8 * count + k * 8) + } + + //* Write country or geocode data + const offset = (ipVersion === '4' ? 8 : 16) * count + k * settings.mainRecordSize + if (type === 'country') { + exportBuf.writeUInt16LE(dbList[j]!, offset) + } + else { + exportBuf.writeInt32LE(dbList[2 * j]!, offset) + exportBuf.writeInt32LE(dbList[2 * j + 1]!, offset + 4) + } + ++k + } + + //* Write the processed data to a file + await writeFile(join(exportDir, ipVersion, numberToDir(i)), exportBuf) + } + + //* Write the index list to a file + await writeFile(join(exportDir, `${ipVersion}.idx`), Buffer.from(indexList.buffer)) +} diff --git a/packages/util/src/constants.ts b/packages/util/src/constants.ts new file mode 100644 index 0000000..368884c --- /dev/null +++ b/packages/util/src/constants.ts @@ -0,0 +1,44 @@ +import type { IpLocationApiSettings, LocalDatabase } from './functions/getSettings.js' + +export const MAXMIND_URL = 'https://download.maxmind.com/app/geoip_download' +export const DATABASE_SUFFIX_SHA = '.zip.sha256' +export const DATABASE_SUFFIX_ZIP = '.zip' +export const MAIN_FIELDS = ['latitude', 'longitude', 'area', 'postcode'] as const +export const LOCATION_FIELDS = ['country', 'region1', 'region1_name', 'region2', 'region2_name', 'metro', 'timezone', 'city', 'eu'] as const +export const v4: LocalDatabase<4> = { + version: 4, + recordSize: 0, + fileLineMax: 0, + folderLineMax: 0, +} +export const v6: LocalDatabase<6> = { + version: 6, + recordSize: 0, + fileLineMax: 0, + folderLineMax: 0, +} +export const DEFAULT_SETTINGS: IpLocationApiSettings = { + licenseKey: 'redist', + series: 'GeoLite2', + dataDir: '../data', + tmpDataDir: '../tmp', + fields: ['country'], + language: 'en', + smallMemory: false, + smallMemoryFileSize: 4096, + //* Generated settings + fieldDir: '', + dataType: 'Country', + locationFile: false, + mainRecordSize: 2, + locationRecordSize: 0, + v4, + v6, + addCountryInfo: false, + silent: false, +} +// eslint-disable-next-line import/no-mutable-exports +export let SAVED_SETTINGS: IpLocationApiSettings = DEFAULT_SETTINGS +export function setSavedSettings(settings: IpLocationApiSettings) { + return SAVED_SETTINGS = settings +} diff --git a/packages/util/src/db.ts b/packages/util/src/db.ts new file mode 100644 index 0000000..0028b22 --- /dev/null +++ b/packages/util/src/db.ts @@ -0,0 +1,47 @@ +import type { IpLocationApiInputSettings } from './functions/getSettings.js' + +import { cp, readdir, rename, rm, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { DATABASE_SUFFIX_SHA } from './constants.js' +import { createDatabase } from './functions/database/createDatabase.js' +import { downloadAndExtractDatabase } from './functions/database/downloadAndExtractDatabase.js' +import { ensureDirectoriesExist } from './functions/database/ensureDirectoriesExist.js' +import { getSettings } from './functions/getSettings.js' +import { log } from './functions/log.js' + +/** + * Updates the GeoIP database based on the provided settings. + * @param inputSettings - Partial settings to override the default ones. + */ +export async function update(inputSettings?: Partial): Promise { + const settings = getSettings(inputSettings) + await ensureDirectoriesExist(settings) + log('info', 'Downloading database...') + const { files, sha256 } = await downloadAndExtractDatabase(settings) + if (!files || files.length === 0) + return + + log('info', 'Creating database...') + await createDatabase(files, settings) + log('info', 'Database created') + + //* Save the sha256 hash of the database + await writeFile(path.join(settings.fieldDir, `${settings.series}-${settings.dataType}-CSV${DATABASE_SUFFIX_SHA}`), sha256) + + //* Rename the temporary files to the correct name + const tmpFiles = (await readdir(settings.fieldDir)).filter(file => file.endsWith('.tmp')) + for (const file of tmpFiles) { + await rename(path.join(settings.fieldDir, file), path.join(settings.fieldDir, file.replace('.tmp', ''))) + } + + if (settings.smallMemory) { + //* Copy the temporary files to the correct name + await cp(path.join(settings.fieldDir, 'v4-tmp'), path.join(settings.fieldDir, 'v4'), { recursive: true, force: true }) + await cp(path.join(settings.fieldDir, 'v6-tmp'), path.join(settings.fieldDir, 'v6'), { recursive: true, force: true }) + //* Remove the temporary files + await rm(path.join(settings.fieldDir, 'v4-tmp'), { recursive: true, force: true, maxRetries: 3 }) + await rm(path.join(settings.fieldDir, 'v6-tmp'), { recursive: true, force: true, maxRetries: 3 }) + } + + log('info', 'Database successfully updated') +} diff --git a/packages/util/src/dbIpUpdate.ts b/packages/util/src/dbIpUpdate.ts new file mode 100644 index 0000000..804e851 --- /dev/null +++ b/packages/util/src/dbIpUpdate.ts @@ -0,0 +1,132 @@ +import type { IpLocationApiInputSettings } from './functions/getSettings.js' +import { Buffer } from 'node:buffer' +import { createReadStream, createWriteStream, existsSync } from 'node:fs' +import { writeFile } from 'node:fs/promises' +import path from 'node:path' +import { pipeline } from 'node:stream/promises' +import { parse } from '@fast-csv/parse' +import { aton4 } from './functions/aton4.js' +import { aton6 } from './functions/aton6.js' +import { countryCodeToNumber } from './functions/countryCodeToNumber.js' +import { ensureDirectoriesExist } from './functions/database/ensureDirectoriesExist.js' +import { getSettings } from './functions/getSettings.js' + +/** + * Updates the GeoIP database based on the provided settings using db-ip.com database. + * Downloads and processes a CSV file containing IP ranges with their corresponding geographical data. + * Creates binary data files for both IPv4 and IPv6 addresses. + * + * @param inputSettings - Partial settings to override the default configuration + * @returns Promise that resolves when the database update is complete + */ +export async function update(inputSettings?: Partial): Promise { + const settings = getSettings(inputSettings) + await ensureDirectoriesExist(settings) + + //* Construct filename based on current year and month (db-ip updates monthly) + const fileName = `dbip-city-lite-${new Date().getFullYear()}-${new Date().getMonth() + 1}.csv` + const tempFilePath = path.join(settings.tmpDataDir, fileName) + + //* Download and decompress the database file if it doesn't exist locally + if (!existsSync(tempFilePath)) { + const response = await fetch(`https://download.db-ip.com/free/${fileName}.gz`) + // @ts-expect-error DecompressionStream is supported in Node.js + await pipeline(response.body!.pipeThrough(new DecompressionStream('gzip')), createWriteStream(tempFilePath)) + } + + return new Promise((resolve, reject) => { + //* Arrays to store processed IP ranges and their metadata + const v4: [number, number, number, number][] = [] // [startIp, endIp, latitudeWithCountry, longitude] + const v6: [bigint, bigint, number, number][] = [] // [startIp, endIp, latitudeWithCountry, longitude] + + //* Keep track of previous entries to merge consecutive ranges with same metadata + let previousDataV4: [number, number, number, number] | null = null + let previousDataV6: [bigint, bigint, number, number] | null = null + + createReadStream(tempFilePath) + .pipe(parse()) + .on('error', reject) + .on('data', ([ + ipStart, + ipEnd, + , //* Skip unused columns from CSV + countryCode, + , + , + latitudeString, + longitudeString, + ]: [string, string, string, string, string, string, string, string]) => { + //* Skip invalid or special country codes + if (!countryCode || countryCode === 'ZZ' || countryCode === 'EU') + return + + //* Convert coordinates to integers with 4 decimal precision + const latitude = Math.round((Number.parseFloat(latitudeString)) * 10000) // -90 ~ 90 -> -900000 ~ 900000 + const longitude = Math.round((Number.parseFloat(longitudeString)) * 10000) // -180 ~ 180 -> -1800000 ~ 1800000 + + //* Combine latitude with country code for efficient storage + const latitudeWithCountryCode = (latitude) << 10 | countryCodeToNumber(countryCode) + + if (ipStart.includes(':')) { + //* Handle IPv6 addresses + const start = aton6(ipStart) + //* Merge with previous range if consecutive and has same metadata + if (previousDataV6 && previousDataV6[1] + 1n === start + && previousDataV6[2] === latitudeWithCountryCode + && previousDataV6[3] === longitude) { + previousDataV6[1] = aton6(ipEnd) + return + } + v6.push(previousDataV6 = [aton6(ipStart), aton6(ipEnd), latitudeWithCountryCode, longitude]) + } + else { + //* Handle IPv4 addresses + const start = aton4(ipStart) + //* Merge with previous range if consecutive and has same metadata + if (previousDataV4 && previousDataV4[1] + 1 === start + && previousDataV4[2] === latitudeWithCountryCode + && previousDataV4[3] === longitude) { + previousDataV4[1] = aton4(ipEnd) + return + } + v4.push(previousDataV4 = [aton4(ipStart), aton4(ipEnd), latitudeWithCountryCode, longitude]) + } + }) + .on('end', async () => { + //* Create binary buffers for IPv4 data + const v4Buf1 = Buffer.alloc(v4.length * 4) // Start IPs + const v4Buf2 = Buffer.alloc(v4.length * 4) // End IPs + const v4Buf3 = Buffer.alloc(v4.length * 8) // Metadata (latitude+country, longitude) + + //* Write IPv4 data to buffers + for (let i = 0; i < v4.length; ++i) { + v4Buf1.writeUInt32LE(v4[i]![0], i * 4) + v4Buf2.writeUInt32LE(v4[i]![1], i * 4) + v4Buf3.writeInt32LE(v4[i]![2], i * 8) + v4Buf3.writeInt32LE(v4[i]![3], i * 8 + 4) + } + + //* Create binary buffers for IPv6 data + const v6Buf1 = Buffer.alloc(v6.length * 8) // Start IPs + const v6Buf2 = Buffer.alloc(v6.length * 8) // End IPs + const v6Buf3 = Buffer.alloc(v6.length * 8) // Metadata (latitude+country, longitude) + + //* Write IPv6 data to buffers + for (let i = 0; i < v6.length; ++i) { + v6Buf1.writeBigUInt64LE(v6[i]![0], i * 8) + v6Buf2.writeBigUInt64LE(v6[i]![1], i * 8) + v6Buf3.writeInt32LE(v6[i]![2], i * 8) + v6Buf3.writeInt32LE(v6[i]![3], i * 8 + 4) + } + + //* Write all buffers to separate data files + await writeFile(path.join(settings.fieldDir, '4-1.dat'), v4Buf1) + await writeFile(path.join(settings.fieldDir, '4-2.dat'), v4Buf2) + await writeFile(path.join(settings.fieldDir, '4-3.dat'), v4Buf3) + await writeFile(path.join(settings.fieldDir, '6-1.dat'), v6Buf1) + await writeFile(path.join(settings.fieldDir, '6-2.dat'), v6Buf2) + await writeFile(path.join(settings.fieldDir, '6-3.dat'), v6Buf3) + resolve() + }) + }) +} diff --git a/packages/util/src/functions/aton4.test.ts b/packages/util/src/functions/aton4.test.ts new file mode 100644 index 0000000..e459b69 --- /dev/null +++ b/packages/util/src/functions/aton4.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest' +import { aton4 } from './aton4' + +describe('aton4', () => { + it('should convert valid IPv4 addresses to 32-bit integers', () => { + expect.soft(aton4('192.168.1.1')).toBe(3232235777) + expect.soft(aton4('10.0.0.1')).toBe(167772161) + expect.soft(aton4('172.16.0.1')).toBe(2886729729) + expect.soft(aton4('255.255.255.255')).toBe(4294967295) + expect.soft(aton4('207.97.227.239')).toBe(3479299055) + expect.soft(aton4('0.0.0.0')).toBe(0) + }) + + it('should handle leading zeros in octets', () => { + expect.soft(aton4('192.168.001.001')).toBe(3232235777) + expect.soft(aton4('010.000.000.001')).toBe(167772161) + }) + + it('should throw an error for invalid IPv4 addresses', () => { + expect.soft(() => aton4('192.168.1')).toThrow('Invalid IPv4 address: 192.168.1') + expect.soft(() => aton4('192.168.1.1.1')).toThrow('Invalid IPv4 address: 192.168.1.1.1') + expect.soft(() => aton4('')).toThrow('Invalid IPv4 address: ') + }) + + it('should handle edge cases', () => { + expect.soft(aton4('127.0.0.1')).toBe(2130706433) // Localhost + expect.soft(aton4('255.255.255.0')).toBe(4294967040) // Subnet mask + expect.soft(aton4('1.2.3.4')).toBe(16909060) + }) + + it('should handle all zeros and all ones', () => { + expect.soft(aton4('0.0.0.0')).toBe(0) + expect.soft(aton4('255.255.255.255')).toBe(4294967295) + }) +}) diff --git a/packages/util/src/functions/aton4.ts b/packages/util/src/functions/aton4.ts new file mode 100644 index 0000000..33e116f --- /dev/null +++ b/packages/util/src/functions/aton4.ts @@ -0,0 +1,26 @@ +/** + * Convert an IPv4 string to a 32-bit integer + * @param string - The IPv4 string to convert + * @returns The 32-bit integer + * @example + * ```ts + * aton4('192.168.1.1') // 3232235777 + * ``` + * @throws Will throw an error if the input is not a valid IPv4 address + */ +export function aton4(string: string): number { + const parts = string.split(/\./) as [string, string, string, string] + if (parts.length !== 4) { + throw new Error(`Invalid IPv4 address: ${string}`) + } + + //* Extract each octet and shift them to their correct position + const [a, b, c, d] = parts + const octet1 = Number.parseInt(a) << 24 + const octet2 = Number.parseInt(b) << 16 + const octet3 = Number.parseInt(c) << 8 + const octet4 = Number.parseInt(d) + + //* Combine the octets using bitwise OR and return the result + return (octet1 | octet2 | octet3 | octet4) >>> 0 +}; diff --git a/packages/util/src/functions/aton6.test.ts b/packages/util/src/functions/aton6.test.ts new file mode 100644 index 0000000..3317b4b --- /dev/null +++ b/packages/util/src/functions/aton6.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest' +import { aton6 } from './aton6' + +describe('aton6', () => { + it('converts a full IPv6 address correctly', () => { + expect.soft(aton6('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(2306139570357600256n) + }) + + it('converts a shortened IPv6 address correctly', () => { + expect.soft(aton6('2001:db8::1')).toBe(2306139568115548160n) + }) + + it('handles IPv6 address with double colons in the middle', () => { + expect.soft(aton6('2001:db8::8a2e:370:7334')).toBe(2306139568115548160n) + }) + + it('handles IPv6 address with double colons at the end', () => { + expect.soft(aton6('2001:db8:85a3::')).toBe(2306139570357600256n) + }) + + it('handles IPv6 address with leading zeros omitted', () => { + expect.soft(aton6('2001:db8:1:2:3:4:5:6')).toBe(2306139568115613698n) + }) + + it('converts ::1 (localhost) correctly', () => { + expect.soft(aton6('::1')).toBe(0n) + }) + + it('converts :: (unspecified address) correctly', () => { + expect.soft(aton6('::')).toBe(0n) + }) + + it('handles IPv6 address with quotes', () => { + expect.soft(aton6('"2001:db8::1"')).toBe(2306139568115548160n) + }) + + it('returns 0 for invalid IPv6 address', () => { + expect.soft(aton6('invalid')).toBe(0n) + }) + + it('returns valid parts of IPv6 address with too many parts', () => { + expect.soft(aton6('2001:db8:85a3:0000:0000:8a2e:0370:7334')).toBe(2306139570357600256n) + expect.soft(aton6('2001:db8:85a3:0000:0000:8a2e:0370:7334:extra')).toBe(2306139570357600256n) + }) +}) diff --git a/packages/util/src/functions/aton6.ts b/packages/util/src/functions/aton6.ts new file mode 100644 index 0000000..ca400e6 --- /dev/null +++ b/packages/util/src/functions/aton6.ts @@ -0,0 +1,33 @@ +/** + * Convert an IPv6 string to a 128-bit BigInt + * @param ipv6String - The IPv6 string to convert + * @returns The 128-bit BigInt representation + * @example + * ```ts + * aton6('2001:db8::1') // 2306139568115548160n + * ``` + */ +export function aton6(ipv6String: string): bigint { + const parts = ipv6String.replace(/"/g, '').split(/:/) + + const length = parts.length - 1 + if (parts[length] === '') + parts[length] = '0' + if (length < 7) { + const omitted = 8 - parts.length + const omitStart = parts.indexOf('') + const omitEnd = omitStart + omitted + for (let i = 7; i >= omitStart; i--) { + parts[i] = i > omitEnd ? parts[i - omitted]! : '0' + } + } + + let result = 0n + for (let i = 0; i < 4; i++) { + const part = parts[i] + if (part) { + result += BigInt(Number.parseInt(part, 16)) << BigInt(16 * (3 - i)) + } + } + return result +} diff --git a/packages/util/src/functions/binarySearch.test.ts b/packages/util/src/functions/binarySearch.test.ts new file mode 100644 index 0000000..986b8e8 --- /dev/null +++ b/packages/util/src/functions/binarySearch.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from 'vitest' +import { binarySearch } from './binarySearch' + +describe('binarySearch', () => { + describe('uint32Array tests', () => { + const uint32Array = new Uint32Array([1, 3, 5, 7, 9, 11, 13, 15]) + + it('should find existing elements', () => { + expect.soft(binarySearch(uint32Array, 1)).toBe(0) + expect.soft(binarySearch(uint32Array, 7)).toBe(3) + expect.soft(binarySearch(uint32Array, 15)).toBe(7) + }) + + it('should return insertion point for non-existing elements', () => { + expect.soft(binarySearch(uint32Array, 4)).toBe(1) + expect.soft(binarySearch(uint32Array, 10)).toBe(4) + expect.soft(binarySearch(uint32Array, 16)).toBe(7) + }) + + it('should return null for elements smaller than all in the array', () => { + expect.soft(binarySearch(uint32Array, 0)).toBe(null) + }) + + it('should work with an array of length 1', () => { + const singleElementArray = new Uint32Array([5]) + expect.soft(binarySearch(singleElementArray, 5)).toBe(0) + expect.soft(binarySearch(singleElementArray, 3)).toBe(null) + expect.soft(binarySearch(singleElementArray, 7)).toBe(0) + }) + + it('should work with an empty array', () => { + const emptyArray = new Uint32Array([]) + expect.soft(binarySearch(emptyArray, 5)).toBe(null) + }) + }) + + describe('bigUint64Array tests', () => { + const bigUint64Array = new BigUint64Array([1n, 3n, 5n, 7n, 9n, 11n, 13n, 15n]) + + it('should find existing elements', () => { + expect.soft(binarySearch(bigUint64Array, 1n)).toBe(0) + expect.soft(binarySearch(bigUint64Array, 7n)).toBe(3) + expect.soft(binarySearch(bigUint64Array, 15n)).toBe(7) + }) + + it('should return insertion point for non-existing elements', () => { + expect.soft(binarySearch(bigUint64Array, 4n)).toBe(1) + expect.soft(binarySearch(bigUint64Array, 10n)).toBe(4) + expect.soft(binarySearch(bigUint64Array, 16n)).toBe(7) + }) + + it('should return null for elements smaller than all in the array', () => { + expect.soft(binarySearch(bigUint64Array, 0n)).toBe(null) + }) + + it('should work with an array of length 1', () => { + const singleElementArray = new BigUint64Array([5n]) + expect.soft(binarySearch(singleElementArray, 5n)).toBe(0) + expect.soft(binarySearch(singleElementArray, 3n)).toBe(null) + expect.soft(binarySearch(singleElementArray, 7n)).toBe(0) + }) + + it('should work with an empty array', () => { + const emptyArray = new BigUint64Array([]) + expect.soft(binarySearch(emptyArray, 5n)).toBe(null) + }) + }) + + describe('edge cases', () => { + it('should handle the maximum possible Uint32 value', () => { + const maxUint32Array = new Uint32Array([4294967295]) + expect.soft(binarySearch(maxUint32Array, 4294967295)).toBe(0) + }) + + it('should handle the maximum possible BigUint64 value', () => { + const maxBigUint64Array = new BigUint64Array([18446744073709551615n]) + expect.soft(binarySearch(maxBigUint64Array, 18446744073709551615n)).toBe(0) + }) + }) +}) diff --git a/packages/util/src/functions/binarySearch.ts b/packages/util/src/functions/binarySearch.ts new file mode 100644 index 0000000..57c6aa8 --- /dev/null +++ b/packages/util/src/functions/binarySearch.ts @@ -0,0 +1,31 @@ +/** + * Performs a binary search on a sorted array of BigUint64 or Uint32 values. + * + * @param array - The sorted array to search in (BigUint64Array or Uint32Array). + * @param target - The value to search for (number or bigint). + * @returns The index of the target if found, or the index where it would be inserted if not found. + * Returns null if the target is smaller than all elements in the array. + */ +export function binarySearch(array: BigUint64Array | Uint32Array, target: number | bigint): number | null { + let low = 0 + let high = array.length - 1 + + while (low <= high) { + //* Bitwise right shift by 1 is equivalent to Math.floor((low + high) / 2) + const mid = (low + high) >> 1 + const midValue = array[mid]! + + if (target < midValue) { + high = mid - 1 + } + else if (target > midValue) { + low = mid + 1 + } + else { + return mid //* Target found + } + } + + //* If target not found, return the insertion point or null if smaller than all elements + return high >= 0 ? high : null +} diff --git a/packages/util/src/functions/countryCodeToNumber.test.ts b/packages/util/src/functions/countryCodeToNumber.test.ts new file mode 100644 index 0000000..cc9a6b1 --- /dev/null +++ b/packages/util/src/functions/countryCodeToNumber.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest' +import { countryCodeToNumber } from './countryCodeToNumber' + +describe('countryCodeToNumber', () => { + it('should convert "AA" to 0', () => { + expect.soft(countryCodeToNumber('AA')).toBe(0) + }) + + it('should convert "ZZ" to 675', () => { + expect.soft(countryCodeToNumber('ZZ')).toBe(675) + }) + + it('should convert "CA" to 52', () => { + expect.soft(countryCodeToNumber('CA')).toBe(52) + }) + + it('should convert "US" to 538', () => { + expect.soft(countryCodeToNumber('US')).toBe(538) + }) + + it('should throw an error for lowercase input', () => { + expect.soft(() => countryCodeToNumber('aa')).toThrow('Input must be a valid two-letter country code (A-Z)') + }) + + it('should throw an error for input with non-alphabetic characters', () => { + expect.soft(() => countryCodeToNumber('A1')).toThrow('Input must be a valid two-letter country code (A-Z)') + }) + + it('should throw an error for input with more than two characters', () => { + expect.soft(() => countryCodeToNumber('USA')).toThrow('Input must be a valid two-letter country code (A-Z)') + }) + + it('should throw an error for input with less than two characters', () => { + expect.soft(() => countryCodeToNumber('A')).toThrow('Input must be a valid two-letter country code (A-Z)') + }) + + it('should throw an error for empty input', () => { + expect.soft(() => countryCodeToNumber('')).toThrow('Input must be a valid two-letter country code (A-Z)') + }) +}) diff --git a/packages/util/src/functions/countryCodeToNumber.ts b/packages/util/src/functions/countryCodeToNumber.ts new file mode 100644 index 0000000..2297553 --- /dev/null +++ b/packages/util/src/functions/countryCodeToNumber.ts @@ -0,0 +1,15 @@ +/** + * Converts a two-letter country code to a unique number. + * @param code - A two-letter country code (e.g., 'AA', 'AB', ..., 'ZZ'). + * @returns A number representing the country code (0-675). + * @throws {Error} If the input code is not a valid two-letter code. + */ +export function countryCodeToNumber(code: string): number { + if (!/^[A-Z]{2}$/.test(code)) { + throw new Error('Input must be a valid two-letter country code (A-Z)') + } + + //* Convert two-letter code to number using ASCII values + //* First letter: A-Z (65-90), Second letter: A-Z (65-90) + return (code.charCodeAt(0) - 65) * 26 + (code.charCodeAt(1) - 65) +} diff --git a/packages/util/src/functions/database/createBlockDatabase.ts b/packages/util/src/functions/database/createBlockDatabase.ts new file mode 100644 index 0000000..4d06cc9 --- /dev/null +++ b/packages/util/src/functions/database/createBlockDatabase.ts @@ -0,0 +1,336 @@ +import type { WriteStream } from 'node:fs' +import type { IpLocationApiSettings } from '../getSettings.js' +import type { LocationData } from './createDatabase.js' +import { Buffer } from 'node:buffer' +import { createReadStream, createWriteStream, existsSync } from 'node:fs' +import { rm } from 'node:fs/promises' +import path from 'node:path' +import { parse } from '@fast-csv/parse' +import { Address4, Address6 } from 'ip-address' +import { aton4 } from '../aton4.js' +import { aton6 } from '../aton6.js' +import { getPostcodeDatabase } from '../getPostcodeDatabase.js' +import { log } from '../log.js' +import { makeDatabase } from '../makeDatabase.js' +import { createSmallMemoryFile } from './createSmallMemoryFile.js' + +/** + * Represents a row in the block database CSV file. + */ +interface BlockDatabaseRow { + network: string + geoname_id: string + latitude: string + longitude: string + accuracy_radius: string + postal_code: string +} + +/** + * Creates a block database for IP geolocation. + * @param file - The CSV file containing IP block data. + * @param locationData - An array of records containing location data. + * @param locationIdList - A list of location IDs. + * @param areaDatabase - A database of area data. + * @param settings - IP location API settings. + */ +export async function createBlockDatabase( + file: string, + locationData: Record[], + locationIdList: number[], + areaDatabase: Record, + settings: IpLocationApiSettings, +): Promise { + const version = file.endsWith('v4.csv') ? 4 : 6 + log('info', `Creating block database for IPv${version}...`) + + //* First count total lines + let totalLines = 0 + await new Promise((resolve) => { + createReadStream(path.join(settings.tmpDataDir, file)) + .pipe(parse({ headers: true })) + .on('data', () => { totalLines++ }) + .on('end', resolve) + }) + + log('info', `Found ${totalLines.toLocaleString()} total records to process`) + + const readStream = createReadStream(path.join(settings.tmpDataDir, file)) + const writeStreamDat1 = createWriteStream(path.join(settings.fieldDir, `${version}-1.dat.tmp`), { highWaterMark: 1024 * 1024 }) + + let writeStreamDat2: WriteStream | undefined + let writeStreamDat3: WriteStream | undefined + let writeStreamSmallMemory: WriteStream | undefined + + if (!settings.smallMemory) { + writeStreamDat2 = createWriteStream(path.join(settings.fieldDir, `${version}-2.dat.tmp`), { highWaterMark: 1024 * 1024 }) + writeStreamDat3 = createWriteStream(path.join(settings.fieldDir, `${version}-3.dat.tmp`), { highWaterMark: 1024 * 1024 }) + } + else { + const dir = path.join(settings.fieldDir, `v${version}-tmp`) + if (existsSync(dir)) { + await rm(dir, { recursive: true, force: true }) + } + } + + return new Promise((resolve, reject) => { + let checkCount = 0 + function check() { + if (++checkCount === 3) { + log('info', `Completed processing IPv${version} block database`) + resolve() + } + } + + let previousData: { + countryCode?: string + end: number | bigint + buffer1?: Buffer + buffer2?: Buffer + buffer3?: Buffer + locationId?: number + latitude?: number + longitude?: number + accuracyRadius?: string + postalCode?: string + counter?: number + } | undefined + let lineCount = 0 + let processedCount = 0 + const logInterval = 100000 // Log every 100k records + + readStream.pipe(parse({ headers: true })) + .on('error', (error) => { + log('error', `Error processing IPv${version} block database: ${error.message}`) + reject(error) + }) + .on('data', (row: BlockDatabaseRow) => { + processedCount++ + if (processedCount % logInterval === 0) { + const percentage = ((processedCount / totalLines) * 100).toFixed(2) + log('info', `Processed ${processedCount.toLocaleString()} IPv${version} records (${percentage}%)...`) + } + + const addr = version === 4 ? new Address4(row.network!) : new Address6(row.network!) + const start = version === 4 ? aton4(addr.startAddress().correctForm()) : aton6(addr.startAddress().correctForm()) + const end = version === 4 ? aton4(addr.endAddress().correctForm()) : aton6(addr.endAddress().correctForm()) + + if (settings.dataType === 'Country') { + const locationDataMap = locationData[0] as Record + const countryCode = locationDataMap[Number.parseInt(row.geoname_id)] + + if (!countryCode || countryCode.length !== 2) { + return //* Invalid country code + } + + if ( + countryCode === previousData?.countryCode + && ( + (version === 4 && (previousData.end as number) + 1 === start) + || (version === 6 && (previousData.end as bigint) + 1n === start) + ) + ) { + if (version === 4) { + previousData.buffer2?.writeUInt32LE(end as number) + } + else { + previousData.buffer2?.writeBigUInt64LE(end as bigint) + } + } + else { + const buffer1 = Buffer.allocUnsafe(version === 4 ? 4 : 8) + const buffer2 = Buffer.allocUnsafe(version === 4 ? 4 : 8) + if (version === 4) { + buffer1.writeUInt32LE(start as number) + buffer2.writeUInt32LE(end as number) + } + else { + buffer1.writeBigUInt64LE(start as bigint) + buffer2.writeBigUInt64LE(end as bigint) + } + + const buffer3 = Buffer.allocUnsafe(2) + buffer3.write(countryCode) + + if (previousData?.buffer1) { + if (!writeStreamDat1.write(previousData.buffer1)) + readStream.pause() + if (settings.smallMemory && previousData.buffer2 && previousData.buffer3) { + writeStreamSmallMemory = createSmallMemoryFile(writeStreamSmallMemory!, version, lineCount++, previousData.buffer2, previousData.buffer3, settings) + } + else { + if (!writeStreamDat2!.write(previousData.buffer2)) + readStream.pause() + if (!writeStreamDat3!.write(previousData.buffer3)) + readStream.pause() + } + } + + previousData = { + countryCode, + end, + buffer1, + buffer2, + buffer3, + } + } + } + else { + const locationDataMap = locationData[0] as Record + const locationId = Number.parseInt(row.geoname_id) + const latitude = Math.round(Number.parseFloat(row.latitude || '0') * 10000) + const longitude = Math.round(Number.parseFloat(row.longitude || '0') * 10000) + const accuracyRadius = row.accuracy_radius + const postalCode = row.postal_code + + //* Check if any relevant fields have changed from the previous entry + let hasChanged = false + if (settings.fields.includes('latitude') && latitude !== previousData?.latitude) + hasChanged = true + if (settings.fields.includes('longitude') && longitude !== previousData?.longitude) + hasChanged = true + if (settings.fields.includes('area') && accuracyRadius !== previousData?.accuracyRadius) + hasChanged = true + if (settings.fields.includes('postcode') && postalCode !== previousData?.postalCode) + hasChanged = true + + let counter = locationDataMap[locationId]?.counter ?? 0 + + //* Check if we can merge this entry with the previous one + if ( + previousData + && ( + locationId === previousData.locationId + || (counter > 0 && counter === previousData.counter) + || !settings.locationFile + ) + && !hasChanged + && ( + (version === 4 && (previousData.end as number) + 1 === start) + || (version === 6 && (previousData.end as bigint) + 1n === start) + ) + ) { + //* Merge by updating the end of the previous entry + if (version === 4) { + previousData.buffer2?.writeUInt32LE(end as number) + } + else { + previousData.buffer2?.writeBigUInt64LE(end as bigint) + } + } + else { + if (!locationId) + return + + const dataMap = locationDataMap[locationId] + if (!dataMap) { + log('warn', `Invalid location ID ${locationId}`) + return + } + + //* Assign a counter if it doesn't exist + if (!dataMap.counter) { + locationIdList.push(locationId) + counter = dataMap.counter = locationIdList.length + } + + //* Write the previous data if it exists + if (previousData?.buffer1) { + if (!writeStreamDat1.write(previousData.buffer1)) + readStream.pause() + if (settings.smallMemory && previousData.buffer2 && previousData.buffer3) { + writeStreamSmallMemory = createSmallMemoryFile(writeStreamSmallMemory!, version, lineCount++, previousData.buffer2, previousData.buffer3, settings) + } + else { + if (!writeStreamDat2!.write(previousData.buffer2)) + readStream.pause() + if (!writeStreamDat3!.write(previousData.buffer3)) + readStream.pause() + } + } + + //* Create new buffers for the current entry + const buffer1 = Buffer.allocUnsafe(version === 4 ? 4 : 8) + const buffer2 = Buffer.allocUnsafe(version === 4 ? 4 : 8) + if (version === 4) { + buffer1.writeUInt32LE(start as number) + buffer2.writeUInt32LE(end as number) + } + else { + buffer1.writeBigUInt64LE(start as bigint) + buffer2.writeBigUInt64LE(end as bigint) + } + + const buffer3 = Buffer.alloc(settings.mainRecordSize) + + let offset = 0 + //* Write location data to buffer3 based on settings + if (settings.locationFile) { + buffer3.writeUInt32LE(dataMap.counter, offset) + offset += 4 + } + + if (settings.fields.includes('latitude')) { + buffer3.writeInt32LE(latitude, offset) + offset += 4 + } + + if (settings.fields.includes('longitude')) { + buffer3.writeInt32LE(longitude, offset) + offset += 4 + } + + if (settings.fields.includes('postcode')) { + const [postcodeLength, postcodeValue] = getPostcodeDatabase(postalCode) + buffer3.writeUInt32LE(postcodeValue, offset) + buffer3.writeInt8(postcodeLength, offset + 4) + offset += 5 + } + + if (settings.fields.includes('area')) { + buffer3.writeUInt8(makeDatabase(accuracyRadius, areaDatabase), offset) + } + + //* Update previousData for the next iteration + previousData = { + locationId, + end, + buffer1, + buffer2, + buffer3, + latitude, + longitude, + accuracyRadius, + counter, + postalCode, + } + } + } + previousData = { + ...(previousData ?? {}), + end, + } + }) + .on('pause', () => { + writeStreamDat1.once('drain', () => readStream.resume()) + if (!settings.smallMemory && writeStreamDat2 && writeStreamDat3) { + writeStreamDat2.once('drain', () => readStream.resume()) + writeStreamDat3.once('drain', () => readStream.resume()) + } + }) + .on('end', () => { + log('info', `Finished processing ${processedCount.toLocaleString()} IPv${version} records (100%)`) + + if (settings.smallMemory && previousData?.buffer2 && previousData?.buffer3) { + writeStreamSmallMemory = createSmallMemoryFile(writeStreamSmallMemory!, version, lineCount++, previousData.buffer2, previousData.buffer3, settings) + writeStreamSmallMemory?.end(check) + ++checkCount + } + else { + writeStreamDat2?.end(check) + writeStreamDat3?.end(check) + } + writeStreamDat1?.end(check) + }) + }) +} diff --git a/packages/util/src/functions/database/createDatabase.ts b/packages/util/src/functions/database/createDatabase.ts new file mode 100644 index 0000000..b04d8b4 --- /dev/null +++ b/packages/util/src/functions/database/createDatabase.ts @@ -0,0 +1,241 @@ +import type { IpLocationApiSettings } from '../getSettings.js' +import { createReadStream } from 'node:fs' +import path from 'node:path' +import { parse } from '@fast-csv/parse' +import { LOCATION_FIELDS } from '../../constants.js' +import { createBlockDatabase } from './createBlockDatabase.js' +import { createLocationDatabase } from './createLocationDatabase.js' + +/** + * Creates a database for IP location data. + * @param files - Array of file paths to process. + * @param settings - IP location API settings. + */ +export async function createDatabase(files: string[], settings: IpLocationApiSettings): Promise { + const locationSources = files.filter(file => file.includes('Locations')) + //* Ensure the English file is first (should only have max 2 location files) + locationSources.sort((a, b) => { + if (a.endsWith('-en.csv')) + return -1 + if (b.endsWith('-en.csv')) + return 1 + return a.localeCompare(b) + }) + + //* Extract location data from files + const locationData: Record[] = [] + for (const locationSource of locationSources) { + locationData.push(await getLocationData(locationSource, settings)) + } + + //* Optimize location file if necessary + if (settings.locationFile) { + minifyLocationData(locationData as Record[], settings) + } + + //* Process block sources and create databases + const blockSources = files.filter(file => file.includes('Blocks')).sort((a, b) => a.localeCompare(b)) + const locationIdList: number[] = [] + const areaDatabase: Record = {} + + for (const blockSource of blockSources) { + await createBlockDatabase(blockSource, locationData, locationIdList, areaDatabase, settings) + } + + if (settings.locationFile) { + await createLocationDatabase(locationData, locationIdList, areaDatabase, settings) + } +} + +/** + * Represents the data for a location in the database. + */ +export interface LocationData { + geoname_id: string + locale_code: string + continent_code: string + continent_name: string + country_iso_code: string + country_name: string + subdivision_1_iso_code: string + subdivision_1_name: string + subdivision_2_iso_code: string + subdivision_2_name: string + city_name: string + metro_code: string + time_zone: string + is_in_european_union: string + counter?: number +} + +/** + * Extracts location data from a CSV file. + * @param file - Path to the CSV file. + * @param settings - IP location API settings. + * @returns A promise that resolves to a record of location data. + */ +async function getLocationData(file: string, settings: IpLocationApiSettings): Promise> { + const stream = createReadStream(path.join(settings.tmpDataDir, file)) + const result: Record = {} + return new Promise((resolve, reject) => { + stream + .pipe(parse({ headers: true })) + .on('error', reject) + .on('data', (row: LocationData) => { + if (settings.dataType === 'Country') { + result[Number.parseInt(row.geoname_id)] = row.country_iso_code + } + else { + result[Number.parseInt(row.geoname_id)] = row + } + }) + .on('end', () => resolve(result)) + }) +} + +/** + * Optimizes location data by merging and deduplicating entries. + * @param mapDatas - Array of location data records. + * @param settings - IP location API settings. + */ +function minifyLocationData(mapDatas: Record[], settings: IpLocationApiSettings): void { + const [primaryData, secondaryData] = mapDatas as [Record, Record | undefined] + + //* Handle non-English languages by merging location names + if (settings.language !== 'en' && secondaryData) { + mergeNonEnglishNames(primaryData, secondaryData) + } + + const locIds = Object.keys(primaryData).map(key => Number.parseInt(key)) + locIds.sort((a, b) => a - b) + + const locFields = getLocationFields(settings) + const checkFields = mapLocationFields(locFields) + + //* Group location IDs by their primary field (usually country) + const groupedLocations = groupLocationsByPrimaryField(primaryData, locIds, checkFields[0]!) + + //* Deduplicate and merge similar locations within each group + deduplicateLocations(primaryData, groupedLocations, checkFields.slice(1)) +} + +/** + * Merges non-English names into the primary data. + * @param primaryData - Primary location data record. + * @param secondaryData - Secondary location data record with non-English names. + */ +function mergeNonEnglishNames(primaryData: Record, secondaryData: Record): void { + for (const locId in primaryData) { + if (secondaryData[locId]) { + const fields = ['city_name', 'subdivision_1_name', 'subdivision_2_name'] as const + for (const field of fields) { + if (secondaryData[locId][field]) { + primaryData[locId]![field] = secondaryData[locId][field] + } + } + } + } +} + +/** + * Retrieves and sorts location fields based on settings. + * @param settings - IP location API settings. + * @returns Sorted array of location fields. + */ +function getLocationFields(settings: IpLocationApiSettings): (typeof LOCATION_FIELDS[number])[] { + const fieldRanking: Record = { + metro: 1, + region2_name: 2, + region2: 3, + timezone: 4, + region1_name: 5, + region1: 6, + country: 7, + city: 8, + eu: 9, + } + return LOCATION_FIELDS + .filter(field => settings.fields.includes(field)) + .sort((a, b) => fieldRanking[b] - fieldRanking[a]) +} + +/** + * Maps location fields to their corresponding LocationData keys. + * @param locFields - Array of location fields. + * @returns Array of LocationData keys. + */ +function mapLocationFields(locFields: (typeof LOCATION_FIELDS[number])[]): (keyof LocationData)[] { + const fieldMapping: Record = { + country: 'country_iso_code', + region1: 'subdivision_1_iso_code', + region1_name: 'subdivision_1_name', + region2: 'subdivision_2_iso_code', + region2_name: 'subdivision_2_name', + city: 'city_name', + metro: 'metro_code', + timezone: 'time_zone', + eu: 'is_in_european_union', + } + return locFields.map(field => fieldMapping[field]) +} + +/** + * Groups location IDs by their primary field value. + * @param primaryData - Primary location data record. + * @param locIds - Array of location IDs. + * @param primaryField - The primary field to group by. + * @returns Record of grouped location IDs. + */ +function groupLocationsByPrimaryField( + primaryData: Record, + locIds: number[], + primaryField: keyof LocationData, +): Record { + const groupedLocations: Record = {} + for (const locId of locIds) { + const key = primaryData[locId]![primaryField as keyof LocationData]! + if (!groupedLocations[key]) { + groupedLocations[key] = [] + } + groupedLocations[key].push(locId) + } + return groupedLocations +} + +/** + * Deduplicates locations within grouped location IDs. + * @param primaryData - Primary location data record. + * @param groupedLocations - Record of grouped location IDs. + * @param checkFields - Array of fields to check for equality. + */ +function deduplicateLocations( + primaryData: Record, + groupedLocations: Record, + checkFields: (keyof LocationData)[], +): void { + for (const group of Object.values(groupedLocations)) { + for (let i = 0; i < group.length; i++) { + const baseLocation = primaryData[group[i]!]! + for (let j = i + 1; j < group.length; j++) { + const compareLocation = primaryData[group[j]!]! + if (areLocationsEqual(baseLocation, compareLocation, checkFields)) { + //* Merge duplicate locations by keeping the base location and removing the duplicate + primaryData[group[j]!] = baseLocation + group.splice(j, 1) + j-- + } + } + } + } +} + +/** + * Checks if two locations are equal based on specified fields. + * @param loc1 - First location to compare. + * @param loc2 - Second location to compare. + * @param fields - Array of fields to check for equality. + * @returns Boolean indicating if locations are equal. + */ +function areLocationsEqual(loc1: LocationData, loc2: LocationData, fields: (keyof LocationData)[]): boolean { + return fields.every(field => loc1[field] === loc2[field]) +} diff --git a/packages/util/src/functions/database/createLocationDatabase.ts b/packages/util/src/functions/database/createLocationDatabase.ts new file mode 100644 index 0000000..3c929ef --- /dev/null +++ b/packages/util/src/functions/database/createLocationDatabase.ts @@ -0,0 +1,281 @@ +import type { WriteStream } from 'node:fs' +import type { IpLocationApiSettings } from '../getSettings.js' +import type { LocationData } from './createDatabase.js' +import { Buffer } from 'node:buffer' +import { createWriteStream } from 'node:fs' +import { writeFile } from 'node:fs/promises' +import path from 'node:path' +import { makeDatabase } from '../makeDatabase.js' +import { stringToNumber37 } from '../stringToNumber37.js' + +/** + * Interface representing various location-related databases + */ +interface LocationDatabases { + sub1Database: Record + sub2Database: Record + timezoneDatabase: Record +} + +/** + * Creates a location database from the provided location data. + * @param locationData - Array of location data records + * @param locationIdList - List of location IDs to process + * @param areaDatabase - A database of area data. + * @param settings - IP location API settings + */ +export async function createLocationDatabase( + locationData: Record[], + locationIdList: number[], + areaDatabase: Record, + settings: IpLocationApiSettings, +): Promise { + const locationDataMap = locationData[0] as Record + const locationDataStream = createWriteStream(path.join(settings.fieldDir, 'location.dat.tmp')) + const nameDataStream = createWriteStream(path.join(settings.fieldDir, 'name.dat.tmp')) + + const cityNameToIndex: Record = {} + const euCountryCodes: Record = {} + const regionDatabases: LocationDatabases = { + sub1Database: {}, + sub2Database: {}, + timezoneDatabase: {}, + } + + //* Process each location ID + for (const locationId of locationIdList) { + const locationInfo = locationDataMap[locationId] + if (!locationInfo) + continue + + const locationBuffer = createLocationBuffer(locationInfo, settings, cityNameToIndex, euCountryCodes, regionDatabases, nameDataStream) + locationDataStream.write(locationBuffer) + } + + locationDataStream.end() + nameDataStream.end() + + await createSubJsonFile(settings, regionDatabases, euCountryCodes, areaDatabase) +} + +/** + * Creates a buffer for a single location record. + * @param locationInfo - The location data + * @param settings - IP location API settings + * @param cityNameToIndex - Hash table for city names + * @param euCountryCodes - Hash table for EU status + * @param regionDatabases - Object containing various location databases + * @param nameDataStream - WriteStream for the name data file + * @returns A buffer containing the encoded location data + */ +function createLocationBuffer( + locationInfo: LocationData, + settings: IpLocationApiSettings, + cityNameToIndex: Record, + euCountryCodes: Record, + regionDatabases: LocationDatabases, + nameDataStream: WriteStream, +): Buffer { + const buffer = Buffer.alloc(settings.locationRecordSize) + let offset = 0 + + //* Write each field to the buffer if it's included in the settings + const fieldWriters: Record number> = { + country: () => writeCountryCode(buffer, offset, locationInfo, settings, euCountryCodes), + region1: () => writeRegion(buffer, offset, locationInfo.subdivision_1_iso_code), + region1_name: () => writeRegionName(buffer, offset, locationInfo.subdivision_1_name, regionDatabases.sub1Database), + region2: () => writeRegion(buffer, offset, locationInfo.subdivision_2_iso_code), + region2_name: () => writeRegionName(buffer, offset, locationInfo.subdivision_2_name, regionDatabases.sub2Database), + metro: () => writeMetroCode(buffer, offset, locationInfo), + timezone: () => writeTimezone(buffer, offset, locationInfo, regionDatabases), + city: () => writeCity(buffer, offset, locationInfo, cityNameToIndex, nameDataStream), + } + + for (const [field, writer] of Object.entries(fieldWriters)) { + if (settings.fields.includes(field as IpLocationApiSettings['fields'][number])) { + offset = writer() + } + } + + return buffer +} + +/** + * Writes the country code to the buffer and updates the EU hash if necessary. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param locationInfo - The location data + * @param settings - IP location API settings + * @param euCountryCodes - Hash table for EU status + * @returns The new offset after writing + */ +function writeCountryCode(buffer: Buffer, offset: number, locationInfo: LocationData, settings: IpLocationApiSettings, euCountryCodes: Record): number { + const { country_iso_code: countryCode, is_in_european_union: isEU } = locationInfo + if (countryCode && countryCode.length === 2) { + buffer.write(countryCode, offset) + if (settings.fields.includes('eu') && Number.parseInt(isEU, 10) === 1) { + euCountryCodes[countryCode] = true + } + } + return offset + 2 +} + +/** + * Writes a region code to the buffer. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param regionCode - The region code to write + * @returns The new offset after writing + */ +function writeRegion(buffer: Buffer, offset: number, regionCode: string | undefined): number { + if (regionCode) + buffer.writeUInt16LE(stringToNumber37(regionCode), offset) + return offset + 2 +} + +/** + * Writes a region name to the buffer. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param regionName - The region name to write + * @param database - The database to use for encoding the region name + * @returns The new offset after writing + */ +function writeRegionName(buffer: Buffer, offset: number, regionName: string | undefined, database: Record): number { + if (regionName) + buffer.writeUInt16LE(makeDatabase(regionName, database), offset) + return offset + 2 +} + +/** + * Writes a metro code to the buffer. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param locationInfo - The location data + * @returns The new offset after writing + */ +function writeMetroCode(buffer: Buffer, offset: number, locationInfo: LocationData): number { + const { metro_code: metro } = locationInfo + if (metro) + buffer.writeUInt16LE(Number.parseInt(metro, 10), offset) + return offset + 2 +} + +/** + * Writes a timezone to the buffer. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param locationInfo - The location data + * @param regionDatabases - Object containing various location databases + * @returns The new offset after writing + */ +function writeTimezone(buffer: Buffer, offset: number, locationInfo: LocationData, regionDatabases: LocationDatabases): number { + const { time_zone: timezone } = locationInfo + if (timezone) + buffer.writeUInt16LE(makeDatabase(timezone, regionDatabases.timezoneDatabase), offset) + return offset + 2 +} + +/** + * Writes a city name to the buffer. + * @param buffer - The buffer to write to + * @param offset - The current offset in the buffer + * @param locationInfo - The location data + * @param cityNameToIndex - Hash table for city names + * @param nameDataStream - WriteStream for the name data file + * @returns The new offset after writing + */ +function writeCity(buffer: Buffer, offset: number, locationInfo: LocationData, cityNameToIndex: Record, nameDataStream: WriteStream): number { + const { city_name: cityName } = locationInfo + if (cityName) { + buffer.writeUInt32LE(inputBuffer(cityNameToIndex, nameDataStream, cityName), offset) + } + return offset + 4 +} + +/** + * Creates a sub.json file with additional location data. + * @param settings - IP location API settings + * @param regionDatabases - Object containing various location databases + * @param euCountryCodes - Hash table for EU status + * @param areaDatabase - A database of area data. + */ +async function createSubJsonFile( + settings: IpLocationApiSettings, + regionDatabases: LocationDatabases, + euCountryCodes: Record, + areaDatabase: Record, +): Promise { + const subJsonData: Record = { + region1_name: databaseToArray(regionDatabases.sub1Database), + region2_name: databaseToArray(regionDatabases.sub2Database), + timezone: databaseToArray(regionDatabases.timezoneDatabase), + area: databaseToArray(areaDatabase).map(area => Number.parseInt(area, 10) || 0), + eu: euCountryCodes, + } + + //* Remove unused fields based on settings + if (!settings.fields.includes('region1_name')) + delete subJsonData.region1_name + if (!settings.fields.includes('region2_name')) + delete subJsonData.region2_name + if (!settings.fields.includes('timezone')) + delete subJsonData.timezone + if (!settings.fields.includes('area')) + delete subJsonData.area + if (!settings.fields.includes('eu')) + delete subJsonData.eu + + if (Object.keys(subJsonData).length > 0) { + await writeFile(path.join(settings.fieldDir, 'sub.json.tmp'), JSON.stringify(subJsonData)) + } +} + +/** + * Converts a database to an array. + * @param database - The database to convert. + * @returns The array. + */ +function databaseToArray(database: Record): string[] { + const array: string[] = [''] + for (const [key, value] of Object.entries(database)) { + array[value] = key + } + return array +} + +/** + * Processes and stores text data in a buffer, managing offsets and hash entries. + * @param nameToIndex - Object to store text-to-number mappings and track buffer offset + * @param dataStream - WriteStream to write buffer data + * @param text - The text to process and store + * @returns A number representing the stored text (either existing hash value or new encoded value) + */ +function inputBuffer(nameToIndex: Record, dataStream: WriteStream, text: string): number { + //* If text is already in nameToIndex, return its value + if (nameToIndex[text]) + return nameToIndex[text] + + //* Initialize buffer offset if not yet set + if (nameToIndex.__offsetBB === undefined) { + const buffer = Buffer.alloc(1) + dataStream.write(buffer) + nameToIndex.__offsetBB = 1 + } + + const offset = nameToIndex.__offsetBB + const buffer = Buffer.from(text) + + //* Encode text length and offset into a single number + //* The first 8 bits represent the length, and the remaining bits represent the offset + const encodedValue = buffer.length + (offset << 8) + + //* Write the text buffer to the data stream + dataStream.write(buffer) + + //* Update the offset for the next write + nameToIndex.__offsetBB = offset + buffer.length + + //* Store and return the encoded value + return nameToIndex[text] = encodedValue +} diff --git a/packages/util/src/functions/database/createSmallMemoryFile.ts b/packages/util/src/functions/database/createSmallMemoryFile.ts new file mode 100644 index 0000000..d0712e8 --- /dev/null +++ b/packages/util/src/functions/database/createSmallMemoryFile.ts @@ -0,0 +1,61 @@ +import type { IpLocationApiSettings } from '../getSettings.js' +import { Buffer } from 'node:buffer' +import { createWriteStream, existsSync, mkdirSync, writeFile, type WriteStream } from 'node:fs' +import path from 'node:path' +import { getSmallMemoryFile } from '../getSmallMemoryFile.js' + +/** + * Creates or appends to a small memory file for IP location data. + * @param writeStream - The current write stream, if any + * @param version - IP version (4 or 6) + * @param lineCount - Number of lines processed + * @param buffer2 - First buffer to write + * @param buffer3 - Second buffer to write + * @param settings - IP location API settings + * @returns The write stream if the operation is not complete, undefined otherwise + */ +export function createSmallMemoryFile( + writeStream: WriteStream, + version: 4 | 6, + lineCount: number, + buffer2: Buffer, + buffer3: Buffer, + settings: IpLocationApiSettings, +) { + //* Get file path and offset based on line count and IP version + const [_dir, file, offset] = getSmallMemoryFile(lineCount, version === 4 ? settings.v4 : settings.v6, true) + + if (offset === 0) { + //* We're starting a new file + const dir = path.join(settings.fieldDir, _dir) + + //* Close the previous write stream if it exists + if (writeStream) + writeStream.end() + + //* Create directory if it doesn't exist (only for the first file) + if (file === '_0' && !existsSync(dir)) { + mkdirSync(dir, { recursive: true }) + } + + //* If the total buffer size is smaller than or equal to the small memory file size, + //* write it all at once and return + if (settings.smallMemoryFileSize <= buffer2.length + buffer3.length) { + const buf = Buffer.alloc(buffer2.length + buffer3.length) + buffer2.copy(buf) + buffer3.copy(buf, buffer2.length) + writeFile(path.join(dir, file), buf, () => {}) + return + } + + //* Create a new write stream for the file + writeStream = createWriteStream(path.join(dir, file)) + } + + //* Write both buffers to the stream + writeStream.write(buffer2) + writeStream.write(buffer3) + + //* Return the write stream for future use + return writeStream +} diff --git a/packages/util/src/functions/database/downloadAndExtractDatabase.ts b/packages/util/src/functions/database/downloadAndExtractDatabase.ts new file mode 100644 index 0000000..4df650c --- /dev/null +++ b/packages/util/src/functions/database/downloadAndExtractDatabase.ts @@ -0,0 +1,202 @@ +import type { IpLocationApiSettings } from '../getSettings.js' +import { createHash } from 'node:crypto' +import { createReadStream, createWriteStream, existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import path from 'node:path' +import { Writable } from 'node:stream' +import ky from 'ky' +import { open } from 'yauzl' +import { DATABASE_SUFFIX_SHA, DATABASE_SUFFIX_ZIP, MAXMIND_URL } from '../../constants.js' +import { log } from '../log.js' + +/** + * Downloads and extracts the database if an update is needed. + * @param settings - The settings object. + * @returns An array of extracted file names or false if no update was needed. + */ +export async function downloadAndExtractDatabase(settings: IpLocationApiSettings): Promise<{ + files: string[] | false + sha256: string +}> { + const { edition, src } = getDatabaseInfo(settings) + const remoteHash = await getRemoteSha256(settings, edition) + const localHash = await getLocalSha256(settings, edition) + + if (await isUpToDate(settings, edition, remoteHash, localHash)) { + return { files: false, sha256: remoteHash } + } + + const zipPath = await downloadDatabase(settings, edition) + log('info', `Decompressing ${edition}...`) + const files = await extractDatabase(zipPath, settings.tmpDataDir, src) + log('info', `Decompressed ${edition}, extracted ${files.length} files`) + return { files, sha256: remoteHash } +} + +/** + * Determines the database edition and source files based on settings. + * @param settings - The settings object. + * @returns An object containing the edition and source file names. + */ +function getDatabaseInfo(settings: IpLocationApiSettings): { edition: string, src: string[] } { + const edition = `${settings.series}-${settings.dataType}-CSV` + const baseSrc = [ + `${settings.series}-${settings.dataType}-Locations-en.csv`, + `${settings.series}-${settings.dataType}-Blocks-IPv4.csv`, + `${settings.series}-${settings.dataType}-Blocks-IPv6.csv`, + ] + + //* Add language-specific file for non-English City databases + const src = settings.language !== 'en' && settings.dataType === 'City' + ? [...baseSrc, `${settings.series}-${settings.dataType}-Locations-${settings.language}.csv`] + : baseSrc + + return { edition, src } +} + +/** + * Fetches the remote SHA256 hash for the database. + * @param settings - The settings object. + * @param databaseEdition - The database edition string. + * @returns The remote SHA256 hash. + * @throws Error if the SHA256 hash cannot be downloaded or parsed. + */ +async function getRemoteSha256(settings: IpLocationApiSettings, databaseEdition: string): Promise { + const shaUrl = settings.licenseKey === 'redist' + ? `https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/${databaseEdition}${DATABASE_SUFFIX_SHA}` + : `${MAXMIND_URL}?edition_id=${databaseEdition}&suffix=${DATABASE_SUFFIX_SHA.slice(1)}&license_key=${settings.licenseKey}` + + const shaText = await ky.get(shaUrl).text() + const sha256 = shaText.match(/\w{50,}/)?.[0] + if (!sha256) { + throw new Error('Cannot download sha256') + } + return sha256 +} + +/** + * Retrieves the local SHA256 hash for the database. + * @param settings - The settings object. + * @param databaseEdition - The database edition string. + * @returns The local SHA256 hash or undefined if not found. + */ +async function getLocalSha256(settings: IpLocationApiSettings, databaseEdition: string): Promise { + try { + return await readFile(path.join(settings.fieldDir, `${databaseEdition}${DATABASE_SUFFIX_SHA}`), 'utf-8') + } + catch { + return undefined + } +} + +/** + * Checks if the local database is up to date. + * @param settings - The settings object. + * @param databaseEdition - The database edition string. + * @param remoteHash - The remote SHA256 hash. + * @param localHash - The local SHA256 hash. + * @returns True if the database is up to date, false otherwise. + */ +async function isUpToDate(settings: IpLocationApiSettings, databaseEdition: string, remoteHash: string, localHash: string | undefined): Promise { + if (localHash !== remoteHash) { + return false + } + + const zipPath = path.join(settings.tmpDataDir, `${databaseEdition}${DATABASE_SUFFIX_ZIP}`) + if (!existsSync(zipPath)) { + return false + } + + const zipHash = await sha256Hash(zipPath) + return zipHash === remoteHash +} + +/** + * Downloads the database ZIP file. + * @param settings - The settings object. + * @param databaseEdition - The database edition string. + * @returns The path to the downloaded ZIP file. + * @throws Error if the database cannot be downloaded. + */ +async function downloadDatabase(settings: IpLocationApiSettings, databaseEdition: string): Promise { + const zipUrl = settings.licenseKey === 'redist' + ? `https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/${databaseEdition}${DATABASE_SUFFIX_ZIP}` + : `${MAXMIND_URL}?edition_id=${databaseEdition}&suffix=${DATABASE_SUFFIX_ZIP.slice(1)}&license_key=${settings.licenseKey}` + + const response = await ky.get(zipUrl) + const stream = response.body + if (!stream) { + throw new Error('Cannot download database') + } + + const zipPath = path.join(settings.tmpDataDir, `${databaseEdition}${DATABASE_SUFFIX_ZIP}`) + await stream.pipeTo(Writable.toWeb(createWriteStream(zipPath))) + return zipPath +} + +/** + * Extracts specific files from the downloaded ZIP database. + * @param zipPath - The path to the ZIP file. + * @param outputDir - The directory to extract files to. + * @param filesToExtract - An array of file names to extract. + * @returns A promise that resolves to an array of extracted file names. + */ +async function extractDatabase(zipPath: string, outputDir: string, filesToExtract: string[]): Promise { + return new Promise((resolve, reject) => { + //* Open the zip file + open(zipPath, { lazyEntries: true }, (err, zipfile) => { + if (err) + return reject(err) + + //* Initialize an array to store extracted files + const extractedFiles: string[] = [] + + //* Listen for entry events in the zip file + zipfile.on('entry', (entry) => { + //* Check if the entry matches any files to extract + const matchedFile = filesToExtract.find(file => entry.fileName.endsWith(file)) + if (matchedFile) { + log('info', `Extracting ${matchedFile}...`) + //* Open the read stream for the entry + zipfile.openReadStream(entry, (err, readStream) => { + if (err) + return reject(err) + //* Create a write stream to save the entry to the output directory + const writeStream = createWriteStream(path.join(outputDir, matchedFile)) + //* Pipe the read stream to the write stream + readStream.pipe(writeStream) + //* When the read stream ends, add the file to the extracted files array and continue reading the next entry + readStream.on('end', () => { + extractedFiles.push(matchedFile) + zipfile.readEntry() + }) + }) + } + else { + //* If no files to extract match the entry, continue reading the next entry + zipfile.readEntry() + } + }) + + //* When the zip file is fully read, resolve the promise with the extracted files + zipfile.on('end', () => resolve(extractedFiles)) + //* Start reading the entries in the zip file + zipfile.readEntry() + }) + }) +} + +/** + * Calculates the SHA256 hash of a file. + * @param file - The path to the file. + * @returns A promise that resolves to the SHA256 hash of the file. + */ +function sha256Hash(file: string): Promise { + return new Promise((resolve, reject) => { + const stream = createReadStream(file) + const hash = createHash('sha256') + hash.once('finish', () => resolve(hash.digest('hex'))) + stream.on('error', reject) + stream.pipe(hash) + }) +} diff --git a/packages/util/src/functions/database/ensureDirectoriesExist.ts b/packages/util/src/functions/database/ensureDirectoriesExist.ts new file mode 100644 index 0000000..2326c47 --- /dev/null +++ b/packages/util/src/functions/database/ensureDirectoriesExist.ts @@ -0,0 +1,19 @@ +import type { IpLocationApiSettings } from '../getSettings.js' +import { existsSync } from 'node:fs' +import { mkdir } from 'node:fs/promises' +import { log } from '../log.js' + +/** + * Ensures that the necessary directories exist. + * @param param0 - Object containing fieldDir and tmpDataDir paths. + * @param param0.fieldDir - The path to the field directory. + * @param param0.tmpDataDir - The path to the temporary data directory. + */ +export async function ensureDirectoriesExist({ fieldDir, tmpDataDir }: IpLocationApiSettings): Promise { + for (const dir of [fieldDir, tmpDataDir]) { + if (!existsSync(dir)) { + log('info', `Creating directory ${dir}`) + await mkdir(dir, { recursive: true }) + } + } +} diff --git a/packages/util/src/functions/fetchArrayBuffer.test.ts b/packages/util/src/functions/fetchArrayBuffer.test.ts new file mode 100644 index 0000000..caf244e --- /dev/null +++ b/packages/util/src/functions/fetchArrayBuffer.test.ts @@ -0,0 +1,94 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { fetchArrayBuffer } from './fetchArrayBuffer' +import { sleep } from './sleep' + +vi.mock('./sleep') + +describe('fetchArrayBuffer', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should fetch ArrayBuffer successfully', async () => { + const mockArrayBuffer = new ArrayBuffer(8) + const mockResponse = { + ok: true, + headers: new Headers({ 'x-jsd-version': '1.0.0' }), + arrayBuffer: vi.fn().mockResolvedValue(mockArrayBuffer), + } + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) + + const result = await fetchArrayBuffer(new URL('https://example.com')) + + expect.soft(result).toEqual({ + buffer: mockArrayBuffer, + versionHeader: '1.0.0', + }) + expect.soft(globalThis.fetch).toHaveBeenCalledWith(new URL('https://example.com')) + }) + + it('should return null for 404 response', async () => { + const mockResponse = { + ok: false, + status: 404, + } + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) + + const result = await fetchArrayBuffer(new URL('https://example.com')) + + expect.soft(result).toBeNull() + expect.soft(globalThis.fetch).toHaveBeenCalledWith(new URL('https://example.com')) + }) + + it('should retry on non-404 error', async () => { + const mockResponse = { + ok: false, + status: 500, + } + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) + vi.mocked(sleep).mockResolvedValue(undefined) + + await fetchArrayBuffer(new URL('https://example.com')) + + expect.soft(globalThis.fetch).toHaveBeenCalledTimes(4) // Initial request + 3 retries + expect.soft(sleep).toHaveBeenCalledTimes(3) + expect.soft(sleep).toHaveBeenNthCalledWith(1, 100) + expect.soft(sleep).toHaveBeenNthCalledWith(2, 400) + expect.soft(sleep).toHaveBeenNthCalledWith(3, 900) + }) + + it('should return null after all retries fail', async () => { + const mockResponse = { + ok: false, + status: 500, + } + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) + vi.mocked(sleep).mockResolvedValue(undefined) + + const result = await fetchArrayBuffer(new URL('https://example.com')) + + expect.soft(result).toBeNull() + expect.soft(globalThis.fetch).toHaveBeenCalledTimes(4) // Initial request + 3 retries + }) + + it('should handle missing version header', async () => { + const mockArrayBuffer = new ArrayBuffer(8) + const mockResponse = { + ok: true, + headers: new Headers(), + arrayBuffer: vi.fn().mockResolvedValue(mockArrayBuffer), + } + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) + + const result = await fetchArrayBuffer(new URL('https://example.com')) + + expect.soft(result).toEqual({ + buffer: mockArrayBuffer, + versionHeader: undefined, + }) + }) +}) diff --git a/packages/util/src/functions/fetchArrayBuffer.ts b/packages/util/src/functions/fetchArrayBuffer.ts new file mode 100644 index 0000000..d79d60b --- /dev/null +++ b/packages/util/src/functions/fetchArrayBuffer.ts @@ -0,0 +1,37 @@ +import { sleep } from './sleep.js' + +/** + * Fetches an ArrayBuffer from a given URL with retry functionality. + * @param url - The URL to fetch the ArrayBuffer from. + * @param retry - The number of retry attempts (default: 3). + * @returns A Promise that resolves to an object containing the ArrayBuffer and version header, or null if the fetch fails. + */ +export async function fetchArrayBuffer( + url: URL, + retry = 3, +): Promise<{ + buffer: ArrayBuffer + versionHeader?: string +} | null> { + return await fetch(url).then( + async (response) => { + if (!response.ok) { + if (response.status === 404) + return null + + if (retry) { + //* Exponential backoff: Delay increases quadratically with each retry + await sleep(100 * (4 - retry) * (4 - retry)) + return fetchArrayBuffer(url, retry - 1) + } + return null + } + + return { + //* Extract the version header if present, otherwise undefined (jsDelivr CDN) + versionHeader: response.headers.get('x-jsd-version') ?? undefined, + buffer: await response.arrayBuffer(), + } + }, + ) +} diff --git a/packages/util/src/functions/getFieldsSize.test.ts b/packages/util/src/functions/getFieldsSize.test.ts new file mode 100644 index 0000000..b612b21 --- /dev/null +++ b/packages/util/src/functions/getFieldsSize.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { getFieldsSize } from './getFieldsSize' + +describe('getFieldsSize', () => { + it('should calculate the correct size for a single field', () => { + expect.soft(getFieldsSize(['postcode'])).toBe(5) + expect.soft(getFieldsSize(['area'])).toBe(1) + expect.soft(getFieldsSize(['latitude'])).toBe(4) + expect.soft(getFieldsSize(['longitude'])).toBe(4) + expect.soft(getFieldsSize(['city'])).toBe(4) + expect.soft(getFieldsSize(['eu'])).toBe(0) + expect.soft(getFieldsSize(['someOtherField'])).toBe(2) + }) + + it('should calculate the correct size for multiple fields', () => { + expect.soft(getFieldsSize(['postcode', 'area', 'latitude'])).toBe(10) + expect.soft(getFieldsSize(['city', 'longitude', 'eu'])).toBe(8) + expect.soft(getFieldsSize(['area', 'someOtherField', 'postcode'])).toBe(8) + }) + + it('should return 0 for an empty array', () => { + expect.soft(getFieldsSize([])).toBe(0) + }) + + it('should calculate the correct size for all possible fields', () => { + expect.soft(getFieldsSize(['postcode', 'area', 'latitude', 'longitude', 'city', 'eu', 'someOtherField'])).toBe(20) + }) +}) diff --git a/packages/util/src/functions/getFieldsSize.ts b/packages/util/src/functions/getFieldsSize.ts new file mode 100644 index 0000000..6963d80 --- /dev/null +++ b/packages/util/src/functions/getFieldsSize.ts @@ -0,0 +1,32 @@ +/** + * Calculates the total size required for a set of fields. + * + * @param {string[]} fields - An array of field names to calculate the size for. + * @returns {number} The total size required for all fields. + */ +export function getFieldsSize(fields: string[]): number { + let size = 0 + + for (const field of fields) { + switch (field) { + case 'postcode': + size += 5 + break + case 'area': + size += 1 + break + case 'latitude': + case 'longitude': + case 'city': + size += 4 + break + case 'eu': + break + default: + size += 2 + break + } + } + + return size +} diff --git a/packages/util/src/functions/getPostcodeDatabase.test.ts b/packages/util/src/functions/getPostcodeDatabase.test.ts new file mode 100644 index 0000000..35e0018 --- /dev/null +++ b/packages/util/src/functions/getPostcodeDatabase.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { getPostcodeDatabase } from './getPostcodeDatabase' + +describe('getPostcodeDatabase', () => { + it('should return [0, 0] for empty input', () => { + expect.soft(getPostcodeDatabase('')).toEqual([0, 0]) + }) + + describe('numeric postcodes', () => { + it('should handle single-part numeric postcodes', () => { + expect.soft(getPostcodeDatabase('12345')).toEqual([5, 12345]) + expect.soft(getPostcodeDatabase('123456789')).toEqual([9, 123456789]) + }) + + it('should handle two-part numeric postcodes', () => { + expect.soft(getPostcodeDatabase('123-456')).toEqual([33, 123456]) + expect.soft(getPostcodeDatabase('12 34')).toEqual([22, 1234]) + expect.soft(getPostcodeDatabase('123456 789012')).toEqual([66, 123456789012]) + }) + }) + + describe('string postcodes', () => { + it('should handle single-part string postcodes (short)', () => { + expect.soft(getPostcodeDatabase('ABC123')).toEqual([-6, 623698779]) + expect.soft(getPostcodeDatabase('XYZ999')).toEqual([-6, 2054135709]) + }) + + it('should handle single-part string postcodes (long)', () => { + expect.soft(getPostcodeDatabase('ABCDEF123456')).toEqual([82, 41474266563513784]) + expect.soft(getPostcodeDatabase('XYZXYZ999999')).toEqual([105, 127959823201186850]) + }) + + it('should handle two-part string postcodes', () => { + expect.soft(getPostcodeDatabase('ABC-123')).toEqual([-33, 623698779]) + expect.soft(getPostcodeDatabase('XY 9Z')).toEqual([-22, 1584071]) + }) + }) + + it('should return [0, 0] for invalid postcodes', () => { + expect.soft(getPostcodeDatabase('ABC-123-XYZ')).toEqual([0, 0]) + expect.soft(getPostcodeDatabase('!@#$%^')).toEqual([0, 0]) + }) + + //* Edge cases + it('should handle edge cases', () => { + expect.soft(getPostcodeDatabase('9'.repeat(9))).toEqual([9, 999999999]) + expect.soft(getPostcodeDatabase('Z'.repeat(6))).toEqual([-6, 2176782335]) + expect.soft(getPostcodeDatabase('A'.repeat(6))).toEqual([-6, 621937810]) + }) +}) diff --git a/packages/util/src/functions/getPostcodeDatabase.ts b/packages/util/src/functions/getPostcodeDatabase.ts new file mode 100644 index 0000000..e48afa9 --- /dev/null +++ b/packages/util/src/functions/getPostcodeDatabase.ts @@ -0,0 +1,67 @@ +import { log } from './log.js' + +const isPostNumReg = /^\d+$/ +const isPostNumReg2 = /^(\d+)[-\s](\d+)$/ +const isPostStrReg = /^[A-Z\d]+$/ +const isPostStrReg2 = /^([A-Z\d]+)[-\s]([A-Z\d]+)$/ + +/** + * Converts a postcode string into a tuple of two numbers for database storage. + * The first number represents the format, and the second number represents the postcode value. + * + * @param postcode - The input postcode string + * @returns A tuple [format, value] where: + * - format is positive for numeric postcodes, negative for string postcodes + * - value is the numeric representation of the postcode + */ +export function getPostcodeDatabase(postcode: string): [number, number] { + if (!postcode) + return [0, 0] + + //* Numeric postcode handling + if (isPostNumReg.test(postcode)) { + return [ + postcode.length, //* Format: 1~9 (length of the numeric postcode) + Number.parseInt(postcode, 10), //* Value: 0~999999999 + ] + } + + const numericMatch = isPostNumReg2.exec(postcode) + if (numericMatch) { + const [, part1, part2] = numericMatch as unknown as [string, string, string] + return [ + Number.parseInt(`${part1.length}${part2.length}`, 10), //* Format: 11~66 (lengths of two parts) + Number.parseInt(part1 + part2, 10), //* Value: 0~999999999 + ] + } + + //* String postcode handling + const stringMatch = isPostStrReg.exec(postcode) + if (stringMatch) { + const num = Number.parseInt(postcode, 36) + if (num < 2 ** 32) { + return [ + -postcode.length, //* Format: -1~-9 (negative length of the string postcode) + num, //* Value: base 36 representation + ] + } + else { + return [ + Number.parseInt(`2${postcode.slice(0, 1)}`, 36), //* Format: 72~107 (special encoding for long postcodes) + Number.parseInt(postcode.slice(1), 36), //* Value: 0~2176782335 (base 36, max 6 chars) + ] + } + } + + const stringMatch2 = isPostStrReg2.exec(postcode) + if (!stringMatch2) { + log('warn', `Invalid postcode ${postcode}`) + return [0, 0] //* Invalid postcode + } + + const [, part1, part2] = stringMatch2 as unknown as [string, string, string] + return [ + -Number.parseInt(`${part1.length}${part2.length}`, 10), //* Format: -11~-55 (negative sum of part lengths) + Number.parseInt(part1 + part2, 36), //* Value: 0~2176782335 (base 36, max 6 chars) + ] +} diff --git a/packages/util/src/functions/getSettings.test.ts b/packages/util/src/functions/getSettings.test.ts new file mode 100644 index 0000000..891665f --- /dev/null +++ b/packages/util/src/functions/getSettings.test.ts @@ -0,0 +1,198 @@ +import type { IpLocationApiInputSettings } from './getSettings' +import { join } from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { DEFAULT_SETTINGS, LOCATION_FIELDS, MAIN_FIELDS } from '../constants' +import { getSettings } from './getSettings' + +describe('getSettings', () => { + const originalEnv = process.env + const originalArgv = process.argv + + beforeEach(() => { + vi.resetModules() + process.env = { ...originalEnv } + process.argv = [...originalArgv] + }) + + afterEach(() => { + process.env = originalEnv + process.argv = originalArgv + }) + + it('should return default settings when no input is provided', () => { + const settings = getSettings() + expect.soft(settings).toMatchObject({ + licenseKey: DEFAULT_SETTINGS.licenseKey, + series: DEFAULT_SETTINGS.series, + fields: DEFAULT_SETTINGS.fields, + language: DEFAULT_SETTINGS.language, + smallMemory: DEFAULT_SETTINGS.smallMemory, + smallMemoryFileSize: DEFAULT_SETTINGS.smallMemoryFileSize, + }) + }) + + it('should override default settings with input settings', () => { + const inputSettings: IpLocationApiInputSettings = { + licenseKey: 'test-key', + series: 'GeoIP2', + fields: ['country', 'city'], + language: 'es', + smallMemory: true, + smallMemoryFileSize: 8192, + } + const settings = getSettings(inputSettings) + expect.soft(settings).toMatchObject({ + licenseKey: 'test-key', + series: 'GeoIP2', + fields: ['country', 'city'], + language: 'es', + smallMemory: true, + smallMemoryFileSize: 8192, + }) + }) + + it('should use environment variables to override default settings', () => { + process.env.ILA_LICENSE_KEY = 'env-key' + process.env.ILA_SERIES = 'GeoIP2' + process.env.ILA_FIELDS = 'country,city' + process.env.ILA_LANGUAGE = 'fr' + process.env.ILA_SMALL_MEMORY = 'true' + process.env.ILA_SMALL_MEMORY_FILE_SIZE = '16384' + + const settings = getSettings() + expect.soft(settings).toMatchObject({ + licenseKey: 'env-key', + series: 'GeoIP2', + fields: ['country', 'city'], + language: 'fr', + smallMemory: true, + smallMemoryFileSize: 16384, + }) + }) + + it('should use CLI arguments to override default and environment settings', () => { + process.env.ILA_LICENSE_KEY = 'env-key' + process.argv = [ + ...process.argv, + 'ILA_LICENSE_KEY=cli-key', + 'ILA_SERIES=GeoIP2', + 'ILA_FIELDS=country,region1', + 'ILA_LANGUAGE=de', + 'ILA_SMALL_MEMORY=true', + 'ILA_SMALL_MEMORY_FILE_SIZE=32768', + ] + + const settings = getSettings() + expect.soft(settings).toMatchObject({ + licenseKey: 'cli-key', + series: 'GeoIP2', + fields: ['country', 'region1'], + language: 'de', + smallMemory: true, + smallMemoryFileSize: 32768, + }) + }) + + it('should process "all" fields correctly', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: 'all', + } + const settings = getSettings(inputSettings) + expect.soft(settings.fields).toEqual([...MAIN_FIELDS, ...LOCATION_FIELDS]) + + process.argv = [...process.argv, 'ILA_FIELDS=all'] + const settings2 = getSettings() + expect.soft(settings2.fields).toEqual([...MAIN_FIELDS, ...LOCATION_FIELDS]) + }) + + it('should filter out invalid fields', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: ['country', 'invalid_field', 'city'] as any, + } + const settings = getSettings(inputSettings) + expect.soft(settings.fields).toEqual(['country', 'city']) + }) + + it('should handle invalid fields input', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: 0 as any, + } + const settings = getSettings(inputSettings) + expect.soft(settings.fields).toEqual(DEFAULT_SETTINGS.fields) + }) + + it('should use default fields if all provided fields are invalid', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: ['invalid_field1', 'invalid_field2'] as any, + } + const settings = getSettings(inputSettings) + expect.soft(settings.fields).toEqual(DEFAULT_SETTINGS.fields) + }) + + it('should process directory paths correctly', () => { + const inputSettings: IpLocationApiInputSettings = { + dataDir: '../custom-data', + tmpDataDir: '/tmp/custom-tmp', + } + const settings = getSettings(inputSettings) + expect.soft(settings.dataDir).toMatch(/custom-data$/) + expect.soft(settings.tmpDataDir).toBe('/tmp/custom-tmp') + }) + + it('should calculate correct record sizes and database settings', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: ['country', 'city', 'latitude', 'longitude'], + smallMemory: true, + smallMemoryFileSize: 4096, + } + const settings = getSettings(inputSettings) + expect.soft(settings.dataType).toBe('City') + expect.soft(settings.locationFile).toBe(true) + expect.soft(settings.mainRecordSize).toBeGreaterThan(0) + expect.soft(settings.locationRecordSize).toBeGreaterThan(0) + expect.soft(settings.v4.recordSize).toBeGreaterThan(settings.mainRecordSize) + expect.soft(settings.v6.recordSize).toBeGreaterThan(settings.mainRecordSize) + expect.soft(settings.v4.fileLineMax).toBeGreaterThan(0) + expect.soft(settings.v6.fileLineMax).toBeGreaterThan(0) + + const settings2 = getSettings({ + fields: ['country', 'city', 'latitude', 'longitude'], + smallMemory: true, + smallMemoryFileSize: 0, + }) + expect.soft(settings2.dataType).toBe('City') + expect.soft(settings2.locationFile).toBe(true) + expect.soft(settings2.mainRecordSize).toBeGreaterThan(0) + expect.soft(settings2.locationRecordSize).toBeGreaterThan(0) + expect.soft(settings2.v4.recordSize).toBeGreaterThan(settings2.mainRecordSize) + expect.soft(settings2.v6.recordSize).toBeGreaterThan(settings2.mainRecordSize) + expect.soft(settings2.v4.fileLineMax).toBe(1) + expect.soft(settings2.v6.fileLineMax).toBe(1) + }) + + it('should generate correct fieldDir', () => { + const inputSettings: IpLocationApiInputSettings = { + fields: ['country', 'city'], + dataDir: '/custom/data/dir', + } + const settings = getSettings(inputSettings) + const expectedFieldDir = join('/custom/data/dir', (16 + 2048).toString(36)) + expect.soft(settings.fieldDir).toBe(expectedFieldDir) + }) + + it('should handle invalid language input', () => { + const inputSettings: IpLocationApiInputSettings = { + language: 'invalid_language' as any, + } + const settings = getSettings(inputSettings) + expect.soft(settings.language).toBe(DEFAULT_SETTINGS.language) + }) + + it('should handle invalid series input', () => { + const inputSettings: IpLocationApiInputSettings = { + series: 'InvalidSeries' as any, + } + const settings = getSettings(inputSettings) + expect.soft(settings.series).toBe(DEFAULT_SETTINGS.series) + }) +}) diff --git a/packages/util/src/functions/getSettings.ts b/packages/util/src/functions/getSettings.ts new file mode 100644 index 0000000..19fef35 --- /dev/null +++ b/packages/util/src/functions/getSettings.ts @@ -0,0 +1,296 @@ +import type { Buffer } from 'node:buffer' +import { dirname, join, resolve as resolvePath } from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { DEFAULT_SETTINGS, LOCATION_FIELDS, MAIN_FIELDS, setSavedSettings, v4, v6 } from '../constants.js' +import { getFieldsSize } from './getFieldsSize.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +export interface LocalDatabase { + version: Version + recordSize: number + fileLineMax: number + folderLineMax: number + loadedData?: { + startIps: Version extends 4 ? Uint32Array : BigUint64Array + endIps?: Version extends 4 ? Uint32Array : BigUint64Array + mainBuffer?: Buffer + lastLine: number + firstIp: Version extends 4 ? number : bigint + } +} + +/** + * The settings for the IpLocationApi + */ +export interface IpLocationApiInputSettings { + /** + * Your MaxMind license key or 'redist' for free GeoLite2 Redistributed database. + * @default 'redist' + */ + licenseKey?: string + /** + * The series of the database, GeoLite2 or GeoIP2. If you use 'redist', this setting is ignored. + * @default 'GeoLite2' + * @requires licenseKey + */ + series?: 'GeoLite2' | 'GeoIP2' + + /** + * The directory to save the database + * @default '../data' + */ + dataDir?: string + /** + * The directory to save the temporary database + * @default '../tmp' + */ + tmpDataDir?: string + /** + * The fields to include in the database + * @default ['country'] + */ + fields?: ((typeof MAIN_FIELDS)[number] | (typeof LOCATION_FIELDS)[number])[] | 'all' + /** + * The language of the database + * @default 'en' + */ + language?: 'de' | 'en' | 'es' | 'fr' | 'ja' | 'pt-BR' | 'ru' | 'zh-CN' + /** + * Whether to use small memory mode + * @default false + */ + smallMemory?: boolean + /** + * The file size for small memory mode + * @default 4096 + */ + smallMemoryFileSize?: number + /** + * Whether to add country info in the result + * @default false + * + * @requires countries-list package (optional peer dependency) + * @link https://www.npmjs.com/package/countries-list + */ + addCountryInfo?: boolean + /** + * Whether to suppress logging + * @default false + */ + silent?: boolean +} + +export interface IpLocationApiSettings { + licenseKey: string + series: 'GeoLite2' | 'GeoIP2' + dataDir: string + tmpDataDir: string + fields: ((typeof MAIN_FIELDS)[number] | (typeof LOCATION_FIELDS)[number])[] + language: 'de' | 'en' | 'es' | 'fr' | 'ja' | 'pt-BR' | 'ru' | 'zh-CN' + fieldDir: string + smallMemory: boolean + smallMemoryFileSize: number + dataType: 'Country' | 'City' + locationFile: boolean + mainRecordSize: number + locationRecordSize: number + v4: LocalDatabase<4> + v6: LocalDatabase<6> + addCountryInfo: boolean + silent: boolean +} + +/** + * Get the key from the environment variable + * @param key The key to get + * @returns The key + * + * @example + * getKey('testKey') -> 'ILA_TEST_KEY' + */ +function getKey(key: string): string { + return `ILA_${key.replace(/([A-Z])/g, '_$1').replace(/^_/, '').toUpperCase()}` +} + +/** + * The bit flag for the fields + */ +const FIELD_BIT_FLAG: Record<((typeof MAIN_FIELDS)[number] | (typeof LOCATION_FIELDS)[number]), number> = { + latitude: 1, + longitude: 2, + area: 4, + postcode: 8, + country: 16, + region1: 32, + region1_name: 64, + region2: 128, + region2_name: 256, + metro: 512, + timezone: 1024, + city: 2048, + eu: 4096, +} + +/** + * Get the settings from various sources and merge them with default values + * @param settings The settings from the function parameters + * @returns The merged and processed IpLocationApiSettings + */ +export function getSettings(settings?: IpLocationApiInputSettings): IpLocationApiSettings { + //* Fetch settings from different sources + const envSettings = getFromIlaObject(process.env) + const cliSettings = getFromIlaObject( + Object.fromEntries( + process.argv + .slice(2) + .map(arg => arg.split('=') as [string, string]) + .filter(([key, value]) => key.startsWith('ILA_') && value), + ), + ) + + //* Merge settings with priority: defaults < env < CLI < function params + const mergedSettings = { + ...DEFAULT_SETTINGS, + ...envSettings, + ...cliSettings, + ...(settings ?? {}), + } + + //* Process and validate individual settings + const fields = processFields(mergedSettings.fields) + const series = processSeries(mergedSettings.series) + const dataDir = processDirectory(mergedSettings.dataDir) + const tmpDataDir = processDirectory(mergedSettings.tmpDataDir) + const language = processLanguage(mergedSettings.language) + const dataType = fields.length === 1 && fields[0] === 'country' ? 'Country' : 'City' + const locationFile = dataType !== 'Country' && LOCATION_FIELDS.some(field => fields.includes(field)) + let mainRecordSize = dataType === 'Country' ? 2 : getFieldsSize(fields.filter(field => (MAIN_FIELDS as unknown as string[]).includes(field))) + if (locationFile) + mainRecordSize += 4 + + v4.recordSize = mainRecordSize + v6.recordSize = mainRecordSize + + if (mergedSettings.smallMemory) { + v4.recordSize += 4 + v4.fileLineMax = (mergedSettings.smallMemoryFileSize / v4.recordSize | 0) || 1 + v4.folderLineMax = v4.fileLineMax * 1024 + + v6.recordSize += 8 + v6.fileLineMax = (mergedSettings.smallMemoryFileSize / v6.recordSize | 0) || 1 + v6.folderLineMax = v6.fileLineMax * 1024 + } + + //* Construct and return the final settings + return setSavedSettings({ + licenseKey: mergedSettings.licenseKey, + dataDir, + tmpDataDir, + fields, + series, + language, + fieldDir: join(dataDir, fields.reduce((sum, v) => sum + FIELD_BIT_FLAG[v], 0).toString(36)), + smallMemory: mergedSettings.smallMemory, + smallMemoryFileSize: mergedSettings.smallMemoryFileSize, + dataType, + locationFile, + mainRecordSize, + locationRecordSize: getFieldsSize(fields.filter(field => (LOCATION_FIELDS as unknown as string[]).includes(field))), + v4, + v6, + addCountryInfo: mergedSettings.addCountryInfo, + silent: mergedSettings.silent, + }) +} + +/** + * Extract IpLocationApi settings from an object with ILA_ prefixed keys + * @param ilaObject An object containing ILA_ prefixed keys + * @returns Partial IpLocationApiInputSettings + */ +function getFromIlaObject(ilaObject: Record): Partial { + const settings = { + licenseKey: ilaObject[getKey('licenseKey')], + series: ilaObject[getKey('series')] as 'GeoLite2' | 'GeoIP2', + dataDir: ilaObject[getKey('dataDir')], + tmpDataDir: ilaObject[getKey('tmpDataDir')], + fields: ilaObject[getKey('fields')] === 'all' ? 'all' : ilaObject[getKey('fields')]?.split(',') as IpLocationApiInputSettings['fields'], + language: ilaObject[getKey('language')] as IpLocationApiInputSettings['language'], + smallMemory: ilaObject[getKey('smallMemory')] ? ilaObject[getKey('smallMemory')] === 'true' : undefined, + smallMemoryFileSize: ilaObject[getKey('smallMemoryFileSize')] ? Number.parseInt(ilaObject[getKey('smallMemoryFileSize')]!) : undefined, + } + + //* Remove keys with undefined values + return Object.fromEntries( + Object.entries(settings).filter(([_, value]) => value !== undefined), + ) as Partial +} + +/** + * Process and validate the fields setting + * @param fields The input fields setting + * @returns Validated array of fields or default fields + */ +function processFields(fields: IpLocationApiInputSettings['fields']): IpLocationApiSettings['fields'] { + if (fields === 'all') { + return [...MAIN_FIELDS, ...LOCATION_FIELDS] + } + + if (Array.isArray(fields)) { + const validFields = fields.filter(field => + MAIN_FIELDS.includes(field as any) || LOCATION_FIELDS.includes(field as any), + ) as IpLocationApiSettings['fields'] + + return validFields.length > 0 ? validFields : DEFAULT_SETTINGS.fields + } + + return DEFAULT_SETTINGS.fields +} + +/** + * Process and validate the series setting + * @param series The input series setting + * @returns Validated series or default series + */ +function processSeries(series: IpLocationApiInputSettings['series']): IpLocationApiSettings['series'] { + if (series === 'GeoLite2' || series === 'GeoIP2') { + return series + } + + return DEFAULT_SETTINGS.series +} + +/** + * Regular expression to match Windows drive letters + */ +const WINDOWS_DRIVE_REG = /^[a-z]:\\/i + +/** + * Process and resolve the directory path + * @param directory The input directory path + * @returns Resolved absolute directory path + */ +export function processDirectory(directory: string): string { + //* If the path is not absolute, resolve it relative to __dirname + if (!directory.startsWith('/') && !directory.startsWith('\\\\') && !WINDOWS_DRIVE_REG.test(directory)) { + directory = resolvePath(__dirname, directory) + } + return directory +} + +/** + * Process and validate the language setting + * @param language The input language setting + * @returns Validated language or default language + */ +function processLanguage(language: IpLocationApiInputSettings['language']): IpLocationApiSettings['language'] { + const validLanguages = ['de', 'en', 'es', 'fr', 'ja', 'pt-BR', 'ru', 'zh-CN'] as const + if (language && validLanguages.includes(language)) { + return language + } + + return DEFAULT_SETTINGS.language +} diff --git a/packages/util/src/functions/getSmallMemoryFile.test.ts b/packages/util/src/functions/getSmallMemoryFile.test.ts new file mode 100644 index 0000000..a2f8852 --- /dev/null +++ b/packages/util/src/functions/getSmallMemoryFile.test.ts @@ -0,0 +1,63 @@ +import type { LocalDatabase } from './getSettings.js' +import { describe, expect, it } from 'vitest' +import { getSmallMemoryFile } from './getSmallMemoryFile' + +describe('getSmallMemoryFile', () => { + const mockDb: LocalDatabase = { + version: 4, + folderLineMax: 1000, + fileLineMax: 100, + recordSize: 10, + } + + it('should return correct values for the first line', () => { + const result = getSmallMemoryFile(0, mockDb) + expect.soft(result).toEqual(['v4/_0', '_0', 0]) + }) + + it('should return correct values for a line within the first file', () => { + const result = getSmallMemoryFile(50, mockDb) + expect.soft(result).toEqual(['v4/_0', '_0', 500]) + }) + + it('should return correct values for the first line of the second file', () => { + const result = getSmallMemoryFile(100, mockDb) + expect.soft(result).toEqual(['v4/_0', '_1', 0]) + }) + + it('should return correct values for a line in a different folder', () => { + const result = getSmallMemoryFile(1500, mockDb) + expect.soft(result).toEqual(['v4/_1', '_5', 0]) + }) + + it('should handle temporary files correctly', () => { + const result = getSmallMemoryFile(2000, mockDb, true) + expect.soft(result).toEqual(['v4-tmp/_2', '_0', 0]) + }) + + it('should handle large line numbers correctly', () => { + const result = getSmallMemoryFile(10000, mockDb) + expect.soft(result).toEqual(['v4/_a', '_0', 0]) + }) + + it('should use base 36 for folder and file numbers', () => { + const result = getSmallMemoryFile(50000, mockDb) + expect.soft(result).toEqual(['v4/1e', '_0', 0]) + }) + + it('should calculate correct line offset', () => { + const result = getSmallMemoryFile(12345, mockDb) + expect.soft(result).toEqual(['v4/_c', '_3', 450]) + }) + + it('should work with different database configurations', () => { + const customDb: LocalDatabase = { + version: 6, + folderLineMax: 5000, + fileLineMax: 500, + recordSize: 20, + } + const result = getSmallMemoryFile(7777, customDb) + expect.soft(result).toEqual(['v6/_1', '_5', 5540]) + }) +}) diff --git a/packages/util/src/functions/getSmallMemoryFile.ts b/packages/util/src/functions/getSmallMemoryFile.ts new file mode 100644 index 0000000..8aa0ee2 --- /dev/null +++ b/packages/util/src/functions/getSmallMemoryFile.ts @@ -0,0 +1,18 @@ +import type { LocalDatabase } from './getSettings.js' +import path from 'node:path' +import { getUnderscoreFill } from './getUnderscoreFill.js' + +/** + * Get the small memory file information for the given line number and database settings. + * @param line - The line number in the database. + * @param db - The database settings. + * @param isTmp - Whether the file is temporary. + * @returns An array containing the directory, file number, and line offset. + */ +export function getSmallMemoryFile(line: number, db: LocalDatabase, isTmp = false): [string, string, number] { + const dbNumber = line / db.folderLineMax | 0 + const fileNumber = (line - dbNumber * db.folderLineMax) / db.fileLineMax | 0 + const lineOffset = line - dbNumber * db.folderLineMax - fileNumber * db.fileLineMax + const dir = path.join(`v${db.version}${isTmp ? '-tmp' : ''}`, getUnderscoreFill(dbNumber.toString(36), 2)) + return [dir, getUnderscoreFill(fileNumber.toString(36), 2), lineOffset * db.recordSize] +} diff --git a/packages/util/src/functions/getUnderscoreFill.test.ts b/packages/util/src/functions/getUnderscoreFill.test.ts new file mode 100644 index 0000000..530567a --- /dev/null +++ b/packages/util/src/functions/getUnderscoreFill.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { getUnderscoreFill } from './getUnderscoreFill' + +describe('getUnderscoreFill', () => { + it('should pad the string with underscores to the left', () => { + expect.soft(getUnderscoreFill('test', 8)).toBe('____test') + }) + + it('should return the original string if it is already longer than the specified length', () => { + expect.soft(getUnderscoreFill('longstring', 5)).toBe('longstring') + }) + + it('should return the original string if it is equal to the specified length', () => { + expect.soft(getUnderscoreFill('equal', 5)).toBe('equal') + }) + + it('should handle empty string input', () => { + expect.soft(getUnderscoreFill('', 3)).toBe('___') + }) + + it('should handle length of 0', () => { + expect.soft(getUnderscoreFill('test', 0)).toBe('test') + }) + + it('should handle very long padding', () => { + expect.soft(getUnderscoreFill('x', 100)).toBe(`${'_'.repeat(99)}x`) + }) +}) diff --git a/packages/util/src/functions/getUnderscoreFill.ts b/packages/util/src/functions/getUnderscoreFill.ts new file mode 100644 index 0000000..ff5ef63 --- /dev/null +++ b/packages/util/src/functions/getUnderscoreFill.ts @@ -0,0 +1,15 @@ +/** + * Pads a string with underscores to the left until it reaches the specified length. + * If the input string is already longer than or equal to the specified length, it's returned unchanged. + * + * @param {string} string - The input string to pad. + * @param {number} length - The desired total length of the resulting string. + * @returns {string} The padded string or the original string if it's already long enough. + */ +export function getUnderscoreFill(string: string, length: number): string { + if (string.length >= length) + return string + + //* Calculate the number of underscores needed and add them to the left of the string + return '_'.repeat(length - string.length) + string +}; diff --git a/packages/util/src/functions/log.test.ts b/packages/util/src/functions/log.test.ts new file mode 100644 index 0000000..b0042f9 --- /dev/null +++ b/packages/util/src/functions/log.test.ts @@ -0,0 +1,19 @@ +import { describe, it, vi } from 'vitest' +import { getSettings } from './getSettings' +import { log } from './log' + +describe('log', () => { + it('should not log when silent is true', ({ expect }) => { + const spy = vi.spyOn(console, 'info') + getSettings({ silent: true }) + log('info', 'test') + expect.soft(spy).not.toHaveBeenCalled() + }) + + it('should log when silent is false', ({ expect }) => { + const spy = vi.spyOn(console, 'info') + getSettings({ silent: false }) + log('info', 'test') + expect.soft(spy).toHaveBeenCalled() + }) +}) diff --git a/packages/util/src/functions/log.ts b/packages/util/src/functions/log.ts new file mode 100644 index 0000000..d5b5584 --- /dev/null +++ b/packages/util/src/functions/log.ts @@ -0,0 +1,9 @@ +import { SAVED_SETTINGS } from '../constants.js' + +export function log(type: 'info' | 'warn' | 'error', ...args: any[]) { + if (SAVED_SETTINGS.silent) + return + + // eslint-disable-next-line no-console + console[type](...args) +} diff --git a/packages/util/src/functions/makeDatabase.test.ts b/packages/util/src/functions/makeDatabase.test.ts new file mode 100644 index 0000000..f79e585 --- /dev/null +++ b/packages/util/src/functions/makeDatabase.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest' +import { makeDatabase } from './makeDatabase' + +describe('makeDatabase', () => { + it('should create a new entry in an empty database', () => { + const database: Record = {} + const result = makeDatabase('test', database) + expect.soft(result).toBe(0) + expect.soft(database).toEqual({ test: 0 }) + }) + + it('should create a new entry in a non-empty database', () => { + const database: Record = { existing: 0 } + const result = makeDatabase('test', database) + expect.soft(result).toBe(1) + expect.soft(database).toEqual({ existing: 0, test: 1 }) + }) + + it('should retrieve an existing entry', () => { + const database: Record = { existing: 0 } + const result = makeDatabase('existing', database) + expect.soft(result).toBe(0) + expect.soft(database).toEqual({ existing: 0 }) + }) + + it('should handle multiple entries correctly', () => { + const database: Record = {} + expect.soft(makeDatabase('first', database)).toBe(0) + expect.soft(makeDatabase('second', database)).toBe(1) + expect.soft(makeDatabase('third', database)).toBe(2) + expect.soft(makeDatabase('first', database)).toBe(0) + expect.soft(database).toEqual({ first: 0, second: 1, third: 2 }) + }) + + it('should work with non-string keys', () => { + const database: Record = {} + expect.soft(makeDatabase('123', database)).toBe(0) + expect.soft(makeDatabase('true', database)).toBe(1) + expect.soft(database).toEqual({ 123: 0, true: 1 }) + }) +}) diff --git a/packages/util/src/functions/makeDatabase.ts b/packages/util/src/functions/makeDatabase.ts new file mode 100644 index 0000000..7a1f0fe --- /dev/null +++ b/packages/util/src/functions/makeDatabase.ts @@ -0,0 +1,12 @@ +/** + * Creates or retrieves an entry in a database. + * @param name - The name to store or retrieve + * @param database - The database to use + * @returns The index of the name in the database + */ +export function makeDatabase(name: string, database: Record): number { + if (database[name] === undefined) { + database[name] = Object.keys(database).length + } + return database[name] +} diff --git a/packages/util/src/functions/ntoa4.test.ts b/packages/util/src/functions/ntoa4.test.ts new file mode 100644 index 0000000..478c31a --- /dev/null +++ b/packages/util/src/functions/ntoa4.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { ntoa4 } from './ntoa4' + +describe('ntoa4', () => { + it('should convert 32-bit integers to valid IPv4 addresses', () => { + expect.soft(ntoa4(3232235777)).toBe('192.168.1.1') + expect.soft(ntoa4(167772161)).toBe('10.0.0.1') + expect.soft(ntoa4(2886729729)).toBe('172.16.0.1') + expect.soft(ntoa4(4294967295)).toBe('255.255.255.255') + expect.soft(ntoa4(0)).toBe('0.0.0.0') + }) + + it('should handle edge cases', () => { + expect.soft(ntoa4(2130706433)).toBe('127.0.0.1') //* Localhost + expect.soft(ntoa4(4294967040)).toBe('255.255.255.0') //* Subnet mask + expect.soft(ntoa4(16909060)).toBe('1.2.3.4') + }) + + it('should handle all zeros and all ones', () => { + expect.soft(ntoa4(0)).toBe('0.0.0.0') + expect.soft(ntoa4(4294967295)).toBe('255.255.255.255') + }) + + it('should throw an error for invalid 32-bit integers', () => { + expect.soft(() => ntoa4(-1)).toThrow('Invalid 32-bit unsigned integer') + expect.soft(() => ntoa4(4294967296)).toThrow('Invalid 32-bit unsigned integer') + }) +}) diff --git a/packages/util/src/functions/ntoa4.ts b/packages/util/src/functions/ntoa4.ts new file mode 100644 index 0000000..41bede6 --- /dev/null +++ b/packages/util/src/functions/ntoa4.ts @@ -0,0 +1,24 @@ +/** + * Convert a 32-bit integer to an IPv4 string + * @param num - The 32-bit integer to convert + * @returns The IPv4 string + * @example + * ```ts + * ntoa4(3232235777) // '192.168.1.1' + * ``` + * @throws Will throw an error if the input is not a valid 32-bit unsigned integer + */ +export function ntoa4(num: number): string { + if (!Number.isInteger(num) || num < 0 || num > 0xFFFFFFFF) { + throw new Error('Invalid 32-bit unsigned integer') + } + + //* Extract each octet using bitwise operations + const octet1 = (num >>> 24) & 255 + const octet2 = (num >>> 16) & 255 + const octet3 = (num >>> 8) & 255 + const octet4 = num & 255 + + //* Combine the octets into an IPv4 string + return `${octet1}.${octet2}.${octet3}.${octet4}` +} diff --git a/packages/util/src/functions/number37ToString.test.ts b/packages/util/src/functions/number37ToString.test.ts new file mode 100644 index 0000000..5fd3e4b --- /dev/null +++ b/packages/util/src/functions/number37ToString.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest' +import { number37ToString } from './number37ToString' + +describe('number37ToString', () => { + it('should convert 0 to empty string', () => { + expect.soft(number37ToString(0)).toBe('') + }) + + it('should convert single-digit numbers correctly', () => { + expect.soft(number37ToString(11)).toBe('A') + expect.soft(number37ToString(36)).toBe('Z') + expect.soft(number37ToString(1)).toBe('0') + expect.soft(number37ToString(10)).toBe('9') + }) + + it('should convert multi-digit numbers correctly', () => { + expect.soft(number37ToString(15516)).toBe('ABC') + expect.soft(number37ToString(2853)).toBe('123') + expect.soft(number37ToString(47877)).toBe('XYZ') + }) + + it('should handle lowercase letters in output', () => { + expect.soft(number37ToString(15516)).toBe('ABC') + expect.soft(number37ToString(47877)).toBe('XYZ') + }) + + it('should handle mixed alphanumeric output', () => { + expect.soft(number37ToString(767144277)).toBe('A1B2C3') + expect.soft(number37ToString(159472233)).toBe('1A2B3C') + }) + + it('should produce different strings for different numbers', () => { + const result1 = number37ToString(12345) + const result2 = number37ToString(67890) + expect.soft(result1).not.toBe(result2) + }) + + it('should handle very large numbers', () => { + const largeNumber = 37 ** 10 - 1 //* Largest 10-digit base-37 number + expect.soft(() => number37ToString(largeNumber)).not.toThrow() + }) +}) diff --git a/packages/util/src/functions/number37ToString.ts b/packages/util/src/functions/number37ToString.ts new file mode 100644 index 0000000..c2514c5 --- /dev/null +++ b/packages/util/src/functions/number37ToString.ts @@ -0,0 +1,18 @@ +/** + * Converts a number to a base-37 string representation. + * + * @param number - The number to convert. + * @returns The base-37 string representation of the input number. + */ +export function number37ToString(number: number): string { + let string = '' + while (number > 0) { + //* Subtract 1 from the remainder to handle the range 0-36 + //* Convert to base-36 string (0-9, then A-Z) + string = (number % 37 - 1).toString(36) + string + //* Integer division by 37 for the next iteration + number = Math.floor(number / 37) + } + //* Convert the result to uppercase + return string.toUpperCase() +} diff --git a/packages/util/src/functions/numberToCountryCode.test.ts b/packages/util/src/functions/numberToCountryCode.test.ts new file mode 100644 index 0000000..d04405f --- /dev/null +++ b/packages/util/src/functions/numberToCountryCode.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest' +import { numberToCountryCode } from './numberToCountryCode' + +describe('numberToCountryCode', () => { + it('should convert 0 to "AA"', () => { + expect.soft(numberToCountryCode(0)).toBe('AA') + }) + + it('should convert 675 to "ZZ"', () => { + expect.soft(numberToCountryCode(675)).toBe('ZZ') + }) + + it('should convert 52 to "CA"', () => { + expect.soft(numberToCountryCode(52)).toBe('CA') + }) + + it('should convert 538 to "US"', () => { + expect.soft(numberToCountryCode(538)).toBe('US') + }) + + it('should convert 25 to "AZ"', () => { + expect.soft(numberToCountryCode(25)).toBe('AZ') + }) + + it('should convert 26 to "BA"', () => { + expect.soft(numberToCountryCode(26)).toBe('BA') + }) + + it('should throw an error for negative input', () => { + expect.soft(() => numberToCountryCode(-1)).toThrow('Input number must be between 0 and 675') + }) + + it('should throw an error for input greater than 675', () => { + expect.soft(() => numberToCountryCode(676)).toThrow('Input number must be between 0 and 675') + }) + + it('should throw an error for non-integer input', () => { + expect.soft(() => numberToCountryCode(3.14)).toThrow('Input number must be between 0 and 675') + }) +}) diff --git a/packages/util/src/functions/numberToCountryCode.ts b/packages/util/src/functions/numberToCountryCode.ts new file mode 100644 index 0000000..5fb4d1f --- /dev/null +++ b/packages/util/src/functions/numberToCountryCode.ts @@ -0,0 +1,15 @@ +/** + * Converts a number to a two-letter country code. + * @param num - A number representing the country code (0-675). + * @returns A two-letter country code (e.g., 'AA', 'AB', ..., 'ZZ'). + * @throws {Error} If the input number is out of range. + */ +export function numberToCountryCode(num: number): string { + if (num < 0 || num > 675 || !Number.isInteger(num)) { + throw new Error('Input number must be between 0 and 675') + } + + //* Convert number to two-letter code using ASCII values + //* First letter: A-Z (65-90), Second letter: A-Z (65-90) + return String.fromCharCode((num / 26 | 0) + 65, num % 26 + 65) +} diff --git a/packages/util/src/functions/numberToDir.test.ts b/packages/util/src/functions/numberToDir.test.ts new file mode 100644 index 0000000..12560b7 --- /dev/null +++ b/packages/util/src/functions/numberToDir.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest' +import { numberToDir } from './numberToDir' + +describe('numberToDir', () => { + it('converts single-digit numbers to padded strings', () => { + expect.soft(numberToDir(0)).toBe('_0') + expect.soft(numberToDir(9)).toBe('_9') + }) + + it('converts double-digit numbers to base 36 strings', () => { + expect.soft(numberToDir(10)).toBe('_a') + expect.soft(numberToDir(35)).toBe('_z') + }) + + it('converts numbers greater than 35 to base 36 strings', () => { + expect.soft(numberToDir(36)).toBe('10') + expect.soft(numberToDir(61)).toBe('1p') + expect.soft(numberToDir(1295)).toBe('zz') + }) + + it('handles large numbers', () => { + expect.soft(numberToDir(1296)).toBe('100') + expect.soft(numberToDir(46655)).toBe('zzz') + expect.soft(numberToDir(46656)).toBe('1000') + }) + + it('handles the maximum safe integer', () => { + expect.soft(numberToDir(Number.MAX_SAFE_INTEGER)).toBe('2gosa7pa2gv') + }) + + it('throws an error for negative numbers', () => { + expect.soft(() => numberToDir(-1)).toThrow() + }) + + it('throws an error for non-integer numbers', () => { + expect.soft(() => numberToDir(3.14)).toThrow() + }) +}) diff --git a/packages/util/src/functions/numberToDir.ts b/packages/util/src/functions/numberToDir.ts new file mode 100644 index 0000000..99623e4 --- /dev/null +++ b/packages/util/src/functions/numberToDir.ts @@ -0,0 +1,16 @@ +import { getUnderscoreFill } from './getUnderscoreFill.js' + +/** + * Converts a number to a two-character directory name. + * + * @param {number} number - The number to convert. + * @returns {string} A two-character string representation of the number. + * @throws {Error} If the input is negative or not an integer. + */ +export function numberToDir(number: number): string { + if (number < 0 || !Number.isInteger(number)) { + throw new Error('Input must be a non-negative integer') + } + //* Convert number to base 36 and pad with underscores to ensure at least 2 characters + return getUnderscoreFill(number.toString(36), 2) +} diff --git a/packages/util/src/functions/parseIp.test.ts b/packages/util/src/functions/parseIp.test.ts new file mode 100644 index 0000000..8141824 --- /dev/null +++ b/packages/util/src/functions/parseIp.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest' +import { parseIp } from './parseIp' + +describe('parseIp', () => { + it('should correctly parse IPv4 addresses', () => { + const result = parseIp('192.168.0.1') + expect.soft(result).toEqual({ version: 4, ip: 3232235521 }) + }) + + it('should correctly parse IPv6 addresses', () => { + const result = parseIp('2001:db8::1') + expect.soft(result).toEqual({ version: 6, ip: 2306139568115548160n }) + }) + + it('should correctly parse IPv4-mapped IPv6 addresses', () => { + const result = parseIp('::ffff:192.0.2.1') + expect.soft(result).toEqual({ version: 4, ip: 3221225985 }) + }) + + it('should throw an error for invalid IP addresses', () => { + expect.soft(() => parseIp('invalid')).toThrow() + }) + + it('should handle edge cases for IPv4', () => { + expect.soft(parseIp('0.0.0.0')).toEqual({ version: 4, ip: 0 }) + expect.soft(parseIp('255.255.255.255')).toEqual({ version: 4, ip: 4294967295 }) + }) + + it('should handle edge cases for IPv6', () => { + expect.soft(parseIp('::')).toEqual({ version: 6, ip: 0n }) + expect.soft(parseIp('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')).toEqual({ + version: 6, + ip: 18446744073709551615n, + }) + }) + + it('should handle compressed IPv6 addresses', () => { + expect.soft(parseIp('2001:db8::')).toEqual({ + version: 6, + ip: 2306139568115548160n, + }) + expect.soft(parseIp('::1')).toEqual({ version: 6, ip: 0n }) + }) +}) diff --git a/packages/util/src/functions/parseIp.ts b/packages/util/src/functions/parseIp.ts new file mode 100644 index 0000000..0e242ce --- /dev/null +++ b/packages/util/src/functions/parseIp.ts @@ -0,0 +1,26 @@ +import { aton4 } from './aton4.js' +import { aton6 } from './aton6.js' + +/** + * Parses an IP address string and returns its details. + * @param {string} ip - The IP address to parse. + * @returns {{ version: 4 | 6, address: string, ip: number | bigint }} An object containing the IP version, original address, and numeric representation. + * + * @throws Will throw an error if the input is not a valid IP address + */ +export function parseIp(ip: string): { + version: 4 | 6 + ip: number | bigint +} { + if (ip.includes(':')) { + if (ip.includes('.')) { + //* Handle IPv4-mapped IPv6 addresses (e.g., ::ffff:192.0.2.1) + ip = ip.split(':').pop()! + return { version: 4, ip: aton4(ip) } + } + //* Handle regular IPv6 addresses + return { version: 6, ip: aton6(ip) } + } + //* Handle IPv4 addresses + return { version: 4, ip: aton4(ip) } +} diff --git a/packages/util/src/functions/sleep.test.ts b/packages/util/src/functions/sleep.test.ts new file mode 100644 index 0000000..ce8c7f9 --- /dev/null +++ b/packages/util/src/functions/sleep.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { sleep } from './sleep' + +describe('sleep', () => { + it('should pause execution for the specified time', async () => { + const startTime = Date.now() + const sleepTime = 100 + + vi.useFakeTimers() + + const sleepPromise = sleep(sleepTime) + vi.advanceTimersByTime(sleepTime) + await sleepPromise + + const endTime = Date.now() + const elapsedTime = endTime - startTime + + expect.soft(elapsedTime).toBeGreaterThanOrEqual(sleepTime) + }) + + it('should resolve after the specified time', async () => { + const sleepTime = 50 + + vi.useFakeTimers() + + const sleepPromise = sleep(sleepTime) + + expect.soft(vi.getTimerCount()).toBe(1) + + vi.advanceTimersByTime(sleepTime - 1) + await Promise.resolve() // Allow any pending microtasks to run + + expect.soft(vi.getTimerCount()).toBe(1) + + vi.advanceTimersByTime(1) + await sleepPromise + + expect.soft(vi.getTimerCount()).toBe(0) + }) + + afterEach(() => { + vi.useRealTimers() + }) +}) diff --git a/packages/util/src/functions/sleep.ts b/packages/util/src/functions/sleep.ts new file mode 100644 index 0000000..9b43c66 --- /dev/null +++ b/packages/util/src/functions/sleep.ts @@ -0,0 +1,6 @@ +/** + * Pauses execution for a specified number of milliseconds. + * @param {number} ms - The number of milliseconds to pause. + * @returns {Promise} A promise that resolves after the specified time. + */ +export const sleep = (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/packages/util/src/functions/stringToNumber37.test.ts b/packages/util/src/functions/stringToNumber37.test.ts new file mode 100644 index 0000000..e058b7d --- /dev/null +++ b/packages/util/src/functions/stringToNumber37.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest' +import { stringToNumber37 } from './stringToNumber37' + +describe('stringToNumber37', () => { + it('should convert empty string to 0', () => { + expect.soft(stringToNumber37('')).toBe(0) + }) + + it('should convert single character strings correctly', () => { + expect.soft(stringToNumber37('a')).toBe(11) + expect.soft(stringToNumber37('z')).toBe(36) + expect.soft(stringToNumber37('0')).toBe(1) + expect.soft(stringToNumber37('9')).toBe(10) + }) + + it('should convert multi-character strings correctly', () => { + expect.soft(stringToNumber37('abc')).toBe(15516) + expect.soft(stringToNumber37('123')).toBe(2853) + expect.soft(stringToNumber37('xyz')).toBe(47877) + }) + + it('should handle uppercase and lowercase letters', () => { + expect.soft(stringToNumber37('ABC')).toBe(15516) + expect.soft(stringToNumber37('XYZ')).toBe(47877) + }) + + it('should handle mixed alphanumeric strings', () => { + expect.soft(stringToNumber37('a1b2c3')).toBe(767144277) + expect.soft(stringToNumber37('1a2b3c')).toBe(159472233) + }) + + it('should produce different results for different strings', () => { + const result1 = stringToNumber37('hello') + const result2 = stringToNumber37('world') + expect.soft(result1).not.toBe(result2) + }) + + it('should handle very long strings', () => { + const longString = 'a'.repeat(1000) + expect.soft(() => stringToNumber37(longString)).not.toThrow() + }) +}) diff --git a/packages/util/src/functions/stringToNumber37.ts b/packages/util/src/functions/stringToNumber37.ts new file mode 100644 index 0000000..fd0e147 --- /dev/null +++ b/packages/util/src/functions/stringToNumber37.ts @@ -0,0 +1,15 @@ +/** + * Converts a string to a number using a base-37 encoding scheme. + * + * @param string - The input string to be converted + * @returns The resulting number after conversion + */ +export function stringToNumber37(string: string): number { + let number = 0 + for (const char of string) { + //* Convert each character to a number (0-35) and add 1 to avoid collisions with 0 + //* Then multiply the current number by 37 and add the new value + number = number * 37 + Number.parseInt(char, 36) + 1 + } + return number +} diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts new file mode 100644 index 0000000..0bd46fc --- /dev/null +++ b/packages/util/src/index.ts @@ -0,0 +1,10 @@ +export { SAVED_SETTINGS } from './constants.js' +export * from './functions/aton4.js' +export * from './functions/aton6.js' +export * from './functions/binarySearch.js' +export * from './functions/getSettings.js' +export * from './functions/getSmallMemoryFile.js' +export * from './functions/log.js' +export * from './functions/ntoa4.js' +export * from './functions/number37ToString.js' +export * from './functions/parseIp.js' diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json new file mode 100644 index 0000000..749bdab --- /dev/null +++ b/packages/util/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "types": ["./environment.d.ts", "@types/node"], + "outDir": "dist" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..99c3367 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5703 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@antfu/eslint-config': + specifier: ^3.7.3 + version: 3.7.3(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(@unocss/eslint-plugin@0.63.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(@vue/compiler-sfc@3.5.10)(eslint-plugin-format@0.1.2(eslint@9.11.1(jiti@2.0.0)))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1)) + '@rollup/plugin-replace': + specifier: ^6.0.1 + version: 6.0.1(rollup@4.22.5) + '@total-typescript/tsconfig': + specifier: ^1.0.4 + version: 1.0.4 + '@types/node': + specifier: ^22.7.4 + version: 22.7.4 + '@unocss/eslint-plugin': + specifier: ^0.63.0 + version: 0.63.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@vitest/coverage-v8': + specifier: ^2.1.1 + version: 2.1.1(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1)) + '@vitest/ui': + specifier: ^2.1.1 + version: 2.1.1(vitest@2.1.1) + bumpp: + specifier: ^9.8.1 + version: 9.8.1(magicast@0.3.5) + concurrently: + specifier: ^9.0.1 + version: 9.0.1 + eslint: + specifier: ^9.11.1 + version: 9.11.1(jiti@2.0.0) + eslint-plugin-format: + specifier: ^0.1.2 + version: 0.1.2(eslint@9.11.1(jiti@2.0.0)) + semver: + specifier: ^7.6.3 + version: 7.6.3 + typescript: + specifier: ^5.6.2 + version: 5.6.2 + vite: + specifier: ^5.4.8 + version: 5.4.8(@types/node@22.7.4) + vite-plugin-checker: + specifier: ^0.8.0 + version: 0.8.0(eslint@9.11.1(jiti@2.0.0))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)) + vite-plugin-dts: + specifier: ^4.2.2 + version: 4.2.2(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)) + vitest: + specifier: ^2.1.1 + version: 2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1) + + packages/country: + devDependencies: + '@iplookup/util': + specifier: workspace:* + version: link:../util + + packages/country-extra: + devDependencies: + '@iplookup/util': + specifier: workspace:* + version: link:../util + countries-list: + specifier: ^3.1.1 + version: 3.1.1 + + packages/geocode: + devDependencies: + '@iplookup/util': + specifier: workspace:* + version: link:../util + + packages/geocode-extra: + devDependencies: + '@iplookup/util': + specifier: workspace:* + version: link:../util + countries-list: + specifier: ^3.1.1 + version: 3.1.1 + + packages/ip-location-api: + dependencies: + '@fast-csv/parse': + specifier: ^5.0.0 + version: 5.0.0 + ip-address: + specifier: ^9.0.5 + version: 9.0.5 + ky: + specifier: ^1.7.2 + version: 1.7.2 + yauzl: + specifier: ^3.1.3 + version: 3.1.3 + devDependencies: + '@iplookup/util': + specifier: workspace:* + version: link:../util + countries-list: + specifier: ^3.1.1 + version: 3.1.1 + + packages/util: + dependencies: + '@fast-csv/parse': + specifier: ^5.0.0 + version: 5.0.0 + ip-address: + specifier: ^9.0.5 + version: 9.0.5 + ky: + specifier: ^1.7.2 + version: 1.7.2 + yauzl: + specifier: ^3.1.3 + version: 3.1.3 + devDependencies: + '@types/yauzl': + specifier: ^2.10.3 + version: 2.10.3 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@antfu/eslint-config@3.7.3': + resolution: {integrity: sha512-vzhKtzQT+f/xBV8T5U8SFy3D7uAqL2CEcjsJVqtA7F8tdKvGuC/96uWeEKMHk5lRfijgj+xRvb+c4qQn60YlIA==} + hasBin: true + peerDependencies: + '@eslint-react/eslint-plugin': ^1.5.8 + '@prettier/plugin-xml': ^3.4.1 + '@unocss/eslint-plugin': '>=0.50.0' + astro-eslint-parser: ^1.0.2 + eslint: ^9.10.0 + eslint-plugin-astro: ^1.2.0 + eslint-plugin-format: '>=0.1.0' + eslint-plugin-react-hooks: ^4.6.0 + eslint-plugin-react-refresh: ^0.4.4 + eslint-plugin-solid: ^0.14.3 + eslint-plugin-svelte: '>=2.35.1' + prettier-plugin-astro: ^0.13.0 + prettier-plugin-slidev: ^1.0.5 + svelte-eslint-parser: '>=0.37.0' + peerDependenciesMeta: + '@eslint-react/eslint-plugin': + optional: true + '@prettier/plugin-xml': + optional: true + '@unocss/eslint-plugin': + optional: true + astro-eslint-parser: + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-slidev: + optional: true + svelte-eslint-parser: + optional: true + + '@antfu/install-pkg@0.4.1': + resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.6': + resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.25.6': + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@clack/core@0.3.4': + resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} + + '@clack/prompts@0.7.0': + resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} + bundledDependencies: + - is-unicode-supported + + '@dprint/formatter@0.3.0': + resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==} + + '@dprint/markdown@0.17.8': + resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==} + + '@dprint/toml@0.6.2': + resolution: {integrity: sha512-Mk5unEANsL/L+WHYU3NpDXt1ARU5bNU5k5OZELxaJodDycKG6RoRnSlZXpW6+7UN2PSnETAFVUdKrh937ZwtHA==} + + '@es-joy/jsdoccomment@0.48.0': + resolution: {integrity: sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==} + engines: {node: '>=16'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.4.0': + resolution: {integrity: sha512-yljsWl5Qv3IkIRmJ38h3NrHXFCm4EUl55M8doGTF6hvzvFF8kRpextgSrg2dwHev9lzBZyafCr9RelGIyQm6fw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.1': + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.1.1': + resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.6.0': + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.11.1': + resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/markdown@6.1.1': + resolution: {integrity: sha512-Z+1js5AeqidwhNBbnIPM6Fn4eY9D5i1NleamS0UBW6BG0J4lpvhIVOKVIi22kmH5gvxDmHUp5MHkkkjda0TehA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fast-csv/parse@5.0.0': + resolution: {integrity: sha512-ecF8tCm3jVxeRjEB6VPzmA+1wGaJ5JgaUX2uesOXdXD6qQp0B3EdshOIed4yT1Xlj/F2f8v4zHSo0Oi31L697g==} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jsdevtools/ez-spawn@3.0.4': + resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} + engines: {node: '>=10'} + + '@microsoft/api-extractor-model@7.29.6': + resolution: {integrity: sha512-gC0KGtrZvxzf/Rt9oMYD2dHvtN/1KPEYsrQPyMKhLHnlVuO/f4AFN3E4toqZzD2pt4LhkKoYmL2H9tX3yCOyRw==} + + '@microsoft/api-extractor@7.47.7': + resolution: {integrity: sha512-fNiD3G55ZJGhPOBPMKD/enozj8yxJSYyVJWxRWdcUtw842rvthDHJgUWq9gXQTensFlMHv2wGuCjjivPv53j0A==} + hasBin: true + + '@microsoft/tsdoc-config@0.17.0': + resolution: {integrity: sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==} + + '@microsoft/tsdoc@0.15.0': + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + + '@rollup/plugin-replace@6.0.1': + resolution: {integrity: sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.1.2': + resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.22.5': + resolution: {integrity: sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.22.5': + resolution: {integrity: sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.22.5': + resolution: {integrity: sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.22.5': + resolution: {integrity: sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.22.5': + resolution: {integrity: sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.22.5': + resolution: {integrity: sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.22.5': + resolution: {integrity: sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.22.5': + resolution: {integrity: sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': + resolution: {integrity: sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.22.5': + resolution: {integrity: sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.22.5': + resolution: {integrity: sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.22.5': + resolution: {integrity: sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.22.5': + resolution: {integrity: sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.22.5': + resolution: {integrity: sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.22.5': + resolution: {integrity: sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.22.5': + resolution: {integrity: sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==} + cpu: [x64] + os: [win32] + + '@rushstack/node-core-library@5.7.0': + resolution: {integrity: sha512-Ff9Cz/YlWu9ce4dmqNBZpA45AEya04XaBFIjV7xTVeEf+y/kTjEasmozqFELXlNG4ROdevss75JrrZ5WgufDkQ==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.5.3': + resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} + + '@rushstack/terminal@0.14.0': + resolution: {integrity: sha512-juTKMAMpTIJKudeFkG5slD8Z/LHwNwGZLtU441l/u82XdTBfsP+LbGKJLCNwP5se+DMCT55GB8x9p6+C4UL7jw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@4.22.6': + resolution: {integrity: sha512-QSRqHT/IfoC5nk9zn6+fgyqOPXHME0BfchII9EUPR19pocsNp/xSbeBCbD3PIR2Lg+Q5qk7OFqk1VhWPMdKHJg==} + + '@stylistic/eslint-plugin@2.8.0': + resolution: {integrity: sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.40.0' + + '@total-typescript/tsconfig@1.0.4': + resolution: {integrity: sha512-fO4ctMPGz1kOFOQ4RCPBRBfMy3gDn+pegUfrGyUFRMv/Rd0ZM3/SHH3hFCYG4u6bPLG8OlmOGcBLDexvyr3A5w==} + + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node@22.7.4': + resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@typescript-eslint/eslint-plugin@8.7.0': + resolution: {integrity: sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.7.0': + resolution: {integrity: sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.7.0': + resolution: {integrity: sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.7.0': + resolution: {integrity: sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.7.0': + resolution: {integrity: sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.7.0': + resolution: {integrity: sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.7.0': + resolution: {integrity: sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.7.0': + resolution: {integrity: sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unocss/config@0.63.0': + resolution: {integrity: sha512-rlhki3GA6pNX05sjDGJO4vTDonOfDngL9CJWEbn9hELgMZQ3+OAO4mePMA6OsYfMxYz5qpfDKwofkuV4CGNDKQ==} + engines: {node: '>=14'} + + '@unocss/core@0.63.0': + resolution: {integrity: sha512-ZGvmQ3CgGlGJy8TDYUCLRzgv+uyUj2nv0a5MsVvLAD1BKHN96YSt/zRdvRprKUkSzP0yLw44k3kv59md8Bn8xw==} + + '@unocss/eslint-plugin@0.63.0': + resolution: {integrity: sha512-f42r0Bk9LAb/BTpsJWNmfobpR3hPPVtytjH4Q673t4Gi8EKwQ3TQ0sVdbNeV+RxwuIckdZ0k1ijmt5FWAJ6oDA==} + engines: {node: '>=14'} + + '@vitest/coverage-v8@2.1.1': + resolution: {integrity: sha512-md/A7A3c42oTT8JUHSqjP5uKTWJejzUW4jalpvs+rZ27gsURsMU8DEb+8Jf8C6Kj2gwfSHJqobDNBuoqlm0cFw==} + peerDependencies: + '@vitest/browser': 2.1.1 + vitest: 2.1.1 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/eslint-plugin@1.1.4': + resolution: {integrity: sha512-kudjgefmJJ7xQ2WfbUU6pZbm7Ou4gLYRaao/8Ynide3G0QhVKHd978sDyWX4KOH0CCMH9cyrGAkFd55eGzJ48Q==} + peerDependencies: + '@typescript-eslint/utils': '>= 8.0' + eslint: '>= 8.57.0' + typescript: '>= 5.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + typescript: + optional: true + vitest: + optional: true + + '@vitest/expect@2.1.1': + resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==} + + '@vitest/mocker@2.1.1': + resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==} + peerDependencies: + '@vitest/spy': 2.1.1 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.1': + resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + + '@vitest/runner@2.1.1': + resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==} + + '@vitest/snapshot@2.1.1': + resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==} + + '@vitest/spy@2.1.1': + resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==} + + '@vitest/ui@2.1.1': + resolution: {integrity: sha512-IIxo2LkQDA+1TZdPLYPclzsXukBWd5dX2CKpGqH8CCt8Wh0ZuDn4+vuQ9qlppEju6/igDGzjWF/zyorfsf+nHg==} + peerDependencies: + vitest: 2.1.1 + + '@vitest/utils@2.1.1': + resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + + '@volar/language-core@2.4.5': + resolution: {integrity: sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==} + + '@volar/source-map@2.4.5': + resolution: {integrity: sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==} + + '@volar/typescript@2.4.5': + resolution: {integrity: sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==} + + '@vue/compiler-core@3.5.10': + resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==} + + '@vue/compiler-dom@3.5.10': + resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==} + + '@vue/compiler-sfc@3.5.10': + resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==} + + '@vue/compiler-ssr@3.5.10': + resolution: {integrity: sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.1.6': + resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/shared@3.5.10': + resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bumpp@9.8.1: + resolution: {integrity: sha512-25W55DZI/rq6FboM0Q5y8eHbUk9eNn9oZ4bg/I5kiWn8/rdZCw6iqML076akQiUOQGhrm6QDvSSn4PgQ48bS4A==} + engines: {node: '>=10'} + hasBin: true + + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + c12@1.11.2: + resolution: {integrity: sha512-oBs8a4uvSDO9dm8b7OCFW7+dgtVrwmwnrVXYzLm43ta7ep2jCn/0MhoUFygIWtxhyy6+/MG7/agvpY0U1Iemew==} + peerDependencies: + magicast: ^0.3.4 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001664: + resolution: {integrity: sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concurrently@9.0.1: + resolution: {integrity: sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==} + engines: {node: '>=18'} + hasBin: true + + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + core-js-compat@3.38.1: + resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} + + countries-list@3.1.1: + resolution: {integrity: sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.29: + resolution: {integrity: sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@0.3.0: + resolution: {integrity: sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@0.4.0: + resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==} + + eslint-formatting-reporter@0.0.0: + resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} + peerDependencies: + eslint: '>=8.40.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-merge-processors@0.1.0: + resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} + peerDependencies: + eslint: '*' + + eslint-parser-plain@0.1.0: + resolution: {integrity: sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==} + + eslint-plugin-antfu@2.7.0: + resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==} + peerDependencies: + eslint: '*' + + eslint-plugin-command@0.2.6: + resolution: {integrity: sha512-T0bHZ1oblW1xUHUVoBKZJR2osSNNGkfZuK4iqboNwuNS/M7tdp3pmURaJtTi/XDzitxaQ02lvOdFH0mUd5QLvQ==} + peerDependencies: + eslint: '*' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + + eslint-plugin-format@0.1.2: + resolution: {integrity: sha512-ZrcO3aiumgJ6ENAv65IWkPjtW77ML/5mp0YrRK0jdvvaZJb+4kKWbaQTMr/XbJo6CtELRmCApAziEKh7L2NbdQ==} + peerDependencies: + eslint: ^8.40.0 || ^9.0.0 + + eslint-plugin-import-x@4.3.0: + resolution: {integrity: sha512-PxGzP7gAjF2DLeRnQtbYkkgZDg1intFyYr/XS1LgTYXUDrSXMHGkXx8++6i2eDv2jMs0jfeO6G6ykyeWxiFX7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + eslint-plugin-jsdoc@50.3.0: + resolution: {integrity: sha512-P7qDB/RckdKETpBM4CtjHRQ5qXByPmFhRi86sN3E+J+tySchq+RSOGGhI2hDIefmmKFuTi/1ACjqsnDJDDDfzg==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.16.0: + resolution: {integrity: sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-n@17.10.3: + resolution: {integrity: sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@3.7.0: + resolution: {integrity: sha512-pemhfcR3LDbYVWeveHok9u048yR7GpsnfyPvn6RsDkp/UV7iqBV0y5K0aGb9ZJMsemOyWok7akxGzPLsz+mHKQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + astro-eslint-parser: ^1.0.2 + eslint: '>=8.0.0' + svelte: '>=3.0.0' + svelte-eslint-parser: ^0.41.1 + vue-eslint-parser: '>=9.0.0' + peerDependenciesMeta: + astro-eslint-parser: + optional: true + svelte: + optional: true + svelte-eslint-parser: + optional: true + vue-eslint-parser: + optional: true + + eslint-plugin-regexp@2.6.0: + resolution: {integrity: sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8.44.0' + + eslint-plugin-toml@0.11.1: + resolution: {integrity: sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-unicorn@55.0.0: + resolution: {integrity: sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==} + engines: {node: '>=18.18'} + peerDependencies: + eslint: '>=8.56.0' + + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-plugin-vue@9.28.0: + resolution: {integrity: sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-yml@1.14.0: + resolution: {integrity: sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-processor-vue-blocks@0.1.2: + resolution: {integrity: sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: ^8.50.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-scope@8.1.0: + resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.1.0: + resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.11.1: + resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.2.0: + resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.3.0: + resolution: {integrity: sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + engines: {node: '>=18'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.9.0: + resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + importx@0.5.0: + resolution: {integrity: sha512-qROz3rSOjQYclmEQAajH9RhBuqpAGHM+5CNd9fk+TsF4JKmQsAI1egafW8XZZv8vARCo4nAmmt5d0eI2B8GUsA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + jiti@2.0.0: + resolution: {integrity: sha512-CJ7e7Abb779OTRv3lomfp7Mns/Sy1+U4pcAx5VbjxCZD5ZM/VJaXPpPjNKjtSvWQy/H86E49REXR34dl1JEz9w==} + hasBin: true + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + ky@1.7.2: + resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} + engines: {node: '>=18'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + markdown-table@3.0.3: + resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + + mdast-util-find-and-replace@3.0.1: + resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + + mdast-util-from-markdown@2.0.1: + resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.0: + resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + + micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.0.8: + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nypm@0.3.12: + resolution: {integrity: sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + ohash@1.1.4: + resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@0.2.0: + resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + + parse-imports@2.2.1: + resolution: {integrity: sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==} + engines: {node: '>= 18'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pkg-types@1.2.0: + resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.22.5: + resolution: {integrity: sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slashes@3.0.12: + resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.20: + resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stable-hash@0.0.4: + resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.6.2: + resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} + engines: {node: '>=12.20'} + + synckit@0.9.1: + resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.6: + resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} + engines: {node: '>=12.0.0'} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toml-eslint-parser@0.10.0: + resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + typescript@5.4.2: + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + unconfig@0.6.0: + resolution: {integrity: sha512-4C67J0nIF2QwSXty2kW3zZx1pMZ3iXabylvJWWgHybWVUcMf9pxwsngoQt0gC+AVstRywFqrRBp3qOXJayhpOw==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vite-node@2.1.1: + resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-plugin-checker@0.8.0: + resolution: {integrity: sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==} + engines: {node: '>=14.16'} + peerDependencies: + '@biomejs/biome': '>=1.7' + eslint: '>=7' + meow: ^9.0.0 + optionator: ^0.9.1 + stylelint: '>=13' + typescript: '*' + vite: '>=2.0.0' + vls: '*' + vti: '*' + vue-tsc: ~2.1.6 + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + + vite-plugin-dts@4.2.2: + resolution: {integrity: sha512-USwTMReZFf8yXV+cKkm4WOMqmFjbReAvkyxON5xzdnZzJEBnFgax6BBDZIGGr9WMJYvhHdpaIHLrOjXDcla4OA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.4.8: + resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.1: + resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.1 + '@vitest/ui': 2.1.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + + vscode-languageclient@7.0.0: + resolution: {integrity: sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==} + engines: {vscode: ^1.52.0} + + vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + + vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml-eslint-parser@1.2.3: + resolution: {integrity: sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==} + engines: {node: ^14.17.0 || >=16.0.0} + + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@3.1.3: + resolution: {integrity: sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@antfu/eslint-config@3.7.3(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(@unocss/eslint-plugin@0.63.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(@vue/compiler-sfc@3.5.10)(eslint-plugin-format@0.1.2(eslint@9.11.1(jiti@2.0.0)))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1))': + dependencies: + '@antfu/install-pkg': 0.4.1 + '@clack/prompts': 0.7.0 + '@eslint-community/eslint-plugin-eslint-comments': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + '@eslint/markdown': 6.1.1 + '@stylistic/eslint-plugin': 2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@vitest/eslint-plugin': 1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1)) + eslint: 9.11.1(jiti@2.0.0) + eslint-config-flat-gitignore: 0.3.0(eslint@9.11.1(jiti@2.0.0)) + eslint-flat-config-utils: 0.4.0 + eslint-merge-processors: 0.1.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-antfu: 2.7.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-command: 0.2.6(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-import-x: 4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + eslint-plugin-jsdoc: 50.3.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-jsonc: 2.16.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-n: 17.10.3(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 3.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vue-eslint-parser@9.4.3(eslint@9.11.1(jiti@2.0.0))) + eslint-plugin-regexp: 2.6.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-toml: 0.11.1(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-unicorn: 55.0.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-vue: 9.28.0(eslint@9.11.1(jiti@2.0.0)) + eslint-plugin-yml: 1.14.0(eslint@9.11.1(jiti@2.0.0)) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.10)(eslint@9.11.1(jiti@2.0.0)) + globals: 15.9.0 + jsonc-eslint-parser: 2.4.0 + local-pkg: 0.5.0 + parse-gitignore: 2.0.0 + picocolors: 1.1.0 + toml-eslint-parser: 0.10.0 + vue-eslint-parser: 9.4.3(eslint@9.11.1(jiti@2.0.0)) + yaml-eslint-parser: 1.2.3 + yargs: 17.7.2 + optionalDependencies: + '@unocss/eslint-plugin': 0.63.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + eslint-plugin-format: 0.1.2(eslint@9.11.1(jiti@2.0.0)) + transitivePeerDependencies: + - '@typescript-eslint/utils' + - '@vue/compiler-sfc' + - supports-color + - svelte + - typescript + - vitest + + '@antfu/install-pkg@0.4.1': + dependencies: + package-manager-detector: 0.2.0 + tinyexec: 0.3.0 + + '@antfu/utils@0.7.10': {} + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.1.0 + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.0 + + '@babel/parser@7.25.6': + dependencies: + '@babel/types': 7.25.6 + + '@babel/types@7.25.6': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@bcoe/v8-coverage@0.2.3': {} + + '@clack/core@0.3.4': + dependencies: + picocolors: 1.1.0 + sisteransi: 1.0.5 + + '@clack/prompts@0.7.0': + dependencies: + '@clack/core': 0.3.4 + picocolors: 1.1.0 + sisteransi: 1.0.5 + + '@dprint/formatter@0.3.0': {} + + '@dprint/markdown@0.17.8': {} + + '@dprint/toml@0.6.2': {} + + '@es-joy/jsdoccomment@0.48.0': + dependencies: + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.4.0(eslint@9.11.1(jiti@2.0.0))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.11.1(jiti@2.0.0) + ignore: 5.3.2 + + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.1(jiti@2.0.0))': + dependencies: + eslint: 9.11.1(jiti@2.0.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.1': {} + + '@eslint/compat@1.1.1': {} + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.6.0': {} + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.2.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.11.1': {} + + '@eslint/markdown@6.1.1': + dependencies: + '@eslint/plugin-kit': 0.2.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-gfm: 3.0.0 + micromark-extension-gfm: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@eslint/object-schema@2.1.4': {} + + '@eslint/plugin-kit@0.2.0': + dependencies: + levn: 0.4.1 + + '@fast-csv/parse@5.0.0': + dependencies: + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.0': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jsdevtools/ez-spawn@3.0.4': + dependencies: + call-me-maybe: 1.0.2 + cross-spawn: 7.0.3 + string-argv: 0.3.2 + type-detect: 4.1.0 + + '@microsoft/api-extractor-model@7.29.6(@types/node@22.7.4)': + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + '@rushstack/node-core-library': 5.7.0(@types/node@22.7.4) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.47.7(@types/node@22.7.4)': + dependencies: + '@microsoft/api-extractor-model': 7.29.6(@types/node@22.7.4) + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + '@rushstack/node-core-library': 5.7.0(@types/node@22.7.4) + '@rushstack/rig-package': 0.5.3 + '@rushstack/terminal': 0.14.0(@types/node@22.7.4) + '@rushstack/ts-command-line': 4.22.6(@types/node@22.7.4) + lodash: 4.17.21 + minimatch: 3.0.8 + resolve: 1.22.8 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.4.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.17.0': + dependencies: + '@microsoft/tsdoc': 0.15.0 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.8 + + '@microsoft/tsdoc@0.15.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@polka/url@1.0.0-next.28': {} + + '@rollup/plugin-replace@6.0.1(rollup@4.22.5)': + dependencies: + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) + magic-string: 0.30.11 + optionalDependencies: + rollup: 4.22.5 + + '@rollup/pluginutils@5.1.2(rollup@4.22.5)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.22.5 + + '@rollup/rollup-android-arm-eabi@4.22.5': + optional: true + + '@rollup/rollup-android-arm64@4.22.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.22.5': + optional: true + + '@rollup/rollup-darwin-x64@4.22.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.22.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.22.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.22.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.22.5': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.22.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.22.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.22.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.22.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.22.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.22.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.22.5': + optional: true + + '@rushstack/node-core-library@5.7.0(@types/node@22.7.4)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 7.0.1 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.8 + semver: 7.5.4 + optionalDependencies: + '@types/node': 22.7.4 + + '@rushstack/rig-package@0.5.3': + dependencies: + resolve: 1.22.8 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.14.0(@types/node@22.7.4)': + dependencies: + '@rushstack/node-core-library': 5.7.0(@types/node@22.7.4) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 22.7.4 + + '@rushstack/ts-command-line@4.22.6(@types/node@22.7.4)': + dependencies: + '@rushstack/terminal': 0.14.0(@types/node@22.7.4) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@stylistic/eslint-plugin@2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + eslint: 9.11.1(jiti@2.0.0) + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 + estraverse: 5.3.0 + picomatch: 4.0.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@total-typescript/tsconfig@1.0.4': {} + + '@types/argparse@1.0.38': {} + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 0.7.34 + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@0.7.34': {} + + '@types/node@22.7.4': + dependencies: + undici-types: 6.19.8 + + '@types/normalize-package-data@2.4.4': {} + + '@types/unist@3.0.3': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 22.7.4 + + '@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@eslint-community/regexpp': 4.11.1 + '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.7.0 + eslint: 9.11.1(jiti@2.0.0) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.7.0 + debug: 4.3.7 + eslint: 9.11.1(jiti@2.0.0) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.7.0': + dependencies: + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 + + '@typescript-eslint/type-utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + debug: 4.3.7 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.7.0': {} + + '@typescript-eslint/typescript-estree@8.7.0(typescript@5.6.2)': + dependencies: + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + eslint: 9.11.1(jiti@2.0.0) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.7.0': + dependencies: + '@typescript-eslint/types': 8.7.0 + eslint-visitor-keys: 3.4.3 + + '@unocss/config@0.63.0': + dependencies: + '@unocss/core': 0.63.0 + unconfig: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@unocss/core@0.63.0': {} + + '@unocss/eslint-plugin@0.63.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)': + dependencies: + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + '@unocss/config': 0.63.0 + '@unocss/core': 0.63.0 + magic-string: 0.30.11 + synckit: 0.9.1 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + + '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.11 + magicast: 0.3.5 + std-env: 3.7.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1) + transitivePeerDependencies: + - supports-color + + '@vitest/eslint-plugin@1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1))': + dependencies: + eslint: 9.11.1(jiti@2.0.0) + optionalDependencies: + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + typescript: 5.6.2 + vitest: 2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1) + + '@vitest/expect@2.1.1': + dependencies: + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.7.4))': + dependencies: + '@vitest/spy': 2.1.1 + estree-walker: 3.0.3 + magic-string: 0.30.11 + optionalDependencies: + vite: 5.4.8(@types/node@22.7.4) + + '@vitest/pretty-format@2.1.1': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.1': + dependencies: + '@vitest/utils': 2.1.1 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.1': + dependencies: + '@vitest/pretty-format': 2.1.1 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.1.1': + dependencies: + tinyspy: 3.0.2 + + '@vitest/ui@2.1.1(vitest@2.1.1)': + dependencies: + '@vitest/utils': 2.1.1 + fflate: 0.8.2 + flatted: 3.3.1 + pathe: 1.1.2 + sirv: 2.0.4 + tinyglobby: 0.2.6 + tinyrainbow: 1.2.0 + vitest: 2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1) + + '@vitest/utils@2.1.1': + dependencies: + '@vitest/pretty-format': 2.1.1 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + + '@volar/language-core@2.4.5': + dependencies: + '@volar/source-map': 2.4.5 + + '@volar/source-map@2.4.5': {} + + '@volar/typescript@2.4.5': + dependencies: + '@volar/language-core': 2.4.5 + path-browserify: 1.0.1 + vscode-uri: 3.0.8 + + '@vue/compiler-core@3.5.10': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.10 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.10': + dependencies: + '@vue/compiler-core': 3.5.10 + '@vue/shared': 3.5.10 + + '@vue/compiler-sfc@3.5.10': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.10 + '@vue/compiler-dom': 3.5.10 + '@vue/compiler-ssr': 3.5.10 + '@vue/shared': 3.5.10 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.10': + dependencies: + '@vue/compiler-dom': 3.5.10 + '@vue/shared': 3.5.10 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.1.6(typescript@5.6.2)': + dependencies: + '@volar/language-core': 2.4.5 + '@vue/compiler-dom': 3.5.10 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.10 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.2 + + '@vue/shared@3.5.10': {} + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + are-docs-informative@0.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.0: + dependencies: + caniuse-lite: 1.0.30001664 + electron-to-chromium: 1.5.29 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.0) + + buffer-crc32@0.2.13: {} + + builtin-modules@3.3.0: {} + + bumpp@9.8.1(magicast@0.3.5): + dependencies: + '@jsdevtools/ez-spawn': 3.0.4 + c12: 1.11.2(magicast@0.3.5) + cac: 6.7.14 + escalade: 3.2.0 + js-yaml: 4.1.0 + jsonc-parser: 3.3.1 + prompts: 2.4.2 + semver: 7.6.3 + tinyglobby: 0.2.10 + transitivePeerDependencies: + - magicast + + bundle-require@5.0.0(esbuild@0.23.1): + dependencies: + esbuild: 0.23.1 + load-tsconfig: 0.2.5 + + c12@1.11.2(magicast@0.3.5): + dependencies: + chokidar: 3.6.0 + confbox: 0.1.7 + defu: 6.1.4 + dotenv: 16.4.5 + giget: 1.2.3 + jiti: 1.21.6 + mlly: 1.7.1 + ohash: 1.1.4 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.2.0 + rc9: 2.1.2 + optionalDependencies: + magicast: 0.3.5 + + cac@6.7.14: {} + + call-me-maybe@1.0.2: {} + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001664: {} + + ccount@2.0.1: {} + + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities@2.0.2: {} + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + ci-info@4.0.0: {} + + citty@0.1.6: + dependencies: + consola: 3.2.3 + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@8.3.0: {} + + comment-parser@1.4.1: {} + + compare-versions@6.1.1: {} + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + concurrently@9.0.1: + dependencies: + chalk: 4.1.2 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + confbox@0.1.7: {} + + consola@3.2.3: {} + + core-js-compat@3.38.1: + dependencies: + browserslist: 4.24.0 + + countries-list@3.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + de-indent@1.0.2: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.4.5: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.29: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.17.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + entities@4.5.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@1.5.4: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.11.1(jiti@2.0.0)): + dependencies: + eslint: 9.11.1(jiti@2.0.0) + semver: 7.6.3 + + eslint-config-flat-gitignore@0.3.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint/compat': 1.1.1 + eslint: 9.11.1(jiti@2.0.0) + find-up-simple: 1.0.0 + + eslint-flat-config-utils@0.4.0: + dependencies: + pathe: 1.1.2 + + eslint-formatting-reporter@0.0.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + eslint: 9.11.1(jiti@2.0.0) + prettier-linter-helpers: 1.0.0 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-merge-processors@0.1.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + eslint: 9.11.1(jiti@2.0.0) + + eslint-parser-plain@0.1.0: {} + + eslint-plugin-antfu@2.7.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@antfu/utils': 0.7.10 + eslint: 9.11.1(jiti@2.0.0) + + eslint-plugin-command@0.2.6(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@es-joy/jsdoccomment': 0.48.0 + eslint: 9.11.1(jiti@2.0.0) + + eslint-plugin-es-x@7.8.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + '@eslint-community/regexpp': 4.11.1 + eslint: 9.11.1(jiti@2.0.0) + eslint-compat-utils: 0.5.1(eslint@9.11.1(jiti@2.0.0)) + + eslint-plugin-format@0.1.2(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@dprint/formatter': 0.3.0 + '@dprint/markdown': 0.17.8 + '@dprint/toml': 0.6.2 + eslint: 9.11.1(jiti@2.0.0) + eslint-formatting-reporter: 0.0.0(eslint@9.11.1(jiti@2.0.0)) + eslint-parser-plain: 0.1.0 + prettier: 3.3.3 + synckit: 0.9.1 + + eslint-plugin-import-x@4.3.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2): + dependencies: + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + debug: 4.3.7 + doctrine: 3.0.0 + eslint: 9.11.1(jiti@2.0.0) + eslint-import-resolver-node: 0.3.9 + get-tsconfig: 4.8.1 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + stable-hash: 0.0.4 + tslib: 2.7.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-jsdoc@50.3.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@es-joy/jsdoccomment': 0.48.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint: 9.11.1(jiti@2.0.0) + espree: 10.2.0 + esquery: 1.6.0 + parse-imports: 2.2.1 + semver: 7.6.3 + spdx-expression-parse: 4.0.0 + synckit: 0.9.1 + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsonc@2.16.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + eslint: 9.11.1(jiti@2.0.0) + eslint-compat-utils: 0.5.1(eslint@9.11.1(jiti@2.0.0)) + espree: 9.6.1 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.0 + natural-compare: 1.4.0 + synckit: 0.6.2 + + eslint-plugin-n@17.10.3(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + enhanced-resolve: 5.17.1 + eslint: 9.11.1(jiti@2.0.0) + eslint-plugin-es-x: 7.8.0(eslint@9.11.1(jiti@2.0.0)) + get-tsconfig: 4.8.1 + globals: 15.9.0 + ignore: 5.3.2 + minimatch: 9.0.5 + semver: 7.6.3 + + eslint-plugin-no-only-tests@3.3.0: {} + + eslint-plugin-perfectionist@3.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)(vue-eslint-parser@9.4.3(eslint@9.11.1(jiti@2.0.0))): + dependencies: + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + eslint: 9.11.1(jiti@2.0.0) + minimatch: 9.0.5 + natural-compare-lite: 1.4.0 + optionalDependencies: + vue-eslint-parser: 9.4.3(eslint@9.11.1(jiti@2.0.0)) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-regexp@2.6.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + '@eslint-community/regexpp': 4.11.1 + comment-parser: 1.4.1 + eslint: 9.11.1(jiti@2.0.0) + jsdoc-type-pratt-parser: 4.1.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-toml@0.11.1(eslint@9.11.1(jiti@2.0.0)): + dependencies: + debug: 4.3.7 + eslint: 9.11.1(jiti@2.0.0) + eslint-compat-utils: 0.5.1(eslint@9.11.1(jiti@2.0.0)) + lodash: 4.17.21 + toml-eslint-parser: 0.10.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-unicorn@55.0.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + ci-info: 4.0.0 + clean-regexp: 1.0.0 + core-js-compat: 3.38.1 + eslint: 9.11.1(jiti@2.0.0) + esquery: 1.6.0 + globals: 15.9.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.0.2 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.6.3 + strip-indent: 3.0.0 + + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0)): + dependencies: + eslint: 9.11.1(jiti@2.0.0) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2))(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2) + + eslint-plugin-vue@9.28.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + eslint: 9.11.1(jiti@2.0.0) + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@9.11.1(jiti@2.0.0)) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-yml@1.14.0(eslint@9.11.1(jiti@2.0.0)): + dependencies: + debug: 4.3.7 + eslint: 9.11.1(jiti@2.0.0) + eslint-compat-utils: 0.5.1(eslint@9.11.1(jiti@2.0.0)) + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 1.2.3 + transitivePeerDependencies: + - supports-color + + eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.10)(eslint@9.11.1(jiti@2.0.0)): + dependencies: + '@vue/compiler-sfc': 3.5.10 + eslint: 9.11.1(jiti@2.0.0) + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.1.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.1.0: {} + + eslint@9.11.1(jiti@2.0.0): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0)) + '@eslint-community/regexpp': 4.11.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.11.1 + '@eslint/plugin-kit': 0.2.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.1.0 + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + 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.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + optionalDependencies: + jiti: 2.0.0 + transitivePeerDependencies: + - supports-color + + espree@10.2.0: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.1.0 + + espree@9.6.1: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + esutils@2.0.3: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.3.0(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fflate@0.8.2: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.0: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-func-name@2.0.2: {} + + get-stream@8.0.1: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@1.2.3: + dependencies: + citty: 0.1.6 + consola: 3.2.3 + defu: 6.1.4 + node-fetch-native: 1.6.4 + nypm: 0.3.12 + ohash: 1.1.4 + pathe: 1.1.2 + tar: 6.2.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@14.0.0: {} + + globals@15.9.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hosted-git-info@2.8.9: {} + + html-escaper@2.0.2: {} + + human-signals@5.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-lazy@4.0.0: {} + + importx@0.5.0: + dependencies: + bundle-require: 5.0.0(esbuild@0.23.1) + debug: 4.3.7 + esbuild: 0.23.1 + jiti: 2.0.0 + pathe: 1.1.2 + tsx: 4.19.1 + transitivePeerDependencies: + - supports-color + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-stream@3.0.0: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + jiti@2.0.0: {} + + jju@1.4.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@1.1.0: {} + + jsdoc-type-pratt-parser@4.1.0: {} + + jsesc@0.5.0: {} + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonc-eslint-parser@2.4.0: + dependencies: + acorn: 8.12.1 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.6.3 + + jsonc-parser@3.3.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kolorist@1.8.0: {} + + ky@1.7.2: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + local-pkg@0.5.0: + dependencies: + mlly: 1.7.1 + pkg-types: 1.2.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.escaperegexp@4.1.2: {} + + lodash.groupby@4.6.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + + lodash.isundefined@3.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + + markdown-table@3.0.3: {} + + mdast-util-find-and-replace@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.1 + micromark-util-character: 2.1.0 + + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + micromark-util-normalize-identifier: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.3 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.1 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-markdown@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-destination@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-label@2.0.0: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-space@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + + micromark-factory-title@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-whitespace@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-character@2.1.0: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-chunked@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-classify-character@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-combine-extensions@2.0.0: + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-decode-numeric-character-reference@2.0.1: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-decode-string@2.0.0: + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + + micromark-util-encode@2.0.0: {} + + micromark-util-html-tag-name@2.0.0: {} + + micromark-util-normalize-identifier@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-resolve-all@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-util-sanitize-uri@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + + micromark-util-subtokenize@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-symbol@2.0.0: {} + + micromark-util-types@2.0.0: {} + + micromark@4.0.0: + dependencies: + '@types/debug': 4.1.12 + debug: 4.3.7 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@4.0.0: {} + + min-indent@1.0.1: {} + + minimatch@3.0.8: + dependencies: + brace-expansion: 1.1.11 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + + mlly@1.7.1: + dependencies: + acorn: 8.12.1 + pathe: 1.1.2 + pkg-types: 1.2.0 + ufo: 1.5.4 + + mrmime@2.0.0: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.7: {} + + natural-compare-lite@1.4.0: {} + + natural-compare@1.4.0: {} + + node-fetch-native@1.6.4: {} + + node-releases@2.0.18: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nypm@0.3.12: + dependencies: + citty: 0.1.6 + consola: 3.2.3 + execa: 8.0.1 + pathe: 1.1.2 + pkg-types: 1.2.0 + ufo: 1.5.4 + + ohash@1.1.4: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@0.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-gitignore@2.0.0: {} + + parse-imports@2.2.1: + dependencies: + es-module-lexer: 1.5.4 + slashes: 3.0.12 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + pend@1.2.0: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.0: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pkg-types@1.2.0: + dependencies: + confbox: 0.1.7 + mlly: 1.7.1 + pathe: 1.1.2 + + pluralize@8.0.0: {} + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.3.3: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.3 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.11.1 + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.11.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.10.0: + dependencies: + jsesc: 0.5.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@4.22.5: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.22.5 + '@rollup/rollup-android-arm64': 4.22.5 + '@rollup/rollup-darwin-arm64': 4.22.5 + '@rollup/rollup-darwin-x64': 4.22.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.5 + '@rollup/rollup-linux-arm-musleabihf': 4.22.5 + '@rollup/rollup-linux-arm64-gnu': 4.22.5 + '@rollup/rollup-linux-arm64-musl': 4.22.5 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.5 + '@rollup/rollup-linux-riscv64-gnu': 4.22.5 + '@rollup/rollup-linux-s390x-gnu': 4.22.5 + '@rollup/rollup-linux-x64-gnu': 4.22.5 + '@rollup/rollup-linux-x64-musl': 4.22.5 + '@rollup/rollup-win32-arm64-msvc': 4.22.5 + '@rollup/rollup-win32-ia32-msvc': 4.22.5 + '@rollup/rollup-win32-x64-msvc': 4.22.5 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.7.0 + + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.11.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + semver@5.7.2: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.1: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 + totalist: 3.0.1 + + sisteransi@1.0.5: {} + + slashes@3.0.12: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.20 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-license-ids@3.0.20: {} + + sprintf-js@1.0.3: {} + + sprintf-js@1.1.3: {} + + stable-hash@0.0.4: {} + + stackback@0.0.2: {} + + std-env@3.7.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.6.2: + dependencies: + tslib: 2.7.0 + + synckit@0.9.1: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.7.0 + + tapable@2.2.1: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + + text-table@0.2.0: {} + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.0: {} + + tinyglobby@0.2.10: + dependencies: + fdir: 6.4.2(picomatch@4.0.2) + picomatch: 4.0.2 + + tinyglobby@0.2.6: + dependencies: + fdir: 6.3.0(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toml-eslint-parser@0.10.0: + dependencies: + eslint-visitor-keys: 3.4.3 + + totalist@3.0.1: {} + + tree-kill@1.2.2: {} + + ts-api-utils@1.3.0(typescript@5.6.2): + dependencies: + typescript: 5.6.2 + + tslib@2.7.0: {} + + tsx@4.19.1: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.1.0: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + typescript@5.4.2: {} + + typescript@5.6.2: {} + + ufo@1.5.4: {} + + unconfig@0.6.0: + dependencies: + '@antfu/utils': 0.7.10 + defu: 6.1.4 + importx: 0.5.0 + transitivePeerDependencies: + - supports-color + + undici-types@6.19.8: {} + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.1(browserslist@4.24.0): + dependencies: + browserslist: 4.24.0 + escalade: 3.2.0 + picocolors: 1.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vite-node@2.1.1(@types/node@22.7.4): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.4.8(@types/node@22.7.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-plugin-checker@0.8.0(eslint@9.11.1(jiti@2.0.0))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)): + dependencies: + '@babel/code-frame': 7.24.7 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + chokidar: 3.6.0 + commander: 8.3.0 + fast-glob: 3.3.2 + fs-extra: 11.2.0 + npm-run-path: 4.0.1 + strip-ansi: 6.0.1 + tiny-invariant: 1.3.3 + vite: 5.4.8(@types/node@22.7.4) + vscode-languageclient: 7.0.0 + vscode-languageserver: 7.0.0 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + optionalDependencies: + eslint: 9.11.1(jiti@2.0.0) + optionator: 0.9.4 + typescript: 5.6.2 + + vite-plugin-dts@4.2.2(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)): + dependencies: + '@microsoft/api-extractor': 7.47.7(@types/node@22.7.4) + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) + '@volar/typescript': 2.4.5 + '@vue/language-core': 2.1.6(typescript@5.6.2) + compare-versions: 6.1.1 + debug: 4.3.7 + kolorist: 1.8.0 + local-pkg: 0.5.0 + magic-string: 0.30.11 + typescript: 5.6.2 + optionalDependencies: + vite: 5.4.8(@types/node@22.7.4) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite@5.4.8(@types/node@22.7.4): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.22.5 + optionalDependencies: + '@types/node': 22.7.4 + fsevents: 2.3.3 + + vitest@2.1.1(@types/node@22.7.4)(@vitest/ui@2.1.1): + dependencies: + '@vitest/expect': 2.1.1 + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.7.4)) + '@vitest/pretty-format': 2.1.1 + '@vitest/runner': 2.1.1 + '@vitest/snapshot': 2.1.1 + '@vitest/spy': 2.1.1 + '@vitest/utils': 2.1.1 + chai: 5.1.1 + debug: 4.3.7 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.8(@types/node@22.7.4) + vite-node: 2.1.1(@types/node@22.7.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.7.4 + '@vitest/ui': 2.1.1(vitest@2.1.1) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vscode-jsonrpc@6.0.0: {} + + vscode-languageclient@7.0.0: + dependencies: + minimatch: 3.1.2 + semver: 7.6.3 + vscode-languageserver-protocol: 3.16.0 + + vscode-languageserver-protocol@3.16.0: + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.16.0: {} + + vscode-languageserver@7.0.0: + dependencies: + vscode-languageserver-protocol: 3.16.0 + + vscode-uri@3.0.8: {} + + vue-eslint-parser@9.4.3(eslint@9.11.1(jiti@2.0.0)): + dependencies: + debug: 4.3.7 + eslint: 9.11.1(jiti@2.0.0) + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yaml-eslint-parser@1.2.3: + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.5.1 + + yaml@2.5.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@3.1.3: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..600b4bb --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/**' diff --git a/rollup.config.mjs b/rollup.config.mjs deleted file mode 100644 index f85accf..0000000 --- a/rollup.config.mjs +++ /dev/null @@ -1,79 +0,0 @@ - -import { nodeResolve } from '@rollup/plugin-node-resolve'; -import terser from '@rollup/plugin-terser'; -import ignore from 'rollup-plugin-ignore'; -import replace from '@rollup/plugin-replace'; -import strip from '@rollup/plugin-strip'; - -const original = { - input: 'src/browser.mjs', - output: { - file: 'browser/country/iplookup.js', - format: 'iife', - name: 'IpLookup', - }, - plugins: [ - { - preventAssignment: true, - "__CDNURL__": false, - "__DATA_TYPE__": "'country'", - }, - nodeResolve({ - browser: true, - }), - ignore(["fs", "path"]), - strip({ - functions: ['console.*'], - }) - ] -} -const settings = [original] - -const DeepCopy = obj => { - if(Array.isArray(obj)) { - return obj.map(DeepCopy) - } else if(obj.constructor === Object) { - const newObj = {} - for(const key in obj) { - newObj[key] = DeepCopy(obj[key]) - } - return newObj - } - return obj -} - -settings[1] = DeepCopy(original) -settings[1].output.file = settings[1].output.file.replace('.js', '.min.js') -settings[1].plugins.push(terser()) - -settings[2] = DeepCopy(original) -settings[2].output.format = 'cjs' -settings[2].output.file = settings[2].output.file.replace('.js', '.cjs') -settings[2].plugins[0]["__CDNURL__"] = '"https://cdn.jsdelivr.net/npm/@iplookup/country/"' - -settings[3] = DeepCopy(original) -settings[3].output.format = 'es' -settings[3].output.file = settings[3].output.file.replace('.js', '.mjs') -settings[3].plugins[0]["__CDNURL__"] = '"https://cdn.jsdelivr.net/npm/@iplookup/country/"' - -settings.push(...settings.map(setting => { - const newSetting = DeepCopy(setting) - newSetting.output.file = newSetting.output.file.replace('country', 'geocode') - newSetting.plugins[0]["__DATA_TYPE__"] = "'geocode'" - return newSetting -})) - -const extraSettings = settings.map(setting => { - const extraSetting = DeepCopy(setting) - extraSetting.input = extraSetting.input.replace('browser', 'browser-extra') - extraSetting.output.file = extraSetting.output.file.replace('country', 'country-extra').replace('geocode', 'geocode-extra') - return extraSetting -}) - -const exportSettings = settings.concat(extraSettings) -exportSettings.forEach(setting => { -// console.log(setting.plugins[0]) - setting.plugins[0] = replace(setting.plugins[0]) -}) - -export default exportSettings; diff --git a/script/NODE_VERSION.md b/script/NODE_VERSION.md deleted file mode 100644 index 392e5e0..0000000 --- a/script/NODE_VERSION.md +++ /dev/null @@ -1,17 +0,0 @@ -fs/promises: Needs Node.js >= v14 - Fix by require('fs').promises -fs.rm: Needs Node.js >= v14.14.0 - USE fs.rmdir as alternative in lower version -BigInt: Needs Node.js >= v10.4.0 - - -top level await >= v14.8 -dynamic import >= 14.x - - -"yauzl": "3" Drop sepport for nodejs < 12 -adm-zip >= 12 -fflate >= 10 -"fast-csv": "5" Drop support for nodejs < 18 - : Cause error in Nodejs 12 - diff --git a/script/_benchmark-all.bat b/script/_benchmark-all.bat deleted file mode 100644 index 2fca88c..0000000 --- a/script/_benchmark-all.bat +++ /dev/null @@ -1,3 +0,0 @@ -cd /d %~dp0 - -node benchmark-this.js ila_fields=all diff --git a/script/_benchmark-city.bat b/script/_benchmark-city.bat deleted file mode 100644 index 89b113c..0000000 --- a/script/_benchmark-city.bat +++ /dev/null @@ -1,16 +0,0 @@ -rem --------------------- -rem CITY -rem --------------------- -cd /d %~dp0 - -rem default configuration -node benchmark-this.js ila_fields=latitude,longitude,area,country,region1,metro,timezone,city - -rem Run the benchmark for geoip-lite -node benchmark-other.cjs geoip-lite - -rem small_memory configuration -node benchmark-this.js ila_small_memory=true ila_fields=latitude,longitude,area,country,region1,metro,timezone,city - -rem Run the benchmark for fast-geoip -node benchmark-other.cjs diff --git a/script/_benchmark-country.bat b/script/_benchmark-country.bat deleted file mode 100644 index 74d6c0e..0000000 --- a/script/_benchmark-country.bat +++ /dev/null @@ -1,10 +0,0 @@ -rem --------------------- -rem COUNTRY -rem --------------------- -cd /d %~dp0 - -rem Run default configuration -node benchmark-this.js ila_data_type=country - -rem Run small_memory configuration -node benchmark-this.js ila_data_type=country ila_small_memory=true diff --git a/script/_benchmark-ll.bat b/script/_benchmark-ll.bat deleted file mode 100644 index df42328..0000000 --- a/script/_benchmark-ll.bat +++ /dev/null @@ -1,7 +0,0 @@ -rem --------------------- -rem -rem --------------------- -cd /d %~dp0 - -rem default configuration -node benchmark-this.js ila_fields=latitude,longitude diff --git a/script/_updatedb-all.bat b/script/_updatedb-all.bat deleted file mode 100644 index 793b23d..0000000 --- a/script/_updatedb-all.bat +++ /dev/null @@ -1,4 +0,0 @@ - -cd /d %~dp0 - -node updatedb.js ila_fields=all diff --git a/script/_updatedb-city.bat b/script/_updatedb-city.bat deleted file mode 100644 index 17e209a..0000000 --- a/script/_updatedb-city.bat +++ /dev/null @@ -1,5 +0,0 @@ - -cd /d %~dp0 - -node updatedb.js ila_fields=latitude,longitude,area,country,region1,metro,timezone,city -node updatedb.js ila_fields=latitude,longitude,area,country,region1,metro,timezone,city ila_small_memory=true diff --git a/script/_updatedb-country.bat b/script/_updatedb-country.bat deleted file mode 100644 index 8d26ac9..0000000 --- a/script/_updatedb-country.bat +++ /dev/null @@ -1,4 +0,0 @@ -cd /d %~dp0 - -node updatedb.js debug -node updatedb.js ila_small_memory=true debug diff --git a/script/_updatedb-geoip-lite.bat b/script/_updatedb-geoip-lite.bat deleted file mode 100644 index 3ae5e0f..0000000 --- a/script/_updatedb-geoip-lite.bat +++ /dev/null @@ -1,4 +0,0 @@ - -cd /d %~dp0 - -node ../node_modules\geoip-lite\scripts\updatedb.js license_key=redist diff --git a/script/_updatedb-ll.bat b/script/_updatedb-ll.bat deleted file mode 100644 index 857fdba..0000000 --- a/script/_updatedb-ll.bat +++ /dev/null @@ -1,4 +0,0 @@ - -cd /d %~dp0 - -node updatedb.js ila_fields=latitude,longitude diff --git a/script/auto-update-country.sh b/script/auto-update-country.sh deleted file mode 100755 index 8d49f9b..0000000 --- a/script/auto-update-country.sh +++ /dev/null @@ -1,18 +0,0 @@ -cd browser/country -RES1=`find data -name '*.idx' -size -1k 2>/dev/null` -if [ -z "$RES1" ]; then - cd ../country-extra - RES2=`find data -name '*.idx' -size -1k 2>/dev/null` - if [ -z "$RES2" ]; then - VERT=`node -p "var j=require('./package.json');j.version=j.version.split('.').slice(0,-1).join('.')+'.'+require('dayjs')().format('YYYYMMDD');require('fs').writeFileSync('./package.json',JSON.stringify(j,null,2));j.version;"` - npm publish - fi - - cd ../country - VERT=`node -p "var j=require('./package.json');j.version=j.version.split('.').slice(0,-1).join('.')+'.'+require('dayjs')().format('YYYYMMDD');require('fs').writeFileSync('./package.json',JSON.stringify(j,null,2));j.version;"` - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -a -m "v${VERT} auto update ip database" - npm publish -fi - diff --git a/script/auto-update-geocode.sh b/script/auto-update-geocode.sh deleted file mode 100755 index 4586b1b..0000000 --- a/script/auto-update-geocode.sh +++ /dev/null @@ -1,23 +0,0 @@ -# get current version from package.json - -cd browser/geocode -RES1=`find data -name '*.idx' -size -1k 2>/dev/null` -if [ -z "$RES1" ]; then - VERT=`node -p "var j=require('./package.json');j.version=j.version.split('.').slice(0,-1).join('.')+'.'+require('dayjs')().format('YYYYMMDD');require('fs').writeFileSync('./package.json',JSON.stringify(j,null,2));j.version;"` - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -a -m "v${VERT} auto update ip database" - npm publish -fi - -cd ../.. - -cd browser/geocode-extra -RES2=`find data -name '*.idx' -size -1k 2>/dev/null` -if [ -z "$RES2" ]; then - VERT=`node -p "var j=require('./package.json');j.version=j.version.split('.').slice(0,-1).join('.')+'.'+require('dayjs')().format('YYYYMMDD');require('fs').writeFileSync('./package.json',JSON.stringify(j,null,2));j.version;"` - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -a -m "v${VERT} auto update ip database" - npm publish -fi diff --git a/script/auto-update.sh b/script/auto-update.sh deleted file mode 100755 index 9b48d67..0000000 --- a/script/auto-update.sh +++ /dev/null @@ -1,11 +0,0 @@ -# get current version from package.json -RES1=`find data -name '*.dat' -size -100k 2>/dev/null` -if [ -z "$RES1" ]; then - if ! git diff --quiet HEAD -- *.dat; then - VERT=`node -p "var j=require('./package.json');j.version=j.version.split('.').slice(0,-1).join('.')+'.'+require('dayjs')().format('YYYYMMDD');require('fs').writeFileSync('./package.json',JSON.stringify(j,null,2));j.version;"` - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -a -m "v${VERT} auto update ip database" - npm publish - fi -fi diff --git a/script/benchmark-other.cjs b/script/benchmark-other.cjs deleted file mode 100644 index b9b8e0f..0000000 --- a/script/benchmark-other.cjs +++ /dev/null @@ -1,57 +0,0 @@ -const fs = require('fs') -const path = require('path') -const process = require('process') - -const isGeoipLite = process.argv.includes('geoip-lite') -const type = isGeoipLite ? 'geoip-lite' : 'doc999tor-fast-geoip' - -console.log('----------------------') -console.log('Using %s', type) -console.log('----------------------') - -const t1 = Date.now() -const geoip = require(type) -const t2 = Date.now() -console.log("Took %d ms to startup", t2 - t1) - -var ipv4 , ipv6 -try { - ipv4 = fs.readFileSync(path.resolve(__dirname, 'ipv4.txt'), 'utf8').split('\n') - ipv6 = fs.readFileSync(path.resolve(__dirname, 'ipv6.txt'), 'utf8').split('\n') -} catch (e) { - console.error("Please run create_ips.js first") - process.exit(1) -} - -async function run () { - for(var ips of [ipv4, ipv6]){ - if(!isGeoipLite) { - ips.length = 10000 - } - const n = ips.length - var r - var ts, te - if(!isGeoipLite){ - ts = Date.now() - for (const ip of ips) { - r = await geoip.lookup(ip) - } - te = Date.now() - } else { - ts = Date.now() - for (const ip of ips) { - r = geoip.lookup(ip) - } - te = Date.now() - } - console.log(ipv4 === ips ? "ipv4" : "ipv6") - console.log("%d ips %d ms (%s ip/s) (%s μs/ip)", n, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) / n * 1000).toFixed(3)) - if(!isGeoipLite) { - console.log('ipv6 - cannot lookup') - break - } - } - console.log('') -} -run() - diff --git a/script/benchmark-this.mjs b/script/benchmark-this.mjs deleted file mode 100644 index b66ec90..0000000 --- a/script/benchmark-this.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import process from 'process' - -import { lookup, reload } from '../src/main.mjs' -import { setting } from '../src/setting.mjs' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const t1 = Date.now() -await reload() -const t2 = Date.now() -console.log("Took %d ms to startup", t2 - t1) - -var ipv4 , ipv6 -try { - ipv4 = fs.readFileSync(path.resolve(__dirname, 'ipv4.txt'), 'utf8').split('\n') - ipv6 = fs.readFileSync(path.resolve(__dirname, 'ipv6.txt'), 'utf8').split('\n') -} catch (e) { - console.error("Please run create_ips.js first") - process.exit(1) -} - -for(var ips of [ipv4, ipv6]){ - if(setting.smallMemory){ - ips.length = 10000 - } - const n = ips.length - var r - var ts, te - if(setting.smallMemory){ - ts = Date.now() - for (const ip of ips) { - r = await lookup(ip) - } - te = Date.now() - } else { - ts = Date.now() - for (const ip of ips) { - r = lookup(ip) - } - te = Date.now() - } - console.log(ipv4 === ips ? "ipv4" : "ipv6") - console.log("%d ips %d ms (%s ip/s) (%s μs/ip)", n, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) / n * 1000).toFixed(3)) -} diff --git a/script/cjs-test.cjs b/script/cjs-test.cjs deleted file mode 100644 index c3dcf2a..0000000 --- a/script/cjs-test.cjs +++ /dev/null @@ -1,35 +0,0 @@ -const fs = require('fs') -const path = require('path') -const process = require('process') - -const t1 = Date.now() -const geoip = require('../cjs/main.cjs') -const t2 = Date.now() -console.log("Took %d ms to startup", t2 - t1) - -var ipv4 , ipv6 -try { - ipv4 = fs.readFileSync(path.resolve(__dirname, 'ipv4.txt'), 'utf8').split('\n') - ipv6 = fs.readFileSync(path.resolve(__dirname, 'ipv6.txt'), 'utf8').split('\n') -} catch (e) { - console.error("Please run create_ips.js first") - process.exit(1) -} - -async function run () { - for(var ips of [ipv4, ipv6]){ - const n = ips.length - var r - var ts, te - ts = Date.now() - for (const ip of ips) { - r = geoip.lookup(ip) - } - te = Date.now() - console.log(ipv4 === ips ? "ipv4" : "ipv6") - console.log("%d ips %d ms (%s ip/s) (%s μs/ip)", n, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) / n * 1000).toFixed(3)) - } - console.log('') -} -run() - diff --git a/script/convert-to-cjs.mjs b/script/convert-to-cjs.mjs deleted file mode 100644 index 335b9fc..0000000 --- a/script/convert-to-cjs.mjs +++ /dev/null @@ -1,37 +0,0 @@ - -import fsSync from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' - -var __filename = fileURLToPath(import.meta.url) -var __dirname = path.dirname(__filename) - -var files = fsSync.readdirSync(path.resolve(__dirname, '..', 'src')) - -for(var file of files){ - if(file.startsWith('browser')) continue; - if(file.endsWith('.mjs')){ - var src = fsSync.readFileSync(path.resolve(__dirname, '..', 'src', file), 'utf8') - var exportList = [] - src = src.replace(/\nconst __filename[^\n]+/, '') - src = src.replace(/\nconst __dirname[^\n]+/, '') - src = src.replace(/\n\s*\/\/[^\n]+/g, '\n') - src = src.replace(/\nawait reload\(\)/g, 'reload(undefined, true)')// remove top level await - src = src.replace(/\nawait\s+[^\n]+/g, '\n')// remove top level await - src = src.replace(/\nexport\s+const\s+(\w+)/g, (m, p1) => { - exportList.push(p1) - return '\nconst ' + p1 - }) - src = src.replace(/\nexport\s+const\s+(\w+)/g, '\nconst $1 = exports.$1') - src = src.replace(/(?:\n|^)import\s+([^\n]+)\s+from([^\n]+)/g, (m, p1, p2) => { - return '\nconst ' + p1 + ' = require(' + p2.replace(".mjs'", ".cjs'").replace(".mjs\"", ".cjs\"").trim() + ')' - }) -// src = src.replace("const fs = require('fs/promises')", "var fs;\ntry{ fs=require('fs/promises') }catch(e){ fs=require('fs').promises }\n") - src = src.replace('export { setting }', '') - - src += '\nmodule.exports={' + exportList.map(v => v+':'+v).join(',') + '}' - - var dstPath = path.resolve(__dirname, '..', 'cjs', file.replace(/\.mjs$/, '.cjs')) - fsSync.writeFileSync(dstPath, src) - } -} diff --git a/script/create_ips.mjs b/script/create_ips.mjs deleted file mode 100644 index c5e3a51..0000000 --- a/script/create_ips.mjs +++ /dev/null @@ -1,39 +0,0 @@ -//--------------------------------------------------------------------------- -// This script is used to create a list of IPs from the GeoLite2 databases. -// Before running this script, download the GeoLite2 databases and put them in the tmp folder. -//--------------------------------------------------------------------------- -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import {parse} from 'fast-csv' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -var counter = {} -var ipv4 = [], ipv6 = [] -fs.createReadStream(path.resolve(__dirname, '../tmp/GeoLite2-City-Blocks-IPv4.csv')) - .pipe(parse()) - .on('data', row => {ipv4.push(row[0].split('/')[0])}) - .on('end', () => write('ipv4', ipv4)) -fs.createReadStream(path.resolve(__dirname, '../tmp/GeoLite2-City-Blocks-IPv6.csv')) - .pipe(parse()) - .on('data', row => {ipv6.push(row[0].split('/')[0])}) - .on('end', () => write('ipv6', ipv6)) -fs.createReadStream(path.resolve(__dirname, '../tmp/GeoLite2-Country-Blocks-IPv4.csv')) - .pipe(parse()) - .on('data', row => {ipv4.push(row[0].split('/')[0])}) - .on('end', () => write('ipv4', ipv4)) -fs.createReadStream(path.resolve(__dirname, '../tmp/GeoLite2-Country-Blocks-IPv6.csv')) - .pipe(parse()) - .on('data', row => {ipv6.push(row[0].split('/')[0])}) - .on('end', () => write('ipv6', ipv6)) - -function write(str, ips){ - if(!counter[str]) counter[str] = 1 - else { - ips.shift() - ips.sort(() => Math.random() - 0.5) - fs.writeFileSync(path.resolve(__dirname, str + '.txt'), ips.join('\n')) - } -} diff --git a/script/isip-benchmark.mjs b/script/isip-benchmark.mjs deleted file mode 100644 index 4b46391..0000000 --- a/script/isip-benchmark.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import fs from 'fs' -import path from 'path' -import { isIP } from 'net' -import { fileURLToPath } from 'url' -import process from 'process' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -var ipv4 , ipv6 -try { - ipv4 = fs.readFileSync(path.resolve(__dirname, 'ipv4.txt'), 'utf8').split('\n') - ipv6 = fs.readFileSync(path.resolve(__dirname, 'ipv6.txt'), 'utf8').split('\n') -} catch (e) { - console.error("Please run create_ips.js first") - process.exit(1) -} - -for(var loop = 0; loop < 2; ++loop){ - if(loop === 0){ - console.log('---------------------') - console.log('string check') - console.log('---------------------') - } else { - console.log('---------------------') - console.log('isIP') - console.log('---------------------') - } - for(var ips of [ipv4, ipv6]){ - const n = ips.length - var r - var ts, te - ts = Date.now() - if(loop === 0){ - for(const ip of ips){ - r = ip.includes(':') ? 6 : 4 - } - } else { - for(const ip of ips){ - r = isIP(ip) - } - } - te = Date.now() - console.log(ipv4 === ips ? "ipv4" : "ipv6") - console.log("%d ips %d ms (%s ip/s) (%s μs/ip)", n, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) / n * 1000).toFixed(3)) - } -} diff --git a/script/memory-usage0.mjs b/script/memory-usage0.mjs deleted file mode 100644 index b17acc0..0000000 --- a/script/memory-usage0.mjs +++ /dev/null @@ -1,15 +0,0 @@ -function checkMemory(){ - if(global.gc){ - global.gc(true) - console.log('Garbage collection done') - } - const used = process.memoryUsage() - const messages = [] - for (let key in used) { - messages.push(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`) - } - console.log(new Date(), messages.join(', ')) -} - -console.log('Memory usage before loading library') -checkMemory() diff --git a/script/memory-usage1.mjs b/script/memory-usage1.mjs deleted file mode 100644 index 537d930..0000000 --- a/script/memory-usage1.mjs +++ /dev/null @@ -1,40 +0,0 @@ -function checkMemory(){ - if(global.gc){ - global.gc(true) - console.log('Garbage collection done') - } - const used = process.memoryUsage() - const messages = [] - for (let key in used) { - messages.push(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`) - } - console.log(new Date(), messages.join(', ')) -} - -console.log('Memory usage before loading library') -checkMemory() - -import { reload } from '../src/main.js' -var timeStart = Date.now() -await reload() -var timeEnd = Date.now() -console.log('Library load time: %d ms', timeEnd - timeStart) - -console.log('Memory usage after loading library') -checkMemory() - -async function test(){ - return new Promise(function(resolve, reject){ - setTimeout(function(){ - checkMemory() - resolve() - }, 5000) - }) -} -async function run(){ - for(var i = 0; i < 10000; ++i){ - await test() - } -} - -run() \ No newline at end of file diff --git a/script/run.mjs b/script/run.mjs deleted file mode 100644 index 44ba9e0..0000000 --- a/script/run.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import {lookup} from '../src/main.mjs' - -var r = await lookup('1.6.72.55') -console.log(r) diff --git a/script/test-geoip-country.cjs b/script/test-geoip-country.cjs deleted file mode 100644 index 2115dec..0000000 --- a/script/test-geoip-country.cjs +++ /dev/null @@ -1,69 +0,0 @@ -const fs = require('fs') -const path = require('path') -const process = require('process') -const utils = require('../cjs/utils.cjs') -const type = '../cjs/main.cjs' //'./geoip-country5' // // - -console.log('----------------------') -console.log('Using %s', type) -console.log('----------------------') - -const t1 = Date.now() -const geoip = require(type) -const t2 = Date.now() -console.log("Took %d ms to startup", t2 - t1) - -var ipv4 , ipv6 -try { - ipv4 = fs.readFileSync(path.resolve(__dirname, 'ipv4.txt'), 'utf8').split('\n') - ipv6 = fs.readFileSync(path.resolve(__dirname, 'ipv6.txt'), 'utf8').split('\n') -} catch (e) { - console.error("Please run create_ips.js first") - process.exit(1) -} - -async function run () { - for(var ips of [ipv4, ipv6]){ - const n = ips.length - const results = [] - var r, preIp - var ts, te - var isV4 = ips === ipv4 - ts = Date.now() - for (const ip of ips) { - r = geoip.lookup(ip) - results.push(r && r.country) - if(isV4){ - if(preIp){ - var ne = utils.aton4(ip) + utils.aton4(preIp) >> 1 - var newIp = utils.ntoa4(ne) - if(newIp !== ip && newIp !== preIp){ - r = geoip.lookup(newIp) - results.push(r && r.country) - } - } - var ne = utils.aton4(ip) + 1 - var newIp = utils.ntoa4(ne) - if(newIp !== ip && newIp !== preIp){ - r = geoip.lookup(newIp) - results.push(r && r.country) - } - var ne = utils.aton4(ip) - 2 - var newIp = utils.ntoa4(ne) - if(newIp !== ip && newIp !== preIp){ - r = geoip.lookup(newIp) - results.push(r && r.country) - } - preIp = ip - } - } - te = Date.now() - console.log(isV4 ? "ipv4" : "ipv6") - console.log("%d ips %d ms (%s ip/s) (%s μs/ip)", n, te - ts, (n * 1000 / (te - ts)).toFixed(3), ((te - ts) / n * 1000).toFixed(3)) - const fileName = path.basename(type) - fs.writeFileSync(path.resolve(__dirname, fileName + (isV4 ? '4' : '6') + '.txt'), results.join('\n')) - } - console.log('') -} -run() - diff --git a/script/updatedb-first.mjs b/script/updatedb-first.mjs deleted file mode 100644 index 71b51c2..0000000 --- a/script/updatedb-first.mjs +++ /dev/null @@ -1,25 +0,0 @@ - -import {update} from '../src/db.mjs' -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -var dataDir = path.join(__dirname, '../data') -var hasDataDir = true -try{ - if(!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir) - } -}catch(e){ - // No write access permission - hasDataDir = false - console.log(e) -} - -var dataFile = path.join(__dirname, '../data/4-1.dat') -if(hasDataDir && !fs.existsSync(dataFile)) { - update() -} diff --git a/script/updatedb.cjs b/script/updatedb.cjs deleted file mode 100644 index 52cec81..0000000 --- a/script/updatedb.cjs +++ /dev/null @@ -1,4 +0,0 @@ - -const {update} = require('../cjs/db.cjs') - -update() diff --git a/script/updatedb.mjs b/script/updatedb.mjs deleted file mode 100644 index 4d460c4..0000000 --- a/script/updatedb.mjs +++ /dev/null @@ -1,4 +0,0 @@ - -import {update} from '../src/db.mjs' - -update() diff --git a/spec/lookup.spec.cjs b/spec/lookup.spec.cjs deleted file mode 100644 index 2214a19..0000000 --- a/spec/lookup.spec.cjs +++ /dev/null @@ -1,26 +0,0 @@ -const { lookup } = require('../cjs/main.cjs') - -describe('CJS lookup', () => { - console.log('CJS lookup') - it('CJS lookup', () => { - var ips = ['1.0.65.0', '2001:4860:b002::68', '2001:df3:e900::'] - var success = 0 - for(var ip of ips){ - var result = lookup(ip) - console.log(ip, result) - if(result) success++ - } - expect(success > (ips.length >> 1)).toBe(true) - }) - it('CJS lookup v4map', () => { - var ips = ['::ffff:2.29.0.82', '::ffff:3.24.1.56', '0:0:0:0:0:ffff:103.175.136.16'] - var success = 0 - for(var ip of ips){ - var result = lookup(ip) - console.log(ip, result) - if(result) success++ - } - expect(success > (ips.length >> 1)).toBe(true) - }) -}) - diff --git a/spec/lookup.spec.mjs b/spec/lookup.spec.mjs deleted file mode 100644 index f317f6a..0000000 --- a/spec/lookup.spec.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import { lookup } from '../src/main.mjs' - -describe('lookup', () => { - console.log('MJS lookup') - it('lookup', () => { - var ips = ['1.0.65.0', '154.45.200.16', '2001:4860:b002::68', '2001:df3:e900::'] - var success = 0 - for(var ip of ips){ - var result = lookup(ip) - console.log(ip, result) - if(result) success++ - } - expect(success > (ips.length >> 1)).toBe(true) - }) - it('lookup v4map', () => { - var ips = ['::ffff:2.29.0.82', '::ffff:3.24.1.56', '0:0:0:0:0:ffff:103.175.136.16'] - var success = 0 - for(var ip of ips){ - var result = lookup(ip) - console.log(ip, result) - if(result) success++ - } - expect(success > (ips.length >> 1)).toBe(true) - }) -}) - - diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json deleted file mode 100644 index ff22420..0000000 --- a/spec/support/jasmine.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "spec_dir": "spec", - "spec_files": [ - "**/*[sS]pec.?(m|c)js" - ], - "helpers": [ - "helpers/**/*.?(m)js" - ], - "env": { - "stopSpecOnExpectationFailure": false, - "random": true - } -} \ No newline at end of file diff --git a/src/browser-extra.mjs b/src/browser-extra.mjs deleted file mode 100644 index 959f766..0000000 --- a/src/browser-extra.mjs +++ /dev/null @@ -1,19 +0,0 @@ - -import ip_lookup from './browser.mjs' - -import { countries } from 'countries-list' - -export default async(ip) => { - const geodata = await ip_lookup(ip) - if(geodata && countries[geodata.country]){ - const h = countries[geodata.country] - geodata.country_name = h.name - geodata.country_native = h.native - geodata.continent = h.continent - geodata.capital = h.capital - geodata.phone = h.phone - geodata.currency = h.currency - geodata.languages = h.languages - } - return geodata -} diff --git a/src/browser.mjs b/src/browser.mjs deleted file mode 100644 index 15563ce..0000000 --- a/src/browser.mjs +++ /dev/null @@ -1,137 +0,0 @@ - -import { aton4, aton6Start, numberToDir, numToCountryCode } from './utils.mjs' - -const TOP_URL = __CDNURL__ || document.currentScript.src.split('/').slice(0, -1).join('/') + '/' -const MAIN_RECORD_SIZE = __DATA_TYPE__ === 'country' ? 2 : 8 -const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)) - -const downloadArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)) - return downloadArrayBuffer(url, retry - 1) - } - return null - } - return res.arrayBuffer() - }) -} - -const downloadVersionArrayBuffer = async(url, retry = 3) => { - return fetch(url, {cache: 'no-cache'}).then(async (res) => { - if(!res.ok) { - if(res.status === 404) return null - if(retry) { - await sleep(100 * (4-retry) * (4-retry)) - return downloadVersionArrayBuffer(url, retry - 1) - } - return null - } - return [res.headers.get('x-jsd-version'), await res.arrayBuffer()] - }) -} - -const downloadIdx = __CDNURL__ ? downloadVersionArrayBuffer : downloadArrayBuffer - -const Idx = {}, Url = {4: TOP_URL, 6: TOP_URL} -const Preload = { - 4: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(__CDNURL__ && buf[0]) { - Url[4] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/') - return Idx[4] = new Uint32Array(buf[1]) - } - return Idx[4] = new Uint32Array(buf) - }), - 6: downloadIdx(TOP_URL + '4.idx').then(buf => { - if(!buf){ -// console.log('ipv6 file cannot download') - return - } - if(__CDNURL__ && buf[0]) { - Url[6] = __CDN_URL__.replace(/\/$/, '@'+buf[0]+'/') - return Idx[6] = new BigUint64Array(buf.slice(1)) - } - return Idx[6] = new BigUint64Array(buf) - }) -} - -export default async (ipString) => { - var ip, version, isv4 = true - if(ipString.includes(':')) { - ip = aton6Start(ipString) - version = ip.constructor === BigInt ? 6 : 4 - if(version === 6) isv4 = false - } else { - ip = aton4(ipString) - version = 4 - } - - const ipIndexes = Idx[version] || (await Preload[version]) - if(!ipIndexes) { -// console.log('Cannot download idx file') - return null - } - if(!(ip >= ipIndexes[0])) return null - var fline = 0, cline = ipIndexes.length-1, line - for(;;){ - line = (fline + cline) >> 1 - if(ip < ipIndexes[line]){ - if(cline - fline < 2) return null - cline = line - 1 - } else { - if(fline === line) { - if(cline > line && ip >= ipIndexes[cline]){ - line = cline - } - break; - } - fline = line - } - } - - const fileName = numberToDir(line) - const dataBuffer = await downloadArrayBuffer(Url[version] + version + '/' + fileName) - if(!dataBuffer) { -// console.log('Cannot download data file') - return null - } - const ipSize = (version - 2) * 2 - const recordSize = MAIN_RECORD_SIZE + ipSize * 2 - const recordCount = dataBuffer.byteLength / recordSize - const startList = isv4 ? new Uint32Array(dataBuffer.slice(0, 4 * recordCount)) : new BigUint64Array(dataBuffer.slice(0, 8 * recordCount)) - fline = 0, cline = recordCount - 1 - for(;;){ - line = fline + cline >> 1 - if(ip < startList[line]){ - if(cline - fline < 2) return null - cline = line - 1 - } else { - if(fline === line) { - if(cline > line && ip >= startList[cline]){ - line = cline - } - break; - } - fline = line - } - } - const endIp = isv4 ? new Uint32Array( dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - : new BigUint64Array(dataBuffer.slice((recordCount+line)*ipSize , (recordCount+line+1)*ipSize))[0] - if(ip >= startList[line] && ip <= endIp){ - if(__DATA_TYPE__ === 'country'){ - const ccCode = new Uint16Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE))[0] - return {country: String.fromCharCode(ccCode&255, ccCode>>8)} - } else { - const arr = new Int32Array(dataBuffer.slice(recordCount*ipSize*2+line*MAIN_RECORD_SIZE, recordCount*ipSize*2+(line+1)*MAIN_RECORD_SIZE)) - const ccCode = numToCountryCode(arr[0] & 1023) - return {latitude: ((arr[0]>>10)) / 10000, longitude: (arr[1]) / 10000, country: ccCode} - } - } - return null -} \ No newline at end of file diff --git a/src/browser_utils.mjs b/src/browser_utils.mjs deleted file mode 100644 index ac92068..0000000 --- a/src/browser_utils.mjs +++ /dev/null @@ -1,25 +0,0 @@ -//---------------------------- -// COUNTRY: IndexLoop = 10 -//---------------------------- -// IPv4: 1172844 >> 2 = 293211 ips -// INDEX_FILE_SIZE = (2^IndexLoop)*4 = 4096 bytes -// COUNTRY_FILE_SIZE = Math.ceil(293211 / IndexSize) * (4 + 4 + 2) = 2870 bytes -// IPv6: 1914064 >> 3 = 146605 ips -// INDEX_FILE_SIZE = (2^IndexLoop)*8 = 8192 bytes -// COUNTRY_FILE_SIZE = Math.ceil(146605 / IndexSize) * (8 + 8 + 2) = 144 * 18 = 2592 bytes - - -//---------------------------- -// LATITUDE + LONGITUDE: IndexLoop = 11 -//---------------------------- -// IPv4: 6474072 >> 2 = 1,618,518 ips -// INDEX_FILE_SIZE = (2^IndexLoop)*4 = 8192 bytes -// COUNTRY_FILE_SIZE = Math.ceil(1618518 / IndexSize) * (4 + 4 + 4 + 4) = 791 * 16 = 12656 bytes -// IPv6: 7621144 >> 3 = 952,643 ips -// INDEX_FILE_SIZE = (2^IndexLoop)*8 = 16384 bytes -// COUNTRY_FILE_SIZE = Math.ceil(952643 / IndexSize) * (8 + 8 + 4 + 4) = 466 * 24 = 11184 bytes - - -export const downloadBuffer = async (url) => { - return fetch(url, {cache: 'no-cache'}).then(res => res.arrayBuffer()) -} diff --git a/src/db.mjs b/src/db.mjs deleted file mode 100644 index efacbad..0000000 --- a/src/db.mjs +++ /dev/null @@ -1,856 +0,0 @@ -import fs from 'fs/promises' -import fsSync from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import { createHash } from 'crypto' -import { pipeline } from 'stream/promises' - -import axios from 'axios' -import { parse } from '@fast-csv/parse' -import { Address4, Address6 } from 'ip-address' -import dayjs from 'dayjs' - -import { setting, consoleLog, consoleWarn } from './setting.mjs' -import { getPostcodeDatabase, strToNum37, aton4, aton6, getSmallMemoryFile, numberToDir, countryCodeToNum } from './utils.mjs' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const rimraf = (dir) => { - if(fs.rm){ - return fs.rm(dir, {recursive: true, force: true, maxRetries: 3}) - } - return fs.rmdir(dir, {recursive: true, maxRetries: 3}) -} - -//--------------------------------------- -// Databse update -//--------------------------------------- -const DownloadServer = 'https://download.maxmind.com/app/geoip_download' -import yauzl from 'yauzl' -export const update = async () => { - var srcList, refreshTmpDir = setting.downloadType !== 'reuse' - if(refreshTmpDir || !fsSync.existsSync(setting.tmpDataDir)){ - // refresh tmp folder - await rimraf(setting.tmpDataDir) - await fs.mkdir(setting.tmpDataDir, {recursive: true}) - } - if (!fsSync.existsSync(setting.fieldDir)){ - await fs.mkdir(setting.fieldDir, {recursive: true}) - } - - consoleLog('Downloading database') - if(setting.browserType === 'geocode'){ - await dbipLocation() - return createBrowserIndex(setting.browserType) - } - - if(setting.ipLocationDb){ - srcList = await ipLocationDb(setting.ipLocationDb.replace(/-country$/, '')) - } else { - srcList = await downloadZip() - } - - if(!srcList){ - return console.log('ERROR TO UPDATE') - } - consoleLog(srcList) - if(srcList === 'NO NEED TO UPDATE') { - return - } - - consoleLog('Creating database for ip-location-api') - await createData(srcList) - consoleLog('Database update completed!!') - - // remove tmp folder - if(refreshTmpDir){ - await rimraf(setting.tmpDataDir, {recursive: true, force: true}) - } - if(SHA256_RESULT){ - // save sha256 - await fs.writeFile(path.join(setting.fieldDir, setting.series + '-' + setting.dataType + '-CSV.zip.sha256'), SHA256_RESULT) - } - - // replace to new database - var tmpFiles = fsSync.readdirSync(setting.fieldDir).filter(file => file.endsWith('.tmp')) - for(var tmpFile of tmpFiles){ - await fs.rename(path.join(setting.fieldDir, tmpFile), path.join(setting.fieldDir, tmpFile.replace('.tmp', ''))) - } - if(setting.smallMemory && !setting.runningUpdate){ - await fs.cp(path.join(setting.fieldDir, 'v4-tmp'), path.join(setting.fieldDir, 'v4'), {recursive: true, force: true}) - await fs.cp(path.join(setting.fieldDir, 'v6-tmp'), path.join(setting.fieldDir, 'v6'), {recursive: true, force: true}) - rimraf(path.join(setting.fieldDir, 'v4-tmp')).catch(consoleWarn) - rimraf(path.join(setting.fieldDir, 'v6-tmp')).catch(consoleWarn) - } - - if(setting.browserType){ - await createBrowserIndex(setting.browserType) - } - - console.log('SUCCESS TO UPDATE') -} - -const ipLocationDb = async (db) => { - var preUrl = 'https://cdn.jsdelivr.net/npm/@ip-location-db/'+db+'-country/'+db+'-country' - var urls = [preUrl+'-ipv4.csv', preUrl+'-ipv6.csv'], fileNames = [] - for(var url of urls){ - fileNames.push(await _ipLocationDb(url)) - } - return fileNames -} - -const _ipLocationDb = async (url) => { - var fileEnd = url.split('-').pop() - return axios({ - method: 'get', - url: url, - responseType: 'stream' - }).then(res => { - return new Promise((resolve, reject) => { - var fileName = setting.ipLocationDb + '-Blocks-' + fileEnd - const ws = fsSync.createWriteStream(path.join(setting.tmpDataDir, fileName)) - ws.write('network1,network2,cc\n') - res.data.pipe(ws) - ws.on('finish', () => { - resolve(fileName) - }) - ws.on('error', reject) - }) - }) -} - -const dbipLocation = async () => { - const address = "https://download.db-ip.com/free/dbip-city-lite-" + dayjs().format('YYYY-MM') + ".csv.gz" - const res = await fetch(address) - const tmpFile = path.join(setting.tmpDataDir, 'dbip-city-lite.csv') - const ws = fsSync.createWriteStream(tmpFile) - await pipeline(res.body.pipeThrough(new DecompressionStream('gzip')), ws) - return new Promise((resolve, reject) => { - const v4 = [], v6 = [] - var preData - fsSync.createReadStream(tmpFile).pipe(parse()) - .on('error', reject) - .on('end', () => { - var v4Buf1 = Buffer.alloc(v4.length * 4) - var v4Buf2 = Buffer.alloc(v4.length * 4) - var v4Buf3 = Buffer.alloc(v4.length * 8) - for(var i = 0; i < v4.length; ++i){ - v4Buf1.writeUInt32LE(v4[i][0], i * 4) - v4Buf2.writeUInt32LE(v4[i][1], i * 4) - v4Buf3.writeInt32LE(v4[i][2], i * 8) - v4Buf3.writeInt32LE(v4[i][3], i * 8 + 4) - } - fsSync.writeFileSync(path.join(setting.fieldDir, '4-1.dat'), v4Buf1) - fsSync.writeFileSync(path.join(setting.fieldDir, '4-2.dat'), v4Buf2) - fsSync.writeFileSync(path.join(setting.fieldDir, '4-3.dat'), v4Buf3) - - var v6Buf1 = Buffer.alloc(v6.length * 8) - var v6Buf2 = Buffer.alloc(v6.length * 8) - var v6Buf3 = Buffer.alloc(v6.length * 8) - for(var i = 0; i < v6.length; ++i){ - v6Buf1.writeBigUInt64LE(v6[i][0], i * 8) - v6Buf2.writeBigUInt64LE(v6[i][1], i * 8) - v6Buf3.writeInt32LE(v6[i][2], i * 8) - v6Buf3.writeInt32LE(v6[i][3], i * 8 + 4) - } - fsSync.writeFileSync(path.join(setting.fieldDir, '6-1.dat'), v6Buf1) - fsSync.writeFileSync(path.join(setting.fieldDir, '6-2.dat'), v6Buf2) - fsSync.writeFileSync(path.join(setting.fieldDir, '6-3.dat'), v6Buf3) - resolve() - }) - .on('data', arr => { - if(!arr[2] || arr[3] === 'ZZ' || arr[3] === 'EU') return; - var latitude = Math.round((parseFloat(arr[6])) * 10000) // -90 ~ 90 -> 10 ~ 190 - // -900000 ~ 900000 -> 1000000 ~ 1900000 - var longitude = Math.round((parseFloat(arr[7])) * 10000)// -180 ~ 180 -> 20 ~ 220 - // -1800000 ~ 1800000 -> 2000000 ~ 2200000 - var countryCodeNum = countryCodeToNum(arr[3]) // 0 ~ 675 - latitude = (latitude) << 10 | countryCodeNum - if(arr[0].includes(':')){ - var start = aton6(arr[0]) - if(preData[1].constructor !== BigInt) preData = null - if(preData && preData[1] + 1n === start && preData[2] === latitude && preData[3] === longitude){ - preData[1] = aton6(arr[1]) - return - } - v6.push(preData = [aton6(arr[0]), aton6(arr[1]), latitude, longitude]) - } else { - var start = aton4(arr[0]) - if(preData && preData[1] + 1 === start && preData[2] === latitude && preData[3] === longitude){ - preData[1] = aton4(arr[1]) - return - } - v4.push(preData = [aton4(arr[0]), aton4(arr[1]), latitude, longitude]) - } - }) - }) -} - -const createBrowserIndex = async (type) => { - const exportDir = path.join(setting.fieldDir, type) - await fs.rm(path.join(exportDir, '4'), {recursive: true, force: true}) - await fs.mkdir(path.join(exportDir, '4'), {recursive: true}) - await fs.rm(path.join(exportDir, '6'), {recursive: true, force: true}) - await fs.mkdir(path.join(exportDir, '6'), {recursive: true}) - - const IndexSize = type === 'country' ? 1024 : 2048 - - // ipv4 - var startBuf = await fs.readFile(path.join(setting.fieldDir, '4-1.dat')) - var startList = new Uint32Array(startBuf.buffer) - var len = startList.length, indexList = new Uint32Array(IndexSize) - var i, j, k - var endBuf = await fs.readFile(path.join(setting.fieldDir, '4-2.dat')) - var endList = new Uint32Array(endBuf.buffer) - var dbInfo = await fs.readFile(path.join(setting.fieldDir, '4-3.dat')) - var dbList = type === 'country' ? new Uint16Array(dbInfo.buffer) : new Int32Array(dbInfo.buffer) - var recordSize = setting.mainRecordSize + 8 - for(i = 0; i < IndexSize; ++i){ - var index = len * i / IndexSize | 0 - indexList[i] = startList[index] - var nextIndex = len * (i + 1) / IndexSize | 0 - var count = nextIndex - index - var exportBuf = Buffer.alloc(recordSize * count) - for(j = index, k = 0; j < nextIndex; ++j){ - exportBuf.writeUInt32LE(startList[j], k * 4) - exportBuf.writeUInt32LE(endList[j], 4 * count + k * 4) - if(type === 'country'){ - exportBuf.writeUInt16LE(dbList[j], 8 * count + k * setting.mainRecordSize) - } else { - exportBuf.writeInt32LE(dbList[2*j], 8 * count + k * setting.mainRecordSize) - exportBuf.writeInt32LE(dbList[2*j+1], 8 * count + k * setting.mainRecordSize + 4) - } - ++k - } - await fs.writeFile(path.join(exportDir, '4', numberToDir(i)), exportBuf) - } - await fs.writeFile(path.join(exportDir, '4.idx'), Buffer.from(indexList.buffer)) - - startBuf = await fs.readFile(path.join(setting.fieldDir, '6-1.dat')) - startList = new BigUint64Array(startBuf.buffer) - len = startList.length - indexList = new BigUint64Array(IndexSize) - endBuf = await fs.readFile(path.join(setting.fieldDir, '6-2.dat')) - endList = new BigUint64Array(endBuf.buffer) - dbInfo = await fs.readFile(path.join(setting.fieldDir, '6-3.dat')) - dbList = type === 'country' ? new Uint16Array(dbInfo.buffer) : new Int32Array(dbInfo.buffer) - recordSize = setting.mainRecordSize + 16 - for(i = 0; i < IndexSize; ++i){ - var index = len * i / IndexSize | 0 - indexList[i] = startList[index] - var nextIndex = len * (i + 1) / IndexSize | 0 - var exportBuf = Buffer.alloc(recordSize * (nextIndex - index)) - var count = nextIndex - index - for(j = index, k = 0; j < nextIndex; ++j){ - exportBuf.writeBigUInt64LE(startList[j], k * 8) - exportBuf.writeBigUInt64LE(endList[j], 8 * count + k * 8) - if(type === 'country'){ - exportBuf.writeUInt16LE(dbList[j], 16 * count + k * setting.mainRecordSize) - } else { - exportBuf.writeInt32LE(dbList[2*j], 16 * count + k * setting.mainRecordSize) - exportBuf.writeInt32LE(dbList[2*j+1], 16 * count + k * setting.mainRecordSize + 4) - } - ++k - } - await fs.writeFile(path.join(exportDir, '6', numberToDir(i)), exportBuf) - } - await fs.writeFile(path.join(exportDir, '6.idx'), Buffer.from(indexList.buffer)) - - var exPath = path.join(__dirname, '..', 'browser', type) - await fs.rm(path.join(exPath, '4'), {recursive: true, force: true}) - await fs.rm(path.join(exPath, '6'), {recursive: true, force: true}) - await fs.cp(exportDir, exPath, {recursive: true}) - exPath = path.join(__dirname, '..', 'browser', type + '-extra') - await fs.rm(path.join(exPath, '4'), {recursive: true, force: true}) - await fs.rm(path.join(exPath, '6'), {recursive: true, force: true}) - await fs.cp(exportDir, exPath, {recursive: true}) - await fs.rm(exportDir, {recursive: true, force: true}) -} - -var SHA256_RESULT -const downloadZip = async () => { - SHA256_RESULT = false - var name = setting.dataType[0].toUpperCase() + setting.dataType.slice(1) - const database = { - type: setting.dataType, - edition: setting.series + '-' + name + '-CSV', - suffix: 'zip.sha256', - src: [ - setting.series + '-' + name + '-Locations-en.csv', - setting.series + '-' + name + '-Blocks-IPv4.csv', - setting.series + '-' + name + '-Blocks-IPv6.csv' - ], - } - if(setting.language !== 'en' && setting.isCity){ - database.src.push(setting.series + '-' + name + '-Locations-' + setting.language + '.csv') - } - if(!setting.licenseKey) { - return consoleWarn('Please set your license key') - } - var url = DownloadServer + '?edition_id=' + database.edition + '&suffix=' + database.suffix + "&license_key=" + setting.licenseKey - if(setting.licenseKey === 'redist'){ - url = 'https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/' - url += database.edition + '.' + database.suffix - } - var text = await axios.get(url) - var reg = /\w{50,}/, r = reg.exec(text.data) - if(!r) { - return consoleWarn('Cannot download sha256') - } - var sha256 = r[0], data = '' - try{ - data = await fs.readFile(path.join(setting.fieldDir, database.edition + '.zip.sha256'), 'utf8') - }catch(e){ - data = '' - } - - const zipPath = path.join(setting.tmpDataDir, database.edition + '.zip') - if(data === sha256){ - if(fsSync.existsSync(zipPath)){ - const zipHash = await sha256Hash(zipPath) - if(zipHash === sha256){ - if(!setting.multiDbDir) { - if(setting.sameDbSetting) return 'NO NEED TO UPDATE' - if(setting.language === 'en') return database.src - } - } - } else if(!setting.multiDbDir){ - if(setting.sameDbSetting) return 'NO NEED TO UPDATE' - } - } - - SHA256_RESULT = sha256 - url = DownloadServer + '?edition_id=' + database.edition + '&suffix=' + database.suffix.replace('.sha256', '') + "&license_key=" + setting.licenseKey - if(setting.licenseKey === 'redist'){ - url = 'https://raw.githubusercontent.com/sapics/node-geolite2-redist/master/redist/' - url += database.edition + '.' + database.suffix.replace('.sha256', '') - } - return axios({ - method: 'get', - url: url, - responseType: 'stream' - }).then(res => { - const dest = fsSync.createWriteStream(zipPath) - return new Promise((resolve, reject) => { - consoleLog('Decompressing', database.edition + '.zip') - res.data.pipe(dest) - res.data.on('end', () => { - yauzl.open(zipPath, {lazyEntries: true}, (err, zipfile) => { - if(err) return reject(err) - zipfile.readEntry() - zipfile.on('entry', entry => { - for(var src of database.src){ - if(!entry.fileName.endsWith(src)) continue; - consoleLog('Extracting', entry.fileName) - return (function(src){ - zipfile.openReadStream(entry, (err, readStream) => { - if(err) return reject(err) - readStream.pipe(fsSync.createWriteStream(path.join(setting.tmpDataDir, src))) - readStream.on('end', () => { - zipfile.readEntry() - }) - }) - })(src) - } - zipfile.readEntry() - }) - zipfile.on('end', () => resolve(database.src)) - }) - }) - res.data.on('error', reject) - }) - }) -} - -const sha256Hash = async (file) => { - return new Promise((resolve, reject) => { - const stream = fsSync.createReadStream(file) - const hash = createHash('sha256') - hash.once('finish', () => resolve(hash.digest('hex'))) - stream.on('error', reject) - stream.pipe(hash) - }) -} - -const createData = async (src) => { - var mapDatas = [] - var locationSrc = src.filter(file => file.includes('Locations')) - locationSrc.sort((a,b) => { - // Locations-en.csv should be the first - if(a.endsWith('-en.csv')) return -1 - if(b.endsWith('-en.csv')) return 1 - }) - for(var file of locationSrc){ - mapDatas.push(await getMapData(file)) - } - if(setting.locFile){ - minifyMapData(mapDatas) - } - var blockSrc = src.filter(file => file.includes('Blocks')) - mapDatas.push([]) - for(var file of blockSrc){ - await createMainData(file, mapDatas) - } - if(setting.locFile){ - await createMapData(mapDatas) - } -} - - -const createSmallMemoryFile = (ws, ipv4, line, buffer2, buffer3) => { - const [ _dir, file, offset ] = getSmallMemoryFile(line, ipv4 ? setting.v4 : setting.v6, true) - if(offset === 0){ - const dir = path.join(setting.fieldDir, _dir) - if(ws) ws.end() - if(file === '_0' && !fsSync.existsSync(dir)){ - fsSync.mkdirSync(dir, {recursive: true}) - } - if(setting.smallMemoryFileSize <= buffer2.length + buffer3.length){ - var buf = Buffer.alloc(buffer2.length + buffer3.length) - buffer2.copy(buf) - buffer3.copy(buf, buffer2.length) - fsSync.writeFile(path.join(dir, file), buf, () => {}) - return - } - ws = fsSync.createWriteStream(path.join(dir, file)) - } - ws.write(buffer2) - ws.write(buffer3) - return ws -} - -const createMainData = async (file, mapDatas) => { - var ipv4 = file.endsWith('v4.csv') - var ipv = ipv4 ? 4 : 6 - var rs = fsSync.createReadStream(path.join(setting.tmpDataDir, file)) - var ws1 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-1.dat.tmp'), {highWaterMark: 1024*1024}) - if(!setting.smallMemory){ - var ws2 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-2.dat.tmp'), {highWaterMark: 1024*1024}) - var ws3 = fsSync.createWriteStream(path.join(setting.fieldDir, ipv + '-3.dat.tmp'), {highWaterMark: 1024*1024}) - } else { - var ws = null - var dir = path.join(setting.fieldDir, 'v' + ipv + '-tmp') - if(fsSync.existsSync(dir)){ - await fs.rm(dir, {recursive: true, force: true}) - } - } - - var preBuffer1, preBuffer2, preBuffer3, preCC, preEnd - var preLocId, preLatitude, preLongitude, preArea, prePostcode -// var preCountryCode - var preLocLocation - var mapData0 = mapDatas[0], locIdList = mapDatas[mapDatas.length - 1] - var lineCount = 0 - areaDatabase = {}, areaCount = 0 - - return new Promise((resolve, reject) => { - var checkCount = 0 - function check(){ - if(++checkCount === 3)resolve() - } - rs.pipe(parse({headers: true})) - .on('error', reject) - .on('data', row => { - var cc, buffer1, buffer2, buffer3, addr, start, end - if(setting.ipLocationDb){ - if(ipv4){ - start = aton4(row.network1) - end = aton4(row.network2) - } else { - start = aton6(row.network1) - end = aton6(row.network2) - } - } else { - if(ipv4){ - addr = new Address4(row.network) - start = aton4(addr.startAddress().correctForm()) - end = aton4(addr.endAddress().correctForm()) - } else { - addr = new Address6(row.network) - start = aton6(addr.startAddress().correctForm()) - end = aton6(addr.endAddress().correctForm()) - } - } - - if(setting.isCountry){ - if(setting.ipLocationDb){ - cc = row.cc - } else { - cc = mapData0[row.geoname_id] - } - if(!cc || cc.length !== 2) { - return;// console.warn('Invalid country code', cc, row.geoname_id) - } - if(cc === preCC && (ipv4 && preEnd + 1 === start || !ipv4 && preEnd + 1n === start)){ - if(ipv4){ - preBuffer2.writeUInt32LE(end) - } else { - preBuffer2.writeBigUInt64LE(end) - } - } else { - if(ipv4){ - buffer1 = Buffer.allocUnsafe(4) - buffer1.writeUInt32LE(start) - buffer2 = Buffer.allocUnsafe(4) - buffer2.writeUInt32LE(end) - } else { - buffer1 = Buffer.allocUnsafe(8) - buffer1.writeBigUInt64LE(start) - buffer2 = Buffer.allocUnsafe(8) - buffer2.writeBigUInt64LE(end) - } - buffer3 = Buffer.allocUnsafe(2) - buffer3.write(cc) - if(preBuffer1){ - if(!ws1.write(preBuffer1)) rs.pause() - if (setting.smallMemory) { - ws = createSmallMemoryFile(ws, ipv4, lineCount++, preBuffer2, preBuffer3) - } else { - if(!ws2.write(preBuffer2)) rs.pause() - if(!ws3.write(preBuffer3)) rs.pause() - } - } - preCC = cc - preBuffer1 = buffer1 - preBuffer2 = buffer2 - preBuffer3 = buffer3 - } - } else { - var locId = row.geoname_id - var latitude = Math.round(row.latitude * 10000) - var longitude = Math.round(row.longitude * 10000) - var area = row.accuracy_radius - var postcode = row.postal_code - // var countryId = row.registered_country_geoname_id - // var countryCode = null - var locLocation = mapData0[locId] && mapData0[locId].counter - - var isSame = true - if(setting.mainFieldHash.latitude && preLatitude !== latitude) isSame = false - if(setting.mainFieldHash.longitude && preLongitude !== longitude) isSame = false - if(setting.mainFieldHash.area && preArea !== area) isSame = false - if(setting.mainFieldHash.postcode && prePostcode !== postcode) isSame = false - - if( (locId === preLocId || locLocation > 0 && locLocation === preLocLocation || setting.noLocFile) - && isSame -// && (locId || (mapData0[countryId] && preCountryCode === mapData0[countryId].country_iso_code)) - && (ipv4 && preEnd + 1 === start || !ipv4 && preEnd + 1n === start)){ - if(ipv4){ - preBuffer2.writeUInt32LE(parseInt(end, 10)) - } else { - preBuffer2.writeBigUInt64LE(end) - } - } else { - if(!locId){ - return; - // save only country code -// if(!countryId) return -// if(!mapData0[countryId]) return console.warn('Invalid country id', countryId) -// countryCode = mapData0[countryId].country_iso_code -// if(!countryCode || countryCode.length !== 2) return - } - if(locId && !mapData0[locId]) { - return consoleWarn('Invalid location id', locId) - } - if(locId){ - if(!mapData0[locId].counter){ - locIdList.push(locId) - locLocation = mapData0[locId].counter = locIdList.length - } - } - if(preBuffer1){ - if(!ws1.write(preBuffer1)) rs.pause() - if(setting.smallMemory){ - ws = createSmallMemoryFile(ws, ipv4, lineCount++, preBuffer2, preBuffer3) - } else { - if(!ws2.write(preBuffer2)) rs.pause() - if(!ws3.write(preBuffer3)) rs.pause() - } - } - if(ipv4){ - buffer1 = Buffer.allocUnsafe(4) - buffer1.writeUInt32LE(parseInt(start, 10)) - buffer2 = Buffer.allocUnsafe(4) - buffer2.writeUInt32LE(parseInt(end, 10)) - } else { - buffer1 = Buffer.allocUnsafe(8) - buffer1.writeBigUInt64LE(start) - buffer2 = Buffer.allocUnsafe(8) - buffer2.writeBigUInt64LE(end) - } - - buffer3 = Buffer.alloc(setting.mainRecordSize) - - var offset = 0 - if(setting.locFile){ - buffer3.writeUInt32LE(mapData0[locId].counter) - offset += 4 - } - if(setting.mainFieldHash.latitude) { - buffer3.writeInt32LE(latitude, offset) - offset += 4 - } - if(setting.mainFieldHash.longitude) { - buffer3.writeInt32LE(longitude, offset) - offset += 4 - } - if(setting.mainFieldHash.postcode) { - var postcodeDb = getPostcodeDatabase(postcode) - buffer3.writeUInt32LE(postcodeDb[1], offset) - buffer3.writeInt8(postcodeDb[0], offset + 4) - offset += 5 - } - if(setting.mainFieldHash.area) { - buffer3.writeUInt8(makeAreaDatabase(area), offset) - } - - preLocLocation = locLocation - preLocId = locId - preLatitude = latitude - preLongitude = longitude - preArea = area -// preCountryCode = countryCode - prePostcode = postcode - preBuffer1 = buffer1 - preBuffer2 = buffer2 - preBuffer3 = buffer3 - } - } - preEnd = end - }) - .on('pause', () => { - ws1.once('drain', () => rs.resume()) - if(!setting.smallMemory){ - ws2.once('drain', () => rs.resume()) - ws3.once('drain', () => rs.resume()) - } - }) - .on('end', () => { - if(setting.smallMemory){ - ws = createSmallMemoryFile(ws, ipv4, lineCount, preBuffer2, preBuffer3) - if(ws) ws.end(check) - else ++checkCount - ++checkCount - } else { - ws2.end(preBuffer2, check) - ws3.end(preBuffer3, check) - } - ws1.end(preBuffer1, check) - }) - }) -} - -const minifyMapData = (mapDatas) => { - var mapData0 = mapDatas[0] - if(setting.language !== 'en') { - var mapData1 = mapDatas.splice(1, 1)[0] - for(var locId in mapData0){ - if(mapData1 && mapData1[locId]){ - if(mapData1[locId].city_name) mapData0[locId].city_name = mapData1[locId].city_name - if(mapData1[locId].subdivision_1_name) mapData0[locId].subdivision_1_name = mapData1[locId].subdivision_1_name - if(mapData1[locId].subdivision_2_name) mapData0[locId].subdivision_2_name = mapData1[locId].subdivision_2_name - } - } - } - - var locIds = Object.keys(mapData0), locFields = Object.keys(setting.locFieldHash).filter(v => setting.locFieldHash[v]) - locIds.sort((a,b) => a-b) - const hash = { - country: 'country_iso_code', - region1: 'subdivision_1_iso_code', - region1_name: 'subdivision_1_name', - region2: 'subdivision_2_iso_code', - region2_name: 'subdivision_2_name', - city: 'city_name', - metro: 'metro_code', - timezone: 'time_zone' - } - const ranking = { - country: 8, - region1: 7, - region1_name: 6, - region2: 4, - region2_name: 3, - city: 9, - metro: 1, - timezone: 5 - } - locFields.sort((a,b) => { return ranking[b] - ranking[a]}) - var checkFields = locFields.map(v => hash[v]) - - var best1 = checkFields.shift() - var listHash = {} - for(var locId of locIds){ - var data = mapData0[locId] - if(!listHash[data[best1]]){ - listHash[data[best1]] = [] - } - listHash[data[best1]].push(locId) - } - - var i, j, len, dataI, dataJ, locIdJ, tmpLocIds - for(var key in listHash){ - tmpLocIds = listHash[key] - for(i = 0, len = tmpLocIds.length; i < len; ++i){ - dataI = mapData0[tmpLocIds[i]] - loopj: for(j = i+1; j < len; ++j){ - dataJ = mapData0[locIdJ = tmpLocIds[j]] - for(var field of checkFields){ - if(dataI[field] !== dataJ[field]){ - continue loopj - } - } - mapData0[locIdJ] = dataI - tmpLocIds.splice(j, 1) - --j - --len - } - } - } -} - -const createMapData = async (mapDatas) => { - var locIdList = mapDatas.pop() - var mapData0 = mapDatas[0] - var ws1 = fsSync.createWriteStream(path.join(setting.fieldDir, 'location.dat.tmp')) - var ws2 = fsSync.createWriteStream(path.join(setting.fieldDir, 'name.dat.tmp')) - var cityHash = {}, euHash = {} - sub1Database = {}, sub2Database = {}, timezoneDatabase = {} - sub1Count = 0, sub2Count = 0, timezoneCount = 0 - - for(var locId of locIdList){ - var data0 = mapData0[locId] - - var cc = data0.country_iso_code - var region1 = data0.subdivision_1_iso_code - var region2 = data0.subdivision_2_iso_code - var timezone = data0.time_zone - var metro = data0.metro_code - - var region1_name = data0.subdivision_1_name - var region2_name = data0.subdivision_2_name - var city = data0.city_name - - var offset = 0 - var b = Buffer.alloc(setting.locRecordSize) - if(setting.locFieldHash.country){ - if(cc && cc.length === 2) { - b.write(cc, offset); //country code [2 bytes] - if(setting.locFieldHash.eu && data0.is_in_european_union == 1){ - euHash[cc] = true - } - } - offset += 2 - } - if(setting.locFieldHash.region1){ - if(region1) b.writeUInt16LE(strToNum37(region1), offset) // subdivision code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region1_name){ - if(region1_name) b.writeUInt16LE(makeSub1Database(region1_name), offset) // subdivision name index [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region2){ - if(region2) b.writeUInt16LE(strToNum37(region2), offset) // subdivision code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.region2_name){ - if(region2_name) b.writeUInt16LE(makeSub2Database(region2_name), offset) // subdivision name index [2 bytes] - offset += 2 - } - if(setting.locFieldHash.metro){ - if(metro) b.writeUInt16LE(metro, offset) // metro code [2 bytes] - offset += 2 - } - if(setting.locFieldHash.timezone){ - if(timezone) b.writeUInt16LE(makeTimezoneDatabase(timezone), offset)// timezone [2 byte] - offset += 2 - } - if(setting.locFieldHash.city){ - if(city){ - b.writeUInt32LE(inputBuffer(cityHash, ws2, city), offset) // cityname index [4 bytes] - } - } - ws1.write(b) - } - ws1.end() - ws2.end() - - var hash = { - region1_name: DatabaseToArray(sub1Database), - region2_name: DatabaseToArray(sub2Database), - timezone: DatabaseToArray(timezoneDatabase), - area: DatabaseToArray(areaDatabase).map(v => parseInt(v, 10)||0), - eu: euHash - } - if(!setting.locFieldHash.region1_name) delete hash.region1_name - if(!setting.locFieldHash.region2_name) delete hash.region2_name - if(!setting.locFieldHash.timezone) delete hash.timezone - if(!setting.mainFieldHash.area) delete hash.area - if(!setting.locFieldHash.eu) delete hash.eu - if(Object.keys(hash).length > 0){ - await fs.writeFile(path.join(setting.fieldDir, 'sub.json.tmp'), JSON.stringify(hash)) - } - sub1Database = sub2Database = timezoneDatabase = areaDatabase = null - mapDatas.length = 0 -} - -const DatabaseToArray = (database) => { - var arr = [''] - for(var key in database){ - arr[database[key]] = key - } - return arr -} - -const inputBuffer = (hash, dataFile, text) => { - if(hash[text]) return hash[text] - if(hash.__offsetBB === undefined) { - var b = Buffer.alloc(1) - dataFile.write(b) - hash.__offsetBB = 1 - } - var offset = hash.__offsetBB - var b = Buffer.from(text) - var n = b.length + (offset << 8) - dataFile.write(b) - hash.__offsetBB = offset + b.length - return hash[text] = n -} - -var sub1Database = {}, sub2Database = {}, timezoneDatabase = {}, areaDatabase = {} -var sub1Count = 0, sub2Count = 0, timezoneCount = 0, areaCount = 0 -const makeSub1Database = (sub1) => { - if(sub1Database[sub1]) return sub1Database[sub1] - return sub1Database[sub1] = ++sub1Count -} -const makeSub2Database = (sub2) => { - if(sub2Database[sub2]) return sub2Database[sub2] - return sub2Database[sub2] = ++sub2Count -} -const makeTimezoneDatabase = (tz) => { - if(timezoneDatabase[tz]) return timezoneDatabase[tz] - return timezoneDatabase[tz] = ++timezoneCount -} -const makeAreaDatabase = (area) => { - if(areaDatabase[area]) return areaDatabase[area] - return areaDatabase[area] = ++areaCount -} - -const getMapData = async (file) => { - const rs = fsSync.createReadStream(path.join(setting.tmpDataDir, file)) - const result = {} - return new Promise((resolve, reject) => { - rs.pipe(parse({headers: true})) - .on('error', reject) - .on('data', row => { - if(setting.isCountry){ - result[row.geoname_id] = row.country_iso_code - } else { - result[row.geoname_id] = row - } - }) - .on('end', () => resolve(result)) - }) -} diff --git a/src/main-pack.mjs b/src/main-pack.mjs deleted file mode 100644 index 908ca35..0000000 --- a/src/main-pack.mjs +++ /dev/null @@ -1,560 +0,0 @@ - -import fs from 'fs/promises' -import fsSync from 'fs' -import path from 'path' -import { exec, execSync } from 'child_process' - -import { countries, continents } from 'countries-list' -import { CronJob } from 'cron' - -import { setting, setSetting, getSettingCmd, consoleLog, consoleWarn } from './setting.mjs' -import { num37ToStr, getSmallMemoryFile, getZeroFill, aton6Start, aton4 } from './utils.mjs' -import { update as updataDbAsync } from './db.mjs' - -const v4db = setting.v4 -const v6db = setting.v6 -const locFieldHash = setting.locFieldHash -const mainFieldHash = setting.mainFieldHash - -// JavaScript type definition -/** - * @typedef {Object} LookupResult - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [postcode] - * @property {string} [area] - * @property {string} [country] - * @property {boolean} [eu] - * @property {string} [region1] - * @property {string} [region1_name] - * @property {string} [region2] - * @property {string} [region2_name] - * @property {number} [metro] - * @property {string} [timezone] - * @property {string} [city] - * @property {string} country_name - * @property {string} country_native - * @property {string} continent - * @property {string} continent_name - * @property {string} capital - * @property {number[]} phone - * @property {string[]} currency - * @property {string[]} languages - */ - - -//--------------------------------------- -// Database lookup -//--------------------------------------- -/** - * lookup ip address - * @param {string} ip - ipv4 or ipv6 formatted address - * @return {LookupResult | Promise | null} location information - */ -export const lookup = (ip) => { - // net.isIP(ip) is good for checking ip address format - // but it's slow for checking ipv6 address - // therefore, we use ip.includes(':') instead - var isIpv6 - if(ip.includes(':')){ - ip = aton6Start(ip) - isIpv6 = ip.constructor === BigInt - } else { - ip = aton4(ip) - isIpv6 = false - } - const db = isIpv6 ? v6db : v4db - if(!(ip >= db.firstIp)) return null - const list = db.startIps - var fline = 0, cline = db.lastLine, line - for(;;){ - line = fline + cline >> 1 - if(ip < list[line]){ - if(cline - fline < 2) return null - cline = line - 1 - } else { - if(fline === line) { - if(cline !== line && ip >= list[cline]) { - line = cline - } - break - } - fline = line - } - } - - if(setting.smallMemory){ - // this case return Promise - return lineToFile(line, db).then(buffer => { - var endIp = isIpv6 ? buffer.readBigUInt64LE(0) : buffer.readUInt32LE(0) - if(ip > endIp) return null - if(setting.isCountry){ - return setCountryInfo({ - country: buffer.toString('latin1', isIpv6 ? 8 : 4, isIpv6 ? 10 : 6) - }) - } - return setCityRecord(buffer, {}, isIpv6 ? 8 : 4) - }) - } - if(ip > db.endIps[line]) return null - if(setting.isCountry){ - return setCountryInfo({ - country: db.mainBuffer.toString('latin1', line * db.recordSize, line * db.recordSize + 2) - }) - } - return setCityRecord(db.mainBuffer, {}, line * db.recordSize) -} - -/** - * setup database without reload - * @param {object} [_setting] - * @return {void} - */ -export const setupWithoutReload = setSetting - -/** - * clear in-memory database - * @type {function} - * @return {void} - */ -export const clear = () => { - v4db.startIps = v6db.startIps = v4db.endIps = v6db.endIps = v4db.mainBuffer = v6db.mainBuffer = null - Region1NameJson = Region2NameJson = TimezoneJson = LocBuffer = CityNameBuffer = EuJson = null -} - -var Region1NameJson, Region2NameJson, TimezoneJson, LocBuffer, CityNameBuffer, AreaJson, EuJson -var updateJob -/** - * reload in-memory database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [sync] - sync mode - * @param {boolean} [_runningUpdate] - if it's running update [internal use] - * @return {Promise|void} - */ -export const reload = async (_setting, sync, _runningUpdate) => { - var curSetting = setting - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - curSetting = Object.assign({}, setting) - Object.assign(setting, oldSetting) - } - const dataDir = curSetting.fieldDir - const v4 = v4db, v6 = v6db - var dataFiles = { - v41: path.join(dataDir, '4-1.dat'), - v42: path.join(dataDir, '4-2.dat'), - v43: path.join(dataDir, '4-3.dat'), - v61: path.join(dataDir, '6-1.dat'), - v62: path.join(dataDir, '6-2.dat'), - v63: path.join(dataDir, '6-3.dat'), - cityLocation: path.join(dataDir, 'location.dat'), - cityName: path.join(dataDir, 'name.dat'), - citySub: path.join(dataDir, 'sub.json') - } - - var locBuffer, cityNameBuffer, subBuffer - var buffer41, buffer42, buffer43, buffer61, buffer62, buffer63 - var testDir = dataDir - if(curSetting.smallMemory){ - testDir = path.join(testDir, 'v4') - } - - if(sync){ - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - updateDb(_setting && curSetting, true, true) - consoleLog('Database created') - } - buffer41 = fsSync.readFileSync(dataFiles.v41) - buffer61 = fsSync.readFileSync(dataFiles.v61) - if(!curSetting.smallMemory){ - buffer42 = fsSync.readFileSync(dataFiles.v42) - buffer43 = fsSync.readFileSync(dataFiles.v43) - buffer62 = fsSync.readFileSync(dataFiles.v62) - buffer63 = fsSync.readFileSync(dataFiles.v63) - } - if(curSetting.locFile){ - locBuffer = fsSync.readFileSync(dataFiles.cityLocation) - if(locFieldHash.city){ - cityNameBuffer = fsSync.readFileSync(dataFiles.cityName) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - subBuffer = fsSync.readFileSync(dataFiles.citySub) - } - } - } else { - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - await updateDb(_setting && curSetting, true) - consoleLog('Database created') - } - var prs = [ - fs.readFile(dataFiles.v41).then(data => buffer41 = data), - fs.readFile(dataFiles.v61).then(data => buffer61 = data), - ] - if(!curSetting.smallMemory){ - prs.push( - fs.readFile(dataFiles.v42).then(data => buffer42 = data), - fs.readFile(dataFiles.v43).then(data => buffer43 = data), - fs.readFile(dataFiles.v62).then(data => buffer62 = data), - fs.readFile(dataFiles.v63).then(data => buffer63 = data) - ) - } - if(curSetting.locFile){ - prs.push(fs.readFile(dataFiles.cityLocation).then(data => locBuffer = data)) - if(locFieldHash.city){ - prs.push(fs.readFile(dataFiles.cityName).then(data => cityNameBuffer = data)) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - prs.push(fs.readFile(dataFiles.citySub).then(data => subBuffer = data)) - } - } - await Promise.all(prs) - } - - if(_setting){ - Object.assign(setting, curSetting) - } - - v4.startIps = new Uint32Array(buffer41.buffer, 0, buffer41.byteLength >> 2) - v6.startIps = new BigUint64Array(buffer61.buffer, 0, buffer61.byteLength >> 3) - if(!curSetting.smallMemory){ - v4.endIps = new Uint32Array(buffer42.buffer, 0, buffer42.byteLength >> 2) - v4.mainBuffer = buffer43 - v6.endIps = new BigUint64Array(buffer62.buffer, 0, buffer62.byteLength >> 3) - v6.mainBuffer = buffer63 - } - - v4.lastLine = v4.startIps.length - 1 - v6.lastLine = v6.startIps.length - 1 - v4.firstIp = v4.startIps[0] - v6.firstIp = v6.startIps[0] - if(curSetting.isCity){ - LocBuffer = locBuffer - CityNameBuffer = cityNameBuffer - if(subBuffer){ - var tmpJson = JSON.parse(subBuffer) - if(locFieldHash.region1_name) Region1NameJson = tmpJson.region1_name - if(locFieldHash.region2_name) Region2NameJson = tmpJson.region2_name - if(locFieldHash.timezone) TimezoneJson = tmpJson.timezone - if(mainFieldHash.area) AreaJson = tmpJson.area - if(locFieldHash.eu) EuJson = tmpJson.eu - } - } - - // To avoid the error (when the database is updated while the server is running), - // we need to syncronous update the database - if(setting.smallMemory && _runningUpdate){ - const rimraf = (dir) => { - if(fs.rm){ - return fs.rm(dir, {recursive: true, force: true, maxRetries: 3}) - } - return fs.rmdir(dir, {recursive: true, maxRetries: 3}) - } - fsSync.cpSync(path.join(setting.fieldDir, 'v4-tmp'), path.join(setting.fieldDir, 'v4'), {recursive: true, force: true}) - fsSync.cpSync(path.join(setting.fieldDir, 'v6-tmp'), path.join(setting.fieldDir, 'v6'), {recursive: true, force: true}) - rimraf(path.join(setting.fieldDir, 'v4-tmp')).catch(consoleWarn) - rimraf(path.join(setting.fieldDir, 'v6-tmp')).catch(consoleWarn) - } - - if(!updateJob && setting.autoUpdate){ - updateJob = new CronJob(setting.autoUpdate, () => { - updateDb().finally(() => {}) - }, null, true, 'UTC') - } else if(updateJob && !setting.autoUpdate){ - updateJob.stop() - updateJob = null - } -} - -const watchHash = {} -/** - * Watch database directory. - * When database file is updated, it reload the database automatically - * This causes error if you use ILA_SMALL_MEMORY=true - * @type {function} - * @param {string} [name] - name of watch. If you want to watch multiple directories, you can set different name for each directory - */ -export const watchDb = (name = 'ILA') => { - var watchId = null - watchHash[name] = fsSync.watch(setting.fieldDir, (eventType, filename) => { - if(!filename.endsWith('.dat')) return; - if(fsSync.existsSync(path.join(setting.fieldDir, filename))) { - if(watchId) clearTimeout(watchId) - watchId = setTimeout(reload, 30 * 1000) - } - }) -} - -/** - * Stop watching database directory - * @type {function} - * @param {string} [name] - */ -export const stopWatchDb = (name = 'ILA') => { - if(watchHash[name]){ - watchHash[name].close() - delete watchHash[name] - } -} - -/** - * Update database and auto reload database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [noReload] - if you don't want to reload the database after update - * @param {boolean} [sync] - if you want to update the database in sync mode - * @return {Promise} - true if database is updated, false if no need to update - */ -export const updateDb = (_setting, noReload, sync) => { - // By import { updateDb } from './db.js' is the better way for update. - // However, db.js import many external modules, it makes slow down the startup time and uses more memory. - // Therefore, we use exec() to run the script in the other process. - var arg, runningUpdate = false - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - arg = getSettingCmd() - Object.assign(setting, oldSetting) - } else { - arg = getSettingCmd() - } - - if(!_setting && !sync && !setting.smallMemory){ - var sameDbSetting = setting.sameDbSetting - if(!sameDbSetting) setting.sameDbSetting = true - return updataDbAsync().then((r) => { - setting.sameDbSetting = sameDbSetting - if(r === true){ - if(!noReload) reload() - } - return true - }).catch(e => { - setting.sameDbSetting = sameDbSetting - throw e - }) - } - - var scriptPath = path.resolve(_setting ? _setting.apiDir : setting.apiDir, 'script', 'updatedb.mjs') - if(scriptPath.includes(' ')) scriptPath = '"' + scriptPath + '"' - var cmd = 'node ' + scriptPath - if(!_setting){ - arg += ' ILA_SAME_DB_SETTING=true' - } - if(_setting && _setting.smallmemory || !_setting && setting.smallMemory){ - runningUpdate = true - arg += ' ILA_RUNNING_UPDATE=true' - } - - if(arg){ - cmd += ' ' + arg - } - if(sync){ - try{ - var stdout = execSync(cmd) - if(stdout.includes('NO NEED TO UPDATE')){ - return true - } - if(stdout.includes('SUCCESS TO UPDATE')){ - if(!noReload){ - reload(_setting, sync) - } - return true - } - return false - }catch(e){ - consoleWarn(e) - return false - } - } - return new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if(err) { - consoleWarn(err) - } - if(stderr) { - consoleWarn(stderr) - } - if(stdout) { - consoleLog(stdout) - } - if(err) { - reject(err) - } else if(stdout.includes('ERROR TO UPDATE')){ - reject(new Error('ERROR TO UPDATE')) - } else if(stdout.includes('NO NEED TO UPDATE')){ - resolve(false) - } else if(stdout.includes('SUCCESS TO UPDATE')){ - if(noReload){ - resolve(true) - } else { - reload(_setting, false, runningUpdate).then(() => { - resolve(true) - }).catch(reject) - } - } else { - consoleLog('UNKNOWN ERROR') - reject(new Error('UNKNOWN ERROR')) - } - }) - }) -} - -/* --- Remain this code for better performance check -const lineToFile = (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - return new Promise((resolve, reject) => { - // stream -// fsSync.createReadStream(path.join(dir, file), {start: offset, end: offset + db.recordSize - 1}, {highWaterMark: db.recordSize}) -// .on('data', resolve) -// .on('error', reject) - - // fs.readFile -// fs.readFile(path.join(dir, file)).then(buffer => resolve(buffer.subarray(offset, offset + db.recordSize))).catch(reject) - - // fs.open + fs.read - fs.open(path.join(dir, file), 'r').then(fd => { - const buffer = Buffer.alloc(db.recordSize) - fd.read(buffer, 0, db.recordSize, offset).then(() => { - fd.close().catch(reject) - resolve(buffer) - }).catch(reject) - }).catch(reject) - }) -} -*/ -const lineToFile = async (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - const fd = await fs.open(path.join(setting.fieldDir, dir, file), 'r') - const buffer = Buffer.alloc(db.recordSize) - await fd.read(buffer, 0, db.recordSize, offset) - fd.close().catch(consoleWarn) - return buffer -} - - - -/** - * Set city record - * @param {any} buffer - * @param {LookupResult} geodata - * @param {number} offset - * @return {LookupResult} - */ -const setCityRecord = (buffer, geodata, offset) => { - var locId - if(setting.locFile){ - locId = buffer.readUInt32LE(offset) - offset += 4 - } - if(mainFieldHash.latitude){ - geodata.latitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.longitude){ - geodata.longitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.postcode){ - var postcode2 = buffer.readUInt32LE(offset) - var postcode1 = buffer.readInt8(offset + 4) - if (postcode2) { - var postcode, tmp - if(postcode1 < -9){ - tmp = (-postcode1).toString() - postcode = postcode2.toString(36) - postcode = getZeroFill(postcode.slice(0, -tmp[1]), tmp[0]-0) + '-' + getZeroFill(postcode.slice(-tmp[1]), tmp[1]-0) - } else if(postcode1 < 0){ - postcode = getZeroFill(postcode2.toString(36), -postcode1) - } else if(postcode1 < 10){ - postcode = getZeroFill(postcode2.toString(10), postcode1) - } else if(postcode1 < 72){ - postcode1 = String(postcode1) - postcode = getZeroFill(postcode2.toString(10), (postcode1[0]-0) + (postcode1[1]-0)) - postcode = postcode.slice(0, postcode1[0]-0) + '-' + postcode.slice(postcode1[0]-0) - } else { - postcode = postcode1.toString(36).slice(1) + postcode2.toString(36) - } - geodata.postcode = postcode.toUpperCase() - } - offset += 5 - } - if(mainFieldHash.area){ - geodata.area = AreaJson[buffer.readUInt8(offset)] - offset += 1 - } - - if(locId){ - var locOffset = (locId-1) * setting.locRecordSize - if(locFieldHash.country){ - geodata.country = LocBuffer.toString('utf8', locOffset, locOffset += 2) - if(locFieldHash.eu){ - geodata.eu = EuJson[geodata.country] - } - } - if(locFieldHash.region1){ - var region1 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1 > 0) geodata.region1 = num37ToStr(region1) - } - if(locFieldHash.region1_name){ - var region1_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1_name > 0) geodata.region1_name = Region1NameJson[region1_name] - } - if(locFieldHash.region2){ - var region2 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2 > 0) geodata.region2 = num37ToStr(region2) - } - if(locFieldHash.region2_name){ - var region2_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2_name > 0) geodata.region2_name = Region2NameJson[region2_name] - } - if(locFieldHash.metro){ - var metro = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(metro > 0) geodata.metro = metro - } - if(locFieldHash.timezone){ - var timezone = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(timezone > 0) geodata.timezone = TimezoneJson[timezone] - } - if(locFieldHash.city){ - var city = LocBuffer.readUInt32LE(locOffset) - locOffset += 4 - if(city > 0){ - var start = city >>> 8 - geodata.city = CityNameBuffer.toString('utf8', start, start + (city & 255)) - } - } - } - return setCountryInfo(geodata) -} - -/** - * Set country information - * @param {LookupResult} geodata - * @return {LookupResult} - */ -const setCountryInfo = (geodata) => { - if(setting.addCountryInfo){ - var h = countries[geodata.country] - geodata.country_name = h.name - geodata.country_native = h.native - geodata.continent = h.continent - geodata.continent_name = continents[h.continent] - geodata.capital = h.capital - geodata.phone = h.phone - geodata.currency = h.currency - geodata.languages = h.languages - } - return geodata -} - -await reload() diff --git a/src/main-pack.patch b/src/main-pack.patch deleted file mode 100644 index ee2aac7..0000000 --- a/src/main-pack.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- main.mjs 2024-10-24 00:32:36.050599300 +0900 -+++ main-pack.mjs 2024-10-24 21:32:41.146024800 +0900 -@@ -9,6 +9,7 @@ import { CronJob } from 'cron' - - import { setting, setSetting, getSettingCmd, consoleLog, consoleWarn } from './setting.mjs' - import { num37ToStr, getSmallMemoryFile, getZeroFill, aton6Start, aton4 } from './utils.mjs' -+import { update as updataDbAsync } from './db.mjs' - - const v4db = setting.v4 - const v6db = setting.v6 -@@ -295,6 +296,21 @@ export const updateDb = (_setting, noRel - arg = getSettingCmd() - } - -+ if(!_setting && !sync && !setting.smallMemory){ -+ var sameDbSetting = setting.sameDbSetting -+ if(!sameDbSetting) setting.sameDbSetting = true -+ return updataDbAsync().then((r) => { -+ setting.sameDbSetting = sameDbSetting -+ if(r === true){ -+ if(!noReload) reload() -+ } -+ return true -+ }).catch(e => { -+ setting.sameDbSetting = sameDbSetting -+ throw e -+ }) -+ } -+ - var scriptPath = path.resolve(_setting ? _setting.apiDir : setting.apiDir, 'script', 'updatedb.mjs') - if(scriptPath.includes(' ')) scriptPath = '"' + scriptPath + '"' - var cmd = 'node ' + scriptPath diff --git a/src/main.mjs b/src/main.mjs deleted file mode 100644 index 151205e..0000000 --- a/src/main.mjs +++ /dev/null @@ -1,544 +0,0 @@ - -import fs from 'fs/promises' -import fsSync from 'fs' -import path from 'path' -import { exec, execSync } from 'child_process' - -import { countries, continents } from 'countries-list' -import { CronJob } from 'cron' - -import { setting, setSetting, getSettingCmd, consoleLog, consoleWarn } from './setting.mjs' -import { num37ToStr, getSmallMemoryFile, getZeroFill, aton6Start, aton4 } from './utils.mjs' - -const v4db = setting.v4 -const v6db = setting.v6 -const locFieldHash = setting.locFieldHash -const mainFieldHash = setting.mainFieldHash - -// JavaScript type definition -/** - * @typedef {Object} LookupResult - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [postcode] - * @property {string} [area] - * @property {string} [country] - * @property {boolean} [eu] - * @property {string} [region1] - * @property {string} [region1_name] - * @property {string} [region2] - * @property {string} [region2_name] - * @property {number} [metro] - * @property {string} [timezone] - * @property {string} [city] - * @property {string} country_name - * @property {string} country_native - * @property {string} continent - * @property {string} continent_name - * @property {string} capital - * @property {number[]} phone - * @property {string[]} currency - * @property {string[]} languages - */ - - -//--------------------------------------- -// Database lookup -//--------------------------------------- -/** - * lookup ip address - * @param {string} ip - ipv4 or ipv6 formatted address - * @return {LookupResult | Promise | null} location information - */ -export const lookup = (ip) => { - // net.isIP(ip) is good for checking ip address format - // but it's slow for checking ipv6 address - // therefore, we use ip.includes(':') instead - var isIpv6 - if(ip.includes(':')){ - ip = aton6Start(ip) - isIpv6 = ip.constructor === BigInt - } else { - ip = aton4(ip) - isIpv6 = false - } - const db = isIpv6 ? v6db : v4db - if(!(ip >= db.firstIp)) return null - const list = db.startIps - var fline = 0, cline = db.lastLine, line - for(;;){ - line = fline + cline >> 1 - if(ip < list[line]){ - if(cline - fline < 2) return null - cline = line - 1 - } else { - if(fline === line) { - if(cline !== line && ip >= list[cline]) { - line = cline - } - break - } - fline = line - } - } - - if(setting.smallMemory){ - // this case return Promise - return lineToFile(line, db).then(buffer => { - var endIp = isIpv6 ? buffer.readBigUInt64LE(0) : buffer.readUInt32LE(0) - if(ip > endIp) return null - if(setting.isCountry){ - return setCountryInfo({ - country: buffer.toString('latin1', isIpv6 ? 8 : 4, isIpv6 ? 10 : 6) - }) - } - return setCityRecord(buffer, {}, isIpv6 ? 8 : 4) - }) - } - if(ip > db.endIps[line]) return null - if(setting.isCountry){ - return setCountryInfo({ - country: db.mainBuffer.toString('latin1', line * db.recordSize, line * db.recordSize + 2) - }) - } - return setCityRecord(db.mainBuffer, {}, line * db.recordSize) -} - -/** - * setup database without reload - * @param {object} [_setting] - * @return {void} - */ -export const setupWithoutReload = setSetting - -/** - * clear in-memory database - * @type {function} - * @return {void} - */ -export const clear = () => { - v4db.startIps = v6db.startIps = v4db.endIps = v6db.endIps = v4db.mainBuffer = v6db.mainBuffer = null - Region1NameJson = Region2NameJson = TimezoneJson = LocBuffer = CityNameBuffer = EuJson = null -} - -var Region1NameJson, Region2NameJson, TimezoneJson, LocBuffer, CityNameBuffer, AreaJson, EuJson -var updateJob -/** - * reload in-memory database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [sync] - sync mode - * @param {boolean} [_runningUpdate] - if it's running update [internal use] - * @return {Promise|void} - */ -export const reload = async (_setting, sync, _runningUpdate) => { - var curSetting = setting - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - curSetting = Object.assign({}, setting) - Object.assign(setting, oldSetting) - } - const dataDir = curSetting.fieldDir - const v4 = v4db, v6 = v6db - var dataFiles = { - v41: path.join(dataDir, '4-1.dat'), - v42: path.join(dataDir, '4-2.dat'), - v43: path.join(dataDir, '4-3.dat'), - v61: path.join(dataDir, '6-1.dat'), - v62: path.join(dataDir, '6-2.dat'), - v63: path.join(dataDir, '6-3.dat'), - cityLocation: path.join(dataDir, 'location.dat'), - cityName: path.join(dataDir, 'name.dat'), - citySub: path.join(dataDir, 'sub.json') - } - - var locBuffer, cityNameBuffer, subBuffer - var buffer41, buffer42, buffer43, buffer61, buffer62, buffer63 - var testDir = dataDir - if(curSetting.smallMemory){ - testDir = path.join(testDir, 'v4') - } - - if(sync){ - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - updateDb(_setting && curSetting, true, true) - consoleLog('Database created') - } - buffer41 = fsSync.readFileSync(dataFiles.v41) - buffer61 = fsSync.readFileSync(dataFiles.v61) - if(!curSetting.smallMemory){ - buffer42 = fsSync.readFileSync(dataFiles.v42) - buffer43 = fsSync.readFileSync(dataFiles.v43) - buffer62 = fsSync.readFileSync(dataFiles.v62) - buffer63 = fsSync.readFileSync(dataFiles.v63) - } - if(curSetting.locFile){ - locBuffer = fsSync.readFileSync(dataFiles.cityLocation) - if(locFieldHash.city){ - cityNameBuffer = fsSync.readFileSync(dataFiles.cityName) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - subBuffer = fsSync.readFileSync(dataFiles.citySub) - } - } - } else { - if(!fsSync.existsSync(testDir)){ - consoleLog('Database creating ...') - await updateDb(_setting && curSetting, true) - consoleLog('Database created') - } - var prs = [ - fs.readFile(dataFiles.v41).then(data => buffer41 = data), - fs.readFile(dataFiles.v61).then(data => buffer61 = data), - ] - if(!curSetting.smallMemory){ - prs.push( - fs.readFile(dataFiles.v42).then(data => buffer42 = data), - fs.readFile(dataFiles.v43).then(data => buffer43 = data), - fs.readFile(dataFiles.v62).then(data => buffer62 = data), - fs.readFile(dataFiles.v63).then(data => buffer63 = data) - ) - } - if(curSetting.locFile){ - prs.push(fs.readFile(dataFiles.cityLocation).then(data => locBuffer = data)) - if(locFieldHash.city){ - prs.push(fs.readFile(dataFiles.cityName).then(data => cityNameBuffer = data)) - } - if(locFieldHash.region1_name || locFieldHash.region2_name || locFieldHash.timezone || mainFieldHash.area || locFieldHash.eu){ - prs.push(fs.readFile(dataFiles.citySub).then(data => subBuffer = data)) - } - } - await Promise.all(prs) - } - - if(_setting){ - Object.assign(setting, curSetting) - } - - v4.startIps = new Uint32Array(buffer41.buffer, 0, buffer41.byteLength >> 2) - v6.startIps = new BigUint64Array(buffer61.buffer, 0, buffer61.byteLength >> 3) - if(!curSetting.smallMemory){ - v4.endIps = new Uint32Array(buffer42.buffer, 0, buffer42.byteLength >> 2) - v4.mainBuffer = buffer43 - v6.endIps = new BigUint64Array(buffer62.buffer, 0, buffer62.byteLength >> 3) - v6.mainBuffer = buffer63 - } - - v4.lastLine = v4.startIps.length - 1 - v6.lastLine = v6.startIps.length - 1 - v4.firstIp = v4.startIps[0] - v6.firstIp = v6.startIps[0] - if(curSetting.isCity){ - LocBuffer = locBuffer - CityNameBuffer = cityNameBuffer - if(subBuffer){ - var tmpJson = JSON.parse(subBuffer) - if(locFieldHash.region1_name) Region1NameJson = tmpJson.region1_name - if(locFieldHash.region2_name) Region2NameJson = tmpJson.region2_name - if(locFieldHash.timezone) TimezoneJson = tmpJson.timezone - if(mainFieldHash.area) AreaJson = tmpJson.area - if(locFieldHash.eu) EuJson = tmpJson.eu - } - } - - // To avoid the error (when the database is updated while the server is running), - // we need to syncronous update the database - if(setting.smallMemory && _runningUpdate){ - const rimraf = (dir) => { - if(fs.rm){ - return fs.rm(dir, {recursive: true, force: true, maxRetries: 3}) - } - return fs.rmdir(dir, {recursive: true, maxRetries: 3}) - } - fsSync.cpSync(path.join(setting.fieldDir, 'v4-tmp'), path.join(setting.fieldDir, 'v4'), {recursive: true, force: true}) - fsSync.cpSync(path.join(setting.fieldDir, 'v6-tmp'), path.join(setting.fieldDir, 'v6'), {recursive: true, force: true}) - rimraf(path.join(setting.fieldDir, 'v4-tmp')).catch(consoleWarn) - rimraf(path.join(setting.fieldDir, 'v6-tmp')).catch(consoleWarn) - } - - if(!updateJob && setting.autoUpdate){ - updateJob = new CronJob(setting.autoUpdate, () => { - updateDb().finally(() => {}) - }, null, true, 'UTC') - } else if(updateJob && !setting.autoUpdate){ - updateJob.stop() - updateJob = null - } -} - -const watchHash = {} -/** - * Watch database directory. - * When database file is updated, it reload the database automatically - * This causes error if you use ILA_SMALL_MEMORY=true - * @type {function} - * @param {string} [name] - name of watch. If you want to watch multiple directories, you can set different name for each directory - */ -export const watchDb = (name = 'ILA') => { - var watchId = null - watchHash[name] = fsSync.watch(setting.fieldDir, (eventType, filename) => { - if(!filename.endsWith('.dat')) return; - if(fsSync.existsSync(path.join(setting.fieldDir, filename))) { - if(watchId) clearTimeout(watchId) - watchId = setTimeout(reload, 30 * 1000) - } - }) -} - -/** - * Stop watching database directory - * @type {function} - * @param {string} [name] - */ -export const stopWatchDb = (name = 'ILA') => { - if(watchHash[name]){ - watchHash[name].close() - delete watchHash[name] - } -} - -/** - * Update database and auto reload database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [noReload] - if you don't want to reload the database after update - * @param {boolean} [sync] - if you want to update the database in sync mode - * @return {Promise} - true if database is updated, false if no need to update - */ -export const updateDb = (_setting, noReload, sync) => { - // By import { updateDb } from './db.js' is the better way for update. - // However, db.js import many external modules, it makes slow down the startup time and uses more memory. - // Therefore, we use exec() to run the script in the other process. - var arg, runningUpdate = false - if(_setting){ - var oldSetting = Object.assign({}, setting) - setSetting(_setting) - arg = getSettingCmd() - Object.assign(setting, oldSetting) - } else { - arg = getSettingCmd() - } - - var scriptPath = path.resolve(_setting ? _setting.apiDir : setting.apiDir, 'script', 'updatedb.mjs') - if(scriptPath.includes(' ')) scriptPath = '"' + scriptPath + '"' - var cmd = 'node ' + scriptPath - if(!_setting){ - arg += ' ILA_SAME_DB_SETTING=true' - } - if(_setting && _setting.smallmemory || !_setting && setting.smallMemory){ - runningUpdate = true - arg += ' ILA_RUNNING_UPDATE=true' - } - - if(arg){ - cmd += ' ' + arg - } - if(sync){ - try{ - var stdout = execSync(cmd) - if(stdout.includes('NO NEED TO UPDATE')){ - return true - } - if(stdout.includes('SUCCESS TO UPDATE')){ - if(!noReload){ - reload(_setting, sync) - } - return true - } - return false - }catch(e){ - consoleWarn(e) - return false - } - } - return new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if(err) { - consoleWarn(err) - } - if(stderr) { - consoleWarn(stderr) - } - if(stdout) { - consoleLog(stdout) - } - if(err) { - reject(err) - } else if(stdout.includes('ERROR TO UPDATE')){ - reject(new Error('ERROR TO UPDATE')) - } else if(stdout.includes('NO NEED TO UPDATE')){ - resolve(false) - } else if(stdout.includes('SUCCESS TO UPDATE')){ - if(noReload){ - resolve(true) - } else { - reload(_setting, false, runningUpdate).then(() => { - resolve(true) - }).catch(reject) - } - } else { - consoleLog('UNKNOWN ERROR') - reject(new Error('UNKNOWN ERROR')) - } - }) - }) -} - -/* --- Remain this code for better performance check -const lineToFile = (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - return new Promise((resolve, reject) => { - // stream -// fsSync.createReadStream(path.join(dir, file), {start: offset, end: offset + db.recordSize - 1}, {highWaterMark: db.recordSize}) -// .on('data', resolve) -// .on('error', reject) - - // fs.readFile -// fs.readFile(path.join(dir, file)).then(buffer => resolve(buffer.subarray(offset, offset + db.recordSize))).catch(reject) - - // fs.open + fs.read - fs.open(path.join(dir, file), 'r').then(fd => { - const buffer = Buffer.alloc(db.recordSize) - fd.read(buffer, 0, db.recordSize, offset).then(() => { - fd.close().catch(reject) - resolve(buffer) - }).catch(reject) - }).catch(reject) - }) -} -*/ -const lineToFile = async (line, db) => { - const [ dir, file, offset ] = getSmallMemoryFile(line, db) - const fd = await fs.open(path.join(setting.fieldDir, dir, file), 'r') - const buffer = Buffer.alloc(db.recordSize) - await fd.read(buffer, 0, db.recordSize, offset) - fd.close().catch(consoleWarn) - return buffer -} - - - -/** - * Set city record - * @param {any} buffer - * @param {LookupResult} geodata - * @param {number} offset - * @return {LookupResult} - */ -const setCityRecord = (buffer, geodata, offset) => { - var locId - if(setting.locFile){ - locId = buffer.readUInt32LE(offset) - offset += 4 - } - if(mainFieldHash.latitude){ - geodata.latitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.longitude){ - geodata.longitude = buffer.readInt32LE(offset) / 10000 - offset += 4 - } - if(mainFieldHash.postcode){ - var postcode2 = buffer.readUInt32LE(offset) - var postcode1 = buffer.readInt8(offset + 4) - if (postcode2) { - var postcode, tmp - if(postcode1 < -9){ - tmp = (-postcode1).toString() - postcode = postcode2.toString(36) - postcode = getZeroFill(postcode.slice(0, -tmp[1]), tmp[0]-0) + '-' + getZeroFill(postcode.slice(-tmp[1]), tmp[1]-0) - } else if(postcode1 < 0){ - postcode = getZeroFill(postcode2.toString(36), -postcode1) - } else if(postcode1 < 10){ - postcode = getZeroFill(postcode2.toString(10), postcode1) - } else if(postcode1 < 72){ - postcode1 = String(postcode1) - postcode = getZeroFill(postcode2.toString(10), (postcode1[0]-0) + (postcode1[1]-0)) - postcode = postcode.slice(0, postcode1[0]-0) + '-' + postcode.slice(postcode1[0]-0) - } else { - postcode = postcode1.toString(36).slice(1) + postcode2.toString(36) - } - geodata.postcode = postcode.toUpperCase() - } - offset += 5 - } - if(mainFieldHash.area){ - geodata.area = AreaJson[buffer.readUInt8(offset)] - offset += 1 - } - - if(locId){ - var locOffset = (locId-1) * setting.locRecordSize - if(locFieldHash.country){ - geodata.country = LocBuffer.toString('utf8', locOffset, locOffset += 2) - if(locFieldHash.eu){ - geodata.eu = EuJson[geodata.country] - } - } - if(locFieldHash.region1){ - var region1 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1 > 0) geodata.region1 = num37ToStr(region1) - } - if(locFieldHash.region1_name){ - var region1_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region1_name > 0) geodata.region1_name = Region1NameJson[region1_name] - } - if(locFieldHash.region2){ - var region2 = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2 > 0) geodata.region2 = num37ToStr(region2) - } - if(locFieldHash.region2_name){ - var region2_name = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(region2_name > 0) geodata.region2_name = Region2NameJson[region2_name] - } - if(locFieldHash.metro){ - var metro = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(metro > 0) geodata.metro = metro - } - if(locFieldHash.timezone){ - var timezone = LocBuffer.readUInt16LE(locOffset) - locOffset += 2 - if(timezone > 0) geodata.timezone = TimezoneJson[timezone] - } - if(locFieldHash.city){ - var city = LocBuffer.readUInt32LE(locOffset) - locOffset += 4 - if(city > 0){ - var start = city >>> 8 - geodata.city = CityNameBuffer.toString('utf8', start, start + (city & 255)) - } - } - } - return setCountryInfo(geodata) -} - -/** - * Set country information - * @param {LookupResult} geodata - * @return {LookupResult} - */ -const setCountryInfo = (geodata) => { - if(setting.addCountryInfo){ - var h = countries[geodata.country] - geodata.country_name = h.name - geodata.country_native = h.native - geodata.continent = h.continent - geodata.continent_name = continents[h.continent] - geodata.capital = h.capital - geodata.phone = h.phone - geodata.currency = h.currency - geodata.languages = h.languages - } - return geodata -} - -await reload() diff --git a/src/setting.mjs b/src/setting.mjs deleted file mode 100644 index 7d01ffd..0000000 --- a/src/setting.mjs +++ /dev/null @@ -1,185 +0,0 @@ -import path from 'path' - -import { fileURLToPath } from 'url' -import { getFieldsSize } from './utils.mjs' - -const defaultSetting = { - // -- setting for all - fields: ['country'], - dataDir: '../data/', - tmpDataDir: '../tmp/', - apiDir: '..', - - // ---- small memory setting - smallMemory: false, - smallMemoryFileSize: 4096, - - // ---- setting for lookup - addCountryInfo: false, - - // -- setting for update - licenseKey: 'redist', - ipLocationDb: '', - downloadType: 'reuse', - series: 'GeoLite2', // or GeoIP2 - language: 'en', - fakeData: false, - autoUpdate: 'default', - - sameDbSetting: false, - multiDbDir: false, - - browserType: false, - silent: false, -} - -// default setting -export const setting = { - v4: {ipv4: true, ipv6: false, name: 'v4'}, - v6: {ipv4: false, ipv6: true, name: 'v6'}, - mainFieldHash: {}, - locFieldHash: {}, -} - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const mainFields = ['latitude', 'longitude', 'area', 'postcode'] -const locFields = ['country', 'region1', 'region1_name', 'region2', 'region2_name', 'metro', 'timezone', 'city', 'eu'] - -const shortNumber = { - latitude: 1, - longitude: 2, - area: 4, - postcode: 8, - country: 16, - region1: 32, - region1_name: 64, - region2: 128, - region2_name: 256, - metro: 512, - timezone: 1024, - city: 2048, - eu: 4096 -} - -const make_key = (key) => { - return 'ILA_' + key.replace(/([A-Z])/g, char => '_' + char).toUpperCase() -} - -export const consoleLog = (...args) => { - if(setting.silent) return - console.log(...args) -} - -export const consoleWarn = (...args) => { - if(setting.silent) return - console.warn(...args) -} - -export const getSettingCmd = () => { - const ret = [] - for(const key in defaultSetting){ - if(setting[key] && setting[key] !== defaultSetting[key]){ - var value = String(setting[key]) - if(value.includes(' ')) value = '"' + value + '"' - ret.push(make_key(key) + '=' + value) - } - } - return ret.join(' ') -} - -const inputSetting = {} -var settingKeys = Object.keys(defaultSetting) -for(var env in process.env){ - for(var key of settingKeys){ - if(env.toUpperCase() === make_key(key)){ - inputSetting[key] = process.env[env] - } - } -} -for(var arg of process.argv){ - var v = arg.toUpperCase() - for(var key of settingKeys){ - if(v.includes(make_key(key) + '=')){ - inputSetting[key] = arg.split('=')[1] - } - } -} - -const NumReg = /^\d+$/ -export const setSetting = (_setting = {}) => { - for(var key in _setting){ - var value = setting[key] = _setting[key] - if(value === "false") setting[key] = false - else if(value === "true") setting[key] = true - else if(NumReg.test(value)) setting[key] = parseInt(value) - } - - if(setting.autoUpdate === 'default'){ - setting.autoUpdate = Math.floor(Math.random()*59.9) + ' ' + Math.floor(Math.random()*59.9) + ' 0 * * wed,sat' - } - - // Directory Setting - const windowsDriveReg = /^[a-zA-Z]:\\/ - if(!setting.dataDir.startsWith('/') && !setting.dataDir.startsWith('\\\\') && !windowsDriveReg.test(setting.dataDir)){ - setting.dataDir = path.resolve(__dirname, setting.dataDir) - } - if(!setting.tmpDataDir.startsWith('/') && !setting.tmpDataDir.startsWith('\\\\') && !windowsDriveReg.test(setting.tmpDataDir)){ - setting.tmpDataDir = path.resolve(__dirname, setting.tmpDataDir) - } - if(!setting.apiDir.startsWith('/') && !setting.apiDir.startsWith('\\\\') && !windowsDriveReg.test(setting.apiDir)){ - setting.apiDir = path.resolve(__dirname, setting.apiDir) - } - - // Fields Setting - if(typeof setting.fields === 'string'){ - setting.fields = setting.fields.split(/\s*,\s*/) - } - if(setting.fields.includes('all')) { - setting.fields = mainFields.concat(locFields) - } else { - setting.fields = setting.fields.filter(v => mainFields.includes(v) || locFields.includes(v)) - } - - if(setting.fields.length === 1 && setting.fields[0] === 'country'){ - setting.dataType = 'country' - } else { - setting.dataType = 'city' - } - setting.isCountry = setting.dataType === 'country' - setting.isCity = !setting.isCountry - - for(var field of mainFields){ - setting.mainFieldHash[field] = setting.fields.includes(field) - } - - setting.noLocFile = true - for(var field of locFields){ - setting.locFieldHash[field] = setting.fields.includes(field) - if(setting.locFieldHash[field]){ - setting.noLocFile = false - } - } - if(setting.isCountry) setting.noLocFile = true - setting.locFile = !setting.noLocFile - - setting.fieldDir = path.join(setting.dataDir, setting.fields.reduce((sum, v) => sum + shortNumber[v], 0).toString(36)) - - // Main Data Record Size - var mainRecordSize = setting.isCountry ? 2 : getFieldsSize(setting.fields.filter(v => mainFields.includes(v))) - if(setting.locFile) mainRecordSize += 4 - setting.v4.recordSize = setting.v6.recordSize = setting.mainRecordSize = mainRecordSize - setting.locRecordSize = getFieldsSize(setting.fields.filter(v => locFields.includes(v))) - if(setting.smallMemory){ - setting.v4.recordSize += 4 - setting.v6.recordSize += 8 - setting.v4.fileLineMax = (setting.smallMemoryFileSize / setting.v4.recordSize | 0) || 1 - setting.v6.fileLineMax = (setting.smallMemoryFileSize / setting.v6.recordSize | 0) || 1 - setting.fileMax = 1024 - setting.v4.folderLineMax = setting.v4.fileLineMax * setting.fileMax - setting.v6.folderLineMax = setting.v6.fileLineMax * setting.fileMax - } -} - -setSetting(Object.assign({}, defaultSetting, inputSetting)) diff --git a/src/utils.mjs b/src/utils.mjs deleted file mode 100644 index ba06a5d..0000000 --- a/src/utils.mjs +++ /dev/null @@ -1,199 +0,0 @@ -import path from 'path' - -// export const DEBUG = process.argv.includes('debug') - -//export const MaxLocationId = 0xFFFFFFFF - 26*26 -export const countryCodeToNum = (code) => { // 0~675 - code = code.toUpperCase() - return (code.charCodeAt(0)-65)*26 + (code.charCodeAt(1)-65) -} -export const numToCountryCode = (num) => { - return String.fromCharCode((num/26|0) + 65, num % 26 + 65) -} - -export const getFieldsSize = (types) => { - var size = 0 - for (const type of types) { - switch (type) { - case 'postcode': - size += 5 - break - case 'area': - size += 1 - break - case 'latitude': - case 'longitude': - case 'city': - size += 4 - break - case 'eu': - break - default: - size += 2 - break - } - } - return size -} - -export const ntoa4 = (n) => { - return [n >>> 24, n >> 16 & 255, n >> 8 & 255, n & 255].join('.') -} - -export const aton4 = (a) => { - a = a.split(/\./) - return (a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]) >>> 0 -} - -export const aton6Start = (a) => { - if(a.includes('.')){ - return aton4(a.split(':').pop()) - } - a = a.split(/:/) - const l = a.length - 1 - var i, r = 0n - if (l < 7) { - const omitStart = a.indexOf('') - if(omitStart < 4){ - const omitted = 8 - a.length, omitEnd = omitStart + omitted - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0 - } - } - } - for (i = 0; i < 4; i++) { - if(a[i]) r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)) - } - return r -} - -export const aton6 = (a) => { - a = a.replace(/"/g, '').split(/:/) - - const l = a.length - 1 - var i - if (a[l] === '') a[l] = 0 - if (l < 7) { - const omitted = 8 - a.length, omitStart = a.indexOf(''), omitEnd = omitStart + omitted - for (i = 7; i >= omitStart; i--) { - a[i] = i > omitEnd ? a[i - omitted] : 0 - } - } - - var r = 0n - for (i = 0; i < 4; i++) { - if (a[i]) { - r += BigInt(parseInt(a[i], 16)) << BigInt(16 * (3 - i)) - } - } - return r -} - -const v4MappedReg = /^(?:0:0:0:0:0|:):ffff:(\d+\.\d+\.\d+\.\d+)$/i -export const v4Mapped = (addr) => { - const match = v4MappedReg.exec(addr) - return match && match[1] -} - -const PrivateIpRegList = [ - /^10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/, - /^192\.168\.([0-9]{1,3})\.([0-9]{1,3})/, - /^172\.16\.([0-9]{1,3})\.([0-9]{1,3})/, - /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/, - /^169\.254\.([0-9]{1,3})\.([0-9]{1,3})/, - /^fc00:/, - /^fe80:/ -] - -export const isPrivateIP = function(addr) { - for(const reg of PrivateIpRegList){ - if(reg.test(addr)){ - return true - } - } - return false -} - -export const strToNum37 = (a) => { - var num = 0 - for(var i = 0; i < a.length; i++){ - num = num * 37 + parseInt(a[i], 36) + 1 - } - return num -} - -export const num37ToStr = (num) => { - var str = '' - while(num > 0){ - str = (num % 37 - 1).toString(36) + str - num = Math.floor(num / 37) - } - return str.toUpperCase() -} - -export const getZeroFill = (num, len) => { - return '0'.repeat(len - num.length) + num -} - -const getUnderberFill = (num, len) => { - if(num.length > len) return num - return '_'.repeat(len - num.length) + num -} -export const numberToDir = (num) => { - return getUnderberFill(num.toString(36), 2) -} -export const getSmallMemoryFile = (line, db, isTmp) => { - const dbNumber = line / db.folderLineMax | 0 - const fileNumber = (line - dbNumber * db.folderLineMax) / db.fileLineMax | 0 - const lineOffset = line - dbNumber * db.folderLineMax - fileNumber * db.fileLineMax - var dir = path.join(db.name + (isTmp ? '-tmp' : ''), getUnderberFill(dbNumber.toString(36), 2)) - return [dir, getUnderberFill(fileNumber.toString(36), 2), lineOffset * db.recordSize] -} - -const isPostNumReg = /^\d+$/ -const isPostNumReg2 = /^(\d+)[-\s](\d+)$/ -const isPostStrReg = /^([A-Z\d]+)$/ -const isPostStrReg2 = /^([A-Z\d]+)[-\s]([A-Z\d]+)$/ -export const getPostcodeDatabase = (postcode) => { - if(!postcode) return [0, 0]; - // number type - if(isPostNumReg.test(postcode)){ - return [ - postcode.length, // 1~9 - parseInt(postcode, 10) // 0~999999999 - ] - } - var r = isPostNumReg2.exec(postcode) - if(r){ - return [ - parseInt(r[1].length + '' + r[2].length, 10), // 11~66 - parseInt(r[1] + r[2], 10) // 0~999999999 - ] - } - - // string type - r = isPostStrReg.exec(postcode) - if(r){ - var num = parseInt(postcode, 36) - if(num < Math.pow(2, 32)){ - return [ - -postcode.length, // -1~-9 - num - ] - } else { - return [ - parseInt('2' + postcode.slice(0, 1), 36), // 72~107, - parseInt(postcode.slice(1), 36) // 0~2176782335 MAX: 6char ZZZZZZ - ] - } - } - - r = isPostStrReg2.exec(postcode) - if(!r){ - console.log('Invalid postcode:', postcode) - } - return [ - - parseInt(r[1].length + "" + r[2].length, 10),// -11~-55 - parseInt(r[1] + r[2], 36) // 0~2176782335 MAX: 6char ZZZZZZ - ] -} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..47d2ae8 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,4 @@ +{ + "extends": "@total-typescript/tsconfig/tsc/dom/library-monorepo", + "exclude": ["**/**/*.config.ts", "**/*.test.ts", "**/*/dist"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8bdeb8f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "references": [ + { + "path": "./packages/ip-location-api/tsconfig.json" + }, + { + "path": "./packages/country/tsconfig.json" + }, + { + "path": "./packages/country-extra/tsconfig.json" + }, + { + "path": "./packages/geocode/tsconfig.json" + }, + { + "path": "./packages/geocode-extra/tsconfig.json" + } + ], + "files": [] +} diff --git a/types/cjs/main.d.cts b/types/cjs/main.d.cts deleted file mode 100644 index adea463..0000000 --- a/types/cjs/main.d.cts +++ /dev/null @@ -1,84 +0,0 @@ -import type { ICountry, TContinentCode, TContinents, TCountryCode } from "countries-list"; - -/** -* Basic lookup result object. -* -* All fields are made optional here, as available fields might differ based your settings. -* -* Requires runtime null-checks. -*/ -export type LookupResult = { - latitude?: number; - longitude?: number; - postcode?: string; - area?: string; - country?: TCountryCode; - eu?: boolean; - region1?: string; - region1_name?: string; - region2?: string; - region2_name?: string; - metro?: number; - timezone?: string; - city?: string; - - country_name?: ICountry["name"]; - country_native?: ICountry["native"]; - continent?: ICountry["continent"]; - continent_name?: TContinents[TContinentCode]; - capital?: ICountry["capital"]; - phone?: ICountry["phone"]; - currency?: ICountry["currency"]; - languages?: ICountry["languages"]; -}; - -/** - * lookup ip address [Sync / Async] - * @param {string} ip - ipv4 or ipv6 formatted address - * @return location information as either object or promise (based on settings), or null if not found. - */ -export const lookup: (ip: string) => LookupResult | Promise | null; -/** - * setup database without reload - * @param {object} [_setting] - * @return {void} - */ -export const setupWithoutReload: (_setting?: {}) => void; -/** - * clear in-memory database - * @type {function} - * @return {void} - */ -export const clear: Function; -/** - * reload in-memory database - * @type {function} - * @param {object} [_setting] - * @param {boolean} [sync] - sync mode - * @param {boolean} [_runningUpdate] - if it's running update [internal use] - * @return {Promise|void} - */ -export const reload: Function; -/** - * Watch database directory. - * When database file is updated, it reload the database automatically - * This causes error if you use ILA_SMALL_MEMORY=true - * @type {function} - * @param {string} [name] - name of watch. If you want to watch multiple directories, you can set different name for each directory - */ -export const watchDb: Function; -/** - * Stop watching database directory - * @type {function} - * @param {string} [name] - */ -export const stopWatchDb: Function; -/** - * Update database and auto reload database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [noReload] - if you don't want to reload the database after update - * @param {boolean} [sync] - if you want to update the database in sync mode - * @return {Promise} - true if database is updated, false if no need to update - */ -export const updateDb: Function; diff --git a/types/src/main.d.mts b/types/src/main.d.mts deleted file mode 100644 index bd7df98..0000000 --- a/types/src/main.d.mts +++ /dev/null @@ -1,84 +0,0 @@ -import type { ICountry, TContinentCode, TContinents, TCountryCode } from "countries-list"; - -/** -* Basic lookup result object. -* -* All fields are made optional here, as available fields might differ based your settings. -* -* Requires runtime null-checks. -*/ -export type LookupResult = { - latitude?: number; - longitude?: number; - postcode?: string; - area?: string; - country?: TCountryCode; - eu?: boolean; - region1?: string; - region1_name?: string; - region2?: string; - region2_name?: string; - metro?: number; - timezone?: string; - city?: string; - - country_name?: ICountry["name"]; - country_native?: ICountry["native"]; - continent?: ICountry["continent"]; - continent_name?: TContinents[TContinentCode]; - capital?: ICountry["capital"]; - phone?: ICountry["phone"]; - currency?: ICountry["currency"]; - languages?: ICountry["languages"]; -}; - -/** - * lookup ip address [Sync / Async] - * @param {string} ip - ipv4 or ipv6 formatted address - * @return location information as either object or promise (based on settings), or null if not found. - */ -export const lookup: (ip: string) => LookupResult | Promise | null; -/** - * setup database without reload - * @param {object} [_setting] - * @return {void} - */ -export const setupWithoutReload: (_setting?: {}) => void; -/** - * clear in-memory database - * @type {function} - * @return {void} - */ -export const clear: Function; -/** - * reload in-memory database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [sync] - sync mode - * @param {boolean} [_runningUpdate] - if it's running update [internal use] - * @return {Promise|void} - */ -export const reload: Function; -/** - * Watch database directory. - * When database file is updated, it reload the database automatically - * This causes error if you use ILA_SMALL_MEMORY=true - * @type {function} - * @param {string} [name] - name of watch. If you want to watch multiple directories, you can set different name for each directory - */ -export const watchDb: Function; -/** - * Stop watching database directory - * @type {function} - * @param {string} [name] - */ -export const stopWatchDb: Function; -/** - * Update database and auto reload database - * @type {function} - * @param {object} [_setting] - if you need to update the database with different setting - * @param {boolean} [noReload] - if you don't want to reload the database after update - * @param {boolean} [sync] - if you want to update the database in sync mode - * @return {Promise} - true if database is updated, false if no need to update - */ -export const updateDb: Function; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..97d2f95 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { coverageConfigDefaults, defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + reporter: ['json-summary', 'text', 'html'], + exclude: ['**/*.config.ts', '**/dist/**', ...coverageConfigDefaults.exclude], + reportOnFailure: true, + }, + }, +}) diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000..b18a56c --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,5 @@ +import { defineWorkspace } from 'vitest/config' + +export default defineWorkspace([ + 'packages/*', +])