Files
MeBox/web/src/api/tools.ts
T
soldosluka857 d03f84e78d feat(organize): organize arbitrary source dir (e.g. downloads) with dedup + 洗版
- Add OrganizeDirectory service: walk an arbitrary source directory (download
  dir / NAS direct-read path) and organize video files into the destination,
  without requiring the source to be a registered library.
- Dedup: skip media already present in the destination (matched by scanned DB
  identity title+year[/season+episode], robust to dir case/layout, plus a
  filesystem folder fallback).
- 洗版 (resolution replacement): when the source resolution is higher than the
  existing version, replace the lower-res file (+NFO sidecar +DB row). Prefers
  scanned dimensions, then ffprobe, then filename token; never replaces on
  unknown resolution.
- New endpoints: GET /admin/organize/sources (download/media dir candidates)
  and POST /admin/organize/source.
- UI: ToolsPage adds a '整理来源目录(去重+洗版)' form so operators can pick the
  download dir as the organize source.

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

59 lines
2.0 KiB
TypeScript

import { api } from './client'
// toolsAPI groups admin-only endpoints that don't fit the other domain
// modules: organizing media files into the canonical naming layout, and
// dispatching a test notification through the configured channels.
// OrganizeOverrides are optional single-request overrides for an organize
// action. Empty fields fall back to the system settings.
// source_path = 源目录(待整理文件所在),dest_path = 目的地目录(整理输出到哪里)。
export interface OrganizeOverrides {
source_path?: string
dest_path?: string
transfer_mode?: string
}
// OrganizeSource is a selectable organize source directory (e.g. the download
// directory) surfaced so operators can organize an arbitrary directory and not
// only registered libraries.
export interface OrganizeSource {
label: string
path: string
kind: string
}
export const toolsAPI = {
organizeMedia: (mediaID: string, opts?: OrganizeOverrides) =>
api
.post<{ path: string }>(`/admin/media/${mediaID}/organize`, opts ?? {})
.then((r) => r.data),
organizeLibrary: (libraryID: string, opts?: OrganizeOverrides) =>
api
.post<Record<string, unknown>>(
`/admin/libraries/${libraryID}/organize`,
opts ?? {},
)
.then((r) => r.data),
// organizeSources lists selectable source directories (download/media dir).
organizeSources: () =>
api
.get<{ sources: OrganizeSource[] }>('/admin/organize/sources')
.then((r) => r.data.sources ?? []),
// organizeDirectory organizes an arbitrary source directory (e.g. downloads)
// into the destination with dedup + 洗版 (resolution replacement).
organizeDirectory: (opts: OrganizeOverrides) =>
api
.post<{ organized: number; skipped: number; replaced?: number; errors?: string[] }>(
'/admin/organize/source',
opts,
)
.then((r) => r.data),
notifyTest: (title: string, body: string) =>
api
.post<{ message: string }>('/admin/notify/test', { title, body })
.then((r) => r.data),
}