UNPKG

animepaste

Version:

Paste your favourite anime online

492 lines (478 loc) 18.3 kB
'use strict'; 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;