From 83bc61c1a40066f6707d7912562a6ff6ea0cb5f8 Mon Sep 17 00:00:00 2001 From: Adam Burgess Date: Thu, 26 Dec 2024 18:16:46 +1100 Subject: [PATCH] initial commit --- .dockerignore | 5 + .gitignore | 4 + Caddyfile | 6 + Dockerfile | 21 + compose.yml | 17 + deploy | 6 + package.json | 25 + pnpm-lock.yaml | 1158 +++++++++++++++++++++++++++++++++++++++++++++++ src/database.ts | 77 ++++ src/main.tsx | 634 ++++++++++++++++++++++++++ src/pubg-api.ts | 132 ++++++ tsconfig.json | 14 + 12 files changed, 2099 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 compose.yml create mode 100755 deploy create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/database.ts create mode 100644 src/main.tsx create mode 100644 src/pubg-api.ts create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..035ae13 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Dockerfile +Caddyfile +deploy +dist +node_modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2ba6e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.env +*.sqlite \ No newline at end of file diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..9830b01 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,6 @@ +pubg.adam.id.au { + import ssl-cloudflare-dns + import strict-transport-security + + reverse_proxy http://pubg:3000 +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dde5d71 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +from aburgess/node:22-npm as deps + +workdir /app +add package.json pnpm-lock.yaml ./ +run pnpm install --prod + +from deps as builder + +run pnpm install +add . . +run nrr build + +from aburgess/node:22 + +workdir /app +run apk add --no-cache tzdata +add package.json ./ +copy --from=deps /app/node_modules /app/node_modules +copy --from=builder /app/dist /app/dist + +entrypoint ["node", "/app/dist/main.js"] diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..e519312 --- /dev/null +++ b/compose.yml @@ -0,0 +1,17 @@ +name: pubg + +services: + pubg: + build: . + container_name: pubg + hostname: pubg + restart: always + init: true + env_file: + - .env + volumes: + - /home/adam/d:/data +networks: + default: + name: platform + external: true diff --git a/deploy b/deploy new file mode 100755 index 0000000..226e828 --- /dev/null +++ b/deploy @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +DOCKER_HOST=ssh://adam@adam.id.au docker compose up -d --build +DEPLOY_PROJECT=pubg DEPLOY_CADDYFILE=Caddyfile node /tmp/client.mjs diff --git a/package.json b/package.json new file mode 100644 index 0000000..b3cac64 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "pubg", + "private": true, + "type": "module", + "dependencies": { + "@adamburgess/linq": "^3.0.0", + "async-mutex": "^0.5.0", + "better-sqlite3": "^11.7.0", + "change-case": "^5.4.4", + "dayjs": "^1.11.13", + "fastify": "^5.2.0", + "knex": "^3.1.0", + "preact": "^10.25.3", + "preact-render-to-string": "^6.5.12" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.12", + "@types/node": "^22.10.2", + "esbuild": "^0.24.2", + "typescript": "^5.7.2" + }, + "scripts": { + "build": "esbuild src/*.ts src/*.tsx --outdir=dist --format=esm --platform=node" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..0079226 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1158 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@adamburgess/linq': + specifier: ^3.0.0 + version: 3.0.0 + async-mutex: + specifier: ^0.5.0 + version: 0.5.0 + better-sqlite3: + specifier: ^11.7.0 + version: 11.7.0 + change-case: + specifier: ^5.4.4 + version: 5.4.4 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + fastify: + specifier: ^5.2.0 + version: 5.2.0 + knex: + specifier: ^3.1.0 + version: 3.1.0(better-sqlite3@11.7.0) + preact: + specifier: ^10.25.3 + version: 10.25.3 + preact-render-to-string: + specifier: ^6.5.12 + version: 6.5.12(preact@10.25.3) + devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.12 + version: 7.6.12 + '@types/node': + specifier: ^22.10.2 + version: 22.10.2 + esbuild: + specifier: ^0.24.2 + version: 0.24.2 + typescript: + specifier: ^5.7.2 + version: 5.7.2 + +packages: + + '@adamburgess/linq@3.0.0': + resolution: {integrity: sha512-3AXhQK8owD+bb991XmK7gosuX01lv7L8bfj2jMbBkhrsxEyfH3Zc7daY4lQxmKUABmIg5ZdVvO0U1C70b3BhQA==} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fastify/ajv-compiler@4.0.1': + resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==} + + '@fastify/error@4.0.0': + resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==} + + '@fastify/fast-json-stringify-compiler@5.0.1': + resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==} + + '@fastify/merge-json-schemas@0.1.1': + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + + '@types/better-sqlite3@7.6.12': + resolution: {integrity: sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==} + + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@11.7.0: + resolution: {integrity: sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + colorette@2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stringify@6.0.0: + resolution: {integrity: sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-uri@2.4.0: + resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} + + fast-uri@3.0.3: + resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + + fastify@5.2.0: + resolution: {integrity: sha512-3s+Qt5S14Eq5dCpnE0FxTp3z4xKChI83ZnMv+k0FwX+VUoZrgCFoLAxpfdi/vT4y6Mk+g7aAMt9pgXDoZmkefQ==} + + fastq@1.18.0: + resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + find-my-way@9.1.0: + resolution: {integrity: sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==} + engines: {node: '>=14'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + getopts@2.3.0: + resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + interpret@2.2.0: + resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} + engines: {node: '>= 0.10'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + json-schema-ref-resolver@1.0.1: + resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + knex@3.1.0: + resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} + engines: {node: '>=16'} + hasBin: true + peerDependencies: + better-sqlite3: '*' + mysql: '*' + mysql2: '*' + pg: '*' + pg-native: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + better-sqlite3: + optional: true + mysql: + optional: true + mysql2: + optional: true + pg: + optional: true + pg-native: + optional: true + sqlite3: + optional: true + tedious: + optional: true + + light-my-request@6.4.0: + resolution: {integrity: sha512-U0UONITz4GVQodMPoygnqJan2RYfhyLsCzFBakJHWNfiQKyHzvp38YOxxLGs8lIDPwR6ngd4gmuZJQQJtRBu/A==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + node-abi@3.71.0: + resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} + engines: {node: '>=10'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.6.0: + resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} + hasBin: true + + preact-render-to-string@6.5.12: + resolution: {integrity: sha512-FpU7/cRipZo4diSWQq7gZWVp+Px76CtVduJZNvQwVzynDsAIxKteMrjCCGPbM2oEasReoDffaeMCMlaur9ohIg==} + peerDependencies: + preact: '>=10' + + preact@10.25.3: + resolution: {integrity: sha512-dzQmIFtM970z+fP9ziQ3yG4e3ULIbwZzJ734vaMVUTaKQ2+Ru1Ou/gjshOYVHCcd1rpAelC6ngjvjDXph98unQ==} + + prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + engines: {node: '>=10'} + hasBin: true + + process-warning@4.0.0: + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.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@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@4.0.0: + resolution: {integrity: sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + secure-json-parse@3.0.1: + resolution: {integrity: sha512-9QR7G96th4QJ2+dJwvZB+JoXyt8PN+DbEjOr6kL2/JU4KH8Eb2sFdU+gt8EDdzWDWoWH0uocDdfCoFzdVSixUA==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tarn@3.0.2: + resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} + engines: {node: '>=8.0.0'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tildify@2.0.0: + resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} + engines: {node: '>=8'} + + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + '@adamburgess/linq@3.0.0': {} + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@fastify/ajv-compiler@4.0.1': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + + '@fastify/error@4.0.0': {} + + '@fastify/fast-json-stringify-compiler@5.0.1': + dependencies: + fast-json-stringify: 6.0.0 + + '@fastify/merge-json-schemas@0.1.1': + dependencies: + fast-deep-equal: 3.1.3 + + '@types/better-sqlite3@7.6.12': + dependencies: + '@types/node': 22.10.2 + + '@types/node@22.10.2': + dependencies: + undici-types: 6.20.0 + + abstract-logging@2.0.1: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + + atomic-sleep@1.0.0: {} + + avvio@9.1.0: + dependencies: + '@fastify/error': 4.0.0 + fastq: 1.18.0 + + base64-js@1.5.1: {} + + better-sqlite3@11.7.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + change-case@5.4.4: {} + + chownr@1.1.4: {} + + colorette@2.0.19: {} + + commander@10.0.1: {} + + cookie@1.0.2: {} + + dayjs@1.11.13: {} + + debug@4.3.4: + dependencies: + ms: 2.1.2 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + detect-libc@2.0.3: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} + + esm@3.2.25: {} + + expand-template@2.0.3: {} + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stringify@6.0.0: + dependencies: + '@fastify/merge-json-schemas': 0.1.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-deep-equal: 3.1.3 + fast-uri: 2.4.0 + json-schema-ref-resolver: 1.0.1 + rfdc: 1.4.1 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-redact@3.5.0: {} + + fast-uri@2.4.0: {} + + fast-uri@3.0.3: {} + + fastify@5.2.0: + dependencies: + '@fastify/ajv-compiler': 4.0.1 + '@fastify/error': 4.0.0 + '@fastify/fast-json-stringify-compiler': 5.0.1 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.0.0 + find-my-way: 9.1.0 + light-my-request: 6.4.0 + pino: 9.6.0 + process-warning: 4.0.0 + proxy-addr: 2.0.7 + rfdc: 1.4.1 + secure-json-parse: 3.0.1 + semver: 7.6.3 + toad-cache: 3.7.0 + + fastq@1.18.0: + dependencies: + reusify: 1.0.4 + + file-uri-to-path@1.0.0: {} + + find-my-way@9.1.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 4.0.0 + + forwarded@0.2.0: {} + + fs-constants@1.0.0: {} + + function-bind@1.1.2: {} + + get-package-type@0.1.0: {} + + getopts@2.3.0: {} + + github-from-package@0.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + interpret@2.2.0: {} + + ipaddr.js@1.9.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + json-schema-ref-resolver@1.0.1: + dependencies: + fast-deep-equal: 3.1.3 + + json-schema-traverse@1.0.0: {} + + knex@3.1.0(better-sqlite3@11.7.0): + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.2.0 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + optionalDependencies: + better-sqlite3: 11.7.0 + transitivePeerDependencies: + - supports-color + + light-my-request@6.4.0: + dependencies: + cookie: 1.0.2 + process-warning: 4.0.0 + set-cookie-parser: 2.7.1 + + lodash@4.17.21: {} + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.2: {} + + napi-build-utils@1.0.2: {} + + node-abi@3.71.0: + dependencies: + semver: 7.6.3 + + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + path-parse@1.0.7: {} + + pg-connection-string@2.6.2: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.6.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 4.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + preact-render-to-string@6.5.12(preact@10.25.3): + dependencies: + preact: 10.25.3 + + preact@10.25.3: {} + + prebuild-install@7.1.2: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.71.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + + process-warning@4.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + quick-format-unescaped@4.0.4: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + real-require@0.2.0: {} + + rechoir@0.8.0: + dependencies: + resolve: 1.22.10 + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + ret@0.5.0: {} + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + safe-buffer@5.2.1: {} + + safe-regex2@4.0.0: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + + secure-json-parse@3.0.1: {} + + semver@7.6.3: {} + + set-cookie-parser@2.7.1: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + split2@4.2.0: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + tar-fs@2.1.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tarn@3.0.2: {} + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tildify@2.0.0: {} + + toad-cache@3.7.0: {} + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + typescript@5.7.2: {} + + undici-types@6.20.0: {} + + util-deprecate@1.0.2: {} + + wrappy@1.0.2: {} diff --git a/src/database.ts b/src/database.ts new file mode 100644 index 0000000..348df26 --- /dev/null +++ b/src/database.ts @@ -0,0 +1,77 @@ +import knex from 'knex'; +import type { MatchResponse, PlayerResponse } from './pubg-api.js'; +import dayjs from 'dayjs'; + +interface Player { + id: number + data: string + created_at: number +} + +interface Match { + match_id: string + data: string + created_at: number +} + +export async function Database() { + const db = knex({ + client: 'better-sqlite3', + connection: { + filename: 'DOCKER' in process.env ? '/data/pubg.sqlite' : './pubg.sqlite' + }, + useNullAsDefault: true + }); + + db.on('start', builder => { + console.log(builder.toQuery()); + }) + + if (!await db.schema.hasTable('player')) { + await db.schema.createTable('player', table => { + table.increments('id').primary(); + table.json('data'); + table.integer('created_at').notNullable(); + }); + } + + if (!await db.schema.hasTable('match')) { + await db.schema.createTable('match', table => { + table.string('match_id').primary(); + table.json('data'); + table.integer('created_at').notNullable(); + }); + } + + return new class Database { + async getCachedPlayer() { + const sec = dayjs().add(-10, 'seconds').unix(); + + const r = await db('player').where('created_at', '>', sec).first(); + console.log('cached player response', r); + if (r) return JSON.parse(r.data) as PlayerResponse; + } + + async savePlayer(player: PlayerResponse) { + await db('player').insert({ + data: JSON.stringify(player), + created_at: dayjs().unix() + }); + console.log('saved cached player response', player); + } + + async getMatch(match_id: string) { + const match = await db('match').where('match_id', match_id).first(); + if (match) return JSON.parse(match.data) as MatchResponse; + } + + async saveMatch(match: MatchResponse) { + const match_id = match.data.id; + await db('match').insert({ + match_id, + data: JSON.stringify(match), + created_at: dayjs().unix() + }).onConflict('match_id').ignore(); + } + } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..1614715 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,634 @@ +import Fastify from 'fastify' +import { Database } from './database.js'; +import { MatchResponse, PlayerResponse, PubgApi } from './pubg-api.js'; +import { Mutex } from 'async-mutex'; +import { render, renderToStringAsync } from 'preact-render-to-string'; +import { h, Fragment } from 'preact'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc.js' +import relativeTime from 'dayjs/plugin/relativeTime.js' +dayjs.extend(utc); +dayjs.extend(relativeTime); + +const fastify = Fastify({ + logger: true +}); + +const db = await Database(); +const api = new PubgApi(); + + +const playerMutex = new Mutex(); +async function getCachedPlayers() { + return await playerMutex.runExclusive(async () => { + const playerNames = ['Pigophone', 'The-High-Ground', 'Tokar-TK']; + + const cached = await db.getCachedPlayer(); + if (cached) return cached; + + const players = await api.getPlayers(playerNames); + await db.savePlayer(players); + + return players; + }); +} +async function getMatch(id: string) { + const cached = await db.getMatch(id); + if (cached) return cached; + + const match = await api.getMatch(id); + await db.saveMatch(match); + + return match; +} + +function getBotCount(match: MatchResponse) { + // bots have account id starting with 'ai.' + let playerCount = 0; + let botCount = 0; + for (const player of match.included) { + if (player.type === 'participant') { + playerCount++; + if (player.attributes.stats.playerId.startsWith('ai.')) { + botCount++; + } + } + } + return { playerCount, botCount, humanCount: playerCount - botCount }; +} + +function getChickenDinnerUrl(match: MatchResponse, player: PlayerResponse['data'][0]) { + const asset = match.included.find(i => i.type === 'asset')!; + const url = asset.attributes.URL.substring('https://telemetry-cdn.pubg.com/bluehole-pubg/'.length).replace('-telemetry.json', ''); + return `https://chickendinner.gg/${url}?follow=${player.attributes.name}`; +} + +function getRank(match: MatchResponse, playerId: string) { + for (const player of match.included) { + if (player.type === 'participant' && player.attributes.stats.playerId === playerId) { + return player.attributes.stats.winPlace; + } + } + return 'N/A'; +} + +function MatchesHtml({ players, matches }: { players: PlayerResponse, matches: Record }) { + return ( + + + Matches + + + + +

Matches

+ {players.data.map(player => ( + <> +

+ {player.attributes.name} +

+
+ {player.relationships.matches.data.map(match => { + var m = matches[match.id]; + const playerName = player.attributes.name; + var map = PubgApi.mapName(m.data.attributes.mapName); + var rank = getRank(m, player.id); + var count = getBotCount(m); + const date = dayjs(m.data.attributes.createdAt); + + return
+
    +
  • Map: {map}
  • +
  • Mode: {PubgApi.modeName(m.data.attributes.gameMode)}
  • +
  • Date: {date.format('ddd DD/MM HH:mm')} ({date.fromNow()})
  • +
  • {playerName}'s rank: {rank}
  • +
  • Players: {count.humanCount} (bots: {count.botCount} - {`${Math.round(count.botCount / count.playerCount * 100)}%`})
  • +
  • Links: + Raw + PUBGLookup + map replay +
  • +
+
+ })} +
+ + ))} + + + ); +} + + +fastify.get('/matches', async (req, res) => { + const players = await getCachedPlayers(); + + const matches: Record = {}; + const matchIds = new Set(); + for (const player of players.data) { + for (const match of player.relationships.matches.data) { + matchIds.add(match.id); + } + } + await Promise.all([...matchIds].map(async id => { + matches[id] = await getMatch(id); + })); + + const html = render(); + + res.header('Content-Type', 'text/html'); + + return html; +}); + +fastify.get<{ Params: { id: string } }>('/match/:id', async (req, res) => { + const match = await getMatch(req.params.id); + return match; +}); + +fastify.get('/style.css', async (req, res) => { + res.header('Content-Type', 'text/css'); + return ` + * { + + box-sizing: border-box; + } + body { + font-family: Open Sans, Arial; + color: #454545; + font-size: 16px; + margin: 2em auto; + max-width: 1600px; + padding: 1em; + line-height: 1.4; + -webkit-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto +} + +html.contrast body { + color: #050505 +} + +html.contrast blockquote { + color: #11151a +} + +html.contrast blockquote:before { + color: #262626 +} + +html.contrast a { + color: #03f +} + +html.contrast a:visited { + color: #7d013e +} + +html.contrast span.wr { + color: #800 +} + +html.contrast span.mfw { + color: #4d0000 +} + +@media screen and (prefers-color-scheme:light) { + html.inverted { + background-color: #000 + } + + html.inverted body { + color: #d9d9d9 + } + + html.inverted #contrast, + html.inverted #invmode { + color: #fff; + background-color: #000 + } + + html.inverted blockquote { + color: #d3c9be + } + + html.inverted blockquote:before { + color: #b8b8b8 + } + + html.inverted a { + color: #00a2e7 + } + + html.inverted a:visited { + color: #ca1a70 + } + + html.inverted span.wr { + color: #d24637 + } + + html.inverted span.mfw { + color: #b00000 + } + + html.inverted.contrast { + background-color: #000 + } + + html.inverted.contrast body { + color: #fff + } + + html.inverted.contrast #contrast, + html.inverted.contrast #invmode { + color: #fff; + background-color: #000 + } + + html.inverted.contrast blockquote { + color: #f8f6f5 + } + + html.inverted.contrast blockquote:before { + color: #e5e5e5 + } + + html.inverted.contrast a { + color: #44c7ff + } + + html.inverted.contrast a:visited { + color: #e9579e + } + + html.inverted.contrast span.wr { + color: #db695d + } + + html.inverted.contrast span.mfw { + color: #ff0d0d + } +} + +@media (prefers-color-scheme:dark) { + html:not(.inverted) { + background-color: #000 + } + + html:not(.inverted) body { + color: #d9d9d9 + } + + html:not(.inverted) #contrast, + html:not(.inverted) #invmode { + color: #fff; + background-color: #000 + } + + html:not(.inverted) blockquote { + color: #d3c9be + } + + html:not(.inverted) blockquote:before { + color: #b8b8b8 + } + + html:not(.inverted) a { + color: #00a2e7 + } + + html:not(.inverted) a:visited { + color: #ca1a70 + } + + html:not(.inverted) span.wr { + color: #d24637 + } + + html:not(.inverted) span.mfw { + color: #b00000 + } + + html:not(.inverted).contrast { + background-color: #000 + } + + html:not(.inverted).contrast body { + color: #fff + } + + html:not(.inverted).contrast #contrast, + html:not(.inverted).contrast #invmode { + color: #fff; + background-color: #000 + } + + html:not(.inverted).contrast blockquote { + color: #f8f6f5 + } + + html:not(.inverted).contrast blockquote:before { + color: #e5e5e5 + } + + html:not(.inverted).contrast a { + color: #44c7ff + } + + html:not(.inverted).contrast a:visited { + color: #e9579e + } + + html:not(.inverted).contrast span.wr { + color: #db695d + } + + html:not(.inverted).contrast span.mfw { + color: #ff0d0d + } + + html.inverted html { + background-color: #fefefe + } +} + +a { + color: #07a +} + +a:visited { + color: #941352 +} + +.noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +span.citneed { + vertical-align: top; + font-size: .7em; + padding-left: .3em +} + +small { + font-size: .4em +} + +p.st { + margin-top: -1em +} + +div.fancyPositioning div.picture-left { + float: left; + width: 40%; + overflow: hidden; + margin-right: 1em +} + +div.fancyPositioning div.picture-left img { + width: 100% +} + +div.fancyPositioning div.picture-left figure { + margin: 10px +} + +div.fancyPositioning div.picture-left figure figcaption { + font-size: .7em +} + +div.fancyPositioning div.tleft { + float: left; + width: 55% +} + +div.fancyPositioning div.tleft p:first-child { + margin-top: 0 +} + +div.fancyPositioning:after { + display: block; + content: ""; + clear: both +} + +ul li img { + height: 1em +} + +blockquote { + color: #456; + margin-left: 0; + margin-top: 2em; + margin-bottom: 2em +} + +blockquote span { + float: left; + margin-left: 1rem; + padding-top: 1rem +} + +blockquote author { + display: block; + clear: both; + font-size: .6em; + margin-left: 2.4rem; + font-style: oblique +} + +blockquote author:before { + content: "- "; + margin-right: 1em +} + +blockquote:before { + font-family: Times New Roman, Times, Arial; + color: #666; + content: open-quote; + font-size: 2.2em; + font-weight: 600; + float: left; + margin-top: 0; + margin-right: .2rem; + width: 1.2rem +} + +blockquote:after { + content: ""; + display: block; + clear: both +} + +@media screen and (max-width:500px) { + body { + text-align: left + } + + div.fancyPositioning div.picture-left, + div.fancyPositioning div.tleft { + float: none; + width: inherit + } + + blockquote span { + width: 80% + } + + blockquote author { + padding-top: 1em; + width: 80%; + margin-left: 15% + } + + blockquote author:before { + content: ""; + margin-right: inherit + } +} + +span.visited { + color: #941352 +} + +span.visited-maroon { + color: #85144b +} + +span.wr { + color: #c0392b; + font-weight: 600 +} + +button.cont-inv, +span.wr { + text-decoration: underline +} + +button.cont-inv { + cursor: pointer; + border-radius: 2px; + position: fixed; + right: 10px; + font-size: .8em; + border: 0; + padding: 2px 5px +} + +#contrast { + color: #000; + top: 10px +} + +#contrast, +#invmode { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +#invmode { + color: #fff; + background-color: #000; + position: fixed; + top: 34px; + text-decoration: underline +} + +@media screen and (max-width:1080px) { + + #contrast, + #invmode { + position: absolute + } +} + +span.sb { + color: #00e +} + +span.sb, +span.sv { + cursor: not-allowed +} + +span.sv { + color: #551a8b +} + +span.foufoufou { + color: #444; + font-weight: 700 +} + +span.foufoufou:before { + content: ""; + display: inline-block; + width: 1em; + height: 1em; + margin-left: .2em; + margin-right: .2em; + background-color: #444 +} + +span.foufivfoufivfoufiv { + color: #454545; + font-weight: 700 +} + +span.foufivfoufivfoufiv:before { + content: ""; + display: inline-block; + width: 1em; + height: 1em; + margin-left: .2em; + margin-right: .2em; + background-color: #454545 +} + +span.mfw { + color: #730000 +} + +a.kopimi, +a.kopimi img.kopimi { + display: block; + margin-left: auto; + margin-right: auto +} + +a.kopimi img.kopimi { + height: 2em +} + +p.fakepre { + font-family: monospace; + font-size: .9em +} + `; +}); + +// Run the server! +try { + await fastify.listen({ port: 3000, host: '0.0.0.0' }) +} catch (err) { + fastify.log.error(err) + process.exit(1) +} diff --git a/src/pubg-api.ts b/src/pubg-api.ts new file mode 100644 index 0000000..b8f44cf --- /dev/null +++ b/src/pubg-api.ts @@ -0,0 +1,132 @@ +import * as changeCase from 'change-case'; + +const API_KEY = process.env.API_KEY; + +function getAcceptHeaders() { + return { + 'Accept': 'application/vnd.api+json' + } +} + +function getAuthHeaders() { + return { + 'Authorization': `Bearer ${API_KEY}`, + ...getAcceptHeaders() + } +} + +export interface PlayerResponse { + data: { + id: string + attributes: { + name: string + } + relationships: { + matches: Relationship + } + }[] +} + +interface Relationship { + data: { + id: string + type: string + }[] +} + +export interface MatchResponse { + data: { + id: string + attributes: { + mapName: string + duration: number + gameMode: string + createdAt: string + }, + relationships: { + rosters: Relationship + assets: Relationship + } + }, + included: (Participant | Roster | Asset)[] +} + +export interface Participant { + type: 'participant' + id: string + attributes: { + stats: { + playerId: string + name: string + winPlace: number + } + } +} +export interface Roster { + type: 'roster' + id: string + attributes: { + stats: { + rank: number + teamId: number + } + won: string + } + relationships: { + participants: Relationship + } +} +export interface Asset { + type: 'asset' + id: string + attributes: { + createdAt: string + URL: string + } +} + +const mapLookup: Record = { + "Baltic_Main": "Erangel", + "Chimera_Main": "Paramo", + "Desert_Main": "Miramar", + "DihorOtok_Main": "Vikendi", + "Erangel_Main": "Erangel", + "Heaven_Main": "Haven", + "Kiki_Main": "Deston", + "Range_Main": "Camp Jackal", + "Savage_Main": "Sanhok", + "Summerland_Main": "Karakin", + "Tiger_Main": "Taego", + "Neon_Main": "Rondo" +} + +export class PubgApi { + async getPlayers(playerNames: string[]) { + const result = await fetch('https://api.pubg.com/shards/steam/players?filter[playerNames]=' + playerNames.join(','), { + headers: getAuthHeaders() + }); + if (!result.ok) throw new Error('cant fetch players'); + const json = await result.json() as PlayerResponse; + return json; + } + + async getMatch(id: string) { + const result = await fetch('https://api.pubg.com/shards/steam/matches/' + id, { + headers: getAcceptHeaders() + }); + if (!result.ok) throw new Error('cant fetch'); + const json = await result.json() as MatchResponse; + return json; + } + + static mapName(name: string) { + return mapLookup[name]; + } + + static modeName(name: string) { + console.log(name); + let [team, per] = name.split('-'); + if(!per) per = 'tpp'; + return per.toUpperCase() + ' ' + changeCase.capitalCase(team); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a9c4218 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16", + "strict": true, + "target": "esnext", + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + }, + "include": [ + "src" + ] +}