diff --git a/backend/.env b/backend/.env deleted file mode 100644 index 81825a8..0000000 --- a/backend/.env +++ /dev/null @@ -1,5 +0,0 @@ -# The port the backend server will run on -PORT=3001 - -# Optional: The password for the login endpoint. If not provided, login is disabled. -PASSWORD= \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index 81825a8..0000000 --- a/backend/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# The port the backend server will run on -PORT=3001 - -# Optional: The password for the login endpoint. If not provided, login is disabled. -PASSWORD= \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 80d1446..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# --- Build Stage --- -FROM node:18-alpine AS builder - -WORKDIR /app - -# Copy package.json and yarn.lock first to leverage Docker cache -COPY package.json yarn.lock ./ -RUN yarn install --frozen-lockfile - -# Copy the rest of the source code -COPY . . - -# Compile TypeScript to JavaScript -RUN yarn build - -# Prune development dependencies -RUN yarn install --production --ignore-scripts --prefer-offline - - -# --- Production Stage --- -FROM node:18-alpine - -WORKDIR /app - -# Copy production dependencies and compiled code from the builder stage -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist - -# Copy config.json from the project root relative to the Docker build context -# IMPORTANT: When building, run `docker build -f backend/Dockerfile .` from the project root. -COPY src/config/config.json dist/config/ - -# Expose the port the app runs on -EXPOSE 3001 - -# The command to run the application -# You can override the port using -e PORT=... in `docker run` -CMD [ "node", "dist/index.docker.js" ] \ No newline at end of file diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index b5d4ec9..0000000 --- a/backend/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "OrionTV-proxy", - "version": "1.0.1", - "description": "Backend service for MyTV application", - "main": "dist/index.js", - "scripts": { - "start": "node dist/index.js", - "build": "tsc", - "dev": "ts-node-dev --respawn --transpile-only src/index.docker.ts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "cors": "^2.8.5", - "express": "^4.19.2", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^20.14.2", - "ts-node-dev": "^2.0.0", - "typescript": "^5.4.5" - } -} diff --git a/backend/src/config/config.json b/backend/src/config/config.json deleted file mode 100644 index dcdf585..0000000 --- a/backend/src/config/config.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cache_time": 7200, - "api_site": { - "dyttzy": { - "api": "http://caiji.dyttzyapi.com/api.php/provide/vod", - "name": "电影天堂资源" - }, - "ruyi": { - "api": "https://cj.rycjapi.com/api.php/provide/vod", - "name": "如意资源" - }, - "mozhua": { - "api": "https://mozhuazy.com/api.php/provide/vod", - "name": "魔爪资源" - }, - "heimuer": { - "api": "https://json.heimuer.xyz/api.php/provide/vod", - "name": "黑木耳" - }, - "bfzy": { - "api": "https://bfzyapi.com/api.php/provide/vod", - "name": "暴风资源" - }, - "tyyszy": { - "api": "https://tyyszy.com/api.php/provide/vod", - "name": "天涯资源" - }, - "ffzy": { - "api": "http://ffzy5.tv/api.php/provide/vod", - "name": "非凡影视" - }, - "zy360": { - "api": "https://360zy.com/api.php/provide/vod", - "name": "360资源" - }, - "iqiyi": { - "api": "https://www.iqiyizyapi.com/api.php/provide/vod", - "name": "iqiyi资源" - }, - "wolong": { - "api": "https://wolongzyw.com/api.php/provide/vod", - "name": "卧龙资源" - }, - "hwba": { - "api": "https://cjhwba.com/api.php/provide/vod", - "name": "华为吧资源" - }, - "jisu": { - "api": "https://jszyapi.com/api.php/provide/vod", - "name": "极速资源" - }, - "dbzy": { - "api": "https://dbzy.tv/api.php/provide/vod", - "name": "豆瓣资源" - }, - "mdzy": { - "api": "https://www.mdzyapi.com/api.php/provide/vod", - "name": "魔都资源" - }, - "zuid": { - "api": "https://api.zuidapi.com/api.php/provide/vod", - "name": "最大资源" - }, - "yinghua": { - "api": "https://m3u8.apiyhzy.com/api.php/provide/vod", - "name": "樱花资源" - }, - "wujin": { - "api": "https://api.wujinapi.me/api.php/provide/vod", - "name": "无尽资源" - }, - "wwzy": { - "api": "https://wwzy.tv/api.php/provide/vod", - "name": "旺旺短剧" - }, - "ikun": { - "api": "https://ikunzyapi.com/api.php/provide/vod", - "name": "iKun资源" - } - } -} diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts deleted file mode 100644 index 2a713ed..0000000 --- a/backend/src/config/index.ts +++ /dev/null @@ -1,80 +0,0 @@ -import fs from "fs"; -import path from "path"; - -export interface ApiSite { - key: string; - api: string; - name: string; - detail?: string; -} - -export interface StorageConfig { - type: "localstorage" | "database"; - database?: { - host?: string; - port?: number; - username?: string; - password?: string; - database?: string; - }; -} - -export interface Config { - cache_time?: number; - api_site: { - [key: string]: ApiSite; - }; - storage?: StorageConfig; -} - -export const API_CONFIG = { - search: { - path: "?ac=videolist&wd=", - pagePath: "?ac=videolist&wd={query}&pg={page}", - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - Accept: "application/json", - }, - }, - detail: { - path: "?ac=videolist&ids=", - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", - Accept: "application/json", - }, - }, -}; - -// Adjust path to read from project root, not from `backend/` -const configPath = path.join(__dirname, "config.json"); -let cachedConfig: Config; - -try { - cachedConfig = JSON.parse(fs.readFileSync(configPath, "utf-8")) as Config; -} catch (error) { - console.info(`Error reading or parsing config.json at ${configPath}`, error); - // Provide a default fallback config to prevent crashes - cachedConfig = { - api_site: {}, - cache_time: 300, - }; -} - -export function getConfig(): Config { - return cachedConfig; -} - -export function getCacheTime(): number { - const config = getConfig(); - return config.cache_time || 300; // 默认5分钟缓存 -} - -export function getApiSites(): ApiSite[] { - const config = getConfig(); - return Object.entries(config.api_site).map(([key, site]) => ({ - ...site, - key, - })); -} diff --git a/backend/src/data/favorites.json b/backend/src/data/favorites.json deleted file mode 100644 index 0967ef4..0000000 --- a/backend/src/data/favorites.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/backend/src/data/playrecords.json b/backend/src/data/playrecords.json deleted file mode 100644 index 9e26dfe..0000000 --- a/backend/src/data/playrecords.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/backend/src/data/searchhistory.json b/backend/src/data/searchhistory.json deleted file mode 100644 index fe51488..0000000 --- a/backend/src/data/searchhistory.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/backend/src/index.docker.ts b/backend/src/index.docker.ts deleted file mode 100644 index 56b7c19..0000000 --- a/backend/src/index.docker.ts +++ /dev/null @@ -1,28 +0,0 @@ -import express, { Express, Request, Response } from "express"; -import cors from "cors"; -import dotenv from "dotenv"; - -dotenv.config(); - -const app: Express = express(); -const port = process.env.PORT || 3001; - -// Middlewares -app.use(cors()); -app.use(express.json()); - -// Health check route -app.get("/", (req: Request, res: Response) => { - res.send("MyTV Backend Service is running!"); -}); - -import apiRouter from "./routes"; - -// API routes -app.use("/api", apiRouter); - -app.listen(port, () => { - console.log(`Server is running on port ${port}`); -}); - -export default app; diff --git a/backend/src/index.ts b/backend/src/index.ts deleted file mode 100644 index efa334e..0000000 --- a/backend/src/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import express, { Express, Request, Response } from "express"; -import cors from "cors"; -import dotenv from "dotenv"; - -dotenv.config(); - -const app: Express = express(); -const port = process.env.PORT || 3001; - -// Middlewares -app.use(cors()); -app.use(express.json()); - -// Health check route -app.get("/", (req: Request, res: Response) => { - res.send("MyTV Backend Service is running!"); -}); - -import apiRouter from "./routes"; - -// API routes -app.use("/api", apiRouter); - -export default app; diff --git a/backend/src/routes/detail.ts b/backend/src/routes/detail.ts deleted file mode 100644 index cb9b47a..0000000 --- a/backend/src/routes/detail.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Router, Request, Response } from "express"; -import { API_CONFIG, ApiSite, getApiSites, getCacheTime } from "../config"; -import { VideoDetail } from "../types"; -import { cleanHtmlTags } from "../utils"; - -const router = Router(); - -// Match m3u8 links -const M3U8_PATTERN = /(https?:\/\/[^"'\s]+?\.m3u8)/g; - -async function handleSpecialSourceDetail(id: string, apiSite: ApiSite): Promise { - const detailUrl = `${apiSite.detail}/index.php/vod/detail/id/${id}.html`; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - - const response = await fetch(detailUrl, { - headers: API_CONFIG.detail.headers, - signal: controller.signal, - }); - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`详情页请求失败: ${response.status}`); - } - - const html = await response.text(); - let matches: string[] = []; - - if (apiSite.key === "ffzy") { - const ffzyPattern = /\$(https?:\/\/[^"'\s]+?\/\d{8}\/\d+_[a-f0-9]+\/index\.m3u8)/g; - matches = html.match(ffzyPattern) || []; - } - - if (matches.length === 0) { - const generalPattern = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g; - matches = html.match(generalPattern) || []; - } - - matches = Array.from(new Set(matches)).map((link: string) => { - link = link.substring(1); - const parenIndex = link.indexOf("("); - return parenIndex > 0 ? link.substring(0, parenIndex) : link; - }); - - const titleMatch = html.match(/]*>([^<]+)<\/h1>/); - const titleText = titleMatch ? titleMatch[1].trim() : ""; - const descMatch = html.match(/]*class=["']sketch["'][^>]*>([\s\S]*?)<\/div>/); - const descText = descMatch ? cleanHtmlTags(descMatch[1]) : ""; - const coverMatch = html.match(/(https?:\/\/[^"'\s]+?\.jpg)/g); - const coverUrl = coverMatch ? coverMatch[0].trim() : ""; - - return { - id, - title: titleText, - poster: coverUrl, - desc: descText, - source_name: apiSite.name, - source: apiSite.key, - }; -} - -async function getDetailFromApi(apiSite: ApiSite, id: string): Promise { - const detailUrl = `${apiSite.api}${API_CONFIG.detail.path}${id}`; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - - const response = await fetch(detailUrl, { - headers: API_CONFIG.detail.headers, - signal: controller.signal, - }); - clearTimeout(timeoutId); - - if (!response.ok) { - throw new Error(`详情请求失败: ${response.status}`); - } - - const data = await response.json(); - if (!data || !data.list || !Array.isArray(data.list) || data.list.length === 0) { - throw new Error("获取到的详情内容无效"); - } - - const videoDetail = data.list[0]; - let episodes: string[] = []; - - if (videoDetail.vod_play_url) { - const playSources = videoDetail.vod_play_url.split("$$$"); - if (playSources.length > 0) { - const mainSource = playSources[0]; - const episodeList = mainSource.split("#"); - episodes = episodeList - .map((ep: string) => { - const parts = ep.split("$"); - return parts.length > 1 ? parts[1] : ""; - }) - .filter((url: string) => url && (url.startsWith("http://") || url.startsWith("https://"))); - } - } - - if (episodes.length === 0 && videoDetail.vod_content) { - const matches = videoDetail.vod_content.match(M3U8_PATTERN) || []; - episodes = matches.map((link: string) => link.replace(/^\$/, "")); - } - - return { - id, - title: videoDetail.vod_name, - poster: videoDetail.vod_pic, - desc: cleanHtmlTags(videoDetail.vod_content), - type: videoDetail.type_name, - year: videoDetail.vod_year?.match(/\d{4}/)?.[0] || "", - area: videoDetail.vod_area, - director: videoDetail.vod_director, - actor: videoDetail.vod_actor, - remarks: videoDetail.vod_remarks, - source_name: apiSite.name, - source: apiSite.key, - }; -} - -async function getVideoDetail(id: string, sourceCode: string): Promise { - if (!id) { - throw new Error("缺少视频ID参数"); - } - if (!/^[\w-]+$/.test(id)) { - throw new Error("无效的视频ID格式"); - } - const apiSites = getApiSites(); - const apiSite = apiSites.find((site) => site.key === sourceCode); - if (!apiSite) { - throw new Error("无效的API来源"); - } - if (apiSite.detail) { - return handleSpecialSourceDetail(id, apiSite); - } - return getDetailFromApi(apiSite, id); -} - -router.get("/", async (req: Request, res: Response) => { - const id = req.query.id as string; - const sourceCode = req.query.source as string; - - if (!id || !sourceCode) { - return res.status(400).json({ error: "缺少必要参数" }); - } - - try { - const result = await getVideoDetail(id, sourceCode); - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json(result); - } catch (error) { - res.status(500).json({ error: (error as Error).message }); - } -}); - -export default router; diff --git a/backend/src/routes/douban.ts b/backend/src/routes/douban.ts deleted file mode 100644 index 829eb70..0000000 --- a/backend/src/routes/douban.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Router, Request, Response } from "express"; -import { getCacheTime } from "../config"; - -const router = Router(); - -// --- Interfaces --- -interface DoubanItem { - title: string; - poster: string; - rate: string; -} - -interface DoubanResponse { - code: number; - message: string; - list: DoubanItem[]; -} - -interface DoubanApiResponse { - subjects: Array<{ - title: string; - cover: string; - rate: string; - }>; -} - -// --- Helper Functions --- - -async function fetchDoubanData(url: string): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - - const fetchOptions = { - signal: controller.signal, - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", - Referer: "https://movie.douban.com/", - Accept: "application/json, text/plain, */*", - }, - }; - - try { - const response = await fetch(url, fetchOptions); - clearTimeout(timeoutId); - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return await response.json(); - } catch (error) { - clearTimeout(timeoutId); - throw error; - } -} - -async function handleTop250(pageStart: number, res: Response) { - const target = `https://movie.douban.com/top250?start=${pageStart}&filter=`; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 10000); - - const fetchOptions = { - signal: controller.signal, - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", - Referer: "https://movie.douban.com/", - Accept: - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - }, - }; - - try { - const fetchResponse = await fetch(target, fetchOptions); - clearTimeout(timeoutId); - - if (!fetchResponse.ok) { - throw new Error(`HTTP error! Status: ${fetchResponse.status}`); - } - - const html = await fetchResponse.text(); - const moviePattern = - /
[\s\S]*?]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]+)<\/span>[\s\S]*?<\/div>/g; - const movies: DoubanItem[] = []; - let match; - - while ((match = moviePattern.exec(html)) !== null) { - const title = match[1]; - const cover = match[2]; - const rate = match[3] || ""; - const processedCover = cover.replace(/^http:/, "https:"); - movies.push({ title, poster: processedCover, rate }); - } - - const apiResponse: DoubanResponse = { - code: 200, - message: "获取成功", - list: movies, - }; - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json(apiResponse); - } catch (error) { - clearTimeout(timeoutId); - res.status(500).json({ - error: "获取豆瓣 Top250 数据失败", - details: (error as Error).message, - }); - } -} - -// --- Main Route Handler --- - -router.get("/", async (req: Request, res: Response) => { - const { type, tag } = req.query; - const pageSize = parseInt((req.query.pageSize as string) || "16"); - const pageStart = parseInt((req.query.pageStart as string) || "0"); - - if (!type || !tag) { - return res.status(400).json({ error: "缺少必要参数: type 或 tag" }); - } - if (typeof type !== "string" || !["tv", "movie"].includes(type)) { - return res.status(400).json({ error: "type 参数必须是 tv 或 movie" }); - } - if (pageSize < 1 || pageSize > 100) { - return res.status(400).json({ error: "pageSize 必须在 1-100 之间" }); - } - if (pageStart < 0) { - return res.status(400).json({ error: "pageStart 不能小于 0" }); - } - - if (tag === "top250") { - return handleTop250(pageStart, res); - } - - const target = `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageSize}&page_start=${pageStart}`; - - try { - const doubanData = await fetchDoubanData(target); - const list: DoubanItem[] = doubanData.subjects.map((item) => ({ - title: item.title, - poster: item.cover, - rate: item.rate, - })); - - const response: DoubanResponse = { - code: 200, - message: "获取成功", - list: list, - }; - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json(response); - } catch (error) { - res.status(500).json({ - error: "获取豆瓣数据失败", - details: (error as Error).message, - }); - } -}); - -export default router; diff --git a/backend/src/routes/favorites.ts b/backend/src/routes/favorites.ts deleted file mode 100644 index c7fa2fe..0000000 --- a/backend/src/routes/favorites.ts +++ /dev/null @@ -1,67 +0,0 @@ -import express, { Request, Response } from "express"; -import fs from "fs/promises"; -import path from "path"; - -const router = express.Router(); -const dataPath = path.join(__dirname, "..", "data", "favorites.json"); - -// Helper function to read data -const readFavorites = async () => { - try { - const data = await fs.readFile(dataPath, "utf-8"); - return JSON.parse(data); - } catch (error) { - // If file doesn't exist or is invalid json, return empty object - return {}; - } -}; - -// Helper function to write data -const writeFavorites = async (data: any) => { - await fs.writeFile(dataPath, JSON.stringify(data, null, 2), "utf-8"); -}; - -// GET /api/favorites -router.get("/favorites", async (req: Request, res: Response) => { - const { key } = req.query; - const favorites = await readFavorites(); - - if (key) { - res.json(favorites[key as string] || null); - } else { - res.json(favorites); - } -}); - -// POST /api/favorites -router.post("/favorites", async (req: Request, res: Response) => { - const { key, favorite } = req.body; - - if (!key || !favorite) { - return res.status(400).json({ message: "Missing key or favorite data" }); - } - - const favorites = await readFavorites(); - favorites[key] = { ...favorite, save_time: Math.floor(Date.now() / 1000) }; - await writeFavorites(favorites); - - res.json({ success: true }); -}); - -// DELETE /api/favorites -router.delete("/favorites", async (req: Request, res: Response) => { - const { key } = req.query; - let favorites = await readFavorites(); - - if (key) { - delete favorites[key as string]; - } else { - // Clear all favorites if no key is provided - favorites = {}; - } - - await writeFavorites(favorites); - res.json({ success: true }); -}); - -export default router; diff --git a/backend/src/routes/image-proxy.ts b/backend/src/routes/image-proxy.ts deleted file mode 100644 index 9c8d4b6..0000000 --- a/backend/src/routes/image-proxy.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Router, Request, Response } from "express"; -import { Readable } from "node:stream"; - -const router = Router(); - -router.get("/", async (req: Request, res: Response) => { - const imageUrl = req.query.url as string; - - if (!imageUrl) { - return res.status(400).send("Missing image URL"); - } - - try { - const imageResponse = await fetch(imageUrl, { - headers: { - Referer: "https://movie.douban.com/", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", - }, - }); - - if (!imageResponse.ok) { - return res.status(imageResponse.status).send(imageResponse.statusText); - } - - const contentType = imageResponse.headers.get("content-type"); - if (contentType) { - res.setHeader("Content-Type", contentType); - } - - if (imageResponse.body) { - const nodeStream = Readable.fromWeb(imageResponse.body as any); - nodeStream.pipe(res); - } else { - res.status(500).send("Image response has no body"); - } - } catch (error) { - console.info("Image proxy error:", error); - res.status(500).send("Error fetching image"); - } -}); - -export default router; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts deleted file mode 100644 index b0b455f..0000000 --- a/backend/src/routes/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Router } from "express"; -import searchRouter from "./search"; -import detailRouter from "./detail"; -import doubanRouter from "./douban"; -import imageProxyRouter from "./image-proxy"; -import serverConfigRouter from "./server-config"; -import loginRouter from "./login"; -import favoritesRouter from "./favorites"; -import playRecordsRouter from "./playrecords"; -import searchHistoryRouter from "./searchhistory"; - -const router = Router(); - -router.use(serverConfigRouter); -router.use(loginRouter); -router.use(favoritesRouter); -router.use(playRecordsRouter); -router.use(searchHistoryRouter); -router.use("/search", searchRouter); -router.use("/detail", detailRouter); -router.use("/douban", doubanRouter); -router.use("/image-proxy", imageProxyRouter); - -export default router; diff --git a/backend/src/routes/login.ts b/backend/src/routes/login.ts deleted file mode 100644 index 3555417..0000000 --- a/backend/src/routes/login.ts +++ /dev/null @@ -1,58 +0,0 @@ -import express, { Request, Response } from "express"; -import dotenv from "dotenv"; - -dotenv.config(); - -const router = express.Router(); -const username = process.env.USERNAME; -const password = process.env.PASSWORD; - -/** - * @api {post} /api/login User Login - * @apiName UserLogin - * @apiGroup User - * - * @apiBody {String} username User's username. - * @apiBody {String} password User's password. - * - * @apiSuccess {Boolean} ok Indicates if the login was successful. - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "ok": true - * } - * - * @apiError {String} message Error message. - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 400 Bad Request - * { - * "message": "Invalid password" - * } - */ -router.post("/login", (req: Request, res: Response) => { - const { username: inputUsername, password: inputPassword } = req.body; - - // Compatibility with old versions, if username is not set, only password is required - if (!username || !password) { - if (inputPassword === password) { - res.cookie("auth", "true", { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); - return res.json({ ok: true }); - } else if (!password) { - // If no password is set, login is always successful. - return res.json({ ok: true }); - } else { - return res.status(400).json({ message: "Invalid password" }); - } - } - - if (inputUsername === username && inputPassword === password) { - res.cookie("auth", "true", { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); - res.json({ ok: true }); - } else { - res.status(400).json({ message: "Invalid username or password" }); - } -}); - -export default router; diff --git a/backend/src/routes/playrecords.ts b/backend/src/routes/playrecords.ts deleted file mode 100644 index 43f6255..0000000 --- a/backend/src/routes/playrecords.ts +++ /dev/null @@ -1,59 +0,0 @@ -import express, { Request, Response } from "express"; -import fs from "fs/promises"; -import path from "path"; - -const router = express.Router(); -const dataPath = path.join(__dirname, "..", "data", "playrecords.json"); - -// Helper function to read data -const readPlayRecords = async () => { - try { - const data = await fs.readFile(dataPath, "utf-8"); - return JSON.parse(data); - } catch (error) { - return {}; - } -}; - -// Helper function to write data -const writePlayRecords = async (data: any) => { - await fs.writeFile(dataPath, JSON.stringify(data, null, 2), "utf-8"); -}; - -// GET /api/playrecords -router.get("/playrecords", async (req: Request, res: Response) => { - const records = await readPlayRecords(); - res.json(records); -}); - -// POST /api/playrecords -router.post("/playrecords", async (req: Request, res: Response) => { - const { key, record } = req.body; - - if (!key || !record) { - return res.status(400).json({ message: "Missing key or record data" }); - } - - const records = await readPlayRecords(); - records[key] = { ...record, time: Math.floor(Date.now() / 1000) }; - await writePlayRecords(records); - - res.json({ success: true }); -}); - -// DELETE /api/playrecords -router.delete("/playrecords", async (req: Request, res: Response) => { - const { key } = req.query; - let records = await readPlayRecords(); - - if (key) { - delete records[key as string]; - } else { - records = {}; - } - - await writePlayRecords(records); - res.json({ success: true }); -}); - -export default router; diff --git a/backend/src/routes/search.ts b/backend/src/routes/search.ts deleted file mode 100644 index ec47b87..0000000 --- a/backend/src/routes/search.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { Router, Request, Response } from "express"; -import { API_CONFIG, ApiSite, getApiSites, getCacheTime } from "../config"; -import { cleanHtmlTags } from "../utils"; - -const router = Router(); - -// 根据环境变量决定最大搜索页数,默认 5 -const MAX_SEARCH_PAGES: number = Number(process.env.SEARCH_MAX_PAGE) || 5; - -export interface SearchResult { - id: string; - title: string; - poster: string; - episodes: string[]; - source: string; - source_name: string; - class?: string; - year: string; - desc?: string; - type_name?: string; -} - -interface ApiSearchItem { - vod_id: string; - vod_name: string; - vod_pic: string; - vod_remarks?: string; - vod_play_url?: string; - vod_class?: string; - vod_year?: string; - vod_content?: string; - type_name?: string; -} - -async function searchFromApi( - apiSite: ApiSite, - query: string -): Promise { - try { - const apiBaseUrl = apiSite.api; - const apiUrl = - apiBaseUrl + API_CONFIG.search.path + encodeURIComponent(query); - const apiName = apiSite.name; - - // 添加超时处理 - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 8000); - - const response = await fetch(apiUrl, { - headers: API_CONFIG.search.headers, - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - return []; - } - - const data = await response.json(); - - console.log( - "apiUrl", - apiSite.name, - "response status", - response.ok, - "response data", - data.list.length - ); - - if ( - !data || - !data.list || - !Array.isArray(data.list) || - data.list.length === 0 - ) { - return []; - } - // 处理第一页结果 - const results = data.list.map((item: ApiSearchItem) => { - let episodes: string[] = []; - - // 使用正则表达式从 vod_play_url 提取 m3u8 链接 - if (item.vod_play_url) { - const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g; - // 先用 $$$ 分割 - const vod_play_url_array = item.vod_play_url.split("$$$"); - // 对每个分片做匹配,取匹配到最多的作为结果 - vod_play_url_array.forEach((url: string) => { - const matches = url.match(m3u8Regex) || []; - if (matches.length > episodes.length) { - episodes = matches; - } - }); - } - - episodes = Array.from(new Set(episodes)).map((link: string) => { - link = link.substring(1); // 去掉开头的 $ - const parenIndex = link.indexOf("("); - return parenIndex > 0 ? link.substring(0, parenIndex) : link; - }); - - return { - id: item.vod_id, - title: item.vod_name, - poster: item.vod_pic, - episodes, - source: apiSite.key, - source_name: apiName, - class: item.vod_class, - year: item.vod_year ? item.vod_year.match(/\d{4}/)?.[0] || "" : "", - desc: cleanHtmlTags(item.vod_content || ""), - type_name: item.type_name, - }; - }); - - // 获取总页数 - const pageCount = data.pagecount || 1; - // 确定需要获取的额外页数 - const pagesToFetch = Math.min(pageCount - 1, MAX_SEARCH_PAGES - 1); - - // 如果有额外页数,获取更多页的结果 - if (pagesToFetch > 0) { - const additionalPagePromises = []; - - for (let page = 2; page <= pagesToFetch + 1; page++) { - const pageUrl = - apiBaseUrl + - API_CONFIG.search.pagePath - .replace("{query}", encodeURIComponent(query)) - .replace("{page}", page.toString()); - - const pagePromise = (async () => { - try { - const pageController = new AbortController(); - const pageTimeoutId = setTimeout( - () => pageController.abort(), - 8000 - ); - - const pageResponse = await fetch(pageUrl, { - headers: API_CONFIG.search.headers, - signal: pageController.signal, - }); - - clearTimeout(pageTimeoutId); - - if (!pageResponse.ok) return []; - - const pageData = await pageResponse.json(); - - if (!pageData || !pageData.list || !Array.isArray(pageData.list)) - return []; - - return pageData.list.map((item: ApiSearchItem) => { - let episodes: string[] = []; - - if (item.vod_play_url) { - const m3u8Regex = /\$(https?:\/\/[^"'\s]+?\.m3u8)/g; - episodes = item.vod_play_url.match(m3u8Regex) || []; - } - - episodes = Array.from(new Set(episodes)).map((link: string) => { - link = link.substring(1); // 去掉开头的 $ - const parenIndex = link.indexOf("("); - return parenIndex > 0 ? link.substring(0, parenIndex) : link; - }); - - return { - id: item.vod_id, - title: item.vod_name, - poster: item.vod_pic, - episodes, - source: apiSite.key, - source_name: apiName, - class: item.vod_class, - year: item.vod_year - ? item.vod_year.match(/\d{4}/)?.[0] || "" - : "", - desc: cleanHtmlTags(item.vod_content || ""), - type_name: item.type_name, - }; - }); - } catch (error) { - return []; - } - })(); - - additionalPagePromises.push(pagePromise); - } - - const additionalResults = await Promise.all(additionalPagePromises); - - additionalResults.forEach((pageResults) => { - if (pageResults.length > 0) { - results.push(...pageResults); - } - }); - } - - return results; - } catch (error) { - return []; - } -} - -router.get("/", async (req: Request, res: Response) => { - const query = req.query.q as string; - - if (!query) { - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - return res.json({ results: [] }); - } - - const apiSites = getApiSites(); - const searchPromises = apiSites.map((site) => searchFromApi(site, query)); - - try { - const results = await Promise.all(searchPromises); - const flattenedResults = results.flat(); - const cacheTime = getCacheTime(); - - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json({ results: flattenedResults }); - } catch (error) { - res.status(500).json({ error: "搜索失败" }); - } -}); - -// 按资源 url 单个获取数据 -router.get("/one", async (req: Request, res: Response) => { - const { resourceId, q } = req.query; - - if (!resourceId || !q) { - return res.status(400).json({ error: "resourceId and q are required" }); - } - - const apiSites = getApiSites(); - const apiSite = apiSites.find((site) => site.key === (resourceId as string)); - - if (!apiSite) { - return res.status(404).json({ error: "Resource not found" }); - } - - try { - const results = await searchFromApi(apiSite, q as string); - const result = results.filter((r) => r.title === (q as string)); - - if (results) { - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json({results: result}); - } else { - res.status(404).json({ error: "Resource not found with the given query" }); - } - } catch (error) { - res.status(500).json({ error: "Failed to fetch resource details" }); - } -}); - -// 获取所有可用的资源列表 -router.get("/resources", async (req: Request, res: Response) => { - const apiSites = getApiSites(); - const cacheTime = getCacheTime(); - res.setHeader("Cache-Control", `public, max-age=${cacheTime}`); - res.json(apiSites); -}); - -export default router; diff --git a/backend/src/routes/searchhistory.ts b/backend/src/routes/searchhistory.ts deleted file mode 100644 index f814018..0000000 --- a/backend/src/routes/searchhistory.ts +++ /dev/null @@ -1,66 +0,0 @@ -import express, { Request, Response } from "express"; -import fs from "fs/promises"; -import path from "path"; - -const router = express.Router(); -const dataPath = path.join(__dirname, "..", "data", "searchhistory.json"); - -// Helper function to read data -const readSearchHistory = async (): Promise => { - try { - const data = await fs.readFile(dataPath, "utf-8"); - return JSON.parse(data); - } catch (error) { - return []; - } -}; - -// Helper function to write data -const writeSearchHistory = async (data: string[]) => { - await fs.writeFile(dataPath, JSON.stringify(data, null, 2), "utf-8"); -}; - -// GET /api/searchhistory -router.get("/searchhistory", async (req: Request, res: Response) => { - const history = await readSearchHistory(); - res.json(history); -}); - -// POST /api/searchhistory -router.post("/searchhistory", async (req: Request, res: Response) => { - const { keyword } = req.body; - - if (!keyword) { - return res.status(400).json({ message: "Missing keyword" }); - } - - let history = await readSearchHistory(); - // Remove keyword if it already exists to move it to the front - history = history.filter((item) => item !== keyword); - // Add to the beginning of the array - history.unshift(keyword); - // Optional: Limit history size - if (history.length > 100) { - history = history.slice(0, 100); - } - - await writeSearchHistory(history); - res.json(history); -}); - -// DELETE /api/searchhistory -router.delete("/searchhistory", async (req: Request, res: Response) => { - const { keyword } = req.query; - let history = await readSearchHistory(); - - if (keyword) { - history = history.filter((item) => item !== keyword); - } else { - history = []; - } - - await writeSearchHistory(history); - res.json({ success: true }); -}); - -export default router; diff --git a/backend/src/routes/server-config.ts b/backend/src/routes/server-config.ts deleted file mode 100644 index fd06fce..0000000 --- a/backend/src/routes/server-config.ts +++ /dev/null @@ -1,38 +0,0 @@ -import express, { Request, Response } from "express"; -import { getConfig } from "../config"; - -const router = express.Router(); - -/** - * @api {get} /api/server-config Get Server Configuration - * @apiName GetServerConfig - * @apiGroup Server - * - * @apiSuccess {String} SiteName The name of the site. - * @apiSuccess {String} StorageType The storage type used by the server ("localstorage" or "database"). - * - * @apiSuccessExample {json} Success-Response (LocalStorage): - * HTTP/1.1 200 OK - * { - * "SiteName": "OrionTV-Local", - * "StorageType": "localstorage" - * } - * - * @apiSuccessExample {json} Success-Response (Database): - * HTTP/1.1 200 OK - * { - * "SiteName": "OrionTV-Cloud", - * "StorageType": "database" - * } - */ -router.get("/server-config", (req: Request, res: Response) => { - const config = getConfig(); - const storageType = config.storage?.type || "database"; // Default to 'database' if not specified - - res.json({ - SiteName: storageType === "localstorage" ? "OrionTV-Local" : "OrionTV-Cloud", - StorageType: storageType, - }); -}); - -export default router; diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts deleted file mode 100644 index 4d67937..0000000 --- a/backend/src/types/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Data structure for play records -export interface PlayRecord { - title: string; - source_name: string; - poster: string; - index: number; // Episode number - total_episodes: number; // Total number of episodes - play_time: number; // Play progress in seconds - total_time: number; // Total duration in seconds - save_time: number; // Timestamp of when the record was saved - user_id: number; // User ID, always 0 in this version -} - -// You can add other shared types here -export interface VideoDetail { - id: string; - title: string; - poster: string; - source: string; - source_name: string; - desc?: string; - type?: string; - year?: string; - area?: string; - director?: string; - actor?: string; - remarks?: string; -} diff --git a/backend/src/utils/index.ts b/backend/src/utils/index.ts deleted file mode 100644 index e4c153d..0000000 --- a/backend/src/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function cleanHtmlTags(text: string): string { - if (!text) return ""; - return text - .replace(/<[^>]+>/g, "\n") // 将 HTML 标签替换为换行 - .replace(/\n+/g, "\n") // 将多个连续换行合并为一个 - .replace(/[ \t]+/g, " ") // 将多个连续空格和制表符合并为一个空格,但保留换行符 - .replace(/^\n+|\n+$/g, "") // 去掉首尾换行 - .replace(/ /g, " ") // 将   替换为空格 - .trim(); // 去掉首尾空格 -} diff --git a/backend/tsconfig.json b/backend/tsconfig.json deleted file mode 100644 index e2082c5..0000000 --- a/backend/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "CommonJS", - "rootDir": "./src", - "outDir": "./dist", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.spec.ts"] -} diff --git a/backend/vercel.json b/backend/vercel.json deleted file mode 100644 index a5afa2e..0000000 --- a/backend/vercel.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 2, - "builds": [ - { - "src": "src/index.ts", - "use": "@vercel/node", - "config": { - "includeFiles": ["./config.json"] - } - } - ], - "routes": [ - { - "src": "/(.*)", - "dest": "src/index.ts" - } - ] -} diff --git a/backend/yarn.lock b/backend/yarn.lock deleted file mode 100644 index d65c895..0000000 --- a/backend/yarn.lock +++ /dev/null @@ -1,1025 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cors@^2.8.17": - version "2.8.19" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" - integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node@*": - version "24.0.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.4.tgz#dbae889912bda33a7f57669fb8587c1a56bc0c1f" - integrity sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA== - dependencies: - undici-types "~7.8.0" - -"@types/node@^20.14.2": - version "20.19.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.1.tgz#cef8bc04aaae86824b5bbe2570769358592bcc59" - integrity sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA== - dependencies: - undici-types "~6.21.0" - -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/strip-bom@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" - integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== - -"@types/strip-json-comments@0.0.30": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" - integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -chokidar@^3.5.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dotenv@^16.4.5: - version "16.6.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.0.tgz#b96bd4e7c2043ba5f51cbe1b8f9347850c864850" - integrity sha512-Omf1L8paOy2VJhILjyhrhqwLIdstqm1BvcDPKg4NGAlkwEu9ODyrFbvk8UymUOMCT+HXo31jg1lArIrVAAhuGA== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -dynamic-dedupe@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" - integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== - dependencies: - xtend "^4.0.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -express@^4.19.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -resolve@^1.0.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rimraf@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.0.6: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -source-map-support@^0.5.12: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-json-comments@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -ts-node-dev@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" - integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== - dependencies: - chokidar "^3.5.1" - dynamic-dedupe "^0.3.0" - minimist "^1.2.6" - mkdirp "^1.0.4" - resolve "^1.0.0" - rimraf "^2.6.1" - source-map-support "^0.5.12" - tree-kill "^1.2.2" - ts-node "^10.4.0" - tsconfig "^7.0.0" - -ts-node@^10.4.0: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tsconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" - integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== - dependencies: - "@types/strip-bom" "^3.0.0" - "@types/strip-json-comments" "0.0.30" - strip-bom "^3.0.0" - strip-json-comments "^2.0.0" - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@^5.4.5: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - -undici-types@~7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" - integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/components/CustomScrollView.tsx b/components/CustomScrollView.tsx index ab45e42..18ae69a 100644 --- a/components/CustomScrollView.tsx +++ b/components/CustomScrollView.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from "react"; +import React, { useCallback } from "react"; import { View, StyleSheet, ScrollView, Dimensions, ActivityIndicator } from "react-native"; import { ThemedText } from "@/components/ThemedText"; @@ -91,7 +91,7 @@ const CustomScrollView: React.FC = ({ <> {/* Render content in a grid layout */} {Array.from({ length: Math.ceil(data.length / numColumns) }).map((_, rowIndex) => ( - + {data.slice(rowIndex * numColumns, (rowIndex + 1) * numColumns).map((item, index) => ( {renderItem({ item, index: rowIndex * numColumns + index })} @@ -121,6 +121,11 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, paddingBottom: 20, }, + rowContainer: { + flexDirection: "row", + justifyContent: "flex-start", + flexWrap: "wrap", + }, itemContainer: { margin: 8, alignItems: "center", diff --git a/components/LoginModal.tsx b/components/LoginModal.tsx index 4802b2f..fa9cf1e 100644 --- a/components/LoginModal.tsx +++ b/components/LoginModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from "react"; -import { Modal, View, TextInput, StyleSheet, ActivityIndicator } from "react-native"; +import { Modal, View, TextInput, StyleSheet, ActivityIndicator, Alert } from "react-native"; import { usePathname } from "expo-router"; import Toast from "react-native-toast-message"; import useAuthStore from "@/stores/authStore"; @@ -55,6 +55,13 @@ const LoginModal = () => { hideLoginModal(); setUsername(""); setPassword(""); + + // Show disclaimer alert after successful login + Alert.alert( + "免责声明", + "本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。", + [{ text: "确定" }] + ); } catch { Toast.show({ type: "error", text1: "登录失败", text2: "用户名或密码错误" }); } finally { diff --git a/components/settings/APIConfigSection.tsx b/components/settings/APIConfigSection.tsx index 306fd33..70bdb2b 100644 --- a/components/settings/APIConfigSection.tsx +++ b/components/settings/APIConfigSection.tsx @@ -76,7 +76,7 @@ export const APIConfigSection = forwardRef {}); - ``` - -#### 2. 动画效果 (react-native-reanimated) -- **影响文件**: `components/VideoCard.tv.tsx` -- **风险**: 动画性能可能下降 -- **关键代码**: - ```typescript - import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated"; - ``` - -### 🟢 低风险组件 - -#### 1. 状态管理 (zustand) -- **影响**: 与React Native版本无直接关系 -- **风险**: 极低 - -#### 2. 数据存储 (AsyncStorage) -- **影响文件**: `services/storage.ts` -- **风险**: 极低,API稳定 - -## 平台特定风险 - -### Android API 23 → 21 降级影响 - -#### 1. 运行时权限模型 -- **API 23+**: 需要运行时权限请求 -- **API 21-22**: 安装时权限模型 -- **影响**: 网络权限处理可能需要调整 - -#### 2. 网络安全配置 -- **风险**: HTTP cleartext流量处理 -- **当前配置**: `android.usesCleartextTraffic = true` -- **建议**: 保持当前配置确保向后兼容 - -#### 3. 后台服务限制 -- **API 23+**: 更严格的后台服务限制 -- **API 21-22**: 相对宽松的后台服务策略 -- **影响**: 远程控制服务可能表现不同 - -## 实施步骤 - -### 1. 准备阶段 -```bash -# 1. 备份当前项目 -git checkout -b android-5-compatibility - -# 2. 清理现有依赖 -rm -rf node_modules -rm yarn.lock -``` - -### 2. 版本降级 -```bash -# 3. 修改package.json依赖版本 -# 4. 重新安装依赖 -yarn install - -# 5. 清理原生代码 -yarn prebuild-tv --clean -``` - -### 3. 配置修改 -```bash -# 6. 修改android/build.gradle -# 7. 更新app.json配置 -# 8. 复制TV相关配置 -yarn copy-config -``` - -### 4. 测试构建 -```bash -# 9. 本地构建测试 -yarn build-local - -# 10. 运行测试 -yarn test -``` - -## 测试清单 - -### 核心功能测试 -- [ ] 视频播放、暂停、进度控制 -- [ ] 遥控器所有按键响应(上下左右、选择、菜单、返回) -- [ ] 长按快进/快退功能 -- [ ] 页面导航和参数传递 -- [ ] 焦点管理和视觉反馈 - -### TV特定功能测试 -- [ ] 控制条自动显示/隐藏 -- [ ] 剧集切换功能 -- [ ] 远程控制HTTP服务 -- [ ] 设置页面各项配置 -- [ ] 搜索功能 - -### 兼容性测试 -- [ ] Android 5.0真机测试 -- [ ] Android TV模拟器测试 -- [ ] Apple TV模拟器测试 -- [ ] 不同屏幕尺寸适配 -- [ ] 内存使用情况 -- [ ] 启动性能测试 - -## 依赖版本对照表 - -| 组件 | 当前版本 | 目标版本 | 风险等级 | 备注 | -|------|----------|----------|----------|------| -| react-native-tvos | ~0.74.2-0 | ~0.73.8-0 | 🔴 高 | TV功能核心 | -| expo | ~51.0.13 | ~50.0.0 | 🔴 高 | 框架基础 | -| expo-av | ~14.0.7 | ~13.10.x | 🔴 高 | 视频播放 | -| expo-router | ~3.5.16 | ~3.4.x | 🔴 高 | 路由导航 | -| react-native-reanimated | ~3.10.1 | ~3.8.x | 🟡 中 | 动画效果 | -| react-native-tcp-socket | ^6.0.6 | ^6.0.4 | 🟡 中 | 网络服务 | -| zustand | ^5.0.6 | ^5.0.6 | 🟢 低 | 状态管理 | -| @react-native-async-storage/async-storage | ^2.2.0 | ^2.1.x | 🟢 低 | 数据存储 | - -## 潜在问题和解决方案 - -### 1. 视频播放问题 -**问题**: expo-av版本降级可能导致某些视频格式不支持 -**解决方案**: -- 测试主要视频格式(MP4, M3U8) -- 必要时实现格式转换 -- 提供播放失败的友好提示 - -### 2. 遥控器响应问题 -**问题**: TV事件处理可能有差异 -**解决方案**: -- 仔细测试所有遥控器按键 -- 调整事件处理逻辑 -- 增加兼容性检查 - -### 3. 路由导航问题 -**问题**: 页面跳转参数传递可能有变化 -**解决方案**: -- 测试所有页面跳转 -- 验证参数正确传递 -- 必要时调整路由配置 - -### 4. 动画性能问题 -**问题**: 动画可能在低端设备上表现不佳 -**解决方案**: -- 简化动画效果 -- 增加性能检测 -- 提供动画开关选项 - -## 建议与结论 - -### 风险总结 -- **总体风险等级**: 🔴 **高等风险** -- **主要风险点**: 视频播放、遥控器功能、路由导航 -- **预计工作量**: 2-3周开发 + 1-2周测试 - -### 成本效益分析 -- **开发成本**: 高(需要大量测试和调试) -- **维护成本**: 高(使用较旧版本,安全更新有限) -- **用户覆盖**: 低(Android 5用户占比通常<2%) - -### 最终建议 -**不建议进行降级**,原因如下: -1. 技术风险高,可能影响核心功能稳定性 -2. 维护成本高,需要长期支持多个版本 -3. 用户收益有限,Android 5用户占比极低 -4. 与业界趋势不符,各大平台都在提升最低版本要求 - -### 替代方案 -1. **统计用户分布**: 收集实际用户设备数据,确认Android 5用户占比 -2. **渐进式升级**: 引导用户升级设备,提供升级指南 -3. **精简版本**: 为老设备提供功能精简的独立版本 -4. **Web版本**: 提供Web端访问方式作为补充 - -## 参考资料 - -- [React Native 0.74 Release Notes](https://reactnative.dev/blog/2024/04/22/release-0.74) -- [Expo SDK 51 Changelog](https://expo.dev/changelog/2024-05-07-sdk-51) -- [React Native TV OS Documentation](https://github.com/react-native-tvos/react-native-tvos) -- [Android API Level Distribution](https://developer.android.com/about/dashboards) diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index c3c1994..0000000 --- a/docs/API.md +++ /dev/null @@ -1,313 +0,0 @@ -### 服务器配置 - -- **接口地址**: `/api/server-config` -- **请求方法**: `GET` -- **功能说明**: 获取服务器配置信息 -- **请求参数**: 无 -- **返回格式**: - ```json - { - "SiteName": "string", - "StorageType": "string" - } - ``` - StorageType 可选值: -- "localstorage" -- "redis" - -localstorage 方式部署的实例,收藏、播放记录和搜索历史无服务器同步,客户端自行处理即可 - -localstorage 方式部署的实例,登录时只需输入密码,无用户名 - -### 登录校验 - -- **接口地址**: `/api/login` -- **请求方法**: `POST` -- **功能说明**: 用户登录认证 -- **请求参数**: - ```json - { - "password": "string", // 必填,用户密码 - "username": "string" // 选填,用户名(非 localStorage 模式时必填) - } - ``` -- **返回格式**: - ```json - { - "ok": true - } - ``` -- **错误码**: - - `400`: 参数错误或密码错误 - - `500`: 服务器内部错误 - -response 会设置 set-cookie 的 auth 字段,用于后续请求的鉴权 - -后续的所有接口请求时都需要携带 auth 字段,否则会返回 401 错误 - -建议客户端保存用户输入的用户名和密码,在每次 app 启动时请求登录接口获取 cookie - -### 视频搜索接口 - -- **接口地址**: `/api/search` -- **请求方法**: `GET` -- **功能说明**: 搜索视频内容 -- **请求参数**: - - `q`: 搜索关键词(可选,不传返回空结果) -- **返回格式**: - ```json - { - "results": [ - { - "id": "string", // 视频在源站中的 id - "title": "string", // 视频标题 - "poster": "string", // 视频封面 - "source": "string", // 视频源站 key - "source_name": "string", // 视频源站名称 - "class": "string", // 视频分类 - "year": "string", // 视频年份 - "desc": "string", // 视频描述 - "type_name": "string", // 视频类型 - "douban_id": "string" // 视频豆瓣 id - } - ] - } - ``` -- **错误码**: - - `500`: 搜索失败 - -### 视频详情接口 - -- **接口地址**: `/api/detail` -- **请求方法**: `GET` -- **功能说明**: 获取视频详细信息 -- **请求参数**: - - `id`: 视频 ID(必填) - - `source`: 视频来源代码(必填) -- **返回格式**: - ```json - { - "id": "string", // 视频在源站中的 id - "title": "string", // 视频标题 - "poster": "string", // 视频封面 - "source": "string", // 视频源站 key - "source_name": "string", // 视频源站名称 - "class": "string", // 视频分类 - "year": "string", // 视频年份 - "desc": "string", // 视频描述 - "type_name": "string", // 视频类型 - "douban_id": "string" // 视频豆瓣 id - } - ``` -- **错误码**: - - `400`: 缺少必要参数或无效参数 - - `500`: 获取详情失败 - -### 豆瓣数据接口 - -- **接口地址**: `/api/douban` -- **请求方法**: `GET` -- **功能说明**: 获取豆瓣电影/电视剧数据 -- **请求参数**: - - `type`: 类型,必须是 `tv` 或 `movie`(必填) - - `tag`: 标签,如 `热门`、`最新` 等(必填) - - `pageSize`: 每页数量,1-100 之间(可选,默认 16) - - `pageStart`: 起始位置,不能小于 0(可选,默认 0) -- **返回格式**: - ```json - { - "code": 200, - "message": "获取成功", - "list": [ - { - "id": "string", - "title": "string", - "poster": "string", - "rate": "string" - } - ] - } - ``` -- **错误码**: - - `400`: 参数错误 - - `500`: 获取豆瓣数据失败 - -### 用户数据接口 - -#### 收藏管理 - -- **接口地址**: `/api/favorites` -- **请求方法**: `GET` / `POST` / `DELETE` -- **功能说明**: 管理用户收藏 -- **认证**: 需要认证 - -##### GET 请求 - 获取收藏 - -- **请求参数**: - - `key`: 收藏项 key(可选,格式为 `source+id`) -- **返回格式**: - - ```json - // 不带key参数时返回所有收藏 - { - "source+id": { - "title": "string", - "poster": "string", - "source_name": "string", - "save_time": 1234567890 - } - } - - // 带key参数时返回单个收藏或null - { - "title": "string", - "poster": "string", - "source_name": "string", - "save_time": 1234567890 - } - ``` - -##### POST 请求 - 添加收藏 - -- **请求参数**: - ```json - { - "key": "string", // 必填,格式为 source+id - "favorite": { - "title": "string", - "poster": "string", - "source_name": "string", - "save_time": 1234567890 - } - } - ``` -- **返回格式**: - ```json - { - "success": true - } - ``` - -##### DELETE 请求 - 删除收藏 - -- **请求参数**: - - `key`: 收藏项 key(可选,不传则清空所有收藏) -- **返回格式**: - - ```json - { - "success": true - } - ``` - -- **错误码**: - - `400`: 参数错误 - - `401`: 未认证 - - `500`: 服务器内部错误 - -#### 播放记录管理 - -- **接口地址**: `/api/playrecords` -- **请求方法**: `GET` / `POST` / `DELETE` -- **功能说明**: 管理用户播放记录 -- **认证**: 需要认证 - -##### GET 请求 - 获取播放记录 - -- **请求参数**: 无 -- **返回格式**: - ```json - { - "source+id": { - "title": "string", - "poster": "string", - "source_name": "string", - "index": 1, - "time": 1234567890 - } - } - ``` - -##### POST 请求 - 保存播放记录 - -- **请求参数**: - ```json - { - "key": "string", // 必填,格式为 source+id - "record": { - "title": "string", - "poster": "string", - "source_name": "string", - "index": 1, - "time": 1234567890 - } - } - ``` -- **返回格式**: - ```json - { - "success": true - } - ``` - -##### DELETE 请求 - 删除播放记录 - -- **请求参数**: - - `key`: 播放记录 key(可选,不传则清空所有记录) -- **返回格式**: - - ```json - { - "success": true - } - ``` - -- **错误码**: - - `400`: 参数错误 - - `401`: 未认证 - - `500`: 服务器内部错误 - -#### 搜索历史管理 - -- **接口地址**: `/api/searchhistory` -- **请求方法**: `GET` / `POST` / `DELETE` -- **功能说明**: 管理用户搜索历史 -- **认证**: 需要认证 - -##### GET 请求 - 获取搜索历史 - -- **请求参数**: 无 -- **返回格式**: - ```json - ["搜索关键词1", "搜索关键词2"] - ``` - -##### POST 请求 - 添加搜索历史 - -- **请求参数**: - ```json - { - "keyword": "string" // 必填,搜索关键词 - } - ``` -- **返回格式**: - ```json - ["搜索关键词1", "搜索关键词2"] - ``` - -##### DELETE 请求 - 删除搜索历史 - -- **请求参数**: - - `keyword`: 要删除的关键词(可选,不传则清空所有历史) -- **返回格式**: - - ```json - { - "success": true - } - ``` - -- **错误码**: - - `400`: 参数错误 - - `401`: 未认证 - - `500`: 服务器内部错误 diff --git a/docs/HTTP_SERVER_IMPLEMENTATION.md b/docs/HTTP_SERVER_IMPLEMENTATION.md deleted file mode 100644 index 2fb6aae..0000000 --- a/docs/HTTP_SERVER_IMPLEMENTATION.md +++ /dev/null @@ -1,305 +0,0 @@ -# OrionTV Native HTTP Server Implementation Documentation - -## Overview - -OrionTV implements a sophisticated native HTTP server solution that enables remote control functionality for the TV application. This implementation uses TCP sockets to create a custom HTTP server directly within the React Native application, providing a web-based remote control interface accessible from mobile devices. - -## Architecture - -### Core Components - -#### 1. TCPHttpServer (`/services/tcpHttpServer.ts`) - -A custom HTTP server implementation built on top of `react-native-tcp-socket` that handles raw TCP connections and implements HTTP protocol parsing and response formatting. - -**Key Features:** -- Custom HTTP request/response parsing -- Fixed port configuration (12346) -- Automatic IP address detection via `@react-native-community/netinfo` -- Support for GET and POST methods -- Error handling and connection management - -**Class Structure:** -```typescript -class TCPHttpServer { - private server: TcpSocket.Server | null = null; - private isRunning = boolean; - private requestHandler: RequestHandler | null = null; -} -``` - -**Core Methods:** -- `start()`: Initializes server and binds to `0.0.0.0:12346` -- `stop()`: Gracefully shuts down the server -- `setRequestHandler()`: Sets the request handling logic -- `parseHttpRequest()`: Parses raw HTTP request data -- `formatHttpResponse()`: Formats HTTP responses - -#### 2. RemoteControlService (`/services/remoteControlService.ts`) - -A service layer that wraps the TCPHttpServer and provides the remote control functionality with predefined routes and HTML interface. - -**API Endpoints:** -- `GET /` - Serves HTML remote control interface -- `POST /message` - Receives messages from mobile devices -- `POST /handshake` - Connection handshake for mobile clients - -**Features:** -- Built-in HTML interface generation -- JSON message parsing -- Callback-based event handling -- Error handling and validation - -#### 3. RemoteControlStore (`/stores/remoteControlStore.ts`) - -Zustand store that manages the remote control server state and provides React component integration. - -**State Management:** -```typescript -interface RemoteControlState { - isServerRunning: boolean; - serverUrl: string | null; - error: string | null; - isModalVisible: boolean; - lastMessage: string | null; - startServer: () => Promise; - stopServer: () => void; - showModal: () => void; - hideModal: () => void; - setMessage: (message: string) => void; -} -``` - -## Technical Implementation Details - -### HTTP Protocol Implementation - -#### Request Parsing -The server implements custom HTTP request parsing that handles: -- HTTP method and URL extraction -- Header parsing with case-insensitive keys -- Body content extraction -- Malformed request detection - -#### Response Formatting -Responses are formatted according to HTTP/1.1 specification: -- Status line with appropriate status codes (200, 400, 404, 500) -- Content-Length header calculation -- Connection: close header for stateless operation -- Proper CRLF line endings - -### Network Configuration - -#### IP Address Detection -The server automatically detects the device's IP address using `@react-native-community/netinfo`: -- Supports WiFi and Ethernet connections -- Validates network connectivity before starting -- Provides clear error messages for network issues - -#### Server Binding -- Binds to `0.0.0.0:12346` for universal access -- Fixed port configuration for consistency -- Supports all network interfaces on the device - -### Security Considerations - -#### Current Implementation -- No authentication mechanism -- Open access on local network -- Basic request validation -- Error handling prevents information disclosure - -#### Limitations -- Suitable only for local network use -- No HTTPS/TLS encryption -- No rate limiting or DDoS protection -- Assumes trusted network environment - -## Web Interface - -### HTML Template -The service provides a responsive web interface optimized for mobile devices: -- Dark theme design matching TV app aesthetics -- Touch-friendly controls with large buttons -- Real-time message sending capability -- Automatic handshake on page load - -### JavaScript Functionality -- Automatic handshake POST request on page load -- Message submission via JSON POST requests -- Input field clearing after submission -- Error handling for network issues - -## Integration with React Native App - -### App Initialization -The server is automatically started when the app launches (`/app/_layout.tsx`): -```typescript -useEffect(() => { - const { setMessage, hideModal } = useRemoteControlStore.getState(); - remoteControlService.init({ - onMessage: setMessage, - onHandshake: hideModal, - }); - useRemoteControlStore.getState().startServer(); - - return () => { - useRemoteControlStore.getState().stopServer(); - }; -}, []); -``` - -### Message Handling -Messages received from mobile devices are processed and displayed as Toast notifications in the TV app, providing visual feedback for remote interactions. - -### QR Code Integration -The app generates QR codes containing the server URL (`http://{device_ip}:12346`) for easy mobile device connection via `RemoteControlModal.tsx`. - -## Dependencies - -### Required Packages -- `react-native-tcp-socket@^6.0.6` - TCP socket implementation -- `@react-native-community/netinfo@^11.3.2` - Network interface information -- `react-native-qrcode-svg@^6.3.1` - QR code generation for UI - -### Platform Compatibility -- iOS (Apple TV) -- Android (Android TV) -- Requires network connectivity (WiFi or Ethernet) - -## Performance Characteristics - -### Connection Handling -- Single-threaded event-driven architecture -- Stateless HTTP connections with immediate closure -- Memory-efficient request buffering -- Graceful error recovery - -### Resource Usage -- Minimal CPU overhead for HTTP parsing -- Low memory footprint -- Network I/O bound operations -- Automatic connection cleanup - -## Error Handling - -### Server Level -- Network binding failures with descriptive messages -- Socket error handling and logging -- Graceful server shutdown procedures -- IP address detection error handling - -### Request Level -- Malformed HTTP request detection -- JSON parsing error handling -- 400/404/500 status code responses -- Request timeout and connection cleanup - -## Debugging and Monitoring - -### Logging -Comprehensive logging throughout the system: -- Server startup/shutdown events -- Client connection/disconnection -- Request processing details -- Error conditions and stack traces - -### Console Output Format -``` -[TCPHttpServer] Server listening on 192.168.1.100:12346 -[RemoteControl] Received request: POST /message -[RemoteControlStore] Server started, URL: http://192.168.1.100:12346 -``` - -## Usage Example - -### Starting the Server -```typescript -// Automatic startup via store -const { startServer } = useRemoteControlStore(); -await startServer(); - -// Manual service usage -await remoteControlService.startServer(); -``` - -### Stopping the Server -```typescript -// Via store -const { stopServer } = useRemoteControlStore(); -stopServer(); - -// Direct service call -remoteControlService.stopServer(); -``` - -### Mobile Device Access -1. Ensure mobile device is on the same network as TV -2. Scan QR code displayed in TV app -3. Access web interface at `http://{tv_ip}:12346` -4. Send messages that appear as notifications on TV - -## Comparison with Alternatives - -### vs react-native-http-bridge -- **Advantages**: More control over HTTP implementation, custom error handling -- **Disadvantages**: More complex implementation, requires manual HTTP parsing - -### vs External Backend Server -- **Advantages**: No additional infrastructure, embedded in app -- **Disadvantages**: Limited scalability, single device constraint - -## Future Enhancement Opportunities - -### Security -- Authentication token implementation -- HTTPS/TLS encryption support -- Request rate limiting -- CORS configuration - -### Functionality -- Multi-device support -- WebSocket integration for real-time communication -- File upload/download capabilities -- Advanced remote control commands - -### Performance -- Connection pooling -- Request caching -- Compression support -- IPv6 compatibility - -## Troubleshooting - -### Common Issues - -#### "Unable to get IP address" Error -- Verify WiFi/Ethernet connection -- Check network interface availability -- Restart network services - -#### Server Won't Start -- Check if port 12346 is already in use -- Verify network permissions -- Restart the application - -#### Mobile Device Can't Connect -- Confirm both devices on same network -- Verify firewall settings -- Check IP address in QR code - -### Diagnostic Commands -```bash -# Check network connectivity -yarn react-native log-ios # View iOS logs -yarn react-native log-android # View Android logs - -# Network debugging -netstat -an | grep 12346 # Check port binding (debugging environment) -``` - -## Conclusion - -The OrionTV native HTTP server implementation provides a robust, embedded solution for remote control functionality without requiring external infrastructure. The custom TCP-based approach offers flexibility and control while maintaining simplicity and performance suitable for TV applications. - -The implementation demonstrates sophisticated understanding of HTTP protocol handling, React Native integration, and TV-specific user experience requirements, making it an effective solution for cross-device interaction in smart TV environments. \ No newline at end of file diff --git a/docs/REMOTE_CONTROL_FEATURE.md b/docs/REMOTE_CONTROL_FEATURE.md deleted file mode 100644 index 163c38f..0000000 --- a/docs/REMOTE_CONTROL_FEATURE.md +++ /dev/null @@ -1,136 +0,0 @@ -# 手机遥控功能实现方案 (V2) - -本文档详细描述了在 OrionTV 应用中集成一个基于 **HTTP 请求** 的手机遥控功能的完整方案。 - ---- - -## 1. 核心功能与流程 - -该功能允许用户通过手机浏览器向 TV 端发送文本消息,TV 端接收后以 Toast 形式进行展示。服务将在应用启动时自动开启,用户可在设置中找到入口以显示连接二维码。 - -### 流程图 - -```mermaid -sequenceDiagram - participant App as App 启动 - participant RemoteControlStore as 状态管理 (TV) - participant RemoteControlService as 遥控服务 (TV) - participant User as 用户 - participant SettingsUI as 设置界面 (TV) - participant PhoneBrowser as 手机浏览器 - - App->>RemoteControlStore: App 启动, 触发 startHttpServer() - RemoteControlStore->>RemoteControlService: 启动 HTTP 服务 - RemoteControlService-->>RemoteControlStore: 更新服务状态 (IP, Port) - - User->>SettingsUI: 打开设置, 点击“手机遥控”按钮 - SettingsUI->>RemoteControlStore: 获取服务 URL - RemoteControlStore-->>SettingsUI: 返回 serverUrl - SettingsUI-->>User: 显示二维码弹窗 - - User->>PhoneBrowser: 扫描二维码 - PhoneBrowser->>RemoteControlService: (HTTP GET) 请求网页 - RemoteControlService-->>PhoneBrowser: 返回 HTML 页面 - User->>PhoneBrowser: 输入文本并发送 - PhoneBrowser->>RemoteControlService: (HTTP POST /message) 发送消息 - RemoteControlService->>RemoteControlStore: 处理消息 (显示 Toast) -``` - ---- - -## 2. 技术选型 - -* **HTTP 服务**: `react-native-http-bridge` -* **二维码生成**: `react-native-qrcode-svg` -* **网络信息 (IP 地址)**: `@react-native-community/netinfo` -* **状态管理**: `zustand` (项目已集成) - ---- - -## 3. 项目结构变更 - -### 新增文件 - -* `services/remoteControlService.ts`: 封装 HTTP 服务的核心逻辑。 -* `stores/remoteControlStore.ts`: 使用 Zustand 管理远程控制服务的状态。 -* `components/RemoteControlModal.tsx`: 显示二维码和连接信息的弹窗组件。 -* `types/react-native-http-bridge.d.ts`: `react-native-http-bridge` 的 TypeScript 类型定义。 - -### 修改文件 - -* `app/_layout.tsx`: 在应用根组件中调用服务启动逻辑。 -* `components/SettingsModal.tsx`: 添加“手机遥控”按钮,用于触发二维码弹窗。 -* `package.json`: 添加新依赖。 - ---- - -## 4. 实现细节 - -### a. 状态管理 (`stores/remoteControlStore.ts`) - -创建一个 Zustand store 来管理遥控服务的状态。 - -* **State**: - * `isServerRunning`: `boolean` - 服务是否正在运行。 - * `serverUrl`: `string | null` - 完整的 HTTP 服务 URL (e.g., `http://192.168.1.5:12346`)。 - * `error`: `string | null` - 错误信息。 -* **Actions**: - * `startServer()`: 异步 action,调用 `remoteControlService.startServer` 并更新 state。 - * `stopServer()`: 调用 `remoteControlService.stopServer` 并更新 state。 - -### b. 服务层 (`services/remoteControlService.ts`) - -实现服务的启动、停止和消息处理。 - -* **`startServer()`**: - 1. 使用 `@react-native-community/netinfo` 获取 IP 地址。 - 2. 定义一个包含 `fetch` API 调用逻辑的 HTML 字符串。 - 3. 使用 `react-native-http-bridge` 在固定端口(如 `12346`)启动 HTTP 服务。 - 4. 配置 `GET /` 路由以返回 HTML 页面。 - 5. 配置 `POST /message` 路由来接收手机端发送的消息,并使用 `Toast` 显示。 - 6. 返回服务器 URL。 -* **`stopServer()`**: - 1. 调用 `httpBridge.stop()`。 - -### c. UI 集成 - -* **`app/_layout.tsx`**: - * 在根组件 `useEffect` 中调用 `useRemoteControlStore.getState().startServer()`,实现服务自启。 -* **`components/SettingsModal.tsx`**: - * 添加一个 ``。 - * 点击按钮时,触发 `RemoteControlModal` 的显示。 -* **`components/RemoteControlModal.tsx`**: - * 从 `remoteControlStore` 中获取 `serverUrl`。 - * 如果 `serverUrl` 存在,则使用 `react-native-qrcode-svg` 的 `` 组件显示二维码。 - * 如果不存在,则显示加载中或错误信息。 - -### d. 网页内容 (HTML) - -一个简单的 HTML 页面,包含一个输入框和一个按钮。 - -```html - - - OrionTV Remote - - - - -

发送消息到 TV

- - - - - \ No newline at end of file diff --git a/docs/SETTINGS_REFACTOR_PLAN.md b/docs/SETTINGS_REFACTOR_PLAN.md deleted file mode 100644 index c83c30c..0000000 --- a/docs/SETTINGS_REFACTOR_PLAN.md +++ /dev/null @@ -1,131 +0,0 @@ -# 设置页面重构方案 - -## 目标 -1. 将设置从弹窗模式改为独立页面 -2. 新增直播源配置功能 -3. 新增远程输入开关配置 -4. 新增播放源启用配置 - -## 现有架构分析 - -### 当前设置相关文件: -- `stores/settingsStore.ts` - 设置状态管理,目前只有API地址配置 -- `components/SettingsModal.tsx` - 设置弹窗组件 -- `stores/remoteControlStore.ts` - 远程控制状态管理 - -### 现有功能: -- API基础地址配置 -- 远程控制服务器(但未集成到设置中) - -## 重构方案 - -### 1. 创建独立设置页面 -- 新建 `app/settings.tsx` 页面 -- 使用 Expo Router 的文件路由系统 -- 删除现有的 `SettingsModal.tsx` 组件 - -### 2. 扩展设置Store -在 `settingsStore.ts` 中新增以下配置项: -```typescript -interface SettingsState { - // 现有配置 - apiBaseUrl: string; - - // 新增配置项 - liveStreamSources: LiveStreamSource[]; // 直播源配置 - remoteInputEnabled: boolean; // 远程输入开关 - videoSourceConfig: VideoSourceConfig; // 播放源配置 -} - -interface LiveStreamSource { - id: string; - name: string; - url: string; - enabled: boolean; -} - -interface VideoSourceConfig { - primarySource: string; - fallbackSources: string[]; - enabledSources: string[]; -} -``` - -### 3. 设置页面UI结构 -``` -设置页面 (app/settings.tsx) -├── API 配置区域 -│ └── API 基础地址输入框 -├── 直播源配置区域 -│ ├── 直播源列表 -│ ├── 添加直播源按钮 -│ └── 编辑/删除直播源功能 -├── 远程输入配置区域 -│ └── 远程输入开关 -└── 播放源配置区域 - ├── 主播放源选择 - ├── 备用播放源配置 - └── 启用的播放源选择 -``` - -### 4. 组件设计 -- 使用 TV 适配的组件和样式 -- 实现焦点管理和遥控器导航 -- 遵循现有的设计规范(ThemedView, ThemedText, StyledButton) - -### 5. 导航集成 -- 在主页面添加设置入口 -- 使用 Expo Router 的 router.push('/settings') 进行导航 - -## 实施步骤 - -1. **扩展 settingsStore.ts** - - 添加新的状态接口 - - 实现新配置项的增删改查方法 - - 集成本地存储 - -2. **创建设置页面** - - 新建 `app/settings.tsx` - - 实现基础页面结构和导航 - -3. **实现配置组件** - - API 配置组件(复用现有逻辑) - - 直播源配置组件 - - 远程输入开关组件 - - 播放源配置组件 - -4. **集成远程控制** - - 将远程控制功能集成到设置页面 - - 统一管理所有设置项 - -5. **更新导航** - - 在主页面添加设置入口 - - 移除现有的设置弹窗触发逻辑 - -6. **测试验证** - - 测试所有配置项的保存和加载 - - 验证TV平台的交互体验 - - 确保配置项生效 - -## 技术考虑 - -### TV平台适配 -- 使用 `useTVRemoteHandler` 处理遥控器事件 -- 实现合适的焦点管理 -- 确保所有交互元素可通过遥控器操作 - -### 数据持久化 -- 使用现有的 `SettingsManager` 进行本地存储 -- 确保新配置项能正确保存和恢复 - -### 向后兼容 -- 保持现有API配置功能不变 -- 为新配置项提供默认值 -- 处理旧版本设置数据的迁移 - -## 预期收益 - -1. **更好的用户体验**:独立页面提供更多空间展示配置选项 -2. **功能扩展性**:为未来添加更多配置项提供良好基础 -3. **代码组织**:将设置相关功能集中管理 -4. **TV平台适配**:更好的遥控器交互体验 \ No newline at end of file diff --git a/docs/components/StyledButton.md b/docs/components/StyledButton.md deleted file mode 100644 index 5b77871..0000000 --- a/docs/components/StyledButton.md +++ /dev/null @@ -1,77 +0,0 @@ -# StyledButton 组件设计文档 - -## 1. 目的 - -为了统一整个应用中的按钮样式和行为,减少代码重复,并提高开发效率和一致性,我们设计了一个通用的 `StyledButton` 组件。 - -该组件将取代以下位置的自定义 `Pressable` 和 `TouchableOpacity` 实现: - -- `app/index.tsx` (分类按钮, 头部图标按钮) -- `components/DetailButton.tsx` -- `components/EpisodeSelectionModal.tsx` (剧集分组按钮, 剧集项按钮, 关闭按钮) -- `components/SettingsModal.tsx` (取消和保存按钮) -- `app/search.tsx` (清除按钮) -- `components/MediaButton.tsx` (媒体控制按钮) -- `components/NextEpisodeOverlay.tsx` (取消按钮) - -## 2. API 设计 - -`StyledButton` 组件将基于 React Native 的 `Pressable` 构建,并提供以下 props: - -```typescript -import { PressableProps, StyleProp, ViewStyle, TextStyle } from "react-native"; - -interface StyledButtonProps extends PressableProps { - // 按钮的主要内容,可以是文本或图标等 React 节点 - children?: React.ReactNode; - - // 如果按钮只包含文本,可以使用此 prop 快速设置 - text?: string; - - // 按钮的视觉变体,用于应用不同的预设样式 - // 'default': 默认灰色背景 - // 'primary': 主题色背景,用于关键操作 - // 'ghost': 透明背景,通常用于图标按钮 - variant?: "default" | "primary" | "ghost"; - - // 按钮是否处于选中状态 - isSelected?: boolean; - - // 覆盖容器的样式 - style?: StyleProp; - - // 覆盖文本的样式 (当使用 `text` prop 时生效) - textStyle?: StyleProp; -} -``` - -## 3. 样式和行为 - -### 状态样式: - -- **默认状态 (`default`)**: - - 背景色: `#333` - - 边框: `transparent` -- **聚焦状态 (`focused`)**: - - 背景色: `#0056b3` (深蓝色) - - 边框: `#fff` - - 阴影/光晕效果 - - 轻微放大 (`transform: scale(1.1)`) -- **选中状态 (`isSelected`)**: - - 背景色: `#007AFF` (亮蓝色) -- **主操作 (`primary`)**: - - 默认背景色: `#007AFF` -- **透明背景 (`ghost`)**: - - 默认背景色: `transparent` - -### 结构: - -组件内部将使用 `Pressable` 作为根元素,并根据 `focused` 和 `isSelected` props 动态计算样式。如果 `children` 和 `text` prop 都提供了,`children` 将优先被渲染。 - -## 4. 实现计划 - -1. **创建 `components/StyledButton.tsx` 文件**。 -2. **实现上述 API 和样式逻辑**。 -3. **逐个重构目标文件**,将原有的 `Pressable`/`TouchableOpacity` 替换为新的 `StyledButton` 组件。 -4. **删除旧的、不再需要的样式**。 -5. **测试所有被修改的界面**,确保按钮的功能和视觉效果符合预期。 diff --git a/package.json b/package.json index 2f2b83c..f355326 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "OrionTV", "private": true, "main": "expo-router/entry", - "version": "1.2.8", + "version": "1.2.9", "scripts": { "start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start", "start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start", diff --git a/stores/authStore.ts b/stores/authStore.ts index c971efa..4efa4c7 100644 --- a/stores/authStore.ts +++ b/stores/authStore.ts @@ -26,7 +26,7 @@ const useAuthStore = create((set) => ({ try { const serverConfig = useSettingsStore.getState().serverConfig; if (!serverConfig?.StorageType) { - Toast.show({ type: "error", text1: "请检查网络或者 API 地址是否可用" }); + Toast.show({ type: "error", text1: "请检查网络或者服务器地址是否可用" }); return } const cookies = await Cookies.get(api.baseURL); diff --git a/stores/homeStore.ts b/stores/homeStore.ts index 19f5fe6..0603c82 100644 --- a/stores/homeStore.ts +++ b/stores/homeStore.ts @@ -147,7 +147,7 @@ const useHomeStore = create((set, get) => ({ } } catch (err: any) { if (err.message === "API_URL_NOT_SET") { - set({ error: "请点击右上角设置按钮,配置您的 API 地址" }); + set({ error: "请点击右上角设置按钮,配置您的服务器地址" }); } else { set({ error: "加载失败,请重试" }); }