8f6b2a761b
zod field schemas in settingsSchema.ts replace setSettings' bespoke per-key switch: parse → skip-on-fail → store, side effects (secret encryption, launch-at-startup sync) kept in settings.ts. sanitizeOptions extracted to settingsOptions.ts to break the import cycle; backup import flows through the same schema. Field-by-field parity pinned in test/settingsSchema.test.ts (15 tests); full suite unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.9 KiB
TypeScript
63 lines
2.9 KiB
TypeScript
import {
|
|
AUDIO_FORMATS,
|
|
VIDEO_CONTAINERS,
|
|
VIDEO_CODECS,
|
|
SPONSORBLOCK_CATEGORIES,
|
|
DEFAULT_DOWNLOAD_OPTIONS,
|
|
type DownloadOptions,
|
|
type SponsorBlockCategory,
|
|
type AudioFormat,
|
|
type VideoContainer,
|
|
type VideoCodecPref
|
|
} from '@shared/ipc'
|
|
|
|
// Coerce an untrusted partial into a valid DownloadOptions, falling back to the
|
|
// defaults for any missing/invalid field. Used both to migrate older settings
|
|
// files (which predate downloadOptions) and to validate renderer writes. Lives
|
|
// in its own module (not settings.ts) so the settings field schemas (CC9) can
|
|
// reuse it without an import cycle.
|
|
export function sanitizeOptions(input: unknown): DownloadOptions {
|
|
const o = (input && typeof input === 'object' ? input : {}) as Partial<DownloadOptions>
|
|
const d = DEFAULT_DOWNLOAD_OPTIONS
|
|
const bool = (v: unknown, fallback: boolean): boolean => (typeof v === 'boolean' ? v : fallback)
|
|
const cats = Array.isArray(o.sponsorBlockCategories)
|
|
? (o.sponsorBlockCategories.filter((c) =>
|
|
(SPONSORBLOCK_CATEGORIES as readonly string[]).includes(c)
|
|
) as SponsorBlockCategory[])
|
|
: d.sponsorBlockCategories
|
|
return {
|
|
audioFormat: (AUDIO_FORMATS as readonly string[]).includes(o.audioFormat as string)
|
|
? (o.audioFormat as AudioFormat)
|
|
: d.audioFormat,
|
|
videoContainer: (VIDEO_CONTAINERS as readonly string[]).includes(o.videoContainer as string)
|
|
? (o.videoContainer as VideoContainer)
|
|
: d.videoContainer,
|
|
preferredVideoCodec: (VIDEO_CODECS as readonly string[]).includes(
|
|
o.preferredVideoCodec as string
|
|
)
|
|
? (o.preferredVideoCodec as VideoCodecPref)
|
|
: d.preferredVideoCodec,
|
|
formatSort: typeof o.formatSort === 'string' ? o.formatSort.trim() : d.formatSort,
|
|
embedSubtitles: bool(o.embedSubtitles, d.embedSubtitles),
|
|
subtitleLanguages:
|
|
typeof o.subtitleLanguages === 'string' && o.subtitleLanguages.trim()
|
|
? o.subtitleLanguages.trim()
|
|
: d.subtitleLanguages,
|
|
autoSubtitles: bool(o.autoSubtitles, d.autoSubtitles),
|
|
sponsorBlock: bool(o.sponsorBlock, d.sponsorBlock),
|
|
sponsorBlockMode: o.sponsorBlockMode === 'mark' ? 'mark' : 'remove',
|
|
sponsorBlockCategories: cats,
|
|
embedChapters: bool(o.embedChapters, d.embedChapters),
|
|
splitChapters: bool(o.splitChapters, d.splitChapters),
|
|
embedMetadata: bool(o.embedMetadata, d.embedMetadata),
|
|
metadataTitle: typeof o.metadataTitle === 'string' ? o.metadataTitle : undefined,
|
|
metadataArtist: typeof o.metadataArtist === 'string' ? o.metadataArtist : undefined,
|
|
metadataAlbum: typeof o.metadataAlbum === 'string' ? o.metadataAlbum : undefined,
|
|
embedThumbnail: bool(o.embedThumbnail, d.embedThumbnail),
|
|
cropThumbnail: bool(o.cropThumbnail, d.cropThumbnail),
|
|
writeInfoJson: bool(o.writeInfoJson, d.writeInfoJson),
|
|
writeThumbnailFile: bool(o.writeThumbnailFile, d.writeThumbnailFile),
|
|
writeDescription: bool(o.writeDescription, d.writeDescription)
|
|
}
|
|
}
|