add manually operated

This commit is contained in:
ForgQi committed 2024-04-25 01:47:44 +08:00
1 parent 664efc89a7
commit 8db64e2d8b
11 files changed
+391 -43

No files matched your search

+22 -10
View File
@@ -17,7 +17,7 @@ import {
IconStar,
IconSun,
IconVideoListStroked,
IconHome,
IconHome, IconSetting,
} from "@douyinfe/semi-icons";
export default function RootLayout({
@@ -139,12 +139,23 @@ export default function RootLayout({
</div>
),
},
// {
// text: '任务平台',
// icon: <IconSetting/>,
// itemKey: 'Job',
// items: [{itemKey: 'About', text: '任务管理'}, {itemKey: 'Dashboard', text: '用户任务查询'}],
// },
{
text: '任务平台',
icon: <div
style={{
backgroundColor: "rgba(var(--semi-lime-2), 1)",
borderRadius:
"var(--semi-border-radius-medium)",
color: "var(--semi-color-bg-0)",
display: "flex",
padding: "4px",
}}
>
<IconSetting size="small"/>
</div>,
itemKey: 'status',
// items: [{itemKey: 'About', text: '任务管理'}, {itemKey: 'Dashboard', text: '用户任务查询'}],
},
].map((value: any) => {
value.text = (
<div
@@ -154,9 +165,9 @@ export default function RootLayout({
(key: string) => value.itemKey === key
) ||
(selectedKeys.some((key: string) =>
openKeys.some((o: string | number) =>
isSub(key, o)
)
openKeys.some((o: string | number) =>
isSub(key, o)
)
) &&
openKeys.some(
(key: any) => value.itemKey === key
@@ -182,6 +193,7 @@ export default function RootLayout({
streamers: "/streamers",
"upload-manager": "/upload-manager",
job: "/job",
status: "/status",
};
if (!routerMap[props.itemKey]) {
return itemElement;
+1 -1
View File
@@ -28,7 +28,7 @@ export function useBiliUsers() {
const updateList = async (item: User) => {
try {
const res = await fetcher(`/bili/space/myinfo?user=${item.value}`, undefined);
const pRes = await proxy(`/bili/proxy?url=${res.data.face}`);
const pRes = await proxy(`/bili/proxy?url=${res.data?.face}`);
const myBlob = await pRes.blob();
return {
+44
View File
@@ -0,0 +1,44 @@
'use client'
import { Layout, Nav, Spin, Typography } from "@douyinfe/semi-ui";
import useSWR from "swr";
import {fetcher} from "@/app/lib/api-streamer";
import {IconCustomerSupport, IconSearch, IconSetting, IconVideoListStroked} from "@douyinfe/semi-icons";
import {JSONTree} from "react-json-tree";
export default function Home() {
const {Header, Footer, Sider, Content} = Layout;
const {data: data, error, isLoading} = useSWR<any[]>("/v1/status", fetcher);
if (isLoading) {
return (<Spin size="large" />);
}
return (<>
<Header style={{backgroundColor: 'var(--semi-color-bg-1)'}}>
<Nav style={{border: 'none'}}
header={<>
<div style={{
backgroundColor: 'rgba(var(--semi-lime-2), 1)',
borderRadius: 'var(--semi-border-radius-large)',
color: 'var(--semi-color-bg-0)',
display: 'flex',
padding: '6px'
}}><IconSetting size='large'/></div>
<h4 style={{marginLeft: '12px'}}>任务平台</h4></>}
mode="horizontal"
></Nav>
</Header>
<Content
style={{
paddingLeft: 12,
paddingRight: 12,
backgroundColor: 'var(--semi-color-bg-0)',
}}
>
<main>
<JSONTree data={data} />
</main>
</Content>
</>);
}
+67 -13
View File
@@ -6,18 +6,18 @@ import {
List,
Nav,
Popconfirm,
Notification,
Notification, Typography, Modal, Transfer,
} from "@douyinfe/semi-ui";
import {
IconCloudStroked,
IconPlusCircle,
IconUserListStroked
} from "@douyinfe/semi-icons";
import { useState } from "react";
import { SetStateAction, useState} from "react";
import Link from "next/link";
import { Card } from '@douyinfe/semi-ui';
import { IconEdit2Stroked, IconSendStroked, IconDeleteStroked } from '@douyinfe/semi-icons';
import {fetcher, requestDelete, StudioEntity} from "../lib/api-streamer";
import {Card} from '@douyinfe/semi-ui';
import {IconEdit2Stroked, IconSendStroked, IconDeleteStroked} from '@douyinfe/semi-icons';
import {fetcher, FileList, requestDelete, sendRequest, StudioEntity} from "../lib/api-streamer";
import useSWR from "swr";
import {useRouter} from "next/navigation";
import UserList from "../ui/UserList";
@@ -25,12 +25,13 @@ import useSWRMutation from "swr/mutation";
import {useBiliUsers} from "../lib/use-streamers";
export default function Union() {
const { Meta } = Card;
const { Header, Content } = Layout;
const {Meta} = Card;
const {Paragraph, Title, Text} = Typography;
const {Header, Content} = Layout;
const [visible, setVisible] = useState(false);
const router = useRouter();
const {trigger: deleteUpload} = useSWRMutation('/v1/upload/streamers', requestDelete);
const { data: templates, error, isLoading } = useSWR<StudioEntity[]>("/v1/upload/streamers", fetcher);
const {data: templates, error, isLoading} = useSWR<StudioEntity[]>("/v1/upload/streamers", fetcher);
const {biliUsers} = useBiliUsers();
const handleAddLinkClick = (event: React.MouseEvent) => {
if (biliUsers.length === 0) {
@@ -51,8 +52,57 @@ export default function Union() {
const onConfirm = async (id: number) => {
await deleteUpload(id);
}
const [visibleModal, setVisibleModal] = useState(false);
const [selectFiles, setSelectFiles] = useState<(string | number)[]>([]);
const [selectEntity, setSelectEntity] = useState<StudioEntity>();
const showDialog = (entity: StudioEntity) => {
setSelectEntity(entity);
setVisibleModal(true);
};
const handleOk = async () => {
await sendRequest('/v1/uploads', { arg: {
files: selectFiles,
params: selectEntity
}})
setVisibleModal(false);
};
const handleCancel = () => {
setVisibleModal(false);
console.log('Cancel button clicked');
};
const handleAfterClose = () => {
console.log('After Close callback executed');
};
const { data: fileList} = useSWR<FileList[]>("/v1/videos", fetcher);
const data = fileList?.map((v) => {
return {
label: v.name,
value: v.name,
disabled: false,
key: v.key,
};
});
return (<>
<UserList visible={visible} onCancel={change}></UserList>
<Modal
size="medium"
title="文件选择"
okText="上传"
visible={visibleModal}
onOk={handleOk}
afterClose={handleAfterClose} //>=1.16.0
onCancel={handleCancel}
closeOnEsc={true}
>
<Transfer
style={{ width: 568, height: 416 }}
dataSource={data}
draggable
onChange={(values, items) => setSelectFiles(values)}
/>
</Modal>
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
<Nav
header={<><div style={{
@@ -112,11 +162,15 @@ export default function Union() {
justifyContent: 'space-between'
}}
>
<Meta
title={item.template_name}
/>
<ButtonGroup theme='borderless'>
<Button icon={<IconSendStroked />}></Button>
<Meta title={
<Text ellipsis={{
showTooltip: true,
pos: 'middle'
}} style={{ maxWidth: 150}}>
{item.template_name}
</Text>}/>
<ButtonGroup style={{minWidth: 100}} theme='borderless'>
<Button icon={<IconSendStroked />} onClick={()=> showDialog(item)}></Button>
<Button icon={<IconEdit2Stroked />} onClick={()=> {
router.push(`/upload-manager/edit?id=${item.id}`);
}}></Button>
+1 -1
View File
@@ -2,7 +2,7 @@ import logging
import platform
import sys
__version__ = "0.4.53"
__version__ = "0.4.54"
LOG_CONF = {
'version': 1,
+1 -8
View File
@@ -1,19 +1,12 @@
import logging
import re
import threading
import time
from urllib.error import HTTPError
from biliup.config import config
from .app import context
from .common.tools import NamedLock
from .engine.decorators import Plugin
from .engine.download import DownloadBase
from .engine.event import Event
from .plugins import general
logger = logging.getLogger('biliup')
def download(fname, url, **kwargs):
pg = general.__plugin__(fname, url)
for plugin in Plugin.download_plugins:
+123
View File
@@ -0,0 +1,123 @@
import time
import stream_gears
from typing import List
from ..engine import Plugin
from ..engine.upload import UploadBase, logger
@Plugin.upload(platform="stream_gears")
class BiliWeb(UploadBase):
def __init__(
self, principal, data, submit_api=None, copyright=2, postprocessor=None, dtime=None,
dynamic='', lines='AUTO', threads=3, tid=122, tags=None, cover_path=None, description='',
dolby=0, hires=0, no_reprint=0, open_elec=0, credits=None,
user_cookie='cookies.json'
):
super().__init__(principal, data, persistence_path='bili.cookie', postprocessor=postprocessor)
if credits is None:
credits = []
if tags is None:
tags = []
self.lines = lines
self.submit_api = submit_api
self.threads = threads
self.tid = tid
self.tags = tags
if cover_path:
self.cover_path = cover_path
elif "live_cover_path" in self.data:
self.cover_path = self.data["live_cover_path"]
else:
self.cover_path = None
self.desc = description
self.credits = credits
self.dynamic = dynamic
self.copyright = copyright
self.dtime = dtime
self.dolby = dolby
self.hires = hires
self.no_reprint = no_reprint
self.open_elec = open_elec
self.user_cookie = user_cookie
def upload(self, file_list) -> List[UploadBase.FileInfo]:
line = None
if self.lines == 'kodo':
line = stream_gears.UploadLine.Kodo
elif self.lines == 'bda2':
line = stream_gears.UploadLine.Bda2
elif self.lines == 'ws':
line = stream_gears.UploadLine.Ws
elif self.lines == 'qn':
line = stream_gears.UploadLine.Qn
elif self.lines == 'cos':
line = stream_gears.UploadLine.Cos
elif self.lines == 'cos-internal':
line = stream_gears.UploadLine.CosInternal
tag = ','.join(self.tags)
if self.credits:
desc_v2 = self.creditsToDesc_v2()
else:
desc_v2 = [{
"raw_text": self.desc,
"biz_id": "",
"type": 1
}]
source = self.data["url"] if self.copyright == 2 else ""
cover = self.cover_path if self.cover_path is not None else ""
dtime = None
if self.dtime:
dtime = int(time.time() + self.dtime)
stream_gears.upload(
file_list,
self.user_cookie,
self.data["format_title"][:80],
self.tid,
tag,
self.copyright,
source,
self.desc,
self.dynamic,
cover,
self.dolby,
self.hires,
self.no_reprint,
self.open_elec,
self.threads,
desc_v2,
dtime,
line
)
logger.info(f"上传成功: {self.principal}")
return file_list
def creditsToDesc_v2(self):
desc_v2 = []
desc_v2_tmp = self.desc
for credit in self.credits:
try:
num = desc_v2_tmp.index("@credit")
desc_v2.append({
"raw_text": " " + desc_v2_tmp[:num],
"biz_id": "",
"type": 1
})
desc_v2.append({
"raw_text": credit["username"],
"biz_id": str(credit["uid"]),
"type": 2
})
self.desc = self.desc.replace(
"@credit", "@" + credit["username"] + " ", 1)
desc_v2_tmp = desc_v2_tmp[num + 7:]
except IndexError:
logger.error('简介中的@credit占位符少于credits的数量,替换失败')
desc_v2.append({
"raw_text": " " + desc_v2_tmp,
"biz_id": "",
"type": 1
})
desc_v2[0]["raw_text"] = desc_v2[0]["raw_text"][1:] # 开头空格会导致识别简介过长
return desc_v2
+43 -1
View File
@@ -7,6 +7,7 @@ from .engine.decorators import Plugin
logger = logging.getLogger('biliup')
def upload(data):
"""
上传入口
@@ -38,6 +39,46 @@ def upload(data):
logger.exception("Uncaught exception:")
def biliup_uploader(filelist, data):
try:
index = data['name']
context = {**data}
platform = context.get("uploader") if context.get("uploader") else "biliup-rs"
cls = Plugin.upload_plugins.get(platform)
if cls is None:
return logger.error(f"No such uploader: {platform}")
data, context = fmt_title_and_desc_m(data)
data['dolby'] = data.get('dolby', 0)
data['hires'] = data.get('hires', 0)
data['no_reprint'] = data.get('no_reprint', 0)
data['open_elec'] = data.get('open_elec', 0)
sig = inspect.signature(cls)
kwargs = {}
for k in sig.parameters:
v = context.get(k)
if v:
kwargs[k] = v
logger.info("start biliup")
return cls(index, data, **kwargs).upload(filelist)
except:
logger.exception("Uncaught exception:")
else:
logger.info("stop biliup")
def fmt_title_and_desc_m(data):
index = data['name']
context = {**data}
streamer = data.get('streamer', index)
date = data.get("date", time.localtime())
title = data.get('title', index)
url = data.get('url')
data["format_title"] = custom_fmtstr(context.get('title') or f'%Y.%m.%d{index}', date, title, streamer, url)
if context.get('description'):
context['description'] = custom_fmtstr(context.get('description'), date, title, streamer, url)
return data, context
# 将格式化标题和简介拆分出来方便复用
def fmt_title_and_desc(data):
index = data['name']
@@ -53,4 +94,5 @@ def fmt_title_and_desc(data):
def custom_fmtstr(string, date, title, streamer, url):
return time.strftime(string.encode('unicode-escape').decode(), date).encode().decode("unicode-escape").format(title=title, streamer=streamer, url=url)
return time.strftime(string.encode('unicode-escape').decode(), date).encode().decode("unicode-escape").format(
title=title, streamer=streamer, url=url)
+16 -1
View File
@@ -4,6 +4,7 @@ import json
import os
import pathlib
import concurrent.futures
import threading
import aiohttp_cors
import requests
@@ -446,6 +447,16 @@ async def users(request):
return web.json_response(resp)
@routes.post('/v1/uploads')
async def m_upload(request):
from ..uploader import biliup_uploader
json_data = await request.json()
json_data['params']['uploader'] = 'stream_gears'
json_data['params']['name'] = json_data['params']['template_name']
threading.Thread(target=biliup_uploader, args=(json_data['files'], json_data['params'])).start()
return web.json_response({'status': 'ok'})
@routes.post('/v1/dump')
async def dump_config(request):
json_data = await request.json()
@@ -485,8 +496,12 @@ async def pre_archive(request):
try:
config.load_cookies(path)
cookies = config.data['user']['cookies']
return web.json_response(BiliBili.tid_archive(cookies))
res = BiliBili.tid_archive(cookies)
if res['code'] != 0:
continue
return web.json_response(res)
except:
logger.exception('pre_archive')
continue
return web.json_response({"status": 500, 'error': "无可用 cookie 文件"}, status=500)
+72 -8
View File
@@ -14,6 +14,7 @@
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-json-tree": "^0.19.0",
"sass": "^1.75.0",
"swr": "^2.2.5",
"xgplayer": "^3.0.14",
@@ -595,6 +596,11 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA=="
},
"node_modules/@types/node": {
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
@@ -607,14 +613,12 @@
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"dev": true
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.2.79",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz",
"integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -1229,11 +1233,22 @@
"node": ">=6"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -1244,8 +1259,16 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/compute-scroll-into-view": {
"version": "1.0.20",
@@ -1310,8 +1333,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/d": {
"version": "1.0.2",
@@ -2721,6 +2743,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/is-async-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
@@ -3233,6 +3260,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3793,6 +3825,17 @@
"node": ">=0.10.0"
}
},
"node_modules/react-base16-styling": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz",
"integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==",
"dependencies": {
"@types/lodash": "^4.17.0",
"color": "^4.2.3",
"csstype": "^3.1.3",
"lodash-es": "^4.17.21"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -3823,6 +3866,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-json-tree": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz",
"integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==",
"dependencies": {
"@types/lodash": "^4.17.0",
"react-base16-styling": "^0.10.0"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-resizable": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
@@ -4154,6 +4210,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+1
View File
@@ -15,6 +15,7 @@
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-json-tree": "^0.19.0",
"sass": "^1.75.0",
"swr": "^2.2.5",
"xgplayer": "^3.0.14",