mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-06 13:26:38 +08:00
fix(media): 修复emby电影库混入剧集/特别篇识别/剧集合集 + 新增手动编辑元数据
emby 兼容层(hills/yamby 等客户端): - 电影库不再混入剧集:filterLikelyEpisodicPathsFromMovieQuery 按路径 特征(Season/S0x/Specials/电视剧/日番/国漫等)剔除误入电影库的剧集 - itemPayload 改用 mediaShouldBeEpisode:有季集号且(属剧集库或路径像剧集) 才标记为 Episode,修正电影库里剧集被当成 Movie 特别篇识别: - episode_parser 新增 patSpecialSeason,识别 S0/Season 0/Specials/SP/OVA/ OAD/番外/特别篇/特典 文件夹 → 第0季;seasonFromParents 返回 found 标志, 区分"明确第0季"与"未识别默认第1季" - emby seasonDir 正则同步支持 Specials/SP/OVA/特别篇 等 剧集合集: - groupSeries 抽出 isSeriesCard,卡片链接按是否剧集判定,修复剧集卡片 跳转;每集分离的根因(季集号/分组key)随入库识别修复一并改善 手动编辑元数据(自采集视频无TMDB信息时可自定义封面/标题/简介/分类): - 新增 MediaService.UpdateMetadata + PATCH /media/:id/metadata(需admin) - 前端 MetadataEditDialog 接入 LibraryPage 与 MediaDetailPage 其他:scanner/scraper/nfo/local+cloud_metadata/adult 元数据识别配套改进; .gitignore 忽略本地 .dev-cache/.dev-data 运行产物。
This commit is contained in:
@@ -28,7 +28,7 @@ export function APIConfigsPanel() {
|
||||
<div>
|
||||
<p className="font-display text-lg font-semibold text-ink-600">外部 API 配置</p>
|
||||
<p className="text-xs text-ink-50">
|
||||
TMDb / Bangumi / TheTVDB / Fanart / OpenAI / Douban 密钥管理
|
||||
TMDb / Bangumi / TheTVDB / Fanart / OpenAI / Douban / Adult 密钥与源管理
|
||||
· AES-GCM 加密存储
|
||||
</p>
|
||||
</div>
|
||||
@@ -73,14 +73,17 @@ export function APIConfigsPanel() {
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 font-mono text-xs">
|
||||
{item.has_key ? (
|
||||
{apiConfigConfigured(item) ? (
|
||||
<span className="text-brand-500">{item.masked_key}</span>
|
||||
) : (
|
||||
<span className="text-gray-500">未配置</span>
|
||||
)}
|
||||
{item.provider === 'adult' && apiConfigSourceCount(item) > 0 ? (
|
||||
<span className="text-brand-500">{apiConfigSourceCount(item)} 个源</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{item.has_key ? (
|
||||
{apiConfigConfigured(item) ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-400/10 px-2 py-0.5 text-xs text-emerald-400">
|
||||
已配置
|
||||
</span>
|
||||
@@ -148,14 +151,17 @@ function EditingRow({
|
||||
}) {
|
||||
const [apiKey, setAPIKey] = useState('')
|
||||
const [baseURL, setBaseURL] = useState(item.base_url ?? '')
|
||||
const [extra, setExtra] = useState(item.extra ?? '')
|
||||
const [enabled, setEnabled] = useState(item.enabled)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const isAdult = item.provider === 'adult'
|
||||
|
||||
const submit = async (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
setSaving(true)
|
||||
try {
|
||||
const patch: Record<string, unknown> = { base_url: baseURL, enabled }
|
||||
if (isAdult) patch.extra = extra
|
||||
if (apiKey.trim()) patch.api_key = apiKey.trim()
|
||||
await apiConfigsAPI.update(item.provider, patch)
|
||||
toast.success(`${item.provider} 已保存`)
|
||||
@@ -175,25 +181,38 @@ function EditingRow({
|
||||
<td colSpan={4} className="px-4 py-3">
|
||||
<form onSubmit={submit} className="flex flex-wrap items-end gap-3">
|
||||
<span className="text-sm font-medium text-ink-600">{item.provider}</span>
|
||||
{!isAdult && (
|
||||
<label className="flex-1 text-xs text-ink-50">
|
||||
API Key
|
||||
<input
|
||||
className="input-base mt-1"
|
||||
type="password"
|
||||
placeholder={item.has_key ? '•••••••••••• (留空保留原值)' : '输入密钥'}
|
||||
value={apiKey}
|
||||
onChange={(e) => setAPIKey(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
<label className="flex-1 text-xs text-ink-50">
|
||||
API Key
|
||||
{isAdult ? '主源 URL' : 'Base URL'}
|
||||
<input
|
||||
className="input-base mt-1"
|
||||
type="password"
|
||||
placeholder={item.has_key ? '•••••••••••• (留空保留原值)' : '输入密钥'}
|
||||
value={apiKey}
|
||||
onChange={(e) => setAPIKey(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex-1 text-xs text-ink-50">
|
||||
Base URL
|
||||
<input
|
||||
className="input-base mt-1"
|
||||
placeholder="https://api.themoviedb.org/3"
|
||||
placeholder={isAdult ? 'https://javdb.com' : 'https://api.themoviedb.org/3'}
|
||||
value={baseURL}
|
||||
onChange={(e) => setBaseURL(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
{isAdult && (
|
||||
<label className="min-w-64 flex-1 text-xs text-ink-50">
|
||||
备用源 URL
|
||||
<textarea
|
||||
className="input-base mt-1 min-h-20 resize-y"
|
||||
placeholder={'https://javbus.sbs\nhttps://www.javbus.com'}
|
||||
value={extra}
|
||||
onChange={(e) => setExtra(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
<label className="flex items-center gap-2 text-xs text-ink-50">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -217,3 +236,19 @@ function EditingRow({
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
function apiConfigConfigured(item: APIConfig): boolean {
|
||||
if (item.provider === 'adult') {
|
||||
return Boolean(item.base_url?.trim() || item.extra?.trim())
|
||||
}
|
||||
return item.has_key
|
||||
}
|
||||
|
||||
function apiConfigSourceCount(item: APIConfig): number {
|
||||
if (item.provider !== 'adult') return 0
|
||||
return [item.base_url, item.extra]
|
||||
.join('\n')
|
||||
.split(/[\s,;]+/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean).length
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user