From 6a799ee897b2776b6e8cab5ca909914e8dba8051 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Fri, 22 Aug 2025 00:24:39 +0800 Subject: [PATCH 01/22] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=97=A5=E5=BF=97=E5=92=8C=E5=A2=9E=E5=BC=BA=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 移除旧版速率限制机制,简化服务部署与维护。(频率限制转移到CloudFlare中设置防护规则) * 新增Cloudflare日志记录功能,提升搜索过程可观测性。 * 统一并细化了各资源平台的API错误响应信息。 * 修复Koyso和真红小站的URL编码及正则匹配问题。 * 优化紫缘社的访问密码错误提示。 --- src/core.ts | 18 +++++++++++++ src/index.ts | 17 ------------ src/platforms/gal/ACGYingYingGuai.ts | 2 +- src/platforms/gal/BiAnXingLu.ts | 2 +- src/platforms/gal/DaoHeGal.ts | 2 +- src/platforms/gal/FuFuACG.ts | 2 +- src/platforms/gal/GGBases.ts | 2 +- src/platforms/gal/GalTuShuGuan.ts | 2 +- src/platforms/gal/GalgameX.ts | 2 +- src/platforms/gal/Hikarinagi.ts | 2 +- src/platforms/gal/JiMengACG.ts | 2 +- src/platforms/gal/Koyso.ts | 8 +++--- src/platforms/gal/KunGalgame.ts | 2 +- src/platforms/gal/LiangZiACG.ts | 2 +- src/platforms/gal/MaoMaoWangPan.ts | 4 +-- src/platforms/gal/MiaoYuanLingYu.ts | 2 +- src/platforms/gal/Nysoure.ts | 2 +- src/platforms/gal/QingJiACG.ts | 2 +- src/platforms/gal/ShenShiTianTang.ts | 2 +- src/platforms/gal/TianYouErCiYuan.ts | 2 +- src/platforms/gal/TouchGal.ts | 2 +- src/platforms/gal/VikaACG.ts | 2 +- src/platforms/gal/WeiZhiYunPan.ts | 4 +-- src/platforms/gal/YouYuDeloli.ts | 2 +- src/platforms/gal/ZeroFive.ts | 4 +-- src/platforms/gal/ZhenHongXiaoZhan.ts | 4 +-- src/platforms/gal/ZiLingDeMiaoMiaoWu.ts | 4 +-- src/platforms/gal/ZiYuanShe.ts | 8 ++++-- src/platforms/gal/xxacg.ts | 2 +- src/platforms/patch/KunGalgameBuDing.ts | 2 +- src/platforms/patch/TWOdfan.ts | 2 +- src/ratelimit.ts | 36 ------------------------- src/utils/httpClient.ts | 19 ++++++++++++- wrangler.toml | 9 ++----- 34 files changed, 80 insertions(+), 99 deletions(-) delete mode 100644 src/ratelimit.ts diff --git a/src/core.ts b/src/core.ts index c69ec87..ca9d87d 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,3 +1,4 @@ +import { logToCF } from "./utils/httpClient"; import type { Platform, PlatformSearchResult, StreamProgress, StreamResult } from "./types"; import platformsGal from "./platforms/gal"; import platformsPatch from "./platforms/patch"; @@ -22,6 +23,11 @@ export async function handleSearchRequestStream( writer: WritableStreamDefaultWriter, zypassword: string = "" // 添加 zypassword 参数 ): Promise { + // 记录搜索关键词 + logToCF({ + message: `搜索关键词: ${game}`, + level: "info", + }); const encoder = new TextEncoder(); const total = platforms.length; let completed = 0; @@ -38,6 +44,13 @@ export async function handleSearchRequestStream( const progress: StreamProgress = { completed, total }; if (result.count > 0 || result.error) { + if (result.error) { + // 记录平台错误 + logToCF({ + message: `平台 ${result.name} 搜索错误: ${result.error}`, + level: "error", + }); + } const streamResult: StreamResult = { name: result.name, color: result.error ? 'red' : platform.color, @@ -53,6 +66,11 @@ export async function handleSearchRequestStream( completed++; // 记录平台内部的未知错误 console.error(`Error searching platform ${platform.name}:`, e); + // 记录平台内部的未知错误 + logToCF({ + message: `平台 ${platform.name} 内部错误: ${e instanceof Error ? e.message : String(e)}`, + level: "error", + }); const progress: StreamProgress = { completed, total }; await writer.write(encoder.encode(formatStreamEvent({ progress }))); } diff --git a/src/index.ts b/src/index.ts index 40948c8..0c7e187 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,5 @@ import { handleSearchRequestStream, PLATFORMS_GAL, PLATFORMS_PATCH } from "./core"; -import { checkRateLimit } from "./ratelimit"; - export interface Env { - RATE_LIMIT_KV: KVNamespace; } const corsHeaders = { @@ -17,20 +14,6 @@ async function handleSearch(request: Request, env: Env, ctx: ExecutionContext, p const game = formData.get("game") as string; const zypassword = formData.get("zypassword") as string || ""; // 获取 zypassword - if (env.RATE_LIMIT_KV) { - const ip = request.headers.get("CF-Connecting-IP") || "unknown"; - const { allowed, retryAfter } = await checkRateLimit(ip, env.RATE_LIMIT_KV); - - if (!allowed) { - return new Response( - JSON.stringify({ error: `搜索过于频繁, 请 ${retryAfter} 秒后再试` }), - { - status: 429, - headers: { "Content-Type": "application/json", ...corsHeaders }, - } - ); - } - } if (!game || typeof game !== 'string') { return new Response(JSON.stringify({ error: "Game name is required" }), { diff --git a/src/platforms/gal/ACGYingYingGuai.ts b/src/platforms/gal/ACGYingYingGuai.ts index de9b880..507e084 100644 --- a/src/platforms/gal/ACGYingYingGuai.ts +++ b/src/platforms/gal/ACGYingYingGuai.ts @@ -17,7 +17,7 @@ async function searchACGYingYingGuai(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/DaoHeGal.ts b/src/platforms/gal/DaoHeGal.ts index d4f591c..37e6691 100644 --- a/src/platforms/gal/DaoHeGal.ts +++ b/src/platforms/gal/DaoHeGal.ts @@ -31,7 +31,7 @@ async function searchDaoHeGal(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as DaoHeGalResponse; diff --git a/src/platforms/gal/FuFuACG.ts b/src/platforms/gal/FuFuACG.ts index e6ab779..43f1d89 100644 --- a/src/platforms/gal/FuFuACG.ts +++ b/src/platforms/gal/FuFuACG.ts @@ -31,7 +31,7 @@ async function searchFuFuACG(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as FuFuACGResponse; diff --git a/src/platforms/gal/GGBases.ts b/src/platforms/gal/GGBases.ts index f13ee7f..232abaa 100644 --- a/src/platforms/gal/GGBases.ts +++ b/src/platforms/gal/GGBases.ts @@ -19,7 +19,7 @@ async function searchGGBases(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/GalTuShuGuan.ts b/src/platforms/gal/GalTuShuGuan.ts index c48d902..5f60bb9 100644 --- a/src/platforms/gal/GalTuShuGuan.ts +++ b/src/platforms/gal/GalTuShuGuan.ts @@ -35,7 +35,7 @@ async function searchGalTuShuGuan(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as GalTuShuGuanResponse; diff --git a/src/platforms/gal/GalgameX.ts b/src/platforms/gal/GalgameX.ts index f402a35..a7e63cb 100644 --- a/src/platforms/gal/GalgameX.ts +++ b/src/platforms/gal/GalgameX.ts @@ -48,7 +48,7 @@ async function searchGalgameX(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as GalgameXResponse; diff --git a/src/platforms/gal/Hikarinagi.ts b/src/platforms/gal/Hikarinagi.ts index 8464e55..28f0b88 100644 --- a/src/platforms/gal/Hikarinagi.ts +++ b/src/platforms/gal/Hikarinagi.ts @@ -17,7 +17,7 @@ async function searchHikarinagi(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/JiMengACG.ts b/src/platforms/gal/JiMengACG.ts index af8bd47..ef1c8a4 100644 --- a/src/platforms/gal/JiMengACG.ts +++ b/src/platforms/gal/JiMengACG.ts @@ -31,7 +31,7 @@ async function searchJiMengACG(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as JiMengACGResponse; diff --git a/src/platforms/gal/Koyso.ts b/src/platforms/gal/Koyso.ts index bc5a312..1ece96e 100644 --- a/src/platforms/gal/Koyso.ts +++ b/src/platforms/gal/Koyso.ts @@ -3,7 +3,7 @@ import type { Platform, PlatformSearchResult, SearchResultItem } from "../../typ const API_URL = "https://koyso.to/"; const BASE_URL = "https://koyso.to"; -const REGEX = /.*?(?.+?)<\/span>/gs; +const REGEX = /.*?(?.+?)<\/span>/gs; async function searchKoyso(game: string): Promise { const searchResult: PlatformSearchResult = { @@ -18,12 +18,12 @@ async function searchKoyso(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); // --- DEBUGGING: Print the full HTML content --- - // console.log("Koyso API HTML Response:", html.substring(0, 1000)); // Print first 1000 chars + console.log("Koyso API HTML Response:", html); // --- END DEBUGGING --- const matches = html.matchAll(REGEX); @@ -33,7 +33,7 @@ async function searchKoyso(game: string): Promise { if (match.groups?.NAME && match.groups?.URL) { items.push({ name: match.groups.NAME.trim(), - url: BASE_URL + encodeURIComponent(match.groups.URL), + url: BASE_URL + match.groups.URL, }); } } diff --git a/src/platforms/gal/KunGalgame.ts b/src/platforms/gal/KunGalgame.ts index 225b783..f2e108d 100644 --- a/src/platforms/gal/KunGalgame.ts +++ b/src/platforms/gal/KunGalgame.ts @@ -28,7 +28,7 @@ async function searchKunGalgame(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as KunGalgameItem[]; diff --git a/src/platforms/gal/LiangZiACG.ts b/src/platforms/gal/LiangZiACG.ts index b8c8e00..234574c 100644 --- a/src/platforms/gal/LiangZiACG.ts +++ b/src/platforms/gal/LiangZiACG.ts @@ -17,7 +17,7 @@ async function searchLiangZiACG(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/MaoMaoWangPan.ts b/src/platforms/gal/MaoMaoWangPan.ts index 6b0490a..9956cf2 100644 --- a/src/platforms/gal/MaoMaoWangPan.ts +++ b/src/platforms/gal/MaoMaoWangPan.ts @@ -43,13 +43,13 @@ async function searchMaoMaoWangPan(game: string): Promise }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as MaoMaoResponse; if (data.message !== "success") { - throw new Error(`API returned an error: ${data.message}`); + throw new Error(`${data.message}`); } const items: SearchResultItem[] = data.data.content diff --git a/src/platforms/gal/MiaoYuanLingYu.ts b/src/platforms/gal/MiaoYuanLingYu.ts index 3504a2f..f5ff9b3 100644 --- a/src/platforms/gal/MiaoYuanLingYu.ts +++ b/src/platforms/gal/MiaoYuanLingYu.ts @@ -18,7 +18,7 @@ async function searchMiaoYuanLingYu(game: string): Promise const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/Nysoure.ts b/src/platforms/gal/Nysoure.ts index 0cf80ba..1b7dd5a 100644 --- a/src/platforms/gal/Nysoure.ts +++ b/src/platforms/gal/Nysoure.ts @@ -28,7 +28,7 @@ async function searchNysoure(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as NysoureResponse; diff --git a/src/platforms/gal/QingJiACG.ts b/src/platforms/gal/QingJiACG.ts index 2ec160c..106c31b 100644 --- a/src/platforms/gal/QingJiACG.ts +++ b/src/platforms/gal/QingJiACG.ts @@ -18,7 +18,7 @@ async function searchQingJiACG(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/ShenShiTianTang.ts b/src/platforms/gal/ShenShiTianTang.ts index 780f944..e7a5a57 100644 --- a/src/platforms/gal/ShenShiTianTang.ts +++ b/src/platforms/gal/ShenShiTianTang.ts @@ -17,7 +17,7 @@ async function searchShenShiTianTang(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as { galgames: { name: string; uniqueId: string }[] }; diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index b67c674..f9e3ce4 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -39,7 +39,7 @@ async function searchVikaACG(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const rawText = await response.text(); diff --git a/src/platforms/gal/WeiZhiYunPan.ts b/src/platforms/gal/WeiZhiYunPan.ts index 04d6b23..3d3f4a2 100644 --- a/src/platforms/gal/WeiZhiYunPan.ts +++ b/src/platforms/gal/WeiZhiYunPan.ts @@ -43,13 +43,13 @@ async function searchWeiZhiYunPan(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as WeiZhiYunPanResponse; if (data.message !== "success") { - throw new Error(`API returned an error: ${data.message}`); + throw new Error(`${data.message}`); } const items: SearchResultItem[] = data.data.content.map(item => ({ diff --git a/src/platforms/gal/YouYuDeloli.ts b/src/platforms/gal/YouYuDeloli.ts index c6e2e52..ca8da51 100644 --- a/src/platforms/gal/YouYuDeloli.ts +++ b/src/platforms/gal/YouYuDeloli.ts @@ -17,7 +17,7 @@ async function searchYouYuDeloli(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const html = await response.text(); diff --git a/src/platforms/gal/ZeroFive.ts b/src/platforms/gal/ZeroFive.ts index dda6cbe..1917f5c 100644 --- a/src/platforms/gal/ZeroFive.ts +++ b/src/platforms/gal/ZeroFive.ts @@ -43,13 +43,13 @@ async function searchZeroFive(game: string): Promise { }); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as ZeroFiveResponse; if (data.message !== "success") { - throw new Error(`API returned an error: ${data.message}`); + throw new Error(`${data.message}`); } const items: SearchResultItem[] = data.data.content.map(item => ({ diff --git a/src/platforms/gal/ZhenHongXiaoZhan.ts b/src/platforms/gal/ZhenHongXiaoZhan.ts index a19e7e9..779ea10 100644 --- a/src/platforms/gal/ZhenHongXiaoZhan.ts +++ b/src/platforms/gal/ZhenHongXiaoZhan.ts @@ -18,7 +18,7 @@ async function searchZhenHongXiaoZhan(game: string): Promise ({ diff --git a/src/platforms/gal/ZiYuanShe.ts b/src/platforms/gal/ZiYuanShe.ts index 53b8c92..0f7fe30 100644 --- a/src/platforms/gal/ZiYuanShe.ts +++ b/src/platforms/gal/ZiYuanShe.ts @@ -43,13 +43,17 @@ async function searchZiYuanShe(game: string, zypassword: string = ""): Promise

({ diff --git a/src/platforms/gal/xxacg.ts b/src/platforms/gal/xxacg.ts index 1ed1ce8..c5f2314 100644 --- a/src/platforms/gal/xxacg.ts +++ b/src/platforms/gal/xxacg.ts @@ -22,7 +22,7 @@ async function searchXxacg(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`Search API response status code is ${response.status}`); + throw new Error(`Search 资源平台 SearchAPI 响应异常状态码 ${response.status}`); } html = await response.text(); diff --git a/src/platforms/patch/KunGalgameBuDing.ts b/src/platforms/patch/KunGalgameBuDing.ts index e04c788..1bd3532 100644 --- a/src/platforms/patch/KunGalgameBuDing.ts +++ b/src/platforms/patch/KunGalgameBuDing.ts @@ -41,7 +41,7 @@ async function searchKunGalgameBuDing(game: string): Promise { const response = await fetchClient(url); if (!response.ok) { - throw new Error(`API response status code is ${response.status}`); + throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } const data = await response.json() as TwoDFanResponse; diff --git a/src/ratelimit.ts b/src/ratelimit.ts deleted file mode 100644 index 7220b9c..0000000 --- a/src/ratelimit.ts +++ /dev/null @@ -1,36 +0,0 @@ -// --- 速率限制常量 --- -const SEARCH_INTERVAL_SECONDS = 15; -// KV 条目将在此秒数后自动过期,以防止存储膨胀 -const IP_ENTRY_TTL_SECONDS = 60; - -/** - * 检查给定 IP 地址是否超出了速率限制。 - * @param ip 客户端的 IP 地址。 - * @param kvNamespace 用于存储 IP 时间戳的 KV 命名空间。 - * @returns 返回一个对象,包含是否允许请求以及剩余的等待秒数。 - */ -export async function checkRateLimit( - ip: string, - kvNamespace: KVNamespace -): Promise<{ allowed: boolean; retryAfter: number }> { - const currentTime = Math.floor(Date.now() / 1000); - const lastSearchTimeStr = await kvNamespace.get(ip); - const lastSearchTime = lastSearchTimeStr ? parseInt(lastSearchTimeStr, 10) : 0; - - if (lastSearchTime && (currentTime - lastSearchTime) < SEARCH_INTERVAL_SECONDS) { - return { - allowed: false, - retryAfter: SEARCH_INTERVAL_SECONDS - (currentTime - lastSearchTime), - }; - } - - // 更新 IP 的最后搜索时间,并设置 TTL - await kvNamespace.put(ip, currentTime.toString(), { - expirationTtl: IP_ENTRY_TTL_SECONDS, - }); - - return { - allowed: true, - retryAfter: 0, - }; -} \ No newline at end of file diff --git a/src/utils/httpClient.ts b/src/utils/httpClient.ts index 3f97360..7a5c81e 100644 --- a/src/utils/httpClient.ts +++ b/src/utils/httpClient.ts @@ -32,10 +32,27 @@ export async function fetchClient( return response; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { - throw new Error(`Request timed out after ${TIMEOUT_SECONDS} seconds`); + throw new Error(`资源平台 SearchAPI 请求超时`); } throw error; } finally { clearTimeout(timeoutId); } +} +/** + * 向 Cloudflare 发送日志。 + * @param data 要记录的数据对象。 + */ +export async function logToCF(data: object) { + try { + await fetch("https://log.gal.homes", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + } catch (error) { + console.error("Failed to log to Cloudflare:", error); + } } \ No newline at end of file diff --git a/wrangler.toml b/wrangler.toml index 2149a8b..adc956e 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -2,10 +2,5 @@ name = "searchgal-worker" main = "src/index.ts" compatibility_date = "2023-10-30" -[vars] -# 如果有需要,可以在这里添加环境变量 - -# --- KV 命名空间绑定 --- -[[kv_namespaces]] -binding = "RATE_LIMIT_KV" -id = "7ed4c4f36baf419bb9ed54538a61f473" +[observability.logs] +enabled = true From 1a9db6a536f40916a06ef94671e29cae6d3ab528 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Fri, 22 Aug 2025 00:52:34 +0800 Subject: [PATCH 02/22] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20VikaACG=20?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=8A=93=E5=8F=96=EF=BC=8C=E5=B9=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复 VikaACG 平台中提取游戏名称的正则表达式,提高匹配准确性。 * 移除 Koyso 平台中不再需要的调试日志输出,优化代码。 * 在 TWOdfan 平台中新增调试日志,以便检查 API 响应的 HTML 内容。 --- src/platforms/gal/Koyso.ts | 3 --- src/platforms/gal/VikaACG.ts | 2 +- src/platforms/patch/TWOdfan.ts | 2 ++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/platforms/gal/Koyso.ts b/src/platforms/gal/Koyso.ts index 1ece96e..a5964ea 100644 --- a/src/platforms/gal/Koyso.ts +++ b/src/platforms/gal/Koyso.ts @@ -22,9 +22,6 @@ async function searchKoyso(game: string): Promise { } const html = await response.text(); - // --- DEBUGGING: Print the full HTML content --- - console.log("Koyso API HTML Response:", html); - // --- END DEBUGGING --- const matches = html.matchAll(REGEX); diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index f9e3ce4..22efcb5 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -6,7 +6,7 @@ const API_URL = "https://www.vikacg.com/wp-json/b2/v1/getPostList"; // The Python code suggests the response text itself might be a JSON string // that contains escaped HTML. Let's try to parse it as JSON first. // The regex is applied to the *unescaped* string. -const REGEX = /

(?.*?)<"/gs; async function searchVikaACG(game: string): Promise { const searchResult: PlatformSearchResult = { diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index df3628a..5ba5a12 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -27,6 +27,8 @@ async function searchTWOdfan(game: string): Promise { const data = await response.json() as TwoDFanResponse; const html = data.subjects; + + console.log("2dfan API HTML Response:", html); const matches = html.matchAll(REGEX); From 6a59d03ac063632b46a029cb9981f679d4b06cf8 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Fri, 22 Aug 2025 01:30:35 +0800 Subject: [PATCH 03/22] =?UTF-8?q?fix:=20=E6=94=B9=E8=BF=9BVikaACG=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E8=A7=A3=E6=9E=90=E5=B9=B6=E8=B0=83=E6=95=B42dfan?= =?UTF-8?q?=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复VikaACG平台HTML解析正则表达式中的结束标签错误。 * 简化VikaACG API响应的处理流程,直接使用文本内容。 * 调整2dfan平台日志输出,在JSON解析前显示原始响应文本。 --- src/platforms/gal/VikaACG.ts | 73 +++++++++++++--------------------- src/platforms/patch/TWOdfan.ts | 4 +- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index 22efcb5..a6e9d8b 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -2,11 +2,7 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; const API_URL = "https://www.vikacg.com/wp-json/b2/v1/getPostList"; - -// The Python code suggests the response text itself might be a JSON string -// that contains escaped HTML. Let's try to parse it as JSON first. -// The regex is applied to the *unescaped* string. -const REGEX = /

(?.*?)<"/gs; +const REGEX = /

(?.*?)<\/a>/gs; async function searchVikaACG(game: string): Promise { const searchResult: PlatformSearchResult = { @@ -16,57 +12,44 @@ async function searchVikaACG(game: string): Promise { }; try { - const payload = { - paged: 1, - post_paged: 1, - post_count: 1000, // Corresponds to MAX_RESULTS - post_type: "post-1", - post_cat: [6], - post_order: "modified", - post_meta: [ - "user", "date", "des", "cats", "like", "comment", "views", "video", "download", "hide", - ], - metas: {}, - search: game, - }; - const response = await fetchClient(API_URL, { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(payload), + body: JSON.stringify({ + paged: 1, + post_paged: 1, + post_count: 1000, // Hardcoded limit, larger values may cause timeouts + post_type: "post-1", + post_cat: [6], + post_order: "modified", + post_meta: [ + "user", + "date", + "des", + "cats", + "like", + "comment", + "views", + "video", + "download", + "hide", + ], + metas: {}, + search: game, + }), }); if (!response.ok) { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } - const rawText = await response.text(); - let htmlContent: string; - - try { - // Attempt to parse as JSON first. If it's a JSON string containing HTML, - // JSON.parse will handle standard escapes like \uXXXX. - const parsedJson = JSON.parse(rawText); - // Assuming the HTML content is directly the value of the JSON, or a specific field. - // The Python code implies the entire response text, after unescaping, is the HTML. - // So, if parsedJson is a string, use it. Otherwise, stringify it. - htmlContent = typeof parsedJson === 'string' ? parsedJson : JSON.stringify(parsedJson); - } catch (jsonError) { - // If JSON.parse fails, it might be due to non-standard Python escapes like \\/ or \\\\ - // Attempt a simple unescape for these specific cases. - // Note: This is a simplified unescape and might not cover all Python's unicode_escape nuances. - const unescapedText = rawText.replace(/\\(.)/g, '$1'); // Replaces \\/ with / and \\\\ with \ - try { - htmlContent = JSON.parse(unescapedText); // Try parsing as JSON again - } catch (finalError) { - // If still fails, assume it's raw HTML that just needs basic unescaping - htmlContent = unescapedText; - } - } - - const matches = htmlContent.matchAll(REGEX); + // The response is a JSON-encoded string containing HTML. + // .json() will parse the JSON and unescape the string content. + const html: string = await response.text(); + + const matches = html.matchAll(REGEX); const items: SearchResultItem[] = []; for (const match of matches) { diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index 5ba5a12..c445f12 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -25,10 +25,10 @@ async function searchTWOdfan(game: string): Promise { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } + console.log("2dfan API HTML Response:", response.text()); + const data = await response.json() as TwoDFanResponse; const html = data.subjects; - - console.log("2dfan API HTML Response:", html); const matches = html.matchAll(REGEX); From 9afdee1cf673df0ad0e5be1318a7db3788e6a98a Mon Sep 17 00:00:00 2001 From: Jurangren Date: Fri, 22 Aug 2025 02:33:33 +0800 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=A0=B9?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E9=87=8D=E5=AE=9A=E5=90=91=E4=B8=8E=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 在根路径 '/' 访问时,自动重定向到主页 'https://searchgal.homes'。 * 在 `logToCF` 函数中添加 `console.log`,方便在 Cloudflare 控制台查看日志。 --- src/index.ts | 4 ++++ src/utils/httpClient.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index 0c7e187..99e6e60 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,6 +54,10 @@ export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const url = new URL(request.url); + if (url.pathname === '/') { + return Response.redirect('https://searchgal.homes', 302); + } + if (request.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } diff --git a/src/utils/httpClient.ts b/src/utils/httpClient.ts index 7a5c81e..4fcf320 100644 --- a/src/utils/httpClient.ts +++ b/src/utils/httpClient.ts @@ -44,6 +44,8 @@ export async function fetchClient( * @param data 要记录的数据对象。 */ export async function logToCF(data: object) { + // 在此处添加 console.log,以便在 Cloudflare 控制台也能看到日志 + console.log(JSON.stringify(data)); try { await fetch("https://log.gal.homes", { method: "POST", From 37f47f981a29701b18c16640634909db15a2670e Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 00:21:14 +0800 Subject: [PATCH 05/22] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BE=93=E5=87=BA=E6=96=B9=E5=BC=8F=E5=B9=B6=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=86=97=E4=BD=99=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 移除 `src/utils/httpClient.ts` 中自定义的 `logToCF` 日志函数。 * 将 `src/core.ts` 中所有 `logToCF` 调用替换为 `console.log` 进行结构化日志输出。 * 删除 `src/platforms/gal/xxacg.ts` 中无匹配项但存在 HTML 的特定错误检查。 --- src/core.ts | 13 ++++++------- src/platforms/gal/xxacg.ts | 5 ----- src/utils/httpClient.ts | 19 ------------------- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/core.ts b/src/core.ts index ca9d87d..64519c7 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,4 +1,3 @@ -import { logToCF } from "./utils/httpClient"; import type { Platform, PlatformSearchResult, StreamProgress, StreamResult } from "./types"; import platformsGal from "./platforms/gal"; import platformsPatch from "./platforms/patch"; @@ -24,10 +23,10 @@ export async function handleSearchRequestStream( zypassword: string = "" // 添加 zypassword 参数 ): Promise { // 记录搜索关键词 - logToCF({ + console.log(JSON.stringify({ message: `搜索关键词: ${game}`, level: "info", - }); + })); const encoder = new TextEncoder(); const total = platforms.length; let completed = 0; @@ -46,10 +45,10 @@ export async function handleSearchRequestStream( if (result.count > 0 || result.error) { if (result.error) { // 记录平台错误 - logToCF({ + console.log(JSON.stringify({ message: `平台 ${result.name} 搜索错误: ${result.error}`, level: "error", - }); + })); } const streamResult: StreamResult = { name: result.name, @@ -67,10 +66,10 @@ export async function handleSearchRequestStream( // 记录平台内部的未知错误 console.error(`Error searching platform ${platform.name}:`, e); // 记录平台内部的未知错误 - logToCF({ + console.log(JSON.stringify({ message: `平台 ${platform.name} 内部错误: ${e instanceof Error ? e.message : String(e)}`, level: "error", - }); + })); const progress: StreamProgress = { completed, total }; await writer.write(encoder.encode(formatStreamEvent({ progress }))); } diff --git a/src/platforms/gal/xxacg.ts b/src/platforms/gal/xxacg.ts index c5f2314..1f762cb 100644 --- a/src/platforms/gal/xxacg.ts +++ b/src/platforms/gal/xxacg.ts @@ -38,11 +38,6 @@ async function searchXxacg(game: string): Promise { } } - if (items.length === 0 && html.length > 0) { - // 如果没有匹配项,但我们确实收到了 HTML,这可能意味着页面结构已更改。 - // 将部分 HTML 包含在错误中以供调试。 - throw new Error(`No matches found on page. HTML starts with: ${html.substring(0, 500)}`); - } searchResult.items = items; searchResult.count = items.length; diff --git a/src/utils/httpClient.ts b/src/utils/httpClient.ts index 4fcf320..81da63f 100644 --- a/src/utils/httpClient.ts +++ b/src/utils/httpClient.ts @@ -38,23 +38,4 @@ export async function fetchClient( } finally { clearTimeout(timeoutId); } -} -/** - * 向 Cloudflare 发送日志。 - * @param data 要记录的数据对象。 - */ -export async function logToCF(data: object) { - // 在此处添加 console.log,以便在 Cloudflare 控制台也能看到日志 - console.log(JSON.stringify(data)); - try { - await fetch("https://log.gal.homes", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - } catch (error) { - console.error("Failed to log to Cloudflare:", error); - } } \ No newline at end of file From 6d16623f36b32724a782aa3581813c5e90733e8f Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 01:01:06 +0800 Subject: [PATCH 06/22] =?UTF-8?q?chore:=20=E6=A0=87=E5=87=86=E5=8C=96=202d?= =?UTF-8?q?fan=20API=20HTML=20=E5=93=8D=E5=BA=94=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 `VikaACG` 和 `TWOdfan` 平台中,标准化 2dfan API HTML 响应的日志格式。 - 日志输出采用 JSON 格式,包含 `message`、`html` 和 `level: "info"` 字段。 - 此更改有助于改进 API 响应的调试和监控。 --- src/platforms/gal/VikaACG.ts | 6 ++++++ src/platforms/patch/TWOdfan.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index a6e9d8b..ebc1575 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -48,6 +48,12 @@ async function searchVikaACG(game: string): Promise { // The response is a JSON-encoded string containing HTML. // .json() will parse the JSON and unescape the string content. const html: string = await response.text(); + + console.log(JSON.stringify({ + message: "2dfan API HTML Response", + html: html, + level: "info", + })); const matches = html.matchAll(REGEX); diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index c445f12..d4fb0d1 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -24,8 +24,12 @@ async function searchTWOdfan(game: string): Promise { if (!response.ok) { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } - - console.log("2dfan API HTML Response:", response.text()); + + console.log(JSON.stringify({ + message: "2dfan API HTML Response", + html: response.text(), + level: "info", + })); const data = await response.json() as TwoDFanResponse; const html = data.subjects; From a3b9c3a75150e3dd54e842ce451b57403c995a33 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 01:36:48 +0800 Subject: [PATCH 07/22] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=202dfan=20?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=8F=8A=E5=AD=97=E7=AC=A6=E8=BD=AC=E4=B9=89=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 调整 2dfan API 响应处理方式,直接读取 HTML 文本而非解析 JSON。 * 在 VikaACG 中处理 2dfan 结果 HTML 中的转义字符,确保正则表达式正确匹配。 * 移除或调整了部分调试日志输出,以保持代码整洁。 --- src/platforms/gal/VikaACG.ts | 8 +------- src/platforms/patch/TWOdfan.ts | 11 +++-------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index ebc1575..6343b7d 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -48,14 +48,8 @@ async function searchVikaACG(game: string): Promise { // The response is a JSON-encoded string containing HTML. // .json() will parse the JSON and unescape the string content. const html: string = await response.text(); - - console.log(JSON.stringify({ - message: "2dfan API HTML Response", - html: html, - level: "info", - })); - const matches = html.matchAll(REGEX); + const matches = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').matchAll(REGEX); const items: SearchResultItem[] = []; for (const match of matches) { diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index d4fb0d1..2bcece6 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -5,10 +5,6 @@ const API_URL = "https://2dfan.com/subjects/search"; const BASE_URL = "https://2dfan.com"; const REGEX = /

(?.*?)<\/a><\/h4>/gs; -interface TwoDFanResponse { - subjects: string; // This is an HTML string -} - async function searchTWOdfan(game: string): Promise { const searchResult: PlatformSearchResult = { name: "2dfan", @@ -25,14 +21,13 @@ async function searchTWOdfan(game: string): Promise { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } + const html: string = await response.text(); + console.log(JSON.stringify({ message: "2dfan API HTML Response", - html: response.text(), + html: html, level: "info", })); - - const data = await response.json() as TwoDFanResponse; - const html = data.subjects; const matches = html.matchAll(REGEX); From 90e047aae893caee2ce8ab12996145d9d27256dd Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 01:40:50 +0800 Subject: [PATCH 08/22] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=90=9C=E7=B4=A2=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * VikaACG 平台:新增处理后 HTML 响应的调试日志,方便排查数据解析问题。 * TWOdfan 平台:移除不再需要的 API HTML 响应调试日志,精简控制台输出。 --- src/platforms/gal/VikaACG.ts | 6 ++++++ src/platforms/patch/TWOdfan.ts | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index 6343b7d..4e9b2b8 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -51,6 +51,12 @@ async function searchVikaACG(game: string): Promise { const matches = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').matchAll(REGEX); + console.log(JSON.stringify({ + message: "VikaACG API HTML Response", + html: html.replaceAll('\\/', '/').replaceAll('\\\\', '\\'), + level: "info", + })); + const items: SearchResultItem[] = []; for (const match of matches) { if (match.groups?.NAME && match.groups?.URL) { diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index 2bcece6..5fa81c6 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -22,12 +22,6 @@ async function searchTWOdfan(game: string): Promise { } const html: string = await response.text(); - - console.log(JSON.stringify({ - message: "2dfan API HTML Response", - html: html, - level: "info", - })); const matches = html.matchAll(REGEX); From 9015c54201be605541b6cbda1d47ccbbac647dec Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 02:34:19 +0800 Subject: [PATCH 09/22] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20VikaACG=20HTM?= =?UTF-8?q?L=20=E5=93=8D=E5=BA=94=E4=B8=AD=E7=9A=84=E8=BD=AC=E4=B9=89?= =?UTF-8?q?=E5=8F=8C=E5=BC=95=E5=8F=B7=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 在处理VikaACG的HTML响应时,增加对转义双引号的正确处理。 * 确保正则表达式能够准确匹配内容,避免因转义字符导致的解析错误。 * 移除调试用的控制台日志输出。 --- src/platforms/gal/VikaACG.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index 4e9b2b8..b155b7c 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -49,13 +49,7 @@ async function searchVikaACG(game: string): Promise { // .json() will parse the JSON and unescape the string content. const html: string = await response.text(); - const matches = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').matchAll(REGEX); - - console.log(JSON.stringify({ - message: "VikaACG API HTML Response", - html: html.replaceAll('\\/', '/').replaceAll('\\\\', '\\'), - level: "info", - })); + const matches = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').replaceAll('\\"', '"').matchAll(REGEX); const items: SearchResultItem[] = []; for (const match of matches) { From eed68a9a4d33dd633c2165c5e3c9d2f294ac0e31 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sat, 23 Aug 2025 03:29:01 +0800 Subject: [PATCH 10/22] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20VikaACG=20?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E4=B8=AD=E7=9A=84=20Unicod?= =?UTF-8?q?e=20=E8=BD=AC=E4=B9=89=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 解决 VikaACG 平台搜索结果中 `\uXXXX` Unicode 转义字符未正确解码的问题。 * 新增逻辑将 `\uXXXX` 转义序列转换为实际字符,确保搜索结果内容正确显示。 --- src/platforms/gal/VikaACG.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index b155b7c..286b764 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -49,7 +49,10 @@ async function searchVikaACG(game: string): Promise { // .json() will parse the JSON and unescape the string content. const html: string = await response.text(); - const matches = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').replaceAll('\\"', '"').matchAll(REGEX); + const decodedHtml = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').replaceAll('\\"', '"').replace(/\\u([\d\w]{4})/gi, (match, grp) => { + return String.fromCharCode(parseInt(grp, 16)); + }); + const matches = decodedHtml.matchAll(REGEX); const items: SearchResultItem[] = []; for (const match of matches) { From 29f4e0c67c0f72cd40d20be8531faa1dbd9f061f Mon Sep 17 00:00:00 2001 From: Jurangren Date: Sun, 5 Oct 2025 18:10:49 +0800 Subject: [PATCH 11/22] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=B4=AB?= =?UTF-8?q?=E7=BC=98=E7=A4=BE=E6=90=9C=E7=B4=A2=E5=B9=B6=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 移除 `zypassword` 参数,简化核心搜索函数接口。 * 紫缘社搜索逻辑重构,改为解析HTML获取数据,不再依赖API。 * 更新紫缘社平台名称为“紫缘社”,并调整其显示颜色。 * 优化紫缘社搜索结果的标题提取,优先使用中文标题。 --- src/core.ts | 5 +-- src/index.ts | 3 +- src/platforms/gal/ZiYuanShe.ts | 81 +++++++++++++--------------------- 3 files changed, 32 insertions(+), 57 deletions(-) diff --git a/src/core.ts b/src/core.ts index 64519c7..d12c6bd 100644 --- a/src/core.ts +++ b/src/core.ts @@ -14,13 +14,11 @@ function formatStreamEvent(data: object): string { * @param game 要搜索的游戏名称。 * @param platforms 要使用的平台列表。 * @param writer 用于写入 SSE 事件的 WritableStreamDefaultWriter。 - * @param zypassword (可选) 访问某些平台可能需要的密码。 */ export async function handleSearchRequestStream( game: string, platforms: Platform[], writer: WritableStreamDefaultWriter, - zypassword: string = "" // 添加 zypassword 参数 ): Promise { // 记录搜索关键词 console.log(JSON.stringify({ @@ -36,8 +34,7 @@ export async function handleSearchRequestStream( const searchPromises = platforms.map(async (platform) => { try { - // 传递 zypassword 给平台搜索函数 - const result = await platform.search(game, zypassword); + const result = await platform.search(game); completed++; const progress: StreamProgress = { completed, total }; diff --git a/src/index.ts b/src/index.ts index 99e6e60..1603ac1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,6 @@ async function handleSearch(request: Request, env: Env, ctx: ExecutionContext, p try { const formData = await request.formData(); const game = formData.get("game") as string; - const zypassword = formData.get("zypassword") as string || ""; // 获取 zypassword if (!game || typeof game !== 'string') { @@ -27,7 +26,7 @@ async function handleSearch(request: Request, env: Env, ctx: ExecutionContext, p // 将异步任务交给 waitUntil 来处理,确保它能完整执行 ctx.waitUntil( - handleSearchRequestStream(game.trim(), platforms, writer, zypassword) // 传递 zypassword + handleSearchRequestStream(game.trim(), platforms, writer) .catch(err => console.error("Streaming error:", err)) .finally(() => writer.close()) ); diff --git a/src/platforms/gal/ZiYuanShe.ts b/src/platforms/gal/ZiYuanShe.ts index 0f7fe30..1d66e7a 100644 --- a/src/platforms/gal/ZiYuanShe.ts +++ b/src/platforms/gal/ZiYuanShe.ts @@ -1,69 +1,48 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; -const API_URL = "https://galzy.eu.org/api/fs/search"; const BASE_URL = "https://galzy.eu.org"; -interface ZiYuanSheItem { - name: string; - parent: string; -} - -interface ZiYuanSheResponse { - message: string; - data: { - content: ZiYuanSheItem[]; - total: number; - }; -} - -async function searchZiYuanShe(game: string, zypassword: string = ""): Promise { +async function searchZiYuanShe(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "紫缘Gal", + name: "紫缘社", count: 0, items: [], }; try { - const payload = { - parent: "/", - keywords: game, - scope: 0, - page: 1, - per_page: 999999, // Corresponds to MAX_RESULTS - password: zypassword, // Pass the zypassword here - }; - - const response = await fetchClient(API_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }); + const response = await fetchClient(`${BASE_URL}/search?q=${encodeURIComponent(game)}`); if (!response.ok) { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } - const data = await response.json() as ZiYuanSheResponse; - - if (data.message !== "success") { - throw new Error(`${data.message}`); - } - - if (data.data.total !== data.data.content.length) { - throw new Error("访问密码错误"); + const html = await response.text(); + const tempContent = html.split('')[0]; + const cleanedScriptContent = scriptContent.substring(scriptContent.indexOf(':') + 1).replace(/\\"/g, '"'); + const jsonData = JSON.parse(cleanedScriptContent); + + const gameListData = jsonData[3].children[2][3].gameListData.hits; + + if (gameListData) { + const items: SearchResultItem[] = gameListData.map((item: any) => ({ + name: (() => { + const zhTitle = item.titles.find((title: any) => title.lang === 'zh-Hans'); + const jaTitle = item.titles.find((title: any) => title.lang === 'ja'); + if (zhTitle) { + return zhTitle.title; + } + if (jaTitle) { + return jaTitle.title; + } + return item.titles[0]?.title || ''; + })(), + url: `${BASE_URL}/${item.id}`, + })); + searchResult.items = items; + searchResult.count = items.length; } - - const items: SearchResultItem[] = data.data.content.map(item => ({ - name: item.name.trim(), - url: BASE_URL + item.parent + "/" + item.name, - })); - - searchResult.items = items; - searchResult.count = items.length; - } catch (error) { if (error instanceof Error) { searchResult.error = error.message; @@ -77,8 +56,8 @@ async function searchZiYuanShe(game: string, zypassword: string = ""): Promise

Date: Sun, 5 Oct 2025 20:12:23 +0800 Subject: [PATCH 12/22] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=B4=AB?= =?UTF-8?q?=E7=BC=98Gal=E6=90=9C=E7=B4=A2=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整搜索API端点,从解析HTML改为直接处理JSON响应。 - 优化游戏标题提取逻辑,优先显示简体/繁体中文标题。 --- src/platforms/gal/ZiYuanShe.ts | 49 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/platforms/gal/ZiYuanShe.ts b/src/platforms/gal/ZiYuanShe.ts index 1d66e7a..b1709c1 100644 --- a/src/platforms/gal/ZiYuanShe.ts +++ b/src/platforms/gal/ZiYuanShe.ts @@ -5,41 +5,44 @@ const BASE_URL = "https://galzy.eu.org"; async function searchZiYuanShe(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "紫缘社", + name: "紫缘Gal", count: 0, items: [], }; try { - const response = await fetchClient(`${BASE_URL}/search?q=${encodeURIComponent(game)}`); - + const response = await fetchClient(`${BASE_URL}/api/search?q=${encodeURIComponent(game)}`); if (!response.ok) { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } - - const html = await response.text(); - const tempContent = html.split('')[0]; - const cleanedScriptContent = scriptContent.substring(scriptContent.indexOf(':') + 1).replace(/\\"/g, '"'); - const jsonData = JSON.parse(cleanedScriptContent); - const gameListData = jsonData[3].children[2][3].gameListData.hits; + const resJson: any = await response.json(); + + const gameListData = resJson.hits; if (gameListData) { - const items: SearchResultItem[] = gameListData.map((item: any) => ({ - name: (() => { - const zhTitle = item.titles.find((title: any) => title.lang === 'zh-Hans'); - const jaTitle = item.titles.find((title: any) => title.lang === 'ja'); - if (zhTitle) { - return zhTitle.title; + const items: SearchResultItem[] = gameListData.map((item: any) => { + let name: string = "未知"; + let firstTitle: string | undefined; + + for (const titleObj of item.titles) { + if (!firstTitle) { + firstTitle = titleObj.title; } - if (jaTitle) { - return jaTitle.title; + if (["zh-Hans", "zh-Hant"].includes(titleObj.lang)) { + name = titleObj.title; + break; } - return item.titles[0]?.title || ''; - })(), - url: `${BASE_URL}/${item.id}`, - })); + } + if (name === "未知" && firstTitle) { + name = firstTitle; + } + + return { + name: name.trim(), + url: `${BASE_URL}/${item.id}`, + }; + }); searchResult.items = items; searchResult.count = items.length; } @@ -56,7 +59,7 @@ async function searchZiYuanShe(game: string): Promise { } const ZiYuanShe: Platform = { - name: "紫缘社", + name: "紫缘Gal", color: "lime", magic: false, search: searchZiYuanShe, From 4d93d1b3e1ecdaf0b9aa77e1787033253e0eea07 Mon Sep 17 00:00:00 2001 From: Jurangren Date: Mon, 6 Oct 2025 22:09:13 +0800 Subject: [PATCH 13/22] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=A0=87=E7=AD=BE=E7=B3=BB=E7=BB=9F=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=B9=B3=E5=8F=B0=E4=BF=A1=E6=81=AF=E5=8F=8A=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入平台标签系统,提供详细的平台特性说明。 - 重构搜索结果初始化,避免平台名称重复定义。 - 修正核心搜索错误日志,确保正确记录平台名称。 - 移除两个Galgame平台:TianYouErCiYuan(收费)和YingZhiGuang(网站转型)。 - 更新部分平台的颜色、魔法属性和标签信息。 --- src/core.ts | 5 ++- src/platforms/gal/ACGYingYingGuai.ts | 2 +- src/platforms/gal/BiAnXingLu.ts | 2 +- src/platforms/gal/DaoHeGal.ts | 2 +- src/platforms/gal/FuFuACG.ts | 2 +- src/platforms/gal/GGBases.ts | 2 +- src/platforms/gal/GGS.ts | 2 +- src/platforms/gal/GalTuShuGuan.ts | 2 +- src/platforms/gal/GalgameX.ts | 2 +- src/platforms/gal/Hikarinagi.ts | 2 +- src/platforms/gal/JiMengACG.ts | 2 +- src/platforms/gal/Koyso.ts | 2 +- src/platforms/gal/KunGalgame.ts | 2 +- src/platforms/gal/LiSiTanACG.ts | 2 +- src/platforms/gal/LiangZiACG.ts | 2 +- src/platforms/gal/MaoMaoWangPan.ts | 2 +- src/platforms/gal/MiaoYuanLingYu.ts | 2 +- src/platforms/gal/Nysoure.ts | 4 +- src/platforms/gal/QingJiACG.ts | 2 +- src/platforms/gal/ShenShiTianTang.ts | 4 +- src/platforms/gal/TaoHuaYuan.ts | 2 +- src/platforms/gal/TianYouErCiYuan.ts | 57 ------------------------ src/platforms/gal/TouchGal.ts | 2 +- src/platforms/gal/VikaACG.ts | 4 +- src/platforms/gal/WeiZhiYunPan.ts | 2 +- src/platforms/gal/YingZhiGuang.ts | 59 ------------------------- src/platforms/gal/YouYuDeloli.ts | 2 +- src/platforms/gal/YueYao.ts | 2 +- src/platforms/gal/ZeroFive.ts | 2 +- src/platforms/gal/ZhenHongXiaoZhan.ts | 2 +- src/platforms/gal/ZiLingDeMiaoMiaoWu.ts | 2 +- src/platforms/gal/ZiYuanShe.ts | 2 +- src/platforms/gal/index.ts | 4 -- src/platforms/gal/xxacg.ts | 4 +- src/platforms/patch/KunGalgameBuDing.ts | 2 +- src/platforms/patch/TWOdfan.ts | 6 +-- src/types.ts | 4 +- 37 files changed, 44 insertions(+), 161 deletions(-) delete mode 100644 src/platforms/gal/TianYouErCiYuan.ts delete mode 100644 src/platforms/gal/YingZhiGuang.ts diff --git a/src/core.ts b/src/core.ts index d12c6bd..1114159 100644 --- a/src/core.ts +++ b/src/core.ts @@ -43,13 +43,14 @@ export async function handleSearchRequestStream( if (result.error) { // 记录平台错误 console.log(JSON.stringify({ - message: `平台 ${result.name} 搜索错误: ${result.error}`, + message: `平台 ${platform.name} 搜索错误: ${result.error}`, level: "error", })); } const streamResult: StreamResult = { - name: result.name, + name: platform.name, color: result.error ? 'red' : platform.color, + tags: platform.tags, items: result.items, error: result.error, }; diff --git a/src/platforms/gal/ACGYingYingGuai.ts b/src/platforms/gal/ACGYingYingGuai.ts index 507e084..8d381be 100644 --- a/src/platforms/gal/ACGYingYingGuai.ts +++ b/src/platforms/gal/ACGYingYingGuai.ts @@ -6,7 +6,6 @@ const REGEX = / { const searchResult: PlatformSearchResult = { - name: "ACG嘤嘤怪", count: 0, items: [], }; @@ -51,6 +50,7 @@ async function searchACGYingYingGuai(game: string): Promise

(?.*? async function searchBiAnXingLu(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "彼岸星露", count: 0, items: [], }; @@ -51,6 +50,7 @@ async function searchBiAnXingLu(game: string): Promise { const BiAnXingLu: Platform = { name: "彼岸星露", color: "lime", + tags: [], magic: false, search: searchBiAnXingLu, }; diff --git a/src/platforms/gal/DaoHeGal.ts b/src/platforms/gal/DaoHeGal.ts index 37e6691..d138e97 100644 --- a/src/platforms/gal/DaoHeGal.ts +++ b/src/platforms/gal/DaoHeGal.ts @@ -18,7 +18,6 @@ interface DaoHeGalResponse { async function searchDaoHeGal(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "稻荷GAL", count: 0, items: [], }; @@ -63,6 +62,7 @@ async function searchDaoHeGal(game: string): Promise { const DaoHeGal: Platform = { name: "稻荷GAL", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchDaoHeGal, }; diff --git a/src/platforms/gal/FuFuACG.ts b/src/platforms/gal/FuFuACG.ts index 43f1d89..640d20a 100644 --- a/src/platforms/gal/FuFuACG.ts +++ b/src/platforms/gal/FuFuACG.ts @@ -15,7 +15,6 @@ interface FuFuACGResponse { async function searchFuFuACG(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "FuFuACG", count: 0, items: [], }; @@ -59,6 +58,7 @@ async function searchFuFuACG(game: string): Promise { const FuFuACG: Platform = { name: "FuFuACG", color: "white", + tags: ["LoginPay"], magic: false, search: searchFuFuACG, }; diff --git a/src/platforms/gal/GGBases.ts b/src/platforms/gal/GGBases.ts index 232abaa..63cf1b2 100644 --- a/src/platforms/gal/GGBases.ts +++ b/src/platforms/gal/GGBases.ts @@ -7,7 +7,6 @@ const REGEX = / { const GGBases: Platform = { name: "GGBases", color: "lime", + tags: ["NoReq", "BTmag"], magic: false, search: searchGGBases, }; diff --git a/src/platforms/gal/GGS.ts b/src/platforms/gal/GGS.ts index d57f223..5a47e75 100644 --- a/src/platforms/gal/GGS.ts +++ b/src/platforms/gal/GGS.ts @@ -11,7 +11,6 @@ interface GgsItem { async function searchGGS(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "GGS", count: 0, items: [], }; @@ -49,6 +48,7 @@ async function searchGGS(game: string): Promise { const GGS: Platform = { name: "GGS", color: "lime", + tags: ["NoReq"], magic: false, search: searchGGS, }; diff --git a/src/platforms/gal/GalTuShuGuan.ts b/src/platforms/gal/GalTuShuGuan.ts index 5f60bb9..5914f79 100644 --- a/src/platforms/gal/GalTuShuGuan.ts +++ b/src/platforms/gal/GalTuShuGuan.ts @@ -21,7 +21,6 @@ function stripHtml(html: string): string { async function searchGalTuShuGuan(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "GAL图书馆", count: 0, items: [], }; @@ -81,6 +80,7 @@ async function searchGalTuShuGuan(game: string): Promise { const GalTuShuGuan: Platform = { name: "GAL图书馆", color: "lime", + tags: ["NoReq", "SplDrive"], magic: false, search: searchGalTuShuGuan, }; diff --git a/src/platforms/gal/GalgameX.ts b/src/platforms/gal/GalgameX.ts index a7e63cb..e35000e 100644 --- a/src/platforms/gal/GalgameX.ts +++ b/src/platforms/gal/GalgameX.ts @@ -15,7 +15,6 @@ interface GalgameXResponse { async function searchGalgameX(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "Galgamex", count: 0, items: [], }; @@ -76,6 +75,7 @@ async function searchGalgameX(game: string): Promise { const GalgameX: Platform = { name: "Galgamex", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchGalgameX, }; diff --git a/src/platforms/gal/Hikarinagi.ts b/src/platforms/gal/Hikarinagi.ts index 28f0b88..5cac60c 100644 --- a/src/platforms/gal/Hikarinagi.ts +++ b/src/platforms/gal/Hikarinagi.ts @@ -6,7 +6,6 @@ const REGEX = /" class="lazyload fit-cover radius8">.*?

async function searchHikarinagi(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "Hikarinagi", count: 0, items: [], }; @@ -51,6 +50,7 @@ async function searchHikarinagi(game: string): Promise { const Hikarinagi: Platform = { name: "Hikarinagi", color: "white", + tags: ["LoginPay", "SuDrive"], magic: false, search: searchHikarinagi, }; diff --git a/src/platforms/gal/JiMengACG.ts b/src/platforms/gal/JiMengACG.ts index ef1c8a4..2d3a9bf 100644 --- a/src/platforms/gal/JiMengACG.ts +++ b/src/platforms/gal/JiMengACG.ts @@ -17,7 +17,6 @@ interface JiMengACGResponse { async function searchJiMengACG(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "绮梦ACG", count: 0, items: [], }; @@ -63,6 +62,7 @@ async function searchJiMengACG(game: string): Promise { const JiMengACG: Platform = { name: "绮梦ACG", color: "lime", + tags: ["Rep", "SuDrive"], magic: false, search: searchJiMengACG, }; diff --git a/src/platforms/gal/Koyso.ts b/src/platforms/gal/Koyso.ts index a5964ea..52722fe 100644 --- a/src/platforms/gal/Koyso.ts +++ b/src/platforms/gal/Koyso.ts @@ -7,7 +7,6 @@ const REGEX = /.*? { const Koyso: Platform = { name: "Koyso", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchKoyso, }; diff --git a/src/platforms/gal/KunGalgame.ts b/src/platforms/gal/KunGalgame.ts index f2e108d..720c4bd 100644 --- a/src/platforms/gal/KunGalgame.ts +++ b/src/platforms/gal/KunGalgame.ts @@ -14,7 +14,6 @@ interface KunGalgameItem { async function searchKunGalgame(game: string): Promise { const searchResult: PlatformSearchResult = { - name: "鲲Galgame", count: 0, items: [], }; @@ -60,6 +59,7 @@ async function searchKunGalgame(game: string): Promise { const KunGalgame: Platform = { name: "鲲Galgame", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchKunGalgame, }; diff --git a/src/platforms/gal/LiSiTanACG.ts b/src/platforms/gal/LiSiTanACG.ts index 6039c6b..e912210 100644 --- a/src/platforms/gal/LiSiTanACG.ts +++ b/src/platforms/gal/LiSiTanACG.ts @@ -7,7 +7,6 @@ const REGEX = /.*?(.*?)<\/title>.*?<url>(.*?)<\/url>.*?<\/entry>/g async function searchLiSiTanACG(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "莉斯坦ACG", count: 0, items: [], }; @@ -52,6 +51,7 @@ async function searchLiSiTanACG(game: string): Promise<PlatformSearchResult> { const LiSiTanACG: Platform = { name: "莉斯坦ACG", color: "lime", + tags: ["NoReq", "NoSplDrive"], magic: false, search: searchLiSiTanACG, }; diff --git a/src/platforms/gal/LiangZiACG.ts b/src/platforms/gal/LiangZiACG.ts index 234574c..c12d73d 100644 --- a/src/platforms/gal/LiangZiACG.ts +++ b/src/platforms/gal/LiangZiACG.ts @@ -6,7 +6,6 @@ const REGEX = />\s*<h2 class="item-heading"><a target="_blank" href="(?<URL>.*?) async function searchLiangZiACG(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "量子acg", count: 0, items: [], }; @@ -51,6 +50,7 @@ async function searchLiangZiACG(game: string): Promise<PlatformSearchResult> { const LiangZiACG: Platform = { name: "量子acg", color: "white", + tags: ["NoReq", "SuDrive"], magic: false, search: searchLiangZiACG, }; diff --git a/src/platforms/gal/MaoMaoWangPan.ts b/src/platforms/gal/MaoMaoWangPan.ts index 9956cf2..149a37b 100644 --- a/src/platforms/gal/MaoMaoWangPan.ts +++ b/src/platforms/gal/MaoMaoWangPan.ts @@ -19,7 +19,6 @@ interface MaoMaoResponse { async function searchMaoMaoWangPan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "猫猫网盘", count: 0, items: [], }; @@ -77,6 +76,7 @@ async function searchMaoMaoWangPan(game: string): Promise<PlatformSearchResult> const MaoMaoWangPan: Platform = { name: "猫猫网盘", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchMaoMaoWangPan, }; diff --git a/src/platforms/gal/MiaoYuanLingYu.ts b/src/platforms/gal/MiaoYuanLingYu.ts index f5ff9b3..e805633 100644 --- a/src/platforms/gal/MiaoYuanLingYu.ts +++ b/src/platforms/gal/MiaoYuanLingYu.ts @@ -6,7 +6,6 @@ const REGEX = /<div class="item-thumbnail">\s*<a target="_blank" href="(?<URL>.* async function searchMiaoYuanLingYu(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "喵源领域", count: 0, items: [], }; @@ -56,6 +55,7 @@ async function searchMiaoYuanLingYu(game: string): Promise<PlatformSearchResult> const MiaoYuanLingYu: Platform = { name: "喵源领域", color: "white", + tags: ["Login", "SuDrive"], magic: false, search: searchMiaoYuanLingYu, }; diff --git a/src/platforms/gal/Nysoure.ts b/src/platforms/gal/Nysoure.ts index 1b7dd5a..6335dee 100644 --- a/src/platforms/gal/Nysoure.ts +++ b/src/platforms/gal/Nysoure.ts @@ -16,7 +16,6 @@ interface NysoureResponse { async function searchNysoure(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "Nysoure", count: 0, items: [], }; @@ -59,7 +58,8 @@ async function searchNysoure(game: string): Promise<PlatformSearchResult> { const Nysoure: Platform = { name: "Nysoure", - color: "gold", + color: "lime", + tags: ["NoReq", "magic", "SuDrive"], magic: true, search: searchNysoure, }; diff --git a/src/platforms/gal/QingJiACG.ts b/src/platforms/gal/QingJiACG.ts index 106c31b..36ec732 100644 --- a/src/platforms/gal/QingJiACG.ts +++ b/src/platforms/gal/QingJiACG.ts @@ -6,7 +6,6 @@ const REGEX = /" class="lazyload fit-cover radius8">.*?<h2 class="item-heading"> async function searchQingJiACG(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "青桔ACG", count: 0, items: [], }; @@ -57,6 +56,7 @@ async function searchQingJiACG(game: string): Promise<PlatformSearchResult> { const QingJiACG: Platform = { name: "青桔ACG", color: "lime", + tags: ["NoReq", "SplDrive"], magic: false, search: searchQingJiACG, }; diff --git a/src/platforms/gal/ShenShiTianTang.ts b/src/platforms/gal/ShenShiTianTang.ts index e7a5a57..37171ac 100644 --- a/src/platforms/gal/ShenShiTianTang.ts +++ b/src/platforms/gal/ShenShiTianTang.ts @@ -6,7 +6,6 @@ const REGEX = /<h2 class="post-list-title">\s*<a href="(?<URL>.*?)" title=".+?" async function searchShenShiTianTang(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "绅仕天堂", count: 0, items: [], }; @@ -50,7 +49,8 @@ async function searchShenShiTianTang(game: string): Promise<PlatformSearchResult const ShenShiTianTang: Platform = { name: "绅仕天堂", - color: "gold", + color: "white", + tags: ["Login", "magic", "SuDrive"], magic: true, search: searchShenShiTianTang, }; diff --git a/src/platforms/gal/TaoHuaYuan.ts b/src/platforms/gal/TaoHuaYuan.ts index 125c3df..4750270 100644 --- a/src/platforms/gal/TaoHuaYuan.ts +++ b/src/platforms/gal/TaoHuaYuan.ts @@ -10,7 +10,6 @@ interface TaoHuaYuanItem { async function searchTaoHuaYuan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "桃花源", count: 0, items: [], }; @@ -48,6 +47,7 @@ async function searchTaoHuaYuan(game: string): Promise<PlatformSearchResult> { const TaoHuaYuan: Platform = { name: "桃花源", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchTaoHuaYuan, }; diff --git a/src/platforms/gal/TianYouErCiYuan.ts b/src/platforms/gal/TianYouErCiYuan.ts deleted file mode 100644 index e087127..0000000 --- a/src/platforms/gal/TianYouErCiYuan.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { fetchClient } from "../../utils/httpClient"; -import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; - -const API_URL = "https://www.tiangal.com/search/"; -const REGEX = /<\/i><\/a><h2><a href="(?<URL>.*?)" title="(?<NAME>.*?)"/gs; - -async function searchTianYouErCiYuan(game: string): Promise<PlatformSearchResult> { - const searchResult: PlatformSearchResult = { - name: "天游二次元", - count: 0, - items: [], - }; - - try { - const url = new URL(API_URL + encodeURIComponent(game)); // URL path parameter - - const response = await fetchClient(url); - if (!response.ok) { - throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); - } - - const html = await response.text(); - const matches = html.matchAll(REGEX); - - const items: SearchResultItem[] = []; - for (const match of matches) { - if (match.groups?.NAME && match.groups?.URL) { - items.push({ - name: match.groups.NAME.trim(), - url: match.groups.URL, - }); - } - } - - searchResult.items = items; - searchResult.count = items.length; - - } catch (error) { - if (error instanceof Error) { - searchResult.error = error.message; - } else { - searchResult.error = "An unknown error occurred"; - } - searchResult.count = -1; - } - - return searchResult; -} - -const TianYouErCiYuan: Platform = { - name: "天游二次元", - color: "gold", - magic: true, - search: searchTianYouErCiYuan, -}; - -export default TianYouErCiYuan; \ No newline at end of file diff --git a/src/platforms/gal/TouchGal.ts b/src/platforms/gal/TouchGal.ts index c829e30..e6726ab 100644 --- a/src/platforms/gal/TouchGal.ts +++ b/src/platforms/gal/TouchGal.ts @@ -6,7 +6,6 @@ const BASE_URL = "https://www.touchgal.us/"; async function searchTouchGal(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "TouchGal", count: 0, items: [], }; @@ -67,6 +66,7 @@ async function searchTouchGal(game: string): Promise<PlatformSearchResult> { const TouchGal: Platform = { name: "TouchGal", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchTouchGal, }; diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index 286b764..50489ec 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -6,7 +6,6 @@ const REGEX = /<h2><a target="_blank" href="(?<URL>.*?)">(?<NAME>.*?)<\/a>/gs; async function searchVikaACG(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "VikaACG", count: 0, items: [], }; @@ -81,7 +80,8 @@ async function searchVikaACG(game: string): Promise<PlatformSearchResult> { const VikaACG: Platform = { name: "VikaACG", - color: "gold", + color: "white", + tags: ["LoginPay", "magic", "MixDrive"], magic: true, search: searchVikaACG, }; diff --git a/src/platforms/gal/WeiZhiYunPan.ts b/src/platforms/gal/WeiZhiYunPan.ts index 3d3f4a2..cda08e8 100644 --- a/src/platforms/gal/WeiZhiYunPan.ts +++ b/src/platforms/gal/WeiZhiYunPan.ts @@ -19,7 +19,6 @@ interface WeiZhiYunPanResponse { async function searchWeiZhiYunPan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "未知云盘", count: 0, items: [], }; @@ -75,6 +74,7 @@ async function searchWeiZhiYunPan(game: string): Promise<PlatformSearchResult> { const WeiZhiYunPan: Platform = { name: "未知云盘", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchWeiZhiYunPan, }; diff --git a/src/platforms/gal/YingZhiGuang.ts b/src/platforms/gal/YingZhiGuang.ts deleted file mode 100644 index db5845c..0000000 --- a/src/platforms/gal/YingZhiGuang.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { fetchClient } from "../../utils/httpClient"; -import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; - -const DATA_URL = "https://yinghu.netlify.app/search.xml"; -const BASE_URL = "https://yinghu.netlify.app"; -const REGEX = /<entry>.*?<title>(.*?)<\/title>.*?<url>(.*?)<\/url>.*?<\/entry>/gs; - -async function searchYingZhiGuang(game: string): Promise<PlatformSearchResult> { - const searchResult: PlatformSearchResult = { - name: "萤ノ光", - count: 0, - items: [], - }; - - try { - const response = await fetchClient(DATA_URL); - if (!response.ok) { - throw new Error(`Failed to fetch data from ${DATA_URL}`); - } - - const xmlText = await response.text(); - const matches = xmlText.matchAll(REGEX); - - const items: SearchResultItem[] = []; - for (const match of matches) { - const title = match[1]; - const urlPath = match[2]; - - if (title && urlPath && title.includes(game)) { - items.push({ - name: title.trim(), - url: BASE_URL + urlPath, - }); - } - } - - searchResult.items = items; - searchResult.count = items.length; - - } catch (error) { - if (error instanceof Error) { - searchResult.error = error.message; - } else { - searchResult.error = "An unknown error occurred"; - } - searchResult.count = -1; - } - - return searchResult; -} - -const YingZhiGuang: Platform = { - name: "萤ノ光", - color: "lime", - magic: false, - search: searchYingZhiGuang, -}; - -export default YingZhiGuang; \ No newline at end of file diff --git a/src/platforms/gal/YouYuDeloli.ts b/src/platforms/gal/YouYuDeloli.ts index ca8da51..9a380a0 100644 --- a/src/platforms/gal/YouYuDeloli.ts +++ b/src/platforms/gal/YouYuDeloli.ts @@ -6,7 +6,6 @@ const REGEX = /<p style="text-align: center;"> <a href=".*?" target="_blank">.*? async function searchYouYuDeloli(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "忧郁的loli", count: 0, items: [], }; @@ -54,6 +53,7 @@ async function searchYouYuDeloli(game: string): Promise<PlatformSearchResult> { const YouYuDeloli: Platform = { name: "忧郁的loli", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchYouYuDeloli, }; diff --git a/src/platforms/gal/YueYao.ts b/src/platforms/gal/YueYao.ts index c50ef2b..a490b1c 100644 --- a/src/platforms/gal/YueYao.ts +++ b/src/platforms/gal/YueYao.ts @@ -7,7 +7,6 @@ const REGEX = /<entry>.*?<title>(.*?)<\/title>.*?<url>(.*?)<\/url>.*?<\/entry>/g async function searchYueYao(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "月谣", count: 0, items: [], }; @@ -52,6 +51,7 @@ async function searchYueYao(game: string): Promise<PlatformSearchResult> { const YueYao: Platform = { name: "月谣", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchYueYao, }; diff --git a/src/platforms/gal/ZeroFive.ts b/src/platforms/gal/ZeroFive.ts index 1917f5c..2c03814 100644 --- a/src/platforms/gal/ZeroFive.ts +++ b/src/platforms/gal/ZeroFive.ts @@ -19,7 +19,6 @@ interface ZeroFiveResponse { async function searchZeroFive(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "05的资源小站", count: 0, items: [], }; @@ -75,6 +74,7 @@ async function searchZeroFive(game: string): Promise<PlatformSearchResult> { const ZeroFive: Platform = { name: "05的资源小站", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchZeroFive, }; diff --git a/src/platforms/gal/ZhenHongXiaoZhan.ts b/src/platforms/gal/ZhenHongXiaoZhan.ts index 779ea10..953f05b 100644 --- a/src/platforms/gal/ZhenHongXiaoZhan.ts +++ b/src/platforms/gal/ZhenHongXiaoZhan.ts @@ -7,7 +7,6 @@ const REGEX = /hover:underline" href="(?<URL>.+?)">\s*(?<NAME>.+?)\s*<\/a>/gs; async function searchZhenHongXiaoZhan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "真红小站", count: 0, items: [], }; @@ -52,6 +51,7 @@ async function searchZhenHongXiaoZhan(game: string): Promise<PlatformSearchResul const ZhenHongXiaoZhan: Platform = { name: "真红小站", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchZhenHongXiaoZhan, }; diff --git a/src/platforms/gal/ZiLingDeMiaoMiaoWu.ts b/src/platforms/gal/ZiLingDeMiaoMiaoWu.ts index c307cc6..75ae756 100644 --- a/src/platforms/gal/ZiLingDeMiaoMiaoWu.ts +++ b/src/platforms/gal/ZiLingDeMiaoMiaoWu.ts @@ -19,7 +19,6 @@ interface ZiLingDeMiaoMiaoWuResponse { async function searchZiLingDeMiaoMiaoWu(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "梓澪の妙妙屋", count: 0, items: [], }; @@ -75,6 +74,7 @@ async function searchZiLingDeMiaoMiaoWu(game: string): Promise<PlatformSearchRes const ZiLingDeMiaoMiaoWu: Platform = { name: "梓澪の妙妙屋", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchZiLingDeMiaoMiaoWu, }; diff --git a/src/platforms/gal/ZiYuanShe.ts b/src/platforms/gal/ZiYuanShe.ts index b1709c1..f425e7a 100644 --- a/src/platforms/gal/ZiYuanShe.ts +++ b/src/platforms/gal/ZiYuanShe.ts @@ -5,7 +5,6 @@ const BASE_URL = "https://galzy.eu.org"; async function searchZiYuanShe(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "紫缘Gal", count: 0, items: [], }; @@ -61,6 +60,7 @@ async function searchZiYuanShe(game: string): Promise<PlatformSearchResult> { const ZiYuanShe: Platform = { name: "紫缘Gal", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchZiYuanShe, }; diff --git a/src/platforms/gal/index.ts b/src/platforms/gal/index.ts index 0ae4d8a..10ce314 100644 --- a/src/platforms/gal/index.ts +++ b/src/platforms/gal/index.ts @@ -19,12 +19,10 @@ import Nysoure from "./Nysoure"; import QingJiACG from "./QingJiACG"; import ShenShiTianTang from "./ShenShiTianTang"; import TaoHuaYuan from "./TaoHuaYuan"; -import TianYouErCiYuan from "./TianYouErCiYuan"; import TouchGal from "./TouchGal"; import VikaACG from "./VikaACG"; import WeiZhiYunPan from "./WeiZhiYunPan"; import xxacg from "./xxacg"; -import YingZhiGuang from "./YingZhiGuang"; import YouYuDeloli from "./YouYuDeloli"; import YueYao from "./YueYao"; import ZeroFive from "./ZeroFive"; @@ -53,12 +51,10 @@ const platforms: Platform[] = [ QingJiACG, ShenShiTianTang, TaoHuaYuan, - TianYouErCiYuan, TouchGal, VikaACG, WeiZhiYunPan, xxacg, - YingZhiGuang, YouYuDeloli, YueYao, ZeroFive, diff --git a/src/platforms/gal/xxacg.ts b/src/platforms/gal/xxacg.ts index 1f762cb..35af273 100644 --- a/src/platforms/gal/xxacg.ts +++ b/src/platforms/gal/xxacg.ts @@ -9,7 +9,6 @@ function stripHtml(html: string): string { async function searchXxacg(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "xxacg", count: 0, items: [], }; @@ -56,7 +55,8 @@ async function searchXxacg(game: string): Promise<PlatformSearchResult> { const xxacg: Platform = { name: "xxacg", - color: "gold", + color: "white", + tags: ["Login", "magic", "NoSplDrive"], magic: true, search: searchXxacg, }; diff --git a/src/platforms/patch/KunGalgameBuDing.ts b/src/platforms/patch/KunGalgameBuDing.ts index 1bd3532..5bd3eb4 100644 --- a/src/platforms/patch/KunGalgameBuDing.ts +++ b/src/platforms/patch/KunGalgameBuDing.ts @@ -15,7 +15,6 @@ interface KunGalgameBuDingResponse { async function searchKunGalgameBuDing(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "鲲Galgame补丁", count: 0, items: [], }; @@ -69,6 +68,7 @@ async function searchKunGalgameBuDing(game: string): Promise<PlatformSearchResul const KunGalgameBuDing: Platform = { name: "鲲Galgame补丁", color: "lime", + tags: ["NoReq", "SuDrive"], magic: false, search: searchKunGalgameBuDing, }; diff --git a/src/platforms/patch/TWOdfan.ts b/src/platforms/patch/TWOdfan.ts index 5fa81c6..d093850 100644 --- a/src/platforms/patch/TWOdfan.ts +++ b/src/platforms/patch/TWOdfan.ts @@ -7,7 +7,6 @@ const REGEX = /<h4 class="media-heading"><a target="_blank" href="(?<URL>.*?)">( async function searchTWOdfan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { - name: "2dfan", count: 0, items: [], }; @@ -52,8 +51,9 @@ async function searchTWOdfan(game: string): Promise<PlatformSearchResult> { const TWOdfan: Platform = { name: "2dfan", - color: "lime", - magic: false, + color: "white", + tags: ["LoginPay", "magic", "MixDrive"], + magic: true, search: searchTWOdfan, }; diff --git a/src/types.ts b/src/types.ts index c5b11e4..4142db3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,13 +2,13 @@ export interface SearchResultItem { name: string; url: string; + tags?: string[]; } // 平台搜索的返回值 export interface PlatformSearchResult { items: SearchResultItem[]; count: number; - name: string; error?: string; } @@ -16,6 +16,7 @@ export interface PlatformSearchResult { export interface Platform { name: string; color: string; + tags: string[]; magic: boolean; search: (game: string, ...args: any[]) => Promise<PlatformSearchResult>; } @@ -24,6 +25,7 @@ export interface Platform { export interface StreamResult { name: string; color: string; + tags: string[]; items: SearchResultItem[]; error?: string; } From 4065a014963d0a24d6c58668c7c61a717f8a04f8 Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Tue, 7 Oct 2025 19:56:30 +0800 Subject: [PATCH 14/22] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=87=8F?= =?UTF-8?q?=E5=AD=90acg=E5=B9=B3=E5=8F=B0=E6=98=BE=E7=A4=BA=E9=A2=9C?= =?UTF-8?q?=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 调整了量子acg平台的显示颜色,从白色改为石灰绿,以改善视觉效果。 --- src/platforms/gal/LiangZiACG.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/gal/LiangZiACG.ts b/src/platforms/gal/LiangZiACG.ts index c12d73d..1e82673 100644 --- a/src/platforms/gal/LiangZiACG.ts +++ b/src/platforms/gal/LiangZiACG.ts @@ -49,7 +49,7 @@ async function searchLiangZiACG(game: string): Promise<PlatformSearchResult> { const LiangZiACG: Platform = { name: "量子acg", - color: "white", + color: "lime", tags: ["NoReq", "SuDrive"], magic: false, search: searchLiangZiACG, From 92327b2354a8d91cc52eae90f855a0836c059671 Mon Sep 17 00:00:00 2001 From: DRG <Noswerksaser@proton.me> Date: Tue, 18 Nov 2025 09:09:39 +0800 Subject: [PATCH 15/22] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E7=A8=BB?= =?UTF-8?q?=E8=8D=B7GAL=E8=AF=B7=E6=B1=82=E6=A0=BC=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 56 ++++++++++++++++++++++++++++++++++- src/platforms/gal/DaoHeGal.ts | 8 ++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index eb070c6..f004dd4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,56 @@ # Wrangler-API -Cloudflare Wrangler 实现的 SearchGal 后端搜索API + +Cloudflare Workers 版 SearchGal 聚合搜索 API。提供 `/gal` 和 `/patch` 两个入口,接收游戏名并以 SSE 流式返回各平台搜索结果。 + +## 准备 +- Node.js 18+,npm +- Cloudflare 账号(发布时需要) + +## 安装 +```bash +npm install +``` + +## 本地开发 +- 纯本地(无 Cloudflare 登录):`npx wrangler dev --local` +- 实时连 Cloudflare:`npx wrangler dev` + +## 发布 +```bash +npx wrangler login # 首次需要 +npx wrangler publish +``` + +## API 使用 +- 路径:`POST /gal` 或 `POST /patch` +- Content-Type:`multipart/form-data` +- 表单字段:`game` (string) +- 响应:`text/event-stream`,每行是一条 JSON,示例: +``` +{"total":33} +{"progress":{"completed":1,"total":33}} +{"progress":{"completed":2,"total":33},"result":{"name":"某平台","color":"lime","tags":["NoReq"],"items":[{"name":"Title","url":"https://..."}]}} +{"done":true} +``` + +## 标签说明(tags) +- `NoReq`:无需登录/回复即可拿到下载信息 +- `Login`:需登录后访问 +- `LoginPay`:需登录且支付积分 +- `LoginRep`:需登录并回复/评论解锁 +- `Rep`:需回复/评论但无需登录 +- `SuDrive`:自建网盘盘源 +- `NoSplDrive`:不限速网盘盘源(如Onedrive/Mega等) +- `SplDrive`:限速网盘盘源(如百度/夸克/天翼等) +- `MixDrive`:不限速与限速网盘盘源混合,可能提供多种下载形式 +- `BTmag`:BT或磁力链接 +- `magic`:站点需要代理访问 + +## 目录速览 +- `src/index.ts` Worker 入口,路由 `/gal`、`/patch` +- `src/core.ts` 处理并行搜索与 SSE 组装 +- `src/platforms/gal` GAL 平台搜集器 +- `src/platforms/patch` 补丁平台搜集器 +- `src/utils/httpClient.ts` 统一请求封装 +- `scripts/generate-indices.js` 可选的索引生成脚本 + diff --git a/src/platforms/gal/DaoHeGal.ts b/src/platforms/gal/DaoHeGal.ts index d138e97..c2713e6 100644 --- a/src/platforms/gal/DaoHeGal.ts +++ b/src/platforms/gal/DaoHeGal.ts @@ -1,7 +1,7 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; -const API_URL = "https://inarigal.com/api/home/list"; +const API_URL = "https://inarigal.com/api/search"; const BASE_URL = "https://inarigal.com/detail/"; interface DaoHeGalItem { @@ -24,9 +24,7 @@ async function searchDaoHeGal(game: string): Promise<PlatformSearchResult> { try { const url = new URL(API_URL); - url.searchParams.set("page", "1"); - url.searchParams.set("pageSize", "18"); // Hardcoded as per original script - url.searchParams.set("search", game); + url.searchParams.set("keywords", game); const response = await fetchClient(url); if (!response.ok) { @@ -67,4 +65,4 @@ const DaoHeGal: Platform = { search: searchDaoHeGal, }; -export default DaoHeGal; \ No newline at end of file +export default DaoHeGal; From 65789b18beac0dcfe35eac8ea4f40c28d0c31b0d Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 00:15:27 +0800 Subject: [PATCH 16/22] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=20Nysoure=20?= =?UTF-8?q?=E5=9F=9F=E5=90=8D=E5=B9=B6=E9=87=8D=E6=9E=84=20VikaACG=20?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 更新 Nysoure 平台的 API 和基础 URL 至新域名 nysoure.com。 * 将 VikaACG 从基于正则匹配 HTML 的搜索改为调用官方 JSON API。 * 为 VikaACG 引入了完整的接口类型定义及更健壮的错误处理。 * 优化了 VikaACG 的搜索请求参数,并支持从 API 获取总结果数。 --- src/platforms/gal/Nysoure.ts | 6 +-- src/platforms/gal/VikaACG.ts | 88 ++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/platforms/gal/Nysoure.ts b/src/platforms/gal/Nysoure.ts index 6335dee..99e2fee 100644 --- a/src/platforms/gal/Nysoure.ts +++ b/src/platforms/gal/Nysoure.ts @@ -1,13 +1,13 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; -const API_URL = "https://res.nyne.dev/api/resource/search"; -const BASE_URL = "https://res.nyne.dev/resources/"; +const API_URL = "https://nysoure.com/api/resource/search"; +const BASE_URL = "https://nysoure.com/resources/"; interface NysoureItem { id: number; title: string; -} +} interface NysoureResponse { success: boolean; diff --git a/src/platforms/gal/VikaACG.ts b/src/platforms/gal/VikaACG.ts index 50489ec..d9caf39 100644 --- a/src/platforms/gal/VikaACG.ts +++ b/src/platforms/gal/VikaACG.ts @@ -1,8 +1,30 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; -const API_URL = "https://www.vikacg.com/wp-json/b2/v1/getPostList"; -const REGEX = /<h2><a target="_blank" href="(?<URL>.*?)">(?<NAME>.*?)<\/a>/gs; +const API_URL = "https://www.vikacg.com/api/vikacg/v1/getPosts"; + +/** + * 响应体最小类型(按你贴的 JSON) + */ +type VikaGetPostsResponse = { + status?: string; + code?: number; + message?: string; + statusMessage?: string; + data?: { + list?: Array<{ + id: number; + title: string; + // 其他字段这里不需要就不展开了 + }>; + count?: number; + paged?: number; + page_count?: number; + pages?: number; + }; +}; + +const POST_URL = (id: number) => `https://www.vikacg.com/p/${id}`; async function searchVikaACG(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { @@ -17,26 +39,17 @@ async function searchVikaACG(game: string): Promise<PlatformSearchResult> { "Content-Type": "application/json", }, body: JSON.stringify({ - paged: 1, - post_paged: 1, - post_count: 1000, // Hardcoded limit, larger values may cause timeouts - post_type: "post-1", - post_cat: [6], - post_order: "modified", - post_meta: [ - "user", - "date", - "des", - "cats", - "like", - "comment", - "views", - "video", - "download", - "hide", - ], - metas: {}, + order: "updated_at", + sort: "desc", + status: null, search: game, + page_count: 50, + paged: 1, + category: null, + tag: null, + rating: null, + is_pinned: false, + user_id: null, }), }); @@ -44,28 +57,25 @@ async function searchVikaACG(game: string): Promise<PlatformSearchResult> { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); } - // The response is a JSON-encoded string containing HTML. - // .json() will parse the JSON and unescape the string content. - const html: string = await response.text(); - - const decodedHtml = html.replaceAll('\\/', '/').replaceAll('\\\\', '\\').replaceAll('\\"', '"').replace(/\\u([\d\w]{4})/gi, (match, grp) => { - return String.fromCharCode(parseInt(grp, 16)); - }); - const matches = decodedHtml.matchAll(REGEX); + const json: VikaGetPostsResponse = await response.json(); - const items: SearchResultItem[] = []; - for (const match of matches) { - if (match.groups?.NAME && match.groups?.URL) { - items.push({ - name: match.groups.NAME.trim(), - url: match.groups.URL, - }); - } + if (json.status !== "success" || !json.data) { + const msg = json.message || json.statusMessage || "接口返回非 success"; + throw new Error(`资源平台 SearchAPI 返回异常:${msg}`); } + const list = json.data.list ?? []; + const items: SearchResultItem[] = list + .filter((p) => typeof p?.id === "number" && typeof p?.title === "string") + .map((p) => ({ + name: p.title.trim(), + url: POST_URL(p.id), + })); + searchResult.items = items; - searchResult.count = items.length; + // ✅ 建议 count 用后端总数(data.count),否则用本页长度兜底 + searchResult.count = typeof json.data.count === "number" ? json.data.count : items.length; } catch (error) { if (error instanceof Error) { searchResult.error = error.message; @@ -86,4 +96,4 @@ const VikaACG: Platform = { search: searchVikaACG, }; -export default VikaACG; \ No newline at end of file +export default VikaACG; From c4839db8c959ae22319f365f21503a44a54a22d9 Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 08:47:04 +0800 Subject: [PATCH 17/22] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20GAL=20?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E5=88=97=E8=A1=A8=E5=8F=8A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 重新对 YingZhiGuang 和 TianYouErCiYuan 平台支持 * 移除已失效或不再维护的 TaoHuaYuan(移除搜索功能) 和 Hikarinagi(网站转型) 平台。 * 修复 YouYuDeloli 平台的搜索请求,补充缺失的 submit 参数。 * 同步更新 src/platforms/gal/index.ts 中的平台导出列表。 --- src/platforms/gal/TaoHuaYuan.ts | 55 ----------------- .../gal/{Hikarinagi.ts => TianYouErCiYuan.ts} | 25 ++++---- src/platforms/gal/YingZhiGuang.ts | 59 +++++++++++++++++++ src/platforms/gal/YouYuDeloli.ts | 1 + src/platforms/gal/index.ts | 8 +-- 5 files changed, 76 insertions(+), 72 deletions(-) delete mode 100644 src/platforms/gal/TaoHuaYuan.ts rename src/platforms/gal/{Hikarinagi.ts => TianYouErCiYuan.ts} (65%) create mode 100644 src/platforms/gal/YingZhiGuang.ts diff --git a/src/platforms/gal/TaoHuaYuan.ts b/src/platforms/gal/TaoHuaYuan.ts deleted file mode 100644 index 4750270..0000000 --- a/src/platforms/gal/TaoHuaYuan.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { fetchClient } from "../../utils/httpClient"; -import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; - -const DATA_URL = "https://peach.sslswwdx.top/page/search/index.json"; - -interface TaoHuaYuanItem { - title: string; - permalink: string; -} - -async function searchTaoHuaYuan(game: string): Promise<PlatformSearchResult> { - const searchResult: PlatformSearchResult = { - count: 0, - items: [], - }; - - try { - const response = await fetchClient(DATA_URL); - if (!response.ok) { - throw new Error(`Failed to fetch data from ${DATA_URL}`); - } - - const data = await response.json() as TaoHuaYuanItem[]; - - const items: SearchResultItem[] = data - .filter(item => item.title.includes(game)) - .map(item => ({ - name: item.title.trim(), - url: item.permalink, - })); - - searchResult.items = items; - searchResult.count = items.length; - - } catch (error) { - if (error instanceof Error) { - searchResult.error = error.message; - } else { - searchResult.error = "An unknown error occurred"; - } - searchResult.count = -1; - } - - return searchResult; -} - -const TaoHuaYuan: Platform = { - name: "桃花源", - color: "lime", - tags: ["NoReq", "SuDrive"], - magic: false, - search: searchTaoHuaYuan, -}; - -export default TaoHuaYuan; \ No newline at end of file diff --git a/src/platforms/gal/Hikarinagi.ts b/src/platforms/gal/TianYouErCiYuan.ts similarity index 65% rename from src/platforms/gal/Hikarinagi.ts rename to src/platforms/gal/TianYouErCiYuan.ts index 5cac60c..40865be 100644 --- a/src/platforms/gal/Hikarinagi.ts +++ b/src/platforms/gal/TianYouErCiYuan.ts @@ -1,19 +1,18 @@ import { fetchClient } from "../../utils/httpClient"; import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; -const API_URL = "https://www.hikarinagi.net/"; -const REGEX = /" class="lazyload fit-cover radius8">.*?<h2 class="item-heading"><a target="_blank" href="(?<URL>.*?)">(?<NAME>.*?)<\/a><\/h2>/gs; +const API_URL = "https://www.tiangal.com/search/"; +const REGEX = /<h2>\s*<a href="(?<URL>[^"]+)" title="(?<NAME>[^"]+)"/gs; -async function searchHikarinagi(game: string): Promise<PlatformSearchResult> { +async function searchTianYouErCiYuan(game: string): Promise<PlatformSearchResult> { const searchResult: PlatformSearchResult = { count: 0, items: [], }; try { - const url = new URL(API_URL); - url.searchParams.set("s", game); - + const url = new URL(API_URL + encodeURIComponent(game)); // URL path parameter + const response = await fetchClient(url); if (!response.ok) { throw new Error(`资源平台 SearchAPI 响应异常状态码 ${response.status}`); @@ -27,7 +26,7 @@ async function searchHikarinagi(game: string): Promise<PlatformSearchResult> { if (match.groups?.NAME && match.groups?.URL) { items.push({ name: match.groups.NAME.trim(), - url: match.groups.URL, + url: new URL(match.groups.URL, API_URL).toString(), }); } } @@ -47,12 +46,12 @@ async function searchHikarinagi(game: string): Promise<PlatformSearchResult> { return searchResult; } -const Hikarinagi: Platform = { - name: "Hikarinagi", +const TianYouErCiYuan: Platform = { + name: "天游二次元", color: "white", - tags: ["LoginPay", "SuDrive"], - magic: false, - search: searchHikarinagi, + tags: ["LoginPay", "MixDrive"], + magic: true, + search: searchTianYouErCiYuan, }; -export default Hikarinagi; \ No newline at end of file +export default TianYouErCiYuan; \ No newline at end of file diff --git a/src/platforms/gal/YingZhiGuang.ts b/src/platforms/gal/YingZhiGuang.ts new file mode 100644 index 0000000..7a44b11 --- /dev/null +++ b/src/platforms/gal/YingZhiGuang.ts @@ -0,0 +1,59 @@ +import { fetchClient } from "../../utils/httpClient"; +import type { Platform, PlatformSearchResult, SearchResultItem } from "../../types"; + +const DATA_URL = "https://yinghu.netlify.app/search.xml"; +const BASE_URL = "https://yinghu.netlify.app"; +const REGEX = /<entry>.*?<title>(.*?)<\/title>.*?<url>(.*?)<\/url>.*?<\/entry>/gs; + +async function searchYingZhiGuang(game: string): Promise<PlatformSearchResult> { + const searchResult: PlatformSearchResult = { + count: 0, + items: [], + }; + + try { + const response = await fetchClient(DATA_URL); + if (!response.ok) { + throw new Error(`Failed to fetch data from ${DATA_URL}`); + } + + const xmlText = await response.text(); + const matches = xmlText.matchAll(REGEX); + + const items: SearchResultItem[] = []; + for (const match of matches) { + const title = match[1]; + const urlPath = match[2]; + + if (title && urlPath && title.includes(game)) { + items.push({ + name: title.trim(), + url: BASE_URL + urlPath, + }); + } + } + + searchResult.items = items; + searchResult.count = items.length; + + } catch (error) { + if (error instanceof Error) { + searchResult.error = error.message; + } else { + searchResult.error = "An unknown error occurred"; + } + searchResult.count = -1; + } + + return searchResult; +} + +const YingZhiGuang: Platform = { + name: "萤ノ光", + color: "lime", + tags: ["NoReq", "SuDrive"], + magic: false, + search: searchYingZhiGuang, +}; + +export default YingZhiGuang; \ No newline at end of file diff --git a/src/platforms/gal/YouYuDeloli.ts b/src/platforms/gal/YouYuDeloli.ts index 9a380a0..44bd581 100644 --- a/src/platforms/gal/YouYuDeloli.ts +++ b/src/platforms/gal/YouYuDeloli.ts @@ -13,6 +13,7 @@ async function searchYouYuDeloli(game: string): Promise<PlatformSearchResult> { try { const url = new URL(API_URL); url.searchParams.set("s", game); + url.searchParams.set("submit", ''); const response = await fetchClient(url); if (!response.ok) { diff --git a/src/platforms/gal/index.ts b/src/platforms/gal/index.ts index 10ce314..936d7ee 100644 --- a/src/platforms/gal/index.ts +++ b/src/platforms/gal/index.ts @@ -7,7 +7,6 @@ import GalgameX from "./GalgameX"; import GalTuShuGuan from "./GalTuShuGuan"; import GGBases from "./GGBases"; import GGS from "./GGS"; -import Hikarinagi from "./Hikarinagi"; import JiMengACG from "./JiMengACG"; import Koyso from "./Koyso"; import KunGalgame from "./KunGalgame"; @@ -18,11 +17,12 @@ import MiaoYuanLingYu from "./MiaoYuanLingYu"; import Nysoure from "./Nysoure"; import QingJiACG from "./QingJiACG"; import ShenShiTianTang from "./ShenShiTianTang"; -import TaoHuaYuan from "./TaoHuaYuan"; +import TianYouErCiYuan from "./TianYouErCiYuan"; import TouchGal from "./TouchGal"; import VikaACG from "./VikaACG"; import WeiZhiYunPan from "./WeiZhiYunPan"; import xxacg from "./xxacg"; +import YingZhiGuang from "./YingZhiGuang"; import YouYuDeloli from "./YouYuDeloli"; import YueYao from "./YueYao"; import ZeroFive from "./ZeroFive"; @@ -39,7 +39,6 @@ const platforms: Platform[] = [ GalTuShuGuan, GGBases, GGS, - Hikarinagi, JiMengACG, Koyso, KunGalgame, @@ -50,11 +49,12 @@ const platforms: Platform[] = [ Nysoure, QingJiACG, ShenShiTianTang, - TaoHuaYuan, + TianYouErCiYuan, TouchGal, VikaACG, WeiZhiYunPan, xxacg, + YingZhiGuang, YouYuDeloli, YueYao, ZeroFive, From 65855b64c1f53a4cce5014dce0adeb965cb46c06 Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 08:54:48 +0800 Subject: [PATCH 18/22] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Docker=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=8A=20GitHub=20Actions=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9E=84=E5=BB=BA=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 新增 Dockerfile,支持在 Node.js 环境下本地运行 API。 * 添加 GitHub Actions 工作流,实现镜像自动构建并推送到 Docker Hub。 * 支持多平台构建(linux/amd64, linux/arm64)。 * 在 README.md 中补充了 Docker 本地运行的操作指南。 --- .github/workflows/docker-publish.yml | 54 ++++++++++++++++++++++++++++ Dockerfile | 12 +++++++ README.md | 7 ++++ 3 files changed, 73 insertions(+) create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..2aeec23 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,54 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + tags: + - "v*" + workflow_dispatch: + +env: + REGISTRY: docker.io + IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/wrangler-api + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7c39d97 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . + +EXPOSE 8787 + +CMD ["npx", "wrangler", "dev", "--local", "--ip", "0.0.0.0", "--port", "8787"] diff --git a/README.md b/README.md index f004dd4..ae951c7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,13 @@ npx wrangler login # 首次需要 npx wrangler publish ``` +## Docker +### 本地运行 +```bash +docker build -t wrangler-api:local . +docker run --rm -p 8787:8787 wrangler-api:local +``` + ## API 使用 - 路径:`POST /gal` 或 `POST /patch` - Content-Type:`multipart/form-data` From 185a64af3dfb58040a7c62fb146ba81a839cdc51 Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 09:08:52 +0800 Subject: [PATCH 19/22] =?UTF-8?q?ci:=20=E6=9B=B4=E6=96=B0=20Docker=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F=E5=90=8D=E7=A7=B0=20(Update=20Docker=20image?= =?UTF-8?q?=20name)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 在 `docker-publish.yml` 工作流中更新了 `IMAGE_NAME` 环境变量。 * 镜像名称从 `wrangler-api` 修改为 `SearchGal-Api`。 * 此更改确保 Docker 镜像推送到正确的 Docker Hub 仓库。 --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 2aeec23..d774e46 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -10,7 +10,7 @@ on: env: REGISTRY: docker.io - IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/wrangler-api + IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/SearchGal-Api jobs: build-and-push: From 73ad909a2f612978cf85d33cb5c9ff0406df94be Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 09:40:42 +0800 Subject: [PATCH 20/22] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20Docker=20?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E9=95=9C=E5=83=8F=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将基础镜像从 node:20-alpine 切换为 bookworm-slim。 - 在 Dockerfile 中配置阿里云 APT 镜像源并安装证书。 - 修改 npm ci 命令以包含可选依赖项。 - 同步更新 README.md 中的 Docker 镜像标签名称。 --- Dockerfile | 15 +++++++++++++-- README.md | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7c39d97..9935a1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,20 @@ -FROM node:20-alpine +FROM node:20-bookworm-slim WORKDIR /app +RUN if [ -f /etc/apt/sources.list ]; then \ + sed -i 's|http://deb.debian.org/debian|http://mirrors.aliyun.com/debian|g' /etc/apt/sources.list; \ + sed -i 's|http://security.debian.org/debian-security|http://mirrors.aliyun.com/debian-security|g' /etc/apt/sources.list || true; \ + elif [ -f /etc/apt/sources.list.d/debian.sources ]; then \ + sed -i 's|http://deb.debian.org/debian|http://mirrors.aliyun.com/debian|g' /etc/apt/sources.list.d/debian.sources; \ + sed -i 's|http://security.debian.org/debian-security|http://mirrors.aliyun.com/debian-security|g' /etc/apt/sources.list.d/debian.sources || true; \ + fi \ + && apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + COPY package*.json ./ -RUN npm ci +RUN npm ci --include=optional COPY . . diff --git a/README.md b/README.md index ae951c7..14927d3 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ npx wrangler publish ## Docker ### 本地运行 ```bash -docker build -t wrangler-api:local . -docker run --rm -p 8787:8787 wrangler-api:local +docker build -t searchgal-api:main . +docker run --rm -p 8787:8787 searchgal-api:main ``` ## API 使用 From 364313941d76e79f65be7966d6388e448d4d9617 Mon Sep 17 00:00:00 2001 From: Jurangren <Noswerksaser@proton.me> Date: Sat, 20 Dec 2025 09:49:28 +0800 Subject: [PATCH 21/22] =?UTF-8?q?ci:=20=E4=BB=8E=20Docker=20=E5=85=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=AD=E7=A7=BB=E9=99=A4=20SHA=20=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 Docker 元数据配置中删除了 `type=sha` 标签。 - 优化镜像标签生成逻辑,仅保留基于分支和 Git 标签的标识。 --- .github/workflows/docker-publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d774e46..ea33a4f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -42,7 +42,6 @@ jobs: tags: | type=ref,event=branch type=ref,event=tag - type=sha - name: Build and push uses: docker/build-push-action@v6 From e3b3fd40f8f1321aa191c4c98f3a0ae6bce23c7f Mon Sep 17 00:00:00 2001 From: AdingApkgg <AdingApkgg@outlook.com> Date: Sun, 21 Dec 2025 00:20:03 +0800 Subject: [PATCH 22/22] 251221 --- README.md | 62 ++- pnpm-lock.yaml | 896 ++++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 4 + 3 files changed, 946 insertions(+), 16 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/README.md b/README.md index 14927d3..2fa9b75 100644 --- a/README.md +++ b/README.md @@ -3,37 +3,66 @@ Cloudflare Workers 版 SearchGal 聚合搜索 API。提供 `/gal` 和 `/patch` 两个入口,接收游戏名并以 SSE 流式返回各平台搜索结果。 ## 准备 -- Node.js 18+,npm + +- Node.js(Win 需要) 与 pnpm - Cloudflare 账号(发布时需要) ## 安装 -```bash -npm install + +### POSIX + +```sh +## 安装 pnpm +curl -fsSL https://get.pnpm.io/install.sh | sh - + +## 安装依赖 +pnpm install +``` + +### Windows + +```sh +## 安装 Node.js +winget install -e --id OpenJS.NodeJS + +## 安装 pnpm +npx pnpm@latest-10 dlx @pnpm/exe@latest-10 setup + +## 安装依赖 +pnpm install +``` + +## 开发 + +```sh +# 纯本地(无 Cloudflare 登录) +npx wrangler dev --local + +# 实时连 Cloudflare +npx wrangler dev ``` -## 本地开发 -- 纯本地(无 Cloudflare 登录):`npx wrangler dev --local` -- 实时连 Cloudflare:`npx wrangler dev` +## 运行 + +```sh +npx wrangler dev --ip 0.0.0.0 +``` ## 发布 -```bash + +```sh npx wrangler login # 首次需要 npx wrangler publish ``` -## Docker -### 本地运行 -```bash -docker build -t searchgal-api:main . -docker run --rm -p 8787:8787 searchgal-api:main -``` - ## API 使用 + - 路径:`POST /gal` 或 `POST /patch` - Content-Type:`multipart/form-data` - 表单字段:`game` (string) - 响应:`text/event-stream`,每行是一条 JSON,示例: -``` + +```json {"total":33} {"progress":{"completed":1,"total":33}} {"progress":{"completed":2,"total":33},"result":{"name":"某平台","color":"lime","tags":["NoReq"],"items":[{"name":"Title","url":"https://..."}]}} @@ -41,6 +70,7 @@ docker run --rm -p 8787:8787 searchgal-api:main ``` ## 标签说明(tags) + - `NoReq`:无需登录/回复即可拿到下载信息 - `Login`:需登录后访问 - `LoginPay`:需登录且支付积分 @@ -54,10 +84,10 @@ docker run --rm -p 8787:8787 searchgal-api:main - `magic`:站点需要代理访问 ## 目录速览 + - `src/index.ts` Worker 入口,路由 `/gal`、`/patch` - `src/core.ts` 处理并行搜索与 SSE 组装 - `src/platforms/gal` GAL 平台搜集器 - `src/platforms/patch` 补丁平台搜集器 - `src/utils/httpClient.ts` 统一请求封装 - `scripts/generate-indices.js` 可选的索引生成脚本 - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..b501112 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,896 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20250821.0 + version: 4.20251125.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + wrangler: + specifier: ^4.31.0 + version: 4.50.0(@cloudflare/workers-types@4.20251125.0) + +packages: + + '@cloudflare/kv-asset-handler@0.4.0': + resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.7.11': + resolution: {integrity: sha512-se23f1D4PxKrMKOq+Stz+Yn7AJ9ITHcEecXo2Yjb+UgbUDCEBch1FXQC6hx6uT5fNA3kmX3mfzeZiUmpK1W9IQ==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: ^1.20251106.1 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20251118.0': + resolution: {integrity: sha512-UmWmYEYS/LkK/4HFKN6xf3Hk8cw70PviR+ftr3hUvs9HYZS92IseZEp16pkL6ZBETrPRpZC7OrzoYF7ky6kHsg==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20251118.0': + resolution: {integrity: sha512-RockU7Qzf4rxNfY1lx3j4rvwutNLjTIX7rr2hogbQ4mzLo8Ea40/oZTzXVxl+on75joLBrt0YpenGW8o/r44QA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20251118.0': + resolution: {integrity: sha512-aT97GnOAbJDuuOG0zPVhgRk0xFtB1dzBMrxMZ09eubDLoU4djH4BuORaqvxNRMmHgKfa4T6drthckT0NjUvBdw==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20251118.0': + resolution: {integrity: sha512-bXZPJcwlq00MPOXqP7DMWjr+goYj0+Fqyw6zgEC2M3FR1+SWla4yjghnZ4IdpN+H1t7VbUrsi5np2LzMUFs0NA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20251118.0': + resolution: {integrity: sha512-2LV99AHSlpr8WcCb/BYbU2QsYkXLUL1izN6YKWkN9Eibv80JKX0RtgmD3dfmajE5sNvClavxZejgzVvHD9N9Ag==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@4.20251125.0': + resolution: {integrity: sha512-YZdO/IX10DiHb2v+7H2CL5SKAbxJQIG22jNefgtW86YMf5LvQk6f75v5T/j1ju56MBwm6RzcqvsECF+cXs2h3g==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@poppinss/colors@4.1.5': + resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.2': + resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==} + + '@sindresorhus/is@7.1.1': + resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.12': + resolution: {integrity: sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==} + + acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + engines: {node: '>=18'} + hasBin: true + + exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + + miniflare@4.20251118.1: + resolution: {integrity: sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici@7.14.0: + resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + workerd@1.20251118.0: + resolution: {integrity: sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.50.0: + resolution: {integrity: sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20251118.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod@3.22.3: + resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + +snapshots: + + '@cloudflare/kv-asset-handler@0.4.0': + dependencies: + mime: 3.0.0 + + '@cloudflare/unenv-preset@2.7.11(unenv@2.0.0-rc.24)(workerd@1.20251118.0)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20251118.0 + + '@cloudflare/workerd-darwin-64@1.20251118.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20251118.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20251118.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20251118.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20251118.0': + optional: true + + '@cloudflare/workers-types@4.20251125.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.4': + optional: true + + '@esbuild/android-arm64@0.25.4': + optional: true + + '@esbuild/android-arm@0.25.4': + optional: true + + '@esbuild/android-x64@0.25.4': + optional: true + + '@esbuild/darwin-arm64@0.25.4': + optional: true + + '@esbuild/darwin-x64@0.25.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.4': + optional: true + + '@esbuild/freebsd-x64@0.25.4': + optional: true + + '@esbuild/linux-arm64@0.25.4': + optional: true + + '@esbuild/linux-arm@0.25.4': + optional: true + + '@esbuild/linux-ia32@0.25.4': + optional: true + + '@esbuild/linux-loong64@0.25.4': + optional: true + + '@esbuild/linux-mips64el@0.25.4': + optional: true + + '@esbuild/linux-ppc64@0.25.4': + optional: true + + '@esbuild/linux-riscv64@0.25.4': + optional: true + + '@esbuild/linux-s390x@0.25.4': + optional: true + + '@esbuild/linux-x64@0.25.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.4': + optional: true + + '@esbuild/netbsd-x64@0.25.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.4': + optional: true + + '@esbuild/openbsd-x64@0.25.4': + optional: true + + '@esbuild/sunos-x64@0.25.4': + optional: true + + '@esbuild/win32-arm64@0.25.4': + optional: true + + '@esbuild/win32-ia32@0.25.4': + optional: true + + '@esbuild/win32-x64@0.25.4': + optional: true + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@poppinss/colors@4.1.5': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.5 + '@sindresorhus/is': 7.1.1 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.2': {} + + '@sindresorhus/is@7.1.1': {} + + '@speed-highlight/core@1.2.12': {} + + acorn-walk@8.3.2: {} + + acorn@8.14.0: {} + + blake3-wasm@2.1.5: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + cookie@1.0.2: {} + + detect-libc@2.1.2: {} + + error-stack-parser-es@1.0.5: {} + + esbuild@0.25.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 + + exit-hook@2.2.1: {} + + fsevents@2.3.3: + optional: true + + glob-to-regexp@0.4.1: {} + + is-arrayish@0.3.4: {} + + kleur@4.1.5: {} + + mime@3.0.0: {} + + miniflare@4.20251118.1: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.14.0 + acorn-walk: 8.3.2 + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + sharp: 0.33.5 + stoppable: 1.1.0 + undici: 7.14.0 + workerd: 1.20251118.0 + ws: 8.18.0 + youch: 4.1.0-beta.10 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + semver@7.7.3: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + stoppable@1.1.0: {} + + supports-color@10.2.2: {} + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + undici@7.14.0: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + workerd@1.20251118.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20251118.0 + '@cloudflare/workerd-darwin-arm64': 1.20251118.0 + '@cloudflare/workerd-linux-64': 1.20251118.0 + '@cloudflare/workerd-linux-arm64': 1.20251118.0 + '@cloudflare/workerd-windows-64': 1.20251118.0 + + wrangler@4.50.0(@cloudflare/workers-types@4.20251125.0): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.0 + '@cloudflare/unenv-preset': 2.7.11(unenv@2.0.0-rc.24)(workerd@1.20251118.0) + blake3-wasm: 2.1.5 + esbuild: 0.25.4 + miniflare: 4.20251118.1 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20251118.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20251125.0 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ws@8.18.0: {} + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.2 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.5 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.12 + cookie: 1.0.2 + youch-core: 0.3.3 + + zod@3.22.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..5ba62b2 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - esbuild + - sharp + - workerd