fix(scrape): 单集名不再覆盖整剧 original_name 修复合集被拆集 + 媒体库页新增单库修复重刮

- 根因: 单集刮削把 TMDB episode.Name 写入 original_name(应为整剧原名),
  导致同剧每集 original_name 不同, 合集分组键回退到标题时被拆成多集无法合并。
  移除该覆盖, 保留单集 overview/剧照/评分/时长回填(不影响合集键)。
- 新增 RepairAndRescrapeLibrary 服务方法 + POST /admin/libraries/:id/repair-rescrape
  路由 + 媒体库页「修复+重刮本库」按钮, 可按库单独触发修复(回填占位符外部 ID)并重刮,
  既修空 ID 又借重刮覆盖此前被单集名污染的 original_name。
This commit is contained in:
ShukeBta
2026-06-19 15:00:00 +08:00
parent f873997d27
commit 9b4884359b
7 changed files with 105 additions and 6 deletions
+7
View File
@@ -104,4 +104,11 @@ export const toolsAPI = {
api
.post<{ status: string }>('/admin/media/repair-rescrape', {})
.then((r) => r.data),
// repairAndRescrapeLibrary 触发「单库修复+重刮」:只对指定媒体库回填占位符
// 外部 ID 并重刮,不影响其它库。后端异步执行,进度通过 WS "scrape" topic 推送。
repairAndRescrapeLibrary: (libraryID: string) =>
api
.post<{ status: string }>(`/admin/libraries/${libraryID}/repair-rescrape`, {})
.then((r) => r.data),
}
+13
View File
@@ -5,6 +5,7 @@ import toast from 'react-hot-toast'
import { ArrowLeft, Play, Film, Database, FileText, Search, Sparkles, Trash2, Pencil } from 'lucide-react'
import { libraryAPI } from '../api/library'
import { toolsAPI } from '../api/tools'
import { storageAPI, type CloudScanStatus } from '../api/storage_config'
import { api } from '../api/client'
import { recycleAPI } from '../api/recycle'
@@ -32,6 +33,7 @@ export function LibraryPage() {
const [scanning, setScanning] = useState(false)
const [scanProgress, setScanProgress] = useState('')
const [scraping, setScraping] = useState(false)
const [repairing, setRepairing] = useState(false)
const [seriesToolBusy, setSeriesToolBusy] = useState('')
const [manualSeriesScrapeOpen, setManualSeriesScrapeOpen] = useState(false)
const [seriesMetadataEditOpen, setSeriesMetadataEditOpen] = useState(false)
@@ -279,6 +281,16 @@ export function LibraryPage() {
finally { setScraping(false) }
}
const handleRepairRescrape = async () => {
if (repairing) return
setRepairing(true)
try {
await toolsAPI.repairAndRescrapeLibrary(id)
toast.success('本库修复+重刮已加入后台队列,进度可在任务中查看')
} catch { toast.error('修复+重刮启动失败') }
finally { setRepairing(false) }
}
const runSeriesTool = async (key: string, label: string, action: (media: Media) => Promise<unknown>) => {
if (selectedSeriesEpisodes.length === 0) return
setSeriesToolBusy(key)
@@ -427,6 +439,7 @@ export function LibraryPage() {
<div className="flex flex-wrap gap-2">
<button onClick={handleScan} disabled={scanning} className="btn-outline">{scanning ? '扫描中…' : '立即扫描'}</button>
<button onClick={handleScrape} disabled={scraping} className="btn-outline">{scraping ? '刮削中…' : '刮削元数据'}</button>
<button onClick={handleRepairRescrape} disabled={repairing} className="btn-outline" title="回填本库占位符外部 ID 并重刮,修正空 ID / 拆集问题">{repairing ? '修复中…' : '修复+重刮本库'}</button>
</div>
)}
</div>