From 4144038c5b830fe6bf7b220743f205b400ed8393 Mon Sep 17 00:00:00 2001 From: Peifan Li Date: Thu, 18 Dec 2025 23:33:38 -0500 Subject: [PATCH] feat: Add public URL field in settings and services --- backend/src/controllers/settingsController.ts | 2 + backend/src/services/CloudStorageService.ts | 42 +++++++++++++++++-- .../Settings/CloudDriveSettings.tsx | 34 +++++++++++++++ frontend/src/pages/SettingsPage.tsx | 1 + frontend/src/types.ts | 1 + frontend/src/utils/locales/ar.ts | 2 + frontend/src/utils/locales/de.ts | 2 + frontend/src/utils/locales/en.ts | 2 + frontend/src/utils/locales/es.ts | 2 + frontend/src/utils/locales/fr.ts | 2 + frontend/src/utils/locales/ja.ts | 2 + frontend/src/utils/locales/ko.ts | 2 + frontend/src/utils/locales/pt.ts | 2 + frontend/src/utils/locales/ru.ts | 2 + frontend/src/utils/locales/zh.ts | 2 + 15 files changed, 96 insertions(+), 4 deletions(-) diff --git a/backend/src/controllers/settingsController.ts b/backend/src/controllers/settingsController.ts index befdac5..8ce1301 100644 --- a/backend/src/controllers/settingsController.ts +++ b/backend/src/controllers/settingsController.ts @@ -26,6 +26,7 @@ interface Settings { cloudDriveEnabled?: boolean; openListApiUrl?: string; openListToken?: string; + openListPublicUrl?: string; cloudDrivePath?: string; homeSidebarOpen?: boolean; subtitlesEnabled?: boolean; @@ -49,6 +50,7 @@ const defaultSettings: Settings = { cloudDriveEnabled: false, openListApiUrl: "", openListToken: "", + openListPublicUrl: "", cloudDrivePath: "", homeSidebarOpen: true, subtitlesEnabled: true, diff --git a/backend/src/services/CloudStorageService.ts b/backend/src/services/CloudStorageService.ts index 0ef045e..e7dc9b6 100644 --- a/backend/src/services/CloudStorageService.ts +++ b/backend/src/services/CloudStorageService.ts @@ -9,6 +9,7 @@ interface CloudDriveConfig { enabled: boolean; apiUrl: string; token: string; + publicUrl?: string; uploadPath: string; } @@ -19,6 +20,7 @@ export class CloudStorageService { enabled: settings.cloudDriveEnabled || false, apiUrl: settings.openListApiUrl || "", token: settings.openListToken || "", + publicUrl: settings.openListPublicUrl || undefined, uploadPath: settings.cloudDrivePath || "/", }; } @@ -399,10 +401,10 @@ export class CloudStorageService { thumbnailThumbUrl?: string; } = {}; - // Extract domain from apiBaseUrl - // If apiBaseUrl is like https://example.com/api/fs/put, then apiBaseUrl will be https://example.com - // Use this as the base domain for building file URLs - const domain = apiBaseUrl; + // Use publicUrl if set, otherwise extract domain from apiBaseUrl + // If publicUrl is set (e.g., https://cloudflare-tunnel-domain.com), use it for file URLs + // Otherwise, use apiBaseUrl (e.g., http://127.0.0.1:5244) + const domain = config.publicUrl || apiBaseUrl; // Find video file if (videoFilename) { @@ -445,6 +447,22 @@ export class CloudStorageService { ); // Also handle \u0026 encoding thumbUrl = thumbUrl.replace(/\\u0026/g, "&"); + // If publicUrl is set, replace the domain in thumbUrl with publicUrl + if (config.publicUrl) { + try { + const thumbUrlObj = new URL(thumbUrl); + const publicUrlObj = new URL(config.publicUrl); + thumbUrl = thumbUrl.replace( + thumbUrlObj.origin, + publicUrlObj.origin + ); + } catch (e) { + // If URL parsing fails, use thumbUrl as is + logger.debug( + `[CloudStorage] Failed to replace domain in thumbUrl: ${thumbUrl}` + ); + } + } result.thumbnailThumbUrl = thumbUrl; } } else { @@ -461,6 +479,22 @@ export class CloudStorageService { "width=1280&height=720" ); thumbUrl = thumbUrl.replace(/\\u0026/g, "&"); + // If publicUrl is set, replace the domain in thumbUrl with publicUrl + if (config.publicUrl) { + try { + const thumbUrlObj = new URL(thumbUrl); + const publicUrlObj = new URL(config.publicUrl); + thumbUrl = thumbUrl.replace( + thumbUrlObj.origin, + publicUrlObj.origin + ); + } catch (e) { + // If URL parsing fails, use thumbUrl as is + logger.debug( + `[CloudStorage] Failed to replace domain in thumbUrl: ${thumbUrl}` + ); + } + } result.thumbnailThumbUrl = thumbUrl; } } diff --git a/frontend/src/components/Settings/CloudDriveSettings.tsx b/frontend/src/components/Settings/CloudDriveSettings.tsx index dc12ba6..cb43183 100644 --- a/frontend/src/components/Settings/CloudDriveSettings.tsx +++ b/frontend/src/components/Settings/CloudDriveSettings.tsx @@ -33,6 +33,22 @@ const CloudDriveSettings: React.FC = ({ settings, onCha return null; }; + // Validate public URL format + const validatePublicUrl = (url: string): string | null => { + if (!url.trim()) { + return null; // Optional field + } + try { + const urlObj = new URL(url); + if (!urlObj.protocol.startsWith('http')) { + return 'URL must start with http:// or https://'; + } + } catch { + return 'Invalid URL format'; + } + return null; + }; + // Validate upload path const validateUploadPath = (path: string): string | null => { if (!path.trim()) { @@ -47,6 +63,9 @@ const CloudDriveSettings: React.FC = ({ settings, onCha const apiUrlError = settings.cloudDriveEnabled && settings.openListApiUrl ? validateApiUrl(settings.openListApiUrl) : null; + const publicUrlError = settings.cloudDriveEnabled && settings.openListPublicUrl + ? validatePublicUrl(settings.openListPublicUrl) + : null; const uploadPathError = settings.cloudDriveEnabled && settings.cloudDrivePath ? validateUploadPath(settings.cloudDrivePath) : null; @@ -155,6 +174,21 @@ const CloudDriveSettings: React.FC = ({ settings, onCha fullWidth /> + onChange('openListPublicUrl', e.target.value)} + helperText={t('publicUrlHelper')} + error={!!publicUrlError} + placeholder="https://your-cloudflare-tunnel-domain.com" + fullWidth + /> + {publicUrlError && ( + + {publicUrlError} + + )} + { cloudDriveEnabled: false, openListApiUrl: '', openListToken: '', + openListPublicUrl: '', cloudDrivePath: '', itemsPerPage: 12, ytDlpConfig: '', diff --git a/frontend/src/types.ts b/frontend/src/types.ts index e8d2ddb..69deca3 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -66,6 +66,7 @@ export interface Settings { cloudDriveEnabled: boolean; openListApiUrl: string; openListToken: string; + openListPublicUrl?: string; cloudDrivePath: string; homeSidebarOpen?: boolean; subtitlesEnabled?: boolean; diff --git a/frontend/src/utils/locales/ar.ts b/frontend/src/utils/locales/ar.ts index 20ae49d..f6f4b4b 100644 --- a/frontend/src/utils/locales/ar.ts +++ b/frontend/src/utils/locales/ar.ts @@ -140,6 +140,8 @@ export const ar = { apiUrl: "رابط API", apiUrlHelper: "مثال: https://your-alist-instance.com/api/fs/put", token: "الرمز المميز (Token)", + publicUrl: "عنوان URL العام", + publicUrlHelper: "النطاق العام للوصول إلى الملفات (مثال: https://your-cloudflare-tunnel-domain.com). إذا تم تعيينه، سيتم استخدامه بدلاً من عنوان API للوصول إلى الملفات.", uploadPath: "مسار التحميل", cloudDrivePathHelper: "مسار الدليل في التخزين السحابي، مثال: /mytube-uploads", cloudDriveNote: diff --git a/frontend/src/utils/locales/de.ts b/frontend/src/utils/locales/de.ts index af7de0a..9a67202 100644 --- a/frontend/src/utils/locales/de.ts +++ b/frontend/src/utils/locales/de.ts @@ -136,6 +136,8 @@ export const de = { apiUrl: "API-URL", apiUrlHelper: "z.B. https://your-alist-instance.com/api/fs/put", token: "Token", + publicUrl: "Öffentliche URL", + publicUrlHelper: "Öffentliche Domain für den Dateizugriff (z.B. https://your-cloudflare-tunnel-domain.com). Wenn gesetzt, wird diese anstelle der API-URL für den Dateizugriff verwendet.", uploadPath: "Upload-Pfad", cloudDrivePathHelper: "Verzeichnispfad im Cloud-Speicher, z.B. /mytube-uploads", diff --git a/frontend/src/utils/locales/en.ts b/frontend/src/utils/locales/en.ts index 1fc151c..9959a6e 100644 --- a/frontend/src/utils/locales/en.ts +++ b/frontend/src/utils/locales/en.ts @@ -137,6 +137,8 @@ export const en = { apiUrl: "API URL", apiUrlHelper: "e.g. https://your-alist-instance.com/api/fs/put", token: "Token", + publicUrl: "Public URL", + publicUrlHelper: "Public domain for accessing files (e.g., https://your-cloudflare-tunnel-domain.com). If set, this will be used instead of the API URL for file access.", uploadPath: "Upload Path", cloudDrivePathHelper: "Directory path in cloud drive, e.g. /mytube-uploads", cloudDriveNote: diff --git a/frontend/src/utils/locales/es.ts b/frontend/src/utils/locales/es.ts index b8d15ba..308559f 100644 --- a/frontend/src/utils/locales/es.ts +++ b/frontend/src/utils/locales/es.ts @@ -151,6 +151,8 @@ export const es = { apiUrl: "URL de la API", apiUrlHelper: "ej. https://your-alist-instance.com/api/fs/put", token: "Token", + publicUrl: "URL Público", + publicUrlHelper: "Dominio público para acceder a archivos (ej. https://your-cloudflare-tunnel-domain.com). Si se establece, se usará en lugar de la URL de la API para acceder a archivos.", uploadPath: "Ruta de carga", cloudDrivePathHelper: "Ruta del directorio en la nube, ej. /mytube-uploads", cloudDriveNote: diff --git a/frontend/src/utils/locales/fr.ts b/frontend/src/utils/locales/fr.ts index 2de3a12..78ed32b 100644 --- a/frontend/src/utils/locales/fr.ts +++ b/frontend/src/utils/locales/fr.ts @@ -150,6 +150,8 @@ export const fr = { apiUrl: "URL de l'API", apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put", token: "Jeton (Token)", + publicUrl: "URL Publique", + publicUrlHelper: "Domaine public pour accéder aux fichiers (ex. https://your-cloudflare-tunnel-domain.com). S'il est défini, il sera utilisé à la place de l'URL de l'API pour accéder aux fichiers.", uploadPath: "Chemin de téléchargement", cloudDrivePathHelper: "Chemin du répertoire dans le cloud, ex. /mytube-uploads", diff --git a/frontend/src/utils/locales/ja.ts b/frontend/src/utils/locales/ja.ts index 36c4877..138ea52 100644 --- a/frontend/src/utils/locales/ja.ts +++ b/frontend/src/utils/locales/ja.ts @@ -145,6 +145,8 @@ export const ja = { apiUrl: "API URL", apiUrlHelper: "例: https://your-alist-instance.com/api/fs/put", token: "トークン", + publicUrl: "公開URL", + publicUrlHelper: "ファイルにアクセスするための公開ドメイン(例: https://your-cloudflare-tunnel-domain.com)。設定されている場合、ファイルアクセスにはAPI URLの代わりにこれが使用されます。", uploadPath: "アップロードパス", cloudDrivePathHelper: "クラウドドライブ内のディレクトリパス、例: /mytube-uploads", diff --git a/frontend/src/utils/locales/ko.ts b/frontend/src/utils/locales/ko.ts index bba8cb9..80406ed 100644 --- a/frontend/src/utils/locales/ko.ts +++ b/frontend/src/utils/locales/ko.ts @@ -142,6 +142,8 @@ export const ko = { apiUrl: "API URL", apiUrlHelper: "예: https://your-alist-instance.com/api/fs/put", token: "토큰", + publicUrl: "공개 URL", + publicUrlHelper: "파일 액세스를 위한 공개 도메인 (예: https://your-cloudflare-tunnel-domain.com). 설정된 경우 파일 액세스에 API URL 대신 이것이 사용됩니다.", uploadPath: "업로드 경로", cloudDrivePathHelper: "클라우드 드라이브 내 디렉토리 경로, 예: /mytube-uploads", diff --git a/frontend/src/utils/locales/pt.ts b/frontend/src/utils/locales/pt.ts index da4c70d..f14a163 100644 --- a/frontend/src/utils/locales/pt.ts +++ b/frontend/src/utils/locales/pt.ts @@ -146,6 +146,8 @@ export const pt = { apiUrl: "URL da API", apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put", token: "Token", + publicUrl: "URL Público", + publicUrlHelper: "Domínio público para acessar arquivos (ex. https://your-cloudflare-tunnel-domain.com). Se definido, será usado em vez da URL da API para acessar arquivos.", uploadPath: "Caminho de upload", cloudDrivePathHelper: "Caminho do diretório na nuvem, ex. /mytube-uploads", cloudDriveNote: diff --git a/frontend/src/utils/locales/ru.ts b/frontend/src/utils/locales/ru.ts index 01f0064..77c40d7 100644 --- a/frontend/src/utils/locales/ru.ts +++ b/frontend/src/utils/locales/ru.ts @@ -154,6 +154,8 @@ export const ru = { apiUrl: "URL API", apiUrlHelper: "напр. https://your-alist-instance.com/api/fs/put", token: "Токен", + publicUrl: "Публичный URL", + publicUrlHelper: "Публичный домен для доступа к файлам (напр. https://your-cloudflare-tunnel-domain.com). Если установлен, будет использоваться вместо URL API для доступа к файлам.", uploadPath: "Путь загрузки", cloudDrivePathHelper: "Путь к каталогу в облаке, напр. /mytube-uploads", cloudDriveNote: diff --git a/frontend/src/utils/locales/zh.ts b/frontend/src/utils/locales/zh.ts index 6e44de4..1ef736f 100644 --- a/frontend/src/utils/locales/zh.ts +++ b/frontend/src/utils/locales/zh.ts @@ -137,6 +137,8 @@ export const zh = { apiUrl: "API 地址", apiUrlHelper: "例如:https://your-alist-instance.com/api/fs/put", token: "Token", + publicUrl: "公开访问域名", + publicUrlHelper: "用于访问文件的公开域名(例如:https://your-cloudflare-tunnel-domain.com)。如果设置,将使用此域名而不是 API 地址来访问文件。", uploadPath: "上传路径", cloudDrivePathHelper: "云端存储中的目录路径,例如:/mytube-uploads", cloudDriveNote: