@animepaste/core
Version:
Paste your favourite anime online
239 lines (232 loc) • 6.49 kB
JavaScript
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 };