diff --git a/scripts/fetch-seafile.ts b/scripts/fetch-seafile.ts index d48ae4d..d1c5d07 100644 --- a/scripts/fetch-seafile.ts +++ b/scripts/fetch-seafile.ts @@ -201,10 +201,11 @@ async function resolveResource( syncedAt: string; }, ): Promise { + const resolvedResourceUrl = resolveResourceUrl(resource.url, input.config.baseUrl); const fallback: ProjectDownload = { name: resource.name, description: resource.description, - url: resource.url, + url: resolvedResourceUrl, type: resource.type, platform: resource.platform, size: resource.size, @@ -230,7 +231,7 @@ async function resolveResource( return { name: resource.name || detail.name || path.basename(resource.path), description: resource.description, - url: downloadUrl || fallback.url, + url: resolveResourceUrl(downloadUrl, input.config.baseUrl) || fallback.url, type: resource.type, platform: resource.platform, size: detail.size ?? resource.size, @@ -355,6 +356,48 @@ function canUseSeafileApi(config: SeafileSyncConfig) { return Boolean(config.baseUrl && config.token); } +function resolveResourceUrl(value: string | undefined, baseUrl: string) { + if (!value) { + return value; + } + + const normalizedPath = normalizeSeafilePath(value); + if (!normalizedPath) { + return ''; + } + + if (isAbsoluteUrl(normalizedPath)) { + return normalizedPath; + } + + if (!baseUrl) { + return normalizedPath; + } + + return new URL(normalizedPath, `${trimTrailingSlash(baseUrl)}/`).toString(); +} + +function isAbsoluteUrl(value: string) { + return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value); +} + +function normalizeSeafilePath(value: string) { + const trimmed = value.trim(); + if (!trimmed) { + return ''; + } + + if (isAbsoluteUrl(trimmed) || trimmed.startsWith('/')) { + return trimmed; + } + + if (/^[A-Za-z0-9_-]+$/.test(trimmed)) { + return `/f/${trimmed}/?dl=1`; + } + + return trimmed; +} + function trimTrailingSlash(value: string) { return value.replace(/\/+$/, ''); } diff --git a/scripts/sync-static-assets.ts b/scripts/sync-static-assets.ts index ce88f5d..5e0332f 100644 --- a/scripts/sync-static-assets.ts +++ b/scripts/sync-static-assets.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import process from 'node:process'; type StaticAssetManifestItem = { - url: string; + source: string; target: string; }; @@ -18,6 +18,7 @@ type StaticAssetManifestGroup = { type StaticAssetConfig = { rootDir: string; manifestPath: string; + seafileBaseUrl: string; strict: boolean; requestTimeoutMs: number; }; @@ -63,6 +64,7 @@ function loadConfig(rootDir: string): StaticAssetConfig { rootDir, process.env.STATIC_ASSET_MANIFEST ?? 'src/content/static-assets/index.json', ), + seafileBaseUrl: process.env.SEAFILE_BASE_URL?.trim() ?? '', strict: getBooleanEnv('STATIC_ASSET_SYNC_STRICT', false), requestTimeoutMs: getNumberEnv('STATIC_ASSET_REQUEST_TIMEOUT_MS', 15000), }; @@ -107,10 +109,10 @@ function normalizeLegacyManifestItem( record: Record, index: number, ): StaticAssetManifestItem { - const url = typeof record.url === 'string' ? record.url.trim() : ''; + const source = typeof record.url === 'string' ? record.url.trim() : ''; const target = typeof record.target === 'string' ? record.target.trim() : ''; - if (!url) { + if (!source) { throw new Error(`manifest item #${index + 1} is missing url`); } @@ -118,7 +120,7 @@ function normalizeLegacyManifestItem( throw new Error(`manifest item #${index + 1} is missing target`); } - return { url, target }; + return { source, target }; } function normalizeManifestGroup( @@ -141,10 +143,10 @@ function normalizeManifestGroup( } const record = item as Record; - const url = typeof record.url === 'string' ? record.url.trim() : ''; + const source = typeof record.url === 'string' ? record.url.trim() : ''; const filename = typeof record.filename === 'string' ? record.filename.trim() : ''; - if (!url) { + if (!source) { throw new Error(`manifest group #${index + 1} file #${fileIndex + 1} is missing url`); } @@ -155,7 +157,7 @@ function normalizeManifestGroup( } return { - url, + source, target: path.posix.join(targetDir.replace(/\\/g, '/'), filename), }; }); @@ -166,7 +168,8 @@ async function syncAsset(item: StaticAssetManifestItem, config: StaticAssetConfi await mkdir(path.dirname(targetPath), { recursive: true }); const tempPath = `${targetPath}.tmp`; - const response = await fetchWithTimeout(item.url, config.requestTimeoutMs); + const resolvedUrl = resolveManifestUrl(item.source, config.seafileBaseUrl); + const response = await fetchWithTimeout(resolvedUrl, config.requestTimeoutMs); try { const arrayBuffer = await response.arrayBuffer(); @@ -176,7 +179,7 @@ async function syncAsset(item: StaticAssetManifestItem, config: StaticAssetConfi await rename(tempPath, targetPath); console.log( - `[sync-static-assets] wrote ${path.relative(config.rootDir, targetPath)} <- ${item.url}`, + `[sync-static-assets] wrote ${path.relative(config.rootDir, targetPath)} <- ${resolvedUrl}`, ); } catch (error) { await safeRemove(tempPath); @@ -236,10 +239,54 @@ function getNumberEnv(name: string, fallback: number) { return Number.isFinite(parsed) ? parsed : fallback; } +function resolveManifestUrl(value: string, baseUrl: string) { + const normalizedPath = normalizeSeafilePath(value); + if (!normalizedPath) { + throw new Error('manifest item url is empty'); + } + + if (isAbsoluteUrl(normalizedPath)) { + return normalizedPath; + } + + if (!baseUrl) { + throw new Error( + `relative asset url "${normalizedPath}" requires SEAFILE_BASE_URL in .env`, + ); + } + + return new URL(normalizedPath, `${trimTrailingSlash(baseUrl)}/`).toString(); +} + +function isAbsoluteUrl(value: string) { + return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value); +} + +function normalizeSeafilePath(value: string) { + const trimmed = value.trim(); + if (!trimmed) { + return ''; + } + + if (isAbsoluteUrl(trimmed) || trimmed.startsWith('/')) { + return trimmed; + } + + if (/^[A-Za-z0-9_-]+$/.test(trimmed)) { + return `/f/${trimmed}/?dl=1`; + } + + return trimmed; +} + function resolveFromRoot(rootDir: string, targetPath: string) { return path.isAbsolute(targetPath) ? targetPath : path.join(rootDir, targetPath); } +function trimTrailingSlash(value: string) { + return value.replace(/\/+$/, ''); +} + function isMissingFileError(error: unknown) { return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT'; } diff --git a/src/content/seafile/index.json b/src/content/seafile/index.json index af57909..2e46ba9 100644 --- a/src/content/seafile/index.json +++ b/src/content/seafile/index.json @@ -1,12 +1,40 @@ { "projects": [ + { + "project_repo": "basil/vampire-like", + "downloads": [ + { + "name": "Windows 构建包", + "description": "项目 3D 类吸血鬼幸存者 Win64 打包文件", + "url": "e78c21a182eb431bb169", + "repo_id": "", + "path": "", + "type": "build", + "platform": "Win64" + } + ] + }, + { + "project_repo": "basil/vampire-like", + "downloads": [ + { + "name": "Android 构建包", + "description": "项目 3D 累吸血鬼幸存者 Android 打包文件", + "url": "b30d0af94fc64fa887bd", + "repo_id": "", + "path": "", + "type": "build", + "platform": "Android" + } + ] + }, { "project_repo": "basil/biography-of-lijie", "downloads": [ { "name": "Windows 构建包", "description": "项目 李诫传 打包文件", - "url": "http://106.12.111.150:8000/f/c6e397439b174fe39106/?dl=1", + "url": "c6e397439b174fe39106", "repo_id": "", "path": "", "type": "build", @@ -19,7 +47,7 @@ { "name": "Unity 游戏客户端开发简历 PDF", "description": "求职 Unity 游戏客户端开发岗位的简历", - "url": "http://106.12.111.150:8000/f/937ec9f34ec94d528a52/?dl=1", + "url": "937ec9f34ec94d528a52", "repo_id": "", "path": "", "type": "document" @@ -27,7 +55,7 @@ { "name": "游戏构建开发简历 PDF", "description": "求职游戏构建开发岗位的简历", - "url": "http://106.12.111.150:8000/f/04deda4586a742a6b5a7/?dl=1", + "url": "04deda4586a742a6b5a7", "repo_id": "", "path": "", "type": "document" diff --git a/src/content/static-assets/index.json b/src/content/static-assets/index.json index 5f2e502..3b97613 100644 --- a/src/content/static-assets/index.json +++ b/src/content/static-assets/index.json @@ -3,19 +3,19 @@ "target_dir": "public/images/projects", "files": [ { - "url": "http://106.12.111.150:8000/f/cfb44c4a96234a939ed2/?dl=1", + "url": "cfb44c4a96234a939ed2", "filename": "vampire-like.png" }, { - "url": "http://106.12.111.150:8000/f/85e956b2a4f14ff9bca8/?dl=1", + "url": "85e956b2a4f14ff9bca8", "filename": "geometry-tower-defense.png" }, { - "url": "http://106.12.111.150:8000/f/7f6ac5bd1b094db5ae95/?dl=1", + "url": "7f6ac5bd1b094db5ae95", "filename": "CPU-Renderer.png" }, { - "url": "http://106.12.111.150:8000/f/1ac8ebc9936b490586be/?dl=1", + "url": "1ac8ebc9936b490586be", "filename": "biography-of-lijie.png" } ] @@ -24,155 +24,155 @@ "target_dir": "public/images/gallery", "files": [ { - "url": "http://106.12.111.150:8000/f/0d357852716f492eb92f/?dl=1", + "url": "0d357852716f492eb92f", "filename": "Alphys.png" }, { - "url": "http://106.12.111.150:8000/f/ec2e2e282ed7478c8fa0/?dl=1", + "url": "ec2e2e282ed7478c8fa0", "filename": "Asgore.png" }, { - "url": "http://106.12.111.150:8000/f/7b7fb8414ab2485ba05a/?dl=1", + "url": "7b7fb8414ab2485ba05a", "filename": "Asriel.png" }, { - "url": "http://106.12.111.150:8000/f/bd8f4b71f3264ce5a36f/?dl=1", + "url": "bd8f4b71f3264ce5a36f", "filename": "AUBURY-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/3f39dda9122f440aafd8/?dl=1", + "url": "3f39dda9122f440aafd8", "filename": "AUBURY-headspace.png" }, { - "url": "http://106.12.111.150:8000/f/92b1355908b74898b7b3/?dl=1", + "url": "92b1355908b74898b7b3", "filename": "BASIL-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/9a3bdc09fa5e4216b7e6/?dl=1", + "url": "9a3bdc09fa5e4216b7e6", "filename": "BASIL-headspace.png" }, { - "url": "http://106.12.111.150:8000/f/00fea1e5adae472796ff/?dl=1", + "url": "00fea1e5adae472796ff", "filename": "Flowey.png" }, { - "url": "http://106.12.111.150:8000/f/edaa7526e5dd4f6e807d/?dl=1", + "url": "edaa7526e5dd4f6e807d", "filename": "HERO-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/309a3b70af944247a273/?dl=1", + "url": "309a3b70af944247a273", "filename": "HERO-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/cf5f1e4459e1453bbdd6/?dl=1", + "url": "cf5f1e4459e1453bbdd6", "filename": "KEL-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/221394e33b3746e89406/?dl=1", + "url": "221394e33b3746e89406", "filename": "KEL-headspace.png" }, { - "url": "http://106.12.111.150:8000/f/5e129d4d015149e28df8/?dl=1", + "url": "5e129d4d015149e28df8", "filename": "Kris-dark.png" }, { - "url": "http://106.12.111.150:8000/f/c5a27832d49f4e5a8fa2/?dl=1", + "url": "c5a27832d49f4e5a8fa2", "filename": "Kris-light.png" }, { - "url": "http://106.12.111.150:8000/f/2dc36c44d04540d29253/?dl=1", + "url": "2dc36c44d04540d29253", "filename": "MARI-farwaytown.png" }, { - "url": "http://106.12.111.150:8000/f/37c25836770c481bb22c/?dl=1", + "url": "37c25836770c481bb22c", "filename": "MARI-headspace.png" }, { - "url": "http://106.12.111.150:8000/f/accd21bb40ed48de8c48/?dl=1", + "url": "accd21bb40ed48de8c48", "filename": "Mettation.png" }, { - "url": "http://106.12.111.150:8000/f/b441f2cb16434bf38974/?dl=1", + "url": "b441f2cb16434bf38974", "filename": "Noelle-dark.png" }, { - "url": "http://106.12.111.150:8000/f/01e300bc2e6e4b3faf4b/?dl=1", + "url": "01e300bc2e6e4b3faf4b", "filename": "Noelle-light.png" }, { - "url": "http://106.12.111.150:8000/f/a3688e213aa249a6bcbb/?dl=1", + "url": "a3688e213aa249a6bcbb", "filename": "OMORI.png" }, { - "url": "http://106.12.111.150:8000/f/6ea8ec1fc7f84632bf50/?dl=1", + "url": "6ea8ec1fc7f84632bf50", "filename": "Papyrus.png" }, { - "url": "http://106.12.111.150:8000/f/806c6cbcee794039b894/?dl=1", + "url": "806c6cbcee794039b894", "filename": "Ralsei-hat.png" }, { - "url": "http://106.12.111.150:8000/f/d4dbf86997594a1d9b31/?dl=1", + "url": "d4dbf86997594a1d9b31", "filename": "Ralsei-nohat.png" }, { - "url": "http://106.12.111.150:8000/f/bf5804465283442fbefb/?dl=1", + "url": "bf5804465283442fbefb", "filename": "Sans.png" }, { - "url": "http://106.12.111.150:8000/f/a06f7a1a8f90404ba79a/?dl=1", + "url": "a06f7a1a8f90404ba79a", "filename": "SOMETHING.png" }, { - "url": "http://106.12.111.150:8000/f/e18e05022ede4e659e8f/?dl=1", + "url": "e18e05022ede4e659e8f", "filename": "SUNNY.png" }, { - "url": "http://106.12.111.150:8000/f/bd59853e81dd4c349c96/?dl=1", + "url": "bd59853e81dd4c349c96", "filename": "Susie-dark.png" }, { - "url": "http://106.12.111.150:8000/f/80faf6386179400db15b/?dl=1", + "url": "80faf6386179400db15b", "filename": "Susie-light.png" }, { - "url": "http://106.12.111.150:8000/f/13e2f6b06db1411bbdeb/?dl=1", + "url": "13e2f6b06db1411bbdeb", "filename": "Toriel.png" }, { - "url": "http://106.12.111.150:8000/f/d309b43c18ca431eaedf/?dl=1", + "url": "d309b43c18ca431eaedf", "filename": "Undyne.png" }, { - "url": "http://106.12.111.150:8000/f/c6b19ca33b274ad4b121/?dl=1", + "url": "c6b19ca33b274ad4b121", "filename": "狛枝凪斗.png" }, { - "url": "http://106.12.111.150:8000/f/10290be473334433848b/?dl=1", + "url": "10290be473334433848b", "filename": "不二咲千寻.png" }, { - "url": "http://106.12.111.150:8000/f/dcba07ca07ba4467b21f/?dl=1", + "url": "dcba07ca07ba4467b21f", "filename": "苗木诚.png" }, { - "url": "http://106.12.111.150:8000/f/fe817cb7b51f4d2bad41/?dl=1", + "url": "fe817cb7b51f4d2bad41", "filename": "七海千秋.png" }, { - "url": "http://106.12.111.150:8000/f/5ed86d51cc834745b127/?dl=1", + "url": "5ed86d51cc834745b127", "filename": "日向创.png" }, { - "url": "http://106.12.111.150:8000/f/36129927521c4f8eac1c/?dl=1", + "url": "36129927521c4f8eac1c", "filename": "十神白夜.png" }, { - "url": "http://106.12.111.150:8000/f/8ed6188acc87487faf38/?dl=1", + "url": "8ed6188acc87487faf38", "filename": "雾切响子.png" }, { - "url": "http://106.12.111.150:8000/f/d8a1d002c0324ca59e58/?dl=1", + "url": "d8a1d002c0324ca59e58", "filename": "小泉真昼.png" } ] diff --git a/src/pages/index.astro b/src/pages/index.astro index 1764d41..61f8087 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -37,19 +37,6 @@ const featuredProjectsUpdatedAt = getLatestDate(

{site.tagline}

{site.description}

-
-

临时访问说明

-

- 如果点击顶部导航后页面跳到 - 106.12.111.150/logs - 这类 - 不带 :2000 - 的地址并出现 404,请手动把地址改成 - 106.12.111.150:2000/对应路径 - 即可正常访问其他视图。这个问题是当前 Nginx 反代导致的临时现象,等域名备案完成后会恢复正常。 -

-
-