combine_video_segments progress
ci / build (push) Failing after 1s Details

This commit is contained in:
CJ_Clippy 2024-09-03 08:28:39 -08:00
parent a5e4fee3a3
commit 614bf16cf8
104 changed files with 4957 additions and 10681 deletions

View File

@ -202,7 +202,6 @@ docker_build(
'./pnpm-workspace.yaml',
'./packages/types',
'./packages/utils',
'./packages/image',
'./services/scout',
],
dockerfile='./dockerfiles/scout.dockerfile',
@ -244,12 +243,12 @@ cmd_button('postgres:refresh',
text='Refresh schema cache'
)
cmd_button('capture-api:create',
argv=['http', '--ignore-stdin', 'POST', 'http://localhost:5003/api/record', "url='https://twitch.tv/ironmouse'", "channel='ironmouse'"],
resource='capture-api',
icon_name='send',
text='Start Recording'
)
# cmd_button('capture-api:create',
# argv=['http', '--ignore-stdin', 'POST', 'http://localhost:5003/api/record', "url='https://twitch.tv/ironmouse'", "channel='ironmouse'"],
# resource='capture-api',
# icon_name='send',
# text='Start Recording'
# )
cmd_button('postgres:migrate',
argv=['./scripts/postgres-migrations.sh'],
@ -322,7 +321,6 @@ docker_build(
'./package.json',
'./pnpm-lock.yaml',
'./pnpm-workspace.yaml',
'./packages/image',
'./services/mailbox',
'./packages/types',
'./packages/utils',
@ -339,25 +337,6 @@ docker_build(
# docker_build(
# 'fp/meal',
# '.',
# dockerfile='dockerfiles/meal.dockerfile',
# target='meal',
# only=[
# './.npmrc',
# './package.json',
# './pnpm-lock.yaml',
# './pnpm-workspace.yaml',
# './packages/meal',
# './packages/taco',
# './packages/types',
# ],
# live_update=[
# sync('./packages/meal', '/app'),
# # run('cd /app && pnpm i', trigger=['./packages/meal/package.json', './packages/meal/pnpm-lock.yaml']),
# ],
# )
docker_build(
'fp/capture',
@ -526,12 +505,12 @@ k8s_resource(
labels=['backend'],
resource_deps=['postgrest'],
)
k8s_resource(
workload='capture-api',
port_forwards=['5003'],
labels=['backend'],
resource_deps=['postgrest', 'postgresql-primary'],
)
# k8s_resource(
# workload='capture-api',
# port_forwards=['5003'],
# labels=['backend'],
# resource_deps=['postgrest', 'postgresql-primary'],
# )
k8s_resource(
workload='capture-worker',
labels=['backend'],

View File

@ -2,21 +2,21 @@
---
apiVersion: v1
kind: Service
metadata:
name: capture-api
namespace: futureporn
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: capture
ports:
- name: http
port: {{ .Values.capture.api.port }}
targetPort: http
protocol: TCP
# ---
# apiVersion: v1
# kind: Service
# metadata:
# name: capture-api
# namespace: futureporn
# spec:
# type: ClusterIP
# selector:
# app.kubernetes.io/name: capture
# ports:
# - name: http
# port: {{ .Values.capture.api.port }}
# targetPort: http
# protocol: TCP
---
apiVersion: apps/v1
@ -86,47 +86,47 @@ spec:
restartPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: capture-api
namespace: futureporn
labels:
app.kubernetes.io/name: capture
spec:
replicas: {{ .Values.capture.api.replicas }}
selector:
matchLabels:
app: capture-api
template:
metadata:
labels:
app: capture-api
spec:
containers:
- name: capture
image: "{{ .Values.capture.imageName }}"
ports:
- name: http
containerPort: {{ .Values.capture.api.port }}
env:
- name: FUNCTION
value: api
- name: HTTP_PROXY
valueFrom:
secretKeyRef:
name: capture
key: httpProxy
- name: WORKER_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: capture
key: workerConnectionString
- name: PORT
value: "{{ .Values.capture.api.port }}"
resources:
limits:
cpu: 100m
memory: 256Mi
restartPolicy: Always
# ---
# apiVersion: apps/v1
# kind: Deployment
# metadata:
# name: capture-api
# namespace: futureporn
# labels:
# app.kubernetes.io/name: capture
# spec:
# replicas: {{ .Values.capture.api.replicas }}
# selector:
# matchLabels:
# app: capture-api
# template:
# metadata:
# labels:
# app: capture-api
# spec:
# containers:
# - name: capture
# image: "{{ .Values.capture.imageName }}"
# ports:
# - name: http
# containerPort: {{ .Values.capture.api.port }}
# env:
# - name: FUNCTION
# value: api
# - name: HTTP_PROXY
# valueFrom:
# secretKeyRef:
# name: capture
# key: httpProxy
# - name: WORKER_CONNECTION_STRING
# valueFrom:
# secretKeyRef:
# name: capture
# key: workerConnectionString
# - name: PORT
# value: "{{ .Values.capture.api.port }}"
# resources:
# limits:
# cpu: 100m
# memory: 256Mi
# restartPolicy: Always

View File

@ -37,7 +37,9 @@ spec:
value: "{{ .Values.s3.endpoint }}"
- name: S3_REGION
value: "{{ .Values.s3.region }}"
- name: S3_BUCKET
- name: S3_MAIN_BUCKET
value: "{{ .Values.s3.buckets.main }}"
- name: S3_USC_BUCKET
value: "{{ .Values.s3.buckets.usc }}"
- name: S3_ACCESS_KEY_ID
valueFrom:

View File

@ -19,9 +19,8 @@ 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/storage/pnpm-lock.yaml ./packages/storage/package.json ./packages/storage/
# COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
COPY ./packages/utils/pnpm-lock.yaml ./packages/utils/package.json ./packages/utils/
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 ./services/factory/pnpm-lock.yaml ./services/factory/package.json ./services/factory/
@ -31,9 +30,10 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install -g node-gyp --prefer-offline
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/storage/ ./packages/storage/
# COPY ./packages/utils/ ./packages/utils/
COPY ./packages/utils/ ./packages/utils/
RUN ls -la /app/packages/utils/node_modules/prevvy/
RUn cat ./packages/utils/package.json
COPY ./packages/storage/ ./packages/storage/
COPY ./packages/types/ ./packages/types/
COPY ./services/factory/ ./services/factory/
# we are grabbing the mp4 files from capture so we can run tests with them
@ -42,10 +42,6 @@ COPY ./services/capture/src/fixtures ./services/capture/src/fixtures
FROM install AS build
## Transpile TS into JS
## we have to build @futureporn/image first because other packages depend on it's built js files
## next we build everything else
# RUN pnpm --filter=@futureporn/image build
# RUN pnpm --filter=!@futureporn/image -r build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm -r build
## Copy all production code into one place

View File

@ -26,7 +26,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/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/
@ -37,17 +36,13 @@ 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/storage/ ./packages/storage/
COPY ./packages/types/ ./packages/types/
COPY ./packages/utils/ ./packages/utils/
COPY ./services/mailbox/ ./services/mailbox/
## Transpile TS into JS
## we have to build @futureporn/image first because other packages depend on it's built js files
## next we build everything else
RUN pnpm --filter=@futureporn/mailbox build
# RUN pnpm --filter=!@futureporn/image -r build
# RUN pnpm -r build
## Copy all production code into one place

View File

@ -1,29 +0,0 @@
## meal is an example package showing how to include dependency workspace packages into the docker build
FROM node:20.15 AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@9.6.0 --activate
FROM base AS build
# ENV NODE_ENV=development
# RUN mkdir -p /app/packages/meal && mkdir /app/packages/taco && mkdir -p /prod/meal
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc .
COPY ./packages/meal/pnpm-lock.yaml ./packages/meal/package.json ./packages/meal/
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --offline
COPY ./packages/meal/ ./packages/meal/
RUN echo "@@@@@@@@@@@@@@@@@@@@ show me the meat!"
RUN ls -lash ./
RUN ls -lash ./packages/meal
RUN pnpm -r build
RUN pnpm deploy --filter=meal --prod /prod/meal
FROM base AS meal
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /prod/meal .
RUN ls -la .
ENTRYPOINT ["pnpm", "start"]

View File

@ -25,7 +25,6 @@ RUN pnpm fetch
COPY ./services/next ./services/next
COPY ./packages/types ./packages/types
# COPY ./packages/strapi ./packages/strapi
# COPY ./packages/image ./packages/image
# COPY ./packages/utils ./packages/utils
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline

View File

@ -21,7 +21,6 @@ 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

View File

@ -29,7 +29,6 @@ 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 pnpm@9.6.0 --activate
# COPY ./packages/image/pnpm-lock.yaml ./packages/image/package.json ./packages/image/
# 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/
@ -42,17 +41,12 @@ 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/storage/ ./packages/storage/
# COPY ./packages/types/ ./packages/types/
# COPY ./packages/utils/ ./packages/utils/
COPY ./packages/worker/ ./packages/worker/
## Transpile TS into JS
## we have to build @futureporn/image first because other packages depend on it's built js files
## next we build everything else
# RUN pnpm --filter=@futureporn/image build
# RUN pnpm --filter=!@futureporn/image -r build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm -r build
## Copy all production code into one place

View File

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

View File

@ -1,28 +0,0 @@
{
"name": "@futureporn/image",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"test": "mocha",
"clean": "rm -rf dist",
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
"keywords": [],
"author": "",
"license": "Unlicense",
"dependencies": {
"@futureporn/utils": "workspace:*",
"@types/chai": "^4.3.16",
"@types/mocha": "^10.0.7",
"prevvy": "^7.0.1",
"sharp": "^0.33.4"
},
"devDependencies": {
"chai": "^5.1.1",
"mocha": "^10.6.0",
"tsx": "^4.17.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
import { getProminentColor, rgbToHex, getStoryboard } from './index.js'
import { expect } from 'chai'
import { describe } from 'mocha'
import path from 'node:path'
describe('image', function () {
describe('getProminentColor', function () {
it('should get a {String} hex code color', async function () {
const color = await getProminentColor(path.join(import.meta.dirname, './fixtures/sample.webp'))
expect(color).to.equal('#0878a8')
})
})
describe('rgbToHex', function () {
it('should convert color to hex {String} hexidecimal code', function () {
const mulberry = [255, 87, 51] as const
const screaminGreen = [77, 255, 106] as const
const amaranth = [227, 64, 81] as const
expect(rgbToHex(...mulberry)).to.equal('#ff5733')
expect(rgbToHex(...screaminGreen)).to.equal('#4dff6a')
expect(rgbToHex(...amaranth)).to.equal('#e34051')
})
})
describe('getStoryboard', function () {
this.timeout(1000*60*15)
it('should accept a URL and return a path to image on disk', async function () {
// const url = 'https://futureporn-b2.b-cdn.net/projektmelody-chaturbate-2024-06-25.mp4'
const url = 'https://futureporn-b2.b-cdn.net/projektmelody-chaturbate-2024-08-31.mp4'
const imagePath = await getStoryboard(url)
expect(imagePath).to.match(/\.png/)
})
})
})

View File

@ -1,32 +0,0 @@
{
"compilerOptions": {
"noEmit": true,
"allowImportingTsExtensions": true,
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
// Enable strict type checking so you can catch bugs early
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Transpile our TypeScript code to JavaScript
"module": "NodeNext",
"outDir": "dist",
"lib": [
"es2022"
]
},
// Include the necessary files for your project
"include": [
"src/**/*.ts",
],
"exclude": [
"node_modules",
"src/**/*.spec.ts"
]
}

View File

@ -1,7 +0,0 @@
# @futureporn/meal
A test package to better understand Internal TypeScript Packages [[1](https://github.com/0x80/isolate-package?tab=readme-ov-file#the-internal-packages-strategy)] [[2](https://turbo.build/blog/you-might-not-need-typescript-project-references)] [[3](https://www.typescriptlang.org/docs/handbook/project-references.html)]
The TL;DR: The consuming application of an internal package must transpile and typecheck it.
In this example, @futureporn/meal consumes @futureporn/taco. A build step happens only in @futureporn/meal.

View File

@ -1,28 +0,0 @@
{
"name": "@futureporn/meal",
"type": "module",
"version": "1.0.1",
"description": "",
"main": "index.js",
"private": true,
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0",
"start": "node dist/index.js",
"build": "tsc --build",
"clean": "rm -rf dist",
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist",
"dev": "tsup ./src/index.ts --watch"
},
"dependencies": {
"@futureporn/taco": "workspace:*",
"@futureporn/types": "workspace:*"
},
"keywords": [],
"author": "@CJ_Clippy",
"license": "Unlicense",
"devDependencies": {
"@types/node": "^20.14.9",
"tsup": "^8.1.2",
"typescript": "^5.5.3"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
import { type Taco, taco } from '@futureporn/taco'
function main() {
console.log(`Oh boy, it's time for another delicious ${taco()}!!`)
setTimeout(() => {
return main()
}, 2000)
}
main()

View File

@ -1,31 +0,0 @@
{
"compilerOptions": {
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
// Enable strict type checking so you can catch bugs early
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Transpile our TypeScript code to JavaScript
"module": "NodeNext",
"outDir": "dist",
"lib": [
"es2022",
"dom"
]
},
// Include the necessary files for your project
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"tsub.config.ts"
]
}

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/taco"],
/**
* 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');
},
});

View File

@ -1 +0,0 @@
dist

View File

@ -1 +0,0 @@
dist

View File

@ -1,5 +0,0 @@
# temporal-workflows
based on https://github.com/temporalio/samples-typescript/tree/main/monorepo-folders
Futureporn has a lot of long-running tasks that need to run in the background. The tasks are defined here, and any other package can invoke them via a Temporal worker.

View File

@ -1,33 +0,0 @@
{
"name": "@futureporn/temporal-workflows",
"version": "1.0.0",
"private": true,
"exports": {
"./workflows": "./src/workflows.ts",
"./activities": "./src/activities.ts"
},
"scripts": {
"lint": "eslint .",
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
"dependencies": {
"@types/qs": "^6.9.15",
"date-fns": "^3.6.0",
"qs": "^6.12.3",
"@futureporn/image": "workspace:*",
"@futureporn/scout": "workspace:*",
"@futureporn/storage": "workspace:*",
"@futureporn/types": "workspace:*",
"@futureporn/utils": "workspace:*"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-deprecation": "^1.5.0",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,334 +0,0 @@
import { type NotificationData } from '@futureporn/types'
import { type Helpers } from 'graphile-worker'
import {
type IStreamResponse,
type IStreamsResponse,
type IPlatformNotificationResponse,
type IVtubersResponse,
type IVtuberResponse,
type IVtuber,
} from '@futureporn/types'
import { subMinutes, addMinutes } from 'date-fns'
import qs from 'qs'
import { getProminentColor } from '@futureporn/image'
import { getImage } from '@futureporn/scout/vtuber.js'
import { fpSlugify } from '@futureporn/utils'
import { uploadFile } from '@futureporn/storage/s3.js'
export async function upsertStream({
date,
vtuberId,
platform,
pNotifId
}: {
date: string,
vtuberId: number,
platform: string,
pNotifId: number
}, helpers: Helpers): Promise<number> {
if (!date) throw new Error(`upsertStream requires date in the arg object, but it was undefined`);
if (!vtuberId) throw new Error(`upsertStream requires vtuberId in the arg object, but it was undefined`);
if (!platform) throw new Error(`upsertStream requires platform in the arg object, but it was undefined`);
if (!pNotifId) throw new Error(`upsertStream requires pNotifId in the arg object, but it was undefined`);
let streamId
// # Step 3.
// Finally we find or create the stream record
// The stream may already be in the db (the streamer is multi-platform streaming), so we look for that record.
// This gets a bit tricky. How do we determine one stream from another?
// For now, the rule is 30 minutes of separation.
// Anything <=30m is interpreted as the same stream. Anything >30m is interpreted as a different stream.
// If the stream is not in the db, we create the stream record
const dateSinceRange = subMinutes(new Date(date), 30)
const dateUntilRange = addMinutes(new Date(date), 30)
helpers.logger.info(`Find a stream within + or - 30 mins of the notif date=${new Date(date).toISOString()}. dateSinceRange=${dateSinceRange.toISOString()}, dateUntilRange=${dateUntilRange.toISOString()}`)
const findStreamQueryString = qs.stringify({
populate: 'platform-notifications',
filters: {
date: {
$gte: dateSinceRange,
$lte: dateUntilRange
},
vtuber: {
id: {
'$eq': vtuberId
}
}
}
}, { encode: false })
helpers.logger.info('>> findStream')
const findStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams?${findStreamQueryString}`, {
method: 'GET',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'Content-Type': 'application/json'
}
})
const findStreamData = await findStreamRes.json() as IStreamsResponse
if (findStreamData?.data && findStreamData.data.length > 0) {
helpers.logger.info('>> we found a findStreamData json. (there is an existing stream for this e-mail/notification)')
helpers.logger.info(JSON.stringify(findStreamData, null, 2))
const stream = findStreamData.data[0]
if (!stream) throw new Error('stream was undefined');
streamId = stream.id
// Before we're done here, we need to do something extra. We need to populate isChaturbateStream and/or isFanslyStream.
// We know which of these booleans to set based on the stream's related platformNotifications
// We go through each pNotif and look at it's platform
let isFanslyStream = false
let isChaturbateStream = false
if (stream.attributes.platformNotifications) {
for (const pn of stream.attributes.platformNotifications) {
if (pn.attributes.platform === 'fansly') {
isFanslyStream = true
} else if (pn.attributes.platform === 'chaturbate') {
isChaturbateStream = true
}
}
}
helpers.logger.info(`>>> updating stream ${streamId}. isFanslyStream=${isFanslyStream}, isChaturbateStream=${isChaturbateStream}`)
const updateStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams/${streamId}`, {
method: 'PUT',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'content-type': 'application/json'
},
body: JSON.stringify({
data: {
isFanslyStream: isFanslyStream,
isChaturbateStream: isChaturbateStream,
platformNotifications: [
pNotifId
]
}
})
})
const updateStreamJson = await updateStreamRes.json() as IStreamResponse
if (updateStreamJson?.error) throw new Error(JSON.stringify(updateStreamJson, null, 2));
helpers.logger.info(`>> assuming a successful update to the stream record. response as follows.`)
helpers.logger.info(JSON.stringify(updateStreamJson, null, 2))
}
if (!streamId) {
helpers.logger.info('>> did not find a streamId, so we go ahead and create a stream record in the db.')
const createStreamPayload = {
data: {
isFanslyStream: (platform === 'fansly') ? true : false,
isChaturbateStream: (platform === 'chaturbate') ? true : false,
archiveStatus: 'missing',
date: date,
date2: date,
date_str: date,
vtuber: vtuberId,
platformNotifications: [
pNotifId
]
}
}
helpers.logger.debug('>> createStreamPayload as follows')
helpers.logger.debug(JSON.stringify(createStreamPayload, null, 2))
const createStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify(createStreamPayload)
})
const createStreamJson = await createStreamRes.json() as IStreamResponse
helpers.logger.debug('>> we got the createStreamJson')
helpers.logger.debug(JSON.stringify(createStreamJson, null, 2))
if (createStreamJson.error) {
console.error(JSON.stringify(createStreamJson.error, null, 2))
throw new Error('Failed to create stream in DB due to an error. (see above)')
}
streamId = createStreamJson.data.id
}
if (!streamId) throw new Error('failed to get streamId')
return streamId
}
export async function upsertPlatformNotification({ source, date, platform, vtuberId }: { source: string, date: string, platform: string, vtuberId: number }, helpers: Helpers): Promise<number> {
helpers.logger.info('hello from upsertPlatformNotification', { source, date, platform, vtuberId });
if (!source) throw new Error(`upsertPlatformNotification requires source arg, but it was undefined`);
if (!date) throw new Error(`upsertPlatformNotification requires date arg, but it was undefined`);
if (!platform) throw new Error(`upsertPlatformNotification requires platform arg, but it was undefined`);
if (!vtuberId) throw new Error(`upsertPlatformNotification requires vtuberId arg, but it was undefined`);
let pNotifId
// # Step 2.
// Next we create the platform-notification record.
// This probably doesn't already exist, so we don't check for a pre-existing platform-notification.
const pNotifPayload = {
data: {
source: source,
date: date,
date2: date,
platform: platform,
vtuber: vtuberId,
}
}
helpers.logger.debug('pNotifPayload as follows')
helpers.logger.debug(JSON.stringify(pNotifPayload, null, 2))
const pNotifCreateRes = await fetch(`${process.env.STRAPI_URL}/api/platform-notifications`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(pNotifPayload)
})
const pNotifData = await pNotifCreateRes.json() as IPlatformNotificationResponse
if (pNotifData.error) {
helpers.logger.error('>> we failed to create platform-notification, there was an error in the response')
helpers.logger.error(JSON.stringify(pNotifData.error, null, 2))
throw new Error(pNotifData.error)
}
helpers.logger.debug(`>> pNotifData (json response) is as follows`)
helpers.logger.debug(JSON.stringify(pNotifData, null, 2))
if (!pNotifData.data?.id) throw new Error('failed to created pNotifData! The response was missing an id');
pNotifId = pNotifData.data.id
if (!pNotifId) throw new Error('failed to get Platform Notification ID');
return pNotifId
}
export async function upsertVtuber({ platform, userId, url, channel }: { platform: string, userId: string | null, url: string, channel: string }, helpers: Helpers): Promise<number> {
let vtuberId
helpers.logger.debug('>> # Step 1, upsertVtuber')
// # Step 1.
// First we find or create the vtuber
// The vtuber may already be in the db, so we look for that record. All we need is the Vtuber ID.
// If the vtuber is not in the db, we create the vtuber record.
// GET /api/:pluralApiId?filters[field][operator]=value
const findVtubersFilters = (() => {
if (platform === 'chaturbate') {
return { chaturbate: { $eq: url } }
} else if (platform === 'fansly') {
if (!userId) throw new Error('Fansly userId was undefined, but it is required.')
return { fanslyId: { $eq: userId } }
}
})()
helpers.logger.debug('>>>>> the following is findVtubersFilters.')
helpers.logger.debug(JSON.stringify(findVtubersFilters, null, 2))
const findVtubersQueryString = qs.stringify({
filters: findVtubersFilters
}, { encode: false })
helpers.logger.debug(`>>>>> platform=${platform}, url=${url}, userId=${userId}`)
helpers.logger.debug('>> findVtuber')
const findVtuberRes = await fetch(`${process.env.STRAPI_URL}/api/vtubers?${findVtubersQueryString}`, {
method: 'GET',
headers: {
'content-type': 'application/json'
}
})
const findVtuberJson = await findVtuberRes.json() as IVtubersResponse
helpers.logger.debug('>> here is the vtuber json')
helpers.logger.debug(JSON.stringify(findVtuberJson, null, 2))
if (findVtuberJson?.data && findVtuberJson.data.length > 0) {
helpers.logger.debug('>> a vtuber was FOUND')
if (findVtuberJson.data.length > 1) throw new Error('There were more than one vtuber matches in the response. There must only be one.');
const vtuber = findVtuberJson.data[0]
if (!vtuber) throw new Error('vtuber did not have an id. vtuber must have an id.')
helpers.logger.debug('here is the findVtuberJson (as follows)')
helpers.logger.debug(JSON.stringify(findVtuberJson, null, 2))
helpers.logger.debug(`the matching vtuber has ID=${vtuber.id} (${vtuber.attributes.displayName})`)
}
if (!vtuberId) {
helpers.logger.info('>> vtuberId was not found so we create')
/**
* We are creating a vtuber record.
* We need a few things.
* * image URL
* * themeColor
*
* To get an image, we have to do a few things.
* * [x] download image from platform
* * [x] get themeColor from image
* * [x] upload image to b2
* * [x] get B2 cdn link to image
*
* To get themeColor, we need the image locally where we can then run
*/
// download image from platform
// vtuber.getImage expects a vtuber object, which we don't have yet, so we create a dummy one
const dummyVtuber: IVtuber = {
id: 69,
attributes: {
slug: fpSlugify(channel),
displayName: 'example',
vods: [],
description1: ' ',
image: ' ',
themeColor: ' ',
fanslyId: (platform === 'fansly') ? (userId ? userId : undefined) : undefined
}
}
const imageFile = await getImage(dummyVtuber)
// get themeColor from image
const themeColor = await getProminentColor(imageFile)
// upload image to b2
const b2FileData = await uploadFile(imageFile)
// get b2 cdn link to image
const imageCdnLink = `${process.env.CDN_BUCKET_URL}/${b2FileData.Key}`
helpers.logger.info(`>>> createVtuberRes here we go 3-2-1, POST!`)
const createVtuberRes = await fetch(`${process.env.STRAPI_URL}/api/vtubers`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'content-type': 'application/json'
},
body: JSON.stringify({
data: {
displayName: channel,
fansly: (platform === 'fansly') ? url : null,
fanslyId: (platform === 'fansly') ? userId : null,
chaturbate: (platform === 'chaturbate') ? url : null,
slug: fpSlugify(channel),
description1: ' ',
image: imageCdnLink,
themeColor: themeColor || '#dde1ec'
}
})
})
const createVtuberJson = await createVtuberRes.json() as IVtuberResponse
helpers.logger.info('>> createVtuberJson as follows')
helpers.logger.info(JSON.stringify(createVtuberJson, null, 2))
if (createVtuberJson.data) {
vtuberId = createVtuberJson.data.id
helpers.logger.info(`>>> vtuber created with id=${vtuberId}`)
}
}
if (!vtuberId) throw new Error(`upsertVtuber failed to produce a vtuberId! This should not happen under normal circumstances.`);
return vtuberId
}
export default async function (payload: NotificationData, helpers: Helpers) {
const source = 'email'
const { url, platform, channel, displayName, date, userId, avatar } = payload
helpers.logger.info(`process_notif_email task execution has begun with date=${date}, channel=${channel}, platform=${platform}, url=${url}, displayName=${displayName}, avatar=${avatar}`);
const vtuberId = await upsertVtuber({channel, platform, url, userId }, helpers);
const pNotifId = await upsertPlatformNotification({date, platform, source, vtuberId}, helpers);
const streamId = await upsertStream({date, platform, pNotifId, vtuberId}, helpers);
return `vtuberId: ${vtuberId} | pNotifId: ${pNotifId} | streamId: ${streamId}`;
}

View File

@ -1,6 +0,0 @@
export * from './workflowA/activities/activitiesA.js'
export * from './workflowA/activities/activitiesB.js'
// export * from './workflowB/activities.js'
// export * from './processNotifEmail/activities/upsertPlatformNotification.js'
// export * from './processNotifEmail/activities/upsertStream.js'
// export * from './processNotifEmail/activities/upsertVtuber.js'

View File

@ -1,60 +0,0 @@
import { log } from '@temporalio/activity';
import { type IPlatformNotificationResponse, type IVtuberResponse, type IStreamResponse } from '@futureporn/types'
if (!process.env.SCOUT_STRAPI_API_KEY) throw new Error('SCOUT_STRAPI_API_KEY is missing from env');
if (!process.env.STRAPI_URL) throw new Error('STRAPI_URL is missing from env');
if (!process.env.CDN_BUCKET_URL) throw new Error('CDN_BUCKET_URL is missing from env');
if (!process.env.SCOUT_NITTER_URL) throw new Error('SCOUT_NITTER_URL is missing from env');
if (!process.env.SCOUT_NITTER_ACCESS_KEY) throw new Error('SCOUT_NITTER_ACCESS_KEY is missing from env');
export async function upsertPlatformNotification({ source, date, platform, vtuberId }: { source: string, date: string, platform: string, vtuberId: number }): Promise<number> {
log.info('hello from upsertPlatformNotification', { source, date, platform, vtuberId });
if (!source) throw new Error(`upsertPlatformNotification requires source arg, but it was undefined`);
if (!date) throw new Error(`upsertPlatformNotification requires date arg, but it was undefined`);
if (!platform) throw new Error(`upsertPlatformNotification requires platform arg, but it was undefined`);
if (!vtuberId) throw new Error(`upsertPlatformNotification requires vtuberId arg, but it was undefined`);
let pNotifId
// # Step 2.
// Next we create the platform-notification record.
// This probably doesn't already exist, so we don't check for a pre-existing platform-notification.
const pNotifPayload = {
data: {
source: source,
date: date,
date2: date,
platform: platform,
vtuber: vtuberId,
}
}
console.log('pNotifPayload as follows')
console.log(pNotifPayload)
const pNotifCreateRes = await fetch(`${process.env.STRAPI_URL}/api/platform-notifications`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(pNotifPayload)
})
const pNotifData = await pNotifCreateRes.json() as IPlatformNotificationResponse
if (pNotifData.error) {
console.error('>> we failed to create platform-notification, there was an error in the response')
console.error(JSON.stringify(pNotifData.error, null, 2))
throw new Error(pNotifData.error)
}
console.log(`>> pNotifData (json response) is as follows`)
console.log(pNotifData)
if (!pNotifData.data?.id) throw new Error('failed to created pNotifData! The response was missing an id');
pNotifId = pNotifData.data.id
if (!pNotifId) throw new Error('failed to get Platform Notification ID');
return pNotifId
}

View File

@ -1,141 +0,0 @@
import { type IStreamResponse, type IStreamsResponse } from '@futureporn/types';
import { subMinutes, addMinutes } from 'date-fns';
import qs from 'qs'
export async function upsertStream({
date,
vtuberId,
platform,
pNotifId
}: {
date: string,
vtuberId: number,
platform: string,
pNotifId: number
}): Promise<number> {
if (!date) throw new Error(`upsertStream requires date in the arg object, but it was undefined`);
if (!vtuberId) throw new Error(`upsertStream requires vtuberId in the arg object, but it was undefined`);
if (!platform) throw new Error(`upsertStream requires platform in the arg object, but it was undefined`);
if (!pNotifId) throw new Error(`upsertStream requires pNotifId in the arg object, but it was undefined`);
let streamId
// # Step 3.
// Finally we find or create the stream record
// The stream may already be in the db (the streamer is multi-platform streaming), so we look for that record.
// This gets a bit tricky. How do we determine one stream from another?
// For now, the rule is 30 minutes of separation.
// Anything <=30m is interpreted as the same stream. Anything >30m is interpreted as a different stream.
// If the stream is not in the db, we create the stream record
const dateSinceRange = subMinutes(new Date(date), 30)
const dateUntilRange = addMinutes(new Date(date), 30)
console.log(`Find a stream within + or - 30 mins of the notif date=${new Date(date).toISOString()}. dateSinceRange=${dateSinceRange.toISOString()}, dateUntilRange=${dateUntilRange.toISOString()}`)
const findStreamQueryString = qs.stringify({
populate: 'platform-notifications',
filters: {
date: {
$gte: dateSinceRange,
$lte: dateUntilRange
},
vtuber: {
id: {
'$eq': vtuberId
}
}
}
}, { encode: false })
console.log('>> findStream')
const findStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams?${findStreamQueryString}`, {
method: 'GET',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'Content-Type': 'application/json'
}
})
const findStreamData = await findStreamRes.json() as IStreamsResponse
if (findStreamData?.data && findStreamData.data.length > 0) {
console.log('>> we found a findStreamData json. (there is an existing stream for this e-mail/notification)')
console.log(JSON.stringify(findStreamData, null, 2))
const stream = findStreamData.data[0]
if (!stream) throw new Error('stream was undefined');
streamId = stream.id
// Before we're done here, we need to do something extra. We need to populate isChaturbateStream and/or isFanslyStream.
// We know which of these booleans to set based on the stream's related platformNotifications
// We go through each pNotif and look at it's platform
let isFanslyStream = false
let isChaturbateStream = false
if (stream.attributes.platformNotifications) {
for (const pn of stream.attributes.platformNotifications) {
if (pn.attributes.platform === 'fansly') {
isFanslyStream = true
} else if (pn.attributes.platform === 'chaturbate') {
isChaturbateStream = true
}
}
}
console.log(`>>> updating stream ${streamId}. isFanslyStream=${isFanslyStream}, isChaturbateStream=${isChaturbateStream}`)
const updateStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams/${streamId}`, {
method: 'PUT',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'content-type': 'application/json'
},
body: JSON.stringify({
data: {
isFanslyStream: isFanslyStream,
isChaturbateStream: isChaturbateStream,
platformNotifications: [
pNotifId
]
}
})
})
const updateStreamJson = await updateStreamRes.json() as IStreamResponse
if (updateStreamJson?.error) throw new Error(JSON.stringify(updateStreamJson, null, 2));
console.log(`>> assuming a successful update to the stream record. response as follows.`)
console.log(JSON.stringify(updateStreamJson, null, 2))
}
if (!streamId) {
console.log('>> did not find a streamId, so we go ahead and create a stream record in the db.')
const createStreamPayload = {
data: {
isFanslyStream: (platform === 'fansly') ? true : false,
isChaturbateStream: (platform === 'chaturbate') ? true : false,
archiveStatus: 'missing',
date: date,
date2: date,
date_str: date,
vtuber: vtuberId,
platformNotifications: [
pNotifId
]
}
}
console.log('>> createStreamPayload as follows')
console.log(createStreamPayload)
const createStreamRes = await fetch(`${process.env.STRAPI_URL}/api/streams`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify(createStreamPayload)
})
const createStreamJson = await createStreamRes.json() as IStreamResponse
console.log('>> we got the createStreamJson')
console.log(createStreamJson)
if (createStreamJson.error) {
console.error(JSON.stringify(createStreamJson.error, null, 2))
throw new Error('Failed to create stream in DB due to an error. (see above)')
}
streamId = createStreamJson.data.id
}
if (!streamId) throw new Error('failed to get streamId')
return streamId
}

View File

@ -1,130 +0,0 @@
import { type IVtubersResponse, type IVtuberResponse, type IVtuber } from '@futureporn/types';
import { fpSlugify } from '@futureporn/utils';
import qs from 'qs';
import { getProminentColor } from '@futureporn/image';
import { getImage } from '@futureporn/scout/vtuber.js';
import { uploadFile } from '@futureporn/storage/s3.js';
/**
* find or create vtuber in Strapi
*/
export async function upsertVtuber({ platform, userId, url, channel }: { platform: string, userId: string | null, url: string, channel: string }): Promise<number> {
let vtuberId
console.log('>> # Step 1, upsertVtuber')
// # Step 1.
// First we find or create the vtuber
// The vtuber may already be in the db, so we look for that record. All we need is the Vtuber ID.
// If the vtuber is not in the db, we create the vtuber record.
// GET /api/:pluralApiId?filters[field][operator]=value
const findVtubersFilters = (() => {
if (platform === 'chaturbate') {
return { chaturbate: { $eq: url } }
} else if (platform === 'fansly') {
if (!userId) throw new Error('Fansly userId was undefined, but it is required.')
return { fanslyId: { $eq: userId } }
}
})()
console.log('>>>>> the following is findVtubersFilters.')
console.log(findVtubersFilters)
const findVtubersQueryString = qs.stringify({
filters: findVtubersFilters
}, { encode: false })
console.log(`>>>>> platform=${platform}, url=${url}, userId=${userId}`)
console.log('>> findVtuber')
const findVtuberRes = await fetch(`${process.env.STRAPI_URL}/api/vtubers?${findVtubersQueryString}`, {
method: 'GET',
headers: {
'content-type': 'application/json'
}
})
const findVtuberJson = await findVtuberRes.json() as IVtubersResponse
console.log('>> here is the vtuber json')
console.log(findVtuberJson)
if (findVtuberJson?.data && findVtuberJson.data.length > 0) {
console.log('>> a vtuber was FOUND')
if (findVtuberJson.data.length > 1) throw new Error('There were more than one vtuber matches in the response. There must only be one.');
const vtuber = findVtuberJson.data[0]
if (!vtuber) throw new Error('vtuber did not have an id. vtuber must have an id.')
console.log('here is the findVtuberJson (as follows)')
console.log(findVtuberJson)
console.log(`the matching vtuber has ID=${vtuber.id} (${vtuber.attributes.displayName})`)
}
if (!vtuberId) {
console.log('>> vtuberId was not found so we create')
/**
* We are creating a vtuber record.
* We need a few things.
* * image URL
* * themeColor
*
* To get an image, we have to do a few things.
* * [x] download image from platform
* * [x] get themeColor from image
* * [x] upload image to b2
* * [x] get B2 cdn link to image
*
* To get themeColor, we need the image locally where we can then run
*/
// download image from platform
// vtuber.getImage expects a vtuber object, which we don't have yet, so we create a dummy one
const dummyVtuber: IVtuber = {
id: 69,
attributes: {
slug: fpSlugify(channel),
displayName: 'example',
vods: [],
description1: ' ',
image: ' ',
themeColor: ' ',
fanslyId: (platform === 'fansly') ? (userId ? userId : undefined) : undefined
}
}
const imageFile = await getImage(dummyVtuber)
// get themeColor from image
const themeColor = await getProminentColor(imageFile)
// upload image to b2
const b2FileData = await uploadFile(imageFile)
// get b2 cdn link to image
const imageCdnLink = `${process.env.CDN_BUCKET_URL}/${b2FileData.Key}`
console.log(`>>> createVtuberRes here we go 3-2-1, POST!`)
const createVtuberRes = await fetch(`${process.env.STRAPI_URL}/api/vtubers`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'content-type': 'application/json'
},
body: JSON.stringify({
data: {
displayName: channel,
fansly: (platform === 'fansly') ? url : null,
fanslyId: (platform === 'fansly') ? userId : null,
chaturbate: (platform === 'chaturbate') ? url : null,
slug: fpSlugify(channel),
description1: ' ',
image: imageCdnLink,
themeColor: themeColor || '#dde1ec'
}
})
})
const createVtuberJson = await createVtuberRes.json() as IVtuberResponse
console.log('>> createVtuberJson as follows')
console.log(JSON.stringify(createVtuberJson, null, 2))
if (createVtuberJson.data) {
vtuberId = createVtuberJson.data.id
console.log(`>>> vtuber created with id=${vtuberId}`)
}
}
if (!vtuberId) throw new Error(`upsertVtuber failed to produce a vtuberId! This should not happen under normal circumstances.`);
return vtuberId
}

View File

@ -1,26 +0,0 @@
import { proxyActivities, sleep, log } from '@temporalio/workflow';
import { NotificationData } from '@futureporn/types';
// Only import the activity types
import type * as upsertVtuberType from './activities/upsertVtuber.js';
import type * as upsertStreamType from './activities/upsertStream.js';
import type * as upsertPlatformNotificationType from './activities/upsertPlatformNotification.js';
const { upsertVtuber } = proxyActivities<typeof upsertVtuberType>({
startToCloseTimeout: '1 minute',
});
const { upsertStream } = proxyActivities<typeof upsertStreamType>({
startToCloseTimeout: '1 minute',
});
const { upsertPlatformNotification } = proxyActivities<typeof upsertPlatformNotificationType>({
startToCloseTimeout: '1 minute',
});
export async function processNotifEmail(args: NotificationData): Promise<string> {
return '@todo @todo @todo'
// const { url, platform, channel, displayName, date, userId, avatar } = args
// log.info('Hello from processNotifEmail workflow');
// const vtuberId = await upsertVtuber({ platform, userId, url, channel });
// const pNotifId = await upsertPlatformNotification({ source: 'email', date, platform, vtuberId });
// const streamId = await upsertStream({ date, vtuberId, platform, pNotifId });
// return `vtuberId: ${vtuberId} | pNotifId: ${pNotifId} | streamId: ${streamId}`;
}

View File

@ -1 +0,0 @@
this was copied over from fp/scout once I realized that there was a temporal recommended way to do workflows at the packages level.

View File

@ -1,121 +0,0 @@
import fetch from "node-fetch"
import { NotificationData, processEmail } from "./workflows.js"
import qs from 'qs'
import { IPlatformNotificationResponse, IVtuberResponse, IStreamResponse } from 'next'
import { getImage } from '../vtuber.js'
import { fpSlugify } from '../utils.js'
import { getProminentColor } from '../image.js'
import { uploadFile } from '../s3.js'
import { addMinutes, subMinutes } from 'date-fns'
export type ChargeResult = {
status: string;
errorMessage?: string;
};
if (!process.env.SCOUT_STRAPI_API_KEY) throw new Error('SCOUT_STRAPI_API_KEY is missing from env');
if (!process.env.STRAPI_URL) throw new Error('STRAPI_URL is missing from env');
if (!process.env.CDN_BUCKET_URL) throw new Error('CDN_BUCKET_URL is missing from env');
if (!process.env.SCOUT_NITTER_URL) throw new Error('SCOUT_NITTER_URL is missing from env');
if (!process.env.SCOUT_NITTER_ACCESS_KEY) throw new Error('SCOUT_NITTER_ACCESS_KEY is missing from env');
export async function upsertPlatformNotification({ source, date, platform, vtuberId }: { source: string, date: string, platform: string, vtuberId: number }): Promise<number> {
if (!source) throw new Error(`upsertPlatformNotification requires source arg, but it was undefined`);
if (!date) throw new Error(`upsertPlatformNotification requires date arg, but it was undefined`);
if (!platform) throw new Error(`upsertPlatformNotification requires platform arg, but it was undefined`);
if (!vtuberId) throw new Error(`upsertPlatformNotification requires vtuberId arg, but it was undefined`);
let pNotifId
// # Step 2.
// Next we create the platform-notification record.
// This probably doesn't already exist, so we don't check for a pre-existing platform-notification.
const pNotifPayload = {
data: {
source: source,
date: date,
date2: date,
platform: platform,
vtuber: vtuberId,
}
}
console.log('pNotifPayload as follows')
console.log(pNotifPayload)
const pNotifCreateRes = await fetch(`${process.env.STRAPI_URL}/api/platform-notifications`, {
method: 'POST',
headers: {
'authorization': `Bearer ${process.env.SCOUT_STRAPI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(pNotifPayload)
})
const pNotifData = await pNotifCreateRes.json() as IPlatformNotificationResponse
if (pNotifData.error) {
console.error('>> we failed to create platform-notification, there was an error in the response')
console.error(JSON.stringify(pNotifData.error, null, 2))
throw new Error(pNotifData.error)
}
console.log(`>> pNotifData (json response) is as follows`)
console.log(pNotifData)
if (!pNotifData.data?.id) throw new Error('failed to created pNotifData! The response was missing an id');
pNotifId = pNotifData.data.id
if (!pNotifId) throw new Error('failed to get Platform Notification ID');
return pNotifId
}
export async function chargeUser(
userId: string,
itemId: string,
quantity: number,
): Promise<ChargeResult> {
// TODO send request to the payments service that looks up the user's saved
// payment info and the cost of the item and attempts to charge their payment
// method.
console.log(`Charging user ${userId} for ${quantity} of item ${itemId}`);
try {
const response = await fetch("http://httpbin.org/get?status=success");
const body: any = await response.json();
return { status: body.args.status };
} catch (e: any) {
return { status: "failure", errorMessage: e.message };
}
}
export async function checkAndDecrementInventory(
itemId: string,
quantity: number,
): Promise<boolean> {
// TODO a database request that—in a single operation or transaction—checks
// whether there are `quantity` items remaining, and if so, decreases the
// total. Something like:
// const result = await db.collection('items').updateOne(
// { _id: itemId, numAvailable: { $gte: quantity } },
// { $inc: { numAvailable: -quantity } }
// )
// return result.modifiedCount === 1
console.log(`Reserving ${quantity} of item ${itemId}`);
return true;
}
export async function incrementInventory(
itemId: string,
quantity: number,
): Promise<boolean> {
// TODO increment inventory:
// const result = await db.collection('items').updateOne(
// { _id: itemId },
// { $inc: { numAvailable: quantity } }
// )
// return result.modifiedCount === 1
console.log(`Incrementing ${itemId} inventory by ${quantity}`);
return true;
}

View File

@ -1,60 +0,0 @@
import path from "path"
import { NativeConnection, Worker } from "@temporalio/worker"
import * as activities from "./activities.js"
import pRetry from 'p-retry'
if (!process.env.TEMPORAL_SERVICE_ADDRESS) throw new Error(`TEMPORAL_SERVICE_ADDRESS is missing in env`);
if (!process.env.TEMPORAL_NAMESPACE) throw new Error(`TEMPORAL_NAMESPACE is missing in env`);
if (!process.env.TEMPORAL_TASK_QUEUE) throw new Error(`TEMPORAL_TASK_QUEUE is missing in env`);
console.log(`
process.env.TEMPORAL_SERVICE_ADDRESS=${process.env.TEMPORAL_SERVICE_ADDRESS}
process.env.TEMPORAL_NAMESPACE=${process.env.TEMPORAL_NAMESPACE}
process.env.TEMPORAL_TASK_QUEUE=${process.env.TEMPORAL_TASK_QUEUE}
import.meta.dirname=${import.meta.dirname}
`)
async function run() {
console.log(' scout-worker startup!')
// Step 1: Establish a connection with Temporal server.
//
// Worker code uses `@temporalio/worker.NativeConnection`.
// (But in your application code it's `@temporalio/client.Connection`.)
const connection = await NativeConnection.connect({
address: process.env.TEMPORAL_SERVICE_ADDRESS,
// TLS and gRPC metadata configuration goes here.
});
// Step 2: Register Workflows and Activities with the Worker.
const worker = await Worker.create({
connection,
namespace: process.env.TEMPORAL_NAMESPACE,
taskQueue: ''+process.env.TEMPORAL_TASK_QUEUE,
// Workflows are registered using a path as they run in a separate JS context.
workflowsPath: path.join(import.meta.dirname, './workflows.ts'),
activities,
maxTaskQueueActivitiesPerSecond: 1 // since we are acessing 3rd party API, we use this as a courtesy throttle
});
console.log('scout-worker is running.')
// Step 3: Start accepting tasks on the `hello-world` queue
//
// The worker runs until it encounters an unexpected error or the process receives a shutdown signal registered on
// the SDK Runtime object.
//
// By default, worker logs are written via the Runtime logger to STDERR at INFO level.
//
// See https://typescript.temporal.io/api/classes/worker.Runtime#install to customize these defaults.
await worker.run();
}
await pRetry(run, {
forever: true,
onFailedAttempt: (e) => {
console.error(e);
console.error(`there was an error during scout-worker run(). run() will now restart.`)
console.log(`P.S., check out these booba --> (.)(.)`)
}
})

View File

@ -1,61 +0,0 @@
import { proxyActivities, continueAsNew, log, sleep } from "@temporalio/workflow"
import type * as activities from "./activities.js"
import { FetchMessageObject } from 'imapflow'
// { isMatch, url, platform, channel, displayName, date, userId, avatar }
const {
upsertPlatformNotification,
upsertStream,
upsertVtuber,
} = proxyActivities<typeof activities>({
startToCloseTimeout: "1 minute",
});
export async function processEmail({
url, platform, channel, displayName, date, userId, avatar
}: NotificationData): Promise<{ vtuberId: number, pNotifId: number, streamId: number }> {
console.log(`processEmail begin. platform=${platform}, date=${date}, url=${url}`)
// * In Strapi, we are finding or updating or creating the following content-types.
// * * vtuber
// * * platform-notification
// * * stream
// Step 1
const vtuberId = await upsertVtuber({ url, platform, channel, displayName, date, userId, avatar })
console.log(` 🤠 upsertVtuber has completed, and the vtuberId=${vtuberId}`)
const pNotifId = await upsertPlatformNotification({ vtuberId, source: 'email', date, platform })
const streamId = await upsertStream({ date, vtuberId, platform, pNotifId })
return { vtuberId, pNotifId, streamId }
}
// export async function order(
// userId: string,
// itemId: string,
// quantity: number,
// ): Promise<string> {
// const haveEnoughInventory: boolean = await checkAndDecrementInventory(
// itemId,
// quantity,
// );
// if (haveEnoughInventory) {
// const result: activities.ChargeResult = await chargeUser(
// userId,
// itemId,
// quantity,
// );
// if (result.status === "success") {
// return `Order successful!`;
// } else {
// await incrementInventory(itemId, quantity);
// return `Unable to complete payment. Error: ${result.errorMessage}`;
// }
// } else {
// return `Sorry, we don't have enough items in stock to fulfill your order.`;
// }
// }

View File

@ -1,6 +0,0 @@
import { log } from '@temporalio/activity';
export async function activityA(name: string): Promise<string> {
log.info('hello from activityA', { name });
return `ActivityA result: A-${name}!`;
}

View File

@ -1,6 +0,0 @@
import { log } from '@temporalio/activity';
export async function activityB(name: string): Promise<string> {
log.info('hello from activityB', { name });
return `ActivityB result: B-${name}!`;
}

View File

@ -1,19 +0,0 @@
import { proxyActivities, sleep, log } from '@temporalio/workflow';
// Only import the activity types
import type * as activitiesA from './activities/activitiesA.js';
import type * as activitiesB from './activities/activitiesB.js';
const { activityA } = proxyActivities<typeof activitiesA>({
startToCloseTimeout: '1 minute',
});
const { activityB } = proxyActivities<typeof activitiesB>({
startToCloseTimeout: '1 minute',
});
export async function WorkflowA(name: string): Promise<string> {
log.info('Hello from WorkflowA');
const res1 = await activityA(name);
await sleep(100);
const res2 = await activityB(name);
return `A: ${res1} | B: ${res2}`;
}

View File

@ -1,11 +0,0 @@
import { log } from '@temporalio/activity';
export async function activityC(name: string): Promise<string> {
log.info('hello from activityC', { name });
return `ActivityC result: C-${name}!`;
}
export async function activityD(name: string): Promise<string> {
log.info('hello from activityD', { name });
return `ActivityD result: D-${name}!`;
}

View File

@ -1,15 +0,0 @@
import { proxyActivities, sleep, log } from '@temporalio/workflow';
// Only import the activity types
import type * as activities from './activities.js';
const { activityC, activityD } = proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
});
export async function WorkflowB(name = 'WorkflowB'): Promise<string> {
log.info('Hello from WorkflowB');
const res1 = await activityC(name);
await sleep(100);
const res2 = await activityD(name);
return `${res1} ${res2}`;
}

View File

@ -1,3 +0,0 @@
export * from './workflowA/workflow.js'
// export * from './workflowB/workflow.js'
// export * from './processNotifEmail/workflow.js'

View File

@ -1,33 +0,0 @@
{
"compilerOptions": {
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
// Enable strict type checking so you can catch bugs early
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Transpile our TypeScript code to JavaScript
"module": "NodeNext",
"outDir": "./dist",
"lib": [
"es2022"
],
"rootDir": "src"
},
// Include the necessary files for your project
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

@ -5,7 +5,7 @@
"description": "",
"main": "index.js",
"exports": {
"./*.js": "./src/*.js"
"./*.ts": "./src/*.ts"
},
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0",
@ -16,10 +16,11 @@
"author": "@CJ_Clippy",
"license": "Unlicense",
"dependencies": {
"@aws-sdk/client-s3": "^3.583.0",
"@aws-sdk/lib-storage": "^3.588.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/lib-storage": "^3.637.0",
"@futureporn/types": "workspace:^",
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^20.14.9",
"@types/node": "^22.5.2",
"dotenv": "^16.4.5"
}
}

View File

@ -9,17 +9,20 @@ importers:
.:
dependencies:
'@aws-sdk/client-s3':
specifier: ^3.583.0
specifier: ^3.637.0
version: 3.637.0
'@aws-sdk/lib-storage':
specifier: ^3.588.0
specifier: ^3.637.0
version: 3.637.0(@aws-sdk/client-s3@3.637.0)
'@futureporn/types':
specifier: workspace:^
version: link:../types
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@types/node':
specifier: ^20.14.9
version: 20.16.1
specifier: ^22.5.2
version: 22.5.2
dotenv:
specifier: ^16.4.5
version: 16.4.5
@ -195,9 +198,9 @@ packages:
resolution: {integrity: sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==}
engines: {node: '>=16.0.0'}
'@noble/hashes@1.4.0':
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16}
'@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
@ -405,8 +408,8 @@ packages:
resolution: {integrity: sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==}
engines: {node: '>=16.0.0'}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/node@22.5.2':
resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@ -969,11 +972,11 @@ snapshots:
'@smithy/types': 3.3.0
tslib: 2.7.0
'@noble/hashes@1.4.0': {}
'@noble/hashes@1.5.0': {}
'@paralleldrive/cuid2@2.2.2':
dependencies:
'@noble/hashes': 1.4.0
'@noble/hashes': 1.5.0
'@smithy/abort-controller@3.1.1':
dependencies:
@ -1306,7 +1309,7 @@ snapshots:
'@smithy/types': 3.3.0
tslib: 2.7.0
'@types/node@20.16.1':
'@types/node@22.5.2':
dependencies:
undici-types: 6.19.8

View File

@ -1,33 +1,88 @@
import dotenv from 'dotenv';
dotenv.config({
path: '../../.env',
});
import { S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
import { S3Client, GetObjectCommand, type S3ClientConfig } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import type { S3FileResponse, S3FileRecord, Stream } from '@futureporn/types';
import { createId } from '@paralleldrive/cuid2';
import { basename } from 'node:path';
import fs from 'node:fs';
import fs, { createWriteStream, createReadStream } from 'node:fs';
import { type Readable, pipeline } from "node:stream";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
if (!process.env.S3_BUCKET_NAME) throw new Error('S3_BUCKET_NAME was undefined in env');
if (!process.env.SCOUT_NITTER_URL) throw new Error('SCOUT_NITTER_URL was undefined in env');
if (!process.env.S3_BUCKET_KEY_ID) throw new Error('S3_BUCKET_KEY_ID was undefined in env');
if (!process.env.S3_BUCKET_APPLICATION_KEY) throw new Error('S3_BUCKET_APPLICATION_KEY was undefined in env');
export interface S3FileArgs {
filePath: string;
s3BucketName: string;
s3AccessKeyId: string;
s3SecretAccessKey: string;
s3Region: string;
s3Endpoint: string;
}
export async function uploadFile(filePath: string): Promise<any> {
if (!filePath) throw new Error("first argument, 'filePath' is undefined");
export const s3CdnMap = new Map([
['futureporn', 'https://futureporn-b2.b-cdn.net'],
['fp-usc-dev', 'https://fp-usc-dev.b-cdn.net'],
])
function assertArguments(args: any): asserts args is S3FileArgs {
if (typeof args!== "object" ||!args) throw new Error("invalid S3FileArgs");
if (!args.filePath) throw new Error('filePath was missing from args');
if (typeof args.filePath!== 'string') throw new Error('filePath must be a string');
if (!args.s3BucketName) throw new Error('s3BucketName was missing from args');
if (typeof args.s3BucketName!== 'string') throw new Error('s3BucketName must be a string');
if (!args.s3AccessKeyId) throw new Error('s3AccessKeyId was missing from args');
if (typeof args.s3AccessKeyId!== 'string') throw new Error('s3AccessKeyId must be a string');
if (!args.s3SecretAccessKey) throw new Error('s3SecretAccessKey was missing from args');
if (typeof args.s3SecretAccessKey!== 'string') throw new Error('s3SecretAccessKey must be a string');
if (!args.s3Region) throw new Error('s3Region was missing from args');
if (typeof args.s3Region!== 'string') throw new Error('s3Region must be a string');
if (!args.s3Endpoint) throw new Error('s3Endpoint was missing from args');
if (typeof args.s3Endpoint!== 'string') throw new Error('s3Endpoint must be a string');
}
export function getCdnUrl(bucket: string, key: string): string {
if (!bucket) throw new Error('getCdnUrl() first arg (bucket name) was missing');
if (!bucket) throw new Error('getCdnUrl() second arg (key) was missing');
return `${s3CdnMap.get(bucket)}/${key}`
}
// export async function downloadFile(args: S3FileArgs) {
// assertArguments(args)
// const { filePath, s3AccessKeyId, s3SecretAccessKey, s3BucketName, s3Endpoint, s3Region } = args
// // throw new Error('@todo')
// const download = S3Client
// }
export async function downloadFile (client: S3Client, bucket: string, s3_key: string): Promise<string> {
console.log(`downloadS3File with s3File s3_key=${s3_key}, bucket=${bucket}`)
const getObjectCommand = new GetObjectCommand({ Bucket: bucket, Key: s3_key })
const response = await client.send(getObjectCommand)
if (!response) throw new Error(`failed to receive a response while calling S3 GetObjectCommand (within downloadS3File)`);
if (!response.Body) throw new Error('S3 GetObjectCommand response did not have a Body (within downloadS3File)');
const readStream = response.Body as Readable
const outputFilePath = join(tmpdir(), s3_key)
const writeStream = createWriteStream(outputFilePath)
const pipelinePromise = promisify(pipeline)
await pipelinePromise(readStream, writeStream)
return outputFilePath
}
export async function uploadFile(args: unknown) {
assertArguments(args)
const { filePath, s3AccessKeyId, s3SecretAccessKey, s3BucketName, s3Endpoint, s3Region } = args;
const options: S3ClientConfig = {
endpoint: 'https://s3.us-west-000.backblazeb2.com',
region: 'us-west-000',
endpoint: s3Endpoint,
region: s3Region,
credentials: {
accessKeyId: process.env.S3_BUCKET_KEY_ID!,
secretAccessKey: process.env.S3_BUCKET_APPLICATION_KEY!,
accessKeyId: s3AccessKeyId,
secretAccessKey: s3SecretAccessKey,
},
}
const client = new S3Client();
const client = new S3Client(options);
const key = `${createId()}-${basename(filePath)}`
const target = {
Bucket: process.env.S3_BUCKET_NAME,
Key: `${createId()}-${basename(filePath)}`,
Bucket: s3BucketName,
Key: key,
Body: fs.createReadStream(filePath),
};

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
// Base Options recommended for all projects
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
@ -22,7 +22,6 @@
// Include the necessary files for your project
"include": [
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"

View File

@ -1,8 +0,0 @@
# @futureporn/taco
A test package to better understand Internal TypeScript Packages [[1](https://github.com/0x80/isolate-package?tab=readme-ov-file#the-internal-packages-strategy)] [[2](https://turbo.build/blog/you-might-not-need-typescript-project-references)] [[3](https://www.typescriptlang.org/docs/handbook/project-references.html)]
The TL;DR: The consuming application of an internal package must transpile and typecheck it.
In this example, @futureporn/meal consumes @futureporn/taco. A build step happens only in @futureporn/meal.

View File

@ -1,14 +0,0 @@
{
"name": "@futureporn/taco",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0"
},
"keywords": [],
"author": "@CJ_Clippy",
"license": "Unlicense"
}

View File

@ -1,9 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.: {}

View File

@ -1,19 +0,0 @@
export type Taco = {
meat: Boolean;
cheese: Boolean;
lettuce: Boolean;
salsa: Boolean;
crunchy: Boolean;
}
export function taco(): Taco {
return {
meat: true,
cheese: false,
lettuce: true,
salsa: true,
crunchy: true
}
}

View File

@ -13,6 +13,6 @@
"author": "",
"license": "Unlicense",
"devDependencies": {
"typescript": "^5.5.3"
"typescript": "^5.5.4"
}
}

View File

@ -9,7 +9,7 @@ importers:
.:
devDependencies:
typescript:
specifier: ^5.5.3
specifier: ^5.5.4
version: 5.5.4
packages:

View File

@ -7,7 +7,13 @@ export type ProcessingState = 'processing'
export type WaitingState = 'pending_recording'
export type Status = Partial<WaitingState | ProcessingState | RecordingState>
export interface S3File {
export interface S3FileRecord {
s3_key: string;
s3_id?: string;
bucket: string;
}
export interface S3FileResponse {
s3_key: string;
s3_id?: string;
bucket: string;
@ -104,7 +110,7 @@ export interface VodRecord {
title?: string;
date: string;
mux_asset: MuxAssetRecord;
thumbnail?: S3File;
thumbnail?: S3FileRecord;
vtuber: string;
ipfs_cid?: string;
torrent?: string;
@ -113,6 +119,7 @@ export interface VodRecord {
note?: string;
url: string;
discord_message_id: string;
s3_file: string;
}
export interface TagRecord {
@ -248,9 +255,6 @@ export interface IPlatformNotificationResponse {
error?: any;
}
export interface Vod {
id: string
}
export interface IVodsResponse {
id: string

View File

@ -3,8 +3,9 @@
"type": "module",
"version": "1.0.0",
"description": "",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
"./*.ts": "./src/*.ts"
},
"scripts": {
"test": "mocha",
"clean": "rm -rf dist",
@ -15,17 +16,19 @@
"license": "Unlicense",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^20.14.9",
"@types/slug": "^5.0.8",
"p-retry": "^5.1.2",
"@types/node": "^22.5.2",
"@types/slug": "^5.0.9",
"p-retry": "^6.2.0",
"prevvy": "^8.0.1",
"sharp": "^0.33.5",
"slug": "^9.1.0"
},
"devDependencies": {
"@types/chai": "^4.3.16",
"@types/chai": "^4.3.19",
"@types/mocha": "^10.0.7",
"chai": "^5.1.1",
"mocha": "^10.6.0",
"mocha": "^10.7.3",
"ts-node": "^10.9.2",
"tsx": "^4.16.2"
"tsx": "^4.19.0"
}
}

View File

@ -12,21 +12,27 @@ importers:
specifier: ^2.2.2
version: 2.2.2
'@types/node':
specifier: ^20.14.9
version: 20.16.1
specifier: ^22.5.2
version: 22.5.2
'@types/slug':
specifier: ^5.0.8
specifier: ^5.0.9
version: 5.0.9
p-retry:
specifier: ^5.1.2
version: 5.1.2
specifier: ^6.2.0
version: 6.2.0
prevvy:
specifier: ^8.0.1
version: 8.0.1
sharp:
specifier: ^0.33.5
version: 0.33.5
slug:
specifier: ^9.1.0
version: 9.1.0
devDependencies:
'@types/chai':
specifier: ^4.3.16
version: 4.3.17
specifier: ^4.3.19
version: 4.3.19
'@types/mocha':
specifier: ^10.0.7
version: 10.0.7
@ -34,14 +40,14 @@ importers:
specifier: ^5.1.1
version: 5.1.1
mocha:
specifier: ^10.6.0
specifier: ^10.7.3
version: 10.7.3
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.16.1)(typescript@5.5.4)
version: 10.9.2(@types/node@22.5.2)(typescript@5.5.4)
tsx:
specifier: ^4.16.2
version: 4.17.0
specifier: ^4.19.0
version: 4.19.0
packages:
@ -49,6 +55,9 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@emnapi/runtime@1.2.0':
resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
'@esbuild/aix-ppc64@0.23.1':
resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
engines: {node: '>=18'}
@ -193,6 +202,111 @@ packages:
cpu: [x64]
os: [win32]
'@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.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.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
'@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.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
'@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.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
'@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.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
'@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.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.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.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.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.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.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.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.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.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]
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@ -203,9 +317,9 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@noble/hashes@1.4.0':
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16}
'@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
@ -222,17 +336,32 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/chai@4.3.19':
resolution: {integrity: sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/fluent-ffmpeg@2.1.26':
resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==}
'@types/luxon@3.4.2':
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
'@types/mocha@10.0.7':
resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/retry@0.12.1':
resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==}
'@types/node@20.16.3':
resolution: {integrity: sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==}
'@types/node@22.5.2':
resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==}
'@types/retry@0.12.2':
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
'@types/slug@5.0.9':
resolution: {integrity: sha512-6Yp8BSplP35Esa/wOG1wLNKiqXevpQTEF/RcL/NV6BBQaMmZh4YlDwCgrrFSoUE4xAGvnKd5c+lkQJmPrBAzfQ==}
@ -272,6 +401,9 @@ packages:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
async@0.2.10:
resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -319,9 +451,20 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
debug@4.3.6:
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
engines: {node: '>=6.0'}
@ -339,6 +482,10 @@ packages:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@ -355,14 +502,18 @@ packages:
engines: {node: '>=18'}
hasBin: true
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@ -375,6 +526,10 @@ packages:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
fluent-ffmpeg@2.1.3:
resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
engines: {node: '>=18'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@ -390,8 +545,12 @@ packages:
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
get-tsconfig@4.7.6:
resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==}
get-stream@8.0.1:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
engines: {node: '>=16'}
get-tsconfig@4.8.0:
resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@ -410,6 +569,10 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
human-signals@5.0.0:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@ -417,6 +580,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@ -433,6 +599,10 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-network-error@1.1.0:
resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==}
engines: {node: '>=16'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@ -441,10 +611,17 @@ packages:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
is-stream@3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
@ -460,9 +637,19 @@ packages:
loupe@3.1.1:
resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
luxon@1.28.1:
resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==}
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
@ -482,9 +669,17 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
npm-run-path@5.3.0:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@ -493,14 +688,22 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
p-retry@5.1.2:
resolution: {integrity: sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
p-retry@6.2.0:
resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==}
engines: {node: '>=16.17'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-key@4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
@ -509,6 +712,9 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
prevvy@8.0.1:
resolution: {integrity: sha512-ztl6JrbdlICy9cBOxN/aVcT8bp8G08SXMUPns/MJvHHMB6cc3goJiTQwTGLL0X2PoMUYr31YZgd6X6k87NU4OQ==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -530,9 +736,33 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
hasBin: true
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
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==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
slug@9.1.0:
resolution: {integrity: sha512-ioOsCfzQSu+D6NZ8XMCR8IW9FgvF8W7Xzz56hBkB/ALvNaWeBs2MUvvPugq3GCrxfHPFeK6hAxGkY/WLnfX2Lg==}
hasBin: true
@ -545,6 +775,10 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-final-newline@3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@ -575,8 +809,11 @@ packages:
'@swc/wasm':
optional: true
tsx@4.17.0:
resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tsx@4.19.0:
resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -591,6 +828,15 @@ packages:
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
workerpool@6.5.1:
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
@ -631,6 +877,11 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@emnapi/runtime@1.2.0':
dependencies:
tslib: 2.7.0
optional: true
'@esbuild/aix-ppc64@0.23.1':
optional: true
@ -703,6 +954,81 @@ snapshots:
'@esbuild/win32-x64@0.23.1':
optional: true
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.2.0
optional: true
'@img/sharp-win32-ia32@0.33.5':
optional: true
'@img/sharp-win32-x64@0.33.5':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.0': {}
@ -712,11 +1038,11 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@noble/hashes@1.4.0': {}
'@noble/hashes@1.5.0': {}
'@paralleldrive/cuid2@2.2.2':
dependencies:
'@noble/hashes': 1.4.0
'@noble/hashes': 1.5.0
'@tsconfig/node10@1.0.11': {}
@ -726,15 +1052,31 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
'@types/chai@4.3.17': {}
'@types/chai@4.3.19': {}
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
'@types/fluent-ffmpeg@2.1.26':
dependencies:
'@types/node': 22.5.2
'@types/luxon@3.4.2': {}
'@types/mocha@10.0.7': {}
'@types/node@20.16.1':
'@types/ms@0.7.34': {}
'@types/node@20.16.3':
dependencies:
undici-types: 6.19.8
'@types/retry@0.12.1': {}
'@types/node@22.5.2':
dependencies:
undici-types: 6.19.8
'@types/retry@0.12.2': {}
'@types/slug@5.0.9': {}
@ -763,6 +1105,8 @@ snapshots:
assertion-error@2.0.1: {}
async@0.2.10: {}
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
@ -818,8 +1162,24 @@ snapshots:
color-name@1.1.4: {}
color-string@1.9.1:
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
color@4.2.3:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
create-require@1.1.1: {}
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
debug@4.3.6(supports-color@8.1.1):
dependencies:
ms: 2.1.2
@ -830,6 +1190,8 @@ snapshots:
deep-eql@5.0.2: {}
detect-libc@2.0.3: {}
diff@4.0.2: {}
diff@5.2.0: {}
@ -863,10 +1225,22 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
execa@8.0.1:
dependencies:
cross-spawn: 7.0.3
get-stream: 8.0.1
human-signals: 5.0.0
is-stream: 3.0.0
merge-stream: 2.0.0
npm-run-path: 5.3.0
onetime: 6.0.0
signal-exit: 4.1.0
strip-final-newline: 3.0.0
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@ -878,6 +1252,11 @@ snapshots:
flat@5.0.2: {}
fluent-ffmpeg@2.1.3:
dependencies:
async: 0.2.10
which: 1.3.1
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@ -887,7 +1266,9 @@ snapshots:
get-func-name@2.0.2: {}
get-tsconfig@4.7.6:
get-stream@8.0.1: {}
get-tsconfig@4.8.0:
dependencies:
resolve-pkg-maps: 1.0.0
@ -907,6 +1288,8 @@ snapshots:
he@1.2.0: {}
human-signals@5.0.0: {}
inflight@1.0.6:
dependencies:
once: 1.4.0
@ -914,6 +1297,8 @@ snapshots:
inherits@2.0.4: {}
is-arrayish@0.3.2: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@ -926,12 +1311,18 @@ snapshots:
dependencies:
is-extglob: 2.1.1
is-network-error@1.1.0: {}
is-number@7.0.0: {}
is-plain-obj@2.1.0: {}
is-stream@3.0.0: {}
is-unicode-supported@0.1.0: {}
isexe@2.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@ -949,8 +1340,14 @@ snapshots:
dependencies:
get-func-name: 2.0.2
luxon@1.28.1: {}
make-error@1.3.6: {}
merge-stream@2.0.0: {}
mimic-fn@4.0.0: {}
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
@ -984,10 +1381,18 @@ snapshots:
normalize-path@3.0.0: {}
npm-run-path@5.3.0:
dependencies:
path-key: 4.0.0
once@1.4.0:
dependencies:
wrappy: 1.0.2
onetime@6.0.0:
dependencies:
mimic-fn: 4.0.0
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@ -996,17 +1401,36 @@ snapshots:
dependencies:
p-limit: 3.1.0
p-retry@5.1.2:
p-retry@6.2.0:
dependencies:
'@types/retry': 0.12.1
'@types/retry': 0.12.2
is-network-error: 1.1.0
retry: 0.13.1
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-key@4.0.0: {}
pathval@2.0.0: {}
picomatch@2.3.1: {}
prevvy@8.0.1:
dependencies:
'@types/debug': 4.1.12
'@types/fluent-ffmpeg': 2.1.26
'@types/luxon': 3.4.2
'@types/node': 20.16.3
debug: 4.3.6(supports-color@8.1.1)
execa: 8.0.1
fluent-ffmpeg: 2.1.3
luxon: 1.28.1
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@ -1023,10 +1447,50 @@ snapshots:
safe-buffer@5.2.1: {}
semver@7.6.3: {}
serialize-javascript@6.0.2:
dependencies:
randombytes: 2.1.0
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.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:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
simple-swizzle@0.2.2:
dependencies:
is-arrayish: 0.3.2
slug@9.1.0: {}
string-width@4.2.3:
@ -1039,6 +1503,8 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
strip-final-newline@3.0.0: {}
strip-json-comments@3.1.1: {}
supports-color@7.2.0:
@ -1053,14 +1519,14 @@ snapshots:
dependencies:
is-number: 7.0.0
ts-node@10.9.2(@types/node@20.16.1)(typescript@5.5.4):
ts-node@10.9.2(@types/node@22.5.2)(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.16.1
'@types/node': 22.5.2
acorn: 8.12.1
acorn-walk: 8.3.3
arg: 4.1.3
@ -1071,10 +1537,13 @@ snapshots:
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
tsx@4.17.0:
tslib@2.7.0:
optional: true
tsx@4.19.0:
dependencies:
esbuild: 0.23.1
get-tsconfig: 4.7.6
get-tsconfig: 4.8.0
optionalDependencies:
fsevents: 2.3.3
@ -1084,6 +1553,14 @@ snapshots:
v8-compile-cache-lib@3.0.1: {}
which@1.3.1:
dependencies:
isexe: 2.0.0
which@2.0.2:
dependencies:
isexe: 2.0.0
workerpool@6.5.1: {}
wrap-ansi@7.0.0:
@ -1108,7 +1585,7 @@ snapshots:
yargs@16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3

View File

@ -0,0 +1,34 @@
import { getTmpFile, download, getPackageVersion } from './file.ts'
import { expect } from 'chai'
import { describe } from 'mocha'
import { dirname, basename, join, isAbsolute } from 'node:path';
import { fileURLToPath } from 'url';
export const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(fileURLToPath(import.meta.url));
describe('file', function () {
describe('integration', function () {
describe('download', function () {
it('should get the file', async function () {
const file = await download({ url: 'https://futureporn-b2.b-cdn.net/sample.webp' })
expect(file).to.match(/\/tmp\/.*sample\.webp$/)
})
})
})
describe('unit', function () {
describe('getTmpFile', function () {
it('should give a /tmp/<random>_<basename> path', function () {
expect(getTmpFile('my-cool-image.webp')).to.match(/\/tmp\/.*_my-cool-image\.webp/)
expect(getTmpFile('video.mp4')).to.match(/\/tmp\/.*_video\.mp4/)
})
})
describe('getPackageVersion', function () {
it('should get the version from package.json', function () {
expect(getPackageVersion(join(__dirname, '../package.json'))).to.match(/\d+\.\d+\.\d+/)
})
it('should handle a relative path', function () {
expect(getPackageVersion('../package.json')).to.match(/\d+\.\d+\.\d+/)
})
})
})
})

View File

@ -1,10 +1,8 @@
import slug from 'slug';
import os from 'node:os';
import fs from 'node:fs';
import { createId } from '@paralleldrive/cuid2';
import { Readable } from 'stream';
import { finished } from 'stream/promises';
import pRetry from 'p-retry';
import { dirname, basename, join, isAbsolute } from 'node:path';
import { fileURLToPath } from 'url';
export const __filename = fileURLToPath(import.meta.url);
@ -27,14 +25,6 @@ export function getPackageVersion(packageJsonPath: string): string {
}
}
export function fpSlugify(str: string): string {
return slug(str, {
replacement: '-',
lower: true,
locale: 'en',
trim: true,
});
}
export function getTmpFile(str: string): string {
return join(os.tmpdir(), `${createId()}_${basename(str)}`);

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,39 @@
import { getProminentColor, rgbToHex, getStoryboard } from './image.ts'
import { expect } from 'chai'
import { describe } from 'mocha'
import { dirname, basename, join, isAbsolute } from 'node:path';
import { fileURLToPath } from 'url';
export const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(fileURLToPath(import.meta.url));
describe('image', function () {
describe('unit', function () {
describe('getProminentColor', function () {
it('should get a {String} hex code color', async function () {
const color = await getProminentColor(join(import.meta.dirname, './fixtures/sample.webp'))
expect(color).to.equal('#0878a8')
})
})
describe('rgbToHex', function () {
it('should convert color to hex {String} hexidecimal code', function () {
const mulberry = [255, 87, 51] as const
const screaminGreen = [77, 255, 106] as const
const amaranth = [227, 64, 81] as const
expect(rgbToHex(...mulberry)).to.equal('#ff5733')
expect(rgbToHex(...screaminGreen)).to.equal('#4dff6a')
expect(rgbToHex(...amaranth)).to.equal('#e34051')
})
})
})
describe('integration', function () {
describe('getStoryboard', function () {
this.timeout(1000*60*15)
it('should accept a URL and return a path to image on disk', async function () {
const url = 'https://futureporn-b2.b-cdn.net/projektmelody-chaturbate-2024-08-31.mp4'
const imagePath = await getStoryboard(url)
expect(imagePath).to.match(/\.png/)
})
})
})
})

View File

@ -1,8 +1,7 @@
// import ColorThief from 'colorthief';
import sharp from 'sharp';
import Prevvy from 'prevvy';
import path from 'path';
import { getTmpFile } from '@futureporn/utils';
import { getTmpFile } from './file.ts';
export async function getProminentColor(imageFile: string): Promise<string> {
const { dominant } = await sharp(imageFile).stats();

View File

@ -1,34 +0,0 @@
import { fpSlugify, getTmpFile, download, getPackageVersion, __dirname } from './index.ts'
import { expect } from 'chai'
import { describe } from 'mocha'
import { join } from 'path'
describe('utils', function () {
describe('fpSlugify', function () {
it('should remove all capitalization and uppercase and spaces and special characters', function () {
expect(fpSlugify('ProjektMelody')).to.equal('projektmelody')
expect(fpSlugify('CJ_Clippy')).to.equal('cjclippy')
})
})
describe('getTmpFile', function () {
it('should give a /tmp/<random>_<basename> path', function () {
expect(getTmpFile('my-cool-image.webp')).to.match(/\/tmp\/.*_my-cool-image\.webp/)
expect(getTmpFile('video.mp4')).to.match(/\/tmp\/.*_video\.mp4/)
})
})
// disabled because it's an integration test, slow
xdescribe('download', function () {
it('should get the file', async function () {
const file = await download({ url: 'https://futureporn-b2.b-cdn.net/sample.webp' })
expect(file).to.match(/\/tmp\/.*sample\.webp$/)
})
})
describe('getPackageVersion', function () {
it('should get the version from package.json', function () {
expect(getPackageVersion(join(__dirname, '../package.json'))).to.match(/\d+\.\d+\.\d+/)
})
it('should handle a relative path', function () {
expect(getPackageVersion('../package.json')).to.match(/\d+\.\d+\.\d+/)
})
})
})

View File

@ -0,0 +1,13 @@
import { fpSlugify } from './name.ts'
import { expect } from 'chai'
describe('name', function () {
describe('unit', function () {
describe('fpSlugify', function () {
it('should remove all capitalization and uppercase and spaces and special characters', function () {
expect(fpSlugify('ProjektMelody')).to.equal('projektmelody')
expect(fpSlugify('CJ_Clippy')).to.equal('cjclippy')
})
})
})
})

View File

@ -0,0 +1,10 @@
import slug from 'slug';
export function fpSlugify(str: string): string {
return slug(str, {
replacement: '-',
lower: true,
locale: 'en',
trim: true,
});
}

View File

@ -0,0 +1,8 @@
import { expect } from 'chai'
describe('video', function () {
describe('unit', function () {
xdescribe('remux', function () {
})
})
})

View File

@ -0,0 +1,15 @@
import { promisify } from "util"
import { execFile } from "child_process"
export async function remux(inputVideoPath: string, outputVideoPath: string): Promise<void> {
console.log(`remuxing video ${inputVideoPath} and ouputting to ${outputVideoPath}.`)
const execFileP = promisify(execFile)
const { stdout, stderr } = await execFileP('ffmpeg', [
'-i', inputVideoPath,
'-c', 'copy',
outputVideoPath
])
console.log(`stdout=${stdout}`)
console.log(`stderr=${stderr}`)
}

View File

@ -1,14 +0,0 @@
{
"name": "video",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Warn: no test specified\" && exit 0",
"clean": "rm -rf dist",
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
"keywords": [],
"author": "",
"license": "Unlicense"
}

View File

@ -1,9 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.: {}

View File

@ -18,8 +18,8 @@ importers:
packages:
'@babel/runtime@7.25.4':
resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==}
'@babel/runtime@7.25.6':
resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==}
engines: {node: '>=6.9.0'}
ansi-regex@5.0.1:
@ -57,8 +57,8 @@ packages:
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
get-caller-file@2.0.5:
@ -137,7 +137,7 @@ packages:
snapshots:
'@babel/runtime@7.25.4':
'@babel/runtime@7.25.6':
dependencies:
regenerator-runtime: 0.14.1
@ -178,11 +178,11 @@ snapshots:
date-fns@2.30.0:
dependencies:
'@babel/runtime': 7.25.4
'@babel/runtime': 7.25.6
emoji-regex@8.0.0: {}
escalade@3.1.2: {}
escalade@3.2.0: {}
get-caller-file@2.0.5: {}
@ -241,7 +241,7 @@ snapshots:
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3

View File

@ -23,33 +23,33 @@
"author": "@CJ_Clippy",
"license": "Unlicense",
"dependencies": {
"@discordeno/bot": "19.0.0-next.746f0a9",
"@discordeno/bot": "19.0.0-next.b1bfe94",
"@discordeno/rest": "19.0.0-next.b3a8c86",
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^22.2.0",
"@types/node": "^22.5.2",
"@types/qs": "^6.9.15",
"date-fns": "^3.6.0",
"dd-cache-proxy": "^2.1.1",
"dd-cache-proxy": "^2.1.2",
"discordeno": "19.0.0-next.b3a8c86",
"dotenv": "^16.4.5",
"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",
"p-retry": "^6.2.0",
"pg": "8.12.0",
"pretty-bytes": "^6.1.1",
"qs": "^6.13.0",
"rate-limiter-flexible": "^5.0.3"
},
"devDependencies": {
"@futureporn/types": "workspace:^",
"@types/chai": "^4.3.16",
"@types/chai": "^4.3.19",
"@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",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}

View File

@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@discordeno/bot':
specifier: 19.0.0-next.746f0a9
version: 19.0.0-next.746f0a9
specifier: 19.0.0-next.b1bfe94
version: 19.0.0-next.b1bfe94
'@discordeno/rest':
specifier: 19.0.0-next.b3a8c86
version: 19.0.0-next.b3a8c86
@ -18,8 +18,8 @@ importers:
specifier: ^2.2.2
version: 2.2.2
'@types/node':
specifier: ^22.2.0
version: 22.5.0
specifier: ^22.5.2
version: 22.5.2
'@types/qs':
specifier: ^6.9.15
version: 6.9.15
@ -27,8 +27,8 @@ importers:
specifier: ^3.6.0
version: 3.6.0
dd-cache-proxy:
specifier: ^2.1.1
version: 2.1.2(@discordeno/bot@19.0.0-next.746f0a9)
specifier: ^2.1.2
version: 2.1.2(@discordeno/bot@19.0.0-next.b1bfe94)
discordeno:
specifier: 19.0.0-next.b3a8c86
version: 19.0.0-next.b3a8c86
@ -45,11 +45,11 @@ importers:
specifier: ^3.3.2
version: 3.3.2
p-retry:
specifier: ^5.1.2
version: 5.1.2
specifier: ^6.2.0
version: 6.2.0
pg:
specifier: 8.8.0
version: 8.8.0
specifier: 8.12.0
version: 8.12.0
pretty-bytes:
specifier: ^6.1.1
version: 6.1.1
@ -64,8 +64,8 @@ importers:
specifier: workspace:^
version: link:../../packages/types
'@types/chai':
specifier: ^4.3.16
version: 4.3.17
specifier: ^4.3.19
version: 4.3.19
'@types/mocha':
specifier: ^10.0.7
version: 10.0.7
@ -80,50 +80,14 @@ importers:
version: 3.1.4
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4)
version: 10.9.2(@types/node@22.5.2)(typescript@5.5.4)
tsx:
specifier: ^4.17.0
version: 4.17.0
specifier: ^4.19.0
version: 4.19.0
typescript:
specifier: ^5.5.4
version: 5.5.4
../..: {}
../../packages/image: {}
../../packages/infra: {}
../../packages/meal: {}
../../packages/old: {}
../../packages/storage: {}
../../packages/taco: {}
../../packages/types: {}
../../packages/utils: {}
../../packages/video: {}
../capture: {}
../factory: {}
../mailbox: {}
../migrations: {}
../next: {}
../scout: {}
../strapi: {}
../uppy: {}
packages:
'@babel/code-frame@7.24.7':
@ -142,32 +106,32 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@discordeno/bot@19.0.0-next.746f0a9':
resolution: {integrity: sha512-M0BqdbGcJSHr7Nmxw/okFtkKZ9mMM0yUHBbB0XApxFxBRt68I1JhVbdFMwDkVAutargEr8BVDSt5SqUVpMnbrQ==}
'@discordeno/bot@19.0.0-next.b1bfe94':
resolution: {integrity: sha512-ZaPDaPM6tuxpRZCyuNB2rzqz2WYHH20IQdutu87jffW2dV26D3Nw8SJCE1LNdXdu9akes3VMXolkNsLJ5MW2Hw==}
'@discordeno/bot@19.0.0-next.b3a8c86':
resolution: {integrity: sha512-Jm7sEmxR0MyK3kwe8CVHDQfAXpbkkPLER2nGuS2nn2e/0AXQKCCgJRMNmorJ+ZzcSw+slrw4hHkVZMBv7JML9Q==}
'@discordeno/gateway@19.0.0-next.746f0a9':
resolution: {integrity: sha512-IvXISmDVC8bGUreR/wo4hYoH4p8w5YanDDMpdO+ex6DTlsA2AgvpzzIHeshfOZNAupdr4spp4TDxziXfq1skhQ==}
'@discordeno/gateway@19.0.0-next.b1bfe94':
resolution: {integrity: sha512-rNkcCua4Inx8NkuCjbuRBAAg+AIg1qYb24Fq6/Y2+1s+iO0VSQ/ZK8PRNbI2gSpAtAbfndvxA9kyU1ggwwLjNw==}
'@discordeno/gateway@19.0.0-next.b3a8c86':
resolution: {integrity: sha512-FGQvHcJFCyC4WCdgvRo3vAMoyvDpTkbHN4DZlEDNhN6uBMsWsd1xbjWPsJTdWGVUvHQd4+5HOv44wPhO02jytg==}
'@discordeno/rest@19.0.0-next.746f0a9':
resolution: {integrity: sha512-qM0d/MFhzC2TWDclwiVL4Tt/37C26gjCUgb0x9mwnQsetJvsYmd+nzQI6SCkzKjsn/esWCtjSSHFQ7z6bdURpw==}
'@discordeno/rest@19.0.0-next.b1bfe94':
resolution: {integrity: sha512-lLeP3hmMM0GFsO+VA3f9EKOwfIo1HvG1hudtlC1Er6BFWgArpz0/VizHbOyOr5SzZuzrQyfcBJJfEhC+48TsRg==}
'@discordeno/rest@19.0.0-next.b3a8c86':
resolution: {integrity: sha512-nhhHSzibxOwxFAckgcuU8nGj+AGo3IRb1qiCcTE4gQStjqmV3ZewoVRzaRGgoY4t5ld5oeDODplXY5tloobkiw==}
'@discordeno/types@19.0.0-next.746f0a9':
resolution: {integrity: sha512-v/nG0vIFukJzFqAzABat2eGV3a7jTDQzbPkj1yoWaFfcB6pxlF44XJ4nsLLsvWj7oRH8eR97yMa2BT697Rs5JA==}
'@discordeno/types@19.0.0-next.b1bfe94':
resolution: {integrity: sha512-X1MmdPFMyzjxFEANEPrrBjdJDJBAz4RTa1vpVlMp46C3dPqTwjMwijaPhPgPnFiSwt01lJ8WaFDpMvfqOwpQNg==}
'@discordeno/types@19.0.0-next.b3a8c86':
resolution: {integrity: sha512-1uUOpfBN3a8zDYOyL61qvWhG+NL4KMjcun7XFg7CB6wjGubUv4Uc2QXKG7SkSfNmtYzxetrrx1kyoSxkbEHLOw==}
'@discordeno/utils@19.0.0-next.746f0a9':
resolution: {integrity: sha512-UY5GataakuY0yc4SN5qJLexUbTc5y293G3gNAWSaOjaZivEytcdxD4xgeqjNj9c4eN57B3Lfzus6tFZHXwXNOA==}
'@discordeno/utils@19.0.0-next.b1bfe94':
resolution: {integrity: sha512-Y+j3G83vCVXiNSEQlFR9WWJ6GZhvBmIfFjb03zFcmHa20nHExILJJBEyDpS+ptziEyF7jB+2hNw5I/SKn8Zm2w==}
'@discordeno/utils@19.0.0-next.b3a8c86':
resolution: {integrity: sha512-W4SDymUvevihQZry9hB4lzCUNSz5QwqAzdT+3VKswjARgmQQnOjZdJ1w9rX/xoMB5FgS8Vhk6er4ABULIwPi6g==}
@ -329,9 +293,9 @@ packages:
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@noble/hashes@1.4.0':
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16}
'@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
@ -348,8 +312,8 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/chai@4.3.19':
resolution: {integrity: sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -363,20 +327,20 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/node@20.16.3':
resolution: {integrity: sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==}
'@types/node@22.5.0':
resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==}
'@types/node@22.5.2':
resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==}
'@types/pg@8.11.6':
resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==}
'@types/pg@8.11.8':
resolution: {integrity: sha512-IqpCf8/569txXN/HoP5i1LjXfKZWL76Yr2R77xgeIICUbAYHeoaEZFhYHo2uDftecLWrTJUq63JvQu8q3lnDyA==}
'@types/qs@6.9.15':
resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==}
'@types/retry@0.12.1':
resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==}
'@types/retry@0.12.2':
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
'@types/semver@7.5.8':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@ -440,10 +404,6 @@ packages:
browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
call-bind@1.0.7:
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
engines: {node: '>= 0.4'}
@ -583,8 +543,8 @@ packages:
engines: {node: '>=18'}
hasBin: true
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@1.0.5:
@ -641,8 +601,8 @@ packages:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
get-tsconfig@4.7.6:
resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==}
get-tsconfig@4.8.0:
resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@ -729,6 +689,10 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-network-error@1.1.0:
resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==}
engines: {node: '>=16'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@ -838,12 +802,9 @@ packages:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
p-retry@5.1.2:
resolution: {integrity: sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
p-retry@6.2.0:
resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==}
engines: {node: '>=16.17'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
@ -908,20 +869,11 @@ packages:
pg-native:
optional: true
pg@8.8.0:
resolution: {integrity: sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
@ -1074,8 +1026,8 @@ packages:
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tsx@4.17.0:
resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==}
tsx@4.19.0:
resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -1168,7 +1120,7 @@ snapshots:
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
picocolors: 1.0.1
picocolors: 1.1.0
'@babel/helper-validator-identifier@7.24.7': {}
@ -1177,18 +1129,18 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
js-tokens: 4.0.0
picocolors: 1.0.1
picocolors: 1.1.0
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@discordeno/bot@19.0.0-next.746f0a9':
'@discordeno/bot@19.0.0-next.b1bfe94':
dependencies:
'@discordeno/gateway': 19.0.0-next.746f0a9
'@discordeno/rest': 19.0.0-next.746f0a9
'@discordeno/types': 19.0.0-next.746f0a9
'@discordeno/utils': 19.0.0-next.746f0a9
'@discordeno/gateway': 19.0.0-next.b1bfe94
'@discordeno/rest': 19.0.0-next.b1bfe94
'@discordeno/types': 19.0.0-next.b1bfe94
'@discordeno/utils': 19.0.0-next.b1bfe94
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@ -1203,10 +1155,10 @@ snapshots:
- bufferutil
- utf-8-validate
'@discordeno/gateway@19.0.0-next.746f0a9':
'@discordeno/gateway@19.0.0-next.b1bfe94':
dependencies:
'@discordeno/types': 19.0.0-next.746f0a9
'@discordeno/utils': 19.0.0-next.746f0a9
'@discordeno/types': 19.0.0-next.b1bfe94
'@discordeno/utils': 19.0.0-next.b1bfe94
ws: 8.18.0
transitivePeerDependencies:
- bufferutil
@ -1221,23 +1173,23 @@ snapshots:
- bufferutil
- utf-8-validate
'@discordeno/rest@19.0.0-next.746f0a9':
'@discordeno/rest@19.0.0-next.b1bfe94':
dependencies:
'@discordeno/types': 19.0.0-next.746f0a9
'@discordeno/utils': 19.0.0-next.746f0a9
'@discordeno/types': 19.0.0-next.b1bfe94
'@discordeno/utils': 19.0.0-next.b1bfe94
'@discordeno/rest@19.0.0-next.b3a8c86':
dependencies:
'@discordeno/types': 19.0.0-next.b3a8c86
'@discordeno/utils': 19.0.0-next.b3a8c86
'@discordeno/types@19.0.0-next.746f0a9': {}
'@discordeno/types@19.0.0-next.b1bfe94': {}
'@discordeno/types@19.0.0-next.b3a8c86': {}
'@discordeno/utils@19.0.0-next.746f0a9':
'@discordeno/utils@19.0.0-next.b1bfe94':
dependencies:
'@discordeno/types': 19.0.0-next.746f0a9
'@discordeno/types': 19.0.0-next.b1bfe94
'@discordeno/utils@19.0.0-next.b3a8c86':
dependencies:
@ -1326,11 +1278,11 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@noble/hashes@1.4.0': {}
'@noble/hashes@1.5.0': {}
'@paralleldrive/cuid2@2.2.2':
dependencies:
'@noble/hashes': 1.4.0
'@noble/hashes': 1.5.0
'@tsconfig/node10@1.0.11': {}
@ -1340,7 +1292,7 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
'@types/chai@4.3.17': {}
'@types/chai@4.3.19': {}
'@types/debug@4.1.12':
dependencies:
@ -1348,29 +1300,29 @@ snapshots:
'@types/interpret@1.1.3':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.2
'@types/mocha@10.0.7': {}
'@types/ms@0.7.34': {}
'@types/node@20.16.1':
'@types/node@20.16.3':
dependencies:
undici-types: 6.19.8
'@types/node@22.5.0':
'@types/node@22.5.2':
dependencies:
undici-types: 6.19.8
'@types/pg@8.11.6':
'@types/pg@8.11.8':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.2
pg-protocol: 1.6.1
pg-types: 4.0.2
'@types/qs@6.9.15': {}
'@types/retry@0.12.1': {}
'@types/retry@0.12.2': {}
'@types/semver@7.5.8': {}
@ -1422,8 +1374,6 @@ snapshots:
browser-stdout@1.3.1: {}
buffer-writer@2.0.0: {}
call-bind@1.0.7:
dependencies:
es-define-property: 1.0.0
@ -1512,9 +1462,9 @@ snapshots:
date-fns@3.6.0: {}
dd-cache-proxy@2.1.2(@discordeno/bot@19.0.0-next.746f0a9):
dd-cache-proxy@2.1.2(@discordeno/bot@19.0.0-next.b1bfe94):
dependencies:
'@discordeno/bot': 19.0.0-next.746f0a9
'@discordeno/bot': 19.0.0-next.b1bfe94
debug@4.3.6:
dependencies:
@ -1601,7 +1551,7 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
escalade@3.2.0: {}
escape-string-regexp@1.0.5: {}
@ -1652,7 +1602,7 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.2
get-tsconfig@4.7.6:
get-tsconfig@4.8.0:
dependencies:
resolve-pkg-maps: 1.0.0
@ -1675,7 +1625,7 @@ snapshots:
graphile-config@0.0.1-beta.9:
dependencies:
'@types/interpret': 1.1.3
'@types/node': 20.16.1
'@types/node': 20.16.3
'@types/semver': 7.5.8
chalk: 4.1.2
debug: 4.3.6
@ -1690,7 +1640,7 @@ snapshots:
dependencies:
'@graphile/logger': 0.2.0
'@types/debug': 4.1.12
'@types/pg': 8.11.6
'@types/pg': 8.11.8
cosmiconfig: 8.3.6(typescript@5.5.4)
graphile-config: 0.0.1-beta.9
json5: 2.2.3
@ -1750,6 +1700,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
is-network-error@1.1.0: {}
is-number@7.0.0: {}
is-plain-obj@2.1.0: {}
@ -1869,13 +1821,12 @@ snapshots:
dependencies:
p-limit: 4.0.0
p-retry@5.1.2:
p-retry@6.2.0:
dependencies:
'@types/retry': 0.12.1
'@types/retry': 0.12.2
is-network-error: 1.1.0
retry: 0.13.1
packet-reader@1.0.0: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@ -1908,10 +1859,6 @@ snapshots:
dependencies:
pg: 8.12.0
pg-pool@3.6.2(pg@8.8.0):
dependencies:
pg: 8.8.0
pg-protocol@1.6.1: {}
pg-types@2.2.0:
@ -1942,21 +1889,11 @@ snapshots:
optionalDependencies:
pg-cloudflare: 1.1.1
pg@8.8.0:
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.6.4
pg-pool: 3.6.2(pg@8.8.0)
pg-protocol: 1.6.1
pg-types: 2.2.0
pgpass: 1.0.5
pgpass@1.0.5:
dependencies:
split2: 4.2.0
picocolors@1.0.1: {}
picocolors@1.1.0: {}
picomatch@2.3.1: {}
@ -2068,14 +2005,14 @@ snapshots:
touch@3.1.1: {}
ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4):
ts-node@10.9.2(@types/node@22.5.2)(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.5.0
'@types/node': 22.5.2
acorn: 8.12.1
acorn-walk: 8.3.3
arg: 4.1.3
@ -2088,10 +2025,10 @@ snapshots:
tslib@2.7.0: {}
tsx@4.17.0:
tsx@4.19.0:
dependencies:
esbuild: 0.23.1
get-tsconfig: 4.7.6
get-tsconfig: 4.8.0
optionalDependencies:
fsevents: 2.3.3
@ -2137,7 +2074,7 @@ snapshots:
yargs@16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
@ -2147,7 +2084,7 @@ snapshots:
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3

View File

@ -4,6 +4,7 @@ import type { Status } from '@futureporn/types'
import { createCommand } from '../commands.ts'
import { bot } from '../bot.ts'
import { configs } from '../config.ts'
import { rbacAllow } from '../middlewares/rbac.ts'
createCommand({
name: 'cancel',
@ -32,6 +33,7 @@ createCommand({
let vodId: string;
try {
await rbacAllow(['admin', 'moderator', 'testAdmin'], interaction)
const response = await fetch(url, options);
bot.logger.info(`response.ok=${response.ok}`)

View File

@ -2,7 +2,7 @@
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 patchVod from '../fetchers/patchVod.ts'
import { quickAddJob, type WorkerUtilsOptions } from 'graphile-worker'
import { configs } from '../config.ts'
import findVod from '../fetchers/findVod.ts'
@ -25,7 +25,7 @@ createCommand({
const options: WorkerUtilsOptions = { connectionString: configs.connectionString }
logger.info(`now we will quickAddJob process_video`)
await quickAddJob(options, 'process_video', { vod_id: vod.id })
logger.info(`now we will patchStreamInDatabase`)
await patchStreamInDatabase(vod.id, { status: 'processing' })
logger.info(`now we will patchVodInDatabase`)
await patchVod(vod.id, { status: 'processing' })
},
})

View File

@ -2,8 +2,8 @@ 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}`
export default async function patchVod(stream_id: string, payload: Partial<Stream>): Promise<void> {
const url = `${configs.postgrestUrl}/vods?id=eq.${stream_id}`
const fetchOptions = {
method: 'PATCH',
headers: {
@ -18,7 +18,7 @@ export default async function patchStreamInDatabase(stream_id: string, payload:
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}`)
throw new Error(`Problem during patchVod. res.status=${res.status}, res.statusText=${res.statusText}`)
}
return
} catch (e) {

View File

@ -128,7 +128,7 @@ function getEmbeds(vod: VodResponse, helpers: Helpers) {
if (segments) {
const getDuration = (s: SegmentResponse) => formatDuration(intervalToDuration({ start: s.created_at, end: s.updated_at }))
embeds.newEmbed()
.setTitle(`Recording Segments`)
.setTitle(`Segments`)
.setFields(segments.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()).map((s, i) => (
{
name: `Segment ${i+1}`,

View File

@ -18,22 +18,22 @@
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.617.0",
"@aws-sdk/lib-storage": "^3.588.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/lib-storage": "^3.637.0",
"@aws-sdk/types": "^3.609.0",
"@futureporn/types": "workspace:^",
"@futureporn/utils": "workspace:^",
"@paralleldrive/cuid2": "^2.2.2",
"@types/chai": "^4.3.16",
"@types/chai-as-promised": "^7.1.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/chai": "^4.3.19",
"@types/chai-as-promised": "^8.0.0",
"@types/fluent-ffmpeg": "^2.1.26",
"@types/mocha": "^10.0.7",
"@types/qs": "^6.9.15",
"date-fns": "^3.6.0",
"discord.js": "^14.15.3",
"discord.js": "^14.16.1",
"diskusage": "^1.2.0",
"dotenv": "^16.4.5",
"execa": "^6.1.0",
"execa": "^9.3.1",
"exponential-backoff": "^3.1.1",
"fastify": "^4.28.1",
"fastify-plugin": "^4.5.1",
@ -45,41 +45,41 @@
"graphile-worker": "^0.16.6",
"https": "^1.0.0",
"ioredis": "^5.4.1",
"minimatch": "^5.1.6",
"p-retry": "^5.1.2",
"pg-boss": "^9.0.3",
"pino-pretty": "^11.2.1",
"minimatch": "^10.0.1",
"p-retry": "^6.2.0",
"pg-boss": "^10.1.1",
"pino-pretty": "^11.2.2",
"postgres": "^3.4.4",
"qs": "^6.13.0",
"rxjs": "^7.8.1",
"sql": "^0.78.0",
"winston": "^3.13.1",
"winston": "^3.14.2",
"youtube-dl-wrap": "github:insanity54/youtube-dl-wrap"
},
"devDependencies": {
"@smithy/util-stream": "^3.1.2",
"@types/node": "^20.14.13",
"@smithy/util-stream": "^3.1.3",
"@types/node": "^22.5.2",
"@types/sinon": "^17.0.3",
"@types/sinon-chai": "^3.2.12",
"aws-sdk": "^2.1663.0",
"aws-sdk": "^2.1687.0",
"aws-sdk-client-mock": "^4.0.1",
"aws-sdk-mock": "^6.0.4",
"chai": "^4.4.1",
"aws-sdk-mock": "^6.1.1",
"chai": "^5.1.1",
"chai-as-promised": "^8.0.0",
"cheerio": "1.0.0-rc.12",
"mocha": "^10.7.0",
"multiformats": "^11.0.2",
"cheerio": "1.0.0",
"mocha": "^10.7.3",
"multiformats": "^13.2.2",
"node-abort-controller": "^3.1.1",
"node-fetch": "^3.3.2",
"nodemon": "^2.0.22",
"nodemon": "^3.1.4",
"pretty-bytes": "^6.1.1",
"s3": "link:aws-sdk/clients/s3",
"sinon": "^15.2.0",
"sinon-chai": "^3.7.0",
"sinon": "^18.0.0",
"sinon-chai": "^4.0.0",
"sinon-test": "^3.1.6",
"ts-node": "^10.9.2",
"tsup": "^8.1.2",
"tsx": "^4.16.2",
"typescript": "^5.5.3"
"tsup": "^8.2.4",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
'use strict'
import fastify, { type FastifyRequest } from 'fastify'
import { getPackageVersion } from '@futureporn/utils'
import { getPackageVersion } from '@futureporn/utils/file.ts'
import fastifyGraphileWorkerPlugin, { type ExtendedFastifyInstance } from './fastify-graphile-worker-plugin.ts'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

View File

@ -7,7 +7,7 @@ import 'dotenv/config'
import { makeWorkerUtils, type WorkerUtils, Runner, RunnerOptions, run as graphileRun } from 'graphile-worker'
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'url';
import { getPackageVersion } from '@futureporn/utils';
import { getPackageVersion } from '@futureporn/utils/file.ts';
import type { GraphileConfig } from "graphile-config";
import type {} from "graphile-worker";

View File

@ -20,10 +20,12 @@
"author": "@cj_clippy",
"license": "Unlicense",
"dependencies": {
"@aws-sdk/client-s3": "^3.627.0",
"@aws-sdk/lib-storage": "^3.588.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/lib-storage": "^3.637.0",
"@futureporn/storage": "workspace:^",
"@futureporn/utils": "workspace:^",
"@paralleldrive/cuid2": "^2.2.2",
"@types/node": "^22.1.0",
"@types/node": "^22.5.2",
"dotenv": "^16.4.5",
"fluture": "^14.0.0",
"graphile-worker": "^0.16.6",
@ -31,9 +33,9 @@
},
"devDependencies": {
"@futureporn/types": "workspace:^",
"@types/ramda": "^0.30.1",
"@types/ramda": "^0.30.2",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"tsx": "^4.17.0"
"tsx": "^4.19.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,8 @@ if (!process.env.S3_ACCESS_KEY_ID) throw new Error('Missing S3_ACCESS_KEY_ID env
if (!process.env.S3_SECRET_ACCESS_KEY) throw new Error('Missing S3_BUCKET_APPLICATION_KEY env var');
if (!process.env.S3_REGION) throw new Error('Missing S3_REGION env var');
if (!process.env.S3_ENDPOINT) throw new Error('Missing S3_REGION env var');
if (!process.env.S3_BUCKET) throw new Error('Missing S3_BUCKET env var');
if (!process.env.S3_MAIN_BUCKET) throw new Error('Missing S3_BUCKET env var');
if (!process.env.S3_USC_BUCKET) throw new Error('Missing S3_USC_BUCKET env var');
const postgrestUrl = process.env.POSTGREST_URL!
const automationUserJwt = process.env.AUTOMATION_USER_JWT!
const connectionString = process.env.WORKER_CONNECTION_STRING!
@ -20,7 +21,8 @@ const s3AccessKeyId = process.env.S3_ACCESS_KEY_ID!
const s3Region = process.env.S3_REGION!
const s3Endpoint = process.env.S3_ENDPOINT!
const s3SecretAccessKey = process.env.S3_SECRET_ACCESS_KEY!
const s3Bucket = process.env.S3_BUCKET!
const s3MainBucket = process.env.S3_MAIN_BUCKET!
const s3UscBucket = process.env.S3_USC_BUCKET!
export interface Config {
postgrestUrl: string;
@ -30,7 +32,8 @@ export interface Config {
s3SecretAccessKey: string;
s3Region: string;
s3Endpoint: string;
s3Bucket: string;
s3UscBucket: string;
s3MainBucket: string;
}
@ -42,5 +45,6 @@ export const configs: Config = {
s3SecretAccessKey,
s3Endpoint,
s3Region,
s3Bucket,
s3MainBucket,
s3UscBucket,
}

View File

@ -0,0 +1,31 @@
import { configs } from "../config"
import type { S3FileRecord, S3FileResponse } from '@futureporn/types'
export default async function createS3File(s3File?: S3FileRecord): Promise<S3FileResponse|null> {
if (!s3File) throw new Error(`first argument passed to createS3File must be a {S3Record}`);
console.log(s3File)
const url = `${configs.postgrestUrl}/s3_files`
const payload: any = {
s3_id: s3File.s3_id,
s3_key: s3File.s3_key,
}
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 createS3File. res.status=${res.status}, res.statusText=${res.statusText}, body=${JSON.stringify(body)}`)
}
const json = await res.json() as S3FileResponse[]
const s3FileRes = json[0]
if (!s3FileRes) return null
else return s3FileRes
}

View File

@ -1,19 +1,20 @@
import { configs } from "../config"
import { Helpers } from "graphile-worker"
import { Stream } from "@futureporn/types"
import type { VodResponse } from "@futureporn/types"
export default async function getVod(vodId: string, helpers: Helpers) {
const url = `${configs.postgrestUrl}/streams?select=*,segments(*)&id=eq.${vodId}`
const url = `${configs.postgrestUrl}/vods?select=*,segments(*)&id=eq.${vodId}`
try {
const res = await fetch(url)
if (!res.ok) {
throw new Error(`failed fetching stream ${vodId}. status=${res.status}, statusText=${res.statusText}`)
throw new Error(`failed fetching getVod ${vodId}. status=${res.status}, statusText=${res.statusText}`)
}
const body = await res.json() as Stream[]
if (!body[0]) throw new Error('body[0] was expected to be Stream data, but it was either null or undefined.');
const body = await res.json() as VodResponse[]
if (!body[0]) throw new Error('body[0] was expected to be Vod data, but it was either null or undefined.');
return body[0];
} catch (e) {
helpers.logger.error(`encountered an error during getStreamFromDatabase()`)
helpers.logger.error(`encountered an error during getVod()`)
if (e instanceof Error) {
helpers.logger.error(e.message)
} else {

View File

@ -1,8 +1,8 @@
import type { Vod } from "@futureporn/types";
import type { VodRecord } from "@futureporn/types";
import { configs } from "../config.ts";
export default async function patchVodInDatabase(vod_id: string, payload: Partial<Vod>): Promise<void> {
export default async function patchVodInDatabase(vod_id: string, payload: Partial<VodRecord>): Promise<void> {
const url = `${configs.postgrestUrl}/vods?id=eq.${vod_id}`
const fetchOptions = {
method: 'PATCH',

View File

@ -18,8 +18,8 @@ async function setupGraphileWorker() {
taskDirectory: join(__dirname, 'tasks')
},
};
console.log('worker preset as follows')
console.log(preset)
// console.log('worker preset as follows')
// console.log(preset)
const runnerOptions: RunnerOptions = {
preset
}

View File

@ -1,7 +1,7 @@
import { Helpers, type Task } from 'graphile-worker'
import { basename, join } from 'node:path';
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
import { S3Client } from "@aws-sdk/client-s3"
import { Upload } from "@aws-sdk/lib-storage"
import { createId } from '@paralleldrive/cuid2';
// import { downloadS3File, uploadToS3, createStrapiB2File, createStrapiVod, createStrapiStream } from '@futureporn/storage'
@ -14,14 +14,16 @@ 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';
import { downloadFile } from '@futureporn/storage/s3.ts';
import { S3FileRecord } from '@futureporn/types';
interface s3File {
interface s3ManifestEntry {
key: string;
bytes: number;
}
interface Payload {
s3_manifest: s3File[];
s3_manifest: s3ManifestEntry[];
vod_id?: string;
}
@ -46,21 +48,6 @@ function assertPayload(payload: any): asserts payload is Payload {
}
const downloadS3File = async function (client: S3Client, s3File: s3File): Promise<string> {
const bucket = configs.s3Bucket;
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)
if (!response) throw new Error(`failed to receive a response while calling S3 GetObjectCommand (within downloadS3File)`);
if (!response.Body) throw new Error('S3 GetObjectCommand response did not have a Body (within downloadS3File)');
const readStream = response.Body as Readable
const outputFilePath = join(tmpdir(), key)
const writeStream = createWriteStream(outputFilePath)
const pipelinePromise = promisify(pipeline)
await pipelinePromise(readStream, writeStream)
return outputFilePath
}
/**
*
@ -177,7 +164,7 @@ export const combine_video_segments: Task = async function (payload: unknown, he
// helpers.logger.info(JSON.stringify(payload?.s3_manifest))
assertPayload(payload)
const { s3_manifest, vod_id } = payload
if (!vod_id) helpers.logger.warn(`combine_video_segments was called without a vod_id. This is not recommended.`);
if (!vod_id) throw new Error('combine_video_segments was called without a vod_id.');
helpers.logger.info(`combine_video_segments started with s3_manifest=${JSON.stringify(s3_manifest)}, vod_id=${vod_id}`)
/**
@ -205,7 +192,7 @@ export const combine_video_segments: Task = async function (payload: unknown, he
}
});
const s3Manifest = s3_manifest
const inputVideoFilePaths = await Promise.all(s3Manifest.map((m) => downloadS3File(client, m)))
const inputVideoFilePaths = await Promise.all(s3Manifest.filter((m) => (m.bytes !== 0)).map((m) => downloadFile(client, configs.s3UscBucket, m.key)))
const concatenatedVideoFile = await concatVideos(inputVideoFilePaths)
const s3KeyName = basename(concatenatedVideoFile)
const inputStream = createReadStream(concatenatedVideoFile)
@ -214,19 +201,18 @@ export const combine_video_segments: Task = async function (payload: unknown, he
setupUploadPipeline({ inputStream, uploadStream })
await upload.done()
if (vod_id) {
if (!vod_id) throw new Error('vod_id was missing from payload');
// update the vod with the s3_file of the combined video
const s3File: S3File = {
const s3File: S3FileRecord = {
s3_key: s3KeyName,
bucket: configs.s3Bucket,
bucket: configs.s3UscBucket,
}
const payload = {
s3_file: s3File,
stream_id
vod_id
}
await patchVodInDatabase(vod_id, payload)
}
} catch (e: any) {
helpers.logger.error('combined_video_segments failed')
@ -235,6 +221,7 @@ export const combine_video_segments: Task = async function (payload: unknown, he
} else {
helpers.logger.error(e)
}
throw e
}

View File

@ -0,0 +1,56 @@
import type { Task, Helpers } from "graphile-worker";
import getVod from "../fetchers/getVod";
import { getStoryboard } from '@futureporn/utils/image.ts'
import { getCdnUrl, uploadFile, s3CdnMap, type S3FileArgs } from '@futureporn/storage/s3.ts'
import patchVodInDatabase from "../fetchers/patchVodInDatabase";
import { configs } from "../config";
import { basename } from "path";
interface Payload {
vod_id: string;
}
function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid payload (it must be an object)");
if (typeof payload.vod_id !== "string") throw new Error("payload.vod_id was not a string");
}
export const generate_thumbnail: Task = async function (payload: unknown, helpers: Helpers) {
assertPayload(payload)
const { vod_id } = payload
if (!vod_id) throw new Error('generate_thumbnail Task was started without a vod_id. This is unsupported.');
helpers.logger.info(`generate_thumbnail started with vod_id=${vod_id}`);
const vod = await getVod(vod_id, helpers)
const s3_file = vod?.s3_file
if (!s3_file) throw new Error(`vod ${vod_id} was missing a s3_file.`);
// we need to get a CDN url to the vod so we can download chunks of the file in order for Prevvy to create the storyboard image.
const cdnUrl = getCdnUrl(configs.s3Bucket, s3_file.s3_key)
const tmpImagePath = await getStoryboard(cdnUrl)
// we need to upload the image to S3
const uploadArgs: S3FileArgs = {
filePath: tmpImagePath,
s3AccessKeyId: configs.s3AccessKeyId,
s3SecretAccessKey: configs.s3SecretAccessKey,
s3BucketName: configs.s3Bucket,
s3Endpoint: configs.s3Region,
s3Region: configs.s3Region
}
const upload = await uploadFile(uploadArgs)
if (!upload) throw new Error(`failed to upload ${tmpImagePath} to S3`);
if (!upload.Key) throw new Error(`failed to upload ${tmpImagePath} to S3 (upload.Key was missing)`);
// we need to create a S3 file in the db
const thumbnail = getCdnUrl(configs.s3Bucket, upload.Key)
await patchVodInDatabase(vod_id, { thumbnail })
}
export default generate_thumbnail

View File

@ -28,6 +28,7 @@ function assertPayload(payload: any): asserts payload is Payload {
* For example, combine_video_segments is only useful on a vod recording which ended up with multiple segments.
*
* - combine_video_segments
* - remux_video
* - generate_thumbnail
* - queue_moderator_review
* - create_mux_asset
@ -35,15 +36,8 @@ function assertPayload(payload: any): asserts payload is Payload {
*
* 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_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_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_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.
*
* thus our solution is to invoke ALL the above jobs immediately, and build those jobs to fail if pre-conditions are unmet.
* all listed jobs are also idempotent, which means if they don't need to run (previously already performed their tasks), they will exit without doing anything.
*
*/
const process_video: Task = async function (payload: unknown, helpers: Helpers) {
@ -51,16 +45,30 @@ const process_video: Task = async function (payload: unknown, helpers: Helpers)
const { vod_id } = payload
helpers.logger.info(`process_video task has begun for vod_id=${vod_id}`)
const vod = await getVod(vod_id, helpers)
if (!vod) throw new Error(`failed to get vod from database.`);
if (!vod.segments) throw new Error(`vod ${vod_id} fetched from database lacked any segments.`);
const isCombinationNeeded = (vod.segments.length > 1)
if (isCombinationNeeded) {
const s3_manifest = vod.segments.map((segment) => ({ key: segment.s3_key }))
const s3_manifest = vod.segments.map((segment) => ({ key: segment.s3_key, bytes: segment.bytes }))
helpers.logger.info(`There are ${vod.segments.length} segments; Concatenation is needed.`)
helpers.addJob('combine_video_segments', { s3_manifest, vod_id })
} else {
helpers.addJob('remux_video', { vod_id })
}
helpers.addJob('generate_thumbnail', { vod_id })
// helpers.addJob('queue_moderator_review', { })
// helpers.addJob('create_mux_asset', { })
// helpers.addJob('create_torrent', { })
// helpers.addJob('create_ipfs', { })
}
export default process_video;

View File

@ -0,0 +1,79 @@
import type { Helpers, Task } from "graphile-worker"
import { configs } from "../config"
import getVod from "../fetchers/getVod"
import { downloadFile, uploadFile, type S3FileArgs } from "@futureporn/storage/s3.ts"
import { remux } from '@futureporn/utils/video.ts'
import { getTmpFile } from "@futureporn/utils/file.ts"
import { basename } from "node:path"
import patchVodInDatabase from "../fetchers/patchVodInDatabase"
interface Payload {
vod_id: string;
}
function assertPayload(payload: any): asserts payload is Payload {
if (typeof payload !== "object" || !payload) throw new Error("invalid payload (it must be an object)");
if (typeof payload.vod_id !== "string") throw new Error("payload.vod_id was not a string");
}
/**
*
* # remux_video
*
* convert a .ts video file to a .mp4 video file (safely, without loss of quality)
*/
const remux_video: Task = async function (payload: unknown, helpers: Helpers) {
assertPayload(payload)
const { vod_id } = payload
helpers.logger.info(`remux_video task has begun for vod_id=${vod_id}`)
const vod = await getVod(vod_id, helpers)
if (!vod) throw new Error(`failed to get vod from database.`);
if (!vod.segments) throw new Error(`vod ${vod_id} fetched from database lacked any segments.`);
if (!vod.segments.at(0)) throw new Error(`vod ${vod_id} lacked a recorded segment.`);
if (!vod.segments.at(0)?.s3_key) throw new Error(`vod ${vod_id} lacked a recorded segment s3_key name.`);
const segmentFileName = vod.segments.at(0)!.s3_key
if (!segmentFileName.endsWith('.ts')) throw new Error(`vod ${vod_id} recording segment ${segmentFileName} did not end with .ts, which is expected.`);
const moreThanOneSegment = (vod.segments.length > 1)
if (moreThanOneSegment) {
throw new Error(`remux_video was given vod=${vod_id} which has more than one segment. This is unsupported. (use combine_video_segments Task to combine multiple videos)`);
}
const tmpFilePath = getTmpFile(segmentFileName)
const downloadArgs: S3FileArgs = {
filePath: tmpFilePath,
s3AccessKeyId: configs.s3AccessKeyId,
s3SecretAccessKey: configs.s3SecretAccessKey,
s3BucketName: configs.s3Bucket,
s3Endpoint: configs.s3Region,
s3Region: configs.s3Region
}
await downloadFile(downloadArgs)
helpers.logger.info('Remuxing the video')
const outputVideoPath = getTmpFile(`${basename(segmentFileName, '.ts')}.mp4`)
await remux(tmpFilePath, outputVideoPath)
helpers.logger.info('Uploading the remuxed video')
const uploadArgs: S3FileArgs = {
filePath: outputVideoPath,
s3AccessKeyId: configs.s3AccessKeyId,
s3SecretAccessKey: configs.s3SecretAccessKey,
s3BucketName: configs.s3Bucket,
s3Endpoint: configs.s3Region,
s3Region: configs.s3Region
}
const upload = await uploadFile(uploadArgs)
helpers.logger.info('Patching the vod in the database')
await patchVodInDatabase(vod_id, { s3_file: upload?.Key })
}
export default remux_video;

View File

@ -15,30 +15,29 @@
"license": "Unlicense",
"devDependencies": {
"@esbuild-plugins/esm-externals": "^0.1.2",
"@futureporn/image": "workspace:^",
"@futureporn/scout": "workspace:^",
"@futureporn/storage": "workspace:^",
"@futureporn/types": "workspace:^",
"@futureporn/utils": "workspace:^",
"@types/chai": "^4.3.16",
"@types/imapflow": "^1.0.18",
"@types/chai": "^4.3.19",
"@types/imapflow": "^1.0.19",
"@types/mailparser": "^3.4.4",
"@types/mocha": "^10.0.7",
"chai": "^5.1.1",
"mocha": "^10.7.0",
"tsup": "^8.1.2",
"typescript": "^5.5.3"
"mocha": "^10.7.3",
"tsup": "^8.2.4",
"typescript": "^5.5.4"
},
"dependencies": {
"@types/node": "^20.14.9",
"@types/node": "^22.5.2",
"@types/qs": "^6.9.15",
"cheerio": "1.0.0-rc.12",
"cheerio": "1.0.0",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"fastify": "^4.12.0",
"fastify": "^4.28.1",
"graphile-worker": "^0.16.6",
"imapflow": "^1.0.164",
"mailparser": "^3.7.1",
"qs": "^6.12.3"
"qs": "^6.13.0"
}
}

View File

@ -9,14 +9,14 @@ importers:
.:
dependencies:
'@types/node':
specifier: ^20.14.9
version: 20.16.1
specifier: ^22.5.2
version: 22.5.2
'@types/qs':
specifier: ^6.9.15
version: 6.9.15
cheerio:
specifier: 1.0.0-rc.12
version: 1.0.0-rc.12
specifier: 1.0.0
version: 1.0.0
date-fns:
specifier: ^3.6.0
version: 3.6.0
@ -24,7 +24,7 @@ importers:
specifier: ^16.4.5
version: 16.4.5
fastify:
specifier: ^4.12.0
specifier: ^4.28.1
version: 4.28.1
graphile-worker:
specifier: ^0.16.6
@ -36,18 +36,15 @@ importers:
specifier: ^3.7.1
version: 3.7.1
qs:
specifier: ^6.12.3
specifier: ^6.13.0
version: 6.13.0
devDependencies:
'@esbuild-plugins/esm-externals':
specifier: ^0.1.2
version: 0.1.2(esbuild@0.23.1)
'@futureporn/image':
specifier: workspace:^
version: link:../../packages/image
'@futureporn/scout':
specifier: workspace:^
version: link:../../packages/scout
version: link:../scout
'@futureporn/storage':
specifier: workspace:^
version: link:../../packages/storage
@ -58,10 +55,10 @@ importers:
specifier: workspace:^
version: link:../../packages/utils
'@types/chai':
specifier: ^4.3.16
version: 4.3.17
specifier: ^4.3.19
version: 4.3.19
'@types/imapflow':
specifier: ^1.0.18
specifier: ^1.0.19
version: 1.0.19
'@types/mailparser':
specifier: ^3.4.4
@ -73,13 +70,13 @@ importers:
specifier: ^5.1.1
version: 5.1.1
mocha:
specifier: ^10.7.0
specifier: ^10.7.3
version: 10.7.3
tsup:
specifier: ^8.1.2
specifier: ^8.2.4
version: 8.2.4(typescript@5.5.4)
typescript:
specifier: ^5.5.3
specifier: ^5.5.4
version: 5.5.4
packages:
@ -298,91 +295,91 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@rollup/rollup-android-arm-eabi@4.21.0':
resolution: {integrity: sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==}
'@rollup/rollup-android-arm-eabi@4.21.2':
resolution: {integrity: sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.21.0':
resolution: {integrity: sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==}
'@rollup/rollup-android-arm64@4.21.2':
resolution: {integrity: sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.21.0':
resolution: {integrity: sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==}
'@rollup/rollup-darwin-arm64@4.21.2':
resolution: {integrity: sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.21.0':
resolution: {integrity: sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==}
'@rollup/rollup-darwin-x64@4.21.2':
resolution: {integrity: sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
resolution: {integrity: sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==}
'@rollup/rollup-linux-arm-gnueabihf@4.21.2':
resolution: {integrity: sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
resolution: {integrity: sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==}
'@rollup/rollup-linux-arm-musleabihf@4.21.2':
resolution: {integrity: sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.21.0':
resolution: {integrity: sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==}
'@rollup/rollup-linux-arm64-gnu@4.21.2':
resolution: {integrity: sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.21.0':
resolution: {integrity: sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==}
'@rollup/rollup-linux-arm64-musl@4.21.2':
resolution: {integrity: sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
resolution: {integrity: sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==}
'@rollup/rollup-linux-powerpc64le-gnu@4.21.2':
resolution: {integrity: sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
resolution: {integrity: sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==}
'@rollup/rollup-linux-riscv64-gnu@4.21.2':
resolution: {integrity: sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.21.0':
resolution: {integrity: sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==}
'@rollup/rollup-linux-s390x-gnu@4.21.2':
resolution: {integrity: sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.21.0':
resolution: {integrity: sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==}
'@rollup/rollup-linux-x64-gnu@4.21.2':
resolution: {integrity: sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.21.0':
resolution: {integrity: sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==}
'@rollup/rollup-linux-x64-musl@4.21.2':
resolution: {integrity: sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.21.0':
resolution: {integrity: sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==}
'@rollup/rollup-win32-arm64-msvc@4.21.2':
resolution: {integrity: sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.21.0':
resolution: {integrity: sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==}
'@rollup/rollup-win32-ia32-msvc@4.21.2':
resolution: {integrity: sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.21.0':
resolution: {integrity: sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==}
'@rollup/rollup-win32-x64-msvc@4.21.2':
resolution: {integrity: sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==}
cpu: [x64]
os: [win32]
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
'@types/chai@4.3.17':
resolution: {integrity: sha512-zmZ21EWzR71B4Sscphjief5djsLre50M6lI622OSySTmn9DB3j+C3kWroHfBQWXbOBwbgg/M8CG/hUxDLIloow==}
'@types/chai@4.3.19':
resolution: {integrity: sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -405,11 +402,14 @@ packages:
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/node@20.16.1':
resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==}
'@types/node@20.16.3':
resolution: {integrity: sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==}
'@types/pg@8.11.6':
resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==}
'@types/node@22.5.2':
resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==}
'@types/pg@8.11.8':
resolution: {integrity: sha512-IqpCf8/569txXN/HoP5i1LjXfKZWL76Yr2R77xgeIICUbAYHeoaEZFhYHo2uDftecLWrTJUq63JvQu8q3lnDyA==}
'@types/qs@6.9.15':
resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==}
@ -559,9 +559,9 @@ packages:
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
cheerio@1.0.0-rc.12:
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
engines: {node: '>= 6'}
cheerio@1.0.0:
resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
engines: {node: '>=18.17'}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
@ -693,6 +693,9 @@ packages:
resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==}
engines: {node: '>=8.10.0'}
encoding-sniffer@0.2.0:
resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@ -713,8 +716,8 @@ packages:
engines: {node: '>=18'}
hasBin: true
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@1.0.5:
@ -885,6 +888,9 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
htmlparser2@9.1.0:
resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@ -1160,6 +1166,9 @@ packages:
parse5-htmlparser2-tree-adapter@7.0.0:
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
parse5-parser-stream@7.1.2:
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
@ -1231,8 +1240,8 @@ packages:
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
@ -1248,8 +1257,8 @@ packages:
resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==}
hasBin: true
pino@9.3.2:
resolution: {integrity: sha512-WtARBjgZ7LNEkrGWxMBN/jvlFiE17LTbBoH0konmBU684Kd0uIiDwBXlcTCW7iJnA6HfIKwUssS/2AC6cDEanw==}
pino@9.4.0:
resolution: {integrity: sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==}
hasBin: true
pirates@4.0.6:
@ -1383,8 +1392,8 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rollup@4.21.0:
resolution: {integrity: sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==}
rollup@4.21.2:
resolution: {integrity: sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@ -1397,8 +1406,8 @@ packages:
safe-regex2@3.1.0:
resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
safe-stable-stringify@2.4.3:
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
safer-buffer@2.1.2:
@ -1456,8 +1465,8 @@ packages:
resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
sonic-boom@4.0.1:
resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==}
sonic-boom@4.1.0:
resolution: {integrity: sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==}
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
@ -1579,9 +1588,21 @@ packages:
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
undici@6.19.8:
resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
engines: {node: '>=18.17'}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@ -1641,7 +1662,7 @@ snapshots:
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
picocolors: 1.0.1
picocolors: 1.1.0
'@babel/helper-validator-identifier@7.24.7': {}
@ -1650,7 +1671,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
js-tokens: 4.0.0
picocolors: 1.0.1
picocolors: 1.1.0
'@esbuild-plugins/esm-externals@0.1.2(esbuild@0.23.1)':
dependencies:
@ -1791,52 +1812,52 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@rollup/rollup-android-arm-eabi@4.21.0':
'@rollup/rollup-android-arm-eabi@4.21.2':
optional: true
'@rollup/rollup-android-arm64@4.21.0':
'@rollup/rollup-android-arm64@4.21.2':
optional: true
'@rollup/rollup-darwin-arm64@4.21.0':
'@rollup/rollup-darwin-arm64@4.21.2':
optional: true
'@rollup/rollup-darwin-x64@4.21.0':
'@rollup/rollup-darwin-x64@4.21.2':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.21.0':
'@rollup/rollup-linux-arm-gnueabihf@4.21.2':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.21.0':
'@rollup/rollup-linux-arm-musleabihf@4.21.2':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.21.0':
'@rollup/rollup-linux-arm64-gnu@4.21.2':
optional: true
'@rollup/rollup-linux-arm64-musl@4.21.0':
'@rollup/rollup-linux-arm64-musl@4.21.2':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.21.0':
'@rollup/rollup-linux-powerpc64le-gnu@4.21.2':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.21.0':
'@rollup/rollup-linux-riscv64-gnu@4.21.2':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.21.0':
'@rollup/rollup-linux-s390x-gnu@4.21.2':
optional: true
'@rollup/rollup-linux-x64-gnu@4.21.0':
'@rollup/rollup-linux-x64-gnu@4.21.2':
optional: true
'@rollup/rollup-linux-x64-musl@4.21.0':
'@rollup/rollup-linux-x64-musl@4.21.2':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.21.0':
'@rollup/rollup-win32-arm64-msvc@4.21.2':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.21.0':
'@rollup/rollup-win32-ia32-msvc@4.21.2':
optional: true
'@rollup/rollup-win32-x64-msvc@4.21.0':
'@rollup/rollup-win32-x64-msvc@4.21.2':
optional: true
'@selderee/plugin-htmlparser2@0.11.0':
@ -1844,7 +1865,7 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
'@types/chai@4.3.17': {}
'@types/chai@4.3.19': {}
'@types/debug@4.1.12':
dependencies:
@ -1854,28 +1875,32 @@ snapshots:
'@types/imapflow@1.0.19':
dependencies:
'@types/node': 20.16.1
'@types/node': 22.5.2
'@types/interpret@1.1.3':
dependencies:
'@types/node': 20.16.1
'@types/node': 22.5.2
'@types/mailparser@3.4.4':
dependencies:
'@types/node': 20.16.1
'@types/node': 22.5.2
iconv-lite: 0.6.3
'@types/mocha@10.0.7': {}
'@types/ms@0.7.34': {}
'@types/node@20.16.1':
'@types/node@20.16.3':
dependencies:
undici-types: 6.19.8
'@types/pg@8.11.6':
'@types/node@22.5.2':
dependencies:
'@types/node': 20.16.1
undici-types: 6.19.8
'@types/pg@8.11.8':
dependencies:
'@types/node': 22.5.2
pg-protocol: 1.6.1
pg-types: 4.0.2
@ -2012,15 +2037,19 @@ snapshots:
domhandler: 5.0.3
domutils: 3.1.0
cheerio@1.0.0-rc.12:
cheerio@1.0.0:
dependencies:
cheerio-select: 2.1.0
dom-serializer: 2.0.0
domhandler: 5.0.3
domutils: 3.1.0
htmlparser2: 8.0.2
encoding-sniffer: 0.2.0
htmlparser2: 9.1.0
parse5: 7.1.2
parse5-htmlparser2-tree-adapter: 7.0.0
parse5-parser-stream: 7.1.2
undici: 6.19.8
whatwg-mimetype: 4.0.0
chokidar@3.6.0:
dependencies:
@ -2147,6 +2176,11 @@ snapshots:
encoding-japanese@2.2.0: {}
encoding-sniffer@0.2.0:
dependencies:
iconv-lite: 0.6.3
whatwg-encoding: 3.1.1
entities@4.5.0: {}
error-ex@1.3.2:
@ -2186,7 +2220,7 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.2: {}
escalade@3.2.0: {}
escape-string-regexp@1.0.5: {}
@ -2253,7 +2287,7 @@ snapshots:
fast-json-stringify: 5.16.1
find-my-way: 8.2.0
light-my-request: 5.13.0
pino: 9.3.2
pino: 9.4.0
process-warning: 3.0.0
proxy-addr: 2.0.7
rfdc: 1.4.1
@ -2347,7 +2381,7 @@ snapshots:
graphile-config@0.0.1-beta.9:
dependencies:
'@types/interpret': 1.1.3
'@types/node': 20.16.1
'@types/node': 20.16.3
'@types/semver': 7.5.8
chalk: 4.1.2
debug: 4.3.6(supports-color@8.1.1)
@ -2362,7 +2396,7 @@ snapshots:
dependencies:
'@graphile/logger': 0.2.0
'@types/debug': 4.1.12
'@types/pg': 8.11.6
'@types/pg': 8.11.8
cosmiconfig: 8.3.6(typescript@5.5.4)
graphile-config: 0.0.1-beta.9
json5: 2.2.3
@ -2407,6 +2441,13 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
htmlparser2@9.1.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.1.0
entities: 4.5.0
human-signals@2.1.0: {}
iconv-lite@0.6.3:
@ -2686,6 +2727,10 @@ snapshots:
domhandler: 5.0.3
parse5: 7.1.2
parse5-parser-stream@7.1.2:
dependencies:
parse5: 7.1.2
parse5@7.1.2:
dependencies:
entities: 4.5.0
@ -2757,7 +2802,7 @@ snapshots:
dependencies:
split2: 4.2.0
picocolors@1.0.1: {}
picocolors@1.1.0: {}
picomatch@2.3.1: {}
@ -2778,11 +2823,11 @@ snapshots:
process-warning: 3.0.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.4.3
sonic-boom: 4.0.1
safe-stable-stringify: 2.5.0
sonic-boom: 4.1.0
thread-stream: 3.1.0
pino@9.3.2:
pino@9.4.0:
dependencies:
atomic-sleep: 1.0.0
fast-redact: 3.5.0
@ -2792,8 +2837,8 @@ snapshots:
process-warning: 4.0.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.4.3
sonic-boom: 4.0.1
safe-stable-stringify: 2.5.0
sonic-boom: 4.1.0
thread-stream: 3.1.0
pirates@4.0.6: {}
@ -2879,26 +2924,26 @@ snapshots:
rfdc@1.4.1: {}
rollup@4.21.0:
rollup@4.21.2:
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@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
'@rollup/rollup-android-arm-eabi': 4.21.2
'@rollup/rollup-android-arm64': 4.21.2
'@rollup/rollup-darwin-arm64': 4.21.2
'@rollup/rollup-darwin-x64': 4.21.2
'@rollup/rollup-linux-arm-gnueabihf': 4.21.2
'@rollup/rollup-linux-arm-musleabihf': 4.21.2
'@rollup/rollup-linux-arm64-gnu': 4.21.2
'@rollup/rollup-linux-arm64-musl': 4.21.2
'@rollup/rollup-linux-powerpc64le-gnu': 4.21.2
'@rollup/rollup-linux-riscv64-gnu': 4.21.2
'@rollup/rollup-linux-s390x-gnu': 4.21.2
'@rollup/rollup-linux-x64-gnu': 4.21.2
'@rollup/rollup-linux-x64-musl': 4.21.2
'@rollup/rollup-win32-arm64-msvc': 4.21.2
'@rollup/rollup-win32-ia32-msvc': 4.21.2
'@rollup/rollup-win32-x64-msvc': 4.21.2
fsevents: 2.3.3
run-parallel@1.2.0:
@ -2911,7 +2956,7 @@ snapshots:
dependencies:
ret: 0.4.3
safe-stable-stringify@2.4.3: {}
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
@ -2964,7 +3009,7 @@ snapshots:
ip-address: 9.0.5
smart-buffer: 4.2.0
sonic-boom@4.0.1:
sonic-boom@4.1.0:
dependencies:
atomic-sleep: 1.0.0
@ -3067,10 +3112,10 @@ snapshots:
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
picocolors: 1.0.1
picocolors: 1.1.0
postcss-load-config: 6.0.1
resolve-from: 5.0.0
rollup: 4.21.0
rollup: 4.21.2
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tree-kill: 1.2.2
@ -3088,8 +3133,16 @@ snapshots:
undici-types@6.19.8: {}
undici@6.19.8: {}
webidl-conversions@4.0.2: {}
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-mimetype@4.0.0: {}
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
@ -3134,7 +3187,7 @@ snapshots:
yargs@16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
@ -3144,7 +3197,7 @@ snapshots:
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.1.2
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3

View File

@ -17,7 +17,6 @@ export default defineConfig({
* be transpiled / bundled together with the deployed code.
*/
noExternal: [
"@futureporn/image",
"@futureporn/utils",
"@futureporn/scout",
"@futureporn/storage",

View File

@ -12,67 +12,67 @@
"superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/fontawesome-free": "^6.6.0",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@futureporn/types": "workspace:*",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.7.0",
"@hookform/resolvers": "^3.9.0",
"@mux/blurhash": "^0.1.2",
"@mux/mux-player": "^2.7.0",
"@mux/mux-player-react": "^2.7.0",
"@mux/mux-player": "^2.9.1",
"@mux/mux-player-react": "^2.9.1",
"@paralleldrive/cuid2": "^2.2.2",
"@react-hookz/web": "^24.0.4",
"@tanstack/react-query": "^5.49.2",
"@tanstack/react-table": "^8.19.2",
"@types/lodash": "^4.17.6",
"@tanstack/react-query": "^5.53.3",
"@tanstack/react-table": "^8.20.5",
"@types/lodash": "^4.17.7",
"@types/qs": "^6.9.15",
"@types/react": "^18.3.3",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@uppy/aws-s3": "^3.6.2",
"@uppy/aws-s3-multipart": "^3.12.0",
"@uppy/core": "^3.13.0",
"@uppy/dashboard": "^3.9.1",
"@uppy/drag-drop": "^3.1.0",
"@uppy/file-input": "^3.1.2",
"@uppy/progress-bar": "^3.1.1",
"@uppy/react": "^3.4.0",
"@uppy/remote-sources": "^1.3.0",
"bulma": "^1.0.1",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.1",
"dayjs": "^1.11.11",
"@uppy/aws-s3": "^4.1.0",
"@uppy/aws-s3-multipart": "^4.0.0",
"@uppy/core": "^4.2.0",
"@uppy/dashboard": "^4.1.0",
"@uppy/drag-drop": "^4.0.2",
"@uppy/file-input": "^4.0.1",
"@uppy/progress-bar": "^4.0.0",
"@uppy/react": "^4.0.2",
"@uppy/remote-sources": "^2.2.0",
"bulma": "^1.0.2",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"dayjs": "^1.11.13",
"feed": "^4.2.2",
"gray-matter": "^4.0.3",
"hls.js": "^1.5.12",
"hls.js": "^1.5.15",
"lodash": "^4.17.21",
"lunarphase-js": "^2.0.3",
"multiformats": "^13.1.3",
"next": "14.0.4",
"multiformats": "^13.2.2",
"next": "14.2.7",
"next-goatcounter": "^1.0.5",
"nextjs-toploader": "^1.6.12",
"nextjs-toploader": "^3.6.15",
"plyr": "^3.7.8",
"prism-react-renderer": "^2.3.1",
"qs": "^6.12.2",
"prism-react-renderer": "^2.4.0",
"qs": "^6.13.0",
"react": "^18.3.1",
"react-data-table-component": "^7.6.2",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.1",
"react-hook-form": "^7.53.0",
"react-loading-skeleton": "^3.4.0",
"react-toastify": "^9.1.3",
"react-toastify": "^10.0.5",
"sass": "^1.77.8",
"sharp": "^0.33.4",
"sharp": "^0.33.5",
"slugify": "^1.6.6",
"styled-components": "5.3.3",
"@futureporn/types": "workspace:*",
"styled-components": "6.1.13",
"yup": "^1.4.0"
},
"devDependencies": {
"@types/node": "^20.14.9",
"eslint": "^8.57.0",
"eslint-config-next": "14.0.4",
"typescript": "5.3.3"
"@types/node": "^22.5.2",
"eslint": "^9.9.1",
"eslint-config-next": "14.2.7",
"typescript": "5.5.4"
},
"packageManager": "pnpm@9.1.3"
}

File diff suppressed because it is too large Load Diff

View File

@ -33,9 +33,9 @@
"@temporalio/workflow": "^1.11.1",
"@tsconfig/node20": "^20.1.4",
"@types/imapflow": "^1.0.19",
"@types/node": "^22.5.0",
"@types/pg": "^8.11.6",
"cheerio": "1.0.0-rc.12",
"@types/node": "^22.5.2",
"@types/pg": "^8.11.8",
"cheerio": "1.0.0",
"concurrently": "^8.2.2",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
@ -45,13 +45,13 @@
"htmlparser2": "^9.1.0",
"imapflow": "^1.0.164",
"js-yaml": "^4.1.0",
"limiter": "2.0.1",
"limiter": "2.1.0",
"mailparser": "^3.7.1",
"node-fetch": "^3.3.2",
"openapi-backend": "^5.10.6",
"p-retry": "^5.1.2",
"pg": "8.8.0",
"prevvy": "^7.5.0",
"p-retry": "^6.2.0",
"pg": "8.12.0",
"prevvy": "^8.0.1",
"qs": "^6.13.0",
"rate-limiter-flexible": "^5.0.3",
"sharp": "^0.33.5",
@ -59,7 +59,7 @@
"swagger-editor-dist": "^4.13.1",
"swagger-ui-dist": "^5.17.14",
"ts-json-schema-generator": "^2.3.0",
"tsx": "^4.18.0",
"tsx": "^4.19.0",
"typescript-json-schema": "^0.65.1",
"xpath": "^0.0.34"
},
@ -68,7 +68,7 @@
"@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7",
"@futureporn/utils": "workspace:^",
"@types/chai": "^4.3.18",
"@types/chai": "^4.3.19",
"@types/cheerio": "^0.22.35",
"@types/mailparser": "^3.4.4",
"@types/mocha": "^10.0.7",
@ -77,7 +77,7 @@
"esmock": "^2.6.7",
"mocha": "^10.7.3",
"nodemon": "^3.1.4",
"sinon": "^15.2.0",
"sinon": "^18.0.0",
"ts-node": "^10.9.2",
"tsup": "^8.2.4",
"typescript": "^5.5.4"

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { download, getTmpFile } from '@futureporn/utils';
import { download, getTmpFile } from '@futureporn/utils/file.ts';
import type { VtuberRecord } from '@futureporn/types';
import { ua0 } from './ua.ts';
import scrapingFetch from './scrapingFetch.ts';

View File

@ -11,7 +11,7 @@ import { VtuberRecord, VtuberResponse, VtuberDataScrape } from './schemas.ts'
import scrapeVtuberData from './scrapeVtuberData.ts'
import { getPlaylistUrl } from './ytdlp.ts'
import { getRandomRoom } from './cb.ts'
import { getPackageVersion } from '@futureporn/utils'
import { getPackageVersion } from '@futureporn/utils/file.ts'
type VtuberDataRequest = FastifyRequest<{

View File

@ -1,7 +1,7 @@
import * as htmlparser2 from "htmlparser2";
import { load } from 'cheerio'
import { download } from '@futureporn/utils';
import { download } from '@futureporn/utils/file.ts';
import pRetry, { AbortError } from 'p-retry';
if (!process.env.SCOUT_NITTER_ACCESS_KEY) throw new Error('SCOUT_NITTER_ACCESS_KEY was undefined in env');

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