feat(web,library): dandanplay file identification entry in manual-match dialog

This commit is contained in:
noelorin 2026-09-27 12:27:51 +08:00
parent 89b090a47b
commit e3c275befa
3 changed files with 50 additions and 8 deletions

View File

@ -1008,6 +1008,21 @@ function LibraryInner(props: { locale: Locale }) {
void utils.media.librarySeries.invalidate(); void utils.media.librarySeries.invalidate();
}, },
}); });
const dandanMatch = trpc.media.scrapeDandanMatch.useMutation({
onSuccess: (res) => {
if (res.matched) {
const best = res.candidates[0];
const title = best?.animeTitle ?? best?.episodeTitle ?? "";
toast.success(t(locale, "library.dandanMatchOk", { title }));
refreshAll();
} else {
toast.error(res.message ?? t(locale, "library.dandanMatchNone"));
}
},
onError: () => {
toast.error(t(locale, "library.dandanMatchFail", { message: "" }));
},
});
const bindWithEp = (bangumiId: number) => { const bindWithEp = (bangumiId: number) => {
if (!matchId) return; if (!matchId) return;
@ -1190,7 +1205,19 @@ function LibraryInner(props: { locale: Locale }) {
{matchId && ( {matchId && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="w-full max-w-lg rounded-lg border border-border bg-background p-4 space-y-3"> <div className="w-full max-w-lg rounded-lg border border-border bg-background p-4 space-y-3">
<div className="flex items-center justify-between gap-2">
<h2 className="font-medium">{t(locale, "library.manualTitle")}</h2> <h2 className="font-medium">{t(locale, "library.manualTitle")}</h2>
<Button
size="sm"
variant="secondary"
disabled={dandanMatch.isPending}
onClick={() =>
matchId && dandanMatch.mutate({ mediaItemId: matchId })
}
>
{t(locale, "library.dandanMatch")}
</Button>
</div>
<Input <Input
placeholder={t(locale, "library.searchWork")} placeholder={t(locale, "library.searchWork")}
value={searchQ} value={searchQ}

View File

@ -69,6 +69,10 @@ export const en: Record<MessageKey, string> = {
"library.reScrape": "Re-scrape", "library.reScrape": "Re-scrape",
"library.manualTitle": "Manual match", "library.manualTitle": "Manual match",
"library.candidates": "Candidates (click to bind)", "library.candidates": "Candidates (click to bind)",
"library.dandanMatch": "Identify via dandanplay",
"library.dandanMatchOk": "Matched: {title}",
"library.dandanMatchNone": "dandanplay could not identify this file",
"library.dandanMatchFail": "Match failed: {message}",
"browse.batchScrape": "Batch scrape folder", "browse.batchScrape": "Batch scrape folder",
"browse.batchBind": "Bind folder as title", "browse.batchBind": "Bind folder as title",
"browse.batchDone": "{message}", "browse.batchDone": "{message}",
@ -220,7 +224,8 @@ export const en: Record<MessageKey, string> = {
"watch.clickToSeek": "Click to seek", "watch.clickToSeek": "Click to seek",
"watch.fileMatch": "Match by file", "watch.fileMatch": "Match by file",
"watch.manualPick": "Pick danmaku source", "watch.manualPick": "Pick danmaku source",
"watch.manualPickHint": "Steps: search title → pick episode → fetch that episode (DanDanPlay open network)", "watch.manualPickHint":
"Steps: search title → pick episode → fetch that episode (DanDanPlay open network)",
"watch.sourceSearch": "Search", "watch.sourceSearch": "Search",
"watch.sourceSearchPlaceholder": "Search by title keyword", "watch.sourceSearchPlaceholder": "Search by title keyword",
"watch.sourceBackToAnime": "Back to titles", "watch.sourceBackToAnime": "Back to titles",
@ -322,7 +327,8 @@ export const en: Record<MessageKey, string> = {
"settings.adminSection": "Open danmaku network", "settings.adminSection": "Open danmaku network",
"settings.adminOnly": "Admin only", "settings.adminOnly": "Admin only",
"settings.openNetworkHint": "Configure DanDanPlay open network AppId / AppSecret for online danmaku matching and fetch. Admin only.", "settings.openNetworkHint":
"Configure DanDanPlay open network AppId / AppSecret for online danmaku matching and fetch. Admin only.",
"settings.openAppId": "AppId", "settings.openAppId": "AppId",
"settings.openAppSecret": "AppSecret", "settings.openAppSecret": "AppSecret",
"settings.secretPlainHint": "Secret is stored in plaintext and visible to admins only.", "settings.secretPlainHint": "Secret is stored in plaintext and visible to admins only.",
@ -332,7 +338,9 @@ export const en: Record<MessageKey, string> = {
"settings.openSaveFailed": "Save failed", "settings.openSaveFailed": "Save failed",
"settings.save": "Save", "settings.save": "Save",
"settings.obtainCreds": "Get credentials", "settings.obtainCreds": "Get credentials",
"settings.obtainCredsHint": "AppId/AppSecret are issued by the DanDanPlay DevCenter (create an app and pass review). They cannot be obtained by signing in with a normal account.", "settings.obtainCredsHint":
"AppId/AppSecret are issued by the DanDanPlay DevCenter (create an app and pass review). They cannot be obtained by signing in with a normal account.",
"settings.openDevCenter": "Open DevCenter", "settings.openDevCenter": "Open DevCenter",
"settings.obtainSteps": "Steps: sign in / register at DevCenter → create an app → wait for review → copy AppId and AppSecret from the app detail", "settings.obtainSteps":
"Steps: sign in / register at DevCenter → create an app → wait for review → copy AppId and AppSecret from the app detail",
}; };

View File

@ -66,6 +66,10 @@ export const zhCN = {
"library.reScrape": "重新刮削", "library.reScrape": "重新刮削",
"library.manualTitle": "手动指定", "library.manualTitle": "手动指定",
"library.candidates": "候选作品(点选绑定)", "library.candidates": "候选作品(点选绑定)",
"library.dandanMatch": "弹弹play 文件识别",
"library.dandanMatchOk": "识别成功:{title}",
"library.dandanMatchNone": "弹弹play 未识别到该文件",
"library.dandanMatchFail": "识别失败:{message}",
"browse.batchScrape": "批量刮削此夹", "browse.batchScrape": "批量刮削此夹",
"browse.batchBind": "指定此夹为作品", "browse.batchBind": "指定此夹为作品",
"browse.batchDone": "{message}", "browse.batchDone": "{message}",
@ -312,7 +316,8 @@ export const zhCN = {
"error.internal.desc": "服务器处理请求时发生意外。请稍后重试;若持续出现请反馈路径与时间。", "error.internal.desc": "服务器处理请求时发生意外。请稍后重试;若持续出现请反馈路径与时间。",
"settings.adminSection": "开放弹幕网络", "settings.adminSection": "开放弹幕网络",
"settings.adminOnly": "仅管理员", "settings.adminOnly": "仅管理员",
"settings.openNetworkHint": "配置弹弹play 开放网络 AppId / AppSecret,用于在线弹幕匹配与拉取。仅管理员可见可改。", "settings.openNetworkHint":
"配置弹弹play 开放网络 AppId / AppSecret,用于在线弹幕匹配与拉取。仅管理员可见可改。",
"settings.openAppId": "AppId", "settings.openAppId": "AppId",
"settings.openAppSecret": "AppSecret", "settings.openAppSecret": "AppSecret",
"settings.secretPlainHint": "Secret 以明文保存,仅管理员可查看与修改。", "settings.secretPlainHint": "Secret 以明文保存,仅管理员可查看与修改。",
@ -322,9 +327,11 @@ export const zhCN = {
"settings.openSaveFailed": "保存失败", "settings.openSaveFailed": "保存失败",
"settings.save": "保存", "settings.save": "保存",
"settings.obtainCreds": "获取凭证", "settings.obtainCreds": "获取凭证",
"settings.obtainCredsHint": "AppId/AppSecret 由弹弹play 开发者中心发放(创建应用并通过审核),无法用普通账号登录自动获取。", "settings.obtainCredsHint":
"AppId/AppSecret 由弹弹play 开发者中心发放(创建应用并通过审核),无法用普通账号登录自动获取。",
"settings.openDevCenter": "打开开发者中心", "settings.openDevCenter": "打开开发者中心",
"settings.obtainSteps": "步骤:登录/注册开发者中心 → 创建应用 → 等待审核 → 在应用详情复制 AppId 与 AppSecret", "settings.obtainSteps":
"步骤:登录/注册开发者中心 → 创建应用 → 等待审核 → 在应用详情复制 AppId 与 AppSecret",
} as const; } as const;
export type MessageKey = keyof typeof zhCN; export type MessageKey = keyof typeof zhCN;