UNPKG

@animepaste/core

Version:

Paste your favourite anime online

239 lines (232 loc) 6.49 kB
import fs from 'node:fs'; import { parse, stringify } from 'yaml'; import { globby } from 'globby'; import path from 'node:path'; import { createDefu } from 'defu'; function formatStringArray(arr) { if (arr !== void 0 && arr !== null) { if (Array.isArray(arr)) { return arr; } else { return [arr]; } } return []; } async function loadPlan(patterns) { const files = await globby(patterns); const plans = await Promise.all( files.map(async (file) => { const content = await fs.promises.readFile(file, "utf-8"); const plan = parse(content); const state = plan.state === "onair" || plan.state === "finish" ? plan.state : "onair"; const date = new Date(plan.date); return { ...plan, name: plan.name ?? "unknown", date, state, onair: formatStringArray(plan.onair).map((o) => { const title = String(o.title); const oState = o.state === "onair" || o.state === "finish" ? o.state : state; const type = ["\u756A\u5267", "\u7535\u5F71", "OVA"].includes(o.type) ? o.type : "\u756A\u5267"; return { ...o, title, bgmId: String(o.bgmId), type, state: oState, season: o.season ? +o.season : 1, date: o.date ? new Date(o.date) : date, keywords: formatKeywordsArray(title, o.keywords) }; }) }; }) ); return plans; } function formatKeywordsArray(title, keywords) { if (keywords !== void 0 && keywords !== null) { if (typeof keywords === "string") { if (!keywords.startsWith("!")) { return { include: [[title, keywords]], exclude: [] }; } else { return { include: [[title]], exclude: [keywords.slice(1)] }; } } else if (Array.isArray(keywords)) { const include = []; const exclude = []; for (const keyword of keywords) { if (typeof keyword === "string") { if (!keyword.startsWith("!")) { include.push([keyword]); } else { exclude.push(keyword.slice(1)); } } else if (Array.isArray(keyword)) { include.push(keyword); } } return { include, exclude }; } } return { include: [[title]], exclude: [] }; } class AnimeSystemError extends Error { constructor(detail) { super(); this.detail = detail; } } const defu = createDefu((obj, key, value) => { if (key === "include" && Array.isArray(obj[key]) && Array.isArray(value)) { obj[key] = [.../* @__PURE__ */ new Set([...value, ...obj[key]])]; return true; } }); const configFilename = `./anime.yaml`; const DefaultStorageDirectory = `./anime`; const DefaultAnimeFormat = "{title} ({yyyy}-{mm})"; const DefaultEpisodeFormat = "[{fansub}] {title} - E{ep}.{extension}"; async function loadSpace(_root, importPlugin) { const root = path.resolve(_root); const configPath = path.join(root, configFilename); if (fs.existsSync(root) && fs.existsSync(configPath)) { const configContent = fs.readFileSync(configPath, "utf-8"); const config = parse(configContent); const storageDirectory = config.storage ?? DefaultStorageDirectory; const plans = formatStringArray(config.plans).map( (p) => path.join(root, p) ); const space = defu( { root, storage: path.resolve(root, storageDirectory), preference: config.preference, plans, plugins: config.plugins }, { preference: { format: { anime: DefaultAnimeFormat, episode: DefaultEpisodeFormat }, extension: { include: ["mp4"], exclude: [] }, keyword: { order: {}, exclude: [] }, fansub: { order: [], exclude: [] } }, plans: [], plugins: [] } ); await validateSpace(space); return load(space); } else { const space = await makeNewSpace(root); return load(space); } async function load(space) { let plans = void 0; const resolved = { ...space, async plans() { if (plans !== void 0) { return plans; } else { return plans = await loadPlan(space.plans); } }, plugins: importPlugin ? (await Promise.all(space.plugins.map((p) => importPlugin(p)))).filter(Boolean) : [] }; for (const plugin of resolved.plugins) { await plugin.prepare?.(resolved); } return resolved; } } async function validateSpace(space) { try { fs.accessSync(space.root, fs.constants.R_OK | fs.constants.W_OK); } catch { throw new AnimeSystemError(`Can not access AnimePaste space directory`); } try { fs.accessSync(space.storage, fs.constants.R_OK | fs.constants.W_OK); } catch { throw new AnimeSystemError(`Can not access local anime storage directory`); } return true; } async function makeNewSpace(root) { const space = { root, storage: path.join(root, DefaultStorageDirectory), preference: { format: { anime: DefaultAnimeFormat, episode: DefaultEpisodeFormat }, extension: { include: ["mp4", "mkv"], exclude: [] }, keyword: { order: { format: ["mp4", "mkv"], language: ["\u7B80", "\u7E41"], resolution: ["1080", "720"] }, exclude: [] }, fansub: { order: [], exclude: [] } }, plans: ["./plans/*.yaml"], plugins: [ { name: "animegarden" }, { name: "download", directory: "./download" } ] }; await fs.promises.mkdir(space.root, { recursive: true }).catch(() => { }); await Promise.all([ fs.promises.mkdir(space.storage, { recursive: true }).catch(() => { }), fs.promises.mkdir(path.join(space.root, "./plans"), { recursive: true }).catch(() => { }), fs.promises.writeFile( path.join(space.root, configFilename), stringify({ ...space, root: void 0, storage: DefaultStorageDirectory }), "utf-8" ) ]); return space; } async function createAnimeSystem(space) { const system = { space, async refresh() { }, async introspect() { } }; return system; } export { AnimeSystemError, createAnimeSystem, formatStringArray, loadPlan, loadSpace };