From 673d3067ab0a033db4c5f9df248075ab5332ca9e Mon Sep 17 00:00:00 2001 From: nyne Date: Wed, 16 Jul 2025 15:30:44 +0800 Subject: [PATCH] Add kun patch. --- frontend/public/kun.webp | Bin 0 -> 2028 bytes frontend/src/app.ts | 2 +- frontend/src/components/navigator.tsx | 4 +- frontend/src/components/resource_card.tsx | 6 +- frontend/src/i18n.ts | 5 + frontend/src/index.css | 2 +- frontend/src/network/kun.ts | 170 +++++++++++++ frontend/src/pages/resource_details_page.tsx | 244 ++++++++++++++++--- frontend/src/pages/tags_page.tsx | 4 +- frontend/vite.config.ts | 4 + 10 files changed, 396 insertions(+), 45 deletions(-) create mode 100644 frontend/public/kun.webp create mode 100644 frontend/src/network/kun.ts diff --git a/frontend/public/kun.webp b/frontend/public/kun.webp new file mode 100644 index 0000000000000000000000000000000000000000..f4b5f81ccf69b69db88ca590e5ac007749b5d57f GIT binary patch literal 2028 zcmVGNk&HG2LJ$9MM6+kP&il$0000G0000#002J#06|PpNZ|tj00EG;ZQI$% zBf68cQ;coTbY^Yawr$(CZQHhOd$hK_JFJrDLz#CXVgewmA|$4ILg@jsyB6&@piTf- z=QOcpDkUcPZ~!X45x?yYW=n9*TP;Efn;j)0Q#qCtz9ma|@vyVvYoc2*47pd(dGoMa zS^LnAJYZIpSlpL`qg;c6VAiu}3=aUR27?C=?i6S(sFi{hWn4)CfI7ZShQVw$n=(I7 z(W#*ArUj##BKbIg)6INhBAeplu`wkoag4-Nb_bwn`jqS!zi#jXo8e8}04Q60BZq&? z@MJ>Q#0P-T=?6Jc+7QSit^r83jQPKXAe-w;nRY%5PFlGABtEL)Pgn6p5P9!^f)04oecpX z6zsRH6ae2UjoRw$@N3Uol=>l0*1VXg0`p)0I~T4=TzEVAY?n|wgi^&-0Q!^M?>|dK zXW~jP`u%i4WnGDKN?TAB>QcW<`83%qQ(Db31^PC()T@> z_S5+P7vS~zOQ6A5BFY}xjo5rt79u5if&BX5KnVr4hc+9pOc5Vm4sUa)$ ztYlABI|ux**AffT71+=spuVag?kKJ`%1Jhr!E=NDPI^5Cj$Uh zP&gpm0{{SU5&)e6DnI~006u{$xKT;NFe7hi>Hi=2^~{p~zSz5OYxE)<mABF z+U8emFI8s1KLQ!sCOLZX9}oY(rV4S@|M0e-w9$lGafyAjcNau zJ=8rkv_J`K5(~vn4~k(?!~c1*#hSc4Km;ml|CHBt4AgqN7EB!8e-7N4Q&Gi%#ZD+C zb-Afbscx5twgXnv`PKQMe%=FwM`;A_U@@s|-~t7=Dwnoi<-Tl=VZ5?x&2~YmP8a|( zy{b;aN}01!kp77f3`rK0jW4l#%toDad4BE{OpQK+nMw#PB?Z%a@gzyP_dC_sOl${C zp(mAqujWR7s0s-zx^bT+$dbUZA0+40bF>v3oAbl`nFE3L`&WkLx}8nTaX~k9+k;o^ z#*r|H+#)e*Dje^9)yaeOY+Z=D_rE+&mct&Wq%ylM2+{&m*yKr8y&n|BdYrlqteuz z)bTBDF@#IP#Ktx*&ou57on^Pu`i6Y~YqT12fnnxFye>ulTu+)&lwip(RtY1tVIwdV z1ZQpl$g~uZ&5a+nxTlvES6hu@p^cP*2d^Uds5{A+*QXe`A&E)lY6CA*j{e3A`Mu5HGln}^f$Z0FX$eodoDgHs*&S& zL4(PmbF^c<0VBDy5{%xkKF4v|fmdmHmj;1(Qm+kgm~<0KPlIBDCSzP6RzM~zS)zw# zlFUNknG0Lh9`#=$T4z7OVdZ*=NNtQj8o($Y&zV>T7b-kDFD4M-3?pE3VkMuwJjy`` zXv9YgM8CwdT523SY*vAfl1 -
-
+
+
-
+
{resource.image != null && (
> { + try { + const client = axios.create({ + validateStatus(status) { + return status === 200 || status === 404; // Accept only 200 and 404 responses + }, + }); + const res = await client.get( + `https://www.moyu.moe/api/hikari?vndb_id=${id}`, + ); + if (res.status === 404) { + return { + success: false, + message: "404", + }; + } + if (res.status !== 200) { + throw new Error(`HTTP error! status: ${res.status}`); + } + return { + success: true, + message: "ok", + data: res.data.data, + }; + } catch (error) { + console.error("Error fetching files:", error); + return { success: false, message: "Failed to fetch files" }; + } + }, +}; + +export default KunApi; + +export interface KunUser { + id: number; + name: string; + avatar: string; +} + +export interface KunPatchResponse { + id: number; + name: string; + // e.g. "vndb_id": "v19658", + vndb_id: string; + banner: string; + introduction: string; + // e.g. "released": "2016-11-25", + released: string; + status: number; + download: number; + view: number; + resource_update_time: Date; + type: string[]; + language: string[]; + engine: string[]; + platform: string[]; + user_id: number; + user: KunUser; + created: Date; + updated: Date; + resource: KunPatchResourceResponse[]; +} + +export interface KunPatchResourceResponse { + id: number; + storage: "s3" | "user"; + name: string; + model_name: string; + size: string; + code: string; + password: string; + note: string; + hash: string; + type: string[]; + language: string[]; + platform: string[]; + download: number; + status: number; + update_time: Date; + user_id: number; + patch_id: number; + created: Date; + user: KunUser; +} + +export interface HikariResponse { + success: boolean; + message: string; + data: KunPatchResponse | null; +} + +const SUPPORTED_LANGUAGE_MAP: Record = { + "zh-Hans": "简体中文", + "zh-Hant": "繁體中文", + "ja": "日本語", + "en": "English", + "other": "其它", +}; + +export function kunLanguageToString(language: string): string { + return SUPPORTED_LANGUAGE_MAP[language] || language; +} + +const SUPPORTED_PLATFORM_MAP: Record = { + windows: "Windows", + android: "Android", + macos: "MacOS", + ios: "iOS", + linux: "Linux", + other: "其它", +}; + +export function kunPlatformToString(platform: string): string { + return SUPPORTED_PLATFORM_MAP[platform] || platform; +} + +const resourceTypes = [ + { + value: "manual", + label: "人工翻译补丁", + }, + { + value: "ai", + label: "AI 翻译补丁", + }, + { + value: "machine_polishing", + label: "机翻润色", + }, + { + value: "machine", + label: "机翻补丁", + }, + { + value: "save", + label: "全 CG 存档", + }, + { + value: "crack", + label: "破解补丁", + }, + { + value: "fix", + label: "修正补丁", + }, + { + value: "mod", + label: "魔改补丁", + }, + { + value: "other", + label: "其它", + }, +]; + +export function kunResourceTypeToString(type: string): string { + const resourceType = resourceTypes.find((t) => t.value === type); + return resourceType ? resourceType.label : type; +} diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index 89fe26a..555ce6d 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -50,6 +50,13 @@ import { BiLogoSteam } from "react-icons/bi"; import { CommentTile } from "../components/comment_tile.tsx"; import { CommentInput } from "../components/comment_input.tsx"; import { useNavigator } from "../components/navigator.tsx"; +import KunApi, { + kunLanguageToString, + KunPatchResourceResponse, + KunPatchResponse, + kunPlatformToString, + kunResourceTypeToString, +} from "../network/kun.ts"; export default function ResourcePage() { const params = useParams(); @@ -63,6 +70,8 @@ export default function ResourcePage() { const [page, setPage] = useState(0); + const [visitedTabs, setVisitedTabs] = useState>(new Set([])); + const location = useLocation(); const navigator = useNavigator(); @@ -113,10 +122,12 @@ export default function ResourcePage() { if (resource) { document.title = resource.title; if (resource.images.length > 0) { - navigator.setBackground(network.getResampledImageUrl(resource.images[0].id)); + navigator.setBackground( + network.getResampledImageUrl(resource.images[0].id), + ); } } - }, [resource]) + }, [resource]); const navigate = useNavigate(); @@ -136,6 +147,7 @@ export default function ResourcePage() { // 初始状态读取hash useEffect(() => { setPage(getPageFromHash(window.location.hash)); + setVisitedTabs(new Set([getPageFromHash(window.location.hash)])); // 监听hash变化 const onHashChange = () => { setPage(getPageFromHash(window.location.hash)); @@ -149,6 +161,8 @@ export default function ResourcePage() { const handleTabChange = (idx: number) => { setPage(idx); setHashByPage(idx); + // Mark tab as visited when switched to + setVisitedTabs((prev) => new Set(prev).add(idx)); }; if (isNaN(id)) { @@ -222,9 +236,12 @@ export default function ResourcePage() {

)} -
+
-
+ {visitedTabs.has(0) &&
}
- + {visitedTabs.has(1) && ( + + )}
- + {visitedTabs.has(2) && }
@@ -315,11 +334,15 @@ function Tags({ tags }: { tags: Tag[] }) { <> {Array.from(tagsMap.entries()).map(([type, tags]) => (

- {type == "" ? t("Other") : type} + + {type == "" ? t("Other") : type} + {tags.map((tag) => ( { navigate(`/tag/${tag.name}`); }} @@ -603,7 +626,7 @@ function RelatedResourceCard({ height: imgHeight, objectFit: "cover", }} - className={"h-full min-h-0 object-cover min-w-0"} + className={"h-full object-cover min-w-0"} alt={"cover"} src={network.getImageUrl(r.image?.id)} /> @@ -702,32 +725,33 @@ function FileTile({ file }: { file: RFile }) {

- { - file.size > 10 * 1024 * 1024 ? ( - - ) : ( - - - - ) - } + {file.size > 10 * 1024 * 1024 ? ( + + ) : ( + + + + )}
@@ -777,7 +801,13 @@ function CloudflarePopup({ file }: { file: RFile }) { ); } -function Files({ files, resourceID }: { files: RFile[]; resourceID: number }) { +function Files({ + files, + resource, +}: { + files: RFile[]; + resource: ResourceDetails; +}) { return (
{files.map((file) => { @@ -786,9 +816,10 @@ function Files({ files, resourceID }: { files: RFile[]; resourceID: number }) {
{(app.canUpload() || app.allowNormalUserUpload) && (
- +
)} +
); } @@ -1412,3 +1443,138 @@ function DeleteFileDialog({ ); } + +function KunFiles({ resource }: { resource: ResourceDetails }) { + let vnid = ""; + for (const link of resource.links) { + if (link.label.toLowerCase() === "vndb") { + vnid = link.url.split("/").pop() || ""; + break; + } + } + + const [data, setData] = useState(null); + + const [isLoading, setLoading] = useState(true); + + const [error, setError] = useState(null); + + const { t } = useTranslation(); + + useEffect(() => { + if (!vnid || !KunApi.isAvailable()) { + return; + } + KunApi.getPatch(vnid).then((res) => { + if (res.success) { + setData(res.data!); + } else if (res.message === "404") { + // ignore + } else { + setError(res.message); + } + setLoading(false); + }); + }, [vnid]); + + if (error) { + return ; + } + + if (isLoading) { + return ; + } + + if (!vnid || !KunApi.isAvailable() || data === null) { + return <>; + } + + return ( + <> + + {data && ( +
+ {data.resource.map((file) => { + return ; + })} + {data.resource.length === 0 && ( +

+ {t("No patches found for this VN.")} +

+ )} +
+ )} + + ); +} + +function KunFile({ + file, + patchID, +}: { + file: KunPatchResourceResponse; + patchID: number; +}) { + const tags: string[] = []; + if (file.model_name) { + tags.push(file.model_name); + } + tags.push(...file.platform.map((p) => kunPlatformToString(p))); + tags.push(...file.language.map((l) => kunLanguageToString(l))); + tags.push(...file.type.map((t) => kunResourceTypeToString(t))); + + return ( +
+
+
+

{file.name}

+

{file.note}

+

+ + + {"avatar"} + {file.user.name} + + + + + {file.size} + + {tags.map((p) => ( + + {p} + + ))} +

+
+
+ + + +
+
+
+ ); +} diff --git a/frontend/src/pages/tags_page.tsx b/frontend/src/pages/tags_page.tsx index f8043c0..1b2ac0e 100644 --- a/frontend/src/pages/tags_page.tsx +++ b/frontend/src/pages/tags_page.tsx @@ -65,7 +65,9 @@ export default function TagsPage() { navigate(`/tag/${tag.name}`); }} key={tag.name} - className={"m-1 cursor-pointer badge-soft badge-primary shadow-xs"} + className={ + "m-1 cursor-pointer badge-soft badge-primary shadow-xs" + } > {tag.name + (tag.resources_count > 0 ? ` (${tag.resources_count})` : "")} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7986745..023c6b9 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -12,6 +12,10 @@ export default defineConfig({ // target: "https://res.nyne.dev", changeOrigin: true, }, + "https://www.moyu.moe": { + target: "https://www.moyu.moe", + changeOrigin: true, + }, }, }, });