feat: Add public URL field in settings and services

This commit is contained in:
Peifan Li
2025-12-18 23:33:38 -05:00
parent 0b6c7c6343
commit 4144038c5b
15 changed files with 96 additions and 4 deletions

View File

@@ -26,6 +26,7 @@ interface Settings {
cloudDriveEnabled?: boolean; cloudDriveEnabled?: boolean;
openListApiUrl?: string; openListApiUrl?: string;
openListToken?: string; openListToken?: string;
openListPublicUrl?: string;
cloudDrivePath?: string; cloudDrivePath?: string;
homeSidebarOpen?: boolean; homeSidebarOpen?: boolean;
subtitlesEnabled?: boolean; subtitlesEnabled?: boolean;
@@ -49,6 +50,7 @@ const defaultSettings: Settings = {
cloudDriveEnabled: false, cloudDriveEnabled: false,
openListApiUrl: "", openListApiUrl: "",
openListToken: "", openListToken: "",
openListPublicUrl: "",
cloudDrivePath: "", cloudDrivePath: "",
homeSidebarOpen: true, homeSidebarOpen: true,
subtitlesEnabled: true, subtitlesEnabled: true,

View File

@@ -9,6 +9,7 @@ interface CloudDriveConfig {
enabled: boolean; enabled: boolean;
apiUrl: string; apiUrl: string;
token: string; token: string;
publicUrl?: string;
uploadPath: string; uploadPath: string;
} }
@@ -19,6 +20,7 @@ export class CloudStorageService {
enabled: settings.cloudDriveEnabled || false, enabled: settings.cloudDriveEnabled || false,
apiUrl: settings.openListApiUrl || "", apiUrl: settings.openListApiUrl || "",
token: settings.openListToken || "", token: settings.openListToken || "",
publicUrl: settings.openListPublicUrl || undefined,
uploadPath: settings.cloudDrivePath || "/", uploadPath: settings.cloudDrivePath || "/",
}; };
} }
@@ -399,10 +401,10 @@ export class CloudStorageService {
thumbnailThumbUrl?: string; thumbnailThumbUrl?: string;
} = {}; } = {};
// Extract domain from apiBaseUrl // Use publicUrl if set, otherwise extract domain from apiBaseUrl
// If apiBaseUrl is like https://example.com/api/fs/put, then apiBaseUrl will be https://example.com // If publicUrl is set (e.g., https://cloudflare-tunnel-domain.com), use it for file URLs
// Use this as the base domain for building file URLs // Otherwise, use apiBaseUrl (e.g., http://127.0.0.1:5244)
const domain = apiBaseUrl; const domain = config.publicUrl || apiBaseUrl;
// Find video file // Find video file
if (videoFilename) { if (videoFilename) {
@@ -445,6 +447,22 @@ export class CloudStorageService {
); );
// Also handle \u0026 encoding // Also handle \u0026 encoding
thumbUrl = thumbUrl.replace(/\\u0026/g, "&"); 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; result.thumbnailThumbUrl = thumbUrl;
} }
} else { } else {
@@ -461,6 +479,22 @@ export class CloudStorageService {
"width=1280&height=720" "width=1280&height=720"
); );
thumbUrl = thumbUrl.replace(/\\u0026/g, "&"); 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; result.thumbnailThumbUrl = thumbUrl;
} }
} }

View File

@@ -33,6 +33,22 @@ const CloudDriveSettings: React.FC<CloudDriveSettingsProps> = ({ settings, onCha
return null; 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 // Validate upload path
const validateUploadPath = (path: string): string | null => { const validateUploadPath = (path: string): string | null => {
if (!path.trim()) { if (!path.trim()) {
@@ -47,6 +63,9 @@ const CloudDriveSettings: React.FC<CloudDriveSettingsProps> = ({ settings, onCha
const apiUrlError = settings.cloudDriveEnabled && settings.openListApiUrl const apiUrlError = settings.cloudDriveEnabled && settings.openListApiUrl
? validateApiUrl(settings.openListApiUrl) ? validateApiUrl(settings.openListApiUrl)
: null; : null;
const publicUrlError = settings.cloudDriveEnabled && settings.openListPublicUrl
? validatePublicUrl(settings.openListPublicUrl)
: null;
const uploadPathError = settings.cloudDriveEnabled && settings.cloudDrivePath const uploadPathError = settings.cloudDriveEnabled && settings.cloudDrivePath
? validateUploadPath(settings.cloudDrivePath) ? validateUploadPath(settings.cloudDrivePath)
: null; : null;
@@ -155,6 +174,21 @@ const CloudDriveSettings: React.FC<CloudDriveSettingsProps> = ({ settings, onCha
fullWidth fullWidth
/> />
<TextField
label={t('publicUrl')}
value={settings.openListPublicUrl || ''}
onChange={(e) => onChange('openListPublicUrl', e.target.value)}
helperText={t('publicUrlHelper')}
error={!!publicUrlError}
placeholder="https://your-cloudflare-tunnel-domain.com"
fullWidth
/>
{publicUrlError && (
<Typography variant="caption" color="error" sx={{ mt: -1.5 }}>
{publicUrlError}
</Typography>
)}
<TextField <TextField
label={t('uploadPath')} label={t('uploadPath')}
value={settings.cloudDrivePath || ''} value={settings.cloudDrivePath || ''}

View File

@@ -52,6 +52,7 @@ const SettingsPage: React.FC = () => {
cloudDriveEnabled: false, cloudDriveEnabled: false,
openListApiUrl: '', openListApiUrl: '',
openListToken: '', openListToken: '',
openListPublicUrl: '',
cloudDrivePath: '', cloudDrivePath: '',
itemsPerPage: 12, itemsPerPage: 12,
ytDlpConfig: '', ytDlpConfig: '',

View File

@@ -66,6 +66,7 @@ export interface Settings {
cloudDriveEnabled: boolean; cloudDriveEnabled: boolean;
openListApiUrl: string; openListApiUrl: string;
openListToken: string; openListToken: string;
openListPublicUrl?: string;
cloudDrivePath: string; cloudDrivePath: string;
homeSidebarOpen?: boolean; homeSidebarOpen?: boolean;
subtitlesEnabled?: boolean; subtitlesEnabled?: boolean;

View File

@@ -140,6 +140,8 @@ export const ar = {
apiUrl: "رابط API", apiUrl: "رابط API",
apiUrlHelper: "مثال: https://your-alist-instance.com/api/fs/put", apiUrlHelper: "مثال: https://your-alist-instance.com/api/fs/put",
token: "الرمز المميز (Token)", token: "الرمز المميز (Token)",
publicUrl: "عنوان URL العام",
publicUrlHelper: "النطاق العام للوصول إلى الملفات (مثال: https://your-cloudflare-tunnel-domain.com). إذا تم تعيينه، سيتم استخدامه بدلاً من عنوان API للوصول إلى الملفات.",
uploadPath: "مسار التحميل", uploadPath: "مسار التحميل",
cloudDrivePathHelper: "مسار الدليل في التخزين السحابي، مثال: /mytube-uploads", cloudDrivePathHelper: "مسار الدليل في التخزين السحابي، مثال: /mytube-uploads",
cloudDriveNote: cloudDriveNote:

View File

@@ -136,6 +136,8 @@ export const de = {
apiUrl: "API-URL", apiUrl: "API-URL",
apiUrlHelper: "z.B. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "z.B. https://your-alist-instance.com/api/fs/put",
token: "Token", 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", uploadPath: "Upload-Pfad",
cloudDrivePathHelper: cloudDrivePathHelper:
"Verzeichnispfad im Cloud-Speicher, z.B. /mytube-uploads", "Verzeichnispfad im Cloud-Speicher, z.B. /mytube-uploads",

View File

@@ -137,6 +137,8 @@ export const en = {
apiUrl: "API URL", apiUrl: "API URL",
apiUrlHelper: "e.g. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "e.g. https://your-alist-instance.com/api/fs/put",
token: "Token", 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", uploadPath: "Upload Path",
cloudDrivePathHelper: "Directory path in cloud drive, e.g. /mytube-uploads", cloudDrivePathHelper: "Directory path in cloud drive, e.g. /mytube-uploads",
cloudDriveNote: cloudDriveNote:

View File

@@ -151,6 +151,8 @@ export const es = {
apiUrl: "URL de la API", apiUrl: "URL de la API",
apiUrlHelper: "ej. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "ej. https://your-alist-instance.com/api/fs/put",
token: "Token", 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", uploadPath: "Ruta de carga",
cloudDrivePathHelper: "Ruta del directorio en la nube, ej. /mytube-uploads", cloudDrivePathHelper: "Ruta del directorio en la nube, ej. /mytube-uploads",
cloudDriveNote: cloudDriveNote:

View File

@@ -150,6 +150,8 @@ export const fr = {
apiUrl: "URL de l'API", apiUrl: "URL de l'API",
apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put",
token: "Jeton (Token)", 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", uploadPath: "Chemin de téléchargement",
cloudDrivePathHelper: cloudDrivePathHelper:
"Chemin du répertoire dans le cloud, ex. /mytube-uploads", "Chemin du répertoire dans le cloud, ex. /mytube-uploads",

View File

@@ -145,6 +145,8 @@ export const ja = {
apiUrl: "API URL", apiUrl: "API URL",
apiUrlHelper: "例: https://your-alist-instance.com/api/fs/put", apiUrlHelper: "例: https://your-alist-instance.com/api/fs/put",
token: "トークン", token: "トークン",
publicUrl: "公開URL",
publicUrlHelper: "ファイルにアクセスするための公開ドメイン(例: https://your-cloudflare-tunnel-domain.com。設定されている場合、ファイルアクセスにはAPI URLの代わりにこれが使用されます。",
uploadPath: "アップロードパス", uploadPath: "アップロードパス",
cloudDrivePathHelper: cloudDrivePathHelper:
"クラウドドライブ内のディレクトリパス、例: /mytube-uploads", "クラウドドライブ内のディレクトリパス、例: /mytube-uploads",

View File

@@ -142,6 +142,8 @@ export const ko = {
apiUrl: "API URL", apiUrl: "API URL",
apiUrlHelper: "예: https://your-alist-instance.com/api/fs/put", apiUrlHelper: "예: https://your-alist-instance.com/api/fs/put",
token: "토큰", token: "토큰",
publicUrl: "공개 URL",
publicUrlHelper: "파일 액세스를 위한 공개 도메인 (예: https://your-cloudflare-tunnel-domain.com). 설정된 경우 파일 액세스에 API URL 대신 이것이 사용됩니다.",
uploadPath: "업로드 경로", uploadPath: "업로드 경로",
cloudDrivePathHelper: cloudDrivePathHelper:
"클라우드 드라이브 내 디렉토리 경로, 예: /mytube-uploads", "클라우드 드라이브 내 디렉토리 경로, 예: /mytube-uploads",

View File

@@ -146,6 +146,8 @@ export const pt = {
apiUrl: "URL da API", apiUrl: "URL da API",
apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put",
token: "Token", 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", uploadPath: "Caminho de upload",
cloudDrivePathHelper: "Caminho do diretório na nuvem, ex. /mytube-uploads", cloudDrivePathHelper: "Caminho do diretório na nuvem, ex. /mytube-uploads",
cloudDriveNote: cloudDriveNote:

View File

@@ -154,6 +154,8 @@ export const ru = {
apiUrl: "URL API", apiUrl: "URL API",
apiUrlHelper: "напр. https://your-alist-instance.com/api/fs/put", apiUrlHelper: "напр. https://your-alist-instance.com/api/fs/put",
token: "Токен", token: "Токен",
publicUrl: "Публичный URL",
publicUrlHelper: "Публичный домен для доступа к файлам (напр. https://your-cloudflare-tunnel-domain.com). Если установлен, будет использоваться вместо URL API для доступа к файлам.",
uploadPath: "Путь загрузки", uploadPath: "Путь загрузки",
cloudDrivePathHelper: "Путь к каталогу в облаке, напр. /mytube-uploads", cloudDrivePathHelper: "Путь к каталогу в облаке, напр. /mytube-uploads",
cloudDriveNote: cloudDriveNote:

View File

@@ -137,6 +137,8 @@ export const zh = {
apiUrl: "API 地址", apiUrl: "API 地址",
apiUrlHelper: "例如https://your-alist-instance.com/api/fs/put", apiUrlHelper: "例如https://your-alist-instance.com/api/fs/put",
token: "Token", token: "Token",
publicUrl: "公开访问域名",
publicUrlHelper: "用于访问文件的公开域名例如https://your-cloudflare-tunnel-domain.com。如果设置将使用此域名而不是 API 地址来访问文件。",
uploadPath: "上传路径", uploadPath: "上传路径",
cloudDrivePathHelper: "云端存储中的目录路径,例如:/mytube-uploads", cloudDrivePathHelper: "云端存储中的目录路径,例如:/mytube-uploads",
cloudDriveNote: cloudDriveNote: