mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
refactor: split discover detail modal sections
This commit is contained in:
@@ -1,64 +1,30 @@
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { Download, Rss, X } from 'lucide-react'
|
||||
|
||||
import type { DiscoverItem } from '../api/discover'
|
||||
import { imageURL } from '../api/client'
|
||||
import { buildSiteSearchFeedURL, buildSubscriptionAliases, subscriptionsAPI } from '../api/subscriptions'
|
||||
import { buildSubscribeKeyword, discoverItemSource } from './discoverPageModel'
|
||||
import { subscriptionsAPI } from '../api/subscriptions'
|
||||
import { discoverItemSource } from './discoverPageModel'
|
||||
import {
|
||||
DiscoverArtworkPanel,
|
||||
DiscoverModalHeader,
|
||||
DiscoverOverviewPanel,
|
||||
DiscoverSubscriptionRules,
|
||||
} from './DiscoverDetailModalSections'
|
||||
import {
|
||||
apiErrorMessage,
|
||||
buildDiscoverSubscriptionInput,
|
||||
initialDiscoverSubscriptionForm,
|
||||
} from './discoverDetailModalModel'
|
||||
|
||||
export function DiscoverDetailModal({ item, onClose }: { item: DiscoverItem; onClose: () => void }) {
|
||||
const source = discoverItemSource(item)
|
||||
const keyword = item.subscribe_keyword || buildSubscribeKeyword(item)
|
||||
const [form, setForm] = useState({
|
||||
keyword,
|
||||
search_mode: 'keyword',
|
||||
imdb_id: '',
|
||||
media_type: item.media_type || '',
|
||||
resolution: 'best',
|
||||
quality: '',
|
||||
effects: '',
|
||||
release_groups: '',
|
||||
exclude_words: 'cam,ts,tc,枪版',
|
||||
wash_enabled: false,
|
||||
wash_priority: 'balanced',
|
||||
save_path: '',
|
||||
media_category: '',
|
||||
priority: 50,
|
||||
run_now: true,
|
||||
})
|
||||
const [form, setForm] = useState(() => initialDiscoverSubscriptionForm(item))
|
||||
const [busy, setBusy] = useState(false)
|
||||
|
||||
const submit = async () => {
|
||||
const finalKeyword = form.keyword.trim() || keyword
|
||||
const feed = buildSiteSearchFeedURL(finalKeyword, source, buildSubscriptionAliases(item))
|
||||
setBusy(true)
|
||||
try {
|
||||
const sub = await subscriptionsAPI.create({
|
||||
name: `${item.title} 自动订阅`,
|
||||
feed_url: feed,
|
||||
filter: finalKeyword,
|
||||
media_type: form.media_type || undefined,
|
||||
media_category: form.media_category || undefined,
|
||||
save_path: form.save_path || undefined,
|
||||
search_mode: form.search_mode,
|
||||
imdb_id: form.imdb_id || undefined,
|
||||
source,
|
||||
poster_url: item.poster_url || undefined,
|
||||
backdrop_url: item.backdrop_url || undefined,
|
||||
overview: item.overview || undefined,
|
||||
original_name: item.original_name || undefined,
|
||||
year: item.year || undefined,
|
||||
resolution: form.resolution === 'best' ? 'best' : form.resolution,
|
||||
quality: form.quality || undefined,
|
||||
effects: form.effects || undefined,
|
||||
release_groups: form.release_groups || undefined,
|
||||
exclude_words: form.exclude_words || undefined,
|
||||
wash_enabled: form.wash_enabled,
|
||||
wash_priority: form.wash_priority,
|
||||
priority: form.priority,
|
||||
enabled: true,
|
||||
})
|
||||
const sub = await subscriptionsAPI.create(buildDiscoverSubscriptionInput(item, form, source))
|
||||
if (form.run_now) {
|
||||
const run = await subscriptionsAPI.runNow(sub.id)
|
||||
toast.success(run.queued > 0 ? `已订阅并加入 ${run.queued} 个下载` : '已订阅,暂未命中可下载资源')
|
||||
@@ -67,10 +33,7 @@ export function DiscoverDetailModal({ item, onClose }: { item: DiscoverItem; onC
|
||||
}
|
||||
onClose()
|
||||
} catch (err) {
|
||||
const msg =
|
||||
(err as { response?: { data?: { error?: string } } })?.response?.data?.error ??
|
||||
'订阅失败'
|
||||
toast.error(msg)
|
||||
toast.error(apiErrorMessage(err, '订阅失败'))
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
@@ -79,137 +42,17 @@ export function DiscoverDetailModal({ item, onClose }: { item: DiscoverItem; onC
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/45 p-4 backdrop-blur-sm">
|
||||
<div className="max-h-[92vh] w-full max-w-5xl overflow-y-auto rounded-3xl border border-white/60 bg-white p-5 shadow-2xl">
|
||||
<div className="mb-4 flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-brand-500">{source}</p>
|
||||
<h2 className="font-display text-2xl font-bold text-ink-600">{item.title}</h2>
|
||||
<p className="mt-1 text-sm text-sand-500">
|
||||
{[item.media_type, item.year && item.year > 0 ? item.year : '', item.rating ? `★ ${item.rating.toFixed(1)}` : '']
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</p>
|
||||
</div>
|
||||
<button className="rounded-full border border-gray-200 p-2 text-ink-50 hover:bg-gray-50" onClick={onClose}>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<DiscoverModalHeader item={item} source={source} onClose={onClose} />
|
||||
<div className="grid gap-5 lg:grid-cols-[260px_1fr]">
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-hidden rounded-2xl bg-gray-100">
|
||||
{item.poster_url ? (
|
||||
<img src={imageURL(item.poster_url)} alt={item.title} className="aspect-[2/3] w-full object-cover" />
|
||||
) : (
|
||||
<div className="flex aspect-[2/3] items-center justify-center text-sand-500">无海报</div>
|
||||
)}
|
||||
</div>
|
||||
{item.backdrop_url && (
|
||||
<img src={imageURL(item.backdrop_url)} alt="" className="h-24 w-full rounded-2xl object-cover" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DiscoverArtworkPanel item={item} />
|
||||
<div className="space-y-5">
|
||||
<section className="rounded-2xl border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 className="mb-2 font-semibold text-ink-600">简介</h3>
|
||||
<p className="text-sm leading-6 text-ink-100">{item.overview || '当前数据源没有返回简介。'}</p>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-primary-400/20 bg-primary-400/5 p-4">
|
||||
<h3 className="mb-3 flex items-center gap-2 font-semibold text-ink-600">
|
||||
<Rss size={16} />
|
||||
订阅下载规则
|
||||
</h3>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<label className="text-xs text-sand-500 md:col-span-2">
|
||||
搜索关键词
|
||||
<input className="input-base mt-1" value={form.keyword} onChange={(e) => setForm((f) => ({ ...f, keyword: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
搜索方式
|
||||
<select className="input-base mt-1" value={form.search_mode} onChange={(e) => setForm((f) => ({ ...f, search_mode: e.target.value }))}>
|
||||
<option value="keyword">标题关键词</option>
|
||||
<option value="imdb">IMDB ID</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
IMDB ID
|
||||
<input className="input-base mt-1" placeholder="tt1160419" value={form.imdb_id} onChange={(e) => setForm((f) => ({ ...f, imdb_id: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
类型
|
||||
<select className="input-base mt-1" value={form.media_type} onChange={(e) => setForm((f) => ({ ...f, media_type: e.target.value }))}>
|
||||
<option value="">自动识别</option>
|
||||
<option value="movie">电影</option>
|
||||
<option value="tv">电视剧</option>
|
||||
<option value="anime">动漫</option>
|
||||
<option value="variety">综艺</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
分辨率
|
||||
<select className="input-base mt-1" value={form.resolution} onChange={(e) => setForm((f) => ({ ...f, resolution: e.target.value }))}>
|
||||
<option value="best">自动择优</option>
|
||||
<option value="2160p">2160p / 4K</option>
|
||||
<option value="1080p">1080p</option>
|
||||
<option value="720p">720p</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
质量
|
||||
<select className="input-base mt-1" value={form.quality} onChange={(e) => setForm((f) => ({ ...f, quality: e.target.value }))}>
|
||||
<option value="">不限</option>
|
||||
<option value="remux">REMUX</option>
|
||||
<option value="bluray">BluRay</option>
|
||||
<option value="web-dl">WEB-DL</option>
|
||||
<option value="hdtv">HDTV</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
特效 / 音轨
|
||||
<input className="input-base mt-1" placeholder="hdr,dolby-vision,atmos" value={form.effects} onChange={(e) => setForm((f) => ({ ...f, effects: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
洗版优先级
|
||||
<select className="input-base mt-1 disabled:opacity-50" disabled={!form.wash_enabled} value={form.wash_priority} onChange={(e) => setForm((f) => ({ ...f, wash_priority: e.target.value }))}>
|
||||
<option value="balanced">均衡</option>
|
||||
<option value="resolution">分辨率优先</option>
|
||||
<option value="quality">片源质量优先</option>
|
||||
<option value="effects">HDR/DV/Atmos 优先</option>
|
||||
<option value="seeders">做种数优先</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-2 text-xs text-ink-100">
|
||||
<input type="checkbox" checked={form.wash_enabled} onChange={(e) => setForm((f) => ({ ...f, wash_enabled: e.target.checked }))} />
|
||||
启用洗版择优
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
发布组
|
||||
<input className="input-base mt-1" placeholder="如 FRDS,OurTV" value={form.release_groups} onChange={(e) => setForm((f) => ({ ...f, release_groups: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
排除词
|
||||
<input className="input-base mt-1" value={form.exclude_words} onChange={(e) => setForm((f) => ({ ...f, exclude_words: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
分类覆盖
|
||||
<input className="input-base mt-1" placeholder="综艺 / 日番 / 欧美剧" value={form.media_category} onChange={(e) => setForm((f) => ({ ...f, media_category: e.target.value }))} />
|
||||
</label>
|
||||
<label className="text-xs text-sand-500">
|
||||
保存路径覆盖
|
||||
<input className="input-base mt-1" value={form.save_path} onChange={(e) => setForm((f) => ({ ...f, save_path: e.target.value }))} />
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<label className="flex items-center gap-2 text-sm text-ink-100">
|
||||
<input type="checkbox" checked={form.run_now} onChange={(e) => setForm((f) => ({ ...f, run_now: e.target.checked }))} />
|
||||
创建后立即搜索并下载
|
||||
</label>
|
||||
<button disabled={busy} onClick={submit} className="neon-button disabled:opacity-60">
|
||||
<Download size={16} />
|
||||
{busy ? '处理中…' : '创建订阅'}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<DiscoverOverviewPanel overview={item.overview} />
|
||||
<DiscoverSubscriptionRules
|
||||
form={form}
|
||||
busy={busy}
|
||||
onChange={(patch) => setForm((current) => ({ ...current, ...patch }))}
|
||||
onSubmit={submit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Download, Rss, X } from 'lucide-react'
|
||||
|
||||
import type { DiscoverItem } from '../api/discover'
|
||||
import { imageURL } from '../api/client'
|
||||
import { discoverItemMetaText, type DiscoverSubscriptionForm } from './discoverDetailModalModel'
|
||||
|
||||
type FormPatch = Partial<DiscoverSubscriptionForm>
|
||||
|
||||
type FieldOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const searchModeOptions: FieldOption[] = [
|
||||
{ value: 'keyword', label: '标题关键词' },
|
||||
{ value: 'imdb', label: 'IMDB ID' },
|
||||
]
|
||||
|
||||
const mediaTypeOptions: FieldOption[] = [
|
||||
{ value: '', label: '自动识别' },
|
||||
{ value: 'movie', label: '电影' },
|
||||
{ value: 'tv', label: '电视剧' },
|
||||
{ value: 'anime', label: '动漫' },
|
||||
{ value: 'variety', label: '综艺' },
|
||||
]
|
||||
|
||||
const resolutionOptions: FieldOption[] = [
|
||||
{ value: 'best', label: '自动择优' },
|
||||
{ value: '2160p', label: '2160p / 4K' },
|
||||
{ value: '1080p', label: '1080p' },
|
||||
{ value: '720p', label: '720p' },
|
||||
]
|
||||
|
||||
const qualityOptions: FieldOption[] = [
|
||||
{ value: '', label: '不限' },
|
||||
{ value: 'remux', label: 'REMUX' },
|
||||
{ value: 'bluray', label: 'BluRay' },
|
||||
{ value: 'web-dl', label: 'WEB-DL' },
|
||||
{ value: 'hdtv', label: 'HDTV' },
|
||||
]
|
||||
|
||||
const washPriorityOptions: FieldOption[] = [
|
||||
{ value: 'balanced', label: '均衡' },
|
||||
{ value: 'resolution', label: '分辨率优先' },
|
||||
{ value: 'quality', label: '片源质量优先' },
|
||||
{ value: 'effects', label: 'HDR/DV/Atmos 优先' },
|
||||
{ value: 'seeders', label: '做种数优先' },
|
||||
]
|
||||
|
||||
export function DiscoverModalHeader({ item, source, onClose }: { item: DiscoverItem; source: string; onClose: () => void }) {
|
||||
return (
|
||||
<div className="mb-4 flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-brand-500">{source}</p>
|
||||
<h2 className="font-display text-2xl font-bold text-ink-600">{item.title}</h2>
|
||||
<p className="mt-1 text-sm text-sand-500">{discoverItemMetaText(item)}</p>
|
||||
</div>
|
||||
<button className="rounded-full border border-gray-200 p-2 text-ink-50 hover:bg-gray-50" onClick={onClose}>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscoverArtworkPanel({ item }: { item: DiscoverItem }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-hidden rounded-2xl bg-gray-100">
|
||||
{item.poster_url ? (
|
||||
<img src={imageURL(item.poster_url)} alt={item.title} className="aspect-[2/3] w-full object-cover" />
|
||||
) : (
|
||||
<div className="flex aspect-[2/3] items-center justify-center text-sand-500">无海报</div>
|
||||
)}
|
||||
</div>
|
||||
{item.backdrop_url && (
|
||||
<img src={imageURL(item.backdrop_url)} alt="" className="h-24 w-full rounded-2xl object-cover" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscoverOverviewPanel({ overview }: { overview?: string }) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 className="mb-2 font-semibold text-ink-600">简介</h3>
|
||||
<p className="text-sm leading-6 text-ink-100">{overview || '当前数据源没有返回简介。'}</p>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscoverSubscriptionRules({
|
||||
form,
|
||||
busy,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
form: DiscoverSubscriptionForm
|
||||
busy: boolean
|
||||
onChange: (patch: FormPatch) => void
|
||||
onSubmit: () => void
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-primary-400/20 bg-primary-400/5 p-4">
|
||||
<h3 className="mb-3 flex items-center gap-2 font-semibold text-ink-600">
|
||||
<Rss size={16} />
|
||||
订阅下载规则
|
||||
</h3>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<TextField label="搜索关键词" value={form.keyword} onChange={(keyword) => onChange({ keyword })} className="md:col-span-2" />
|
||||
<SelectField label="搜索方式" value={form.search_mode} options={searchModeOptions} onChange={(search_mode) => onChange({ search_mode })} />
|
||||
<TextField label="IMDB ID" value={form.imdb_id} placeholder="tt1160419" onChange={(imdb_id) => onChange({ imdb_id })} />
|
||||
<SelectField label="类型" value={form.media_type} options={mediaTypeOptions} onChange={(media_type) => onChange({ media_type })} />
|
||||
<SelectField label="分辨率" value={form.resolution} options={resolutionOptions} onChange={(resolution) => onChange({ resolution })} />
|
||||
<SelectField label="质量" value={form.quality} options={qualityOptions} onChange={(quality) => onChange({ quality })} />
|
||||
<TextField label="特效 / 音轨" value={form.effects} placeholder="hdr,dolby-vision,atmos" onChange={(effects) => onChange({ effects })} />
|
||||
<SelectField label="洗版优先级" value={form.wash_priority} options={washPriorityOptions} disabled={!form.wash_enabled} onChange={(wash_priority) => onChange({ wash_priority })} />
|
||||
<CheckboxField label="启用洗版择优" checked={form.wash_enabled} onChange={(wash_enabled) => onChange({ wash_enabled })} />
|
||||
<TextField label="发布组" value={form.release_groups} placeholder="如 FRDS,OurTV" onChange={(release_groups) => onChange({ release_groups })} />
|
||||
<TextField label="排除词" value={form.exclude_words} onChange={(exclude_words) => onChange({ exclude_words })} />
|
||||
<TextField label="分类覆盖" value={form.media_category} placeholder="综艺 / 日番 / 欧美剧" onChange={(media_category) => onChange({ media_category })} />
|
||||
<TextField label="保存路径覆盖" value={form.save_path} onChange={(save_path) => onChange({ save_path })} />
|
||||
</div>
|
||||
<DiscoverSubmitBar busy={busy} runNow={form.run_now} onRunNowChange={(run_now) => onChange({ run_now })} onSubmit={onSubmit} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function TextField({ label, value, placeholder, className = '', onChange }: { label: string; value: string; placeholder?: string; className?: string; onChange: (value: string) => void }) {
|
||||
return (
|
||||
<label className={`text-xs text-sand-500 ${className}`}>
|
||||
{label}
|
||||
<input className="input-base mt-1" placeholder={placeholder} value={value} onChange={(event) => onChange(event.target.value)} />
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectField({ label, value, options, disabled, onChange }: { label: string; value: string; options: FieldOption[]; disabled?: boolean; onChange: (value: string) => void }) {
|
||||
return (
|
||||
<label className="text-xs text-sand-500">
|
||||
{label}
|
||||
<select className="input-base mt-1 disabled:opacity-50" disabled={disabled} value={value} onChange={(event) => onChange(event.target.value)}>
|
||||
{options.map((option) => (
|
||||
<option key={option.value || option.label} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
function CheckboxField({ label, checked, onChange }: { label: string; checked: boolean; onChange: (checked: boolean) => void }) {
|
||||
return (
|
||||
<label className="flex items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-2 text-xs text-ink-100">
|
||||
<input type="checkbox" checked={checked} onChange={(event) => onChange(event.target.checked)} />
|
||||
{label}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
function DiscoverSubmitBar({ busy, runNow, onRunNowChange, onSubmit }: { busy: boolean; runNow: boolean; onRunNowChange: (value: boolean) => void; onSubmit: () => void }) {
|
||||
return (
|
||||
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<label className="flex items-center gap-2 text-sm text-ink-100">
|
||||
<input type="checkbox" checked={runNow} onChange={(event) => onRunNowChange(event.target.checked)} />
|
||||
创建后立即搜索并下载
|
||||
</label>
|
||||
<button disabled={busy} onClick={onSubmit} className="neon-button disabled:opacity-60">
|
||||
<Download size={16} />
|
||||
{busy ? '处理中…' : '创建订阅'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { DiscoverItem } from '../api/discover'
|
||||
import { buildSiteSearchFeedURL, buildSubscriptionAliases } from '../api/subscriptions'
|
||||
import { buildSubscribeKeyword } from './discoverPageModel'
|
||||
|
||||
export type DiscoverSubscriptionForm = {
|
||||
keyword: string
|
||||
search_mode: string
|
||||
imdb_id: string
|
||||
media_type: string
|
||||
resolution: string
|
||||
quality: string
|
||||
effects: string
|
||||
release_groups: string
|
||||
exclude_words: string
|
||||
wash_enabled: boolean
|
||||
wash_priority: string
|
||||
save_path: string
|
||||
media_category: string
|
||||
priority: number
|
||||
run_now: boolean
|
||||
}
|
||||
|
||||
export function discoverSubscriptionKeyword(item: DiscoverItem): string {
|
||||
return item.subscribe_keyword || buildSubscribeKeyword(item)
|
||||
}
|
||||
|
||||
export function initialDiscoverSubscriptionForm(item: DiscoverItem): DiscoverSubscriptionForm {
|
||||
return {
|
||||
keyword: discoverSubscriptionKeyword(item),
|
||||
search_mode: 'keyword',
|
||||
imdb_id: '',
|
||||
media_type: item.media_type || '',
|
||||
resolution: 'best',
|
||||
quality: '',
|
||||
effects: '',
|
||||
release_groups: '',
|
||||
exclude_words: 'cam,ts,tc,枪版',
|
||||
wash_enabled: false,
|
||||
wash_priority: 'balanced',
|
||||
save_path: '',
|
||||
media_category: '',
|
||||
priority: 50,
|
||||
run_now: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function discoverItemMetaText(item: DiscoverItem): string {
|
||||
return [
|
||||
item.media_type,
|
||||
item.year && item.year > 0 ? item.year : '',
|
||||
item.rating ? `★ ${item.rating.toFixed(1)}` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')
|
||||
}
|
||||
|
||||
export function buildDiscoverSubscriptionInput(
|
||||
item: DiscoverItem,
|
||||
form: DiscoverSubscriptionForm,
|
||||
source: string,
|
||||
) {
|
||||
const finalKeyword = form.keyword.trim() || discoverSubscriptionKeyword(item)
|
||||
return {
|
||||
name: `${item.title} 自动订阅`,
|
||||
feed_url: buildSiteSearchFeedURL(finalKeyword, source, buildSubscriptionAliases(item)),
|
||||
filter: finalKeyword,
|
||||
media_type: form.media_type || undefined,
|
||||
media_category: form.media_category || undefined,
|
||||
save_path: form.save_path || undefined,
|
||||
search_mode: form.search_mode,
|
||||
imdb_id: form.imdb_id || undefined,
|
||||
source,
|
||||
poster_url: item.poster_url || undefined,
|
||||
backdrop_url: item.backdrop_url || undefined,
|
||||
overview: item.overview || undefined,
|
||||
original_name: item.original_name || undefined,
|
||||
year: item.year || undefined,
|
||||
resolution: form.resolution === 'best' ? 'best' : form.resolution,
|
||||
quality: form.quality || undefined,
|
||||
effects: form.effects || undefined,
|
||||
release_groups: form.release_groups || undefined,
|
||||
exclude_words: form.exclude_words || undefined,
|
||||
wash_enabled: form.wash_enabled,
|
||||
wash_priority: form.wash_priority,
|
||||
priority: form.priority,
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function apiErrorMessage(err: unknown, fallback: string): string {
|
||||
return (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? fallback
|
||||
}
|
||||
Reference in New Issue
Block a user