mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
refactor: split strm page hooks
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
export function apiErrorMessage(err: unknown, fallback: string): string {
|
||||
return (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? fallback
|
||||
}
|
||||
|
||||
export function isHTTPURL(value: string): boolean {
|
||||
return /^https?:\/\//i.test(value)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { FormEvent } from 'react'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { mediaAPI } from '../api/library'
|
||||
import { strmAPI } from '../api/strm'
|
||||
import { confirmAction } from '../components/confirmAction'
|
||||
import type { Media } from '../types'
|
||||
import { apiErrorMessage, isHTTPURL } from './strmPageUtils'
|
||||
|
||||
export function useStrmAttachForm() {
|
||||
const [query, setQuery] = useState('')
|
||||
const [searching, setSearching] = useState(false)
|
||||
const [results, setResults] = useState<Media[]>([])
|
||||
const [drafts, setDrafts] = useState<Record<string, string>>({})
|
||||
|
||||
const doSearch = async (event?: FormEvent) => {
|
||||
event?.preventDefault()
|
||||
const trimmedQuery = query.trim()
|
||||
if (!trimmedQuery) return
|
||||
setSearching(true)
|
||||
try {
|
||||
const result = await mediaAPI.search(trimmedQuery, 30)
|
||||
setResults(result.items ?? [])
|
||||
} catch {
|
||||
toast.error('搜索失败')
|
||||
} finally {
|
||||
setSearching(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onAttach = async (media: Media) => {
|
||||
const next = (drafts[media.id] ?? '').trim()
|
||||
if (!next) return
|
||||
if (!isHTTPURL(next)) {
|
||||
toast.error('URL 必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await strmAPI.set(media.id, next)
|
||||
toast.success('已设置 STRM URL')
|
||||
setResults((rows) =>
|
||||
rows.map((item) => (item.id === media.id ? ({ ...item, container: 'strm' } as Media) : item)),
|
||||
)
|
||||
setDrafts((current) => ({ ...current, [media.id]: '' }))
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '设置失败'))
|
||||
}
|
||||
}
|
||||
|
||||
const onDetach = async (media: Media) => {
|
||||
const ok = await confirmAction({
|
||||
title: '清除 STRM URL',
|
||||
message: `清除「${media.title}」的 STRM URL?`,
|
||||
confirmText: '清除',
|
||||
})
|
||||
if (!ok) return
|
||||
|
||||
try {
|
||||
await strmAPI.clear(media.id)
|
||||
toast.success('已清除')
|
||||
setResults((rows) =>
|
||||
rows.map((item) =>
|
||||
item.id === media.id
|
||||
? ({ ...item, container: item.container === 'strm' ? '' : item.container } as Media)
|
||||
: item,
|
||||
),
|
||||
)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '清除失败'))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
doSearch,
|
||||
drafts,
|
||||
onAttach,
|
||||
onDetach,
|
||||
query,
|
||||
results,
|
||||
searching,
|
||||
setDrafts,
|
||||
setQuery,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { FormEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { adminAPI } from '../api/admin'
|
||||
import { strmAPI, type GenerateSTRMResult } from '../api/strm'
|
||||
import type { Library } from '../types'
|
||||
import {
|
||||
inferSTRMOutputRoot,
|
||||
playbackStatusText,
|
||||
preferredSTRMBaseURL,
|
||||
suggestedSTRMOutputDir,
|
||||
type CloudPlaybackMode,
|
||||
} from './strmPageModel'
|
||||
import { apiErrorMessage, isHTTPURL } from './strmPageUtils'
|
||||
|
||||
export function useStrmGenerateForm(libraries: Library[]) {
|
||||
const [generateLibraryID, setGenerateLibraryID] = useState('')
|
||||
const [baseURL, setBaseURL] = useState('')
|
||||
const [outputDir, setOutputDir] = useState('')
|
||||
const [outputRoot, setOutputRoot] = useState('')
|
||||
const [outputScope, setOutputScope] = useState('')
|
||||
const [outputDirTouched, setOutputDirTouched] = useState(false)
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false)
|
||||
const [cloudPlaybackMode, setCloudPlaybackMode] = useState<CloudPlaybackMode>('redirect_proxy')
|
||||
const [strmPlaybackEnabled, setStrmPlaybackEnabled] = useState(false)
|
||||
const [redirectProxyEnabled, setRedirectProxyEnabled] = useState(true)
|
||||
const [autoGenerate, setAutoGenerate] = useState(false)
|
||||
const [savingSettings, setSavingSettings] = useState(false)
|
||||
const [overwrite, setOverwrite] = useState(false)
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [generateResult, setGenerateResult] = useState<GenerateSTRMResult | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
adminAPI
|
||||
.listSettings()
|
||||
.then((rows) => {
|
||||
const settings = Object.fromEntries(rows.map((row) => [row.key, row.value]))
|
||||
const savedOutputDir = settings['strm.output_dir'] || ''
|
||||
const mode = settings['cloud.playback_mode']
|
||||
const nextMode =
|
||||
mode === 'strm' || mode === 'redirect_proxy'
|
||||
? mode
|
||||
: settings['strm.enabled'] === 'true'
|
||||
? 'strm'
|
||||
: 'redirect_proxy'
|
||||
|
||||
setBaseURL(preferredSTRMBaseURL(settings['strm.base_url'] || settings['app.server_url'] || ''))
|
||||
setOutputDir(savedOutputDir)
|
||||
setOutputRoot(savedOutputDir)
|
||||
setOutputScope(settings['strm.output_scope'] || '')
|
||||
setOutputDirTouched(false)
|
||||
setCloudPlaybackMode(nextMode)
|
||||
setStrmPlaybackEnabled(
|
||||
settings['cloud.playback_strm_enabled'] != null
|
||||
? settings['cloud.playback_strm_enabled'] === 'true'
|
||||
: settings['strm.enabled'] === 'true' || nextMode === 'strm',
|
||||
)
|
||||
setRedirectProxyEnabled(settings['cloud.playback_redirect_proxy_enabled'] !== 'false')
|
||||
setAutoGenerate(settings['strm.auto_generate_enabled'] === 'true')
|
||||
setSettingsLoaded(true)
|
||||
})
|
||||
.catch(() => setSettingsLoaded(true))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!generateLibraryID && libraries[0]) setGenerateLibraryID(libraries[0].id)
|
||||
}, [libraries, generateLibraryID])
|
||||
|
||||
useEffect(() => {
|
||||
if (!settingsLoaded || outputDirTouched || !generateLibraryID) return
|
||||
const root = outputScope === 'library' ? inferSTRMOutputRoot(outputRoot, libraries) : outputRoot
|
||||
if (generateLibraryID === '*') {
|
||||
setOutputDir(root)
|
||||
return
|
||||
}
|
||||
const library = libraries.find((item) => item.id === generateLibraryID)
|
||||
if (library) setOutputDir(suggestedSTRMOutputDir(root, library))
|
||||
}, [settingsLoaded, outputDirTouched, outputRoot, outputScope, generateLibraryID, libraries])
|
||||
|
||||
const onOutputDirChange = (value: string) => {
|
||||
setOutputDir(value)
|
||||
setOutputRoot(value)
|
||||
setOutputScope('')
|
||||
setOutputDirTouched(true)
|
||||
}
|
||||
|
||||
const onGenerate = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
const trimmedBaseURL = baseURL.trim()
|
||||
if (!generateLibraryID || !trimmedBaseURL) return
|
||||
if (!isHTTPURL(trimmedBaseURL)) {
|
||||
toast.error('域名必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
setGenerating(true)
|
||||
try {
|
||||
const result = await strmAPI.generate({
|
||||
library_id: generateLibraryID,
|
||||
base_url: trimmedBaseURL.replace(/\/+$/, ''),
|
||||
output_dir: outputDir.trim(),
|
||||
overwrite,
|
||||
enabled: autoGenerate,
|
||||
include_local: true,
|
||||
})
|
||||
const nextOutputDir = result.output_dir || outputDir
|
||||
setGenerateResult(result)
|
||||
setOutputDir(nextOutputDir)
|
||||
setOutputRoot(inferSTRMOutputRoot(nextOutputDir, libraries))
|
||||
setOutputScope(generateLibraryID === '*' ? 'all' : 'library')
|
||||
setOutputDirTouched(false)
|
||||
toast.success(`生成完成:新增 ${result.generated} · 更新 ${result.updated} · 跳过 ${result.skipped}`)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '生成失败'))
|
||||
} finally {
|
||||
setGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const saveSTRMSettings = async () => {
|
||||
setSavingSettings(true)
|
||||
try {
|
||||
const effectiveMode: CloudPlaybackMode =
|
||||
strmPlaybackEnabled && !redirectProxyEnabled
|
||||
? 'strm'
|
||||
: !strmPlaybackEnabled && redirectProxyEnabled
|
||||
? 'redirect_proxy'
|
||||
: cloudPlaybackMode
|
||||
await Promise.all([
|
||||
adminAPI.updateSetting('cloud.playback_mode', effectiveMode),
|
||||
adminAPI.updateSetting('cloud.playback_strm_enabled', String(strmPlaybackEnabled)),
|
||||
adminAPI.updateSetting('cloud.playback_redirect_proxy_enabled', String(redirectProxyEnabled)),
|
||||
adminAPI.updateSetting('strm.enabled', String(strmPlaybackEnabled)),
|
||||
adminAPI.updateSetting('strm.auto_generate_enabled', String(autoGenerate)),
|
||||
])
|
||||
setCloudPlaybackMode(effectiveMode)
|
||||
toast.success(
|
||||
strmPlaybackEnabled || redirectProxyEnabled
|
||||
? `播放设置已保存:优先 ${effectiveMode === 'strm' ? 'STRMURL' : '302/反代'}`
|
||||
: '播放设置已保存:云盘第三方播放已关闭',
|
||||
)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '保存 STRM 开关失败'))
|
||||
} finally {
|
||||
setSavingSettings(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
autoGenerate,
|
||||
baseURL,
|
||||
cloudPlaybackMode,
|
||||
generateLibraryID,
|
||||
generateResult,
|
||||
generating,
|
||||
onGenerate,
|
||||
outputDir,
|
||||
overwrite,
|
||||
playbackStatus: playbackStatusText(strmPlaybackEnabled, redirectProxyEnabled, cloudPlaybackMode),
|
||||
redirectProxyEnabled,
|
||||
saveSTRMSettings,
|
||||
savingSettings,
|
||||
setAutoGenerate,
|
||||
setBaseURL,
|
||||
setCloudPlaybackMode,
|
||||
setGenerateLibraryID,
|
||||
setOutputDir: onOutputDirChange,
|
||||
setOverwrite,
|
||||
setRedirectProxyEnabled,
|
||||
setStrmPlaybackEnabled,
|
||||
strmPlaybackEnabled,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { FormEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { strmAPI } from '../api/strm'
|
||||
import type { Library } from '../types'
|
||||
import { apiErrorMessage, isHTTPURL } from './strmPageUtils'
|
||||
|
||||
export function useStrmImportForm(libraries: Library[]) {
|
||||
const [libraryID, setLibraryID] = useState('')
|
||||
const [title, setTitle] = useState('')
|
||||
const [url, setURL] = useState('')
|
||||
const [importing, setImporting] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!libraryID && libraries[0]) setLibraryID(libraries[0].id)
|
||||
}, [libraries, libraryID])
|
||||
|
||||
const onImport = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
const trimmedTitle = title.trim()
|
||||
const trimmedURL = url.trim()
|
||||
if (!libraryID || !trimmedTitle || !trimmedURL) return
|
||||
if (!isHTTPURL(trimmedURL)) {
|
||||
toast.error('URL 必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
setImporting(true)
|
||||
try {
|
||||
await strmAPI.importURL(libraryID, trimmedTitle, trimmedURL)
|
||||
toast.success(`已导入「${trimmedTitle}」`)
|
||||
setTitle('')
|
||||
setURL('')
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '导入失败'))
|
||||
} finally {
|
||||
setImporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
importing,
|
||||
libraryID,
|
||||
onImport,
|
||||
setLibraryID,
|
||||
setTitle,
|
||||
setURL,
|
||||
title,
|
||||
url,
|
||||
}
|
||||
}
|
||||
+12
-288
@@ -1,301 +1,25 @@
|
||||
import { FormEvent, useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { adminAPI } from '../api/admin'
|
||||
import { libraryAPI, mediaAPI } from '../api/library'
|
||||
import { strmAPI, type GenerateSTRMResult } from '../api/strm'
|
||||
import { confirmAction } from '../components/confirmAction'
|
||||
import type { Library, Media } from '../types'
|
||||
import {
|
||||
inferSTRMOutputRoot,
|
||||
playbackStatusText,
|
||||
preferredSTRMBaseURL,
|
||||
suggestedSTRMOutputDir,
|
||||
type CloudPlaybackMode,
|
||||
} from './strmPageModel'
|
||||
|
||||
function apiErrorMessage(err: unknown, fallback: string): string {
|
||||
return (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? fallback
|
||||
}
|
||||
|
||||
function isHTTPURL(value: string): boolean {
|
||||
return /^https?:\/\//i.test(value)
|
||||
}
|
||||
import { libraryAPI } from '../api/library'
|
||||
import type { Library } from '../types'
|
||||
import { useStrmAttachForm } from './useStrmAttachForm'
|
||||
import { useStrmGenerateForm } from './useStrmGenerateForm'
|
||||
import { useStrmImportForm } from './useStrmImportForm'
|
||||
|
||||
export function useStrmPage() {
|
||||
const [libraries, setLibraries] = useState<Library[]>([])
|
||||
|
||||
const [generateLibraryID, setGenerateLibraryID] = useState('')
|
||||
const [baseURL, setBaseURL] = useState('')
|
||||
const [outputDir, setOutputDir] = useState('')
|
||||
const [outputRoot, setOutputRoot] = useState('')
|
||||
const [outputScope, setOutputScope] = useState('')
|
||||
const [outputDirTouched, setOutputDirTouched] = useState(false)
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false)
|
||||
const [cloudPlaybackMode, setCloudPlaybackMode] = useState<CloudPlaybackMode>('redirect_proxy')
|
||||
const [strmPlaybackEnabled, setStrmPlaybackEnabled] = useState(false)
|
||||
const [redirectProxyEnabled, setRedirectProxyEnabled] = useState(true)
|
||||
const [autoGenerate, setAutoGenerate] = useState(false)
|
||||
const [savingSettings, setSavingSettings] = useState(false)
|
||||
const [overwrite, setOverwrite] = useState(false)
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [generateResult, setGenerateResult] = useState<GenerateSTRMResult | null>(null)
|
||||
|
||||
const [libraryID, setLibraryID] = useState('')
|
||||
const [title, setTitle] = useState('')
|
||||
const [url, setURL] = useState('')
|
||||
const [importing, setImporting] = useState(false)
|
||||
|
||||
const [query, setQuery] = useState('')
|
||||
const [searching, setSearching] = useState(false)
|
||||
const [results, setResults] = useState<Media[]>([])
|
||||
const [drafts, setDrafts] = useState<Record<string, string>>({})
|
||||
const generate = useStrmGenerateForm(libraries)
|
||||
const importForm = useStrmImportForm(libraries)
|
||||
const attach = useStrmAttachForm()
|
||||
|
||||
useEffect(() => {
|
||||
libraryAPI.list().then(setLibraries).catch(() => undefined)
|
||||
adminAPI
|
||||
.listSettings()
|
||||
.then((rows) => {
|
||||
const settings = Object.fromEntries(rows.map((row) => [row.key, row.value]))
|
||||
const savedOutputDir = settings['strm.output_dir'] || ''
|
||||
const mode = settings['cloud.playback_mode']
|
||||
const nextMode =
|
||||
mode === 'strm' || mode === 'redirect_proxy'
|
||||
? mode
|
||||
: settings['strm.enabled'] === 'true'
|
||||
? 'strm'
|
||||
: 'redirect_proxy'
|
||||
|
||||
setBaseURL(preferredSTRMBaseURL(settings['strm.base_url'] || settings['app.server_url'] || ''))
|
||||
setOutputDir(savedOutputDir)
|
||||
setOutputRoot(savedOutputDir)
|
||||
setOutputScope(settings['strm.output_scope'] || '')
|
||||
setOutputDirTouched(false)
|
||||
setCloudPlaybackMode(nextMode)
|
||||
setStrmPlaybackEnabled(
|
||||
settings['cloud.playback_strm_enabled'] != null
|
||||
? settings['cloud.playback_strm_enabled'] === 'true'
|
||||
: settings['strm.enabled'] === 'true' || nextMode === 'strm',
|
||||
)
|
||||
setRedirectProxyEnabled(settings['cloud.playback_redirect_proxy_enabled'] !== 'false')
|
||||
setAutoGenerate(settings['strm.auto_generate_enabled'] === 'true')
|
||||
setSettingsLoaded(true)
|
||||
})
|
||||
.catch(() => setSettingsLoaded(true))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!libraryID && libraries[0]) setLibraryID(libraries[0].id)
|
||||
if (!generateLibraryID && libraries[0]) setGenerateLibraryID(libraries[0].id)
|
||||
}, [libraries, libraryID, generateLibraryID])
|
||||
|
||||
useEffect(() => {
|
||||
if (!settingsLoaded || outputDirTouched || !generateLibraryID) return
|
||||
const root = outputScope === 'library' ? inferSTRMOutputRoot(outputRoot, libraries) : outputRoot
|
||||
if (generateLibraryID === '*') {
|
||||
setOutputDir(root)
|
||||
return
|
||||
}
|
||||
const library = libraries.find((item) => item.id === generateLibraryID)
|
||||
if (library) setOutputDir(suggestedSTRMOutputDir(root, library))
|
||||
}, [settingsLoaded, outputDirTouched, outputRoot, outputScope, generateLibraryID, libraries])
|
||||
|
||||
const onOutputDirChange = (value: string) => {
|
||||
setOutputDir(value)
|
||||
setOutputRoot(value)
|
||||
setOutputScope('')
|
||||
setOutputDirTouched(true)
|
||||
}
|
||||
|
||||
const onGenerate = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
const trimmedBaseURL = baseURL.trim()
|
||||
if (!generateLibraryID || !trimmedBaseURL) return
|
||||
if (!isHTTPURL(trimmedBaseURL)) {
|
||||
toast.error('域名必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
setGenerating(true)
|
||||
try {
|
||||
const result = await strmAPI.generate({
|
||||
library_id: generateLibraryID,
|
||||
base_url: trimmedBaseURL.replace(/\/+$/, ''),
|
||||
output_dir: outputDir.trim(),
|
||||
overwrite,
|
||||
enabled: autoGenerate,
|
||||
include_local: true,
|
||||
})
|
||||
const nextOutputDir = result.output_dir || outputDir
|
||||
setGenerateResult(result)
|
||||
setOutputDir(nextOutputDir)
|
||||
setOutputRoot(inferSTRMOutputRoot(nextOutputDir, libraries))
|
||||
setOutputScope(generateLibraryID === '*' ? 'all' : 'library')
|
||||
setOutputDirTouched(false)
|
||||
toast.success(`生成完成:新增 ${result.generated} · 更新 ${result.updated} · 跳过 ${result.skipped}`)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '生成失败'))
|
||||
} finally {
|
||||
setGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const saveSTRMSettings = async () => {
|
||||
setSavingSettings(true)
|
||||
try {
|
||||
const effectiveMode: CloudPlaybackMode =
|
||||
strmPlaybackEnabled && !redirectProxyEnabled
|
||||
? 'strm'
|
||||
: !strmPlaybackEnabled && redirectProxyEnabled
|
||||
? 'redirect_proxy'
|
||||
: cloudPlaybackMode
|
||||
await Promise.all([
|
||||
adminAPI.updateSetting('cloud.playback_mode', effectiveMode),
|
||||
adminAPI.updateSetting('cloud.playback_strm_enabled', String(strmPlaybackEnabled)),
|
||||
adminAPI.updateSetting('cloud.playback_redirect_proxy_enabled', String(redirectProxyEnabled)),
|
||||
adminAPI.updateSetting('strm.enabled', String(strmPlaybackEnabled)),
|
||||
adminAPI.updateSetting('strm.auto_generate_enabled', String(autoGenerate)),
|
||||
])
|
||||
setCloudPlaybackMode(effectiveMode)
|
||||
toast.success(
|
||||
strmPlaybackEnabled || redirectProxyEnabled
|
||||
? `播放设置已保存:优先 ${effectiveMode === 'strm' ? 'STRMURL' : '302/反代'}`
|
||||
: '播放设置已保存:云盘第三方播放已关闭',
|
||||
)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '保存 STRM 开关失败'))
|
||||
} finally {
|
||||
setSavingSettings(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onImport = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
const trimmedTitle = title.trim()
|
||||
const trimmedURL = url.trim()
|
||||
if (!libraryID || !trimmedTitle || !trimmedURL) return
|
||||
if (!isHTTPURL(trimmedURL)) {
|
||||
toast.error('URL 必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
setImporting(true)
|
||||
try {
|
||||
await strmAPI.importURL(libraryID, trimmedTitle, trimmedURL)
|
||||
toast.success(`已导入「${trimmedTitle}」`)
|
||||
setTitle('')
|
||||
setURL('')
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '导入失败'))
|
||||
} finally {
|
||||
setImporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const doSearch = async (event?: FormEvent) => {
|
||||
event?.preventDefault()
|
||||
const trimmedQuery = query.trim()
|
||||
if (!trimmedQuery) return
|
||||
setSearching(true)
|
||||
try {
|
||||
const result = await mediaAPI.search(trimmedQuery, 30)
|
||||
setResults(result.items ?? [])
|
||||
} catch {
|
||||
toast.error('搜索失败')
|
||||
} finally {
|
||||
setSearching(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onAttach = async (media: Media) => {
|
||||
const next = (drafts[media.id] ?? '').trim()
|
||||
if (!next) return
|
||||
if (!isHTTPURL(next)) {
|
||||
toast.error('URL 必须以 http:// 或 https:// 开头')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await strmAPI.set(media.id, next)
|
||||
toast.success('已设置 STRM URL')
|
||||
setResults((rows) =>
|
||||
rows.map((item) => (item.id === media.id ? ({ ...item, container: 'strm' } as Media) : item)),
|
||||
)
|
||||
setDrafts((current) => ({ ...current, [media.id]: '' }))
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '设置失败'))
|
||||
}
|
||||
}
|
||||
|
||||
const onDetach = async (media: Media) => {
|
||||
const ok = await confirmAction({
|
||||
title: '清除 STRM URL',
|
||||
message: `清除「${media.title}」的 STRM URL?`,
|
||||
confirmText: '清除',
|
||||
})
|
||||
if (!ok) return
|
||||
|
||||
try {
|
||||
await strmAPI.clear(media.id)
|
||||
toast.success('已清除')
|
||||
setResults((rows) =>
|
||||
rows.map((item) =>
|
||||
item.id === media.id
|
||||
? ({ ...item, container: item.container === 'strm' ? '' : item.container } as Media)
|
||||
: item,
|
||||
),
|
||||
)
|
||||
} catch (err: unknown) {
|
||||
toast.error(apiErrorMessage(err, '清除失败'))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
attach: {
|
||||
doSearch,
|
||||
drafts,
|
||||
onAttach,
|
||||
onDetach,
|
||||
query,
|
||||
results,
|
||||
searching,
|
||||
setDrafts,
|
||||
setQuery,
|
||||
},
|
||||
generate: {
|
||||
autoGenerate,
|
||||
baseURL,
|
||||
cloudPlaybackMode,
|
||||
generateLibraryID,
|
||||
generateResult,
|
||||
generating,
|
||||
onGenerate,
|
||||
outputDir,
|
||||
overwrite,
|
||||
playbackStatus: playbackStatusText(strmPlaybackEnabled, redirectProxyEnabled, cloudPlaybackMode),
|
||||
redirectProxyEnabled,
|
||||
saveSTRMSettings,
|
||||
savingSettings,
|
||||
setAutoGenerate,
|
||||
setBaseURL,
|
||||
setCloudPlaybackMode,
|
||||
setGenerateLibraryID,
|
||||
setOutputDir: onOutputDirChange,
|
||||
setOverwrite,
|
||||
setRedirectProxyEnabled,
|
||||
setStrmPlaybackEnabled,
|
||||
strmPlaybackEnabled,
|
||||
},
|
||||
importForm: {
|
||||
importing,
|
||||
libraryID,
|
||||
onImport,
|
||||
setLibraryID,
|
||||
setTitle,
|
||||
setURL,
|
||||
title,
|
||||
url,
|
||||
},
|
||||
attach,
|
||||
generate,
|
||||
importForm,
|
||||
libraries,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user