animepaste
Version:
Paste your favourite anime online
492 lines (478 loc) • 18.3 kB
JavaScript
;
const createDebug = require('debug');
const color = require('@breadc/color');
const node_child_process = require('node:child_process');
const index = require('./shared/animepaste.7298200f.cjs');
const breadc = require('breadc');
const fs = require('fs-extra');
const path = require('node:path');
const node_url = require('node:url');
const prompts = require('prompts');
const dateFns = require('date-fns');
require('node:os');
require('js-yaml');
require('@animepaste/database');
require('lbear');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const createDebug__default = /*#__PURE__*/_interopDefaultCompat(createDebug);
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
const prompts__default = /*#__PURE__*/_interopDefaultCompat(prompts);
const __dirname$1 = path__namespace.join(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('cli.cjs', document.baseURI).href))), "../");
createDebug__default("anime:cli");
function getVersion() {
const pkg = path__namespace.join(__dirname$1, "../package.json");
if (fs__default.existsSync(pkg)) {
return JSON.parse(fs__default.readFileSync(pkg, "utf-8")).version;
} else {
return JSON.parse(
fs__default.readFileSync(path__namespace.join(__dirname$1, "../../package.json"), "utf-8")
).version;
}
}
async function promptConfirm(message, initial = true) {
const { yes } = await prompts__default(
{
type: "confirm",
name: "yes",
message,
initial
},
{
onCancel: () => {
throw new Error("Operation cancelled");
}
}
);
return yes;
}
const app = breadc.breadc("anime", {
version: getVersion(),
description: "Paste your favourite anime online.",
plugins: [
{
onPreCommand: {
"*": async (result) => {
await index.context.init({ force: result.options.force ?? false });
}
}
}
]
}).option("-f, --force", "Enable force mode and prefer not using cache");
function setup$3() {
app.command("watch", "Watch anime resources update").option("-i, --interval <duration>", "Damon interval in minutes", {
default: "10",
cast: (t) => +t
}).option("-o, --once", "Just do an immediate update").option("--index", "Enable index resource", { default: true }).option("--upload", "Enable upload videos to OSS", { default: true }).option("--sync", "Enable sync onair with the server", { default: true }).action(async (option) => {
const { startDaemon } = await import('./chunks/index2.cjs');
await startDaemon(option);
});
app.command("plan onair", "Preview onair plan").action(async () => {
const { Plan } = await import('./chunks/index2.cjs');
const plan = await Plan.create();
plan.printOnair();
});
app.command("plan magnet [anime]", "Refresh magnet list").action(async (anime) => {
const { createDaemon } = await import('./chunks/index2.cjs');
index.logger.config.level = false;
const daemon = createDaemon();
await daemon.initPlan({ log: false });
await daemon.initClient();
await daemon.refreshEpisode({
filter: anime ? (o) => o.bgmId === anime || o.title.indexOf(anime) !== -1 : void 0
});
});
app.command("plan download <anime>", "Download remote videos from OSS").alias("plan down").option("--id", "Use bgmId instead of name").action(async (name, option) => {
const { Plan } = await import('./chunks/index2.cjs');
const { download } = await import('./chunks/index3.cjs');
const { initClient } = await import('./chunks/index.cjs').then(function (n) { return n.index; });
const { bangumiLink } = await import('./chunks/index4.cjs');
if (/^\d+$/.test(name)) {
option.id = true;
}
const client = await initClient();
const plans = await Plan.create();
const findFn = (o) => {
if (option.id) {
return o.bgmId === name;
} else {
return o.title.indexOf(name) !== -1;
}
};
const plan = plans.onairs().find(findFn);
const onair = client.onair.find(findFn);
if (onair && plan) {
const episodes = await index.context.episodeStore.listEpisodes(onair.bgmId);
const localRoot = await index.context.makeLocalAnimeRoot(onair.title);
const tasks = index.filterDef(
onair.episodes.map((onairEp) => {
if ("storage" in onairEp) {
const ep = episodes.find(
(ep2) => ep2.magnet.id === onairEp.storage.source.magnetId
);
if (ep) {
return {
filepath: path__namespace.join(localRoot, index.formatEpisodeName(plan, ep)),
url: onairEp.playURL,
ep
};
} else {
return void 0;
}
} else {
return void 0;
}
})
);
index.logger.println(
`${index.okColor("Download")} ${index.titleColor(onair.title)} (${bangumiLink(
onair.bgmId
)})`
);
for (const task of tasks) {
index.logger.println(
` ${color.dim(index.formatEP(task.ep.ep))} ${color.link(
path__namespace.basename(task.filepath),
task.url
)}`
);
}
if (await promptConfirm(
`Are you sure to download these ${tasks.length} videos`
)) {
await download(...tasks);
index.logger.println(
`${index.okColor("Download")} ${index.titleColor(onair.title)} ${index.okColor(
"OK"
)} (${bangumiLink(onair.bgmId)})`
);
}
}
});
app.command("search [anime]", "Search Bangumi resources").option("--type <type>", {
default: "tv",
cast(t) {
if (t && ["tv", "web", "movie", "ova"].includes(t)) {
return t;
} else {
return "tv";
}
}
}).option("--raw", "Print raw magnets").option("--index", "Index magnet database").option("--year <year>").option("--month <month>").option("-p, --plan", "Output plan.yaml").action(async (anime, option) => {
const { userSearch } = await import('./chunks/index4.cjs');
if (option.index) {
await index.context.magnetStore.index({ listener: index.IndexListener });
}
index.logger.config.level = false;
await userSearch(anime, option);
});
app.command("fetch <id> [...keywords]", "Fetch resources using Bangumi ID").option("--raw", "Print raw magnets").option("--index", "Index magnet database").option("-p, --plan", "Output plan.yaml").action(async (id, keywords, option) => {
const { daemonSearch } = await import('./chunks/index4.cjs');
if (option.index) {
await index.context.magnetStore.index({ listener: index.IndexListener });
}
const { BgmClient } = await import('@animepaste/bangumi/bgm');
const { getBgmLink } = await import('@animepaste/bangumi/utils');
const client = new BgmClient();
client.setupUserAgent();
const bgm = await client.fetchSubject(id);
{
index.logger.println(
`${color.bold("Title")} ${color.link(bgm.titleCN, getBgmLink(bgm.bgmId))}`
);
index.logger.println(`${color.bold("Begin")} ${bgm.begin}`);
}
index.logger.config.level = false;
await daemonSearch(id, [bgm.title, bgm.titleCN, ...keywords], {
...option,
title: bgm.titleCN,
type: "tv"
});
});
}
function setup$2() {
app.command("user create", "Create a new token").option("--comment <comment>", "Comment of the new token").option("--type <type>", "One of admin or user").action(async (option) => {
const { RemoteSyncClient: UserClient } = await import('./chunks/index.cjs').then(function (n) { return n.index; });
const client = await UserClient.create();
const token = await client.createToken({
comment: option.comment,
type: option.type === "admin" ? "admin" : "user"
});
if (token) {
index.logger.println(`${color.green(`\u2713 Create token OK`)}`);
index.logger.tab.println(`${color.dim("Token")} ${token.token}`);
index.logger.tab.println(`${color.dim("Type")} ${token.type}`);
index.logger.tab.println(
`${color.dim("Comment")} ${token.comment ? token.comment : "(Empty)"}`
);
} else {
index.logger.println(`${color.red(`\u2717 Create token fail`)}`);
}
});
app.command("user list", "List user tokens").alias("user ls").action(async () => {
const { RemoteSyncClient: UserClient } = await import('./chunks/index.cjs').then(function (n) { return n.index; });
const client = await UserClient.create();
const tokens = await client.listToken();
if (tokens.length > 0) {
index.logger.println(`${color.green(`\u2713 There are ${tokens.length} tokens`)}`);
for (const token of tokens) {
const comment = token.type === "visitor" && token.access?.length ? color.dim(`(IP: ${token.access[0].ip})`) : !!token.comment ? color.dim(`(Comment: ${token.comment})`) : "";
index.logger.println(
`${index.DOT} ${color.lightBlue(token.type)} ${token.token} ${comment}`
);
}
}
});
app.command("user remove [token]", "Remove user tokens").option("--visitor", "Clear all the visitor tokens").action(async (token, option) => {
const { RemoteSyncClient: UserClient } = await import('./chunks/index.cjs').then(function (n) { return n.index; });
const client = await UserClient.create();
if (option.visitor) {
const tokens = await client.removeVisitors();
if (tokens !== void 0) {
index.logger.println(
`${color.green(`\u2713 Remove ${tokens.length} visitor tokens`)}`
);
if (tokens.length > 0) {
for (const token2 of tokens) {
index.logger.println(`${index.DOT} ${token2}`);
}
}
} else {
index.logger.println(`${color.red(`\u2717 Remove visitor tokens fail`)}`);
}
} else if (token) {
const ok = await client.removeToken(token);
if (ok) {
index.logger.println(`${color.green(`\u2713 Remove ${token}`)}`);
} else {
index.logger.println(`${color.red(`\u2717 Remove ${token} fail`)}`);
}
}
});
}
function setup$1() {
app.command("store list [name]", "List all uploaded video info").alias("store ls").action(async (name) => {
const { useStore } = await import('./chunks/index3.cjs');
const store = await useStore("ali")();
const videos = await store.listLocalVideos();
videos.sort((a, b) => a.title.localeCompare(b.title));
const filtered = videos.filter(
(v) => !name || v.title.indexOf(name) !== -1
);
index.logger.println(
color.lightGreen(
`There are ${color.bold(filtered.length)} videos uploaded at ${store.platform}`
)
);
printVideoInfoList(filtered);
});
app.command("store info <id>", "Print video info on OSS").action(async (id) => {
const { useStore } = await import('./chunks/index3.cjs');
const store = await useStore("ali")();
const info = await store.fetchVideoInfo(id);
if (info) {
printVideoInfo(info);
} else {
index.logger.println(`${color.red(`\u2717 video "${id}" not found`)}`);
}
});
app.command("store put <file>", "Upload video to OSS").action(async (filename) => {
const { useStore } = await import('./chunks/index3.cjs');
const store = await useStore("ali")();
try {
const info = await store.upload(path__namespace.resolve(filename));
if (info) {
printVideoInfo(info);
} else {
throw new Error();
}
} catch (error) {
index.logger.empty();
index.logger.println(`${color.red("\u2717 Fail")} uploading ${filename}`);
}
});
app.command("store remove [...ids]", "Remove video info on OSS").alias("store rm").option("-y, --yes", "Disable prompt").option("--local", "Remove local videos").action(async (ids, option) => {
const { useStore } = await import('./chunks/index3.cjs');
const store = await useStore("ali")();
const removeVideo = async (id, info) => {
index.logger.empty();
if (info) {
printVideoInfo(info);
await store.deleteVideo(info.videoId);
index.logger.println(`${color.green(`\u2713 Delete ${info.videoId}`)}`);
} else {
index.logger.println(`${color.red(`\u2717 Video ${id} is not found`)}`);
}
if (option.local && info?.source.directory) {
const filepath = index.context.decodePath(
info?.source.directory,
info.title
);
await fs__default.remove(filepath);
index.logger.println(`${color.green(`\u2713 Delete ${filepath}`)}`);
}
};
if (ids.length > 0) {
for (const id of ids) {
const info = await store.fetchVideoInfo(id);
await removeVideo(id, info);
}
} else {
index.logger.println(color.lightBlue(" Init admin client"));
const { initClient } = await import('./chunks/index.cjs').then(function (n) { return n.index; });
const client = await initClient();
const onairs = client.onair.flatMap((o) => o.episodes);
const videoIds = /* @__PURE__ */ new Set();
for (const onair of onairs) {
if ("storage" in onair) {
if (onair.storage.type === store.platform) {
videoIds.add(onair.storage.videoId);
}
}
}
const videos = (await store.listLocalVideos()).filter(
(v) => !videoIds.has(v.videoId)
);
if (videos.length > 0) {
index.logger.println(
color.lightRed(`\u2713 There are ${color.bold(videos.length)} videos to be removed`)
);
printVideoInfoList(
videos.sort((lhs, rhs) => lhs.title.localeCompare(rhs.title))
);
if (option.yes || await promptConfirm(
`Are you sure to remove these ${videos.length} videos`
)) {
for (const video of videos) {
await removeVideo(video.videoId, video);
}
}
} else {
index.logger.println(color.lightGreen(`\u2713 There are no videos to be removed`));
}
}
});
app.command("video info <file>", "Check video info").action(async (file) => {
const { getVideoInfo } = await import('./chunks/video.cjs');
const info = await getVideoInfo(file);
console.log(JSON.stringify(info, null, 2));
});
function printVideoInfoList(videos) {
const titles = [];
const ids = [];
for (const info of videos) {
titles.push(`${info.title}`);
ids.push(`(${color.link(info.videoId, info.playUrl[0])})`);
}
const padded = index.padRight(titles);
for (let i = 0; i < padded.length; i++) {
index.logger.println(`${index.DOT} ${padded[i]} ${ids[i]}`);
}
}
function printVideoInfo(videoInfo) {
index.logger.println(`${color.bold("Platform")} ${videoInfo.platform}`);
index.logger.println(`${color.bold("VideoId")} ${videoInfo.videoId}`);
index.logger.println(`${color.bold("Title")} ${videoInfo.title}`);
index.logger.println(
`${color.bold("Created at")} ${dateFns.format(
new Date(videoInfo.createdAt),
"yyyy-MM-dd HH:mm:ss"
)}`
);
if (videoInfo.playUrl.length === 1) {
index.logger.println(`${color.bold("Play URL")} ${videoInfo.playUrl[0]}`);
} else {
index.logger.println(`${color.bold("Play URL")}`);
for (const url of videoInfo.playUrl) {
index.logger.tab.println(`${url}`);
}
}
}
}
const NUM_RE = /^[pP]?(\d+)$/;
const PAGE_SIZE = 80;
function setup() {
app.command("magnet index", "Index magnet database").alias("index").option("--limit <date>", "Stop at this date").option("--page <page>", "Start indexing at this page", {
default: "1",
cast: (t) => +t
}).action(async (option) => {
index.logger.config.level = false;
await index.context.magnetStore.index({
limit: option.limit ? new Date(option.limit) : void 0,
startPage: option.page,
earlyStop: !option.force,
listener: index.IndexListener
});
});
app.command("magnet list <keyword/page>", "List magnet resource").alias("magnet ls").action(async (keyword) => {
const match = NUM_RE.exec(keyword);
const magnets = match ? await index.context.magnetStore.list({
skip: (+match[1] - 1) * PAGE_SIZE,
take: PAGE_SIZE
}) : await index.context.magnetStore.search(keyword);
magnets.sort((a, b) => a.title.localeCompare(b.title));
for (const item of magnets) {
index.logger.println(
`${index.DOT} ${color.link(item.title, index.context.magnetStore.idToLink(item.id))}`
);
}
});
}
app.command("space", "Open AnimePaste space directory and run script on it").option("--editor", "Open AnimePaste space editor").action(async (option) => {
const cmd = option["--"];
if (cmd.length > 0) {
node_child_process.execSync(cmd.join(" "), {
cwd: index.context.root,
env: process.env,
stdio: ["inherit", "inherit", "inherit"]
});
} else {
console.log(index.context.root);
if (option.editor) {
node_child_process.execSync(`code "${index.context.root}"`, {
env: process.env,
stdio: ["pipe", "pipe", "pipe"],
windowsHide: true
});
}
}
});
setup$3();
setup$2();
setup$1();
setup();
const debug = createDebug__default("anime:cli");
async function bootstrap() {
const handle = (error) => {
if (error instanceof Error) {
console.error(color.lightRed(" Error ") + error.message);
} else {
console.error(error);
}
debug(error);
};
process.setMaxListeners(128);
process.on("unhandledRejection", (error) => {
debug(error);
});
try {
await app.run(process.argv.slice(2));
process.exit(0);
} catch (error) {
handle(error);
process.exit(1);
}
}
bootstrap();
exports.bootstrap = bootstrap;