Files
MeBox/web/src/api/storage_config.ts
T
soldosluka857 74271081f4 feat(cloud): 115/夸克网盘 providers with cookie + QR login and 302 playback
Add a pluggable cloud-disk subsystem (internal/service/cloud) exposing remote
files as playable media via HTTP 302 redirects, so the host never transcodes
nor (by default) streams the bytes:

- quark (夸克网盘): cookie auth, directory listing + download_url resolution.
- cloud115 (115 网盘): cookie auth + QR-code login flow (token/poll/exchange);
  pickcode → CDN URL resolved for 302 offload.
- StorageConfigService gains cloud types, Ping() probes, List/Resolve/Import.
- New endpoints: admin /cloud/:type/{list,import,qr/start,qr/poll} and authed
  /cloud/play/:type (302 redirect, or reverse-proxy when the link needs auth
  headers). Imported files become Media rows with STRMURL → the play endpoint.
- Frontend: 115网盘/夸克网盘 tabs with cookie input, 115 QR-code login, a cloud
  file browser and one-click 302 import.

Providers are exercised against httptest mock servers (list/resolve/QR state
machine). Live login + playback require a real cloud account.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-30 21:04:23 +08:00

78 lines
1.9 KiB
TypeScript

import { api } from './client'
export type StorageType = 'alist' | 's3' | 'webdav' | 'cloud115' | 'quark'
export interface CloudEntry {
id: string
name: string
is_dir: boolean
size: number
pick_code?: string
}
export interface QRSession {
uid: string
time: number
sign: string
qr_image_url: string
}
export interface QRStatus {
state: 'waiting' | 'scanned' | 'confirmed' | 'expired'
cookie?: string
}
export interface StorageConfig {
id: string
type: StorageType
config: Record<string, string>
enabled: boolean
last_error?: string
created_at: string
updated_at: string
}
export const storageAPI = {
status: () =>
api
.get<{ items: StorageConfig[] }>('/admin/storage/status')
.then((r) => r.data.items),
get: (type: StorageType) =>
api.get<StorageConfig>(`/admin/storage/${type}`).then((r) => r.data),
save: (type: StorageType, config: Record<string, string>, enabled = true) =>
api
.put<StorageConfig>(`/admin/storage/${type}`, { type, config, enabled })
.then((r) => r.data),
test: (type: StorageType, config: Record<string, string>) =>
api
.post<{ ok: boolean; error?: string }>(`/admin/storage/${type}/test`, {
type,
config,
})
.then((r) => r.data),
}
// cloudAPI drives 网盘 browsing, QR login and 302 import.
export const cloudAPI = {
list: (type: StorageType, dir = '') =>
api
.get<{ items: CloudEntry[]; error?: string }>(`/admin/cloud/${type}/list`, {
params: { dir },
})
.then((r) => r.data),
import: (type: StorageType, ref: string, name: string, size: number) =>
api
.post(`/admin/cloud/${type}/import`, { ref, name, size })
.then((r) => r.data),
qrStart: (type: StorageType) =>
api.post<QRSession>(`/admin/cloud/${type}/qr/start`).then((r) => r.data),
qrPoll: (type: StorageType, sess: QRSession) =>
api.post<QRStatus>(`/admin/cloud/${type}/qr/poll`, sess).then((r) => r.data),
}