From 733e2ab006b2fdd3b2882ae16324f63ca9532f93 Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Thu, 12 Jan 2023 12:05:53 +0100 Subject: [PATCH] it just works --- .dockerignore | 3 +- .gitignore | 4 +- Makefile | 16 + frontend/index.html | 2 +- package.json => frontend/package.json | 24 +- frontend/src/App.tsx | 8 +- frontend/src/Archived.tsx | 46 - frontend/src/Home.tsx | 26 +- frontend/src/Settings.tsx | 13 +- frontend/src/components/StackableResult.tsx | 55 +- frontend/src/components/Statistics.tsx | 52 - .../core/argsParser.ts} | 0 frontend/src/{ => features/core}/events.ts | 0 .../src/{i18n.ts => features/core/intl.ts} | 4 +- frontend/src/{ => features/core}/rpcClient.ts | 9 +- frontend/{ => src}/index.tsx | 2 +- frontend/src/interfaces.ts | 33 - frontend/src/types.d.ts | 17 + frontend/src/utils.ts | 32 - vite.config.ts => frontend/vite.config.ts | 4 +- go.mod | 1 + go.sum | 3 + main.go | 31 +- pnpm-lock.yaml | 2209 ----------------- server-node/src/core/HTTPServer.ts | 86 - server-node/src/core/Process.ts | 166 -- server-node/src/core/downloadArchive.ts | 30 - server-node/src/core/downloader.ts | 251 -- server-node/src/core/states.ts | 9 - server-node/src/core/streamer.ts | 39 - server-node/src/db/memoryDB.ts | 80 - .../src/interfaces/IDownloadMetadata.d.ts | 15 - server-node/src/interfaces/IPayload.d.ts | 13 - server-node/src/interfaces/IRecord.d.ts | 14 - server-node/src/interfaces/ISettings.d.ts | 5 - server-node/src/main.ts | 128 - server-node/src/types/index.d.ts | 6 - server-node/src/utils/BetterLogger.ts | 59 - server-node/src/utils/directoryUtils.ts | 59 - server-node/src/utils/logger.ts | 25 - server-node/src/utils/params.ts | 4 - server-node/src/utils/procUtils.ts | 36 - server-node/src/utils/updater.ts | 90 - server/config/config_singleton.go | 60 + server/process.go | 40 +- server/server.go | 4 +- server/service.go | 26 +- server/sys/fs.go | 13 +- server/types.go | 5 +- server/updater/forced_update.go | 45 + server/updater/types.go | 6 + server/updater/update.go | 17 + settings.json | 5 - tsconfig.json | 14 - 54 files changed, 336 insertions(+), 3608 deletions(-) create mode 100644 Makefile rename package.json => frontend/package.json (56%) delete mode 100644 frontend/src/Archived.tsx delete mode 100644 frontend/src/components/Statistics.tsx rename frontend/src/{classes.ts => features/core/argsParser.ts} (100%) rename frontend/src/{ => features/core}/events.ts (100%) rename frontend/src/{i18n.ts => features/core/intl.ts} (87%) rename frontend/src/{ => features/core}/rpcClient.ts (86%) rename frontend/{ => src}/index.tsx (87%) delete mode 100644 frontend/src/interfaces.ts rename vite.config.ts => frontend/vite.config.ts (76%) delete mode 100644 pnpm-lock.yaml delete mode 100644 server-node/src/core/HTTPServer.ts delete mode 100644 server-node/src/core/Process.ts delete mode 100644 server-node/src/core/downloadArchive.ts delete mode 100644 server-node/src/core/downloader.ts delete mode 100644 server-node/src/core/states.ts delete mode 100644 server-node/src/core/streamer.ts delete mode 100644 server-node/src/db/memoryDB.ts delete mode 100644 server-node/src/interfaces/IDownloadMetadata.d.ts delete mode 100644 server-node/src/interfaces/IPayload.d.ts delete mode 100644 server-node/src/interfaces/IRecord.d.ts delete mode 100644 server-node/src/interfaces/ISettings.d.ts delete mode 100644 server-node/src/main.ts delete mode 100644 server-node/src/types/index.d.ts delete mode 100644 server-node/src/utils/BetterLogger.ts delete mode 100644 server-node/src/utils/directoryUtils.ts delete mode 100644 server-node/src/utils/logger.ts delete mode 100644 server-node/src/utils/params.ts delete mode 100644 server-node/src/utils/procUtils.ts delete mode 100644 server-node/src/utils/updater.ts create mode 100644 server/config/config_singleton.go create mode 100644 server/updater/forced_update.go create mode 100644 server/updater/types.go create mode 100644 server/updater/update.go delete mode 100644 settings.json delete mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore index dc69752..acfeba9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,4 +11,5 @@ src/server/core/yt-dlp .env *.mp4 *.ytdl -*.db \ No newline at end of file +*.db +build/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a804c8..0f5b409 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ src/server/core/yt-dlp *.part *.db downloads -.DS_Store \ No newline at end of file +.DS_Store +build/ +yt-dlp-webui diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..882764c --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +default: + go build -o yt-dlp-webui main.go + +all: + cd frontend && pnpm build && cd .. + go build -o yt-dlp-webui main.go + +multiarch: + GOOS=linux GOARCH=arm go build -o yt-dlp-webui_linux-arm *.go + GOOS=linux GOARCH=arm64 go build -o yt-dlp-webui_linux-arm64 *.go + GOOS=linux GOARCH=amd64 go build -o yt-dlp-webui_linux-amd64 *.go + mkdir -p build + mv yt-dlp-webui* build + +clean: + rm -rf build \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 5ff2e1e..c657b3f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -10,7 +10,7 @@
- + \ No newline at end of file diff --git a/package.json b/frontend/package.json similarity index 56% rename from package.json rename to frontend/package.json index 195ad3f..85b0d42 100644 --- a/package.json +++ b/frontend/package.json @@ -3,17 +3,8 @@ "version": "1.1.0", "description": "A terrible webUI for yt-dlp, all-in-one solution.", "scripts": { - "dev": "nodemon dist/main.js", - "start": "node dist/main.js", - "watch": "tsc --build -w", - "build": "vite build", - "build-server": "tsc --build", - "build-all": "tsc --build && npm run build && npm run fetch", - "clean": "tsc --build --clean", - "clean-all": "rm -r dist", - "fe": "vite", - "fetch-dev": "./fetch-yt-dlp.sh && mv yt-dlp ./server/core", - "fetch": "./fetch-yt-dlp.sh && mv yt-dlp ./dist/core" + "dev": "vite", + "build": "vite build" }, "author": "marcobaobao", "license": "ISC", @@ -24,26 +15,17 @@ "@mui/icons-material": "^5.6.2", "@mui/material": "^5.6.4", "@reduxjs/toolkit": "^1.8.1", - "koa": "^2.13.4", - "koa-router": "^10.1.1", - "koa-static": "^5.0.0", - "mime-types": "^2.1.35", "radash": "^10.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.1", "react-router-dom": "^6.3.0", "rxjs": "^7.4.0", - "socket.io": "^4.3.2", - "socket.io-client": "^4.3.2", "uuid": "^8.3.2" }, "devDependencies": { "@modyfi/vite-plugin-yaml": "^1.0.2", - "@types/koa": "^2.13.4", - "@types/koa-router": "^7.4.4", - "@types/mime-types": "^2.1.1", - "@types/node": "^17.0.31", + "@types/node": "^18.11.18", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 870f5f2..c218829 100755 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,13 +17,12 @@ import { } from "@mui/material"; import { grey } from "@mui/material/colors"; import ListItemButton from '@mui/material/ListItemButton'; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { Provider, useSelector } from "react-redux"; import { BrowserRouter as Router, Link, Route, Routes } from 'react-router-dom'; -import ArchivedDownloads from "./Archived"; import { AppBar } from "./components/AppBar"; import { Drawer } from "./components/Drawer"; import Home from "./Home"; @@ -163,9 +162,8 @@ function AppContent() { > - }> - }> - }> + } /> + } /> diff --git a/frontend/src/Archived.tsx b/frontend/src/Archived.tsx deleted file mode 100644 index 722188e..0000000 --- a/frontend/src/Archived.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Backdrop, CircularProgress, Container, Grid } from "@mui/material"; -import { ArchiveResult } from "./components/ArchiveResult"; -import { useSelector } from "react-redux"; -import { RootState } from "./stores/store"; - -export default function archivedDownloads() { - const [loading, setLoading] = useState(true); - const [archived, setArchived] = useState([]); - - const settings = useSelector((state: RootState) => state.settings) - - useEffect(() => { - fetch(`http://${settings.serverAddr}:3022/getAllDownloaded`) - .then(res => res.json()) - .then(data => setArchived(data)) - .then(() => setLoading(false)) - }, []); - - return ( - - theme.zIndex.drawer + 1 }} - open={loading} - > - - - {/* - archived.length > 0 ? - - { - archived.map((el, idx) => - - - - ) - } - - : null - */} - - ); -} \ No newline at end of file diff --git a/frontend/src/Home.tsx b/frontend/src/Home.tsx index 3582cb0..c2311ff 100644 --- a/frontend/src/Home.tsx +++ b/frontend/src/Home.tsx @@ -21,16 +21,14 @@ import { import { Buffer } from 'buffer'; import { Fragment, useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { CliArguments } from "./classes"; import { StackableResult } from "./components/StackableResult"; -import { serverStates } from "./events"; +import { CliArguments } from "./features/core/argsParser"; +import I18nBuilder from "./features/core/intl"; +import { RPCClient } from "./features/core/rpcClient"; import { connected, setFreeSpace } from "./features/status/statusSlice"; -import { I18nBuilder } from "./i18n"; -import { IDLMetadata, IMessage } from "./interfaces"; -import { RPCClient } from "./rpcClient"; import { RootState } from "./stores/store"; -import { RPCResult } from "./types"; -import { isValidURL, toFormatArgs, updateInStateMap } from "./utils"; +import { IDLMetadata, RPCResult } from "./types"; +import { isValidURL, toFormatArgs } from "./utils"; type Props = { socket: WebSocket @@ -43,11 +41,7 @@ export default function Home({ socket }: Props) { const dispatch = useDispatch() // ephemeral state - const [progressMap, setProgressMap] = useState(new Map()); - const [messageMap, setMessageMap] = useState(new Map()); - const [activeDownloads, setActiveDownloads] = useState(new Array()); - const [downloadInfoMap, setDownloadInfoMap] = useState(new Map()); const [downloadFormats, setDownloadFormats] = useState(); const [pickedVideoFormat, setPickedVideoFormat] = useState(''); const [pickedAudioFormat, setPickedAudioFormat] = useState(''); @@ -60,6 +54,7 @@ export default function Home({ socket }: Props) { const [url, setUrl] = useState(''); const [workingUrl, setWorkingUrl] = useState(''); + const [showBackdrop, setShowBackdrop] = useState(false); const [showToast, setShowToast] = useState(true); @@ -89,9 +84,9 @@ export default function Home({ socket }: Props) { useEffect(() => { socket.onmessage = (event) => { const res = client.decode(event.data) - if (showBackdrop) { - setShowBackdrop(false) - } + + setShowBackdrop(false) + switch (typeof res.result) { case 'object': setActiveDownloads( @@ -127,6 +122,8 @@ export default function Home({ socket }: Props) { client.download( immediate || url || workingUrl, cliArgs.toString() + toFormatArgs(codes), + availableDownloadPaths[downloadPath] ?? '', + fileNameOverride ) setUrl('') @@ -154,7 +151,6 @@ export default function Home({ socket }: Props) { client.formats(url) ?.then(formats => { - console.log(formats) setDownloadFormats(formats.result) setShowBackdrop(false) resetInput() diff --git a/frontend/src/Settings.tsx b/frontend/src/Settings.tsx index f26590b..6379329 100644 --- a/frontend/src/Settings.tsx +++ b/frontend/src/Settings.tsx @@ -20,8 +20,8 @@ import { import { useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { debounceTime, distinctUntilChanged, map, of, takeWhile } from "rxjs"; -import { Socket } from "socket.io-client"; -import { CliArguments } from "./classes"; +import { CliArguments } from "./features/core/argsParser"; +import I18nBuilder from "./features/core/intl"; import { LanguageUnion, setCliArgs, @@ -34,16 +34,11 @@ import { setTheme, ThemeUnion } from "./features/settings/settingsSlice"; -import { alreadyUpdated, updated } from "./features/status/statusSlice"; -import { I18nBuilder } from "./i18n"; +import { updated } from "./features/status/statusSlice"; import { RootState } from "./stores/store"; import { validateDomain, validateIP } from "./utils"; -type Props = { - socket: WebSocket -} - -export default function Settings({ socket }: Props) { +export default function Settings() { const settings = useSelector((state: RootState) => state.settings) const status = useSelector((state: RootState) => state.status) const dispatch = useDispatch() diff --git a/frontend/src/components/StackableResult.tsx b/frontend/src/components/StackableResult.tsx index 29ee858..097be39 100644 --- a/frontend/src/components/StackableResult.tsx +++ b/frontend/src/components/StackableResult.tsx @@ -1,6 +1,18 @@ import { EightK, FourK, Hd, Sd } from "@mui/icons-material"; -import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Chip, LinearProgress, Skeleton, Stack, Typography } from "@mui/material"; -import { IMessage } from "../interfaces"; +import { + Button, + Card, + CardActionArea, + CardActions, + CardContent, + CardMedia, + Chip, + LinearProgress, + Skeleton, + Stack, + Typography +} from "@mui/material"; +import { useEffect, useState } from "react"; import { ellipsis } from "../utils"; type Props = { @@ -22,6 +34,14 @@ export function StackableResult({ size, stopCallback }: Props) { + const [isCompleted, setIsCompleted] = useState(false) + + useEffect(() => { + if (percentage === '-1') { + setIsCompleted(true) + } + }, [percentage]) + const guessResolution = (xByY: string): any => { if (!xByY) return null; if (xByY.includes('4320')) return (); @@ -31,9 +51,10 @@ export function StackableResult({ return null; } - const percentageToNumber = () => Number(percentage.replace('%', '')) + const percentageToNumber = () => isCompleted ? 100 : Number(percentage.replace('%', '')) - const roundMB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)}MiB` + const roundMiB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)} MiB` + const formatSpeedMiB = (val: number) => `${roundMiB(val)}/s` return ( @@ -54,21 +75,33 @@ export function StackableResult({ } - - {percentage} - {speed} - {roundMB(size ?? 0)} + + {!isCompleted ? percentage : ''} + {!isCompleted ? formatSpeedMiB(speed) : ''} + {roundMiB(size ?? 0)} {guessResolution(resolution)} {percentage ? - : + : null } - diff --git a/frontend/src/components/Statistics.tsx b/frontend/src/components/Statistics.tsx deleted file mode 100644 index ff6b522..0000000 --- a/frontend/src/components/Statistics.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { Line } from "react-chartjs-2"; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, -} from 'chart.js'; -import { on } from "../events"; - -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend -); - -export function Statistics() { - const dataset = new Array(); - const chartRef = useRef(null) - - useEffect(() => { - on('dlSpeed', (data: CustomEvent) => { - dataset.push(data.detail) - chartRef.current.update() - }) - }, []) - - const data = { - labels: dataset.map(() => ''), - datasets: [ - { - data: dataset, - label: 'download speed', - borderColor: 'rgb(53, 162, 235)', - } - ] - } - - return ( -
- -
- ) -} \ No newline at end of file diff --git a/frontend/src/classes.ts b/frontend/src/features/core/argsParser.ts similarity index 100% rename from frontend/src/classes.ts rename to frontend/src/features/core/argsParser.ts diff --git a/frontend/src/events.ts b/frontend/src/features/core/events.ts similarity index 100% rename from frontend/src/events.ts rename to frontend/src/features/core/events.ts diff --git a/frontend/src/i18n.ts b/frontend/src/features/core/intl.ts similarity index 87% rename from frontend/src/i18n.ts rename to frontend/src/features/core/intl.ts index 9152f89..6d6ac9f 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/features/core/intl.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import i18n from "./assets/i18n.yaml"; +import i18n from "../../assets/i18n.yaml"; -export class I18nBuilder { +export default class I18nBuilder { private language: string; private textMap = i18n.languages; diff --git a/frontend/src/rpcClient.ts b/frontend/src/features/core/rpcClient.ts similarity index 86% rename from frontend/src/rpcClient.ts rename to frontend/src/features/core/rpcClient.ts index c75b337..b693ac7 100644 --- a/frontend/src/rpcClient.ts +++ b/frontend/src/features/core/rpcClient.ts @@ -1,7 +1,6 @@ -import type { RPCRequest, RPCResponse } from "./types" -import type { IDLMetadata } from './interfaces' +import type { RPCRequest, RPCResponse, IDLMetadata } from "../../types" -import { getHttpRPCEndpoint } from './utils' +import { getHttpRPCEndpoint } from '../../utils' export class RPCClient { private socket: WebSocket @@ -31,7 +30,7 @@ export class RPCClient { }) } - public download(url: string, args: string) { + public download(url: string, args: string, pathOverride = '', renameTo = '') { if (url) { this.send({ id: this.incrementSeq(), @@ -39,6 +38,8 @@ export class RPCClient { params: [{ URL: url.split("?list").at(0)!, Params: args.split(" ").map(a => a.trim()), + Path: pathOverride, + Rename: renameTo, }] }) } diff --git a/frontend/index.tsx b/frontend/src/index.tsx similarity index 87% rename from frontend/index.tsx rename to frontend/src/index.tsx index 762aa39..be257af 100644 --- a/frontend/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import { App } from './src/App' +import { App } from './App' const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( diff --git a/frontend/src/interfaces.ts b/frontend/src/interfaces.ts deleted file mode 100644 index 032d37d..0000000 --- a/frontend/src/interfaces.ts +++ /dev/null @@ -1,33 +0,0 @@ -export interface IMessage { - status: string, - progress?: string, - size?: number, - dlSpeed?: string - pid: number -} - -export interface IDLMetadata { - formats: Array, - best: IDLFormat, - thumbnail: string, - title: string, -} - -export interface IDLFormat { - format_id: string, - format_note: string, - fps: number, - resolution: string, - vcodec: string, - acodec: string, -} - -export interface IDLMetadataAndPID { - pid: number, - metadata: IDLMetadata -} - -export interface IDLSpeed { - effective: number, - unit: string, -} diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index be65a37..ccb8726 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -6,6 +6,7 @@ export type RPCMethods = | "Service.FreeSpace" | "Service.Formats" | "Service.DirectoryTree" + | "Service.UpdateExecutable" export type RPCRequest = { method: RPCMethods, @@ -41,4 +42,20 @@ export type RPCResult = { export type RPCParams = { URL: string Params?: string +} + +export interface IDLMetadata { + formats: Array, + best: IDLFormat, + thumbnail: string, + title: string, +} + +export interface IDLFormat { + format_id: string, + format_note: string, + fps: number, + resolution: string, + vcodec: string, + acodec: string, } \ No newline at end of file diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 454441b..fe8f138 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -1,5 +1,3 @@ -import { IMessage } from "./interfaces" - /** * Validate an ip v4 via regex * @param {string} ipAddr @@ -65,36 +63,6 @@ export function detectSpeed(str: string): number { } } -/** - * Update a map stored in React State, in this specific impl. all maps have integer keys - * @param k Map key - * @param v Map value - * @param target The target map saved in-state - * @param callback calls React's StateAction function with the newly created Map - * @param remove -optional- is it an update or a deletion operation? - */ -export function updateInStateMap(k: K, v: any, target: Map, callback: Function, remove: boolean = false) { - if (remove) { - const _target = target - _target.delete(k) - callback(new Map(_target)) - return; - } - callback(new Map(target.set(k, v))); -} - -export function updateInStateArray(v: T, target: Array, callback: Function) { } - -/** - * Pre like function - * @param data - * @returns formatted server message - */ -export function buildMessage(data: IMessage) { - return `operation: ${data.status || '...'} \nprogress: ${data.progress || '?'} \nsize: ${data.size || '?'} \nspeed: ${data.dlSpeed || '?'}`; -} - - export function toFormatArgs(codes: string[]): string { if (codes.length > 1) { return codes.reduce((v, a) => ` -f ${v}+${a}`) diff --git a/vite.config.ts b/frontend/vite.config.ts similarity index 76% rename from vite.config.ts rename to frontend/vite.config.ts index 9a0149f..fb6b74a 100644 --- a/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,10 +9,10 @@ export default defineConfig(() => { react(), ViteYaml(), ], - root: resolve(__dirname, 'frontend'), + root: resolve(__dirname, '.'), build: { emptyOutDir: true, - outDir: resolve(__dirname, 'dist', 'frontend'), + outDir: resolve(__dirname, 'dist'), } } }) diff --git a/go.mod b/go.mod index e5d5f39..cd92fe1 100644 --- a/go.mod +++ b/go.mod @@ -23,4 +23,5 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.43.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5a88ba0..d7f4b9d 100644 --- a/go.sum +++ b/go.sum @@ -56,3 +56,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 6de05a8..7a1283d 100644 --- a/main.go +++ b/main.go @@ -3,34 +3,51 @@ package main import ( "context" "embed" + "flag" "io/fs" "log" - "os" "github.com/marcopeocchi/yt-dlp-web-ui/server" + "github.com/marcopeocchi/yt-dlp-web-ui/server/config" ) type ContextKey interface{} var ( - port = os.Getenv("PORT") - //go:embed dist/frontend + port int + downloadPath string + downloaderPath string + configFile string + + //go:embed frontend/dist frontend embed.FS ) func init() { - if port == "" { - port = "3033" - } + flag.IntVar(&port, "port", 3033, "Port where server will listen at") + flag.StringVar(&downloadPath, "out", ".", "Directory where files will be saved") + flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path") + flag.StringVar(&configFile, "conf", "", "yt-dlp-WebUI config file path") + flag.Parse() } func main() { - frontend, err := fs.Sub(frontend, "dist/frontend") + frontend, err := fs.Sub(frontend, "frontend/dist") if err != nil { log.Fatalln(err) } + cfg := config.Instance() + + if configFile != "" { + cfg.LoadFromFile(configFile) + } + + cfg.SetPort(port) + cfg.DownloadPath(downloadPath) + cfg.DownloaderPath(downloaderPath) + ctx := context.Background() ctx = context.WithValue(ctx, ContextKey("port"), port) ctx = context.WithValue(ctx, ContextKey("frontend"), frontend) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 0b0e568..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2209 +0,0 @@ -lockfileVersion: 5.4 - -specifiers: - '@emotion/react': ^11.9.0 - '@emotion/styled': ^11.8.1 - '@koa/cors': ^3.3.0 - '@modyfi/vite-plugin-yaml': ^1.0.2 - '@mui/icons-material': ^5.6.2 - '@mui/material': ^5.6.4 - '@reduxjs/toolkit': ^1.8.1 - '@types/koa': ^2.13.4 - '@types/koa-router': ^7.4.4 - '@types/mime-types': ^2.1.1 - '@types/node': ^17.0.31 - '@types/react': ^18.0.21 - '@types/react-dom': ^18.0.6 - '@types/react-router-dom': ^5.3.3 - '@types/uuid': ^8.3.4 - '@vitejs/plugin-react': ^1.3.2 - buffer: ^6.0.3 - koa: ^2.13.4 - koa-router: ^10.1.1 - koa-static: ^5.0.0 - mime-types: ^2.1.35 - path-browserify: ^1.0.1 - process: ^0.11.10 - radash: ^10.6.0 - react: ^18.2.0 - react-dom: ^18.2.0 - react-redux: ^8.0.1 - react-router-dom: ^6.3.0 - rxjs: ^7.4.0 - socket.io: ^4.3.2 - socket.io-client: ^4.3.2 - typescript: ^4.6.4 - uuid: ^8.3.2 - vite: ^2.9.10 - -dependencies: - '@emotion/react': 11.10.4_iapumuv4e6jcjznwuxpf4tt22e - '@emotion/styled': 11.10.4_g3tud4ene45llglqap74b5kkse - '@koa/cors': 3.4.3 - '@mui/icons-material': 5.10.9_6usjrp3ypnzobhq35dcwvjrt3m - '@mui/material': 5.10.9_ikcgkdnp4bn3rgptamntbhbo7e - '@reduxjs/toolkit': 1.8.6_kuo2ie247izvzll3jejufdtq3q - koa: 2.13.4 - koa-router: 10.1.1 - koa-static: 5.0.0 - mime-types: 2.1.35 - radash: 10.6.0 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-redux: 8.0.4_5uumaiclxbdbzaqafclbf6maf4 - react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y - rxjs: 7.5.7 - socket.io: 4.5.2 - socket.io-client: 4.5.2 - uuid: 8.3.2 - -devDependencies: - '@modyfi/vite-plugin-yaml': 1.0.3_vite@2.9.15 - '@types/koa': 2.13.5 - '@types/koa-router': 7.4.4 - '@types/mime-types': 2.1.1 - '@types/node': 17.0.45 - '@types/react': 18.0.21 - '@types/react-dom': 18.0.6 - '@types/react-router-dom': 5.3.3 - '@types/uuid': 8.3.4 - '@vitejs/plugin-react': 1.3.2 - buffer: 6.0.3 - path-browserify: 1.0.1 - process: 0.11.10 - typescript: 4.8.4 - vite: 2.9.15 - -packages: - - /@ampproject/remapping/2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.17 - dev: true - - /@babel/code-frame/7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - - /@babel/compat-data/7.19.4: - resolution: {integrity: sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/core/7.19.3: - resolution: {integrity: sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.5 - '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.3 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helpers': 7.19.4 - '@babel/parser': 7.19.4 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/generator/7.19.5: - resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - dev: true - - /@babel/helper-annotate-as-pure/7.18.6: - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.3: - resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.4 - '@babel/core': 7.19.3 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.4 - semver: 6.3.0 - dev: true - - /@babel/helper-environment-visitor/7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-function-name/7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.19.4 - dev: true - - /@babel/helper-hoist-variables/7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/helper-module-imports/7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - - /@babel/helper-module-transforms/7.19.0: - resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.19.4 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-plugin-utils/7.19.0: - resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} - engines: {node: '>=6.9.0'} - - /@babel/helper-simple-access/7.19.4: - resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/helper-split-export-declaration/7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/helper-string-parser/7.19.4: - resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier/7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-option/7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helpers/7.19.4: - resolution: {integrity: sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/highlight/7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - - /@babel/parser/7.19.4: - resolution: {integrity: sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/plugin-syntax-jsx/7.18.6: - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/helper-plugin-utils': 7.19.0 - dev: false - - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - - /@babel/plugin-transform-react-jsx-development/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.3 - dev: true - - /@babel/plugin-transform-react-jsx-self/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - - /@babel/plugin-transform-react-jsx-source/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - - /@babel/plugin-transform-react-jsx/7.19.0_@babel+core@7.19.3: - resolution: {integrity: sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.3 - '@babel/types': 7.19.4 - dev: true - - /@babel/runtime/7.19.4: - resolution: {integrity: sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.10 - dev: false - - /@babel/template/7.18.10: - resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 - dev: true - - /@babel/traverse/7.19.4: - resolution: {integrity: sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.5 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/types/7.19.4: - resolution: {integrity: sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - - /@emotion/babel-plugin/11.10.2: - resolution: {integrity: sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.18.6 - '@babel/runtime': 7.19.4 - '@emotion/hash': 0.9.0 - '@emotion/memoize': 0.8.0 - '@emotion/serialize': 1.1.0 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.0.13 - dev: false - - /@emotion/cache/11.10.3: - resolution: {integrity: sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==} - dependencies: - '@emotion/memoize': 0.8.0 - '@emotion/sheet': 1.2.0 - '@emotion/utils': 1.2.0 - '@emotion/weak-memoize': 0.3.0 - stylis: 4.0.13 - dev: false - - /@emotion/hash/0.9.0: - resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} - dev: false - - /@emotion/is-prop-valid/1.2.0: - resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} - dependencies: - '@emotion/memoize': 0.8.0 - dev: false - - /@emotion/memoize/0.8.0: - resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} - dev: false - - /@emotion/react/11.10.4_iapumuv4e6jcjznwuxpf4tt22e: - resolution: {integrity: sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/babel-plugin': 11.10.2 - '@emotion/cache': 11.10.3 - '@emotion/serialize': 1.1.0 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0 - '@emotion/utils': 1.2.0 - '@emotion/weak-memoize': 0.3.0 - '@types/react': 18.0.21 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - dev: false - - /@emotion/serialize/1.1.0: - resolution: {integrity: sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==} - dependencies: - '@emotion/hash': 0.9.0 - '@emotion/memoize': 0.8.0 - '@emotion/unitless': 0.8.0 - '@emotion/utils': 1.2.0 - csstype: 3.1.1 - dev: false - - /@emotion/sheet/1.2.0: - resolution: {integrity: sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==} - dev: false - - /@emotion/styled/11.10.4_g3tud4ene45llglqap74b5kkse: - resolution: {integrity: sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ==} - peerDependencies: - '@babel/core': ^7.0.0 - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/babel-plugin': 11.10.2 - '@emotion/is-prop-valid': 1.2.0 - '@emotion/react': 11.10.4_iapumuv4e6jcjznwuxpf4tt22e - '@emotion/serialize': 1.1.0 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0 - '@emotion/utils': 1.2.0 - '@types/react': 18.0.21 - react: 18.2.0 - dev: false - - /@emotion/unitless/0.8.0: - resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} - dev: false - - /@emotion/use-insertion-effect-with-fallbacks/1.0.0_react@18.2.0: - resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==} - peerDependencies: - react: '>=16.8.0' - dependencies: - react: 18.2.0 - dev: false - - /@emotion/utils/1.2.0: - resolution: {integrity: sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==} - dev: false - - /@emotion/weak-memoize/0.3.0: - resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} - dev: false - - /@esbuild/linux-loong64/0.14.54: - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@jridgewell/gen-mapping/0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /@jridgewell/gen-mapping/0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 - dev: true - - /@jridgewell/resolve-uri/3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/set-array/1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/sourcemap-codec/1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - - /@jridgewell/trace-mapping/0.3.17: - resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /@koa/cors/3.4.3: - resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==} - engines: {node: '>= 8.0.0'} - dependencies: - vary: 1.1.2 - dev: false - - /@modyfi/vite-plugin-yaml/1.0.3_vite@2.9.15: - resolution: {integrity: sha512-UB9A9b5h9v4fruyZPeLQh4k7ZF7KPrOuMnTfSIJD2WYXx9rHE0U19/RzLjFhDzWnTBPZWu6RoT4wxsaDkxXUzw==} - requiresBuild: true - peerDependencies: - vite: ^2.6.0 || ^3.0.0 - dependencies: - '@rollup/pluginutils': 4.2.1 - js-yaml: 4.1.0 - tosource: 2.0.0-alpha.3 - vite: 2.9.15 - dev: true - - /@mui/base/5.0.0-alpha.101_rj7ozvcq3uehdlnj3cbwzbi5ce: - resolution: {integrity: sha512-a54BcXvArGOKUZ2zyS/7B9GNhAGgfomEQSkfEZ88Nc9jKvXA+Mppenfz5o4JCAnD8c4VlePmz9rKOYvvum1bZw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/is-prop-valid': 1.2.0 - '@mui/types': 7.2.0_@types+react@18.0.21 - '@mui/utils': 5.10.9_react@18.2.0 - '@popperjs/core': 2.11.6 - '@types/react': 18.0.21 - clsx: 1.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/core-downloads-tracker/5.10.9: - resolution: {integrity: sha512-rqoFu4qww6KJBbXYhyRd9YXjwBHa3ylnBPSWbGf1bdfG0AYMKmVzg8zxkWvxAWOp97kvx3M2kNPb0xMIDZiogQ==} - dev: false - - /@mui/icons-material/5.10.9_6usjrp3ypnzobhq35dcwvjrt3m: - resolution: {integrity: sha512-sqClXdEM39WKQJOQ0ZCPTptaZgqwibhj2EFV9N0v7BU1PO8y4OcX/a2wIQHn4fNuDjIZktJIBrmU23h7aqlGgg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@mui/material': ^5.0.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@mui/material': 5.10.9_ikcgkdnp4bn3rgptamntbhbo7e - '@types/react': 18.0.21 - react: 18.2.0 - dev: false - - /@mui/material/5.10.9_ikcgkdnp4bn3rgptamntbhbo7e: - resolution: {integrity: sha512-sdOzlgpCmyw48je+E7o9UGGJpgBaF+60FlTRpVpcd/z+LUhnuzzuis891yPI5dPPXLBDL/bO4SsGg51lgNeLBw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/react': 11.10.4_iapumuv4e6jcjznwuxpf4tt22e - '@emotion/styled': 11.10.4_g3tud4ene45llglqap74b5kkse - '@mui/base': 5.0.0-alpha.101_rj7ozvcq3uehdlnj3cbwzbi5ce - '@mui/core-downloads-tracker': 5.10.9 - '@mui/system': 5.10.9_h33l6npc22g7vcra72cibfsrvm - '@mui/types': 7.2.0_@types+react@18.0.21 - '@mui/utils': 5.10.9_react@18.2.0 - '@types/react': 18.0.21 - '@types/react-transition-group': 4.4.5 - clsx: 1.2.1 - csstype: 3.1.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-is: 18.2.0 - react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y - dev: false - - /@mui/private-theming/5.10.9_iapumuv4e6jcjznwuxpf4tt22e: - resolution: {integrity: sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@mui/utils': 5.10.9_react@18.2.0 - '@types/react': 18.0.21 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/styled-engine/5.10.8_hfzxdiydbrbhhfpkwuv3jhvwmq: - resolution: {integrity: sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/cache': 11.10.3 - '@emotion/react': 11.10.4_iapumuv4e6jcjznwuxpf4tt22e - '@emotion/styled': 11.10.4_g3tud4ene45llglqap74b5kkse - csstype: 3.1.1 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/system/5.10.9_h33l6npc22g7vcra72cibfsrvm: - resolution: {integrity: sha512-B6fFC0sK06hNmqY7fAUfwShQv594+u/DT1YEFHPtK4laouTu7V4vSGQWi1WJT9Bjs9Db5D1bRDJ+Yy+tc3QOYA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@emotion/react': 11.10.4_iapumuv4e6jcjznwuxpf4tt22e - '@emotion/styled': 11.10.4_g3tud4ene45llglqap74b5kkse - '@mui/private-theming': 5.10.9_iapumuv4e6jcjznwuxpf4tt22e - '@mui/styled-engine': 5.10.8_hfzxdiydbrbhhfpkwuv3jhvwmq - '@mui/types': 7.2.0_@types+react@18.0.21 - '@mui/utils': 5.10.9_react@18.2.0 - '@types/react': 18.0.21 - clsx: 1.2.1 - csstype: 3.1.1 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/types/7.2.0_@types+react@18.0.21: - resolution: {integrity: sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==} - peerDependencies: - '@types/react': '*' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.0.21 - dev: false - - /@mui/utils/5.10.9_react@18.2.0: - resolution: {integrity: sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.19.4 - '@types/prop-types': 15.7.5 - '@types/react-is': 17.0.3 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@popperjs/core/2.11.6: - resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} - dev: false - - /@reduxjs/toolkit/1.8.6_kuo2ie247izvzll3jejufdtq3q: - resolution: {integrity: sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - dependencies: - immer: 9.0.15 - react: 18.2.0 - react-redux: 8.0.4_5uumaiclxbdbzaqafclbf6maf4 - redux: 4.2.0 - redux-thunk: 2.4.1_redux@4.2.0 - reselect: 4.1.6 - dev: false - - /@remix-run/router/1.0.2: - resolution: {integrity: sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==} - engines: {node: '>=14'} - dev: false - - /@rollup/pluginutils/4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true - - /@socket.io/component-emitter/3.1.0: - resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} - dev: false - - /@types/accepts/1.3.5: - resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} - dependencies: - '@types/node': 17.0.45 - dev: true - - /@types/body-parser/1.19.2: - resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} - dependencies: - '@types/connect': 3.4.35 - '@types/node': 17.0.45 - dev: true - - /@types/connect/3.4.35: - resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} - dependencies: - '@types/node': 17.0.45 - dev: true - - /@types/content-disposition/0.5.5: - resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==} - dev: true - - /@types/cookie/0.4.1: - resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - dev: false - - /@types/cookies/0.7.7: - resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} - dependencies: - '@types/connect': 3.4.35 - '@types/express': 4.17.14 - '@types/keygrip': 1.0.2 - '@types/node': 17.0.45 - dev: true - - /@types/cors/2.8.12: - resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} - dev: false - - /@types/express-serve-static-core/4.17.31: - resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} - dependencies: - '@types/node': 17.0.45 - '@types/qs': 6.9.7 - '@types/range-parser': 1.2.4 - dev: true - - /@types/express/4.17.14: - resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} - dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.31 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.0 - dev: true - - /@types/history/4.7.11: - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - dev: true - - /@types/hoist-non-react-statics/3.3.1: - resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} - dependencies: - '@types/react': 18.0.21 - hoist-non-react-statics: 3.3.2 - dev: false - - /@types/http-assert/1.5.3: - resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} - dev: true - - /@types/http-errors/1.8.2: - resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} - dev: true - - /@types/keygrip/1.0.2: - resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} - dev: true - - /@types/koa-compose/3.2.5: - resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} - dependencies: - '@types/koa': 2.13.5 - dev: true - - /@types/koa-router/7.4.4: - resolution: {integrity: sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==} - dependencies: - '@types/koa': 2.13.5 - dev: true - - /@types/koa/2.13.5: - resolution: {integrity: sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA==} - dependencies: - '@types/accepts': 1.3.5 - '@types/content-disposition': 0.5.5 - '@types/cookies': 0.7.7 - '@types/http-assert': 1.5.3 - '@types/http-errors': 1.8.2 - '@types/keygrip': 1.0.2 - '@types/koa-compose': 3.2.5 - '@types/node': 17.0.45 - dev: true - - /@types/mime-types/2.1.1: - resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==} - dev: true - - /@types/mime/3.0.1: - resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} - dev: true - - /@types/node/17.0.45: - resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - - /@types/parse-json/4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} - dev: false - - /@types/prop-types/15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - - /@types/qs/6.9.7: - resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - dev: true - - /@types/range-parser/1.2.4: - resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - dev: true - - /@types/react-dom/18.0.6: - resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} - dependencies: - '@types/react': 18.0.21 - - /@types/react-is/17.0.3: - resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} - dependencies: - '@types/react': 18.0.21 - dev: false - - /@types/react-router-dom/5.3.3: - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} - dependencies: - '@types/history': 4.7.11 - '@types/react': 18.0.21 - '@types/react-router': 5.1.19 - dev: true - - /@types/react-router/5.1.19: - resolution: {integrity: sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==} - dependencies: - '@types/history': 4.7.11 - '@types/react': 18.0.21 - dev: true - - /@types/react-transition-group/4.4.5: - resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} - dependencies: - '@types/react': 18.0.21 - dev: false - - /@types/react/18.0.21: - resolution: {integrity: sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.2 - csstype: 3.1.1 - - /@types/scheduler/0.16.2: - resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} - - /@types/serve-static/1.15.0: - resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} - dependencies: - '@types/mime': 3.0.1 - '@types/node': 17.0.45 - dev: true - - /@types/use-sync-external-store/0.0.3: - resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} - dev: false - - /@types/uuid/8.3.4: - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} - dev: true - - /@vitejs/plugin-react/1.3.2: - resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==} - engines: {node: '>=12.0.0'} - dependencies: - '@babel/core': 7.19.3 - '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.3 - '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-react-jsx-self': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-react-jsx-source': 7.18.6_@babel+core@7.19.3 - '@rollup/pluginutils': 4.2.1 - react-refresh: 0.13.0 - resolve: 1.22.1 - transitivePeerDependencies: - - supports-color - dev: true - - /accepts/1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - dev: false - - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - - /argparse/2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /babel-plugin-macros/3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - dependencies: - '@babel/runtime': 7.19.4 - cosmiconfig: 7.0.1 - resolve: 1.22.1 - dev: false - - /base64-js/1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true - - /base64id/2.0.0: - resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} - engines: {node: ^4.5.0 || >= 5.9} - dev: false - - /browserslist/4.21.4: - resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001419 - electron-to-chromium: 1.4.282 - node-releases: 2.0.6 - update-browserslist-db: 1.0.10_browserslist@4.21.4 - dev: true - - /buffer/6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - - /cache-content-type/1.0.1: - resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} - engines: {node: '>= 6.0.0'} - dependencies: - mime-types: 2.1.35 - ylru: 1.3.2 - dev: false - - /callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: false - - /caniuse-lite/1.0.30001419: - resolution: {integrity: sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==} - dev: true - - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - - /clsx/1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false - - /co/4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: false - - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - - /color-name/1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - - /content-disposition/0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - dependencies: - safe-buffer: 5.2.1 - dev: false - - /content-type/1.0.4: - resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} - engines: {node: '>= 0.6'} - dev: false - - /convert-source-map/1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - /cookie/0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - dev: false - - /cookies/0.8.0: - resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} - engines: {node: '>= 0.8'} - dependencies: - depd: 2.0.0 - keygrip: 1.1.0 - dev: false - - /cors/2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - dev: false - - /cosmiconfig/7.0.1: - resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} - engines: {node: '>=10'} - dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - dev: false - - /csstype/3.1.1: - resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} - - /debug/3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: false - - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - - /deep-equal/1.0.1: - resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} - dev: false - - /delegates/1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dev: false - - /depd/1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - dev: false - - /depd/2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dev: false - - /destroy/1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dev: false - - /dom-helpers/5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dependencies: - '@babel/runtime': 7.19.4 - csstype: 3.1.1 - dev: false - - /ee-first/1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: false - - /electron-to-chromium/1.4.282: - resolution: {integrity: sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==} - dev: true - - /encodeurl/1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - dev: false - - /engine.io-client/6.2.3: - resolution: {integrity: sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - engine.io-parser: 5.0.4 - ws: 8.2.3 - xmlhttprequest-ssl: 2.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /engine.io-parser/5.0.4: - resolution: {integrity: sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==} - engines: {node: '>=10.0.0'} - dev: false - - /engine.io/6.2.0: - resolution: {integrity: sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==} - engines: {node: '>=10.0.0'} - dependencies: - '@types/cookie': 0.4.1 - '@types/cors': 2.8.12 - '@types/node': 17.0.45 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.4.2 - cors: 2.8.5 - debug: 4.3.4 - engine.io-parser: 5.0.4 - ws: 8.2.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /error-ex/1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - dev: false - - /esbuild-android-64/0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild/0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: true - - /escape-html/1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false - - /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - /escape-string-regexp/4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: false - - /estree-walker/2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - - /find-root/1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: false - - /fresh/0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - dev: false - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - - /gensync/1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true - - /globals/11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true - - /has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - /has-symbols/1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: false - - /has-tostringtag/1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: false - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - - /hoist-non-react-statics/3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - dependencies: - react-is: 16.13.1 - dev: false - - /http-assert/1.5.0: - resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} - engines: {node: '>= 0.8'} - dependencies: - deep-equal: 1.0.1 - http-errors: 1.8.1 - dev: false - - /http-errors/1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} - engines: {node: '>= 0.6'} - dependencies: - depd: 1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 - statuses: 1.5.0 - dev: false - - /http-errors/1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - dev: false - - /ieee754/1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true - - /immer/9.0.15: - resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==} - dev: false - - /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: false - - /inherits/2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - dev: false - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - - /is-arrayish/0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: false - - /is-core-module/2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} - dependencies: - has: 1.0.3 - - /is-generator-function/1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: false - - /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - /js-yaml/4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /jsesc/2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /json-parse-even-better-errors/2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: false - - /json5/2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} - engines: {node: '>=6'} - hasBin: true - dev: true - - /keygrip/1.1.0: - resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} - engines: {node: '>= 0.6'} - dependencies: - tsscmp: 1.0.6 - dev: false - - /koa-compose/4.1.0: - resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - dev: false - - /koa-convert/2.0.0: - resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} - engines: {node: '>= 10'} - dependencies: - co: 4.6.0 - koa-compose: 4.1.0 - dev: false - - /koa-router/10.1.1: - resolution: {integrity: sha512-z/OzxVjf5NyuNO3t9nJpx7e1oR3FSBAauiwXtMQu4ppcnuNZzTaQ4p21P8A6r2Es8uJJM339oc4oVW+qX7SqnQ==} - engines: {node: '>= 8.0.0'} - dependencies: - debug: 4.3.4 - http-errors: 1.8.1 - koa-compose: 4.1.0 - methods: 1.1.2 - path-to-regexp: 6.2.1 - transitivePeerDependencies: - - supports-color - dev: false - - /koa-send/5.0.1: - resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} - engines: {node: '>= 8'} - dependencies: - debug: 4.3.4 - http-errors: 1.8.1 - resolve-path: 1.4.0 - transitivePeerDependencies: - - supports-color - dev: false - - /koa-static/5.0.0: - resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} - engines: {node: '>= 7.6.0'} - dependencies: - debug: 3.2.7 - koa-send: 5.0.1 - transitivePeerDependencies: - - supports-color - dev: false - - /koa/2.13.4: - resolution: {integrity: sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==} - engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} - dependencies: - accepts: 1.3.8 - cache-content-type: 1.0.1 - content-disposition: 0.5.4 - content-type: 1.0.4 - cookies: 0.8.0 - debug: 4.3.4 - delegates: 1.0.0 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - fresh: 0.5.2 - http-assert: 1.5.0 - http-errors: 1.8.1 - is-generator-function: 1.0.10 - koa-compose: 4.1.0 - koa-convert: 2.0.0 - on-finished: 2.4.1 - only: 0.0.2 - parseurl: 1.3.3 - statuses: 1.5.0 - type-is: 1.6.18 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - dev: false - - /lines-and-columns/1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false - - /loose-envify/1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - - /media-typer/0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - dev: false - - /methods/1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - dev: false - - /mime-db/1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false - - /mime-types/2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: false - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - /ms/2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: false - - /nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /negotiator/0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - dev: false - - /node-releases/2.0.6: - resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} - dev: true - - /object-assign/4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - - /on-finished/2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - dependencies: - ee-first: 1.1.1 - dev: false - - /only/0.0.2: - resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} - dev: false - - /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: false - - /parse-json/5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.18.6 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - dev: false - - /parseurl/1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - dev: false - - /path-browserify/1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - dev: true - - /path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: false - - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - /path-to-regexp/6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: false - - /path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: false - - /picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /postcss/8.4.18: - resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /process/0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - dev: true - - /prop-types/15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - dev: false - - /radash/10.6.0: - resolution: {integrity: sha512-L0PD+kBVaPGxn0UO9yVLJUKUkuu7bLqroZbieecPUGuSEtByCtMedDSyw+arA8pnLtZduYTgHnMjRfN90gozpQ==} - engines: {node: '>=14.18.0'} - dev: false - - /react-dom/18.2.0_react@18.2.0: - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - - /react-is/16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: false - - /react-is/18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: false - - /react-redux/8.0.4_5uumaiclxbdbzaqafclbf6maf4: - resolution: {integrity: sha512-yMfQ7mX6bWuicz2fids6cR1YT59VTuT8MKyyE310wJQlINKENCeT1UcPdEiX6znI5tF8zXyJ/VYvDgeGuaaNwQ==} - peerDependencies: - '@types/react': ^16.8 || ^17.0 || ^18.0 - '@types/react-dom': ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: '>=0.59' - redux: ^4 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - react-dom: - optional: true - react-native: - optional: true - redux: - optional: true - dependencies: - '@babel/runtime': 7.19.4 - '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.0.21 - '@types/react-dom': 18.0.6 - '@types/use-sync-external-store': 0.0.3 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-is: 18.2.0 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - - /react-refresh/0.13.0: - resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==} - engines: {node: '>=0.10.0'} - dev: true - - /react-router-dom/6.4.2_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.0.2 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-router: 6.4.2_react@18.2.0 - dev: false - - /react-router/6.4.2_react@18.2.0: - resolution: {integrity: sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - dependencies: - '@remix-run/router': 1.0.2 - react: 18.2.0 - dev: false - - /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - dependencies: - '@babel/runtime': 7.19.4 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - dev: false - - /react/18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - - /redux-thunk/2.4.1_redux@4.2.0: - resolution: {integrity: sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==} - peerDependencies: - redux: ^4 - dependencies: - redux: 4.2.0 - dev: false - - /redux/4.2.0: - resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} - dependencies: - '@babel/runtime': 7.19.4 - dev: false - - /regenerator-runtime/0.13.10: - resolution: {integrity: sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==} - dev: false - - /reselect/4.1.6: - resolution: {integrity: sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==} - dev: false - - /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: false - - /resolve-path/1.4.0: - resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} - engines: {node: '>= 0.8'} - dependencies: - http-errors: 1.6.3 - path-is-absolute: 1.0.1 - dev: false - - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.10.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - /rollup/2.77.3: - resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /rxjs/7.5.7: - resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==} - dependencies: - tslib: 2.4.0 - dev: false - - /safe-buffer/5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - - /scheduler/0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - - /setprototypeof/1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - dev: false - - /setprototypeof/1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - dev: false - - /socket.io-adapter/2.4.0: - resolution: {integrity: sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==} - dev: false - - /socket.io-client/4.5.2: - resolution: {integrity: sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg==} - engines: {node: '>=10.0.0'} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - engine.io-client: 6.2.3 - socket.io-parser: 4.2.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /socket.io-parser/4.2.1: - resolution: {integrity: sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==} - engines: {node: '>=10.0.0'} - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - - /socket.io/4.5.2: - resolution: {integrity: sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==} - engines: {node: '>=10.0.0'} - dependencies: - accepts: 1.3.8 - base64id: 2.0.0 - debug: 4.3.4 - engine.io: 6.2.0 - socket.io-adapter: 2.4.0 - socket.io-parser: 4.2.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - - /source-map/0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: false - - /statuses/1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} - dev: false - - /stylis/4.0.13: - resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==} - dev: false - - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - /to-fast-properties/2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - - /toidentifier/1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - dev: false - - /tosource/2.0.0-alpha.3: - resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==} - engines: {node: '>=10'} - dev: true - - /tslib/2.4.0: - resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - dev: false - - /tsscmp/1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - dev: false - - /type-is/1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - dev: false - - /typescript/4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /update-browserslist-db/1.0.10_browserslist@4.21.4: - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.4 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - - /use-sync-external-store/1.2.0_react@18.2.0: - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /uuid/8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: false - - /vary/1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - dev: false - - /vite/2.9.15: - resolution: {integrity: sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ==} - engines: {node: '>=12.2.0'} - hasBin: true - peerDependencies: - less: '*' - sass: '*' - stylus: '*' - peerDependenciesMeta: - less: - optional: true - sass: - optional: true - stylus: - optional: true - dependencies: - esbuild: 0.14.54 - postcss: 8.4.18 - resolve: 1.22.1 - rollup: 2.77.3 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /ws/8.2.3: - resolution: {integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - - /xmlhttprequest-ssl/2.0.0: - resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} - engines: {node: '>=0.4.0'} - dev: false - - /yaml/1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: false - - /ylru/1.3.2: - resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} - engines: {node: '>= 4.0.0'} - dev: false diff --git a/server-node/src/core/HTTPServer.ts b/server-node/src/core/HTTPServer.ts deleted file mode 100644 index cc05960..0000000 --- a/server-node/src/core/HTTPServer.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { createServer, Server } from 'http'; -import { parse as urlParse } from 'url'; -import { open, close, readFile, fstat } from 'fs'; -import { parse, join } from 'path'; - -namespace server { - export const mimes = { - '.html': 'text/html', - '.ico': 'image/x-icon', - '.js': 'text/javascript', - '.json': 'application/json', - '.css': 'text/css', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.webp': 'image/webp', - }; -} - -class Jean { - private workingDir: string; - - /** - * Jean static file server its only purpose is serving SPA and images - * with the lowest impact possible. - * @param workingDir sets the root directory automatically trying index.html - * If specified the file in addition to the directory it will serve the - * file directly. - * *e.g* new Jean(path.join(__dirname, 'dist')) will try - * index.html from the dist directory; - * @author me :D - */ - - constructor(workingDir: string) { - this.workingDir = workingDir; - } - - /** - * Create a static file server - * @returns an instance of a standard NodeJS http.Server - */ - public createServer(): Server { - return createServer((req, res) => { - // parse the current given url - const parsedUrl = urlParse(req.url, false) - // extract the pathname and guard it with the working dir - let pathname = join(this.workingDir, `.${parsedUrl.pathname}`); - // extract the file extension - const ext = parse(pathname).ext; - - // open the file or directory and fetch its descriptor - open(pathname, 'r', (err, fd) => { - // whoops, not found, send a 404 - if (err) { - res.statusCode = 404; - res.end(`File ${pathname} not found!`); - return; - } - // something's gone wrong it's not a file or a directory - fstat(fd, (err, stat) => { - if (err) { - res.statusCode = 500; - res.end(err); - } - // try file index.html - if (stat.isDirectory()) { - pathname = join(pathname, 'index.html') - } - // read the file - readFile(pathname, (err, data) => { - if (err) { - res.statusCode = 500; - res.end(`Error reading the file: ${err}`); - } else { - // infer it's extension otherwise it's the index.html - res.setHeader('Content-type', server.mimes[ext] || 'text/html'); - res.end(data); - close(fd); - } - }); - }) - }); - }) - } -} - -export default Jean; \ No newline at end of file diff --git a/server-node/src/core/Process.ts b/server-node/src/core/Process.ts deleted file mode 100644 index c14914f..0000000 --- a/server-node/src/core/Process.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { spawn } from 'child_process'; -import { join } from 'path'; -import { Readable } from 'stream'; -import { ISettings } from '../interfaces/ISettings'; -import { availableParams } from '../utils/params'; -import Logger from '../utils/BetterLogger'; -import { IDownloadFormat, IDownloadMetadata } from '../interfaces/IDownloadMetadata'; - -const log = Logger.instance; - -/** - * Represents a download process that spawns yt-dlp. - * @param url - The downlaod url. - * @param params - The cli arguments passed by the frontend. - * @param settings - The download settings passed by the frontend. - */ - -class Process { - public readonly url: string; - public readonly params: Array; - private settings: ISettings; - private stdout: Readable; - private pid: number; - private metadata?: IDownloadMetadata; - private exePath = join(__dirname, 'yt-dlp'); - private customFileName?: string; - - private readonly template = `download: - { - "eta":%(progress.eta)s, - "percentage":"%(progress._percent_str)s", - "speed":"%(progress._speed_str)s", - "size":%(info.filesize_approx)s - }` - .replace(/\s\s+/g, ' ') - .replace('\n', ''); - - constructor(url: string, params: Array, settings: any, customFileName?: string) { - this.url = url; - this.params = params || []; - this.settings = settings - this.stdout = undefined; - this.pid = undefined; - this.metadata = undefined; - this.customFileName = customFileName; - } - - /** - * function that launch the download process, sets the stdout property and the pid - * @param callback not yet implemented - * @returns the process instance - */ - public async start(callback?: Function): Promise { - const sanitizedParams = this.params.filter((param: string) => availableParams.includes(param)); - - if (this.settings?.download_path) { - if (this.settings.download_path.charAt(this.settings.download_path.length - 1) !== '/') { - this.settings.download_path = `${this.settings.download_path}/` - } - } - - const ytldp = spawn(this.exePath, - [ - '-o', `${this.settings?.download_path || 'downloads/'}${this.customFileName || '%(title)s'}.%(ext)s`, - '--progress-template', this.template, - '--no-colors', - ] - .concat(sanitizedParams) - .concat((this.settings?.cliArgs ?? []).map(arg => arg.split(' ')).flat()) - .concat([this.url]) - ); - - this.pid = ytldp.pid; - this.stdout = ytldp.stdout; - - log.info('proc', `Spawned a new process, pid: ${this.pid}`) - - if (callback) { - callback() - } - - return this; - } - - /** - * function used internally by the download process to fetch information, usually thumbnail and title - * @returns Promise to the lock - */ - public getMetadata(): Promise { - if (!this.metadata) { - let stdoutChunks = []; - const ytdlpInfo = spawn(this.exePath, ['-J', this.url]); - - ytdlpInfo.stdout.on('data', (data) => { - stdoutChunks.push(data); - }); - - return new Promise((resolve, reject) => { - ytdlpInfo.on('exit', () => { - try { - const buffer = Buffer.concat(stdoutChunks); - const json = JSON.parse(buffer.toString()); - const info = { - formats: json.formats.map((format: IDownloadFormat) => { - return { - format_id: format.format_id ?? '', - format_note: format.format_note ?? '', - fps: format.fps ?? '', - resolution: format.resolution ?? '', - vcodec: format.vcodec ?? '', - acodec: format.acodec ?? '', - } - }).filter((format: IDownloadFormat) => format.format_note !== 'storyboard'), - best: { - format_id: json.format_id ?? '', - format_note: json.format_note ?? '', - fps: json.fps ?? '', - resolution: json.resolution ?? '', - vcodec: json.vcodec ?? '', - acodec: json.acodec ?? '', - }, - thumbnail: json.thumbnail, - title: json.title, - } - resolve(info); - this.metadata = info; - - } catch (e) { - reject('failed fetching formats, downloading best available'); - } - }); - }) - } - return new Promise((resolve) => { resolve(this.metadata!) }); - } - - /** - * function that kills the current process - */ - async kill() { - spawn('kill', [String(this.pid)]).on('exit', () => { - log.info('proc', `Stopped ${this.pid} because SIGKILL`) - }); - } - - /** - * pid getter function - * @returns {number} pid - */ - getPid(): number { - if (!this.pid) { - throw "Process isn't started" - } - return this.pid; - } - - /** - * stdout getter function - * @returns {Readable} stdout as stream - */ - getStdout(): Readable { - return this.stdout - } -} - -export default Process; \ No newline at end of file diff --git a/server-node/src/core/downloadArchive.ts b/server-node/src/core/downloadArchive.ts deleted file mode 100644 index af5ce1c..0000000 --- a/server-node/src/core/downloadArchive.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { resolve as pathResolve } from "path"; -import { readdir } from "fs"; -import { ISettings } from "../interfaces/ISettings"; -import Logger from "../utils/BetterLogger"; - -let settings: ISettings; -const log = Logger.instance; - -try { - settings = require('../../settings.json'); -} catch (e) { - log.warn('dl', 'settings.json not found'); -} - -export function listDownloaded(ctx: any) { - return new Promise((resolve, reject) => { - readdir(pathResolve(settings.download_path || 'download'), (err, files) => { - if (err) { - reject({ err: true }) - return - } - ctx.body = files.map(file => { - resolve({ - filename: file, - path: pathResolve(file), - }) - }) - }) - }) -} diff --git a/server-node/src/core/downloader.ts b/server-node/src/core/downloader.ts deleted file mode 100644 index dd1d53b..0000000 --- a/server-node/src/core/downloader.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { spawn } from 'child_process'; -import { from, interval } from 'rxjs'; -import { map, throttle } from 'rxjs/operators'; -import { Socket } from 'socket.io'; -import MemoryDB from '../db/memoryDB'; -import { IPayload } from '../interfaces/IPayload'; -import { ISettings } from '../interfaces/ISettings'; -import { CLIProgress } from '../types'; -import Logger from '../utils/BetterLogger'; -import Process from './Process'; -import { states } from './states'; - -// settings read from settings.json -let settings: ISettings; -const log = Logger.instance; - -const mem_db = new MemoryDB(); - -try { - settings = require('../../settings.json'); -} -catch (e) { - new Promise(resolve => setTimeout(resolve, 500)) - .then(() => log.warn('dl', 'settings.json not found, ignore if using Docker')); -} -/** - * Get download info such as thumbnail, title, resolution and list all formats - * @param socket - * @param url - */ -export async function getFormatsAndMetadata(socket: Socket, url: string) { - let p = new Process(url, [], settings); - try { - const formats = await p.getMetadata(); - socket.emit('available-formats', formats) - } catch (e) { - log.warn('dl', e) - socket.emit('progress', { - status: states.PROG_DONE, - pid: -1, - }); - } finally { - p = null; - } -} - -/** - * Invoke a new download. - * Called by the websocket messages listener. - * @param {Socket} socket current connection socket - * @param {object} payload frontend download payload - * @returns - */ -export async function download(socket: Socket, payload: IPayload) { - if (!payload || payload.url === '' || payload.url === null) { - socket.emit('progress', { status: states.PROG_DONE }); - return; - } - - const url = payload.url; - const params = typeof payload.params !== 'object' ? - payload.params.split(' ') : - payload.params; - - const renameTo = payload.renameTo - - const scopedSettings: ISettings = { - ...settings, - download_path: payload.path - } - - let p = new Process(url, params, scopedSettings, renameTo); - - p.start().then(downloader => { - mem_db.add(downloader) - displayDownloadMetadata(downloader, socket); - streamProcess(downloader, socket); - }); - - // GC - p = null; -} - -/** - * Send via websocket download info "chunk" - * @param process - * @param socket - */ -function displayDownloadMetadata(process: Process, socket: Socket) { - process.getMetadata() - .then(metadata => { - socket.emit('metadata', { - pid: process.getPid(), - metadata: metadata, - }); - }) - .catch((e) => { - socket.emit('progress', { - status: states.PROG_DONE, - pid: process.getPid(), - }); - log.warn('dl', e) - }) -} - -/** - * Stream via websocket download stdoud "chunks" - * @param process - * @param socket - */ -function streamProcess(process: Process, socket: Socket) { - const emitAbort = () => { - socket.emit('progress', { - status: states.PROG_DONE, - pid: process.getPid(), - }); - } - - from(process.getStdout().removeAllListeners()) // stdout as observable - .pipe( - throttle(() => interval(500)), // discard events closer than 500ms - map(stdout => formatter(String(stdout), process.getPid())) - ) - .subscribe({ - next: (stdout) => { - socket.emit('progress', stdout) - }, - complete: () => { - process.kill().then(() => { - emitAbort(); - mem_db.remove(process); - }); - }, - error: () => { - emitAbort(); - mem_db.remove(process); - } - }); -} - -/** - * Retrieve all downloads. - * If the server has just been launched retrieve the ones saved to the database. - * If the server is running fetches them from the process pool. - * @param {Socket} socket current connection socket - * @returns - */ -export async function retrieveDownload(socket: Socket) { - // it's a cold restart: the server has just been started with pending - // downloads, so fetch them from the database and resume. - - // if (coldRestart) { - // coldRestart = false; - // let downloads = []; - // // sanitize - // downloads = [...new Set(downloads.filter(el => el !== undefined))]; - // log.info('dl', `Cold restart, retrieving ${downloads.length} jobs`) - // for (const entry of downloads) { - // if (entry) { - // await download(socket, entry); - // } - // } - // return; - // } - - // it's an hot-reload the server it's running and the frontend ask for - // the pending job: retrieve them from the "in-memory database" (ProcessPool) - - const _poolSize = mem_db.size() - log.info('dl', `Retrieving ${_poolSize} jobs from pool`) - socket.emit('pending-jobs', _poolSize) - - const it = mem_db.iterator(); - - // resume the jobs - for (const entry of it) { - const [, process] = entry - displayDownloadMetadata(process, socket); - streamProcess(process, socket); - } -} - -/** - * Abort a specific download if pid is provided, in the other case - * calls the abortAllDownloads function - * @see abortAllDownloads - * @param {Socket} socket currenct connection socket - * @param {*} args args sent by the frontend. MUST contain the PID. - * @returns - */ -export function abortDownload(socket: Socket, args: any) { - if (!args) { - abortAllDownloads(socket); - return; - } - const { pid } = args; - - spawn('kill', [pid]) - .on('exit', () => { - socket.emit('progress', { - status: states.PROC_ABORT, - process: pid, - }); - log.warn('dl', `Aborting download ${pid}`); - }); -} - -/** - * Unconditionally kills all yt-dlp process. - * @param {Socket} socket currenct connection socket - */ -export function abortAllDownloads(socket: Socket) { - spawn('killall', ['yt-dlp']) - .on('exit', () => { - socket.emit('progress', { status: states.PROC_ABORT }); - log.info('dl', 'Aborting downloads'); - }); - mem_db.flush(); -} - -/** - * Get pool current size - */ -export function getQueueSize(): number { - return mem_db.size(); -} - -/** - * @private Formats the yt-dlp stdout to a frontend-readable format - * @param {string} stdout stdout as string - * @param {number} pid current process id relative to stdout - * @returns - */ -const formatter = (stdout: string, pid: number) => { - try { - const p: CLIProgress = JSON.parse(stdout); - if (p) { - return { - status: states.PROC_DOWNLOAD, - progress: p.percentage, - size: p.size, - dlSpeed: p.speed, - pid: pid, - } - } - } catch (e) { - return { - progress: 0, - } - } -} diff --git a/server-node/src/core/states.ts b/server-node/src/core/states.ts deleted file mode 100644 index 788786c..0000000 --- a/server-node/src/core/states.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Possible server states map - */ -export const states = { - PROC_DOWNLOAD: 'download', - PROC_MERGING: 'merging', - PROC_ABORT: 'abort', - PROG_DONE: 'status_done', -} \ No newline at end of file diff --git a/server-node/src/core/streamer.ts b/server-node/src/core/streamer.ts deleted file mode 100644 index 3ee2448..0000000 --- a/server-node/src/core/streamer.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { stat, createReadStream } from 'fs'; -import { lookup } from 'mime-types'; - -export function streamer(ctx: any, next: any) { - const filepath = '' - stat(filepath, (err, stat) => { - if (err) { - ctx.response.status = 404; - ctx.body = { err: 'resource not found' }; - next(); - } - const fileSize = stat.size; - const range = ctx.headers.range; - if (range) { - const parts = range.replace(/bytes=/, '').split('-'); - const start = parseInt(parts[0], 10); - const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; - const chunksize = end - start + 1; - const file = createReadStream(filepath, { start, end }); - const head = { - 'Content-Range': `bytes ${start}-${end}/${fileSize}`, - 'Accept-Ranges': 'bytes', - 'Content-Length': chunksize, - 'Content-Type': lookup(filepath) - }; - ctx.res.writeHead(206, head); - file.pipe(ctx.res); - next(); - } else { - const head = { - 'Content-Length': fileSize, - 'Content-Type': 'video/mp4' - }; - ctx.res.writeHead(200, head); - createReadStream(ctx.params.filepath).pipe(ctx.res); - next(); - } - }); -} \ No newline at end of file diff --git a/server-node/src/db/memoryDB.ts b/server-node/src/db/memoryDB.ts deleted file mode 100644 index 4e775f8..0000000 --- a/server-node/src/db/memoryDB.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Represents a download process that spawns yt-dlp. - */ - -import Process from "../core/Process"; - -class MemoryDB { - private _pool: Map - private _size: number - - constructor() { - this.init() - } - - private init() { - this._pool = new Map() - this._size = 0 - } - - /** - * Pool size getter - * @returns {number} pool's size - */ - size(): number { - return this._size - } - - /** - * Add a process to the pool - * @param {Process} process - */ - add(process: Process) { - this._pool.set(process.getPid(), process) - this._size++ - } - - /** - * Delete a process from the pool - * @param {Process} process - */ - remove(process: Process) { - if (this._size === 0) return - this._pool.delete(process.getPid()) - this._size-- - } - - /** - * Delete a process from the pool by its pid - * @param {number} pid - */ - removeByPid(pid: number) { - this._pool.delete(pid) - } - - /** - * get an iterator for the pool - * @returns {IterableIterator} iterator - */ - iterator(): IterableIterator<[number, Process]> { - return this._pool.entries() - } - - /** - * get a process by its pid - * @param {number} pid - * @returns {Process} - */ - getByPid(pid: number): Process { - return this._pool.get(pid) - } - - /** - * Clear memory db - */ - flush() { - this.init() - } -} - -export default MemoryDB; \ No newline at end of file diff --git a/server-node/src/interfaces/IDownloadMetadata.d.ts b/server-node/src/interfaces/IDownloadMetadata.d.ts deleted file mode 100644 index 0702307..0000000 --- a/server-node/src/interfaces/IDownloadMetadata.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface IDownloadMetadata { - formats: Array, - best: IDownloadFormat, - thumbnail: string, - title: string, -} - -export interface IDownloadFormat { - format_id: string, - format_note: string, - fps: number, - resolution: string, - vcodec: string, - acodec: string, -} \ No newline at end of file diff --git a/server-node/src/interfaces/IPayload.d.ts b/server-node/src/interfaces/IPayload.d.ts deleted file mode 100644 index ab6e953..0000000 --- a/server-node/src/interfaces/IPayload.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Represent a download payload sent by the frontend - */ - -export interface IPayload { - url: string - params: Array | string - path: string - title?: string - thumbnail?: string - size?: string - renameTo?: string -} \ No newline at end of file diff --git a/server-node/src/interfaces/IRecord.d.ts b/server-node/src/interfaces/IRecord.d.ts deleted file mode 100644 index e4e6109..0000000 --- a/server-node/src/interfaces/IRecord.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Represent a download db record - */ - -export interface IRecord { - uid: string, - url: string, - title: string, - thumbnail: string, - created: Date, - size: string, - pid: number, - params: string, -} \ No newline at end of file diff --git a/server-node/src/interfaces/ISettings.d.ts b/server-node/src/interfaces/ISettings.d.ts deleted file mode 100644 index 4e9a385..0000000 --- a/server-node/src/interfaces/ISettings.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ISettings { - download_path: string, - cliArgs?: string[], - port?: number, -} \ No newline at end of file diff --git a/server-node/src/main.ts b/server-node/src/main.ts deleted file mode 100644 index beda902..0000000 --- a/server-node/src/main.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { splash } from './utils/logger'; -import { join } from 'path'; -import { Server } from 'socket.io'; -import { ytdlpUpdater } from './utils/updater'; -import { - download, - abortDownload, - retrieveDownload, - abortAllDownloads, - getFormatsAndMetadata -} from './core/downloader'; -import { getFreeDiskSpace } from './utils/procUtils'; -import { listDownloaded } from './core/downloadArchive'; -import { createServer } from 'http'; -import { streamer } from './core/streamer'; -import * as Koa from 'koa'; -import * as Router from 'koa-router'; -import * as serve from 'koa-static'; -import * as cors from '@koa/cors'; -import Logger from './utils/BetterLogger'; -import { ISettings } from './interfaces/ISettings'; -import { directoryTree } from './utils/directoryUtils'; - -const app = new Koa(); -const server = createServer(app.callback()); -const router = new Router(); -const log = Logger.instance; -const io = new Server(server, { - cors: { - origin: "*", - methods: ["GET", "POST"] - } -}); - -let settings: ISettings; - -try { - settings = require('../settings.json'); -} catch (e) { - log.warn('settings', 'file not found, ignore if using Docker'); -} - -// Koa routing -router.get('/settings', (ctx, next) => { - ctx.redirect('/') - next() -}) -router.get('/downloaded', (ctx, next) => { - ctx.redirect('/') - next() -}) -router.get('/archive', (ctx, next) => { - listDownloaded(ctx) - .then((res: any) => { - ctx.body = res - next() - }) - .catch((err: any) => { - ctx.body = err; - next() - }) -}) -router.get('/stream/:filepath', (ctx, next) => { - streamer(ctx, next) -}) -router.get('/tree', (ctx, next) => { - ctx.body = directoryTree() - next() -}) - -// WebSocket listeners -io.on('connection', socket => { - log.info('ws', `${socket.handshake.address} connected!`) - - socket.on('send-url', (args) => { - log.info('ws', args?.url) - download(socket, args) - }) - socket.on('send-url-format-selection', (args) => { - log.info('ws', `Formats ${args?.url}`) - if (args.url) getFormatsAndMetadata(socket, args?.url) - }) - socket.on('abort', (args) => { - abortDownload(socket, args) - }) - socket.on('abort-all', () => { - abortAllDownloads(socket) - }) - socket.on('update-bin', () => { - ytdlpUpdater(socket) - }) - socket.on('retrieve-jobs', () => { - retrieveDownload(socket) - }) - socket.on('disk-space', () => { - getFreeDiskSpace(socket, settings.download_path || 'downloads/') - }) -}) - -io.on('disconnect', (socket) => { - log.info('ws', `${socket.handshake.address} disconnected`) -}) - -app.use(serve(join(__dirname, 'frontend'))) -app.use(cors()) -app.use(router.routes()) - -server.listen(process.env.PORT || settings.port || 3022) - -splash() -log.info('http', `Server started on port ${process.env.PORT || settings.port || 3022}`) - -/** - * Cleanup handler - */ -const gracefullyStop = () => { - log.warn('proc', 'Shutting down...') - io.disconnectSockets(true) - server.close() - log.info('proc', 'Done!') - process.exit(0) -} - -// Intercepts singnals and perform cleanups before shutting down. -process - .on('SIGTERM', () => gracefullyStop()) - .on('SIGUSR1', () => gracefullyStop()) - .on('SIGUSR2', () => gracefullyStop()) diff --git a/server-node/src/types/index.d.ts b/server-node/src/types/index.d.ts deleted file mode 100644 index 189a7ea..0000000 --- a/server-node/src/types/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type CLIProgress = { - percentage: string - speed: string - size: number - eta: number -} \ No newline at end of file diff --git a/server-node/src/utils/BetterLogger.ts b/server-node/src/utils/BetterLogger.ts deleted file mode 100644 index 0852464..0000000 --- a/server-node/src/utils/BetterLogger.ts +++ /dev/null @@ -1,59 +0,0 @@ -const ansi = { - reset: '\u001b[0m', - red: '\u001b[31m', - cyan: '\u001b[36m', - green: '\u001b[32m', - yellow: '\u001b[93m', - bold: '\u001b[1m', - normal: '\u001b[22m', -} - -class Logger { - private static _instance: Logger; - - constructor() { }; - - static get instance() { - if (this._instance) { - return this._instance - } - this._instance = new Logger() - return this._instance; - } - /** - * Print a standard info message - * @param {string} proto the context/protocol/section outputting the message - * @param {string} args the acutal message - */ - public info(proto: string, args: string) { - process.stdout.write( - this.formatter(proto, args) - ) - } - /** - * Print a warn message - * @param {string} proto the context/protocol/section outputting the message - * @param {string} args the acutal message - */ - public warn(proto: string, args: string) { - process.stdout.write( - `${ansi.yellow}${this.formatter(proto, args)}${ansi.reset}` - ) - } - /** - * Print an error message - * @param {string} proto the context/protocol/section outputting the message - * @param {string} args the acutal message - */ - public err(proto: string, args: string) { - process.stdout.write( - `${ansi.red}${this.formatter(proto, args)}${ansi.reset}` - ) - } - - private formatter(proto: any, args: any) { - return `${ansi.bold}[${proto}]${ansi.normal}\t${args}\n` - } -} - -export default Logger; \ No newline at end of file diff --git a/server-node/src/utils/directoryUtils.ts b/server-node/src/utils/directoryUtils.ts deleted file mode 100644 index 5ebcca9..0000000 --- a/server-node/src/utils/directoryUtils.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { readdirSync, statSync } from "fs"; -import { ISettings } from "../interfaces/ISettings"; - -let settings: ISettings; - -class Node { - public path: string - public children: Node[] - - constructor(path: string) { - this.path = path - this.children = [] - } -} - -function buildTreeDFS(rootPath: string, directoryOnly: boolean) { - const root = new Node(rootPath) - const stack: Node[] = [] - const flattened: string[] = [] - - stack.push(root) - flattened.push(rootPath) - - while (stack.length) { - const current = stack.pop() - if (current) { - const children = readdirSync(current.path) - for (const it of children) { - const childPath = `${current.path}/${it}` - const childNode = new Node(childPath) - - if (directoryOnly) { - if (statSync(childPath).isDirectory()) { - current.children.push(childNode) - stack.push(childNode) - flattened.push(childNode.path) - } - } else { - current.children.push(childNode) - if (statSync(childPath).isDirectory()) { - stack.push(childNode) - flattened.push(childNode.path) - } - } - } - } - } - - return { - tree: root, - flat: flattened - } -} - -try { - settings = require('../../settings.json'); -} catch (e) { } - -export const directoryTree = () => buildTreeDFS(settings.download_path || 'downloads', true) \ No newline at end of file diff --git a/server-node/src/utils/logger.ts b/server-node/src/utils/logger.ts deleted file mode 100644 index cfe322e..0000000 --- a/server-node/src/utils/logger.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Simplest logger function, takes two argument: first one put between - * square brackets (the protocol), the second one it's the effective message - * @param {string} proto protocol - * @param {string} args message - */ -export const logger = (proto: string, args: string) => { - console.log(`[${proto}]\t${args}`) -} - -/** - * CLI splash - */ - -export const splash = () => { - const fg = "\u001b[38;2;50;113;168m" - const reset = "\u001b[0m" - console.log(`${fg} __ ____ __ __ ______`) - console.log(" __ __/ /________/ / /__ _ _____ / / / / / / _/") - console.log(" / // / __/___/ _ / / _ \\ | |/|/ / -_) _ \\/ /_/ // / ") - console.log(" \\_, /\\__/ \\_,_/_/ .__/ |__,__/\\__/_.__/\\____/___/ ") - console.log(`/___/ /_/ \n${reset}`) - console.log(" yt-dlp-webUI - A web-ui for yt-dlp, simply enough") - console.log("---------------------------------------------------\n") -} \ No newline at end of file diff --git a/server-node/src/utils/params.ts b/server-node/src/utils/params.ts deleted file mode 100644 index 79bd8d1..0000000 --- a/server-node/src/utils/params.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const availableParams = [ - '--no-mtime', - '-x', -] \ No newline at end of file diff --git a/server-node/src/utils/procUtils.ts b/server-node/src/utils/procUtils.ts deleted file mode 100644 index 26043c1..0000000 --- a/server-node/src/utils/procUtils.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { exec, spawn } from 'child_process'; -import { statSync } from 'fs'; -import Logger from './BetterLogger'; - -const log = Logger.instance; - -/** - * Browse /proc in order to find the specific pid - * @param {number} pid - * @returns {*} process stats if any - */ -export function existsInProc(pid: number): any { - try { - return statSync(`/proc/${pid}`) - } catch (e) { - log.warn('proc', `pid ${pid} not found in procfs`) - } -} - -/** - * Kills a process with a sys-call - * @param {number} pid the killed process pid - */ -export async function killProcess(pid: number) { - const res = spawn('kill', [String(pid)]) - res.on('exit', () => { - log.info('proc', `Successfully killed yt-dlp process, pid: ${pid}`) - }) -} - -export function getFreeDiskSpace(socket: any, path: string) { - const message: string = 'free-space'; - exec(`df -h ${path} | tail -1 | awk '{print $4}'`, (_, stdout) => { - socket.emit(message, stdout) - }) -} \ No newline at end of file diff --git a/server-node/src/utils/updater.ts b/server-node/src/utils/updater.ts deleted file mode 100644 index f0a5de2..0000000 --- a/server-node/src/utils/updater.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { get } from 'https'; -import { rmSync, createWriteStream, chmod } from 'fs'; -import { join } from 'path'; - -// endpoint to github API -const options = { - hostname: 'api.github.com', - path: '/repos/yt-dlp/yt-dlp/releases/latest', - headers: { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0' - }, - method: 'GET', - port: 443, -} - -/** - * Build the binary url based on the release tag - * @param {string} release yt-dlp GitHub release tag - * @returns {*} the fetch options with the correct tag and headers - */ -function buildDonwloadOptions(release) { - return { - hostname: 'github.com', - path: `/yt-dlp/yt-dlp/releases/download/${release}/yt-dlp`, - headers: { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:88.0) Gecko/20100101 Firefox/88.0' - }, - method: 'GET', - port: 443, - } -} - -/** - * gets the yt-dlp latest binary URL from GitHub API - */ -async function update() { - // ensure that the binary has been removed - try { - rmSync(join(__dirname, '..', 'core', 'yt-dlp')) - } - catch (e) { - console.log('file not found!') - } - // body buffer - let chunks = [] - get(options, res => { - // push the http packets chunks into the buffer - res.on('data', chunk => { - chunks.push(chunk) - }); - // the connection has ended so build the body from the buffer - // parse it as a JSON and get the tag_name - res.on('end', () => { - const buffer = Buffer.concat(chunks) - const release = JSON.parse(buffer.toString())['tag_name'] - console.log('The latest release is:', release) - // invoke the binary downloader - downloadBinary(buildDonwloadOptions(release)) - }) - }) -} -/** - * Utility that Pipes the latest binary to a file - * @param {string} url yt-dlp GitHub release url - */ -function downloadBinary(url) { - get(url, res => { - // if it is a redirect follow the url - if (res.statusCode === 301 || res.statusCode === 302) { - return downloadBinary(res.headers.location) - } - let bin = createWriteStream(join(__dirname, '..', 'core', 'yt-dlp')) - res.pipe(bin) - // once the connection has ended make the file executable - res.on('end', () => { - chmod(join(__dirname, '..', 'core', 'yt-dlp'), 0o775, err => { - err ? console.error('failed updating!') : console.log('done!') - }) - }) - }) -} -/** - * Invoke the yt-dlp update procedure - * @param {Socket} socket the current connection socket - */ -export function ytdlpUpdater(socket) { - update().then(() => { - socket.emit('updated') - }) -} \ No newline at end of file diff --git a/server/config/config_singleton.go b/server/config/config_singleton.go new file mode 100644 index 0000000..fae1c04 --- /dev/null +++ b/server/config/config_singleton.go @@ -0,0 +1,60 @@ +package config + +import ( + "os" + "sync" + + "gopkg.in/yaml.v3" +) + +var lock = &sync.Mutex{} + +type serverConfig struct { + Port int `yaml:"port"` + DownloadPath string `yaml:"downloadPath"` + DownloaderPath string `yaml:"downloaderPath"` +} + +type config struct { + cfg serverConfig +} + +func (c *config) LoadFromFile(filename string) (serverConfig, error) { + bytes, err := os.ReadFile(filename) + if err != nil { + return serverConfig{}, err + } + + yaml.Unmarshal(bytes, &c.cfg) + + return c.cfg, nil +} + +func (c *config) GetConfig() serverConfig { + return c.cfg +} + +func (c *config) SetPort(port int) { + c.cfg.Port = port +} + +func (c *config) DownloadPath(path string) { + c.cfg.DownloadPath = path +} + +func (c *config) DownloaderPath(path string) { + c.cfg.DownloaderPath = path +} + +var instance *config + +func Instance() *config { + if instance == nil { + lock.Lock() + defer lock.Unlock() + if instance == nil { + instance = &config{serverConfig{}} + } + } + return instance +} diff --git a/server/process.go b/server/process.go index c209a9d..2f73768 100644 --- a/server/process.go +++ b/server/process.go @@ -2,6 +2,7 @@ package server import ( "bufio" + "fmt" "regexp" "github.com/goccy/go-json" @@ -13,6 +14,7 @@ import ( "time" "github.com/marcopeocchi/fazzoletti/slices" + "github.com/marcopeocchi/yt-dlp-web-ui/server/config" "github.com/marcopeocchi/yt-dlp-web-ui/server/rx" ) @@ -23,7 +25,9 @@ const template = `download: "speed":%(progress.speed)s }` -const driver = "yt-dlp" +var ( + cfg = config.Instance() +) type ProgressTemplate struct { Percentage string `json:"percentage"` @@ -43,12 +47,17 @@ type Process struct { proc *os.Process } +type downloadOutput struct { + path string + filaneme string +} + // Starts spawns/forks a new yt-dlp process and parse its stdout. // The process is spawned to outputting a custom progress text that // Resembles a JSON Object in order to Unmarshal it later. // This approach is anyhow not perfect: quotes are not escaped properly. // Each process is not identified by its PID but by a UUIDv2 -func (p *Process) Start() { +func (p *Process) Start(path, filename string) { // escape bash variable escaping and command piping, you'll never know // what they might come with... p.params = slices.Filter(p.params, func(e string) bool { @@ -56,6 +65,18 @@ func (p *Process) Start() { return !match }) + out := downloadOutput{ + path: cfg.GetConfig().DownloadPath, + filaneme: "%(title)s.%(ext)s", + } + + if path != "" { + out.path = path + } + if filename != "" { + out.filaneme = filename + ".%(ext)s" + } + params := append([]string{ strings.Split(p.url, "?list")[0], //no playlist "--newline", @@ -63,11 +84,11 @@ func (p *Process) Start() { "--no-playlist", "--progress-template", strings.ReplaceAll(template, "\n", ""), "-o", - "./downloads/%(title)s.%(ext)s", + fmt.Sprintf("%s/%s", out.path, out.filaneme), }, p.params...) // ----------------- main block ----------------- // - cmd := exec.Command(driver, params...) + cmd := exec.Command(cfg.GetConfig().DownloaderPath, params...) r, err := cmd.StdoutPipe() if err != nil { log.Panicln(err) @@ -85,7 +106,7 @@ func (p *Process) Start() { // ----------------- info block ----------------- // // spawn a goroutine that retrieves the info for the download go func() { - cmd := exec.Command(driver, p.url, "-J") + cmd := exec.Command(cfg.GetConfig().DownloaderPath, p.url, "-J") stdout, err := cmd.Output() if err != nil { log.Println("Cannot retrieve info for", p.url) @@ -100,18 +121,19 @@ func (p *Process) Start() { eventChan := make(chan string) // spawn a goroutine that does the dirty job of parsing the stdout - // fill the channel with as many stdout line as yt-dlp produces (producer) + // filling the channel with as many stdout line as yt-dlp produces (producer) go func() { - defer cmd.Wait() defer r.Close() defer p.Complete() + defer close(eventChan) for scan.Scan() { eventChan <- scan.Text() } + cmd.Wait() }() // do the unmarshal operation every 500ms (consumer) - go rx.Sample(time.Millisecond*500, eventChan, func(text string) { + go rx.Debounce(time.Millisecond*500, eventChan, func(text string) { stdout := ProgressTemplate{} err := json.Unmarshal([]byte(text), &stdout) if err == nil { @@ -147,7 +169,7 @@ func (p *Process) Kill() error { } func (p *Process) GetFormatsSync() (DownloadFormats, error) { - cmd := exec.Command(driver, p.url, "-J") + cmd := exec.Command(cfg.GetConfig().DownloaderPath, p.url, "-J") stdout, err := cmd.Output() if err != nil { diff --git a/server/server.go b/server/server.go index ac275f8..ba0bb4c 100644 --- a/server/server.go +++ b/server/server.go @@ -23,7 +23,7 @@ func init() { func RunBlocking(ctx context.Context) { fe := ctx.Value("frontend").(fs.SubFS) - port := ctx.Value("port") + port := ctx.Value("port").(int) service := new(Service) rpc.Register(service) @@ -62,5 +62,5 @@ func RunBlocking(ctx context.Context) { app.Server().StreamRequestBody = true - log.Fatal(app.Listen(fmt.Sprintf(":%s", port))) + log.Fatal(app.Listen(fmt.Sprintf(":%d", port))) } diff --git a/server/service.go b/server/service.go index 2803ae9..bdf87ae 100644 --- a/server/service.go +++ b/server/service.go @@ -4,6 +4,7 @@ import ( "log" "github.com/marcopeocchi/yt-dlp-web-ui/server/sys" + "github.com/marcopeocchi/yt-dlp-web-ui/server/updater" ) type Service int @@ -12,18 +13,27 @@ type Running []ProcessResponse type Pending []string type NoArgs struct{} + type Args struct { Id string URL string Params []string } +type DownloadSpecificArgs struct { + Id string + URL string + Path string + Rename string + Params []string +} + // Exec spawns a Process. // The result of the execution is the newly spawned process Id. -func (t *Service) Exec(args Args, result *string) error { +func (t *Service) Exec(args DownloadSpecificArgs, result *string) error { log.Printf("Spawning new process for %s\n", args.URL) p := Process{mem: &db, url: args.URL, params: args.Params} - p.Start() + p.Start(args.Path, args.Rename) *result = p.id return nil } @@ -86,7 +96,17 @@ func (t *Service) FreeSpace(args NoArgs, free *uint64) error { } func (t *Service) DirectoryTree(args NoArgs, tree *[]string) error { - dfsTree, err := sys.DirectoryTree("downloads") + dfsTree, err := sys.DirectoryTree() *tree = *dfsTree return err } + +func (t *Service) UpdateExecutable(args NoArgs, updated *bool) error { + err := updater.UpdateExecutable() + if err != nil { + *updated = true + return err + } + *updated = false + return err +} diff --git a/server/sys/fs.go b/server/sys/fs.go index c086316..2473ab3 100644 --- a/server/sys/fs.go +++ b/server/sys/fs.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" + "github.com/marcopeocchi/yt-dlp-web-ui/server/config" "github.com/marcopeocchi/yt-dlp-web-ui/server/internal" "golang.org/x/sys/unix" ) @@ -13,20 +14,20 @@ import ( // FreeSpace gets the available Bytes writable to download directory func FreeSpace() (uint64, error) { var stat unix.Statfs_t - wd, err := os.Getwd() - if err != nil { - return 0, err - } - unix.Statfs(wd+"/downloads", &stat) + unix.Statfs(config.Instance().GetConfig().DownloadPath, &stat) return (stat.Bavail * uint64(stat.Bsize)), nil } -func DirectoryTree(rootPath string) (*[]string, error) { +// Build a directory tree started from the specified path using DFS. +// Then return the flattened tree represented as a list. +func DirectoryTree() (*[]string, error) { type Node struct { path string children []Node } + rootPath := config.Instance().GetConfig().DownloadPath + stack := internal.Stack[Node]{ Nodes: make([]*internal.Node[Node], 5), } diff --git a/server/types.go b/server/types.go index 26fcede..54e4964 100644 --- a/server/types.go +++ b/server/types.go @@ -55,6 +55,7 @@ type AbortRequest struct { // struct representing the intent to start a download type DownloadRequest struct { - Url string `json:"url"` - Params []string `json:"params"` + Url string `json:"url"` + Params []string `json:"params"` + RenameTo string `json:"renameTo"` } diff --git a/server/updater/forced_update.go b/server/updater/forced_update.go new file mode 100644 index 0000000..1dbb909 --- /dev/null +++ b/server/updater/forced_update.go @@ -0,0 +1,45 @@ +package updater + +import ( + "io" + "log" + "net/http" + + "github.com/goccy/go-json" +) + +const ( + gitHubAPILatest = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest" + gitHubAPIDownload = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/download" +) + +var ( + client = &http.Client{ + CheckRedirect: http.DefaultClient.CheckRedirect, + } +) + +func getLatestReleaseTag() (string, error) { + res, err := client.Get(gitHubAPILatest) + if err != nil { + log.Println("Cannot get release tag from GitHub API") + return "", err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + + if err != nil { + log.Println("Cannot parse response from GitHub API") + return "", err + } + + tag := ReleaseLatestResponse{} + json.Unmarshal(body, &tag) + + return tag.TagName, nil +} + +func ForceUpdate() { + getLatestReleaseTag() +} diff --git a/server/updater/types.go b/server/updater/types.go new file mode 100644 index 0000000..d967425 --- /dev/null +++ b/server/updater/types.go @@ -0,0 +1,6 @@ +package updater + +type ReleaseLatestResponse struct { + Name string `json:"name"` + TagName string `json:"tag_name"` +} diff --git a/server/updater/update.go b/server/updater/update.go new file mode 100644 index 0000000..8bf8b37 --- /dev/null +++ b/server/updater/update.go @@ -0,0 +1,17 @@ +package updater + +import ( + "os/exec" + + "github.com/marcopeocchi/yt-dlp-web-ui/server/config" +) + +var path = config.Instance().GetConfig().DownloaderPath + +func UpdateExecutable() error { + cmd := exec.Command(path, "-U") + cmd.Start() + + err := cmd.Wait() + return err +} diff --git a/settings.json b/settings.json deleted file mode 100644 index 667e474..0000000 --- a/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "port": 0, - "download_path": "", - "cliArgs": [] -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index f27b390..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist", - "target": "ES2020", - "strict": false, - "noEmit": false, - "moduleResolution": "node", - "module": "commonjs", - "skipLibCheck": true, - }, - "include": [ - "server-node/src/**/*" - ] -} \ No newline at end of file