change scout to a service
ci / build (push) Failing after 0s Details

This commit is contained in:
CJ_Clippy 2024-08-26 23:11:24 -08:00
parent f714504cac
commit fdd295e2b8
121 changed files with 9984 additions and 11083 deletions

View File

@ -183,6 +183,7 @@ docker_build(
'./pnpm-workspace.yaml',
'./services/bot',
'./packages/types',
'./packages/utils',
],
dockerfile='./dockerfiles/bot.dockerfile',
target='dev',
@ -191,6 +192,26 @@ docker_build(
]
)
docker_build(
'fp/scout',
'.',
only=[
'./.npmrc',
'./package.json',
'./pnpm-lock.yaml',
'./pnpm-workspace.yaml',
'./packages/types',
'./packages/utils',
'./packages/image',
'./services/scout',
],
dockerfile='./dockerfiles/scout.dockerfile',
target='dev',
live_update=[
sync('./services/scout', '/app/services/scout')
]
)
@ -289,18 +310,6 @@ docker_build(
# docker_build(
# 'fp/scout',
# '.',
# dockerfile='dockerfiles/scout.dockerfile',
# target='scout',
# live_update=[
# sync('./packages/scout', '/app'),
# run('cd /app && pnpm i', trigger=['./packages/scout/package.json', './packages/scout/pnpm-lock.yaml']),
# ],
# entrypoint='pnpm nodemon --ext js,ts,json,yaml --exec node --no-warnings=ExperimentalWarning --loader ts-node/esm ./src/index.ts'
# # entrypoint='pnpm tsx watch ./src/index.ts'
# )
docker_build(
'fp/mailbox',
@ -313,7 +322,6 @@ docker_build(
'./pnpm-lock.yaml',
'./pnpm-workspace.yaml',
'./packages/image',
'./packages/scout',
'./services/mailbox',
'./packages/types',
'./packages/utils',
@ -360,7 +368,6 @@ docker_build(
'./package.json',
'./pnpm-lock.yaml',
'./pnpm-workspace.yaml',
'./packages/scout',
'./packages/types',
'./packages/utils',
'./services/capture',
@ -395,7 +402,12 @@ docker_build(
# labels='debug'
# )
k8s_resource(
workload='scout',
resource_deps=['postgresql-primary'],
port_forwards=['5134'],
labels=['backend'],
)
k8s_resource(
workload='uppy',
links=[
@ -410,7 +422,7 @@ k8s_resource(
links=[
link('https://next.fp.sbtp.xyz'),
],
resource_deps=['strapi', 'postgresql-primary'],
resource_deps=['postgrest', 'postgresql-primary'],
labels=['frontend'],
)
k8s_resource(

View File

@ -22,6 +22,8 @@ spec:
env:
- name: POSTGREST_URL
value: "{{ .Values.postgrest.url }}"
- name: NODE_ENV
value: production
- name: AUTOMATION_USER_JWT
valueFrom:
secretKeyRef:
@ -52,6 +54,11 @@ spec:
secretKeyRef:
name: bot
key: workerConnectionString
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: capture
key: httpProxy
resources:
limits:
cpu: 150m

View File

@ -111,6 +111,11 @@ spec:
env:
- name: FUNCTION
value: api
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: capture
key: httpProxy
- name: WORKER_CONNECTION_STRING
valueFrom:
secretKeyRef:

View File

@ -0,0 +1,49 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scout
namespace: futureporn
labels:
app.kubernetes.io/name: scout
spec:
replicas: {{ .Values.scout.replicas }}
selector:
matchLabels:
app: scout
template:
metadata:
labels:
app: scout
spec:
containers:
- name: scout
image: "{{ .Values.scout.imageName }}"
env:
- name: POSTGREST_URL
value: "{{ .Values.postgrest.url }}"
- name: NODE_ENV
value: production
- name: AUTOMATION_USER_JWT
valueFrom:
secretKeyRef:
name: bot
key: automationUserJwt
- name: WORKER_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: bot
key: workerConnectionString
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: capture
key: httpProxy
- name: PORT
value: "{{ .Values.scout.port }}"
resources:
limits:
cpu: 150m
memory: 1024Mi
restartPolicy: Always

View File

@ -72,6 +72,10 @@ bot:
discordGuildId: "1084674137391374338"
imageName: fp/bot
replicas: 1
scout:
imageName: fp/scout
replicas: 1
port: 5134
postgrest:
url: http://postgrest.futureporn.svc.cluster.local:9000
image: postgrest/postgrest

View File

@ -2,13 +2,14 @@ FROM node:20 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
RUN corepack enable && corepack prepare --activate
ENTRYPOINT ["pnpm"]
FROM base AS install
COPY pnpm-lock.yaml .npmrc package.json .
COPY ./services/bot/ ./services/bot/
COPY ./packages/types/ ./packages/types/
COPY ./packages/utils/ ./packages/utils/
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline

View File

@ -2,7 +2,7 @@ FROM node:20-alpine AS base
## Install dependencies only when needed
## Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
RUN corepack enable && corepack prepare --activate
## Enable `pnpm add --global` on Alpine Linux by setting
## home location environment variable to a location already in $PATH
@ -22,7 +22,6 @@ FROM base AS build
## Copy the manifests and lockfiles into the build context
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc .
COPY ./services/capture/package.json ./services/capture/pnpm-lock.yaml ./services/capture/
COPY ./packages/scout/package.json ./packages/scout/pnpm-lock.yaml ./packages/scout/
COPY ./packages/types/package.json ./packages/types/pnpm-lock.yaml ./packages/types/
COPY ./packages/utils/package.json ./packages/utils/pnpm-lock.yaml ./packages/utils/
@ -32,7 +31,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --pre
## Copy in all project files
COPY ./services/capture/ ./services/capture/
COPY ./packages/scout/ ./packages/scout/
COPY ./packages/types/ ./packages/types/
COPY ./packages/utils/ ./packages/utils/

View File

@ -10,7 +10,7 @@ ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
COPY --from=mwader/static-ffmpeg:7.0.2 /ffmpeg /usr/local/bin/
COPY --from=mwader/static-ffmpeg:7.0.2 /ffprobe /usr/local/bin/
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
RUN corepack enable && corepack prepare --activate
ENTRYPOINT ["pnpm"]
FROM base AS install
@ -20,7 +20,6 @@ RUN mkdir -p /app/services/factory && mkdir -p /prod/factory
## Copy manfiests, lockfiles, and configs into docker context
COPY package.json pnpm-lock.yaml .npmrc .
# COPY ./packages/image/pnpm-lock.yaml ./packages/image/package.json ./packages/image/
# COPY ./packages/scout/pnpm-lock.yaml ./packages/scout/package.json ./packages/scout/
# COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/
# COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/
@ -33,7 +32,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -g node-gyp --pre
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline
## Copy package code into docker context
# COPY ./packages/image/ ./packages/image/
# COPY ./packages/scout/ ./packages/scout/
# COPY ./packages/storage/ ./packages/storage/
# COPY ./packages/utils/ ./packages/utils/
COPY ./packages/types/ ./packages/types/

View File

@ -18,7 +18,7 @@ FROM node:20 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
RUN corepack enable && corepack prepare --activate
FROM base AS build
WORKDIR /app
@ -27,7 +27,6 @@ RUN mkdir -p /app/services/mailbox && mkdir -p /prod/mailbox
## Copy manfiests, lockfiles, and configs into docker context
COPY package.json pnpm-lock.yaml .npmrc .
COPY ./packages/image/pnpm-lock.yaml ./packages/image/package.json ./packages/image/
COPY ./packages/scout/pnpm-lock.yaml ./packages/scout/package.json ./packages/scout/
COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/
COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/
COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
@ -39,7 +38,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --fro
## Copy package code into docker context
COPY ./packages/image/ ./packages/image/
COPY ./packages/scout/ ./packages/scout/
COPY ./packages/storage/ ./packages/storage/
COPY ./packages/types/ ./packages/types/
COPY ./packages/utils/ ./packages/utils/

View File

@ -4,7 +4,7 @@ FROM node:20.15 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
RUN corepack enable && corepack prepare --activate
FROM base AS build
# ENV NODE_ENV=development

View File

@ -2,7 +2,7 @@ FROM node:20-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
RUN corepack enable && corepack prepare --activate
FROM base AS build
COPY ./pnpm-workspace.yaml ./.npmrc .

View File

@ -6,7 +6,7 @@ FROM node:20-slim AS base
FROM base AS deps
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
RUN corepack enable && corepack prepare --activate
WORKDIR /app
@ -25,7 +25,6 @@ RUN pnpm fetch
COPY ./services/next ./services/next
COPY ./packages/types ./packages/types
# COPY ./packages/strapi ./packages/strapi
# COPY ./packages/scout ./packages/scout
# COPY ./packages/image ./packages/image
# COPY ./packages/utils ./packages/utils

View File

@ -2,41 +2,42 @@ FROM node:20 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
FROM base AS build
WORKDIR /app
RUN mkdir -p /app/packages/scout && mkdir /app/packages/taco && mkdir -p /prod/scout
## @important If pnpm is downloading node during the build, that's a bandwidth-expensive mistake.
## Node already exists in the docker image at /usr/local/bin/node.
## We should use the node version that exists in the docker image.
## The only thing that should be downloaded by corepack is pnpm.
## The reason we explicitly set a pnpm version here is because we want to have pnpm cached.
## We haven't copied any .npmrc or package.json files at this point in the build, so corepack has no way of knowing which version to get.
## There might be a more optimal way of doing this that doesn't require syncing this version with the version in package.json
## but I'm not sure what that would look like.
##
## @important match the pnpm version between all pnpm workspace packages or multiple versions of pnpm will get installed (slow)
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
ENTRYPOINT ["pnpm"]
## Copy manfiests, lockfiles, and configs into docker context
COPY package.json pnpm-lock.yaml .npmrc .
COPY ./packages/image/pnpm-lock.yaml ./packages/image/package.json ./packages/image/
COPY ./packages/scout/pnpm-lock.yaml ./packages/scout/package.json ./packages/scout/
COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/
COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/
COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
## Install npm packages
FROM base AS install
COPY pnpm-lock.yaml .npmrc package.json .
COPY ./services/scout/ ./services/scout/
COPY ./packages/types/ ./packages/types/
COPY ./packages/image/ ./packages/image/
COPY ./packages/utils/ ./packages/utils/
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline
## Copy package code into docker context
COPY ./packages/image/ ./packages/image/
COPY ./packages/scout/ ./packages/scout/
COPY ./packages/storage/ ./packages/storage/
COPY ./packages/types/ ./packages/types/
COPY ./packages/utils/ ./packages/utils/
RUN ls -la ./packages/image
## Transpile TS into JS
RUN pnpm --filter=@futureporn/image build
## Deploy (copy all production code into one place)
FROM install AS build
RUN pnpm -r build
RUN pnpm deploy --filter=scout --prod /prod/scout
FROM base AS scout
COPY --from=build /prod/scout .
RUN ls -la .
ENTRYPOINT ["pnpm", "start"]
FROM install AS dev
WORKDIR /app/services/scout
CMD ["run", "dev"]
FROM base AS prod
COPY --from=build /prod/scout .
CMD ["run", "start"]

View File

@ -1,6 +1,6 @@
FROM node:20 AS strapi
WORKDIR /usr/src/app/
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
RUN corepack enable && corepack prepare --activate
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# ENV NODE_EXTRA_CA_CERTS ${NODE_EXTRA_CA_CERTS}

View File

@ -2,7 +2,7 @@ FROM node:20-alpine3.18 AS base
## Installing libvips-dev for sharp Compatibility
## (only necessary for alpine docker images)
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev git
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
RUN corepack enable && corepack prepare --activate
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ARG NODE_ENV=development

View File

@ -21,7 +21,6 @@ FROM node:20 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.5.0 --activate
FROM base AS build
WORKDIR /app
@ -29,8 +28,8 @@ RUN mkdir -p /app/packages/worker && mkdir -p /prod/worker
## Copy manfiests, lockfiles, and configs into docker context
COPY package.json pnpm-lock.yaml .npmrc .
RUN corepack enable && corepack prepare --activate
# COPY ./packages/image/pnpm-lock.yaml ./packages/image/package.json ./packages/image/
# COPY ./packages/scout/pnpm-lock.yaml ./packages/scout/package.json ./packages/scout/
# COPY ./packages/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/
# COPY ./packages/types/pnpm-lock.yaml ./packages/types/package.json ./packages/types/
# COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
@ -44,7 +43,6 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --recursive --fro
## Copy package code into docker context
# COPY ./packages/image/ ./packages/image/
# COPY ./packages/scout/ ./packages/scout/
# COPY ./packages/storage/ ./packages/storage/
# COPY ./packages/types/ ./packages/types/
# COPY ./packages/utils/ ./packages/utils/

View File

@ -4,19 +4,18 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0",
"test": "act -W ./.gitea/workflows",
"clean": "rm -rf node_modules && rm -rf pnpm-lock.yaml",
"dev": "tilt up"
},
"keywords": [],
"author": "@CJ_Clippy",
"license": "Unlicense",
"packageManager": "pnpm@9.5.0",
"packageManager": "pnpm@9.6.0",
"dependencies": {
"types": "^0.1.1"
},
"devDependencies": {
"concurrently": "^8.2.2",
"lerna": "^8.1.7"
"concurrently": "^8.2.2"
}
}

View File

@ -13,7 +13,7 @@ importers:
version: link:../utils
'@types/chai':
specifier: ^4.3.16
version: 4.3.16
version: 4.3.17
'@types/mocha':
specifier: ^10.0.7
version: 10.0.7
@ -22,14 +22,14 @@ importers:
version: 7.5.0
sharp:
specifier: ^0.33.4
version: 0.33.4
version: 0.33.5
devDependencies:
chai:
specifier: ^5.1.1
version: 5.1.1
mocha:
specifier: ^10.6.0
version: 10.7.0
version: 10.7.3
tsx:
specifier: ^4.17.0
version: 4.17.0
@ -39,271 +39,263 @@ packages:
'@emnapi/runtime@1.2.0':
resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
'@esbuild/aix-ppc64@0.23.0':
resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.23.0':
resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==}
'@esbuild/android-arm64@0.23.1':
resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.23.0':
resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==}
'@esbuild/android-arm@0.23.1':
resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.23.0':
resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==}
'@esbuild/android-x64@0.23.1':
resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.23.0':
resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==}
'@esbuild/darwin-arm64@0.23.1':
resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.23.0':
resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==}
'@esbuild/darwin-x64@0.23.1':
resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.23.0':
resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==}
'@esbuild/freebsd-arm64@0.23.1':
resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.23.0':
resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==}
'@esbuild/freebsd-x64@0.23.1':
resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.23.0':
resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==}
'@esbuild/linux-arm64@0.23.1':
resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.23.0':
resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==}
'@esbuild/linux-arm@0.23.1':
resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.23.0':
resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==}
'@esbuild/linux-ia32@0.23.1':
resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.23.0':
resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==}
'@esbuild/linux-loong64@0.23.1':
resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.23.0':
resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==}
'@esbuild/linux-mips64el@0.23.1':
resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.23.0':
resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==}
'@esbuild/linux-ppc64@0.23.1':
resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.23.0':
resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==}
'@esbuild/linux-riscv64@0.23.1':
resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.23.0':
resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==}
'@esbuild/linux-s390x@0.23.1':
resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.23.0':
resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==}
'@esbuild/linux-x64@0.23.1':
resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.23.0':
resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==}
'@esbuild/netbsd-x64@0.23.1':
resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.23.0':
resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
'@esbuild/openbsd-arm64@0.23.1':
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.23.0':
resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==}
'@esbuild/openbsd-x64@0.23.1':
resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.23.0':
resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==}
'@esbuild/sunos-x64@0.23.1':
resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.23.0':
resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==}
'@esbuild/win32-arm64@0.23.1':
resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.23.0':
resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==}
'@esbuild/win32-ia32@0.23.1':
resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.23.0':
resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==}
'@esbuild/win32-x64@0.23.1':
resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/sharp-darwin-arm64@0.33.4':
resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==}
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'}
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.33.4':
resolution: {integrity: sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==}
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'}
'@img/sharp-darwin-x64@0.33.5':
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.0.2':
resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==}
engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-darwin-arm64@1.0.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.0.2':
resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==}
engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-darwin-x64@1.0.4':
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.0.2':
resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==}
engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linux-arm64@1.0.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.0.2':
resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==}
engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.0.2':
resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==}
engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.0.2':
resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==}
engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.0.2':
resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==}
engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.0.2':
resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==}
engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.33.4':
resolution: {integrity: sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==}
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'}
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.33.4':
resolution: {integrity: sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==}
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'}
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
'@img/sharp-linux-s390x@0.33.4':
resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==}
engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.33.4':
resolution: {integrity: sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==}
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'}
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.33.4':
resolution: {integrity: sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==}
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'}
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.33.4':
resolution: {integrity: sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==}
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'}
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.33.4':
resolution: {integrity: sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-ia32@0.33.4':
resolution: {integrity: sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-win32-ia32@0.33.5':
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.33.4':
resolution: {integrity: sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
'@img/sharp-win32-x64@0.33.5':
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@types/chai@4.3.16':
resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/fluent-ffmpeg@2.1.24':
resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==}
'@types/fluent-ffmpeg@2.1.25':
resolution: {integrity: sha512-a9/Jtv/RVaCG4lUwWIcuClWE5eXJFoFS/oHOecOv/RS8n+lQdJzcJVmDlxA8Xbk4B82YpO88Dijcoljb6sYTcA==}
'@types/luxon@3.4.2':
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
@ -314,8 +306,8 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/node@20.14.13':
resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@ -429,8 +421,8 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
esbuild@0.23.0:
resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==}
esbuild@0.23.1:
resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
engines: {node: '>=18'}
hasBin: true
@ -579,8 +571,8 @@ packages:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
mocha@10.7.0:
resolution: {integrity: sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==}
mocha@10.7.3:
resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==}
engines: {node: '>= 14.0.0'}
hasBin: true
@ -661,9 +653,9 @@ packages:
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
sharp@0.33.4:
resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==}
engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@ -708,8 +700,8 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tsx@4.17.0:
resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==}
@ -721,8 +713,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
@ -767,165 +759,165 @@ snapshots:
'@emnapi/runtime@1.2.0':
dependencies:
tslib: 2.6.3
tslib: 2.7.0
optional: true
'@esbuild/aix-ppc64@0.23.0':
'@esbuild/aix-ppc64@0.23.1':
optional: true
'@esbuild/android-arm64@0.23.0':
'@esbuild/android-arm64@0.23.1':
optional: true
'@esbuild/android-arm@0.23.0':
'@esbuild/android-arm@0.23.1':
optional: true
'@esbuild/android-x64@0.23.0':
'@esbuild/android-x64@0.23.1':
optional: true
'@esbuild/darwin-arm64@0.23.0':
'@esbuild/darwin-arm64@0.23.1':
optional: true
'@esbuild/darwin-x64@0.23.0':
'@esbuild/darwin-x64@0.23.1':
optional: true
'@esbuild/freebsd-arm64@0.23.0':
'@esbuild/freebsd-arm64@0.23.1':
optional: true
'@esbuild/freebsd-x64@0.23.0':
'@esbuild/freebsd-x64@0.23.1':
optional: true
'@esbuild/linux-arm64@0.23.0':
'@esbuild/linux-arm64@0.23.1':
optional: true
'@esbuild/linux-arm@0.23.0':
'@esbuild/linux-arm@0.23.1':
optional: true
'@esbuild/linux-ia32@0.23.0':
'@esbuild/linux-ia32@0.23.1':
optional: true
'@esbuild/linux-loong64@0.23.0':
'@esbuild/linux-loong64@0.23.1':
optional: true
'@esbuild/linux-mips64el@0.23.0':
'@esbuild/linux-mips64el@0.23.1':
optional: true
'@esbuild/linux-ppc64@0.23.0':
'@esbuild/linux-ppc64@0.23.1':
optional: true
'@esbuild/linux-riscv64@0.23.0':
'@esbuild/linux-riscv64@0.23.1':
optional: true
'@esbuild/linux-s390x@0.23.0':
'@esbuild/linux-s390x@0.23.1':
optional: true
'@esbuild/linux-x64@0.23.0':
'@esbuild/linux-x64@0.23.1':
optional: true
'@esbuild/netbsd-x64@0.23.0':
'@esbuild/netbsd-x64@0.23.1':
optional: true
'@esbuild/openbsd-arm64@0.23.0':
'@esbuild/openbsd-arm64@0.23.1':
optional: true
'@esbuild/openbsd-x64@0.23.0':
'@esbuild/openbsd-x64@0.23.1':
optional: true
'@esbuild/sunos-x64@0.23.0':
'@esbuild/sunos-x64@0.23.1':
optional: true
'@esbuild/win32-arm64@0.23.0':
'@esbuild/win32-arm64@0.23.1':
optional: true
'@esbuild/win32-ia32@0.23.0':
'@esbuild/win32-ia32@0.23.1':
optional: true
'@esbuild/win32-x64@0.23.0':
'@esbuild/win32-x64@0.23.1':
optional: true
'@img/sharp-darwin-arm64@0.33.4':
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.2
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
'@img/sharp-darwin-x64@0.33.4':
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.2
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.2':
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.2':
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.2':
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm@1.0.2':
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.2':
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
'@img/sharp-libvips-linux-x64@1.0.2':
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.2':
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.2':
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
'@img/sharp-linux-arm64@0.33.4':
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.2
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
'@img/sharp-linux-arm@0.33.4':
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.2
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
'@img/sharp-linux-s390x@0.33.4':
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.2
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
'@img/sharp-linux-x64@0.33.4':
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.2
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
'@img/sharp-linuxmusl-arm64@0.33.4':
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.2
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
'@img/sharp-linuxmusl-x64@0.33.4':
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.2
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
'@img/sharp-wasm32@0.33.4':
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.2.0
optional: true
'@img/sharp-win32-ia32@0.33.4':
'@img/sharp-win32-ia32@0.33.5':
optional: true
'@img/sharp-win32-x64@0.33.4':
'@img/sharp-win32-x64@0.33.5':
optional: true
'@types/chai@4.3.16': {}
'@types/chai@4.3.17': {}
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
'@types/fluent-ffmpeg@2.1.24':
'@types/fluent-ffmpeg@2.1.25':
dependencies:
'@types/node': 20.14.13
'@types/node': 20.16.1
'@types/luxon@3.4.2': {}
@ -933,9 +925,9 @@ snapshots:
'@types/ms@0.7.34': {}
'@types/node@20.14.13':
'@types/node@20.16.1':
dependencies:
undici-types: 5.26.5
undici-types: 6.19.8
ansi-colors@4.1.3: {}
@ -1043,32 +1035,32 @@ snapshots:
emoji-regex@8.0.0: {}
esbuild@0.23.0:
esbuild@0.23.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.23.0
'@esbuild/android-arm': 0.23.0
'@esbuild/android-arm64': 0.23.0
'@esbuild/android-x64': 0.23.0
'@esbuild/darwin-arm64': 0.23.0
'@esbuild/darwin-x64': 0.23.0
'@esbuild/freebsd-arm64': 0.23.0
'@esbuild/freebsd-x64': 0.23.0
'@esbuild/linux-arm': 0.23.0
'@esbuild/linux-arm64': 0.23.0
'@esbuild/linux-ia32': 0.23.0
'@esbuild/linux-loong64': 0.23.0
'@esbuild/linux-mips64el': 0.23.0
'@esbuild/linux-ppc64': 0.23.0
'@esbuild/linux-riscv64': 0.23.0
'@esbuild/linux-s390x': 0.23.0
'@esbuild/linux-x64': 0.23.0
'@esbuild/netbsd-x64': 0.23.0
'@esbuild/openbsd-arm64': 0.23.0
'@esbuild/openbsd-x64': 0.23.0
'@esbuild/sunos-x64': 0.23.0
'@esbuild/win32-arm64': 0.23.0
'@esbuild/win32-ia32': 0.23.0
'@esbuild/win32-x64': 0.23.0
'@esbuild/aix-ppc64': 0.23.1
'@esbuild/android-arm': 0.23.1
'@esbuild/android-arm64': 0.23.1
'@esbuild/android-x64': 0.23.1
'@esbuild/darwin-arm64': 0.23.1
'@esbuild/darwin-x64': 0.23.1
'@esbuild/freebsd-arm64': 0.23.1
'@esbuild/freebsd-x64': 0.23.1
'@esbuild/linux-arm': 0.23.1
'@esbuild/linux-arm64': 0.23.1
'@esbuild/linux-ia32': 0.23.1
'@esbuild/linux-loong64': 0.23.1
'@esbuild/linux-mips64el': 0.23.1
'@esbuild/linux-ppc64': 0.23.1
'@esbuild/linux-riscv64': 0.23.1
'@esbuild/linux-s390x': 0.23.1
'@esbuild/linux-x64': 0.23.1
'@esbuild/netbsd-x64': 0.23.1
'@esbuild/openbsd-arm64': 0.23.1
'@esbuild/openbsd-x64': 0.23.1
'@esbuild/sunos-x64': 0.23.1
'@esbuild/win32-arm64': 0.23.1
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
@ -1193,7 +1185,7 @@ snapshots:
dependencies:
brace-expansion: 2.0.1
mocha@10.7.0:
mocha@10.7.3:
dependencies:
ansi-colors: 4.1.3
browser-stdout: 1.3.1
@ -1255,9 +1247,9 @@ snapshots:
prevvy@7.5.0:
dependencies:
'@types/debug': 4.1.12
'@types/fluent-ffmpeg': 2.1.24
'@types/fluent-ffmpeg': 2.1.25
'@types/luxon': 3.4.2
'@types/node': 20.14.13
'@types/node': 20.16.1
debug: 4.3.6(supports-color@8.1.1)
execa: 8.0.1
fluent-ffmpeg: 2.1.3
@ -1286,31 +1278,31 @@ snapshots:
dependencies:
randombytes: 2.1.0
sharp@0.33.4:
sharp@0.33.5:
dependencies:
color: 4.2.3
detect-libc: 2.0.3
semver: 7.6.3
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.4
'@img/sharp-darwin-x64': 0.33.4
'@img/sharp-libvips-darwin-arm64': 1.0.2
'@img/sharp-libvips-darwin-x64': 1.0.2
'@img/sharp-libvips-linux-arm': 1.0.2
'@img/sharp-libvips-linux-arm64': 1.0.2
'@img/sharp-libvips-linux-s390x': 1.0.2
'@img/sharp-libvips-linux-x64': 1.0.2
'@img/sharp-libvips-linuxmusl-arm64': 1.0.2
'@img/sharp-libvips-linuxmusl-x64': 1.0.2
'@img/sharp-linux-arm': 0.33.4
'@img/sharp-linux-arm64': 0.33.4
'@img/sharp-linux-s390x': 0.33.4
'@img/sharp-linux-x64': 0.33.4
'@img/sharp-linuxmusl-arm64': 0.33.4
'@img/sharp-linuxmusl-x64': 0.33.4
'@img/sharp-wasm32': 0.33.4
'@img/sharp-win32-ia32': 0.33.4
'@img/sharp-win32-x64': 0.33.4
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
'@img/sharp-libvips-darwin-arm64': 1.0.4
'@img/sharp-libvips-darwin-x64': 1.0.4
'@img/sharp-libvips-linux-arm': 1.0.5
'@img/sharp-libvips-linux-arm64': 1.0.4
'@img/sharp-libvips-linux-s390x': 1.0.4
'@img/sharp-libvips-linux-x64': 1.0.4
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
'@img/sharp-linux-arm': 0.33.5
'@img/sharp-linux-arm64': 0.33.5
'@img/sharp-linux-s390x': 0.33.5
'@img/sharp-linux-x64': 0.33.5
'@img/sharp-linuxmusl-arm64': 0.33.5
'@img/sharp-linuxmusl-x64': 0.33.5
'@img/sharp-wasm32': 0.33.5
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
shebang-command@2.0.0:
dependencies:
@ -1350,19 +1342,19 @@ snapshots:
dependencies:
is-number: 7.0.0
tslib@2.6.3:
tslib@2.7.0:
optional: true
tsx@4.17.0:
dependencies:
esbuild: 0.23.0
esbuild: 0.23.1
get-tsconfig: 4.7.6
optionalDependencies:
fsevents: 2.3.3
typescript@5.5.4: {}
undici-types@5.26.5: {}
undici-types@6.19.8: {}
which@1.3.1:
dependencies:

View File

@ -1,5 +1,7 @@
{
"compilerOptions": {
"noEmit": true,
"allowImportingTsExtensions": true,
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,

View File

@ -17,156 +17,156 @@ importers:
devDependencies:
'@types/node':
specifier: ^20.14.9
version: 20.14.13
version: 20.16.1
tsup:
specifier: ^8.1.2
version: 8.2.3(typescript@5.5.4)
version: 8.2.4(typescript@5.5.4)
typescript:
specifier: ^5.5.3
version: 5.5.4
packages:
'@esbuild/aix-ppc64@0.23.0':
resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.23.0':
resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==}
'@esbuild/android-arm64@0.23.1':
resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.23.0':
resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==}
'@esbuild/android-arm@0.23.1':
resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.23.0':
resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==}
'@esbuild/android-x64@0.23.1':
resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.23.0':
resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==}
'@esbuild/darwin-arm64@0.23.1':
resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.23.0':
resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==}
'@esbuild/darwin-x64@0.23.1':
resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.23.0':
resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==}
'@esbuild/freebsd-arm64@0.23.1':
resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.23.0':
resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==}
'@esbuild/freebsd-x64@0.23.1':
resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.23.0':
resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==}
'@esbuild/linux-arm64@0.23.1':
resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.23.0':
resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==}
'@esbuild/linux-arm@0.23.1':
resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.23.0':
resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==}
'@esbuild/linux-ia32@0.23.1':
resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.23.0':
resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==}
'@esbuild/linux-loong64@0.23.1':
resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.23.0':
resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==}
'@esbuild/linux-mips64el@0.23.1':
resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.23.0':
resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==}
'@esbuild/linux-ppc64@0.23.1':
resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.23.0':
resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==}
'@esbuild/linux-riscv64@0.23.1':
resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.23.0':
resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==}
'@esbuild/linux-s390x@0.23.1':
resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.23.0':
resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==}
'@esbuild/linux-x64@0.23.1':
resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.23.0':
resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==}
'@esbuild/netbsd-x64@0.23.1':
resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.23.0':
resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
'@esbuild/openbsd-arm64@0.23.1':
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.23.0':
resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==}
'@esbuild/openbsd-x64@0.23.1':
resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.23.0':
resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==}
'@esbuild/sunos-x64@0.23.1':
resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.23.0':
resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==}
'@esbuild/win32-arm64@0.23.1':
resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.23.0':
resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==}
'@esbuild/win32-ia32@0.23.1':
resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.23.0':
resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==}
'@esbuild/win32-x64@0.23.1':
resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@ -209,91 +209,91 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@rollup/rollup-android-arm-eabi@4.19.1':
resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==}
'@rollup/rollup-android-arm-eabi@4.21.0':
resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.19.1':
resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==}
'@rollup/rollup-android-arm64@4.21.0':
resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.19.1':
resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==}
'@rollup/rollup-darwin-arm64@4.21.0':
resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.19.1':
resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==}
'@rollup/rollup-darwin-x64@4.21.0':
resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.19.1':
resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.19.1':
resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.19.1':
resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
'@rollup/rollup-linux-arm64-gnu@4.21.0':
resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.19.1':
resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
'@rollup/rollup-linux-arm64-musl@4.21.0':
resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.19.1':
resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
'@rollup/rollup-linux-s390x-gnu@4.21.0':
resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.19.1':
resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
'@rollup/rollup-linux-x64-gnu@4.21.0':
resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.19.1':
resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
'@rollup/rollup-linux-x64-musl@4.21.0':
resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.19.1':
resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
'@rollup/rollup-win32-arm64-msvc@4.21.0':
resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.19.1':
resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==}
'@rollup/rollup-win32-ia32-msvc@4.21.0':
resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.19.1':
resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==}
'@rollup/rollup-win32-x64-msvc@4.21.0':
resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==}
cpu: [x64]
os: [win32]
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/node@20.14.13':
resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@ -391,8 +391,8 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
esbuild@0.23.0:
resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==}
esbuild@0.23.1:
resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
engines: {node: '>=18'}
hasBin: true
@ -411,8 +411,8 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
foreground-child@3.2.1:
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
foreground-child@3.3.0:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
fsevents@2.3.3:
@ -440,8 +440,8 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
is-binary-path@2.1.0:
@ -502,8 +502,8 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mimic-fn@2.1.0:
@ -603,8 +603,8 @@ packages:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.19.1:
resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==}
rollup@4.21.0:
resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@ -680,8 +680,8 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tsup@8.2.3:
resolution: {integrity: sha512-6YNT44oUfXRbZuSMNmN36GzwPPIlD2wBccY7looM2fkTcxkf2NEmwr3OZuDZoySklnrIG4hoEtzy8yUXYOqNcg==}
tsup@8.2.4:
resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
@ -704,8 +704,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@ -728,76 +728,76 @@ packages:
snapshots:
'@esbuild/aix-ppc64@0.23.0':
'@esbuild/aix-ppc64@0.23.1':
optional: true
'@esbuild/android-arm64@0.23.0':
'@esbuild/android-arm64@0.23.1':
optional: true
'@esbuild/android-arm@0.23.0':
'@esbuild/android-arm@0.23.1':
optional: true
'@esbuild/android-x64@0.23.0':
'@esbuild/android-x64@0.23.1':
optional: true
'@esbuild/darwin-arm64@0.23.0':
'@esbuild/darwin-arm64@0.23.1':
optional: true
'@esbuild/darwin-x64@0.23.0':
'@esbuild/darwin-x64@0.23.1':
optional: true
'@esbuild/freebsd-arm64@0.23.0':
'@esbuild/freebsd-arm64@0.23.1':
optional: true
'@esbuild/freebsd-x64@0.23.0':
'@esbuild/freebsd-x64@0.23.1':
optional: true
'@esbuild/linux-arm64@0.23.0':
'@esbuild/linux-arm64@0.23.1':
optional: true
'@esbuild/linux-arm@0.23.0':
'@esbuild/linux-arm@0.23.1':
optional: true
'@esbuild/linux-ia32@0.23.0':
'@esbuild/linux-ia32@0.23.1':
optional: true
'@esbuild/linux-loong64@0.23.0':
'@esbuild/linux-loong64@0.23.1':
optional: true
'@esbuild/linux-mips64el@0.23.0':
'@esbuild/linux-mips64el@0.23.1':
optional: true
'@esbuild/linux-ppc64@0.23.0':
'@esbuild/linux-ppc64@0.23.1':
optional: true
'@esbuild/linux-riscv64@0.23.0':
'@esbuild/linux-riscv64@0.23.1':
optional: true
'@esbuild/linux-s390x@0.23.0':
'@esbuild/linux-s390x@0.23.1':
optional: true
'@esbuild/linux-x64@0.23.0':
'@esbuild/linux-x64@0.23.1':
optional: true
'@esbuild/netbsd-x64@0.23.0':
'@esbuild/netbsd-x64@0.23.1':
optional: true
'@esbuild/openbsd-arm64@0.23.0':
'@esbuild/openbsd-arm64@0.23.1':
optional: true
'@esbuild/openbsd-x64@0.23.0':
'@esbuild/openbsd-x64@0.23.1':
optional: true
'@esbuild/sunos-x64@0.23.0':
'@esbuild/sunos-x64@0.23.1':
optional: true
'@esbuild/win32-arm64@0.23.0':
'@esbuild/win32-arm64@0.23.1':
optional: true
'@esbuild/win32-ia32@0.23.0':
'@esbuild/win32-ia32@0.23.1':
optional: true
'@esbuild/win32-x64@0.23.0':
'@esbuild/win32-x64@0.23.1':
optional: true
'@isaacs/cliui@8.0.2':
@ -841,59 +841,59 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@rollup/rollup-android-arm-eabi@4.19.1':
'@rollup/rollup-android-arm-eabi@4.21.0':
optional: true
'@rollup/rollup-android-arm64@4.19.1':
'@rollup/rollup-android-arm64@4.21.0':
optional: true
'@rollup/rollup-darwin-arm64@4.19.1':
'@rollup/rollup-darwin-arm64@4.21.0':
optional: true
'@rollup/rollup-darwin-x64@4.19.1':
'@rollup/rollup-darwin-x64@4.21.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.19.1':
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.19.1':
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.19.1':
'@rollup/rollup-linux-arm64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.19.1':
'@rollup/rollup-linux-arm64-musl@4.21.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.19.1':
'@rollup/rollup-linux-s390x-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.19.1':
'@rollup/rollup-linux-x64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.19.1':
'@rollup/rollup-linux-x64-musl@4.21.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.19.1':
'@rollup/rollup-win32-arm64-msvc@4.21.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.19.1':
'@rollup/rollup-win32-ia32-msvc@4.21.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.19.1':
'@rollup/rollup-win32-x64-msvc@4.21.0':
optional: true
'@types/estree@1.0.5': {}
'@types/node@20.14.13':
'@types/node@20.16.1':
dependencies:
undici-types: 5.26.5
undici-types: 6.19.8
ansi-regex@5.0.1: {}
@ -926,9 +926,9 @@ snapshots:
dependencies:
fill-range: 7.1.1
bundle-require@5.0.0(esbuild@0.23.0):
bundle-require@5.0.0(esbuild@0.23.1):
dependencies:
esbuild: 0.23.0
esbuild: 0.23.1
load-tsconfig: 0.2.5
cac@6.7.14: {}
@ -975,32 +975,32 @@ snapshots:
emoji-regex@9.2.2: {}
esbuild@0.23.0:
esbuild@0.23.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.23.0
'@esbuild/android-arm': 0.23.0
'@esbuild/android-arm64': 0.23.0
'@esbuild/android-x64': 0.23.0
'@esbuild/darwin-arm64': 0.23.0
'@esbuild/darwin-x64': 0.23.0
'@esbuild/freebsd-arm64': 0.23.0
'@esbuild/freebsd-x64': 0.23.0
'@esbuild/linux-arm': 0.23.0
'@esbuild/linux-arm64': 0.23.0
'@esbuild/linux-ia32': 0.23.0
'@esbuild/linux-loong64': 0.23.0
'@esbuild/linux-mips64el': 0.23.0
'@esbuild/linux-ppc64': 0.23.0
'@esbuild/linux-riscv64': 0.23.0
'@esbuild/linux-s390x': 0.23.0
'@esbuild/linux-x64': 0.23.0
'@esbuild/netbsd-x64': 0.23.0
'@esbuild/openbsd-arm64': 0.23.0
'@esbuild/openbsd-x64': 0.23.0
'@esbuild/sunos-x64': 0.23.0
'@esbuild/win32-arm64': 0.23.0
'@esbuild/win32-ia32': 0.23.0
'@esbuild/win32-x64': 0.23.0
'@esbuild/aix-ppc64': 0.23.1
'@esbuild/android-arm': 0.23.1
'@esbuild/android-arm64': 0.23.1
'@esbuild/android-x64': 0.23.1
'@esbuild/darwin-arm64': 0.23.1
'@esbuild/darwin-x64': 0.23.1
'@esbuild/freebsd-arm64': 0.23.1
'@esbuild/freebsd-x64': 0.23.1
'@esbuild/linux-arm': 0.23.1
'@esbuild/linux-arm64': 0.23.1
'@esbuild/linux-ia32': 0.23.1
'@esbuild/linux-loong64': 0.23.1
'@esbuild/linux-mips64el': 0.23.1
'@esbuild/linux-ppc64': 0.23.1
'@esbuild/linux-riscv64': 0.23.1
'@esbuild/linux-s390x': 0.23.1
'@esbuild/linux-x64': 0.23.1
'@esbuild/netbsd-x64': 0.23.1
'@esbuild/openbsd-arm64': 0.23.1
'@esbuild/openbsd-x64': 0.23.1
'@esbuild/sunos-x64': 0.23.1
'@esbuild/win32-arm64': 0.23.1
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
execa@5.1.1:
dependencies:
@ -1020,7 +1020,7 @@ snapshots:
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.7
micromatch: 4.0.8
fastq@1.17.1:
dependencies:
@ -1030,7 +1030,7 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
foreground-child@3.2.1:
foreground-child@3.3.0:
dependencies:
cross-spawn: 7.0.3
signal-exit: 4.1.0
@ -1046,7 +1046,7 @@ snapshots:
glob@10.4.5:
dependencies:
foreground-child: 3.2.1
foreground-child: 3.3.0
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
@ -1058,13 +1058,13 @@ snapshots:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
ignore: 5.3.1
ignore: 5.3.2
merge2: 1.4.1
slash: 3.0.0
human-signals@2.1.0: {}
ignore@5.3.1: {}
ignore@5.3.2: {}
is-binary-path@2.1.0:
dependencies:
@ -1106,7 +1106,7 @@ snapshots:
merge2@1.4.1: {}
micromatch@4.0.7:
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
@ -1172,26 +1172,26 @@ snapshots:
reusify@1.0.4: {}
rollup@4.19.1:
rollup@4.21.0:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.19.1
'@rollup/rollup-android-arm64': 4.19.1
'@rollup/rollup-darwin-arm64': 4.19.1
'@rollup/rollup-darwin-x64': 4.19.1
'@rollup/rollup-linux-arm-gnueabihf': 4.19.1
'@rollup/rollup-linux-arm-musleabihf': 4.19.1
'@rollup/rollup-linux-arm64-gnu': 4.19.1
'@rollup/rollup-linux-arm64-musl': 4.19.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.19.1
'@rollup/rollup-linux-riscv64-gnu': 4.19.1
'@rollup/rollup-linux-s390x-gnu': 4.19.1
'@rollup/rollup-linux-x64-gnu': 4.19.1
'@rollup/rollup-linux-x64-musl': 4.19.1
'@rollup/rollup-win32-arm64-msvc': 4.19.1
'@rollup/rollup-win32-ia32-msvc': 4.19.1
'@rollup/rollup-win32-x64-msvc': 4.19.1
'@rollup/rollup-android-arm-eabi': 4.21.0
'@rollup/rollup-android-arm64': 4.21.0
'@rollup/rollup-darwin-arm64': 4.21.0
'@rollup/rollup-darwin-x64': 4.21.0
'@rollup/rollup-linux-arm-gnueabihf': 4.21.0
'@rollup/rollup-linux-arm-musleabihf': 4.21.0
'@rollup/rollup-linux-arm64-gnu': 4.21.0
'@rollup/rollup-linux-arm64-musl': 4.21.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.21.0
'@rollup/rollup-linux-riscv64-gnu': 4.21.0
'@rollup/rollup-linux-s390x-gnu': 4.21.0
'@rollup/rollup-linux-x64-gnu': 4.21.0
'@rollup/rollup-linux-x64-musl': 4.21.0
'@rollup/rollup-win32-arm64-msvc': 4.21.0
'@rollup/rollup-win32-ia32-msvc': 4.21.0
'@rollup/rollup-win32-x64-msvc': 4.21.0
fsevents: 2.3.3
run-parallel@1.2.0:
@ -1266,21 +1266,21 @@ snapshots:
ts-interface-checker@0.1.13: {}
tsup@8.2.3(typescript@5.5.4):
tsup@8.2.4(typescript@5.5.4):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.0)
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
chokidar: 3.6.0
consola: 3.2.3
debug: 4.3.6
esbuild: 0.23.0
esbuild: 0.23.1
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
picocolors: 1.0.1
postcss-load-config: 6.0.1
resolve-from: 5.0.0
rollup: 4.19.1
rollup: 4.21.0
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tree-kill: 1.2.2
@ -1294,7 +1294,7 @@ snapshots:
typescript@5.5.4: {}
undici-types@5.26.5: {}
undici-types@6.19.8: {}
webidl-conversions@4.0.2: {}

View File

@ -31,7 +31,7 @@ importers:
version: 3.6.0
qs:
specifier: ^6.12.3
version: 6.12.3
version: 6.13.0
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^5.62.0
@ -53,7 +53,7 @@ importers:
version: 3.1.4
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.0.0)(typescript@5.5.4)
version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4)
typescript:
specifier: ^5.5.3
version: 5.5.4
@ -135,8 +135,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@22.0.0':
resolution: {integrity: sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==}
'@types/node@22.5.0':
resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==}
'@types/qs@6.9.15':
resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==}
@ -573,8 +573,8 @@ packages:
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
engines: {node: '>= 4'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
import-fresh@3.3.0:
@ -654,8 +654,8 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
minimatch@3.1.2:
@ -732,8 +732,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qs@6.12.3:
resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
queue-microtask@1.2.3:
@ -861,8 +861,8 @@ packages:
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tsutils@3.21.0:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@ -886,8 +886,8 @@ packages:
undefsafe@2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
undici-types@6.11.1:
resolution: {integrity: sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@ -995,9 +995,9 @@ snapshots:
'@types/json-schema@7.0.15': {}
'@types/node@22.0.0':
'@types/node@22.5.0':
dependencies:
undici-types: 6.11.1
undici-types: 6.19.8
'@types/qs@6.9.15': {}
@ -1013,7 +1013,7 @@ snapshots:
debug: 4.3.6(supports-color@5.5.0)
eslint: 7.32.0
graphemer: 1.4.0
ignore: 5.3.1
ignore: 5.3.2
natural-compare-lite: 1.4.0
semver: 7.6.3
tsutils: 3.21.0(typescript@5.5.4)
@ -1304,7 +1304,7 @@ snapshots:
dependencies:
'@typescript-eslint/utils': 6.21.0(eslint@7.32.0)(typescript@5.5.4)
eslint: 7.32.0
tslib: 2.6.3
tslib: 2.7.0
tsutils: 3.21.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
@ -1400,7 +1400,7 @@ snapshots:
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.7
micromatch: 4.0.8
fast-json-stable-stringify@2.1.0: {}
@ -1467,7 +1467,7 @@ snapshots:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
ignore: 5.3.1
ignore: 5.3.2
merge2: 1.4.1
slash: 3.0.0
@ -1497,7 +1497,7 @@ snapshots:
ignore@4.0.6: {}
ignore@5.3.1: {}
ignore@5.3.2: {}
import-fresh@3.3.0:
dependencies:
@ -1561,7 +1561,7 @@ snapshots:
merge2@1.4.1: {}
micromatch@4.0.7:
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
@ -1632,7 +1632,7 @@ snapshots:
punycode@2.3.1: {}
qs@6.12.3:
qs@6.13.0:
dependencies:
side-channel: 1.0.6
@ -1736,14 +1736,14 @@ snapshots:
dependencies:
typescript: 5.5.4
ts-node@10.9.2(@types/node@22.0.0)(typescript@5.5.4):
ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 22.0.0
'@types/node': 22.5.0
acorn: 8.12.1
acorn-walk: 8.3.3
arg: 4.1.3
@ -1756,7 +1756,7 @@ snapshots:
tslib@1.14.1: {}
tslib@2.6.3: {}
tslib@2.7.0: {}
tsutils@3.21.0(typescript@5.5.4):
dependencies:
@ -1773,7 +1773,7 @@ snapshots:
undefsafe@2.0.5: {}
undici-types@6.11.1: {}
undici-types@6.19.8: {}
uri-js@4.4.1:
dependencies:

View File

@ -1,4 +0,0 @@
engine-strict=true
package-manager-strict=true
use-node-version=20.13.1
node-version=20.13.1

View File

@ -1,25 +0,0 @@
import { describe } from 'mocha'
import { expect } from 'chai';
import { getInitialRoomDossier, getRandomRoom } from './cb.js'
describe('cb', function () {
describe('integration', function () {
describe('getInitialRoomDossier', function () {
this.timeout(1000*16)
it('should return json', async function () {
const dossier = await getInitialRoomDossier('https://chaturbate.com/projektmelody')
expect(dossier).to.have.property('wschat_host', 'dossier was missing wschat_host')
})
})
describe('getRandomRoom', function () {
it('should return a Room object of an online room', async function () {
this.timeout(1000*16)
const room = await getRandomRoom()
expect(room).to.have.property('url')
expect(room).to.have.property('name')
expect(room.name).to.match(/[a-z_]/)
expect(room.url).to.match(/https:\/\//)
})
})
})
})

View File

@ -1,109 +0,0 @@
import * as cheerio from 'cheerio'
import fetch from 'node-fetch'
export interface ChaturbateModel {
gender: string;
location: string;
current_show: 'public' | 'private';
username: string;
room_subject: string;
tags: string[];
is_new: boolean;
num_users: number;
num_followers: number;
country: string;
spoken_languages: string;
display_name: string;
birthday: string;
is_hd: boolean;
age: number;
seconds_online: number;
image_url: string;
image_url_360x270: string;
chat_room_url_revshare: string;
iframe_embed_revshare: string;
chat_room_url: string;
iframe_embed: string;
slug: string;
}
export interface ChaturbateOnlineModelsResponse {
results: ChaturbateModel[],
count: number
}
export interface Room {
name: string;
url: string;
}
/**
*
* @param {String} roomUrl example: https://chaturbate.com/projektmelody
* @returns {Object} initialRoomDossier
*/
export async function getInitialRoomDossier(roomUrl: string) {
try {
const res = await fetch(roomUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
}
});
const body = await res.text()
const $ = cheerio.load(body);
let rawScript = $('script:contains(window.initialRoomDossier)').html();
if (!rawScript) {
throw new Error('window.initialRoomDossier is null. This could mean the channel is in password mode');
}
let rawDossier = rawScript.slice(rawScript.indexOf('"'), rawScript.lastIndexOf('"') + 1);
let dossier = JSON.parse(JSON.parse(rawDossier));
return dossier;
} catch (error) {
if (error instanceof Error) {
// Handle the error gracefully
console.error(`Error fetching initial room dossier: ${error.message}`);
return null; // Or any other appropriate action you want to take
} else {
console.error('caught an exotic error, uh-oh')
}
}
}
export async function getRandomRoom(): Promise<Room> {
try {
const res = await fetch('https://chaturbate.com/api/public/affiliates/onlinerooms/?wm=DiPkB&client_ip=request_ip', {
headers: {
accept: 'application/json'
}
});
const data = await res.json() as ChaturbateOnlineModelsResponse;
if (!data || !Array.isArray(data.results) || data.results.length === 0) {
throw new Error('No results found');
}
const results = data.results;
const randomIndex = Math.floor(Math.random() * results.length);
if (!results[randomIndex]) {
throw new Error('No result found at random index');
}
const username = results[randomIndex].username;
return {
url: `https://chaturbate.com/${username}`,
name: username
}
} catch (error) {
if (error instanceof Error) {
console.error(`Error in getRandomRoom: ${error.message}`);
} else {
console.error('An unexpected error occurred');
}
throw error; // Re-throw the error to propagate it further
}
}

View File

@ -1,36 +0,0 @@
import { download, getTmpFile } from '@futureporn/utils';
const regex = {
username: new RegExp(/^https:\/\/fansly\.com\/(?:live\/)?([^\/]+)/)
}
const normalize = (url) => {
if (!url) throw new Error('normalized received a null or undefined url.');
return fromUsername(fansly.regex.username.exec(url).at(1))
}
const fromUsername = (username) => `https://fansly.com/${username}`
const image = async function image (fanslyUserId) {
if (!fanslyUserId) throw new Error(`first arg passed to fansly.data.image must be a {string} fanslyUserId`);
const url = `https://api.fansly.com/api/v1/account/${fanslyUserId}/avatar`
const filePath = getTmpFile('avatar.jpg')
return download({ filePath, url })
}
const url = {
normalize,
fromUsername
}
const data = {
image
}
const fansly = {
regex,
url,
data,
}
export default fansly

View File

@ -1,34 +0,0 @@
import { expect } from 'chai'
import fansly from './fansly.js'
import { describe } from 'mocha'
describe('fansly', function () {
describe('regex', function () {
describe('username', function () {
it('should get the username of the channel', function () {
expect(fansly.regex.username.exec('https://fansly.com/18Plus/posts').at(1)).to.equal('18Plus')
expect(fansly.regex.username.exec('https://fansly.com/projektmelody/posts').at(1)).to.equal('projektmelody')
expect(fansly.regex.username.exec('https://fansly.com/GoodKittenVR').at(1)).to.equal('GoodKittenVR')
expect(fansly.regex.username.exec('https://fansly.com/live/MzLewdieB').at(1)).to.equal('MzLewdieB')
expect(fansly.regex.username.exec('https://fansly.com/live/340602399334871040').at(1)).to.equal('340602399334871040')
})
})
})
describe('url', function () {
describe('fromUsername', function () {
it('should accept a channel name and give us a valid channel URL', function () {
expect(fansly.url.fromUsername('projektmelody')).to.equal('https://fansly.com/projektmelody')
expect(fansly.url.fromUsername('GoodKittenVR')).to.equal('https://fansly.com/GoodKittenVR')
expect(fansly.url.fromUsername('MzLewdieB')).to.equal('https://fansly.com/MzLewdieB')
expect(fansly.url.fromUsername('340602399334871040')).to.equal('https://fansly.com/340602399334871040')
})
})
describe('normalize', function () {
it('should accept a live URL and return a normal channel url.', function () {
expect(fansly.url.normalize('https://fansly.com/live/projektmelody')).to.equal('https://fansly.com/projektmelody')
expect(fansly.url.normalize('https://fansly.com/live/340602399334871040')).to.equal('https://fansly.com/340602399334871040')
expect(fansly.url.normalize('https://fansly.com/live/GoodKittenVR')).to.equal('https://fansly.com/GoodKittenVR')
})
})
})
})

View File

@ -1,78 +0,0 @@
'use strict'
/**
* watches an e-mail inbox for going live notifications
*/
// import { checkEmail } from './parsers.js'
// // import { createStreamInDb } from './signals.js'
// import { Email } from './imap.js'
import { Client, Connection } from '@temporalio/client'
// import { type NotificationData } from '@futureporn/types'
// import { type FetchMessageObject } from 'imapflow'
import { createId } from '@paralleldrive/cuid2'
console.log('connecting to temporal...')
const connection = await Connection.connect({ address: 'temporal-frontend.futureporn.svc.cluster.local:7233' });
const client = new Client({ connection, namespace: 'futureporn' });
// async function handleMessage({ email, msg }: { email: Email, msg: FetchMessageObject }) {
// try {
// console.log(' ✏️ loading message')
// const body = await email.loadMessage(msg.uid)
// console.log(' ✏️ checking e-mail')
// const { isMatch, url, platform, channel, displayName, date, userId, avatar }: NotificationData = (await checkEmail(body) )
// if (isMatch) {
// const wfId = `process-email-${createId()}`
// // console.log(` ✏️ [DRY] starting Temporal workflow ${wfId} @todo actually start temporal workflow`)
// // await signalRealtime({ url, platform, channel, displayName, date, userId, avatar })
// // @todo invoke a Temporal workflow here
// console.log(' ✏️✏️ starting Temporal workflow')
// // const handle = await client.workflow.start(WorkflowA, {
// // workflowId: wfId,
// // taskQueue: 'futureporn'
// // });
// // // const handle = await client.workflow.start(processNotifEmail, {
// // // workflowId: wfId,
// // // taskQueue: 'futureporn',
// // // args: [{ url, platform, channel, displayName, date, userId, avatar }]
// // // });
// // // const handle = client.getHandle(workflowId);
// // const result = await handle.result()
// // console.log(`result of the workflow is as follows`)
// // console.log(result)
// }
// console.log(' ✏️ archiving e-mail')
// await email.archiveMessage(msg.uid)
// } catch (e) {
// // console.error('error encoutered')
// console.error(`An error was encountered while handling the following e-mail message.\n${JSON.stringify(msg, null, 2)}\nError as follows.`)
// console.error(e)
// }
// }
(async () => {
// const email = new Email()
// email.once('message', (msg: FetchMessageObject) => handleMessage({ email, msg }))
// await email.connect()
console.log('scout is starting @todo @todo')
const wfId = `process-email-${createId()}`
// @todo
// const handle = await client.workflow.start(WorkflowA, {
// workflowId: wfId,
// taskQueue: 'futureporn',
// args: [ 'CJ_Clippy' ]
// });
// const result = await handle.result()
// console.log(result)
})()
// console.log('init')

View File

@ -1,23 +0,0 @@
import { defineConfig } from "tsup";
import { exec } from 'child_process';
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm"],
target: "node20",
clean: true,
sourcemap: true,
/**
* The common package is using the internal packages approach, so it needs to
* be transpiled / bundled together with the deployed code.
*/
// noExternal: ["@futureporn/temporal-workflows"],
/**
* We do not use tsup for generating d.ts files because it can not generate type
* the definition maps required for go-to-definition to work in our IDE. We
* use tsc for that.
*/
onSuccess: async () => {
exec('tsc --emitDeclarationOnly');
},
});

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,94 @@ declare namespace Futureporn {
type PlatformNotificationType = 'email' | 'manual' | 'twitter'
type ArchiveStatus = 'good' | 'issue' | 'missing'
type RecordingState = 'recording' | 'stalled' | 'aborted' | 'failed' | 'finished'
type Status = Partial<'pending_recording' | RecordingState>
type ProcessingState = 'processing'
type WaitingState = 'pending_recording'
type Status = Partial<WaitingState | ProcessingState | RecordingState>
interface S3File {
s3_key: string;
s3_id?: string;
bucket: string;
created_at?: Date;
updated_at?: Date;
}
interface VtuberResponse {
id: string;
}
interface VtuberRecord {
id: string;
display_name: string;
chaturbate: string;
twitter: string;
patreon: string;
twitch: string;
tiktok: string;
onlyfans: string;
youtube: string;
linktree: string;
carrd: string;
fansly: string;
pornhub: string;
discord: string;
reddit: string;
throne: string;
instagram: string;
facebook: string;
merch: string;
slug: string;
image: string;
theme_color: string;
image_blur: string;
fansly_id: string;
chaturbate_id: string;
twitter_id: string;
}
interface VodRecord {
id: string;
stream: Stream;
stream_id: string;
created_at: string;
updated_at: string;
published_at?: string;
title?: string;
date: string;
mux_asset: MuxAsset;
thumbnail?: S3File;
vtuber: string;
ipfs_cid?: string;
torrent?: string;
announce_title?: string;
announce_url?: string;
note?: string;
url: string
}
interface VodResponse {
id: string;
stream: Stream;
stream_id: string;
created_at: string;
updated_at: string;
published_at?: string;
title?: string;
date: string;
mux_asset: MuxAsset;
thumbnail?: S3File;
vtuber: Vtuber;
tags?: Tag[];
timestamps?: Timestamp[];
ipfs_cid?: string;
s3_file?: S3File;
torrent?: string;
announce_title?: string;
announce_url?: string;
uploader?: User;
note?: string;
url: string
}
interface Stream {
id: string;
@ -19,6 +106,7 @@ declare namespace Futureporn {
updated_at: Date;
vtuber: string;
tweet: string;
vods?: Vod[];
archive_status: ArchiveStatus;
is_chaturbate_stream: Boolean;
is_fansly_stream: Boolean;

View File

@ -14,7 +14,6 @@
"author": "@CJ_Clippy",
"license": "Unlicense",
"dependencies": {
"@futureporn/scout": "workspace:*",
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^20.14.9",
"@types/slug": "^5.0.8",

View File

@ -8,18 +8,15 @@ importers:
.:
dependencies:
'@futureporn/scout':
specifier: workspace:*
version: link:../scout
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@types/node':
specifier: ^20.14.9
version: 20.14.13
version: 20.16.1
'@types/slug':
specifier: ^5.0.8
version: 5.0.8
version: 5.0.9
p-retry:
specifier: ^5.1.2
version: 5.1.2
@ -29,7 +26,7 @@ importers:
devDependencies:
'@types/chai':
specifier: ^4.3.16
version: 4.3.16
version: 4.3.17
'@types/mocha':
specifier: ^10.0.7
version: 10.0.7
@ -38,13 +35,13 @@ importers:
version: 5.1.1
mocha:
specifier: ^10.6.0
version: 10.7.0
version: 10.7.3
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.14.13)(typescript@5.5.4)
version: 10.9.2(@types/node@20.16.1)(typescript@5.5.4)
tsx:
specifier: ^4.16.2
version: 4.16.2
version: 4.17.0
packages:
@ -52,141 +49,147 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
'@esbuild/android-arm64@0.23.1':
resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
'@esbuild/android-arm@0.23.1':
resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
'@esbuild/android-x64@0.23.1':
resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
'@esbuild/darwin-arm64@0.23.1':
resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
'@esbuild/darwin-x64@0.23.1':
resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
'@esbuild/freebsd-arm64@0.23.1':
resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
'@esbuild/freebsd-x64@0.23.1':
resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
'@esbuild/linux-arm64@0.23.1':
resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
'@esbuild/linux-arm@0.23.1':
resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
'@esbuild/linux-ia32@0.23.1':
resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
'@esbuild/linux-loong64@0.23.1':
resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
'@esbuild/linux-mips64el@0.23.1':
resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
'@esbuild/linux-ppc64@0.23.1':
resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
'@esbuild/linux-riscv64@0.23.1':
resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
'@esbuild/linux-s390x@0.23.1':
resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
'@esbuild/linux-x64@0.23.1':
resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
'@esbuild/netbsd-x64@0.23.1':
resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
'@esbuild/openbsd-arm64@0.23.1':
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.23.1':
resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
'@esbuild/sunos-x64@0.23.1':
resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
'@esbuild/win32-arm64@0.23.1':
resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
'@esbuild/win32-ia32@0.23.1':
resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
'@esbuild/win32-x64@0.23.1':
resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@ -219,20 +222,20 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/chai@4.3.16':
resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/mocha@10.0.7':
resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==}
'@types/node@20.14.13':
resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/retry@0.12.1':
resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==}
'@types/slug@5.0.8':
resolution: {integrity: sha512-mblTWR1OST257k1gZ3QvqG+ERSr8Ea6dyM1FH6Jtm4jeXi0/r0/95VNctofuiywPxCVQuE8AuFoqmvJ4iVUlXQ==}
'@types/slug@5.0.9':
resolution: {integrity: sha512-6Yp8BSplP35Esa/wOG1wLNKiqXevpQTEF/RcL/NV6BBQaMmZh4YlDwCgrrFSoUE4xAGvnKd5c+lkQJmPrBAzfQ==}
acorn-walk@8.3.3:
resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==}
@ -347,9 +350,9 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
esbuild@0.23.1:
resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
engines: {node: '>=18'}
hasBin: true
escalade@3.1.2:
@ -464,8 +467,8 @@ packages:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
mocha@10.7.0:
resolution: {integrity: sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==}
mocha@10.7.3:
resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==}
engines: {node: '>= 14.0.0'}
hasBin: true
@ -572,8 +575,8 @@ packages:
'@swc/wasm':
optional: true
tsx@4.16.2:
resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==}
tsx@4.17.0:
resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -582,8 +585,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@ -628,73 +631,76 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@esbuild/aix-ppc64@0.21.5':
'@esbuild/aix-ppc64@0.23.1':
optional: true
'@esbuild/android-arm64@0.21.5':
'@esbuild/android-arm64@0.23.1':
optional: true
'@esbuild/android-arm@0.21.5':
'@esbuild/android-arm@0.23.1':
optional: true
'@esbuild/android-x64@0.21.5':
'@esbuild/android-x64@0.23.1':
optional: true
'@esbuild/darwin-arm64@0.21.5':
'@esbuild/darwin-arm64@0.23.1':
optional: true
'@esbuild/darwin-x64@0.21.5':
'@esbuild/darwin-x64@0.23.1':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
'@esbuild/freebsd-arm64@0.23.1':
optional: true
'@esbuild/freebsd-x64@0.21.5':
'@esbuild/freebsd-x64@0.23.1':
optional: true
'@esbuild/linux-arm64@0.21.5':
'@esbuild/linux-arm64@0.23.1':
optional: true
'@esbuild/linux-arm@0.21.5':
'@esbuild/linux-arm@0.23.1':
optional: true
'@esbuild/linux-ia32@0.21.5':
'@esbuild/linux-ia32@0.23.1':
optional: true
'@esbuild/linux-loong64@0.21.5':
'@esbuild/linux-loong64@0.23.1':
optional: true
'@esbuild/linux-mips64el@0.21.5':
'@esbuild/linux-mips64el@0.23.1':
optional: true
'@esbuild/linux-ppc64@0.21.5':
'@esbuild/linux-ppc64@0.23.1':
optional: true
'@esbuild/linux-riscv64@0.21.5':
'@esbuild/linux-riscv64@0.23.1':
optional: true
'@esbuild/linux-s390x@0.21.5':
'@esbuild/linux-s390x@0.23.1':
optional: true
'@esbuild/linux-x64@0.21.5':
'@esbuild/linux-x64@0.23.1':
optional: true
'@esbuild/netbsd-x64@0.21.5':
'@esbuild/netbsd-x64@0.23.1':
optional: true
'@esbuild/openbsd-x64@0.21.5':
'@esbuild/openbsd-arm64@0.23.1':
optional: true
'@esbuild/sunos-x64@0.21.5':
'@esbuild/openbsd-x64@0.23.1':
optional: true
'@esbuild/win32-arm64@0.21.5':
'@esbuild/sunos-x64@0.23.1':
optional: true
'@esbuild/win32-ia32@0.21.5':
'@esbuild/win32-arm64@0.23.1':
optional: true
'@esbuild/win32-x64@0.21.5':
'@esbuild/win32-ia32@0.23.1':
optional: true
'@esbuild/win32-x64@0.23.1':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
@ -720,17 +726,17 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
'@types/chai@4.3.16': {}
'@types/chai@4.3.17': {}
'@types/mocha@10.0.7': {}
'@types/node@20.14.13':
'@types/node@20.16.1':
dependencies:
undici-types: 5.26.5
undici-types: 6.19.8
'@types/retry@0.12.1': {}
'@types/slug@5.0.8': {}
'@types/slug@5.0.9': {}
acorn-walk@8.3.3:
dependencies:
@ -830,31 +836,32 @@ snapshots:
emoji-regex@8.0.0: {}
esbuild@0.21.5:
esbuild@0.23.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
'@esbuild/aix-ppc64': 0.23.1
'@esbuild/android-arm': 0.23.1
'@esbuild/android-arm64': 0.23.1
'@esbuild/android-x64': 0.23.1
'@esbuild/darwin-arm64': 0.23.1
'@esbuild/darwin-x64': 0.23.1
'@esbuild/freebsd-arm64': 0.23.1
'@esbuild/freebsd-x64': 0.23.1
'@esbuild/linux-arm': 0.23.1
'@esbuild/linux-arm64': 0.23.1
'@esbuild/linux-ia32': 0.23.1
'@esbuild/linux-loong64': 0.23.1
'@esbuild/linux-mips64el': 0.23.1
'@esbuild/linux-ppc64': 0.23.1
'@esbuild/linux-riscv64': 0.23.1
'@esbuild/linux-s390x': 0.23.1
'@esbuild/linux-x64': 0.23.1
'@esbuild/netbsd-x64': 0.23.1
'@esbuild/openbsd-arm64': 0.23.1
'@esbuild/openbsd-x64': 0.23.1
'@esbuild/sunos-x64': 0.23.1
'@esbuild/win32-arm64': 0.23.1
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
@ -948,7 +955,7 @@ snapshots:
dependencies:
brace-expansion: 2.0.1
mocha@10.7.0:
mocha@10.7.3:
dependencies:
ansi-colors: 4.1.3
browser-stdout: 1.3.1
@ -1046,14 +1053,14 @@ snapshots:
dependencies:
is-number: 7.0.0
ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4):
ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.14.13
'@types/node': 20.16.1
acorn: 8.12.1
acorn-walk: 8.3.3
arg: 4.1.3
@ -1064,16 +1071,16 @@ snapshots:
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
tsx@4.16.2:
tsx@4.17.0:
dependencies:
esbuild: 0.21.5
esbuild: 0.23.1
get-tsconfig: 4.7.6
optionalDependencies:
fsevents: 2.3.3
typescript@5.5.4: {}
undici-types@5.26.5: {}
undici-types@6.19.8: {}
v8-compile-cache-lib@3.0.1: {}

View File

@ -1,4 +1,4 @@
import { fpSlugify, getTmpFile, download, getPackageVersion, __dirname } from './index.js'
import { fpSlugify, getTmpFile, download, getPackageVersion, __dirname } from './index.ts'
import { expect } from 'chai'
import { describe } from 'mocha'
import { join } from 'path'

View File

@ -45,10 +45,18 @@ export function getTmpFile(str: string): string {
* @param {String} url
* @returns {String} filePath
*
* greetz https://stackoverflow.com/a/74722818/1004931
* @see https://stackoverflow.com/a/74722818/1004931
* greetz chatgpt
*/
export async function download({ url, filePath }: { url: string; filePath?: string }): Promise<string | null> {
if (!url) throw new Error(`second arg passed to download() must be a {string} url`);
export async function download({
url,
filePath,
}: {
url: string;
filePath?: string;
}): Promise<string> {
if (!url) throw new Error(`The 'url' parameter is required and must be a string.`);
const fileBaseName = basename(url);
filePath = filePath || join(os.tmpdir(), `${createId()}_${fileBaseName}`);
const stream = fs.createWriteStream(filePath);
@ -61,24 +69,21 @@ export async function download({ url, filePath }: { url: string; filePath?: stri
});
const { body } = response;
if (!body) throw new Error('body was null');
await finished(Readable.fromWeb(body).pipe(stream));
if (!body) throw new Error('Response body was null.');
// Convert the Fetch API ReadableStream to a Node.js Readable stream
const nodeStream = Readable.from(body as any);
await finished(nodeStream.pipe(stream));
// Abort retrying if the resource doesn't exist
if (response.status === 404) {
throw new Error(response.statusText);
throw new Error('Resource not found (404).');
}
return;
};
try {
await pRetry(requestData, { retries: 3 });
} catch (e) {
console.error(`utils.download failed to download ${url}`);
console.error(e);
return null;
}
await requestData();
return filePath;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/mocharc.json",
"extensions": ["ts"],
"spec": "./src/**/*.spec.ts",
"require": "tsx"
}

View File

@ -24,3 +24,5 @@ Example invocation as follows.
DISCORD_TOKEN=your-token-goes-here DISCORD_CHANNEL_ID=1185024773231759481 node index.js
### Register our custom commands with Discord

View File

@ -13,6 +13,6 @@
# * * * * * task ?opts {payload}
## every n minutes, we see which /records are stale and we mark them as such.
## every n minutes, we see which /vods are stale and we mark them as such.
## this prevents stalled Record updates by marking stalled recordings as stopped
* * * * * update_stream_statuses ?max=1 { stalled_minutes:1 }
* * * * * update_svod_statuses ?max=1 { stalled_minutes:1 }

View File

@ -5,7 +5,9 @@
"description": "Futureporn Discord bot",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0",
"test": "echo \"please use one of test.integration or test.unit",
"test.integration": "mocha -g integration",
"test.unit": "mocha -g unit",
"start": "node ./dist/index.js",
"dev": "pnpm run dev.nodemon # yes this is crazy to have nodemon execute tsx, but it's the only way I have found to get live reloading in TS/ESM/docker with Graphile Worker's way of loading tasks",
"dev.tsx": "tsx ./src/index.ts",
@ -23,6 +25,8 @@
"dependencies": {
"@discordeno/bot": "19.0.0-next.746f0a9",
"@discordeno/rest": "19.0.0-next.b3a8c86",
"@futureporn/scout": "workspace:^",
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^22.2.0",
"@types/qs": "^6.9.15",
"date-fns": "^3.6.0",
@ -32,11 +36,18 @@
"graphile-config": "0.0.1-beta.9",
"graphile-worker": "^0.16.6",
"node-fetch": "^3.3.2",
"p-retry": "^5.1.2",
"pg": "8.8.0",
"pretty-bytes": "^6.1.1",
"qs": "^6.13.0"
"qs": "^6.13.0",
"rate-limiter-flexible": "^5.0.3"
},
"devDependencies": {
"@futureporn/types": "workspace:^",
"@types/chai": "^4.3.16",
"@types/mocha": "^10.0.7",
"chai": "^5.1.1",
"mocha": "^10.7.3",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"tsx": "^4.17.0",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
import { ApplicationCommandTypes, logger, type Interaction } from '@discordeno/bot'
import { createCommand } from '../commands.ts'
import getStreamFromDatabase from '../fetchers/getStreamFromDatabase.ts'
import patchStreamInDatabase from '../fetchers/patchStreamInDatabase.ts'
import { quickAddJob, type WorkerUtilsOptions } from 'graphile-worker'
import { configs } from '../config.ts'
function throwErr(msg: string) {
logger.error(msg)
throw new Error(msg)
}
createCommand({
name: 'process',
description: 'Process a vod for publishing',
type: ApplicationCommandTypes.ChatInput,
async execute(interaction: Interaction) {
const discord_message_id = String(interaction?.message?.id)
logger.info(`process command begins.`)
if (!discord_message_id) return throwErr('failed to get discord message id');
const stream = await getStreamFromDatabase(discord_message_id)
if (!stream) return throwErr('failed to get stream');
const options: WorkerUtilsOptions = { connectionString: configs.connectionString }
logger.info(`now we will quickAddJob process_video`)
await quickAddJob(options, 'process_video', { stream_id: stream.id })
logger.info(`now we will patchStreamInDatabase`)
await patchStreamInDatabase(stream.id, { status: 'processing' })
},
})

View File

@ -1,8 +1,6 @@
import {
type Interaction,
type InteractionCallbackData,
type CreateMessageOptions,
MessageFlags,
ApplicationCommandOptionTypes,
ApplicationCommandTypes,
EmbedsBuilder,
@ -13,34 +11,10 @@ import { rbacAllow } from '../middlewares/rbac.ts'
import { createCommand } from '../commands.ts'
import { configs } from '../config.ts'
import type { Stream } from '@futureporn/types'
async function createStreamInDatabase(url: string, discordMessageId: string) {
const streamPayload = {
url,
status: 'pending_recording',
discord_message_id: discordMessageId
}
const res = await fetch(`${configs.postgrestUrl}/streams`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(streamPayload)
})
if (!res.ok) {
const status = res.status
const statusText = res.statusText
const body = await res.text()
const msg = `Failed to create stream in database. status=${status}, statusText=${statusText}, body=${body}`
console.error(msg)
throw new Error(msg)
}
const id = res.headers.get('location')?.split('.').at(-1)
if (!id) throw new Error('id could not be parsed from location header');
return parseInt(id)
}
import createStreamInDatabase from '../fetchers/createStreamInDatabase.ts'
import createVod from '../fetchers/createVod.ts'
import findOrCreateVtuber from '../fetchers/findOrCreateVtuber.ts'
import findOrCreateStream from '../fetchers/findOrCreateStream.ts'
async function getUrlFromMessage(interaction: Interaction): Promise<string|null> {
const messageId = interaction.message?.id
@ -60,7 +34,7 @@ async function getUrlFromMessage(interaction: Interaction): Promise<string|null>
if (!res.ok) {
const body = await res.json()
logger.error(body)
throw new Error(`Problem during getOptionsMessage. res.status=${res.status}, res.statusText=${res.statusText}`)
throw new Error(`Problem during getUrlFromMessage. res.status=${res.status}, res.statusText=${res.statusText}`)
}
const json = await res.json() as Stream[]
const stream = json[0]
@ -119,7 +93,7 @@ createCommand({
// respond to the interaction and get a message ID which we will then add to the database Record
const embeds = new EmbedsBuilder()
.setTitle(`Stream`)
.setTitle(`VOD`)
.setDescription('Waiting for a worker to start the job.')
.setFields([
{ name: 'Status', value: 'Pending', inline: true },
@ -136,8 +110,13 @@ createCommand({
throw new Error(msg)
}
const stream = await createStreamInDatabase(url, message.id.toString())
logger.info(stream)
const date = new Date()
const vtuberId = await findOrCreateVtuber({ url })
const streamId = await findOrCreateStream({ vtuberId, date })
if (!streamId) throw new Error(`failed to find or create a Stream in database`);
const vodId = await createVod({ stream_id: streamId, vtuber: vtuberId, url })
logger.info(`Success! We have created VOD id=${vodId}`)
} catch (e) {
const message = `Record failed due to the following error.\n${e}`
logger.error(message)

View File

@ -1,24 +1,28 @@
import 'dotenv/config'
if (!process.env.WORKER_CONNECTION_STRING) throw new Error("WORKER_CONNECTION_STRING was missing from env");
if (!process.env.POSTGREST_URL) throw new Error('Missing POSTGREST_URL env var');
if (!process.env.DISCORD_TOKEN) throw new Error('Missing DISCORD_TOKEN env var');
if (!process.env.DISCORD_CHANNEL_ID) throw new Error("DISCORD_CHANNEL_ID was missing from env");
if (!process.env.DISCORD_GUILD_ID) throw new Error("DISCORD_GUILD_ID was missing from env");
if (!process.env.DISCORD_APPLICATION_ID) throw new Error('DISCORD_APPLICATION_ID was missing from env');
if (!process.env.AUTOMATION_USER_JWT) throw new Error('Missing AUTOMATION_USER_JWT env var');
const token = process.env.DISCORD_TOKEN!
const postgrestUrl = process.env.POSTGREST_URL!
const discordChannelId = process.env.DISCORD_CHANNEL_ID!
const discordGuildId = process.env.DISCORD_GUILD_ID!
const automationUserJwt = process.env.AUTOMATION_USER_JWT!
const connectionString = process.env.WORKER_CONNECTION_STRING!
const discordApplicationId = process.env.DISCORD_APPLICATION_ID!
const requiredEnvVars = [
'HTTP_PROXY',
'WORKER_CONNECTION_STRING',
'POSTGREST_URL',
'DISCORD_TOKEN',
'DISCORD_CHANNEL_ID',
'DISCORD_GUILD_ID',
'DISCORD_APPLICATION_ID',
'AUTOMATION_USER_JWT',
] as const;
const getEnvVar = (key: typeof requiredEnvVars[number]): string => {
const value = process.env[key];
if (!value) {
throw new Error(`Missing ${key} env var`);
}
return value;
};
export interface Config {
token: string;
postgrestUrl: string;
httpProxy: string;
token: string;
automationUserJwt: string;
discordGuildId: string;
discordChannelId: string;
@ -28,11 +32,13 @@ export interface Config {
export const configs: Config = {
token,
postgrestUrl,
automationUserJwt,
discordGuildId,
discordChannelId,
connectionString,
discordApplicationId,
connectionString: getEnvVar('WORKER_CONNECTION_STRING'),
httpProxy: getEnvVar('HTTP_PROXY'),
postgrestUrl: getEnvVar('POSTGREST_URL'),
token: getEnvVar('DISCORD_TOKEN'),
automationUserJwt: getEnvVar('AUTOMATION_USER_JWT'),
discordGuildId: getEnvVar('DISCORD_GUILD_ID'),
discordChannelId: getEnvVar('DISCORD_CHANNEL_ID'),
discordApplicationId: getEnvVar('DISCORD_APPLICATION_ID'),
}

View File

@ -0,0 +1,30 @@
import { configs } from '../config.ts'
export default async function createStreamInDatabase(url: string, discordMessageId: string): Promise<string> {
const streamPayload = {
url,
status: 'pending_recording',
discord_message_id: discordMessageId,
date: new Date().toISOString()
}
const res = await fetch(`${configs.postgrestUrl}/streams`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(streamPayload)
})
if (!res.ok) {
const status = res.status
const statusText = res.statusText
const body = await res.text()
const msg = `Failed to create stream in database. status=${status}, statusText=${statusText}, body=${body}`
console.error(msg)
throw new Error(msg)
}
const id = res.headers.get('location')?.split('.').at(-1)
if (!id) throw new Error('id could not be parsed from location header');
return id
}

View File

@ -0,0 +1,28 @@
import { configs } from "../config.ts"
import type { VodRecord } from "@futureporn/types"
export default async function createVod(payload: Partial<VodRecord>): Promise<string> {
const vodPayload = {
date: new Date().toISOString()
}
const res = await fetch(`${configs.postgrestUrl}/vods`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(Object.assign(vodPayload, payload))
})
if (!res.ok) {
const status = res.status
const statusText = res.statusText
const body = await res.text()
const msg = `Failed to create vod in database. status=${status}, statusText=${statusText}, body=${body}`
console.error(msg)
throw new Error(msg)
}
const id = res.headers.get('location')?.split('.').at(-1)
if (!id) throw new Error('id could not be parsed from location header');
return id
}

View File

@ -0,0 +1,84 @@
import { configs } from "../config.ts"
import type { Stream, VodRecord } from "@futureporn/types"
import { sub } from 'date-fns'
import { bot } from "../bot.ts"
export async function findStream(vtuberId: string, lteDate: Date, gteDate: Date): Promise<string|null> {
const fetchUrl = `${configs.postgrestUrl}/streams?select=id&vtuber=eq.${vtuberId}&date=gte.${gteDate.toISOString()}&date=lte.${lteDate.toISOString()}`
const fetchOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation'
}
}
const res = await fetch(fetchUrl, fetchOptions)
if (!res.ok) {
const body = await res.text()
const msg = `findStream fetch failed. status=${res.status}, statusText=${res.statusText}, body=${body}`
bot.logger.error(msg)
throw new Error(msg)
}
const json = await res.json() as Stream[]
if (!json || !json[0]) return null
return json[0].id
}
export async function createStream(): Promise<string|null> {
const payload = {
}
const fetchUrl = `${configs.postgrestUrl}/streams?select=id`
const fetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(payload)
}
const res = await fetch(fetchUrl, fetchOptions)
if (!res.ok) {
const body = await res.text()
const msg = `createStream fetch failed. status=${res.status}, statusText=${res.statusText}, body=${body}`
bot.logger.error(msg)
throw new Error(msg)
}
const json = await res.json() as Stream[]
if (!json || !json[0]) return null
return json[0].id
}
/**
* Find or create a stream in the database.
*
* We use the vtuberId and the date to find a stream taking place within the last n minutes.
*
* The stream date range that we accept is inclusive between date and (date-minutes)
*
* This is useful for eliminating duplicates, since a stream can be created by more than one input.
*
* - manual
* - email
* - twitter
*
*/
export default async function findOrCreateStream({ vtuberId, date, minutes = 15 }: { vtuberId: string, date: Date, minutes?: number }): Promise<string|null> {
bot.logger.info(`findOrCreateStream with vtuberId=${vtuberId}, date=${date.toISOString()}, minutes=${minutes}`)
if (!vtuberId) throw new Error(`findOrCreateStream requires vruberId passed in the options argument.`);
if (!date) throw new Error(`findOrCreateStream requires date passed in the options argument.`);
const gteDate = sub(date, { minutes })
const lteDate = date
const foundStream = await findStream(vtuberId, lteDate, gteDate)
if (!foundStream) {
return createStream()
} else {
return foundStream
}
}

View File

@ -0,0 +1,20 @@
import findOrCreateVtuber from "./findOrCreateVtuber.ts"
import { expect } from 'chai'
// These tests are meant to run in docker via gitea-actions
// or https://github.com/nektos/act
describe('integration', function () {
this.beforeAll(function () {
// set up db
// seed db
// set env
})
describe('findOrCreateVtuber', function () {
xit('should find projektmelody when given url=https://chaturbate.com/projektmelody', async function () {
const url = 'https://chaturbate.com/projektmelody'
const vtuber = await findOrCreateVtuber({ url }, { prefer: 'return=representation' })
expect(vtuber).to.have.property('slug', 'projektmelody')
})
})
})

View File

@ -0,0 +1,108 @@
import { configs } from "../config.ts"
import type { VtuberRecord, VtuberResponse } from "@futureporn/types"
import scrapeVtuberData from '@futureporn/scout/scrapeVtuberData.ts'
import { bot } from '../bot.ts'
import qs from 'qs'
interface vTuberSearchQuery {
url: string;
name: string;
}
interface vTuberSearchOptions {
prefer: string;
}
const columns = [
'chaturbate',
'twitter',
'patreon',
'twitch',
'tiktok',
'onlyfans',
'youtube',
'fansly',
'pornhub'
];
// greetz https://bobbyhadz.com/blog/javascript-remove-trailing-slash-from-string
function removeTrailingSlash(url: string) {
return url.replace(/\/+$/, '');
}
function buildUrlSearchOrTemplate(columns: string[], url: string): string {
return `(${columns.map(column => `${column}.ilike.*${url}*`).join(',')})`;
}
async function findVtuber(query: Partial<vTuberSearchQuery>, options?: Partial<vTuberSearchOptions>): Promise<string|null> {
const { url, name } = query
const queryOptions: any = {}
// When url argument exists, we try searching against the vtuber's platform urls
if (url) queryOptions.or = buildUrlSearchOrTemplate(columns, removeTrailingSlash(url));
if (!url && !name) throw new Error(`findVtuber requires url or name passed in an object as argument.`);
const fetchUrl = `${configs.postgrestUrl}/vtubers?${qs.stringify(queryOptions, { encode: false })}`
bot.logger.info(fetchUrl)
const fetchOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
'Authorization': `Bearer ${configs.automationUserJwt}`,
}
}
const res = await fetch(fetchUrl, fetchOptions)
if (!res.ok) {
const msg = `request failed. status=${res.status}, statusText=${res.statusText}`
bot.logger.error(msg)
throw new Error(msg)
}
const json = await res.json() as VtuberResponse[]
bot.logger.info(json)
return json?.at(0)?.id || null
}
async function createVtuber(vtuber: Partial<VtuberRecord>): Promise<string> {
const createVtuberPayload = vtuber
const fetchUrl = `${configs.postgrestUrl}/vtubers`
const fetchOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(createVtuberPayload)
}
const res = await fetch(fetchUrl, fetchOptions)
if (!res.ok) {
const body = await res.text()
const msg = `createVtuber fetch failed. status=${res.status}, statusText=${res.statusText}, body=${body}`
bot.logger.error(msg)
throw new Error(msg)
}
const json = await res.json() as VtuberResponse[]
bot.logger.info(json)
const vtuberData = json[0]
if (!vtuberData) throw new Error('failed to createVtuber')
return vtuberData.id
}
export default async function findOrCreateVtuber(query: Partial<vTuberSearchQuery>, options?: Partial<vTuberSearchOptions>): Promise<string> {
if (!query) throw new Error(`findOrCreateVtuber requires an object as argument`);
const { url, name } = query
if (!url) throw new Error('findOrCreateVtuber was missing url which is required');
bot.logger.info(`findOrCreateVtuber. url=${url}, name=${name}`)
const foundVtuber = await findVtuber(query)
if (!foundVtuber) {
const vtuber = await scrapeVtuberData(url)
return createVtuber(vtuber)
} else {
return foundVtuber
}
}

View File

@ -0,0 +1,36 @@
import { configs } from "../config.ts"
import type { Stream } from '@futureporn/types'
import { bot } from '../bot.ts'
export default async function getStreamFromDatabase(messageId: string): Promise<Stream|null> {
const pgRequestUrl = `${configs.postgrestUrl}/streams?discord_message_id=eq.${messageId}`
bot.logger.info(`pgRequestUrl=${pgRequestUrl}`)
const requestOptions = {
method: 'GET',
headers: {
'Authorization': `Bearer ${configs.automationUserJwt}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation'
}
}
try {
const res = await fetch (pgRequestUrl, requestOptions)
if (!res.ok) {
const body = await res.json()
bot.logger.error(body)
throw new Error(`Problem during getStreamFromDatabase. res.status=${res.status}, res.statusText=${res.statusText}`)
}
bot.logger.info(`we got an OK response. let's look at the json.`)
const json = await res.json() as Stream[]
bot.logger.info(json)
const stream = json[0]
if (!stream) return null
else return stream
} catch (e) {
bot.logger.error(e)
throw e
}
}

View File

@ -0,0 +1,9 @@
import { type Interaction } from "discordeno"
import { isCuid } from '@paralleldrive/cuid2';
export default function getStreamIdFromMessage(interaction: Interaction): string|null {
const embeds = interaction.message?.embeds
const streamId = embeds?.at(0)?.title?.split(' ').at(1)
if (!streamId || !isCuid(streamId) || streamId === 'undefined') return null
else return streamId
}

View File

@ -0,0 +1,36 @@
import { type Interaction } from "discordeno"
import { configs } from "../config.ts"
import { type Stream } from "@futureporn/types"
import { logger } from "@discordeno/bot"
export default async function getUrlFromMessage(interaction: Interaction): Promise<string|null> {
const messageId = interaction.message?.id
const pgRequestUrl = `${configs.postgrestUrl}/streams?discord_message_id=eq.${messageId}`
logger.info(`pgRequestUrl=${pgRequestUrl}`)
const requestOptions = {
method: 'GET',
headers: {
'Authorization': `Bearer ${configs.automationUserJwt}`,
'Content-Type': 'application/json',
'Prefer': 'return=representation'
}
}
try {
const res = await fetch (pgRequestUrl, requestOptions)
if (!res.ok) {
const body = await res.json()
logger.error(body)
throw new Error(`Problem during getUrlFromMessage. res.status=${res.status}, res.statusText=${res.statusText}`)
}
const json = await res.json() as Stream[]
const stream = json[0]
const url = stream?.url
if (!url) return null
else return url
} catch (e) {
logger.error(e)
throw e
}
}

View File

@ -0,0 +1,29 @@
import type { Stream } from "@futureporn/types";
import { configs } from "../config.ts";
import { logger } from "@discordeno/bot";
export default async function patchStreamInDatabase(stream_id: string, payload: Partial<Stream>): Promise<void> {
const url = `${configs.postgrestUrl}/streams?id=eq.${stream_id}`
const fetchOptions = {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${configs.automationUserJwt}`,
'Content-Type': 'application/json',
'Prefer': 'return=headers-only'
},
body: JSON.stringify(payload)
}
try {
const res = await fetch(url, fetchOptions)
if (!res.ok) {
const body = await res.json()
logger.error(body)
throw new Error(`Problem during patchStreamInDatabase. res.status=${res.status}, res.statusText=${res.statusText}`)
}
return
} catch (e) {
logger.error(e)
throw e
}
}

View File

@ -4,8 +4,10 @@ import { bot } from './bot.ts'
import type { Interaction } from '@discordeno/bot'
import { importDirectory } from './utils/loader.ts'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'url';
import { fileURLToPath } from 'url'
import { configs } from './config.ts'
import { updateApplicationCommands } from './utils/update-commands.ts'
const __dirname = dirname(fileURLToPath(import.meta.url));
@ -53,6 +55,7 @@ async function setupBot() {
async function main() {
await setupBot()
await setupGraphileWorker()
await updateApplicationCommands() // this needs to run after importDirectory() has run
}
main().catch((e) => {

View File

@ -12,3 +12,11 @@ SELECT graphile_worker.add_job('process_stream_recording', max_attempts := 3);
```sql
SELECT * FROM graphile_worker.complete_jobs(ARRAY[7, 99, 38674, ...]);
```
## cancel all jobs
```sql
SELECT * FROM graphile_worker.complete_jobs(
ARRAY(SELECT id FROM graphile_worker.jobs)
);
```

View File

@ -18,14 +18,14 @@ const yeahEmojiId = BigInt('1253191939461873756')
interface Payload {
stream_id: number;
vod_id: number;
}
function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid payload");
if (!payload.stream_id) throw new Error(`stream_id was absent in the payload`);
if (!payload.vod_id) throw new Error(`vod_id was absent in the payload`);
}
@ -51,11 +51,11 @@ async function editDiscordMessage({ helpers, stream }: { stream: Stream, helpers
async function getStreamFromDatabase(streamId: number) {
const res = await fetch(`${process.env.POSTGREST_URL}/streams?select=*,segments(*)&id=eq.${streamId}`)
const res = await fetch(`${configs.postgrestUrl}/streams?select=*,segments(*)&id=eq.${streamId}`)
if (!res.ok) {
throw new Error(`failed fetching stream ${streamId}. status=${res.status}, statusText=${res.statusText}`)
}
const body = await res.json() as any
const body = await res.json() as Stream[]
return body[0];
}
@ -71,10 +71,11 @@ async function getStreamFromDatabase(streamId: number) {
export const update_discord_message: Task = async function (payload, helpers: Helpers) {
try {
assertPayload(payload)
const { stream_id } = payload
const streamId = stream_id
const { vod_id } = payload
const streamId = vod_id
const stream = await getStreamFromDatabase(streamId)
if (!stream) throw new Error('failed to get stream from database');
// helpers.logger.info(`update_discord_message with streamId=${streamId}. stream=${JSON.stringify(stream)}`)
editDiscordMessage({ helpers, stream })
} catch (e) {
@ -116,6 +117,10 @@ function getEmbeds(stream: Stream) {
embeds
.setDescription("The recording has ended abnorminally.")
.setColor(8289651)
} else if (status === 'processing') {
embeds
.setDescription("The recording is being prepared for publishing.")
.setColor(392960)
} else if (status === 'stalled') {
embeds
.setDescription("We have not received a progress update in the past two minutes.")
@ -126,7 +131,7 @@ function getEmbeds(stream: Stream) {
.setColor(10855845)
}
// Add an Embed for each segment
// Add an Embed for segments
if (segments) {
const getDuration = (s: Segment) => formatDuration(intervalToDuration({ start: s.created_at, end: s.updated_at }))
embeds.newEmbed()
@ -139,6 +144,21 @@ function getEmbeds(stream: Stream) {
}
)))
}
// Add an Embed for processing tasks
if (status === 'processing') {
const tasks = [
{ name: 'combine_video_segments', complete: false },
{ name: 'generate_thumbnail', complete: false },
{ name: 'queue_moderator_review', complete: false },
{ name: 'create_mux_asset', complete: false },
{ name: 'create_torrent', complete: false },
]
embeds.newEmbed()
.setTitle('Processing Tasks')
.setDescription(tasks.map((task) => `* ${task.complete ? '🗹' : '☐'} ${task.name}`).join('\n')) }
return embeds
}
@ -185,6 +205,10 @@ function getButtonRow(streamStatus: Status): ActionRow[] {
components.push(processButton) // @todo this is only for testing. normally the process button is hidden if the stream was aborted.
} else if (streamStatus === 'finished') {
components.push(processButton)
} else if (streamStatus === 'processing') {
// @todo redo this section which is only this way for testing
components.push(retryButton)
components.push(processButton)
} else {
components.push(retryButton)
}

View File

@ -15,7 +15,7 @@ function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload.stalled_minutes !== 'number') throw new Error(`stalled_minutes parameter was not a number`);
}
async function updateStalledStreams({
async function updateStalledVods({
helpers,
stalled_minutes,
url
@ -25,8 +25,8 @@ async function updateStalledStreams({
url: string
}) {
// 1. identify and update stalled /streams
// Any streams that was updated earlier than n minute ago AND is in 'pending_recording' or 'recording' state is marked as stalled.
// 1. identify and update stalled /vods
// Any vods that was updated earlier than n minute ago AND is in 'pending_recording' or 'recording' state is marked as stalled.
const timestamp = sub(new Date(), { minutes: stalled_minutes }).toISOString()
const queryOptions = {
updated_at: `lt.${timestamp}`,
@ -57,7 +57,7 @@ async function updateStalledStreams({
}
async function updateRecordingStreams({
async function updateRecordingVods({
helpers,
url
}: {
@ -65,8 +65,8 @@ async function updateRecordingStreams({
url: string
}) {
// identify and update recording /streams
// Any streams that has a segment that was updated within the past 1 minutes is considered recording
// identify and update recording /vods
// Any vods that has a segment that was updated within the past 1 minutes is considered recording
const timestamp = sub(new Date(), { minutes: 1 }).toISOString()
const queryOptions = {
select: 'status,id,segments!inner(updated_at)',
@ -98,20 +98,20 @@ async function updateRecordingStreams({
}
export const update_stream_statuses: Task = async function (payload: unknown, helpers: Helpers) {
export const update_vod_statuses: Task = async function (payload: unknown, helpers: Helpers) {
assertPayload(payload)
const { stalled_minutes } = payload
// helpers.logger.info(`update_stream_statuses has begun.`)
// helpers.logger.info(`update_vod_statuses has begun.`)
const url = 'http://postgrest.futureporn.svc.cluster.local:9000/streams'
const url = 'http://postgrest.futureporn.svc.cluster.local:9000/vods'
try {
// await updateStalledStreams({ helpers, url, stalled_minutes })
await updateRecordingStreams({ helpers, url })
// await updateStalledVods({ helpers, url, stalled_minutes })
await updateRecordingVods({ helpers, url })
} catch (e: any) {
if (e instanceof Error) {
helpers.logger.error(`hi there we encountered an error while fetching /streams`)
helpers.logger.error(`hi there we encountered an error while fetching /vods`)
helpers.logger.error(e.message)
} else {
helpers.logger.error(e)
@ -120,4 +120,4 @@ export const update_stream_statuses: Task = async function (payload: unknown, he
}
}
export default update_stream_statuses
export default update_vod_statuses

View File

@ -2,5 +2,7 @@ import { bot } from '../bot.ts'
import { commands } from '../commands.ts'
export async function updateApplicationCommands(): Promise<void> {
bot.logger.info(`updating application commands. commands=${commands.array()}`)
console.log(commands)
await bot.helpers.upsertGlobalApplicationCommands(commands.array())
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
import type { Helpers } from 'graphile-worker'
import { configs } from '../config.ts'
import querystring from 'node:querystring'
export default async function createSegmentInDatabase(s3_key: string, vod_id: string, helpers: Helpers): Promise<number> {
if (!s3_key) throw new Error('getSegments requires {string} s3_key as first arg');
const segmentPayload = {
s3_key,
vod_id
}
helpers.logger.info(`Creating segment with s3_key=${s3_key}. payload as follows`)
helpers.logger.info(JSON.stringify(segmentPayload))
const res = await fetch(`${configs.postgrestUrl}/segments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(segmentPayload)
})
if (!res.ok) {
const body = await res.text()
const msg = `failed to create Segment. status=${res.status}, statusText=${res.statusText}, body=${body}`
helpers.logger.error(msg)
throw new Error(msg);
}
const location = res.headers.get('location')
if (!location) throw new Error(`failed to get location header in response from postgrest`);
const parsedQuery = querystring.parse(location)
const segmentsId = parsedQuery['/segments?id']
if (!segmentsId) throw new Error('segmentsId was undefined which is unexpected');
if (Array.isArray(segmentsId)) throw new Error('segmentsId was an array which is unexpected');
const id = segmentsId.split('.').at(-1)
if (!id) throw new Error('failed to get id ');
return parseInt(id)
}

View File

@ -0,0 +1,35 @@
import type { Helpers } from 'graphile-worker'
import { configs } from '../config.ts'
import querystring from 'node:querystring'
export default async function createSegmentsVodLink(vod_id: string, segment_id: number, helpers: Helpers): Promise<number> {
if (!vod_id) throw new Error('createSegmentsVodLink requires {string} vod_id as first arg');
if (!segment_id) throw new Error('createSegmentsVodLink requires {Number} segment_id as second arg');
const segmentVodLinkPayload = {
vod_id,
segment_id
}
const res = await fetch(`${configs.postgrestUrl}/segments_vod_links`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(segmentVodLinkPayload)
})
if (!res.ok) {
const body = await res.text()
throw new Error(`failed to create SegmentsVodLink. status=${res.status}, statusText=${res.statusText}, body=${body}`);
}
const location = res.headers.get('location')
if (!location) throw new Error(`failed to get location header in response from postgrest`);
const parsedQuery = querystring.parse(location)
const segmentsId = parsedQuery['/segments_vod_links?id']
if (!segmentsId) throw new Error('segments_vod_links?id was undefined which is unexpected');
if (Array.isArray(segmentsId)) throw new Error('segments_vod_links was an array which is unexpected');
const id = segmentsId.split('.').at(-1)
if (!id) throw new Error('failed to get id ');
return parseInt(id)
}

View File

@ -0,0 +1,39 @@
import type { Segment } from '@futureporn/types'
import type { Helpers } from 'graphile-worker'
import { configs } from '../config.ts'
import querystring from 'node:querystring'
export default async function getSegmentsFromDatabase(s3_key: string, helpers: Helpers): Promise<number> {
if (!s3_key) throw new Error('getSegments requires {string} s3_key as first arg');
const segmentPayload = {
s3_key
}
helpers.logger.info(`Creating segment with s3_key=${s3_key}. payload as follows`)
helpers.logger.info(JSON.stringify(segmentPayload))
const res = await fetch(`${configs.postgrestUrl}/segments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(segmentPayload)
})
if (!res.ok) {
const body = await res.text()
const msg = `failed to create Segment. status=${res.status}, statusText=${res.statusText}, body=${body}`
helpers.logger.error(msg)
throw new Error(msg);
}
const location = res.headers.get('location')
if (!location) throw new Error(`failed to get location header in response from postgrest`);
const parsedQuery = querystring.parse(location)
const segmentsId = parsedQuery['/segments?id']
if (!segmentsId) throw new Error('segmentsId was undefined which is unexpected');
if (Array.isArray(segmentsId)) throw new Error('segmentsId was an array which is unexpected');
const id = segmentsId.split('.').at(-1)
if (!id) throw new Error('failed to get id ');
return parseInt(id)
}

View File

@ -0,0 +1,42 @@
import type { Segment } from '@futureporn/types'
import type { Helpers } from 'graphile-worker'
import { configs } from '../config.ts'
export default async function updateSegmentInDatabase({
segment_id,
fileSize,
helpers
}: {
segment_id: number,
fileSize: number,
helpers: Helpers
}): Promise<Segment> {
const payload: any = {
bytes: fileSize
}
const res = await fetch(`${configs.postgrestUrl}/segments?id=eq.${segment_id}&select=stream:streams(is_recording_aborted)`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(payload)
})
if (!res.ok) {
const body = await res.text()
const msg = `failed to updateDatabaseRecord. status=${res.status}, statusText=${res.statusText}, body=${body}`
helpers.logger.error(msg)
throw new Error(msg);
}
// helpers.logger.info(`response was OK~`)
const body = await res.json() as Segment[];
if (!body[0]) throw new Error(`failed to get a segment that matched segment_id=${segment_id}`);
const bod = body[0]
// helpers.logger.info('the following was the response from PATCH-ing /segments')
// helpers.logger.info(JSON.stringify(bod))
return bod
}

View File

@ -1,115 +1,14 @@
/**
*
* # notes
*
* # creation
*
* ## api.records
*
* id: 2
* url: 'https://chaturbate.com/example'
* discord_message_id: 238492348324
* recording_state: 'pending'
* is_aborted: false
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:36:27.796Z
*
* ## api.segments
*
* id: 5
* s3_key: example-date-cuid.mp4
* s3_id: 2342309492348324
* bytes: 0
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:36:27.796Z
*
* ## api.records_segments_links
*
* id: 9
* stream_id: 2
* segment_id: 5
* segment_order: 0
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:36:27.796Z
*
* # progress
*
* ## api.records
*
* id: 2
* url: 'https://chaturbate.com/example'
* discord_message_id: 238492348324
* recording_state: 'recording'
* is_aborted: false
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:37:37.168Z
*
* ## api.segments
*
* id: 5
* s3_key: example-2024-08-15-72ff4b5ae7dae73b.mp4
* s3_id: 2342309492348324
* bytes: 8384
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:37:37.168Z
*
*
* # new segment
*
* ## api.segments
*
* id: 6
* s3_key: example-2024-08-15-cda21be5e54621f2.mp4
* s3_id: a974eb6e194b7987
* byte: 0
* created_at: 2024-08-15T21:38:34.878Z
* updated_at: 2024-08-15T21:38:34.878Z
*
* ## api.records_segments_links
*
* id: 10
* stream_id: 2
* segment_id: 6
* segment_order: 1
* created_at: 2024-08-15T21:38:34.878Z
* updated_at: 2024-08-15T21:38:34.878Z
*
* # progress
*
* ## api.segments
*
* id: 6
* s3_key: example-2024-08-15-cda21be5e54621f2.mp4
* s3_id: a974eb6e194b7987
* byte: 1024
* created_at: 2024-08-15T21:38:34.878Z
* updated_at: 2024-08-15T21:39:11.437Z
*
* # completion
*
* ## api.records
*
* id: 2
* url: 'https://chaturbate.com/example'
* discord_message_id: 238492348324
* recording_state: 'finished'
* is_aborted: false
* created_at: 2024-08-15T21:36:27.796Z
* updated_at: 2024-08-15T21:39:41.692Z
*
*/
import querystring from 'node:querystring'
import updateSegmentInDatabase from '../fetchers/updateSegmentInDatabase.ts'
import { Helpers, type Task } from 'graphile-worker'
import Record from '../Record.ts'
import { getPlaylistUrl } from '@futureporn/scout/ytdlp.ts'
import type { RecordingState, RecordingRecord, Segment } from '@futureporn/types'
import { add } from 'date-fns'
import { backOff } from "exponential-backoff";
import type { Segment } from '@futureporn/types'
import { configs } from '../config.ts'
import qs from 'qs'
import { createId } from '@paralleldrive/cuid2'
import createSegmentInDatabase from '../fetchers/createSegmentInDatabase.ts'
import createSegmentsVodLink from '../fetchers/createSegmentsVodLink.ts'
/**
* url is the URL to be recorded. Ex: chaturbate.com/projektmelody
@ -118,7 +17,7 @@ import { createId } from '@paralleldrive/cuid2'
*/
interface Payload {
url: string;
stream_id: string;
vod_id: string;
}
@ -126,7 +25,7 @@ interface Payload {
function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid payload");
if (typeof payload.url !== "string") throw new Error("invalid url");
if (typeof payload.stream_id !== "string") throw new Error(`invalid stream_id=${payload.stream_id}`);
if (typeof payload.vod_id !== "string") throw new Error(`invalid vod_id=${payload.vod_id}`);
}
@ -142,7 +41,7 @@ async function getRecordInstance(url: string, segment_id: number, helpers: Helpe
const s3Client = Record.makeS3Client({ accessKeyId, secretAccessKey, region, endpoint })
const inputStream = Record.getFFmpegStream({ url: playlistUrl })
const onProgress = (fileSize: number) => {
updateDatabaseRecord({ segment_id, fileSize, helpers })
updateSegmentInDatabase({ segment_id, fileSize, helpers })
.then(checkIfAborted)
.then((isAborted) => {
isAborted ? abortController.abort() : null
@ -160,132 +59,8 @@ function checkIfAborted(segment: Partial<Segment>): boolean {
return (!!segment?.stream?.at(0)?.is_recording_aborted)
}
async function updateDatabaseRecord({
segment_id,
fileSize,
helpers
}: {
segment_id: number,
fileSize: number,
helpers: Helpers
}): Promise<Segment> {
const payload: any = {
bytes: fileSize
}
const res = await fetch(`${configs.postgrestUrl}/segments?id=eq.${segment_id}&select=stream:streams(is_recording_aborted)`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(payload)
})
if (!res.ok) {
const body = await res.text()
const msg = `failed to updateDatabaseRecord. status=${res.status}, statusText=${res.statusText}, body=${body}`
helpers.logger.error(msg)
throw new Error(msg);
}
// helpers.logger.info(`response was OK~`)
const body = await res.json() as Segment[];
if (!body[0]) throw new Error(`failed to get a segment that matched segment_id=${segment_id}`);
const bod = body[0]
// helpers.logger.info('the following was the response from PATCH-ing /segments')
// helpers.logger.info(JSON.stringify(bod))
return bod
}
const getSegments = async function getSegments(stream_id: string): Promise<Segment> {
if (!stream_id) throw new Error('getSegments requires {String} stream_id as first arg');
const res = await fetch(`${configs.postgrestUrl}/segments_stream_links?stream_id=eq.${stream_id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation'
},
})
if (!res.ok) {
const body = await res.text()
throw new Error(`failed to getSegments. status=${res.status}, statusText=${res.statusText}, body=${body}`);
}
const body = await res.json() as Segment[];
if (!body[0]) throw new Error(`failed to get segments that matched stream_id=${stream_id}`)
return body[0]
}
const createSegment = async function createSegment(s3_key: string, helpers: Helpers): Promise<number> {
if (!s3_key) throw new Error('getSegments requires {string} s3_key as first arg');
const segmentPayload = {
s3_key
}
helpers.logger.info(`Creating segment with s3_key=${s3_key}. payload as follows`)
helpers.logger.info(JSON.stringify(segmentPayload))
const res = await fetch(`${configs.postgrestUrl}/segments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`
},
body: JSON.stringify(segmentPayload)
})
if (!res.ok) {
const body = await res.text()
const msg = `failed to create Segment. status=${res.status}, statusText=${res.statusText}, body=${body}`
helpers.logger.error(msg)
throw new Error(msg);
}
const location = res.headers.get('location')
if (!location) throw new Error(`failed to get location header in response from postgrest`);
const parsedQuery = querystring.parse(location)
const segmentsId = parsedQuery['/segments?id']
if (!segmentsId) throw new Error('segmentsId was undefined which is unexpected');
if (Array.isArray(segmentsId)) throw new Error('segmentsId was an array which is unexpected');
const id = segmentsId.split('.').at(-1)
if (!id) throw new Error('failed to get id ');
return parseInt(id)
}
const createSegmentsStreamLink = async function createSegmentsStreamLink(stream_id: string, segment_id: number, helpers: Helpers): Promise<number> {
if (!stream_id) throw new Error('createSegmentsStreamLink requires {string} stream_id as first arg');
if (!segment_id) throw new Error('createSegmentsStreamLink requires {Number} segment_id as second arg');
const segmentStreamLinkPayload = {
stream_id,
segment_id
}
const res = await fetch(`${configs.postgrestUrl}/segments_stream_links`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=headers-only',
'Authorization': `Bearer ${configs.automationUserJwt}`,
},
body: JSON.stringify(segmentStreamLinkPayload)
})
if (!res.ok) {
const body = await res.text()
throw new Error(`failed to create SegmentsStreamLink. status=${res.status}, statusText=${res.statusText}, body=${body}`);
}
const location = res.headers.get('location')
if (!location) throw new Error(`failed to get location header in response from postgrest`);
const parsedQuery = querystring.parse(location)
const segmentsId = parsedQuery['/segments_stream_links?id']
if (!segmentsId) throw new Error('segments_stream_links?id was undefined which is unexpected');
if (Array.isArray(segmentsId)) throw new Error('segments_stream_links was an array which is unexpected');
const id = segmentsId.split('.').at(-1)
if (!id) throw new Error('failed to get id ');
return parseInt(id)
}
/**
* # doRecordSegment
@ -298,13 +73,13 @@ const createSegmentsStreamLink = async function createSegmentsStreamLink(stream_
*
* This function also names the S3 file (s3_key) with a datestamp and a cuid.
*/
const doRecordSegment = async function doRecordSegment(url: string, stream_id: string, helpers: Helpers): Promise<void> {
const doRecordSegment = async function doRecordSegment(url: string, vod_id: string, helpers: Helpers): Promise<void> {
const s3_key = `${new Date().toISOString()}-${createId()}.ts`
helpers.logger.info(`let's create a segment...`)
const segment_id = await createSegment(s3_key, helpers)
helpers.logger.info(`let's create a segment using vod_id=${vod_id}, url=${url}`)
const segment_id = await createSegmentInDatabase(s3_key, vod_id, helpers)
helpers.logger.info(`let's create a segmentsStreamLink...`)
const segmentsStreamLinkId = await createSegmentsStreamLink(stream_id, segment_id, helpers)
helpers.logger.info(`doTheRecording with segmentsStreamLinkId=${segmentsStreamLinkId}, stream_id=${stream_id}, segment_id=${segment_id}, url=${url}`)
const segmentsVodLinkId = await createSegmentsVodLink(vod_id, segment_id, helpers)
helpers.logger.info(`doTheRecording with createSegmentsVodLink segmentsVodLinkId=${segmentsVodLinkId}, vod_id=${vod_id}, segment_id=${segment_id}, url=${url}`)
const record = await getRecordInstance(url, segment_id, helpers)
await record.start()
}
@ -315,8 +90,8 @@ export const record: Task = async function (payload: unknown, helpers: Helpers)
assertPayload(payload)
const { url, stream_id } = payload
const streamId = stream_id
const { url, vod_id } = payload
const vodId = vod_id
try {
/**
* We do an exponential backoff timer when we record. If the Record() instance throws an error, we try again after a delay.
@ -327,9 +102,9 @@ export const record: Task = async function (payload: unknown, helpers: Helpers)
* @todo We must implement retrying at a higher level, and retry a few times to handle this type of corner-case.
*/
// await backOff(() => doRecordSegment(url, recordId, helpers))
await doRecordSegment(url, streamId, helpers)
await doRecordSegment(url, vodId, helpers)
} catch (e) {
// await updateDatabaseRecord({ recordId: stream_id, recordingState: 'failed' })
// await updateDatabaseRecord({ recordId: vod_id, recordingState: 'failed' })
helpers.logger.error(`caught an error during record Task`)
if (e instanceof Error) {
helpers.logger.info(`error.name=${e.name}`)

View File

@ -0,0 +1,33 @@
import { configs } from "../config"
import type { Vod, Stream } from '@futureporn/types'
export default async function createVod(stream?: Stream): Promise<Vod|null> {
if (!stream) throw new Error(`first argument passed to createVod must be a {Stream}`);
console.log(stream)
const url = `${configs.postgrestUrl}/vods`
const payload: any = {
stream_id: stream.id,
date: stream.date,
vtuber: stream.vtuber,
}
const fetchOptions = {
method: 'POST',
headers: {
'Authorization': `Bearer ${configs.automationUserJwt}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'Prefer': 'return=representation'
},
body: JSON.stringify(payload)
}
const res = await fetch (url, fetchOptions)
if (!res.ok) {
const body = await res.json()
throw new Error(`Problem during createVod. res.status=${res.status}, res.statusText=${res.statusText}, body=${JSON.stringify(body)}`)
}
const json = await res.json() as Vod[]
const vod = json[0]
if (!vod) return null
else return vod
}

View File

@ -0,0 +1,23 @@
import type { Vod } from "@futureporn/types";
import { configs } from "../config.ts";
export default async function patchVodInDatabase(vod_id: string, payload: Partial<Vod>): Promise<void> {
const url = `${configs.postgrestUrl}/vods?id=eq.${vod_id}`
const fetchOptions = {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${configs.automationUserJwt}`,
'Content-Type': 'application/json',
'Prefer': 'return=headers-only'
},
body: JSON.stringify(payload)
}
const res = await fetch(url, fetchOptions)
if (!res.ok) {
const body = await res.json()
throw new Error(`Problem during patchVodInDatabase. res.status=${res.status}, res.statusText=${res.statusText}`)
}
return
}

View File

@ -13,14 +13,17 @@ import { createReadStream, createWriteStream, write } from 'node:fs';
import { writeFile, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { promisify } from 'node:util';
import patchVodInDatabase from '../fetchers/patchVodInDatabase'
import type { S3File, Stream } from '@futureporn/types';
interface s3File {
key: string;
id: string;
}
interface Payload {
s3_manifest: s3File[];
stream_id?: string;
vod_id?: string;
}
interface S3Target {
@ -46,7 +49,7 @@ function assertPayload(payload: any): asserts payload is Payload {
const downloadS3File = async function (client: S3Client, s3File: s3File): Promise<string> {
const bucket = configs.s3Bucket;
const { key, id } = s3File
const { key } = s3File
console.log(`downloadS3File with s3File key=${key}, bucket=${bucket}`)
const getObjectCommand = new GetObjectCommand({ Bucket: bucket, Key: key })
const response = await client.send(getObjectCommand)
@ -174,17 +177,22 @@ export const combine_video_segments: Task = async function (payload: unknown, he
// helpers.logger.info(payload)
// helpers.logger.info(JSON.stringify(payload?.s3_manifest))
assertPayload(payload)
const { s3_manifest } = payload
helpers.logger.info(`combine_video_segments started with s3_manifest=${JSON.stringify(s3_manifest)}`)
const { s3_manifest, vod_id, stream_id } = payload
if (!stream_id) helpers.logger.warn(`combine_video_segments was called without a stream_id. This is not recommended.`);
if (!vod_id) helpers.logger.warn(`combine_video_segments was called without a vod_id. This is not recommended.`);
helpers.logger.info(`combine_video_segments started with s3_manifest=${JSON.stringify(s3_manifest)}, vod_id=${vod_id}, stream_id=${stream_id}`)
/**
* Here we take a manifest of S3 files and we download each of them.
* Then we combine them all, preserving original order using `ffmpeg -f concat`
* Then we upload the resulting video to S3
* Then we create records in Strapi
* * B2 file
* * VOD
* * Stream(?)
* Then we create records in Postgrest
* * s3_file
* * vod
*
* After the records are created,
* if we were told about a stream record that this recording belongs to,
* we edit the stream record, adding a relation to the vod we just created.
*/
@ -207,6 +215,21 @@ export const combine_video_segments: Task = async function (payload: unknown, he
const { uploadStream, upload } = await getS3ParallelUpload({ client, s3KeyName, filePath })
setupUploadPipeline({ inputStream, uploadStream })
await upload.done()
if (vod_id && stream_id) {
// update the vod with the s3_file of the combined video
const s3File: S3File = {
s3_key: s3KeyName,
bucket: configs.s3Bucket,
}
const payload = {
s3_file: s3File,
stream_id
}
await patchVodInDatabase(vod_id, payload)
}
} catch (e: any) {
helpers.logger.error('combined_video_segments failed')
if (e instanceof Error) {

View File

@ -1,6 +1,7 @@
import type { Helpers, Task } from "graphile-worker"
import { configs } from "../config"
import type { Stream } from '@futureporn/types'
import createVod from "../fetchers/createVod"
interface Payload {
stream_id: string;
@ -36,12 +37,12 @@ async function getStreamFromDatabase(streamId: string, helpers: Helpers) {
/**
*
* # process_recording
* # process_video
*
* We just recorded a livestream. Now what?
* process_recording takes a /streams record and runs a bunch of processes to get it ready for publishing.
* process_video takes a /streams record and runs a bunch of processes to get it ready for publishing.
*
* The following are graphile-worker tasks which process_recording is responsible for adding to the job queue.
* The following are graphile-worker tasks which process_video is responsible for adding to the job queue.
* Some of these tasks are run conditionally based on the structure of the /streams record.
* For example, combine_video_segments is only useful on a stream recording which ended up with multiple segments.
*
@ -53,31 +54,38 @@ async function getStreamFromDatabase(streamId: string, helpers: Helpers) {
*
* Some of the above Tasks are dependent on others. generate_thumbnail and everything following it depends on combine_video_segments.
* graphile-worker doesn't have support for dependent tasks,
* thus our solution is to run process_recording as many times as needed, each time adding as many parallel tasks as possible.
* thus our solution is to run process_video as many times as needed, each time adding as many parallel tasks as possible.
*
* For the first run, we add only combine_video_segments, which itself will add process_recording when it's done.
* For the first run, we add only combine_video_segments, which itself will add process_video when it's done.
* The second run can tell that combine_video_segments has successfully completed it's task, so it doesn't run it a second time.
* generate_thumbnail runs next, after which it itself adds process_recording to the work queue.
* generate_thumbnail runs next, after which it itself adds process_video to the work queue.
*
* On the third run, combine_video_segments and generate_thumbnail are skipped due to idempotency.
* The three next tasks are added simultaneously for parallel execution-- queue_moderator_review, create_mux_asset, and create_torrent.
*
*
*/
export const process_recording: Task = async function (payload: unknown, helpers: Helpers) {
const process_video: Task = async function (payload: unknown, helpers: Helpers) {
assertPayload(payload)
const { stream_id } = payload
helpers.logger.info(`process_recording task has begun for stream_id=${stream_id}`)
helpers.logger.info(`process_video task has begun for stream_id=${stream_id}`)
const stream = await getStreamFromDatabase(stream_id, helpers)
if (!stream) throw new Error(`failed to get stream from database.`);
if (!stream.segments) throw new Error(`stream ${stream_id} fetched from database lacked any segments.`);
if (stream.segments.length > 1) {
const isVodPresent: boolean = !!(stream?.vods && stream.vods.length > 0)
if (!isVodPresent) {
const vod = await createVod(stream)
if (!vod) throw new Error('failed to create vod')
const vod_id = vod.id
const isCombinationNeeded = (stream.segments.length > 1)
if (isCombinationNeeded) {
const s3_manifest = stream.segments.map((segment) => ({ key: segment.s3_key }))
helpers.addJob('combine_video_segments', { s3_manifest })
}
helpers.addJob('combine_video_segments', { s3_manifest, vod_id, stream_id })
}
}
}
export default process_video;

View File

@ -10,7 +10,7 @@ importers:
dependencies:
'@types/node':
specifier: ^20.14.9
version: 20.14.13
version: 20.16.1
'@types/qs':
specifier: ^6.9.15
version: 6.9.15
@ -37,11 +37,11 @@ importers:
version: 3.7.1
qs:
specifier: ^6.12.3
version: 6.12.3
version: 6.13.0
devDependencies:
'@esbuild-plugins/esm-externals':
specifier: ^0.1.2
version: 0.1.2(esbuild@0.23.0)
version: 0.1.2(esbuild@0.23.1)
'@futureporn/image':
specifier: workspace:^
version: link:../../packages/image
@ -59,7 +59,7 @@ importers:
version: link:../../packages/utils
'@types/chai':
specifier: ^4.3.16
version: 4.3.16
version: 4.3.17
'@types/imapflow':
specifier: ^1.0.18
version: 1.0.19
@ -74,10 +74,10 @@ importers:
version: 5.1.1
mocha:
specifier: ^10.7.0
version: 10.7.0
version: 10.7.3
tsup:
specifier: ^8.1.2
version: 8.2.3(typescript@5.5.4)
version: 8.2.4(typescript@5.5.4)
typescript:
specifier: ^5.5.3
version: 5.5.4
@ -101,146 +101,146 @@ packages:
peerDependencies:
esbuild: '*'
'@esbuild/aix-ppc64@0.23.0':
resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.23.0':
resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==}
'@esbuild/android-arm64@0.23.1':
resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.23.0':
resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==}
'@esbuild/android-arm@0.23.1':
resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.23.0':
resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==}
'@esbuild/android-x64@0.23.1':
resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.23.0':
resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==}
'@esbuild/darwin-arm64@0.23.1':
resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.23.0':
resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==}
'@esbuild/darwin-x64@0.23.1':
resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.23.0':
resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==}
'@esbuild/freebsd-arm64@0.23.1':
resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.23.0':
resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==}
'@esbuild/freebsd-x64@0.23.1':
resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.23.0':
resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==}
'@esbuild/linux-arm64@0.23.1':
resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.23.0':
resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==}
'@esbuild/linux-arm@0.23.1':
resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.23.0':
resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==}
'@esbuild/linux-ia32@0.23.1':
resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.23.0':
resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==}
'@esbuild/linux-loong64@0.23.1':
resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.23.0':
resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==}
'@esbuild/linux-mips64el@0.23.1':
resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.23.0':
resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==}
'@esbuild/linux-ppc64@0.23.1':
resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.23.0':
resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==}
'@esbuild/linux-riscv64@0.23.1':
resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.23.0':
resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==}
'@esbuild/linux-s390x@0.23.1':
resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.23.0':
resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==}
'@esbuild/linux-x64@0.23.1':
resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.23.0':
resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==}
'@esbuild/netbsd-x64@0.23.1':
resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.23.0':
resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
'@esbuild/openbsd-arm64@0.23.1':
resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.23.0':
resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==}
'@esbuild/openbsd-x64@0.23.1':
resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.23.0':
resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==}
'@esbuild/sunos-x64@0.23.1':
resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.23.0':
resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==}
'@esbuild/win32-arm64@0.23.1':
resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.23.0':
resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==}
'@esbuild/win32-ia32@0.23.1':
resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.23.0':
resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==}
'@esbuild/win32-x64@0.23.1':
resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@ -298,91 +298,91 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@rollup/rollup-android-arm-eabi@4.19.1':
resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==}
'@rollup/rollup-android-arm-eabi@4.21.0':
resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.19.1':
resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==}
'@rollup/rollup-android-arm64@4.21.0':
resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.19.1':
resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==}
'@rollup/rollup-darwin-arm64@4.21.0':
resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.19.1':
resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==}
'@rollup/rollup-darwin-x64@4.21.0':
resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.19.1':
resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.19.1':
resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.19.1':
resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
'@rollup/rollup-linux-arm64-gnu@4.21.0':
resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.19.1':
resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
'@rollup/rollup-linux-arm64-musl@4.21.0':
resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.19.1':
resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
'@rollup/rollup-linux-s390x-gnu@4.21.0':
resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.19.1':
resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
'@rollup/rollup-linux-x64-gnu@4.21.0':
resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.19.1':
resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
'@rollup/rollup-linux-x64-musl@4.21.0':
resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.19.1':
resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
'@rollup/rollup-win32-arm64-msvc@4.21.0':
resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.19.1':
resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==}
'@rollup/rollup-win32-ia32-msvc@4.21.0':
resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.19.1':
resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==}
'@rollup/rollup-win32-x64-msvc@4.21.0':
resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==}
cpu: [x64]
os: [win32]
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
'@types/chai@4.3.16':
resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -405,8 +405,8 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/node@20.14.13':
resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/pg@8.11.6':
resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==}
@ -489,8 +489,8 @@ packages:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
avvio@8.3.2:
resolution: {integrity: sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==}
avvio@8.4.0:
resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -708,8 +708,8 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
esbuild@0.23.0:
resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==}
esbuild@0.23.1:
resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
engines: {node: '>=18'}
hasBin: true
@ -788,8 +788,8 @@ packages:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
foreground-child@3.2.1:
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
foreground-child@3.3.0:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
forwarded@0.2.0:
@ -896,8 +896,8 @@ packages:
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
imapflow@1.0.164:
@ -1063,8 +1063,8 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.7:
resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mimic-fn@2.1.0:
@ -1083,8 +1083,8 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
mocha@10.7.0:
resolution: {integrity: sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==}
mocha@10.7.3:
resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==}
engines: {node: '>= 14.0.0'}
hasBin: true
@ -1331,8 +1331,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qs@6.12.3:
resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
queue-microtask@1.2.3:
@ -1383,8 +1383,8 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rollup@4.19.1:
resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==}
rollup@4.21.0:
resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@ -1418,8 +1418,8 @@ packages:
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
set-cookie-parser@2.6.0:
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
set-cookie-parser@2.7.0:
resolution: {integrity: sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
@ -1546,11 +1546,11 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tsup@8.2.3:
resolution: {integrity: sha512-6YNT44oUfXRbZuSMNmN36GzwPPIlD2wBccY7looM2fkTcxkf2NEmwr3OZuDZoySklnrIG4hoEtzy8yUXYOqNcg==}
tsup@8.2.4:
resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
@ -1576,8 +1576,8 @@ packages:
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@ -1652,84 +1652,84 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.0.1
'@esbuild-plugins/esm-externals@0.1.2(esbuild@0.23.0)':
'@esbuild-plugins/esm-externals@0.1.2(esbuild@0.23.1)':
dependencies:
debug: 4.3.6(supports-color@8.1.1)
esbuild: 0.23.0
esbuild: 0.23.1
escape-string-regexp: 4.0.0
transitivePeerDependencies:
- supports-color
'@esbuild/aix-ppc64@0.23.0':
'@esbuild/aix-ppc64@0.23.1':
optional: true
'@esbuild/android-arm64@0.23.0':
'@esbuild/android-arm64@0.23.1':
optional: true
'@esbuild/android-arm@0.23.0':
'@esbuild/android-arm@0.23.1':
optional: true
'@esbuild/android-x64@0.23.0':
'@esbuild/android-x64@0.23.1':
optional: true
'@esbuild/darwin-arm64@0.23.0':
'@esbuild/darwin-arm64@0.23.1':
optional: true
'@esbuild/darwin-x64@0.23.0':
'@esbuild/darwin-x64@0.23.1':
optional: true
'@esbuild/freebsd-arm64@0.23.0':
'@esbuild/freebsd-arm64@0.23.1':
optional: true
'@esbuild/freebsd-x64@0.23.0':
'@esbuild/freebsd-x64@0.23.1':
optional: true
'@esbuild/linux-arm64@0.23.0':
'@esbuild/linux-arm64@0.23.1':
optional: true
'@esbuild/linux-arm@0.23.0':
'@esbuild/linux-arm@0.23.1':
optional: true
'@esbuild/linux-ia32@0.23.0':
'@esbuild/linux-ia32@0.23.1':
optional: true
'@esbuild/linux-loong64@0.23.0':
'@esbuild/linux-loong64@0.23.1':
optional: true
'@esbuild/linux-mips64el@0.23.0':
'@esbuild/linux-mips64el@0.23.1':
optional: true
'@esbuild/linux-ppc64@0.23.0':
'@esbuild/linux-ppc64@0.23.1':
optional: true
'@esbuild/linux-riscv64@0.23.0':
'@esbuild/linux-riscv64@0.23.1':
optional: true
'@esbuild/linux-s390x@0.23.0':
'@esbuild/linux-s390x@0.23.1':
optional: true
'@esbuild/linux-x64@0.23.0':
'@esbuild/linux-x64@0.23.1':
optional: true
'@esbuild/netbsd-x64@0.23.0':
'@esbuild/netbsd-x64@0.23.1':
optional: true
'@esbuild/openbsd-arm64@0.23.0':
'@esbuild/openbsd-arm64@0.23.1':
optional: true
'@esbuild/openbsd-x64@0.23.0':
'@esbuild/openbsd-x64@0.23.1':
optional: true
'@esbuild/sunos-x64@0.23.0':
'@esbuild/sunos-x64@0.23.1':
optional: true
'@esbuild/win32-arm64@0.23.0':
'@esbuild/win32-arm64@0.23.1':
optional: true
'@esbuild/win32-ia32@0.23.0':
'@esbuild/win32-ia32@0.23.1':
optional: true
'@esbuild/win32-x64@0.23.0':
'@esbuild/win32-x64@0.23.1':
optional: true
'@fastify/ajv-compiler@3.6.0':
@ -1791,52 +1791,52 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@rollup/rollup-android-arm-eabi@4.19.1':
'@rollup/rollup-android-arm-eabi@4.21.0':
optional: true
'@rollup/rollup-android-arm64@4.19.1':
'@rollup/rollup-android-arm64@4.21.0':
optional: true
'@rollup/rollup-darwin-arm64@4.19.1':
'@rollup/rollup-darwin-arm64@4.21.0':
optional: true
'@rollup/rollup-darwin-x64@4.19.1':
'@rollup/rollup-darwin-x64@4.21.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.19.1':
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.19.1':
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.19.1':
'@rollup/rollup-linux-arm64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.19.1':
'@rollup/rollup-linux-arm64-musl@4.21.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.19.1':
'@rollup/rollup-linux-s390x-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.19.1':
'@rollup/rollup-linux-x64-gnu@4.21.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.19.1':
'@rollup/rollup-linux-x64-musl@4.21.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.19.1':
'@rollup/rollup-win32-arm64-msvc@4.21.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.19.1':
'@rollup/rollup-win32-ia32-msvc@4.21.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.19.1':
'@rollup/rollup-win32-x64-msvc@4.21.0':
optional: true
'@selderee/plugin-htmlparser2@0.11.0':
@ -1844,7 +1844,7 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
'@types/chai@4.3.16': {}
'@types/chai@4.3.17': {}
'@types/debug@4.1.12':
dependencies:
@ -1854,28 +1854,28 @@ snapshots:
'@types/imapflow@1.0.19':
dependencies:
'@types/node': 20.14.13
'@types/node': 20.16.1
'@types/interpret@1.1.3':
dependencies:
'@types/node': 20.14.13
'@types/node': 20.16.1
'@types/mailparser@3.4.4':
dependencies:
'@types/node': 20.14.13
'@types/node': 20.16.1
iconv-lite: 0.6.3
'@types/mocha@10.0.7': {}
'@types/ms@0.7.34': {}
'@types/node@20.14.13':
'@types/node@20.16.1':
dependencies:
undici-types: 5.26.5
undici-types: 6.19.8
'@types/pg@8.11.6':
dependencies:
'@types/node': 20.14.13
'@types/node': 20.16.1
pg-protocol: 1.6.1
pg-types: 4.0.2
@ -1935,7 +1935,7 @@ snapshots:
atomic-sleep@1.0.0: {}
avvio@8.3.2:
avvio@8.4.0:
dependencies:
'@fastify/error': 3.4.1
fastq: 1.17.1
@ -1963,9 +1963,9 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
bundle-require@5.0.0(esbuild@0.23.0):
bundle-require@5.0.0(esbuild@0.23.1):
dependencies:
esbuild: 0.23.0
esbuild: 0.23.1
load-tsconfig: 0.2.5
cac@6.7.14: {}
@ -2159,32 +2159,32 @@ snapshots:
es-errors@1.3.0: {}
esbuild@0.23.0:
esbuild@0.23.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.23.0
'@esbuild/android-arm': 0.23.0
'@esbuild/android-arm64': 0.23.0
'@esbuild/android-x64': 0.23.0
'@esbuild/darwin-arm64': 0.23.0
'@esbuild/darwin-x64': 0.23.0
'@esbuild/freebsd-arm64': 0.23.0
'@esbuild/freebsd-x64': 0.23.0
'@esbuild/linux-arm': 0.23.0
'@esbuild/linux-arm64': 0.23.0
'@esbuild/linux-ia32': 0.23.0
'@esbuild/linux-loong64': 0.23.0
'@esbuild/linux-mips64el': 0.23.0
'@esbuild/linux-ppc64': 0.23.0
'@esbuild/linux-riscv64': 0.23.0
'@esbuild/linux-s390x': 0.23.0
'@esbuild/linux-x64': 0.23.0
'@esbuild/netbsd-x64': 0.23.0
'@esbuild/openbsd-arm64': 0.23.0
'@esbuild/openbsd-x64': 0.23.0
'@esbuild/sunos-x64': 0.23.0
'@esbuild/win32-arm64': 0.23.0
'@esbuild/win32-ia32': 0.23.0
'@esbuild/win32-x64': 0.23.0
'@esbuild/aix-ppc64': 0.23.1
'@esbuild/android-arm': 0.23.1
'@esbuild/android-arm64': 0.23.1
'@esbuild/android-x64': 0.23.1
'@esbuild/darwin-arm64': 0.23.1
'@esbuild/darwin-x64': 0.23.1
'@esbuild/freebsd-arm64': 0.23.1
'@esbuild/freebsd-x64': 0.23.1
'@esbuild/linux-arm': 0.23.1
'@esbuild/linux-arm64': 0.23.1
'@esbuild/linux-ia32': 0.23.1
'@esbuild/linux-loong64': 0.23.1
'@esbuild/linux-mips64el': 0.23.1
'@esbuild/linux-ppc64': 0.23.1
'@esbuild/linux-riscv64': 0.23.1
'@esbuild/linux-s390x': 0.23.1
'@esbuild/linux-x64': 0.23.1
'@esbuild/netbsd-x64': 0.23.1
'@esbuild/openbsd-arm64': 0.23.1
'@esbuild/openbsd-x64': 0.23.1
'@esbuild/sunos-x64': 0.23.1
'@esbuild/win32-arm64': 0.23.1
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
@ -2220,7 +2220,7 @@ snapshots:
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.7
micromatch: 4.0.8
fast-json-stringify@5.16.1:
dependencies:
@ -2248,7 +2248,7 @@ snapshots:
'@fastify/error': 3.4.1
'@fastify/fast-json-stringify-compiler': 4.3.0
abstract-logging: 2.0.1
avvio: 8.3.2
avvio: 8.4.0
fast-content-type-parse: 1.1.0
fast-json-stringify: 5.16.1
find-my-way: 8.2.0
@ -2282,7 +2282,7 @@ snapshots:
flat@5.0.2: {}
foreground-child@3.2.1:
foreground-child@3.3.0:
dependencies:
cross-spawn: 7.0.3
signal-exit: 4.1.0
@ -2316,7 +2316,7 @@ snapshots:
glob@10.4.5:
dependencies:
foreground-child: 3.2.1
foreground-child: 3.3.0
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
@ -2336,7 +2336,7 @@ snapshots:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
ignore: 5.3.1
ignore: 5.3.2
merge2: 1.4.1
slash: 3.0.0
@ -2347,13 +2347,13 @@ snapshots:
graphile-config@0.0.1-beta.9:
dependencies:
'@types/interpret': 1.1.3
'@types/node': 20.14.13
'@types/node': 20.16.1
'@types/semver': 7.5.8
chalk: 4.1.2
debug: 4.3.6(supports-color@8.1.1)
interpret: 3.1.1
semver: 7.6.3
tslib: 2.6.3
tslib: 2.7.0
yargs: 17.7.2
transitivePeerDependencies:
- supports-color
@ -2367,7 +2367,7 @@ snapshots:
graphile-config: 0.0.1-beta.9
json5: 2.2.3
pg: 8.12.0
tslib: 2.6.3
tslib: 2.7.0
yargs: 17.7.2
transitivePeerDependencies:
- pg-native
@ -2415,7 +2415,7 @@ snapshots:
ieee754@1.2.1: {}
ignore@5.3.1: {}
ignore@5.3.2: {}
imapflow@1.0.164:
dependencies:
@ -2528,7 +2528,7 @@ snapshots:
dependencies:
cookie: 0.6.0
process-warning: 3.0.0
set-cookie-parser: 2.6.0
set-cookie-parser: 2.7.0
lilconfig@3.1.2: {}
@ -2580,7 +2580,7 @@ snapshots:
merge2@1.4.1: {}
micromatch@4.0.7:
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
@ -2597,7 +2597,7 @@ snapshots:
minipass@7.1.2: {}
mocha@10.7.0:
mocha@10.7.3:
dependencies:
ansi-colors: 4.1.3
browser-stdout: 1.3.1
@ -2839,7 +2839,7 @@ snapshots:
punycode@2.3.1: {}
qs@6.12.3:
qs@6.13.0:
dependencies:
side-channel: 1.0.6
@ -2879,26 +2879,26 @@ snapshots:
rfdc@1.4.1: {}
rollup@4.19.1:
rollup@4.21.0:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.19.1
'@rollup/rollup-android-arm64': 4.19.1
'@rollup/rollup-darwin-arm64': 4.19.1
'@rollup/rollup-darwin-x64': 4.19.1
'@rollup/rollup-linux-arm-gnueabihf': 4.19.1
'@rollup/rollup-linux-arm-musleabihf': 4.19.1
'@rollup/rollup-linux-arm64-gnu': 4.19.1
'@rollup/rollup-linux-arm64-musl': 4.19.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.19.1
'@rollup/rollup-linux-riscv64-gnu': 4.19.1
'@rollup/rollup-linux-s390x-gnu': 4.19.1
'@rollup/rollup-linux-x64-gnu': 4.19.1
'@rollup/rollup-linux-x64-musl': 4.19.1
'@rollup/rollup-win32-arm64-msvc': 4.19.1
'@rollup/rollup-win32-ia32-msvc': 4.19.1
'@rollup/rollup-win32-x64-msvc': 4.19.1
'@rollup/rollup-android-arm-eabi': 4.21.0
'@rollup/rollup-android-arm64': 4.21.0
'@rollup/rollup-darwin-arm64': 4.21.0
'@rollup/rollup-darwin-x64': 4.21.0
'@rollup/rollup-linux-arm-gnueabihf': 4.21.0
'@rollup/rollup-linux-arm-musleabihf': 4.21.0
'@rollup/rollup-linux-arm64-gnu': 4.21.0
'@rollup/rollup-linux-arm64-musl': 4.21.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.21.0
'@rollup/rollup-linux-riscv64-gnu': 4.21.0
'@rollup/rollup-linux-s390x-gnu': 4.21.0
'@rollup/rollup-linux-x64-gnu': 4.21.0
'@rollup/rollup-linux-x64-musl': 4.21.0
'@rollup/rollup-win32-arm64-msvc': 4.21.0
'@rollup/rollup-win32-ia32-msvc': 4.21.0
'@rollup/rollup-win32-x64-msvc': 4.21.0
fsevents: 2.3.3
run-parallel@1.2.0:
@ -2927,7 +2927,7 @@ snapshots:
dependencies:
randombytes: 2.1.0
set-cookie-parser@2.6.0: {}
set-cookie-parser@2.7.0: {}
set-function-length@1.2.2:
dependencies:
@ -3054,23 +3054,23 @@ snapshots:
ts-interface-checker@0.1.13: {}
tslib@2.6.3: {}
tslib@2.7.0: {}
tsup@8.2.3(typescript@5.5.4):
tsup@8.2.4(typescript@5.5.4):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.0)
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
chokidar: 3.6.0
consola: 3.2.3
debug: 4.3.6(supports-color@8.1.1)
esbuild: 0.23.0
esbuild: 0.23.1
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
picocolors: 1.0.1
postcss-load-config: 6.0.1
resolve-from: 5.0.0
rollup: 4.19.1
rollup: 4.21.0
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tree-kill: 1.2.2
@ -3086,7 +3086,7 @@ snapshots:
uc.micro@2.1.0: {}
undici-types@5.26.5: {}
undici-types@6.19.8: {}
webidl-conversions@4.0.2: {}

View File

@ -0,0 +1,46 @@
-- create s3_files table
CREATE TABLE api.s3_files (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
s3_id TEXT NOT NULL,
s3_key TEXT NOT NULL,
bucket TEXT NOT NULL
);
GRANT all ON api.vods TO automation;
GRANT SELECT ON api.vods TO web_anon;
-- create mux_assets table
CREATE TABLE api.mux_assets (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
asset_id TEXT NOT NULL,
playback_id TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
GRANT all ON api.mux_assets TO automation;
-- web_anon is intentionally not given privs to this table
-- re-create vods table
DROP TABLE api.vods CASCADE;
CREATE TABLE api.vods (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
stream_id UUID NOT NULL REFERENCES api.streams(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
published_at DATE,
title TEXT,
date DATE NOT NULL,
mux_asset UUID REFERENCES api.mux_assets(id),
thumbnail UUID REFERENCES api.s3_files(id),
vtuber UUID REFERENCES api.vtubers(id),
ipfs_cid TEXT,
s3_file UUID REFERENCES api.s3_files(id),
torrent TEXT,
announce_title TEXT,
announce_url TEXT,
note TEXT
);
GRANT all ON api.vods TO automation;
GRANT SELECT ON api.vods TO web_anon;

View File

@ -0,0 +1,24 @@
-- segments get moved to vods
DROP TABLE api.segments_stream_links CASCADE;
-- segments to vod, many-to-one
CREATE TABLE api.segments_vod_links (
id int PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
vod_id text NOT NULL,
segment_id text NOT NULL
);
-- roles & permissions
GRANT all ON api.segments_vod_links TO automation;
GRANT SELECT ON api.segments_vod_links TO web_anon;
-- establish many-to-one relationship by adding a foreign key to segments
ALTER TABLE api.segments
ADD COLUMN vod_id TEXT;
ALTER TABLE api.segments
ADD CONSTRAINT vod_id_not_null
CHECK (vod_id IS NOT NULL) NOT VALID;

View File

@ -0,0 +1,159 @@
-- seeding some test vtubers for dev environment
INSERT INTO api.vtubers (
display_name,
slug,
image,
theme_color,
chaturbate,
twitter,
patreon,
twitch,
tiktok,
onlyfans,
youtube,
linktree,
carrd,
fansly,
pornhub,
discord,
reddit,
throne,
instagram,
facebook,
merch,
description1,
description2,
image_blur
) VALUES (
'ProjektMelody', -- display_name
'projektmelody', -- slug
'https://futureporn-b2.b-cdn.net/projekt-melody.jpg', -- image
'#5C23C0', -- theme_color
'https://chaturbate.com/projektmelody', -- chaturbate
'https://twitter.com/projektmelody', -- twitter
'https://www.patreon.com/projektmelody', -- patreon
'https://twitch.tv/projektmelody', -- twitch
'https://www.tiktok.com/@realprojektmelody', -- tiktok
'https://onlyfans.com/projektbutt', -- onlyfans
'https://www.youtube.com/projektmelodyofficial', -- youtube
'https://linktr.ee/projektmelody', -- linktree
NULL, -- carrd (no data provided)
'https://fansly.com/r/scienceteam', -- fansly
'https://www.pornhub.com/model/projekt-melody', -- pornhub
NULL, -- discord (no data provided)
'https://www.reddit.com/r/projektmelody/', -- reddit
'https://throne.com/realprojektmelody', -- throne
NULL, -- instagram (no data provided)
NULL, -- facebook (no data provided)
'https://melody.vshojo.com/', -- merch
'Also known as, ''Daddy''s little grandpa,'' ProjektMelody is the pioneering hentai cam model who embodies a unique blend of sweetness and perversion. She engages with her audience with remarkable patience and politeness, demonstrating her commitment to fostering an inclusive environment.', -- description1
'On her livestreams, ProjektMelody''s demeanor can range from overtly sexual to adorably reserved and self-censored. Alongside her captivating presence, she actively promotes pro-social behavior, advocates for sexual education, and emphasizes the importance of kindness.', -- description2
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAADUlEQVQImWOIUT7wHwAEXQI/2aBnMAAAAABJRU5ErkJggg==' -- image_blur
);
INSERT INTO api.vtubers (
display_name,
slug,
image,
theme_color,
chaturbate,
twitter,
patreon,
twitch,
tiktok,
onlyfans,
youtube,
linktree,
carrd,
fansly,
pornhub,
discord,
reddit,
throne,
instagram,
facebook,
merch,
description1,
description2,
image_blur
) VALUES (
'el_XoX', -- display_name
'el_xox', -- slug
'https://futureporn-b2.b-cdn.net/el_xox.jpg', -- image
'#353FFF', -- theme_color
'https://chaturbate.com/el_xox/', -- chaturbate
'https://twitter.com/el_XoX34', -- twitter
NULL, -- patreon (no data provided)
'https://www.twitch.tv/el_xox', -- twitch
NULL, -- tiktok (no data provided)
NULL, -- onlyfans (no data provided)
NULL, -- youtube (no data provided)
NULL, -- linktree (no data provided)
'https://elxox.carrd.co/', -- carrd
NULL, -- fansly (no data provided)
NULL, -- pornhub (no data provided)
NULL, -- discord (no data provided)
NULL, -- reddit (no data provided)
NULL, -- throne (no data provided)
NULL, -- instagram (no data provided)
NULL, -- facebook (no data provided)
'https://elxox34.com/pages/limited-merch', -- merch
' ', -- description1 (empty string)
NULL, -- description2 (no data provided)
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAADUlEQVQImWNgYGD4DwABBAEAfbLI3wAAAABJRU5ErkJggg==' -- image_blur
);
INSERT INTO api.vtubers (
display_name,
slug,
image,
theme_color,
chaturbate,
twitter,
patreon,
twitch,
tiktok,
onlyfans,
youtube,
linktree,
carrd,
fansly,
pornhub,
discord,
reddit,
throne,
instagram,
facebook,
merch,
description1,
description2,
image_blur
) VALUES (
'Vexruby', -- display_name
'vexruby', -- slug
'https://futureporn-b2.b-cdn.net/vexruby.jpg', -- image
'#f882f5', -- theme_color
'https://chaturbate.com/vexruby', -- chaturbate
'https://x.com/vexxxruby', -- twitter
'https://www.patreon.com/ViRoClub', -- patreon
NULL, -- twitch (no data provided)
NULL, -- tiktok (no data provided)
NULL, -- onlyfans (no data provided)
NULL, -- youtube (no data provided)
NULL, -- linktree (no data provided)
NULL, -- carrd (no data provided)
NULL, -- fansly (no data provided)
NULL, -- pornhub (no data provided)
NULL, -- discord (no data provided)
NULL, -- reddit (no data provided)
NULL, -- throne (no data provided)
NULL, -- instagram (no data provided)
NULL, -- facebook (no data provided)
NULL, -- merch (no data provided)
' ', -- description1 (empty string)
NULL, -- description2 (no data provided)
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAADUlEQVQImWMwtf//HwAEkwJzh0T9qwAAAABJRU5ErkJggg==' -- image_blur
);

View File

@ -0,0 +1,14 @@
-- instead of using record_id, we need to use stream_id
DROP FUNCTION public.tg__update_discord_message CASCADE;
CREATE FUNCTION public.tg__update_discord_message() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
SET search_path TO 'pg_catalog', 'public', 'pg_temp'
AS $$
begin
PERFORM graphile_worker.add_job('update_discord_message', json_build_object(
'vod_id', NEW.id
), max_attempts := 3);
return NEW;
end;
$$;

View File

@ -0,0 +1,16 @@
-- we are moving url column from streams to vods
ALTER TABLE api.streams
DROP COLUMN url;
ALTER TABLE api.vods
ADD COLUMN url TEXT;
-- remove unused things
ALTER TABLE api.streams
DROP COLUMN status;
ALTER TABLE api.streams
DROP COLUMN is_recording_aborted;
ALTER TABLE api.streams
DROP COLUMN discord_message_id;

View File

@ -0,0 +1,41 @@
-- we are moving recording functionality from streams to vods
-- delete outdated
DROP FUNCTION IF EXISTS public.tg__add_record_job CASCADE;
DROP TRIGGER IF EXISTS stream_update ON api.streams;
DROP TRIGGER IF EXISTS stream_create ON api.streams;
-- We create a function which lets Postgrest's automation user create jobs in Graphile Worker.
-- Normally only the database owner, in our case `postgres`, can add jobs due to RLS in graphile_worker tables.
-- Under the advice of graphile_worker author, we can use a SECURITY DEFINER wrapper function.
-- @see https://worker.graphile.org/docs/sql-add-job#graphile_workeradd_job:~:text=graphile_worker.add_job(...),that%20are%20necessary.)
-- @see https://discord.com/channels/489127045289476126/1179293106336694333/1179605043729670306
-- @see https://discord.com/channels/489127045289476126/498852330754801666/1067707497235873822
CREATE FUNCTION public.tg__add_record_job() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
SET search_path TO 'pg_catalog', 'public', 'pg_temp'
AS $$
begin
PERFORM graphile_worker.add_job('record', json_build_object(
'url', NEW.url,
'vod_id', NEW.id
), max_attempts := 12);
return NEW;
end;
$$;
-- when a vod is updated, we add a job in graphile to update_discord_message
CREATE TRIGGER vod_update
AFTER UPDATE ON api.vods
FOR EACH ROW
EXECUTE PROCEDURE public.tg__update_discord_message('update_discord_message');
-- when a vod is created, we add a 'record' job in graphile-worker
CREATE TRIGGER vod_create
AFTER INSERT ON api.vods
FOR EACH ROW
EXECUTE PROCEDURE public.tg__add_record_job('record');

View File

@ -0,0 +1,7 @@
-- schema for rate limiter (https://github.com/animir/node-rate-limiter-flexible)
-- @see https://github.com/animir/node-rate-limiter-flexible/blob/661d794212441f104a6941092c28805b3bd76537/lib/RateLimiterPostgres.js#L161
CREATE TABLE public.limiter (
key varchar(255) PRIMARY KEY,
points integer NOT NULL DEFAULT 0,
expire bigint
);

View File

@ -0,0 +1,9 @@
-- theme_color, and image are made optional, because they require extra fetches.
-- the intention is to have a graphile-worker populate these columns in a parallel workflow,
-- rather than force the onboarding process to gather this data which increases the runtime of that process.
ALTER TABLE IF EXISTS api.vtubers
ALTER COLUMN image DROP NOT NULL;
ALTER TABLE IF EXISTS api.vtubers
ALTER COLUMN theme_color DROP NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@ -39,3 +39,9 @@ For when we get to the point where we need it, here are the packages we used wit
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-repl": "^2.3.3",
"puppeteer-extra-plugin-stealth": "^2.11.2"
## Adding API routes
* Routes are specified in ./src/api.yml
* Route handlers are specified in ./src/app.js

View File

@ -3,18 +3,16 @@
"type": "module",
"version": "4.0.1",
"description": "vtuber data acquisition",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
"./*.ts": "./src/*.ts"
},
"scripts": {
"build": "tsup",
"test": "pnpm run test.unit && pnpm run test.integration",
"test.unit": "mocha --require ts-node/register src/**/*.spec.ts -g unit",
"test.integration": "mocha --require ts-node/register src/**/*.spec.ts -g integration",
"build": "tsup",
"dev": "nodemon --ext js,ts,json,yaml --exec \"node --loader ts-node/esm --disable-warning=ExperimentalWarning ./src/index.ts\"",
"start": "node ./dist/index.js",
"dev": "nodemon --ext js,ts,json,yaml --exec \"node --import tsx --disable-warning=ExperimentalWarning ./src/index.ts\"",
"start": "node ./dist/index.cjs",
"clean": "rm -rf dist",
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
@ -22,55 +20,67 @@
"author": "@CJ_Clippy",
"license": "Unlicense",
"dependencies": {
"@aws-sdk/client-s3": "^3.583.0",
"@aws-sdk/lib-storage": "^3.588.0",
"@aws-sdk/s3-request-presigner": "^3.588.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/lib-storage": "^3.637.0",
"@aws-sdk/s3-request-presigner": "^3.637.0",
"@fastify/static": "^7.0.4",
"@fastify/swagger": "^8.15.0",
"@fastify/swagger-ui": "^4.1.0",
"@futureporn/types": "workspace:*",
"@paralleldrive/cuid2": "^2.2.2",
"@temporalio/client": "^1.9.0",
"@temporalio/worker": "^1.9.0",
"@temporalio/workflow": "^1.9.0",
"@temporalio/client": "^1.11.1",
"@temporalio/worker": "^1.11.1",
"@temporalio/workflow": "^1.11.1",
"@tsconfig/node20": "^20.1.4",
"@types/imapflow": "^1.0.18",
"@types/imapflow": "^1.0.19",
"@types/node": "^22.5.0",
"@types/pg": "^8.11.6",
"cheerio": "1.0.0-rc.12",
"concurrently": "^8.2.2",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"fastify": "^4.28.1",
"fastq": "^1.17.1",
"faye": "^1.4.0",
"htmlparser2": "^9.1.0",
"imapflow": "^1.0.160",
"imapflow": "^1.0.164",
"js-yaml": "^4.1.0",
"limiter": "2.0.1",
"mailparser": "^3.7.1",
"node-fetch": "^3.3.0",
"node-fetch": "^3.3.2",
"openapi-backend": "^5.10.6",
"p-retry": "^5.1.2",
"prevvy": "^7.0.1",
"qs": "^6.12.1",
"sharp": "^0.33.4",
"pg": "8.8.0",
"prevvy": "^7.5.0",
"qs": "^6.13.0",
"rate-limiter-flexible": "^5.0.3",
"sharp": "^0.33.5",
"slugify": "^1.6.6",
"tsx": "^4.7.2",
"swagger-editor-dist": "^4.13.1",
"swagger-ui-dist": "^5.17.14",
"tsx": "^4.18.0",
"xpath": "^0.0.34"
},
"packageManager": "pnpm@9.2.0",
"packageManager": "pnpm@9.6.0",
"devDependencies": {
"@babel/preset-env": "^7.25.0",
"@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7",
"@futureporn/utils": "workspace:^",
"@jest/globals": "^29.7.0",
"@types/chai": "^4.3.16",
"@types/chai": "^4.3.18",
"@types/cheerio": "^0.22.35",
"@types/jest": "^29.5.12",
"@types/mailparser": "^3.4.4",
"@types/mocha": "^10.0.7",
"@types/sinon": "^17.0.3",
"chai": "^5.1.0",
"chai": "^5.1.1",
"esmock": "^2.6.7",
"jest": "^29.7.0",
"mocha": "^10.4.0",
"mocha": "^10.7.3",
"nodemon": "^3.1.4",
"sinon": "^15.2.0",
"ts-node": "^10.9.2",
"tsup": "^8.1.2",
"typescript": "^5.5.3"
"tsup": "^8.2.4",
"typescript": "^5.5.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
openapi: '3.0.1'
info:
title: "@futureporn/scout"
version: '4.0.1'
description: REST API for acquiring vtuber data
paths:
'/pets':
get:
operationId: getPets
summary: List pets
responses:
200:
$ref: '#/components/responses/PetListWithExample'
'/pets/{id}':
get:
operationId: getPetById
summary: Get pet by its id
parameters:
- name: id
in: path
required: true
schema:
type: integer
minimum: 1
responses:
200:
$ref: '#/components/responses/PetResponseWithSchema'
components:
responses:
PetListWithExample:
description: List of pets
content:
'application/json':
example:
- id: 1
name: Garfield
- id: 2
name: Odie
PetResponseWithSchema:
description: A single pet
content:
'application/json':
schema:
type: object
properties:
id:
type: integer
minimum: 1
name:
type: string
example: Garfield

View File

@ -0,0 +1,45 @@
import { describe } from 'mocha'
import { expect } from 'chai';
import { getBroadcasterDisplayName, getInitialRoomDossier, getRandomRoom } from './cb.js'
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import fs from 'node:fs/promises'
const __dirname = dirname(fileURLToPath(import.meta.url));
const cbRoomFixturePath = join(__dirname, './fixtures/cb.projektmelody.fixture.html')
describe('cb', function () {
let fixture: string
beforeEach(async function () {
const fileContent = await fs.readFile(cbRoomFixturePath, { encoding: 'utf-8' })
fixture = fileContent
})
describe('unit', function () {
describe('getInitialRoomDossier', function () {
it('should return a Dossier object', function () {
const dossier = getInitialRoomDossier(fixture)
expect(dossier).to.have.property('broadcaster_username', 'projektmelody')
expect(dossier).to.have.property('broadcaster_gender', 'female')
expect(dossier).to.have.property('room_uid', 'G0TWFS5')
})
})
describe('getBroadcasterDisplayName', function () {
it('should get a formatted display name', function () {
expect(fixture).to.match(/Chaturbate/)
const displayName = getBroadcasterDisplayName(fixture)
expect(displayName).to.equal('Projektmelody')
})
})
})
describe('integration', function () {
describe('getRandomRoom', function () {
it('should return a Room object of an online room', async function () {
this.timeout(1000*16)
const room = await getRandomRoom()
expect(room).to.have.property('url')
expect(room).to.have.property('name')
expect(room.name).to.match(/[a-z_]/)
expect(room.url).to.match(/https:\/\//)
})
})
})
})

233
services/scout/src/cb.ts Normal file
View File

@ -0,0 +1,233 @@
import * as cheerio from 'cheerio'
import fetch from 'node-fetch'
import scrapingFetch from './scrapingFetch.ts';
interface Dossier {
viewer_uid: null | string;
is_age_verified: boolean;
age: number;
room_status: 'offline' | 'online';
num_viewers: number;
wschat_host: string;
viewer_username: string;
viewer_gender: 'm' | 'f';
allow_anonymous_tipping: boolean;
chat_username: string;
chat_password: string;
broadcaster_username: string;
room_pass: string;
last_pass: string;
chat_rules: string;
room_title: string;
room_uid: string;
broadcaster_uid: string;
broadcaster_gender: 'female' | 'male';
apps_running: string;
hls_source: string;
dismissible_messages: string[];
edge_auth: string;
is_widescreen: boolean;
allow_private_shows: boolean;
private_show_price: number;
private_min_minutes: number;
allow_show_recordings: boolean;
spy_private_show_price: number;
private_show_id: string;
low_satisfaction_score: boolean;
hidden_message: string;
following: boolean;
follow_notification_frequency: string;
is_moderator: boolean;
chat_settings: {
font_size: string;
show_emoticons: boolean;
emoticon_autocomplete_delay: number;
sort_users_key: string;
room_entry_for: 'org' | 'user';
room_leave_for: 'org' | 'user';
c2c_notify_limit: number;
silence_broadcasters: string;
allowed_chat: 'all' | 'whisper';
collapse_notices: boolean;
highest_token_color: string;
mod_expire: number;
max_pm_age: number;
font_family: string;
font_color: string;
tip_volume: number;
ignored_users: string;
};
broadcaster_on_new_chat: boolean;
token_balance: number;
is_supporter: boolean;
needs_supporter_to_pm: boolean;
server_name: string;
num_followed: number;
num_followed_online: number;
has_studio: boolean;
is_mobile: boolean;
ignored_emoticons: string[];
tfa_enabled: boolean;
satisfaction_score: {
percent: number;
up_votes: number;
down_votes: number;
max: number;
};
hide_satisfaction_score: boolean;
tips_in_past_24_hours: number;
last_vote_in_past_24_hours: string | null;
last_vote_in_past_90_days_down: boolean;
show_mobile_site_banner_link: boolean;
exploring_hashtag: string;
source_name: 'un' | 'o';
performer_has_fanclub: boolean;
opt_out: boolean;
fan_club_is_member: boolean;
asp_auth_url: string;
browser_id: string;
edge_region: string;
userlist_color: 'g' | 'b' | 'r';
active_password: boolean;
fan_club_paid_with_tokens: boolean;
quality: {
quality: string;
rate: number;
stopped: boolean;
};
}
export interface ChaturbateModel {
gender: string;
location: string;
current_show: 'public' | 'private';
username: string;
room_subject: string;
tags: string[];
is_new: boolean;
num_users: number;
num_followers: number;
country: string;
spoken_languages: string;
display_name: string;
birthday: string;
is_hd: boolean;
age: number;
seconds_online: number;
image_url: string;
image_url_360x270: string;
chat_room_url_revshare: string;
iframe_embed_revshare: string;
chat_room_url: string;
iframe_embed: string;
slug: string;
}
export interface ChaturbateOnlineModelsResponse {
results: ChaturbateModel[],
count: number
}
export interface Room {
name: string;
url: string;
}
// display_name is tricky.
// there is no capitaliziation in dossier.broadcaster_username.
// there is a `Real Name` k/v field in CB bios, but it's not foolproof as the broadcaster can enter anything, append notes, etc.
// so for now I'm just going to use the lowercase dossier.broadcaster_username
// actually, we can get username from <meta property="og:title" content="Watch Projektmelody live on Chaturbate!" />
export async function fetchHtml(url: string): Promise<string> {
const fetchOptions = {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
}
}
const res = await scrapingFetch(url, fetchOptions);
const body = await res.text()
return body
}
export function getBroadcasterDisplayName(html: string): string|null {
if (!html) throw new Error('getBroadcasterDisplayName requires html as argument');
const $ = cheerio.load(html);
const ogTitle = $('meta[property="og:title"]')?.attr('content')?.split(' ')?.at(1);
return (!!ogTitle) ? ogTitle : null
}
/**
* getInitialRoomDossier
*
* Gets data about the model from their CB page.
*
* @param {String} roomUrl example: https://chaturbate.com/projektmelody
* @returns {Object} initialRoomDossier
*
* @todo please get a fixture of when a room is in password mode
*/
export function getInitialRoomDossier(html: string): Dossier|null {
if (!html) throw new Error('getInitialRoomDossier requires html as argument');
try {
const $ = cheerio.load(html);
let rawScript = $('script:contains(window.initialRoomDossier)').html();
if (!rawScript) {
throw new Error('window.initialRoomDossier is null. This could mean the channel is in password mode');
}
let rawDossier = rawScript.slice(rawScript.indexOf('"'), rawScript.lastIndexOf('"') + 1);
let dossier: Dossier = JSON.parse(JSON.parse(rawDossier));
return dossier;
} catch (error) {
if (error instanceof Error) {
// Handle the error gracefully
console.error(`Error fetching initial room dossier: ${error.message}`);
} else {
console.error('caught an exotic error, uh-oh')
console.error(error)
}
return null;
}
}
export async function getRandomRoom(): Promise<Room> {
try {
const res = await scrapingFetch('https://chaturbate.com/api/public/affiliates/onlinerooms/?wm=DiPkB&client_ip=request_ip', {
headers: {
accept: 'application/json'
}
});
const data = await res.json() as ChaturbateOnlineModelsResponse;
if (!data || !Array.isArray(data.results) || data.results.length === 0) {
throw new Error('No results found');
}
const results = data.results;
const randomIndex = Math.floor(Math.random() * results.length);
if (!results[randomIndex]) {
throw new Error('No result found at random index');
}
const username = results[randomIndex].username;
return {
url: `https://chaturbate.com/${username}`,
name: username
}
} catch (error) {
if (error instanceof Error) {
console.error(`Error in getRandomRoom: ${error.message}`);
} else {
console.error('An unexpected error occurred');
}
throw error; // Re-throw the error to propagate it further
}
}

View File

@ -0,0 +1,35 @@
import 'dotenv/config'
const requiredEnvVars = [
'HTTP_PROXY',
'POSTGREST_URL',
'NODE_ENV',
'WORKER_CONNECTION_STRING',
'PORT',
] as const;
const getEnvVar = (key: typeof requiredEnvVars[number]): string => {
const value = process.env[key];
if (!value) {
throw new Error(`Missing ${key} env var`);
}
return value;
};
export interface Config {
postgrestUrl: string;
httpProxy: string;
nodeEnv: string;
workerConnectionString: string;
port: number;
}
export const configs: Config = {
httpProxy: getEnvVar('HTTP_PROXY'),
postgrestUrl: getEnvVar('POSTGREST_URL'),
nodeEnv: getEnvVar('NODE_ENV'),
workerConnectionString: getEnvVar('WORKER_CONNECTION_STRING'),
port: parseInt(getEnvVar('PORT')),
}

View File

@ -0,0 +1,851 @@
/* @see https://github.com/Amoenus/SwaggerDark/tree/master */
@media only screen and (prefers-color-scheme: dark) {
a { color: #8c8cfa; }
::-webkit-scrollbar-track-piece { background-color: rgba(255, 255, 255, .2) !important; }
::-webkit-scrollbar-track { background-color: rgba(255, 255, 255, .3) !important; }
::-webkit-scrollbar-thumb { background-color: rgba(255, 255, 255, .5) !important; }
embed[type="application/pdf"] { filter: invert(90%); }
html {
background: #1f1f1f !important;
box-sizing: border-box;
filter: contrast(100%) brightness(100%) saturate(100%);
overflow-y: scroll;
}
body {
background: #1f1f1f;
background-color: #1f1f1f;
background-image: none !important;
}
button, input, select, textarea {
background-color: #1f1f1f;
color: #bfbfbf;
}
font, html { color: #bfbfbf; }
.swagger-ui, .swagger-ui section h3 { color: #b5bac9; }
.swagger-ui a { background-color: transparent; }
.swagger-ui mark {
background-color: #664b00;
color: #bfbfbf;
}
.swagger-ui legend { color: inherit; }
.swagger-ui .debug * { outline: #e6da99 solid 1px; }
.swagger-ui .debug-white * { outline: #fff solid 1px; }
.swagger-ui .debug-black * { outline: #bfbfbf solid 1px; }
.swagger-ui .debug-grid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0; }
.swagger-ui .debug-grid-16 { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0; }
.swagger-ui .debug-grid-8-solid { background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21; }
.swagger-ui .debug-grid-16-solid { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21; }
.swagger-ui .b--black { border-color: #000; }
.swagger-ui .b--near-black { border-color: #121212; }
.swagger-ui .b--dark-gray { border-color: #333; }
.swagger-ui .b--mid-gray { border-color: #545454; }
.swagger-ui .b--gray { border-color: #787878; }
.swagger-ui .b--silver { border-color: #999; }
.swagger-ui .b--light-silver { border-color: #6e6e6e; }
.swagger-ui .b--moon-gray { border-color: #4d4d4d; }
.swagger-ui .b--light-gray { border-color: #2b2b2b; }
.swagger-ui .b--near-white { border-color: #242424; }
.swagger-ui .b--white { border-color: #1c1c21; }
.swagger-ui .b--white-90 { border-color: rgba(28, 28, 33, .9); }
.swagger-ui .b--white-80 { border-color: rgba(28, 28, 33, .8); }
.swagger-ui .b--white-70 { border-color: rgba(28, 28, 33, .7); }
.swagger-ui .b--white-60 { border-color: rgba(28, 28, 33, .6); }
.swagger-ui .b--white-50 { border-color: rgba(28, 28, 33, .5); }
.swagger-ui .b--white-40 { border-color: rgba(28, 28, 33, .4); }
.swagger-ui .b--white-30 { border-color: rgba(28, 28, 33, .3); }
.swagger-ui .b--white-20 { border-color: rgba(28, 28, 33, .2); }
.swagger-ui .b--white-10 { border-color: rgba(28, 28, 33, .1); }
.swagger-ui .b--white-05 { border-color: rgba(28, 28, 33, .05); }
.swagger-ui .b--white-025 { border-color: rgba(28, 28, 33, .024); }
.swagger-ui .b--white-0125 { border-color: rgba(28, 28, 33, .01); }
.swagger-ui .b--black-90 { border-color: rgba(0, 0, 0, .9); }
.swagger-ui .b--black-80 { border-color: rgba(0, 0, 0, .8); }
.swagger-ui .b--black-70 { border-color: rgba(0, 0, 0, .7); }
.swagger-ui .b--black-60 { border-color: rgba(0, 0, 0, .6); }
.swagger-ui .b--black-50 { border-color: rgba(0, 0, 0, .5); }
.swagger-ui .b--black-40 { border-color: rgba(0, 0, 0, .4); }
.swagger-ui .b--black-30 { border-color: rgba(0, 0, 0, .3); }
.swagger-ui .b--black-20 { border-color: rgba(0, 0, 0, .2); }
.swagger-ui .b--black-10 { border-color: rgba(0, 0, 0, .1); }
.swagger-ui .b--black-05 { border-color: rgba(0, 0, 0, .05); }
.swagger-ui .b--black-025 { border-color: rgba(0, 0, 0, .024); }
.swagger-ui .b--black-0125 { border-color: rgba(0, 0, 0, .01); }
.swagger-ui .b--dark-red { border-color: #bc2f36; }
.swagger-ui .b--red { border-color: #c83932; }
.swagger-ui .b--light-red { border-color: #ab3c2b; }
.swagger-ui .b--orange { border-color: #cc6e33; }
.swagger-ui .b--purple { border-color: #5e2ca5; }
.swagger-ui .b--light-purple { border-color: #672caf; }
.swagger-ui .b--dark-pink { border-color: #ab2b81; }
.swagger-ui .b--hot-pink { border-color: #c03086; }
.swagger-ui .b--pink { border-color: #8f2464; }
.swagger-ui .b--light-pink { border-color: #721d4d; }
.swagger-ui .b--dark-green { border-color: #1c6e50; }
.swagger-ui .b--green { border-color: #279b70; }
.swagger-ui .b--light-green { border-color: #228762; }
.swagger-ui .b--navy { border-color: #0d1d35; }
.swagger-ui .b--dark-blue { border-color: #20497e; }
.swagger-ui .b--blue { border-color: #4380d0; }
.swagger-ui .b--light-blue { border-color: #20517e; }
.swagger-ui .b--lightest-blue { border-color: #143a52; }
.swagger-ui .b--washed-blue { border-color: #0c312d; }
.swagger-ui .b--washed-green { border-color: #0f3d2c; }
.swagger-ui .b--washed-red { border-color: #411010; }
.swagger-ui .b--transparent { border-color: transparent; }
.swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow { border-color: #664b00; }
.swagger-ui .shadow-1 { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; }
.swagger-ui .shadow-2 { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; }
.swagger-ui .shadow-3 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; }
.swagger-ui .shadow-4 { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; }
.swagger-ui .shadow-5 { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; }
@media screen and (min-width: 30em) {
.swagger-ui .shadow-1-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; }
.swagger-ui .shadow-2-ns { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; }
.swagger-ui .shadow-3-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; }
.swagger-ui .shadow-4-ns { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; }
.swagger-ui .shadow-5-ns { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; }
}
@media screen and (max-width: 60em) and (min-width: 30em) {
.swagger-ui .shadow-1-m { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; }
.swagger-ui .shadow-2-m { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; }
.swagger-ui .shadow-3-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; }
.swagger-ui .shadow-4-m { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; }
.swagger-ui .shadow-5-m { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; }
}
@media screen and (min-width: 60em) {
.swagger-ui .shadow-1-l { box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px; }
.swagger-ui .shadow-2-l { box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px; }
.swagger-ui .shadow-3-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px; }
.swagger-ui .shadow-4-l { box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0; }
.swagger-ui .shadow-5-l { box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0; }
}
.swagger-ui .black-05 { color: rgba(191, 191, 191, .05); }
.swagger-ui .bg-black-05 { background-color: rgba(0, 0, 0, .05); }
.swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover { color: rgba(191, 191, 191, .9); }
.swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover { color: rgba(191, 191, 191, .8); }
.swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover { color: rgba(191, 191, 191, .7); }
.swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover { color: rgba(191, 191, 191, .6); }
.swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover { color: rgba(191, 191, 191, .5); }
.swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover { color: rgba(191, 191, 191, .4); }
.swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover { color: rgba(191, 191, 191, .3); }
.swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover { color: rgba(191, 191, 191, .2); }
.swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover { color: rgba(191, 191, 191, .1); }
.swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 { color: rgba(255, 255, 255, .9); }
.swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 { color: rgba(255, 255, 255, .8); }
.swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 { color: rgba(255, 255, 255, .7); }
.swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 { color: rgba(255, 255, 255, .6); }
.swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 { color: rgba(255, 255, 255, .5); }
.swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 { color: rgba(255, 255, 255, .4); }
.swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 { color: rgba(255, 255, 255, .3); }
.swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 { color: rgba(255, 255, 255, .2); }
.swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 { color: rgba(255, 255, 255, .1); }
.swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray { color: #ccc; }
.swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray { color: #ededed; }
.swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white { color: #f5f5f5; }
.swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover { color: #e6999d; }
.swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red { color: #e69d99; }
.swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red { color: #e6a399; }
.swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange { color: #e6b699; }
.swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover { color: #e6d099; }
.swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow { color: #e6da99; }
.swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow { color: #ede6b6; }
.swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple { color: #b99ae4; }
.swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple { color: #bb99e6; }
.swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover { color: #e699cc; }
.swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink { color: #e699c7; }
.swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink { color: #edb6d5; }
.swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover { color: #99e6c9; }
.swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green { color: #a1e8ce; }
.swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy { color: #99b8e6; }
.swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover { color: #99bae6; }
.swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue { color: #a9cbea; }
.swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue { color: #d6e9f5; }
.swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue { color: #f7fdfc; }
.swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green { color: #ebfaf4; }
.swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow { color: #fbf9ef; }
.swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red { color: #f9e7e7; }
.swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover { color: inherit; }
.swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover { background-color: rgba(0, 0, 0, .9); }
.swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover { background-color: rgba(0, 0, 0, .8); }
.swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover { background-color: rgba(0, 0, 0, .7); }
.swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover { background-color: rgba(0, 0, 0, .6); }
.swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover { background-color: rgba(0, 0, 0, .5); }
.swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover { background-color: rgba(0, 0, 0, .4); }
.swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover { background-color: rgba(0, 0, 0, .3); }
.swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover { background-color: rgba(0, 0, 0, .2); }
.swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover { background-color: rgba(28, 28, 33, .9); }
.swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover { background-color: rgba(28, 28, 33, .8); }
.swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover { background-color: rgba(28, 28, 33, .7); }
.swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover { background-color: rgba(28, 28, 33, .6); }
.swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover { background-color: rgba(28, 28, 33, .5); }
.swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover { background-color: rgba(28, 28, 33, .4); }
.swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover { background-color: rgba(28, 28, 33, .3); }
.swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover { background-color: rgba(28, 28, 33, .2); }
.swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover { background-color: #000; }
.swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover { background-color: #121212; }
.swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover { background-color: #333; }
.swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover { background-color: #545454; }
.swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover { background-color: #787878; }
.swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover { background-color: #999; }
.swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover { background-color: #1c1c21; }
.swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover { background-color: transparent; }
.swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover { background-color: #bc2f36; }
.swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover { background-color: #c83932; }
.swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover { background-color: #ab3c2b; }
.swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover { background-color: #cc6e33; }
.swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover { background-color: #664b00; }
.swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover { background-color: #5e2ca5; }
.swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover { background-color: #672caf; }
.swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover { background-color: #ab2b81; }
.swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover { background-color: #c03086; }
.swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover { background-color: #8f2464; }
.swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover { background-color: #721d4d; }
.swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover { background-color: #1c6e50; }
.swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover { background-color: #279b70; }
.swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover { background-color: #228762; }
.swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover { background-color: #0d1d35; }
.swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover { background-color: #20497e; }
.swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover { background-color: #4380d0; }
.swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover { background-color: #20517e; }
.swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover { background-color: #143a52; }
.swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover { background-color: #0c312d; }
.swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover { background-color: #0f3d2c; }
.swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover { background-color: #411010; }
.swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover { background-color: inherit; }
.swagger-ui .shadow-hover { transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s; }
.swagger-ui .shadow-hover::after {
border-radius: inherit;
box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px;
content: "";
height: 100%;
left: 0;
opacity: 0;
position: absolute;
top: 0;
transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s;
width: 100%;
z-index: -1;
}
.swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover { transition: background-color .15s ease-in-out 0s; }
.swagger-ui .nested-links a {
color: #99bae6;
transition: color .15s ease-in 0s;
}
.swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover {
color: #a9cbea;
transition: color .15s ease-in 0s;
}
.swagger-ui .opblock-tag {
border-bottom: 1px solid rgba(58, 64, 80, .3);
color: #b5bac9;
transition: all .2s ease 0s;
}
.swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg { transition: all .4s ease 0s; }
.swagger-ui .opblock {
border: 1px solid #000;
border-radius: 4px;
box-shadow: rgba(0, 0, 0, .19) 0 0 3px;
margin: 0 0 15px;
}
.swagger-ui .opblock .tab-header .tab-item.active h4 span::after { background: gray; }
.swagger-ui .opblock.is-open .opblock-summary { border-bottom: 1px solid #000; }
.swagger-ui .opblock .opblock-section-header {
background: rgba(28, 28, 33, .8);
box-shadow: rgba(0, 0, 0, .1) 0 1px 2px;
}
.swagger-ui .opblock .opblock-section-header > label > span { padding: 0 10px 0 0; }
.swagger-ui .opblock .opblock-summary-method {
background: #000;
color: #fff;
text-shadow: rgba(0, 0, 0, .1) 0 1px 0;
}
.swagger-ui .opblock.opblock-post {
background: rgba(72, 203, 144, .1);
border-color: #48cb90;
}
.swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after { background: #48cb90; }
.swagger-ui .opblock.opblock-post .opblock-summary { border-color: #48cb90; }
.swagger-ui .opblock.opblock-put {
background: rgba(213, 157, 88, .1);
border-color: #d59d58;
}
.swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after { background: #d59d58; }
.swagger-ui .opblock.opblock-put .opblock-summary { border-color: #d59d58; }
.swagger-ui .opblock.opblock-delete {
background: rgba(200, 50, 50, .1);
border-color: #c83232;
}
.swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after { background: #c83232; }
.swagger-ui .opblock.opblock-delete .opblock-summary { border-color: #c83232; }
.swagger-ui .opblock.opblock-get {
background: rgba(42, 105, 167, .1);
border-color: #2a69a7;
}
.swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after { background: #2a69a7; }
.swagger-ui .opblock.opblock-get .opblock-summary { border-color: #2a69a7; }
.swagger-ui .opblock.opblock-patch {
background: rgba(92, 214, 188, .1);
border-color: #5cd6bc;
}
.swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after { background: #5cd6bc; }
.swagger-ui .opblock.opblock-patch .opblock-summary { border-color: #5cd6bc; }
.swagger-ui .opblock.opblock-head {
background: rgba(140, 63, 207, .1);
border-color: #8c3fcf;
}
.swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after { background: #8c3fcf; }
.swagger-ui .opblock.opblock-head .opblock-summary { border-color: #8c3fcf; }
.swagger-ui .opblock.opblock-options {
background: rgba(36, 89, 143, .1);
border-color: #24598f;
}
.swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after { background: #24598f; }
.swagger-ui .opblock.opblock-options .opblock-summary { border-color: #24598f; }
.swagger-ui .opblock.opblock-deprecated {
background: rgba(46, 46, 46, .1);
border-color: #2e2e2e;
opacity: .6;
}
.swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after { background: #2e2e2e; }
.swagger-ui .opblock.opblock-deprecated .opblock-summary { border-color: #2e2e2e; }
.swagger-ui .filter .operation-filter-input { border: 2px solid #2b3446; }
.swagger-ui .tab li:first-of-type::after { background: rgba(0, 0, 0, .2); }
.swagger-ui .download-contents {
background: #7c8192;
color: #fff;
}
.swagger-ui .scheme-container {
background: #1c1c21;
box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0;
}
.swagger-ui .loading-container .loading::before {
animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity;
border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1);
}
.swagger-ui .response-control-media-type--accept-controller select { border-color: #196619; }
.swagger-ui .response-control-media-type__accept-message { color: #99e699; }
.swagger-ui .version-pragma__message code { background-color: #3b3b3b; }
.swagger-ui .btn {
background: 0 0;
border: 2px solid gray;
box-shadow: rgba(0, 0, 0, .1) 0 1px 2px;
color: #b5bac9;
}
.swagger-ui .btn:hover { box-shadow: rgba(0, 0, 0, .3) 0 0 5px; }
.swagger-ui .btn.authorize, .swagger-ui .btn.cancel {
background-color: transparent;
border-color: #a72a2a;
color: #e69999;
}
.swagger-ui .btn.cancel:hover {
background-color: #a72a2a;
color: #fff;
}
.swagger-ui .btn.authorize {
border-color: #48cb90;
color: #9ce3c3;
}
.swagger-ui .btn.authorize svg { fill: #9ce3c3; }
.btn.authorize.unlocked:hover {
background-color: #48cb90;
color: #fff;
}
.btn.authorize.unlocked:hover svg {
fill: #fbfbfb;
}
.swagger-ui .btn.execute {
background-color: #5892d5;
border-color: #5892d5;
color: #fff;
}
.swagger-ui .copy-to-clipboard { background: #7c8192; }
.swagger-ui .copy-to-clipboard button { background: url("data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill=\"%23fff\" fill-rule=\"evenodd\" d=\"M2 13h4v1H2v-1zm5-6H2v1h5V7zm2 3V8l-3 3 3 3v-2h5v-2H9zM4.5 9H2v1h2.5V9zM2 12h2.5v-1H2v1zm9 1h1v2c-.02.28-.11.52-.3.7-.19.18-.42.28-.7.3H1c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h3c0-1.11.89-2 2-2 1.11 0 2 .89 2 2h3c.55 0 1 .45 1 1v5h-1V6H1v9h10v-2zM2 5h8c0-.55-.45-1-1-1H8c-.55 0-1-.45-1-1s-.45-1-1-1-1 .45-1 1-.45 1-1 1H3c-.55 0-1 .45-1 1z\"/></svg>") 50% center no-repeat; }
.swagger-ui select {
background: url("data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"><path d=\"M13.418 7.859a.695.695 0 01.978 0 .68.68 0 010 .969l-3.908 3.83a.697.697 0 01-.979 0l-3.908-3.83a.68.68 0 010-.969.695.695 0 01.978 0L10 11l3.418-3.141z\"/></svg>") right 10px center/20px no-repeat #212121;
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21;
border: 2px solid #41444e;
}
.swagger-ui select[multiple] { background: #212121; }
.swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid {
background: #390e0e;
border-color: #c83232;
}
.swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea {
background: #1c1c21;
border: 1px solid #404040;
}
.swagger-ui textarea {
background: rgba(28, 28, 33, .8);
color: #b5bac9;
}
.swagger-ui input[disabled], .swagger-ui select[disabled] {
background-color: #1f1f1f;
color: #bfbfbf;
}
.swagger-ui textarea[disabled] {
background-color: #41444e;
color: #fff;
}
.swagger-ui select[disabled] { border-color: #878787; }
.swagger-ui textarea:focus { border: 2px solid #2a69a7; }
.swagger-ui .checkbox input[type=checkbox] + label > .item {
background: #303030;
box-shadow: #303030 0 0 0 2px;
}
.swagger-ui .checkbox input[type=checkbox]:checked + label > .item { background: url("data:image/svg+xml;charset=utf-8,<svg width=\"10\" height=\"8\" viewBox=\"3 7 10 8\" xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"%2341474E\" fill-rule=\"evenodd\" d=\"M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z\"/></svg>") 50% center no-repeat #303030; }
.swagger-ui .dialog-ux .backdrop-ux { background: rgba(0, 0, 0, .8); }
.swagger-ui .dialog-ux .modal-ux {
background: #1c1c21;
border: 1px solid #2e2e2e;
box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0;
}
.swagger-ui .dialog-ux .modal-ux-header .close-modal { background: 0 0; }
.swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td { color: #bfbfbf !important; }
.swagger-ui .model-toggle::after { background: url("data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg>") 50% center/100% no-repeat; }
.swagger-ui .model-hint {
background: rgba(0, 0, 0, .7);
color: #ebebeb;
}
.swagger-ui section.models { border: 1px solid rgba(58, 64, 80, .3); }
.swagger-ui section.models.is-open h4 { border-bottom: 1px solid rgba(58, 64, 80, .3); }
.swagger-ui section.models .model-container { background: rgba(0, 0, 0, .05); }
.swagger-ui section.models .model-container:hover { background: rgba(0, 0, 0, .07); }
.swagger-ui .model-box { background: rgba(0, 0, 0, .1); }
.swagger-ui .prop-type { color: #aaaad4; }
.swagger-ui table thead tr td, .swagger-ui table thead tr th {
border-bottom: 1px solid rgba(58, 64, 80, .2);
color: #b5bac9;
}
.swagger-ui .parameter__name.required::after { color: rgba(230, 153, 153, .6); }
.swagger-ui .topbar .download-url-wrapper .select-label { color: #f0f0f0; }
.swagger-ui .topbar .download-url-wrapper .download-url-button {
background: #63a040;
color: #fff;
}
.swagger-ui .info .title small { background: #7c8492; }
.swagger-ui .info .title small.version-stamp { background-color: #7a9b27; }
.swagger-ui .auth-container .errors {
background-color: #350d0d;
color: #b5bac9;
}
.swagger-ui .errors-wrapper {
background: rgba(200, 50, 50, .1);
border: 2px solid #c83232;
}
.swagger-ui .markdown code, .swagger-ui .renderedmarkdown code {
background: rgba(0, 0, 0, .05);
color: #c299e6;
}
.swagger-ui .model-toggle:after { background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat; }
/* arrows for each operation and request are now white */
.arrow, #large-arrow-up { fill: #fff; }
#unlocked { fill: #fff; }
::-webkit-scrollbar-track { background-color: #646464 !important; }
::-webkit-scrollbar-thumb {
background-color: #242424 !important;
border: 2px solid #3e4346 !important;
}
::-webkit-scrollbar-button:vertical:start:decrement {
background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:vertical:end:increment {
background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:end:increment {
background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:start:decrement {
background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece { background-color: #3e4346 !important; }
.swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension { color: #bfbfbf; }
.swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white { color: #fff; }
.swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) { background-color: rgba(0, 0, 0, .1); }
.swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) { background-color: rgba(28, 28, 33, .1); }
.swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) { background-color: #6e6e6e; }
.swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) { background-color: #4d4d4d; }
.swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) { background-color: #2b2b2b; }
.swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) { background-color: #242424; }
.swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover { background: rgba(0, 0, 0, .02); }
.swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td { color: #b5bac9; }
.swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star { color: #e69999; }
.swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl {
background: #41444e;
border-radius: 4px;
color: #fff;
}
.swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg { fill: #bfbfbf; }
.swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header { border-bottom: 1px solid #2e2e2e; }
.swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] { border: 2px solid #63a040; }
.swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a { color: #99bde6; }
/* Dark Scrollbar */
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-button {
background-color: #3e4346 !important;
}
::-webkit-scrollbar-track {
background-color: #646464 !important;
}
::-webkit-scrollbar-track-piece {
background-color: #3e4346 !important;
}
::-webkit-scrollbar-thumb {
height: 50px;
background-color: #242424 !important;
border: 2px solid #3e4346 !important;
}
::-webkit-scrollbar-corner {}
::-webkit-resizer {}
::-webkit-scrollbar-button:vertical:start:decrement {
background:
linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:vertical:end:increment {
background:
linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:end:increment {
background:
linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:start:decrement {
background:
linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
}

View File

@ -0,0 +1,47 @@
import { expect } from 'chai'
import { usernameRegex, normalize, urlFromUsername, getAccountData } from './fansly.ts'
import { describe } from 'mocha'
describe('fansly', function () {
describe('integration', function () {
describe('getAccountData', function () {
it('should get username, id, and location', async function () {
const data = await getAccountData('projektmelody')
// console.log(data)
expect(data).to.have.property('username', 'ProjektMelody')
expect(data).to.have.property('id', '284824898138812416')
expect(data).to.have.property('location', 'Casting Couch, JP')
})
})
})
describe('unit', function () {
describe('regex', function () {
describe('username', function () {
it('should get the username of the channel', function () {
expect(usernameRegex.exec('https://fansly.com/18Plus/posts')?.at(1)).to.equal('18Plus')
expect(usernameRegex.exec('https://fansly.com/projektmelody/posts')?.at(1)).to.equal('projektmelody')
expect(usernameRegex.exec('https://fansly.com/GoodKittenVR')?.at(1)).to.equal('GoodKittenVR')
expect(usernameRegex.exec('https://fansly.com/live/MzLewdieB')?.at(1)).to.equal('MzLewdieB')
expect(usernameRegex.exec('https://fansly.com/live/340602399334871040')?.at(1)).to.equal('340602399334871040')
})
})
})
describe('url', function () {
describe('fromUsername', function () {
it('should accept a channel name and give us a valid channel URL', function () {
expect(urlFromUsername('projektmelody')).to.equal('https://fansly.com/projektmelody')
expect(urlFromUsername('GoodKittenVR')).to.equal('https://fansly.com/GoodKittenVR')
expect(urlFromUsername('MzLewdieB')).to.equal('https://fansly.com/MzLewdieB')
expect(urlFromUsername('340602399334871040')).to.equal('https://fansly.com/340602399334871040')
})
})
describe('normalize', function () {
it('should accept a live URL and return a normal channel url.', function () {
expect(normalize('https://fansly.com/live/projektmelody')).to.equal('https://fansly.com/projektmelody')
expect(normalize('https://fansly.com/live/340602399334871040')).to.equal('https://fansly.com/340602399334871040')
expect(normalize('https://fansly.com/live/GoodKittenVR')).to.equal('https://fansly.com/GoodKittenVR')
})
})
})
})
})

View File

@ -0,0 +1,171 @@
import { download, getTmpFile } from '@futureporn/utils';
import type { VtuberRecord } from '@futureporn/types';
import { ua0 } from './ua.ts';
import scrapingFetch from './scrapingFetch.ts';
export interface FanslyProfile {
id: string;
username: string;
displayName?: string | null;
flags: number;
version: number;
followCount: number;
subscriberCount: number;
permissions: {
accountPermissionFlags: {
flags: number;
};
};
timelineStats: {
accountId: string;
imageCount: number;
videoCount: number;
bundleCount: number;
bundleImageCount: number;
bundleVideoCount: number;
fetchedAt: number;
};
profileAccessFlags: number;
profileFlags: number;
about: string;
location: string;
profileSocials: any[];
pinnedPosts: {
postId: string;
accountId: string;
pos: number;
createdAt: number;
}[];
statusId: number;
lastSeenAt: number;
postLikes: number;
accountMediaLikes: number;
avatar: {
id: string;
type: number;
status: number;
accountId: string;
mimetype: string;
flags: number;
location: string;
width: number;
height: number;
metadata: string;
updatedAt: number;
createdAt: number;
variants: any[];
variantHash: {};
locations: any[];
};
banner: {
id: string;
type: number;
status: number;
accountId: string;
mimetype: string;
flags: number;
location: string;
width: number;
height: number;
metadata: string;
updatedAt: number;
createdAt: number;
variants: any[];
variantHash: {};
locations: any[];
};
mediaStoryState: {
accountId: string;
status: number;
storyCount: number;
version: number;
createdAt: number;
updatedAt: number;
hasActiveStories: boolean;
};
subscriptionTiers: {
id: string;
accountId: string;
name: string;
color: string;
pos: number;
price: number;
maxSubscribers: number;
subscriptionBenefits?: any[];
includedTierIds: string[];
plans: any[];
}[];
streaming: {
accountId: string;
channel: {
id: string;
accountId: string;
playbackUrl: string;
chatRoomId: string;
status: number;
version: number;
createdAt: number;
updatedAt?: null | number;
stream: any;
arn: null;
ingestEndpoint: null;
};
enabled: boolean;
};
walls: {
id: string;
accountId: string;
pos: null;
name: string;
description: '';
metadata: '';
}[];
profileAccess: boolean;
}
export const usernameRegex = new RegExp(/^https:\/\/fansly\.com\/(?:live\/)?([^\/]+)/)
export const urlFromUsername = (username: string) => `https://fansly.com/${username}`
export const normalize = (url: string) => {
if (!url) throw new Error('normalized received a null or undefined url.');
const username = usernameRegex.exec(url)?.at(1)
if (!username) throw new Error('failed to get username from url');
return urlFromUsername(username)
}
export const imageUrlFromUsername = async function image(fanslyUserId: string) {
if (!fanslyUserId) throw new Error(`first arg passed to fansly.data.image must be a {string} fanslyUserId`);
const url = `https://api.fansly.com/api/v1/account/${fanslyUserId}/avatar`
const filePath = getTmpFile('avatar.jpg')
return download({ filePath, url })
}
export async function getProfileImage(username: string): Promise<string> {
const data = await getAccountData(username)
return data.avatar.location
}
export async function getAccountData(username: string): Promise<FanslyProfile> {
const fetchUrl = `https://apiv3.fansly.com/api/v1/account?usernames=${username}&ngsw-bypass=true`
const fetchOptions = {
method: 'GET',
headers: {
'User-Agent': ua0,
'Accept': 'application/json'
}
}
const res = await scrapingFetch(fetchUrl, fetchOptions)
if (!res.ok) {
const body = await res.text()
const msg = `failed to fetch getAccountData res.status=${res.status}, res.statusText=${res.statusText}, body=${body}`
console.error(msg)
throw new Error(msg)
}
const data = await res.json() as any
if (!data?.success) throw new Error('data.success was expected to exist but it is absent.');
if (data.success !== true) throw new Error('data.success was not true');
if (!data?.response) throw new Error('data.response was expected to exist but it is absent.');
return data.response[0]
}

View File

@ -0,0 +1,106 @@
import Fastify from 'fastify'
import fastifySwagger from '@fastify/swagger'
import fastifySwaggerUi from '@fastify/swagger-ui'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url));
const swaggerDarkCss = readFileSync(join(__dirname, './css/SwaggerDark.css'), { encoding: 'utf-8' })
async function fastifySetup(configs) {
const fastify = Fastify({
logger: true
})
await fastify.register(fastifySwagger)
await fastify.register(fastifySwaggerUi, {
theme: {
title: '@futureporn/scout',
css: [
{ filename: 'SwaggerDark.css', content: swaggerDarkCss }
]
},
routePrefix: '/',
uiConfig: {
docExpansion: 'full',
deepLinking: false
},
uiHooks: {
onRequest: function (request, reply, next) { next() },
preHandler: function (request, reply, next) { next() }
},
staticCSP: true,
transformStaticCSP: (header) => header,
transformSpecification: (swaggerObject, request, reply) => { return swaggerObject },
transformSpecificationClone: true
})
fastify.put('/some-route/:id', {
schema: {
description: 'post some data',
tags: ['vtuber'],
summary: 'qwerty',
params: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'user id'
}
}
},
body: {
type: 'object',
properties: {
hello: { type: 'string' },
obj: {
type: 'object',
properties: {
some: { type: 'string' }
}
}
}
},
response: {
201: {
description: 'Successful response',
type: 'object',
properties: {
hello: { type: 'string' }
}
},
default: {
description: 'Default response',
type: 'object',
properties: {
foo: { type: 'string' }
}
}
},
}
}, (req, reply) => {
reply.send('HAHAHAHAHAH')
})
fastify.listen({ port: configs.port }, function (err, address) {
console.log(`@futureporn/scout listening on ${address}`)
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
}
export default fastifySetup

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
import fastify from './fastify.js'
import { configs } from './config.js'
fastify(configs)

View File

@ -0,0 +1,13 @@
import { readFileSync } from 'node:fs'
import yaml from 'js-yaml'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
export function loadOpenApiSpec () {
const rawFile = readFileSync(join(__dirname, './api.yaml'), { encoding: 'utf-8' })
const doc = yaml.load(rawFile)
return doc
}

View File

@ -0,0 +1,14 @@
import scrapeVtuberData from "./scrapeVtuberData.ts"
import { expect } from "chai"
describe('integration', function () {
describe('scrapeVtuberData', function () {
it('should get displayname, username, and id from chaturbate', async function () {
const vtuber = await scrapeVtuberData('https://chaturbate.com/projektmelody')
expect(vtuber).to.have.property('display_name', 'Projektmelody')
expect(vtuber).to.have.property('chaturbate_id', 'G0TWFS5')
expect(vtuber).to.have.property('slug', 'projektmelody')
expect(vtuber).to.have.property('theme_color', '#5C23C0')
})
})
})

Some files were not shown because too many files have changed in this diff Show More