mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-03-21 18:04:10 -05:00
Presence embeds
This commit is contained in:
parent
f122ef8c92
commit
cbf6e257be
|
|
@ -24,6 +24,7 @@ RUN npm ci --production
|
|||
|
||||
COPY bin /app/bin
|
||||
COPY resources /app/resources
|
||||
COPY resources/cli/fonts /usr/local/share/fonts
|
||||
COPY --from=build /app/dist /app/dist
|
||||
|
||||
RUN ln -s /app/bin/nxapi.js /usr/local/bin/nxapi
|
||||
|
|
|
|||
763
package-lock.json
generated
763
package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
|||
"node-notifier": "^10.0.1",
|
||||
"node-persist": "^3.1.3",
|
||||
"read": "^3.0.0",
|
||||
"sharp": "^0.33.1",
|
||||
"splatnet3-types": "^0.2.20231119210145",
|
||||
"supports-color": "^8.1.1",
|
||||
"tslib": "^2.6.2",
|
||||
|
|
@ -2682,6 +2683,15 @@
|
|||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "0.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz",
|
||||
"integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
|
||||
|
|
@ -2707,6 +2717,437 @@
|
|||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"macos": ">=11",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"macos": ">=10.13",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz",
|
||||
"integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz",
|
||||
"integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz",
|
||||
"integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz",
|
||||
"integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.28",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"glibc": ">=2.26",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"musl": ">=1.2.2",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz",
|
||||
"integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^0.44.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz",
|
||||
"integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"pnpm": ">=7.1.0",
|
||||
"yarn": ">=3.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/create-cache-key-function": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
|
||||
|
|
@ -5084,6 +5525,18 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -5100,6 +5553,15 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||
|
|
@ -5655,6 +6117,14 @@
|
|||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
||||
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
|
|
@ -10108,6 +10578,59 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
|
||||
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.2",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"libvips": ">=8.15.0",
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.1",
|
||||
"@img/sharp-darwin-x64": "0.33.1",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
|
||||
"@img/sharp-linux-arm": "0.33.1",
|
||||
"@img/sharp-linux-arm64": "0.33.1",
|
||||
"@img/sharp-linux-s390x": "0.33.1",
|
||||
"@img/sharp-linux-x64": "0.33.1",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.1",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.1",
|
||||
"@img/sharp-wasm32": "0.33.1",
|
||||
"@img/sharp-win32-ia32": "0.33.1",
|
||||
"@img/sharp-win32-x64": "0.33.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -10164,6 +10687,19 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||
|
|
@ -13131,6 +13667,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@emnapi/runtime": {
|
||||
"version": "0.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz",
|
||||
"integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@fastify/busboy": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
|
||||
|
|
@ -13153,6 +13698,147 @@
|
|||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz",
|
||||
"integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz",
|
||||
"integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz",
|
||||
"integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz",
|
||||
"integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-linux-arm": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz",
|
||||
"integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-s390x": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz",
|
||||
"integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz",
|
||||
"integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-wasm32": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz",
|
||||
"integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/runtime": "^0.44.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-win32-ia32": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz",
|
||||
"integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-win32-x64": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz",
|
||||
"integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==",
|
||||
"optional": true
|
||||
},
|
||||
"@jest/create-cache-key-function": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
|
||||
|
|
@ -15061,6 +15747,15 @@
|
|||
"mimic-response": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -15074,6 +15769,15 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||
|
|
@ -15520,6 +16224,11 @@
|
|||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
||||
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="
|
||||
},
|
||||
"detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
|
|
@ -19065,6 +19774,45 @@
|
|||
"kind-of": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
|
||||
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
|
||||
"requires": {
|
||||
"@img/sharp-darwin-arm64": "0.33.1",
|
||||
"@img/sharp-darwin-x64": "0.33.1",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
|
||||
"@img/sharp-linux-arm": "0.33.1",
|
||||
"@img/sharp-linux-arm64": "0.33.1",
|
||||
"@img/sharp-linux-s390x": "0.33.1",
|
||||
"@img/sharp-linux-x64": "0.33.1",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.1",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.1",
|
||||
"@img/sharp-wasm32": "0.33.1",
|
||||
"@img/sharp-win32-ia32": "0.33.1",
|
||||
"@img/sharp-win32-x64": "0.33.1",
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.2",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -19109,6 +19857,21 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"simple-update-notifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
"node-notifier": "^10.0.1",
|
||||
"node-persist": "^3.1.3",
|
||||
"read": "^3.0.0",
|
||||
"sharp": "^0.33.1",
|
||||
"splatnet3-types": "^0.2.20231119210145",
|
||||
"supports-color": "^8.1.1",
|
||||
"tslib": "^2.6.2",
|
||||
|
|
|
|||
BIN
resources/cli/fonts/opensans-normal-400.ttf
Normal file
BIN
resources/cli/fonts/opensans-normal-400.ttf
Normal file
Binary file not shown.
BIN
resources/cli/fonts/opensans-normal-500.ttf
Normal file
BIN
resources/cli/fonts/opensans-normal-500.ttf
Normal file
Binary file not shown.
|
|
@ -2,12 +2,13 @@ import * as net from 'node:net';
|
|||
import * as os from 'node:os';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import express, { Request, Response } from 'express';
|
||||
import { fetch } from 'undici';
|
||||
import * as persist from 'node-persist';
|
||||
import { BankaraMatchSetting_schedule, CoopRule, CoopSetting_schedule, DetailFestRecordDetailResult, DetailVotingStatusResult, FestMatchSetting_schedule, FestRecordResult, FestState, FestTeam_schedule, FestTeam_votingStatus, FestVoteState, Fest_schedule, FriendListResult, FriendOnlineState, Friend_friendList, GraphQLSuccessResponse, KnownRequestId, LeagueMatchSetting_schedule, RegularMatchSetting_schedule, StageScheduleResult, XMatchSetting_schedule } from 'splatnet3-types/splatnet3';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { product, version } from '../util/product.js';
|
||||
import { git, product, version } from '../util/product.js';
|
||||
import Users, { CoralUser } from '../common/users.js';
|
||||
import { Friend } from '../api/coral-types.js';
|
||||
import SplatNet3Api, { PersistedQueryResult, RequestIdSymbol } from '../api/splatnet3.js';
|
||||
|
|
@ -22,6 +23,7 @@ import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js';
|
|||
import { getTitleIdFromEcUrl } from '../util/misc.js';
|
||||
import { getSettingForCoopRule, getSettingForVsMode } from '../discord/monitor/splatoon3.js';
|
||||
import { CoralApiInterface } from '../api/coral.js';
|
||||
import { PresenceEmbedFormat, getUserEmbedOptionsFromRequest, renderUserEmbedImage, renderUserEmbedSvg } from './util/presence-embed.js';
|
||||
|
||||
const debug = createDebug('cli:presence-server');
|
||||
const debugSplatnet3Proxy = createDebug('cli:presence-server:splatnet3-proxy');
|
||||
|
|
@ -31,7 +33,7 @@ interface AllUsersResult extends Friend {
|
|||
splatoon3?: Friend_friendList | null;
|
||||
splatoon3_fest_team?: (FestTeam_schedule & FestTeam_votingStatus) | null;
|
||||
}
|
||||
interface PresenceResponse {
|
||||
export interface PresenceResponse {
|
||||
friend: Friend;
|
||||
title: TitleResult | null;
|
||||
splatoon3?: Friend_friendList | null;
|
||||
|
|
@ -529,7 +531,8 @@ class Server extends HttpServer {
|
|||
app: express.Express;
|
||||
|
||||
titles = new Map</** NSA ID */ string, [TitleResult | null, /** updated */ number]>();
|
||||
readonly promise_image = new Map<string, Promise<string>>();
|
||||
readonly promise_image = new Map<string, Promise<string |
|
||||
readonly [name: string, data: Uint8Array, type: string]>>();
|
||||
|
||||
constructor(
|
||||
readonly storage: persist.LocalStorage,
|
||||
|
|
@ -565,6 +568,18 @@ class Server extends HttpServer {
|
|||
app.get('/api/presence/:user/events', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceStreamRequest(req, res, req.params.user)));
|
||||
|
||||
app.get('/api/presence/:user/image', this.createApiRequestHandler((req, res) =>
|
||||
this.handleUserImageRequest(req, res, req.params.user)));
|
||||
|
||||
app.get('/api/presence/:user/embed', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.SVG)));
|
||||
app.get('/api/presence/:user/embed.png', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.PNG)));
|
||||
app.get('/api/presence/:user/embed.jpeg', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.JPEG)));
|
||||
app.get('/api/presence/:user/embed.webp', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.WEBP)));
|
||||
|
||||
if (image_proxy_path?.baas) {
|
||||
this.image_proxy_path_baas = image_proxy_path.baas;
|
||||
app.use('/api/presence/resources/baas', express.static(this.image_proxy_path_baas, {redirect: false}));
|
||||
|
|
@ -1097,6 +1112,55 @@ class Server extends HttpServer {
|
|||
}
|
||||
}
|
||||
|
||||
async handleUserImageRequest(req: Request, res: Response, presence_user_nsaid: string) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
const result = await this.handlePresenceRequest(req, null, presence_user_nsaid);
|
||||
|
||||
const url_map = await this.downloadImages({
|
||||
url: result.friend.imageUri,
|
||||
}, this.getResourceBaseUrls(req));
|
||||
|
||||
const image_url = url_map[result.friend.imageUri];
|
||||
|
||||
res.statusCode = 303;
|
||||
res.setHeader('Location', image_url);
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.write('Redirecting to ' + image_url + '\n');
|
||||
res.end();
|
||||
}
|
||||
|
||||
async handlePresenceEmbedRequest(req: Request, res: Response, presence_user_nsaid: string, format = PresenceEmbedFormat.SVG) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
const result = await this.handlePresenceRequest(req, null, presence_user_nsaid);
|
||||
|
||||
const {theme, friend_code, transparent} = getUserEmbedOptionsFromRequest(req);
|
||||
|
||||
const etag = createHash('sha256').update(JSON.stringify({
|
||||
result,
|
||||
theme,
|
||||
friend_code,
|
||||
transparent,
|
||||
v: version + '-' + git?.revision,
|
||||
})).digest('base64url');
|
||||
|
||||
if (req.headers['if-none-match'] === '"' + etag + '"' || req.headers['if-none-match'] === 'W/"' + etag + '"') {
|
||||
res.statusCode = 304;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const url_map = await this.downloadImages(result, this.getResourceBaseUrls(req));
|
||||
|
||||
const svg = renderUserEmbedSvg(result, url_map, theme, friend_code);
|
||||
const [image, type] = await renderUserEmbedImage(svg, format);
|
||||
|
||||
res.setHeader('Content-Type', type);
|
||||
res.setHeader('Etag', '"' + etag + '"');
|
||||
res.end(image);
|
||||
}
|
||||
|
||||
async handleSplatNet3ProxyFriends(req: Request, res: Response) {
|
||||
if (!this.enable_splatnet3_proxy) throw new ResponseError(403, 'forbidden');
|
||||
|
||||
|
|
@ -1154,6 +1218,37 @@ class Server extends HttpServer {
|
|||
atum: string | null;
|
||||
splatnet3: string | null;
|
||||
}): Promise<Record<string, string>> {
|
||||
const image_urls = this.getImageUrls(data, base_url);
|
||||
const url_map: Record<string, string> = {};
|
||||
|
||||
await Promise.all(image_urls.map(async ([url, dir, base_url]) => {
|
||||
url_map[url] = new URL(await this.downloadImage(url, dir), base_url).toString();
|
||||
}));
|
||||
|
||||
return url_map;
|
||||
}
|
||||
|
||||
async getImages(data: unknown, base_url: {
|
||||
baas: string | null;
|
||||
atum: string | null;
|
||||
splatnet3: string | null;
|
||||
}): Promise<Record<string, readonly [name: string, data: Uint8Array, type: string]>> {
|
||||
const image_urls = this.getImageUrls(data, base_url);
|
||||
const url_map: Record<string, readonly [name: string, data: Uint8Array, type: string]> = {};
|
||||
|
||||
await Promise.all(image_urls.map(async ([url, dir, base_url]) => {
|
||||
const [name, data, type] = await this.downloadImage(url, dir, true);
|
||||
url_map[url] = [new URL(name, base_url).toString(), data, type];
|
||||
}));
|
||||
|
||||
return url_map;
|
||||
}
|
||||
|
||||
getImageUrls(data: unknown, base_url: {
|
||||
baas: string | null;
|
||||
atum: string | null;
|
||||
splatnet3: string | null;
|
||||
}) {
|
||||
const image_urls: [url: string, dir: string, base_url: string][] = [];
|
||||
|
||||
// Use JSON.stringify to iterate over everything in the response
|
||||
|
|
@ -1185,13 +1280,7 @@ class Server extends HttpServer {
|
|||
return value;
|
||||
});
|
||||
|
||||
const url_map: Record<string, string> = {};
|
||||
|
||||
await Promise.all(image_urls.map(async ([url, dir, base_url]) => {
|
||||
url_map[url] = new URL(await this.downloadImage(url, dir), base_url).toString();
|
||||
}));
|
||||
|
||||
return url_map;
|
||||
return image_urls;
|
||||
}
|
||||
|
||||
getResourceBaseUrls(req: Request) {
|
||||
|
|
@ -1206,7 +1295,10 @@ class Server extends HttpServer {
|
|||
};
|
||||
}
|
||||
|
||||
downloadImage(url: string, dir: string) {
|
||||
downloadImage(url: string, dir: string, return_image_data: true): Promise<readonly [name: string, data: Uint8Array, type: string]>
|
||||
downloadImage(url: string, dir: string, return_image_data?: false): Promise<string>
|
||||
downloadImage(url: string, dir: string, return_image_data?: boolean): Promise<string | readonly [name: string, data: Uint8Array, type: string]>
|
||||
downloadImage(url: string, dir: string, return_image_data?: boolean) {
|
||||
const pathname = new URL(url).pathname;
|
||||
const name = pathname.substr(1).toLowerCase()
|
||||
.replace(/^resources\//g, '')
|
||||
|
|
@ -1215,6 +1307,11 @@ class Server extends HttpServer {
|
|||
|
||||
const promise = this.promise_image.get(dir + '/' + name) ?? Promise.resolve().then(async () => {
|
||||
try {
|
||||
if (return_image_data) {
|
||||
const data = await fs.readFile(path.join(dir, name));
|
||||
return [name, data, 'image/jpeg'] as const;
|
||||
}
|
||||
|
||||
await fs.stat(path.join(dir, name));
|
||||
return name;
|
||||
} catch (err) {}
|
||||
|
|
@ -1230,6 +1327,10 @@ class Server extends HttpServer {
|
|||
|
||||
debug('Downloaded image %s', name);
|
||||
|
||||
if (return_image_data) {
|
||||
return [name, data, 'image/jpeg'] as const;
|
||||
}
|
||||
|
||||
return name;
|
||||
}).then(result => {
|
||||
this.promise_image.delete(dir + '/' + name);
|
||||
|
|
|
|||
|
|
@ -5,3 +5,5 @@ export * as discordActivity from './discord-activity.js';
|
|||
export * as discordRpc from './discord-rpc.js';
|
||||
export * as remoteConfig from './remote-config.js';
|
||||
export * as storage from './storage.js';
|
||||
export * as presenceEmbedRender from './presence-embed-render.js';
|
||||
export * as presenceEmbedServer from './presence-embed-server.js';
|
||||
|
|
|
|||
78
src/cli/util/presence-embed-render.ts
Normal file
78
src/cli/util/presence-embed-render.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import type { Arguments as ParentArguments } from '../util.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
|
||||
import { getPresenceFromUrl } from '../../api/znc-proxy.js';
|
||||
import { PresenceResponse } from '../presence-server.js';
|
||||
import { PresenceEmbedFormat, PresenceEmbedTheme, renderUserEmbedImage, renderUserEmbedSvg } from './presence-embed.js';
|
||||
|
||||
const debug = createDebug('cli:util:render-presence-embed');
|
||||
|
||||
export const command = 'render-presence-embed <url>';
|
||||
export const desc = 'Render presence embed';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('url', {
|
||||
describe: 'Presence URL',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('output', {
|
||||
describe: 'Output (svg, png, jpeg or webp)',
|
||||
type: 'string',
|
||||
default: 'svg',
|
||||
}).option('theme', {
|
||||
describe: 'Theme (light or dark)',
|
||||
type: 'string',
|
||||
default: 'light',
|
||||
}).option('friend-code', {
|
||||
describe: 'Friend code',
|
||||
type: 'string',
|
||||
}).option('scale', {
|
||||
describe: 'Image scale',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
}).option('transparent', {
|
||||
describe: 'Remove border and use transparent background',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const theme = argv.theme === 'dark' ? PresenceEmbedTheme.DARK : PresenceEmbedTheme.LIGHT;
|
||||
const format =
|
||||
argv.output === 'png' ? PresenceEmbedFormat.PNG :
|
||||
argv.output === 'jpeg' ? PresenceEmbedFormat.JPEG :
|
||||
argv.output === 'webp' ? PresenceEmbedFormat.WEBP :
|
||||
PresenceEmbedFormat.SVG;
|
||||
|
||||
if (argv.friendCode && !argv.friendCode.match(/^\d{4}-\d{4}-\d{4}$/)) {
|
||||
throw new TypeError('Invalid friend code');
|
||||
}
|
||||
|
||||
const [presence, user, data] = await getPresenceFromUrl(argv.url);
|
||||
const result = data as PresenceResponse;
|
||||
|
||||
const image_urls = [result.friend.imageUri];
|
||||
|
||||
if ('imageUri' in result.friend.presence.game) image_urls.push(result.friend.presence.game.imageUri);
|
||||
|
||||
const url_map: Record<string, readonly [name: string, data: Uint8Array, type: string]> = {};
|
||||
|
||||
debug('images', image_urls);
|
||||
|
||||
await Promise.all(image_urls.map(async (url) => {
|
||||
debug('Fetching image %s', url);
|
||||
const response = await fetch(url);
|
||||
const data = new Uint8Array(await response.arrayBuffer());
|
||||
|
||||
url_map[url] = [url, data, 'image/jpeg'];
|
||||
}));
|
||||
|
||||
const svg = renderUserEmbedSvg(result, url_map, theme, argv.friendCode, argv.scale, argv.transparent);
|
||||
const [image, type] = await renderUserEmbedImage(svg, format);
|
||||
|
||||
console.warn('output type', type);
|
||||
process.stdout.write(image);
|
||||
}
|
||||
178
src/cli/util/presence-embed-server.ts
Normal file
178
src/cli/util/presence-embed-server.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import * as os from 'node:os';
|
||||
import * as net from 'node:net';
|
||||
import express, { Request, Response } from 'express';
|
||||
import { createHash } from 'node:crypto';
|
||||
import type { Arguments as ParentArguments } from '../util.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js';
|
||||
import { getPresenceFromUrl } from '../../api/znc-proxy.js';
|
||||
import { PresenceResponse } from '../presence-server.js';
|
||||
import { addCliFeatureUserAgent } from '../../util/useragent.js';
|
||||
import { HttpServer, ResponseError } from './http-server.js';
|
||||
import { git, product, version } from '../../util/product.js';
|
||||
import { parseListenAddress } from '../../util/net.js';
|
||||
import { RawValueSymbol, htmlentities } from '../../util/misc.js';
|
||||
import { PresenceEmbedFormat, PresenceEmbedTheme, getUserEmbedOptionsFromRequest, renderUserEmbedImage, renderUserEmbedSvg } from './presence-embed.js';
|
||||
|
||||
const debug = createDebug('cli:util:presence-embed-server');
|
||||
|
||||
export const command = 'presence-embed-server <url>';
|
||||
export const desc = 'Presence embed test server';
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('url', {
|
||||
describe: 'Presence URL',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('listen', {
|
||||
describe: 'Server address and port',
|
||||
type: 'array',
|
||||
default: ['[::]:0'],
|
||||
});
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
addCliFeatureUserAgent('presence-embed-test-server');
|
||||
|
||||
const server = new Server(argv.url);
|
||||
const app = server.app;
|
||||
|
||||
for (const address of argv.listen) {
|
||||
const [host, port] = parseListenAddress(address);
|
||||
const server = app.listen(port, host ?? '::');
|
||||
server.on('listening', () => {
|
||||
const address = server.address() as net.AddressInfo;
|
||||
console.log('Listening on %s, port %d', address.address, address.port);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Server extends HttpServer {
|
||||
app: express.Express;
|
||||
|
||||
constructor(
|
||||
readonly base_url: string,
|
||||
) {
|
||||
super();
|
||||
|
||||
const app = this.app = express();
|
||||
|
||||
app.use('/api/presence', (req, res, next) => {
|
||||
console.log('[%s] %s %s HTTP/%s from %s, port %d%s, %s',
|
||||
new Date(), req.method, req.url, req.httpVersion,
|
||||
req.socket.remoteAddress, req.socket.remotePort,
|
||||
req.headers['x-forwarded-for'] ? ' (' + req.headers['x-forwarded-for'] + ')' : '',
|
||||
req.headers['user-agent']);
|
||||
|
||||
res.setHeader('Server', product + ' presence-embed-test-server');
|
||||
res.setHeader('X-Server', product + ' presence-embed-test-server');
|
||||
res.setHeader('X-Served-By', os.hostname());
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/api/presence/:user/embed', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.SVG)));
|
||||
app.get('/api/presence/:user/embed.png', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.PNG)));
|
||||
app.get('/api/presence/:user/embed.jpeg', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.JPEG)));
|
||||
app.get('/api/presence/:user/embed.webp', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedRequest(req, res, req.params.user, PresenceEmbedFormat.WEBP)));
|
||||
|
||||
app.get('/api/presence/:user/embed.html', this.createApiRequestHandler((req, res) =>
|
||||
this.handlePresenceEmbedHtmlRequest(req, res, req.params.user)));
|
||||
}
|
||||
|
||||
async handlePresenceEmbedRequest(req: Request, res: Response, presence_user_nsaid: string, format = PresenceEmbedFormat.SVG) {
|
||||
if (!presence_user_nsaid.match(/^[0-9a-f]{16}$/)) throw new ResponseError(404, 'not_found');
|
||||
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
const url = new URL(req.url, 'https://localhost');
|
||||
url.searchParams.delete('theme');
|
||||
url.searchParams.delete('friend-code');
|
||||
url.searchParams.delete('transparent');
|
||||
const qs = url.searchParams.size ? '?' + url.searchParams.toString() : '';
|
||||
|
||||
const [presence, user, data] = await getPresenceFromUrl(this.base_url + '/' + presence_user_nsaid + qs);
|
||||
const result = data as PresenceResponse;
|
||||
|
||||
const {theme, friend_code, transparent} = getUserEmbedOptionsFromRequest(req);
|
||||
|
||||
const etag = createHash('sha256').update(JSON.stringify({
|
||||
data,
|
||||
format,
|
||||
theme,
|
||||
friend_code,
|
||||
transparent,
|
||||
v: version + '-' + git?.revision,
|
||||
})).digest('base64url');
|
||||
|
||||
if (req.headers['if-none-match'] === '"' + etag + '"' || req.headers['if-none-match'] === 'W/"' + etag + '"') {
|
||||
res.statusCode = 304;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const image_urls = [result.friend.imageUri];
|
||||
|
||||
if ('imageUri' in result.friend.presence.game) image_urls.push(result.friend.presence.game.imageUri);
|
||||
|
||||
const url_map: Record<string, readonly [name: string, data: Uint8Array, type: string]> = {};
|
||||
|
||||
await Promise.all(image_urls.map(async (url) => {
|
||||
debug('Fetching image %s', url);
|
||||
const response = await fetch(url);
|
||||
const data = new Uint8Array(await response.arrayBuffer());
|
||||
|
||||
url_map[url] = [url, data, 'image/jpeg'];
|
||||
}));
|
||||
|
||||
const svg = renderUserEmbedSvg(result, url_map, theme, friend_code, 1, transparent);
|
||||
const [image, type] = await renderUserEmbedImage(svg, format);
|
||||
|
||||
res.setHeader('Content-Type', type);
|
||||
res.setHeader('Etag', '"' + etag + '"');
|
||||
res.end(image);
|
||||
}
|
||||
|
||||
async handlePresenceEmbedHtmlRequest(req: Request, res: Response, presence_user_nsaid: string) {
|
||||
if (!presence_user_nsaid.match(/^[0-9a-f]{16}$/)) throw new ResponseError(404, 'not_found');
|
||||
|
||||
const url = new URL(req.url, 'https://localhost');
|
||||
url.searchParams.delete('theme');
|
||||
const qs = url.searchParams.size ? '&' + url.searchParams.toString() : '';
|
||||
|
||||
const url_2 = new URL(req.url, 'https://localhost');
|
||||
url_2.searchParams.delete('theme');
|
||||
url_2.searchParams.delete('friend-code');
|
||||
const qs_2 = url_2.searchParams.size ? '?' + url_2.searchParams.toString() : '';
|
||||
|
||||
const [presence, user, data] = await getPresenceFromUrl(this.base_url + '/' + presence_user_nsaid + qs_2);
|
||||
const result = data as PresenceResponse;
|
||||
|
||||
const image_urls = [result.friend.imageUri];
|
||||
|
||||
if ('imageUri' in result.friend.presence.game) image_urls.push(result.friend.presence.game.imageUri);
|
||||
|
||||
const url_map: Record<string, readonly [name: string, data: Uint8Array, type: string]> = {};
|
||||
|
||||
await Promise.all(image_urls.map(async (url) => {
|
||||
debug('Fetching image %s', url);
|
||||
const response = await fetch(url);
|
||||
const data = new Uint8Array(await response.arrayBuffer());
|
||||
|
||||
url_map[url] = [url, data, 'image/jpeg'];
|
||||
}));
|
||||
|
||||
const svg = renderUserEmbedSvg(result, url_map, PresenceEmbedTheme.LIGHT, undefined, 1, true);
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.write(`<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width,initial-scale=1"/><style>body{margin:0;min-height:100vh;width:100vw;min-width:fit-content;display:flex;align-items:center;justify-content:center}</style></head>`);
|
||||
res.write(htmlentities`<body><picture><source srcset="embed?theme=dark${qs}" media="(prefers-color-scheme:dark)"/><img src="embed?theme=light${qs}" alt="Nintendo Switch presence"/></picture><p>${{[RawValueSymbol]: svg}}</p></body></html>\n`);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
220
src/cli/util/presence-embed.ts
Normal file
220
src/cli/util/presence-embed.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { Request } from 'express';
|
||||
import sharp from 'sharp';
|
||||
import { FriendOnlineState } from 'splatnet3-types/splatnet3';
|
||||
import { dir } from '../../util/product.js';
|
||||
import createDebug from '../../util/debug.js';
|
||||
import { Game, PresenceState } from '../../api/coral-types.js';
|
||||
import { RawValueSymbol, htmlentities } from '../../util/misc.js';
|
||||
import { PresenceResponse } from '../presence-server.js';
|
||||
|
||||
const debug = createDebug('cli:util:presence-embed');
|
||||
|
||||
export enum PresenceEmbedFormat {
|
||||
SVG,
|
||||
PNG,
|
||||
JPEG,
|
||||
WEBP,
|
||||
}
|
||||
|
||||
export enum PresenceEmbedTheme {
|
||||
LIGHT,
|
||||
DARK,
|
||||
}
|
||||
interface PresenceEmbedThemeColours {
|
||||
background: string;
|
||||
separator: string;
|
||||
text: string;
|
||||
online: string;
|
||||
online_border: string;
|
||||
}
|
||||
|
||||
const embed_themes: Record<PresenceEmbedTheme, PresenceEmbedThemeColours> = {
|
||||
[PresenceEmbedTheme.LIGHT]: {
|
||||
background: '#ebebeb',
|
||||
separator: '#7b7b7b',
|
||||
text: '#000000',
|
||||
online: '#2db742',
|
||||
online_border: '#0eb728',
|
||||
},
|
||||
[PresenceEmbedTheme.DARK]: {
|
||||
background: '#2d2d2d',
|
||||
separator: '#7e7e7e',
|
||||
text: '#ffffff',
|
||||
online: '#47e85f',
|
||||
online_border: '#19e838',
|
||||
},
|
||||
};
|
||||
|
||||
const embed_titles: Partial<Record<string, (
|
||||
result: PresenceResponse,
|
||||
url_map: Record<string, string | readonly [url: string, data: Uint8Array, type: string]>,
|
||||
image: (url: string) => string | undefined,
|
||||
theme?: PresenceEmbedTheme,
|
||||
) => readonly [svg: string, height: number]>> = {
|
||||
'0100c2500fc20000': renderUserSplatoon3EmbedPartialSvg,
|
||||
};
|
||||
|
||||
export function getUserEmbedOptionsFromRequest(req: Request) {
|
||||
const url = new URL(req.url, 'https://localhost');
|
||||
|
||||
const theme = url.searchParams.get('theme') === 'dark' ? PresenceEmbedTheme.DARK : PresenceEmbedTheme.LIGHT;
|
||||
const friend_code = url.searchParams.getAll('friend-code').find(c => c.match(/^\d{4}-\d{4}-\d{4}$/));
|
||||
const transparent = url.searchParams.get('transparent') === '1';
|
||||
|
||||
return {theme, friend_code, transparent};
|
||||
}
|
||||
|
||||
export async function renderUserEmbedImage(
|
||||
svg: string,
|
||||
format: PresenceEmbedFormat,
|
||||
): Promise<[data: Buffer, type: string]> {
|
||||
if (format === PresenceEmbedFormat.SVG) {
|
||||
return [Buffer.from(svg), 'image/svg+xml'];
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
debug('generating image, format %s', PresenceEmbedFormat[format]);
|
||||
|
||||
let image = sharp(Buffer.from(svg)).withMetadata({
|
||||
density: 72 * 2,
|
||||
});
|
||||
|
||||
if (format === PresenceEmbedFormat.PNG) image = image.png();
|
||||
if (format === PresenceEmbedFormat.JPEG) image = image.jpeg();
|
||||
if (format === PresenceEmbedFormat.WEBP) image = image.webp();
|
||||
|
||||
const data = await image.toBuffer();
|
||||
|
||||
debug('generated %s in %d ms', PresenceEmbedFormat[format], Date.now() - start);
|
||||
|
||||
if (format === PresenceEmbedFormat.PNG) return [data, 'image/png'];
|
||||
if (format === PresenceEmbedFormat.JPEG) return [data, 'image/jpeg'];
|
||||
if (format === PresenceEmbedFormat.WEBP) return [data, 'image/webp'];
|
||||
|
||||
throw new TypeError('Invalid format');
|
||||
}
|
||||
|
||||
export function renderUserEmbedSvg(
|
||||
result: PresenceResponse,
|
||||
url_map: Record<string, string | readonly [url: string, data: Uint8Array, type: string]>,
|
||||
theme = PresenceEmbedTheme.LIGHT,
|
||||
friend_code?: string,
|
||||
scale = 1,
|
||||
transparent = false,
|
||||
) {
|
||||
let width = 500;
|
||||
let height = 180;
|
||||
if (friend_code) height += 40;
|
||||
|
||||
const colours = embed_themes[theme];
|
||||
const font_family = '\'Open Sans\', -apple-system, BlinkMacSystemFont, Arial, sans-serif';
|
||||
|
||||
const state = result.friend.presence.state;
|
||||
const game = 'name' in result.friend.presence.game ? result.friend.presence.game : null;
|
||||
|
||||
const image = (url: string) =>
|
||||
url_map[url] instanceof Array ?
|
||||
'data:' + url_map[url][2] + ';base64,' +
|
||||
Buffer.from(url_map[url][1]).toString('base64') :
|
||||
url_map[url] as string | undefined;
|
||||
|
||||
const title_extra = result.title ? embed_titles[result.title.id]?.call(null, result, url_map, image, theme) : null;
|
||||
if (title_extra) height += title_extra[1];
|
||||
|
||||
return htmlentities`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- ${JSON.stringify(result, null, 4)} -->
|
||||
<svg
|
||||
width="${(width + (transparent ? -60 : 0)) * scale}"
|
||||
height="${(height + (transparent ? -60 : 0) + (title_extra?.[1] ?? 0)) * scale}"
|
||||
viewBox="${transparent ? '30 30' : '0 0'} ${width + (transparent ? -60 : 0)} ${height + (transparent ? -60 : 0) + (title_extra?.[1] ?? 0)}"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>${embed_style}</style>
|
||||
|
||||
<defs>
|
||||
<linearGradient id="gradient-out">
|
||||
<stop offset="0%" stop-opacity="1" stop-color="#ffffff"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#ffffff"></stop>
|
||||
</linearGradient>
|
||||
|
||||
<mask id="mask-out">
|
||||
<rect x="0" y="0" width="${width - 50}" height="${height}" fill="#ffffff"></rect>
|
||||
<rect x="${width - 50}" y="0" width="20" height="${height}" fill="url(#gradient-out)"></rect>
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
${{[RawValueSymbol]: transparent ? '' : htmlentities`
|
||||
<rect x="0" y="0" width="${width}" height="${height}" fill="${colours.background}" />
|
||||
`}}
|
||||
|
||||
<image x="30" y="30" width="120" height="120"
|
||||
href="${image(result.friend.imageUri) ?? result.friend.imageUri}" />
|
||||
<text x="180" y="57" fill="${colours.text}" font-size="26" font-family="${font_family}" font-weight="500">${result.friend.name}</text>
|
||||
|
||||
<line x1="180" y1="73" x2="470" y2="73" stroke="${colours.separator}" />
|
||||
|
||||
${{[RawValueSymbol]: game && (state === PresenceState.ONLINE || state === PresenceState.PLAYING) ? htmlentities`
|
||||
<image x="180" y="87" width="60" height="60"
|
||||
href="${image(game.imageUri) ?? game.imageUri}" />
|
||||
|
||||
${{[RawValueSymbol]: renderUserTitleEmbedPartialSvg(game, colours, font_family)}}
|
||||
` : htmlentities`
|
||||
<text x="180" y="97" fill="${colours.text}" font-size="14" font-family="${font_family}" font-weight="400">Offline</text>
|
||||
`}}
|
||||
|
||||
${{[RawValueSymbol]: friend_code ? htmlentities`
|
||||
<text x="30" y="186" fill="${colours.text}" font-size="14" font-family="${font_family}" font-weight="400">Friend code: SW-${friend_code}</text>
|
||||
` : ''}}
|
||||
|
||||
${{[RawValueSymbol]: title_extra?.[0] ?? ''}}
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderUserTitleEmbedPartialSvg(game: Game, colours: PresenceEmbedThemeColours, font_family: string) {
|
||||
const playing_text_offset = game.sysDescription ? 92 : 97;
|
||||
const title_name_text_offset = game.sysDescription ? 122 : 133;
|
||||
|
||||
return htmlentities`
|
||||
<rect x="255" y="${playing_text_offset}" width="10" height="10" fill="${colours.online}"
|
||||
stroke="${colours.online_border}" stroke-width="1" rx="1" ry="1" stroke-linejoin="round"
|
||||
/>
|
||||
<text x="272" y="${playing_text_offset + 10}" fill="${colours.online}" font-size="14" font-family="${font_family}" font-weight="400" mask="url(#mask-out)">Playing</text>
|
||||
|
||||
<text x="255" y="${title_name_text_offset}" fill="${colours.text}" font-size="14" font-family="${font_family}" font-weight="400" mask="url(#mask-out)">${game.name}</text>
|
||||
|
||||
<text x="255" y="142" fill="${colours.text}" font-size="14" font-family="${font_family}" font-weight="300" mask="url(#mask-out)">${game.sysDescription}</text>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderUserSplatoon3EmbedPartialSvg(
|
||||
result: PresenceResponse,
|
||||
url_map: Record<string, string | readonly [url: string, data: Uint8Array, type: string]>,
|
||||
image: (url: string) => string | undefined,
|
||||
theme = PresenceEmbedTheme.LIGHT,
|
||||
) {
|
||||
return ['', 0] as const;
|
||||
}
|
||||
|
||||
const embed_fonts: [name: string, style: string, weight: string, files: [format: string, type: string, path: string][]][] = [
|
||||
['Open Sans', 'normal', '400', [['opentype', 'font/ttf', 'opensans-normal-400.ttf']]],
|
||||
['Open Sans', 'normal', '500', [['opentype', 'font/ttf', 'opensans-normal-500.ttf']]],
|
||||
];
|
||||
|
||||
const embed_style = `
|
||||
text {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
` + (await Promise.all(embed_fonts.map(async ([name, style, weight, files]) => `@font-face {
|
||||
font-family: '${name}';
|
||||
font-style: ${style};
|
||||
font-weight: ${weight};
|
||||
src: ${(await Promise.all(files.map(async ([format, type, file]) => `url('data:${type};base64,${
|
||||
(await fs.readFile(path.join(dir, 'resources', 'cli', 'fonts', file))).toString('base64')
|
||||
}') format('${format}')`))).join(',')};
|
||||
}`))).join('\n');
|
||||
|
|
@ -40,3 +40,14 @@ export function timeoutSignal(ms = 60 * 1000) {
|
|||
|
||||
return [controller.signal, () => clearTimeout(timeout), controller] as const;
|
||||
}
|
||||
|
||||
export const RawValueSymbol = Symbol('RawValue');
|
||||
export type RawValue = {[RawValueSymbol]: string};
|
||||
|
||||
export function htmlentities(strings: TemplateStringsArray, ...args: (string | number | RawValue)[]): string {
|
||||
return strings.map((s, i) => s + (args[i] ? (
|
||||
typeof args[i] === 'object' && RawValueSymbol in (args[i] as object) ?
|
||||
(args[i] as RawValue)[RawValueSymbol] :
|
||||
args[i].toString().replace(/[\u00A0-\u9999<>\&]/gim, c => '&#' + c.charCodeAt(0) + ';')
|
||||
) : '')).join('');
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user