animepaste
Version:
Paste your favourite anime online
280 lines (274 loc) • 8.81 kB
JavaScript
;
const node_module = require('node:module');
const prompts = require('prompts');
const createDebug = require('debug');
const fastestLevenshtein = require('fastest-levenshtein');
const color = require('@breadc/color');
const dateFns = require('date-fns');
const index = require('./animepaste.7298200f.cjs');
const utils = require('./animepaste.5967bc8e.cjs');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const prompts__default = /*#__PURE__*/_interopDefaultCompat(prompts);
const createDebug__default = /*#__PURE__*/_interopDefaultCompat(createDebug);
const debug = createDebug__default("anime:search");
function getDefaultKeywords(bgm) {
return index.filterDef([
bgm.title,
...Object.values(bgm.titleTranslate).flat(),
bgm.dmhy
]);
}
async function userSearch(anime, option) {
const bgms = await promptSearch(anime, option);
for (const bgm of bgms) {
const keywords = getDefaultKeywords(bgm);
option.beginDate = dateFns.subMonths(new Date(bgm.begin), 1);
await search(bgm, keywords, option);
}
if (option.plan && bgms.length > 0) {
await outputPlan(bgms);
}
}
async function daemonSearch(bgmId, optionKeywords, option = { type: "tv" }) {
const items = await importBgmdata();
let found = false;
for (const bgm of items) {
if (bgmId === bgm.bgmId) {
found = true;
const keywords = optionKeywords ?? getDefaultKeywords(bgm);
option.beginDate = dateFns.subMonths(new Date(bgm.begin), 1);
await search(bgm, keywords, option);
if (option.plan) {
await outputPlan([bgm]);
}
break;
}
}
if (!found) {
const keywords = [...optionKeywords ?? []];
if (option.title && !option.plan) {
await search({ bgmId, titleCN: option.title }, keywords, option);
} else if (!option.title) {
debug(`Fallback to search ${bgmId} on bgm.tv`);
const { BgmClient } = await import('@animepaste/bangumi/bgm');
const client = new BgmClient();
client.setupUserAgent();
const bgm = await client.fetchSubject(bgmId);
keywords.push(bgm.title, bgm.titleCN);
await search(bgm, keywords, option);
if (option.plan) {
await outputPlan([bgm]);
}
}
}
}
async function search(bgm, keywords, option = { type: "tv" }) {
if (option.title) {
keywords = [...keywords];
keywords.push(option.title);
}
const log = option.log ?? true;
if (log) {
index.logger.empty();
index.logger.info(
index.okColor("Refresh ") + index.titleColor(bgm.titleCN) + ` (${utils.bangumiLink(bgm.bgmId)})`
);
}
debug(`Search "${bgm.titleCN}"`);
for (const keyword of keywords) {
debug("- " + keyword);
}
const result = await index.context.magnetStore.search(keywords, {
limit: option.beginDate,
listener: index.IndexListener,
Episode: true
});
for (const resource of result) {
if (resource.title.indexOf("MKV") !== -1)
continue;
if (resource.title.indexOf("HEVC") !== -1)
continue;
if (!resource.Episode) {
await index.context.episodeStore.createEpisode(bgm.bgmId, resource);
}
}
if (option.raw) {
index.printMagnets(result);
} else if (log) {
const episodes = await index.context.episodeStore.listEpisodes(bgm.bgmId);
const map = index.groupBy(episodes, (ep) => ep.fansub);
for (const [key, eps] of map) {
index.logger.tab.info(color.bold(key));
eps.sort((a, b) => a.ep - b.ep);
for (const ep of eps) {
index.logger.tab.tab.info(
`${color.dim(index.formatEP(ep.ep))} ${color.link(
ep.magnet.title,
index.context.magnetStore.idToLink(ep.magnet.id)
)}`
);
}
}
}
}
async function outputPlan(animes) {
const date = new Date(
Math.min(...animes.map((a) => new Date(a.begin).getTime()))
);
index.logger.empty();
index.logger.println(`--- ${dateFns.format( new Date(), "yyyy-MM-dd \u65B0\u756A\u653E\u9001\u8BA1\u5212")} ---`);
index.logger.empty();
index.logger.println(`name: ${dateFns.format( new Date(), "yyyy-MM-dd \u65B0\u756A\u653E\u9001\u8BA1\u5212")}`);
index.logger.empty();
index.logger.println(`date: ${dateFns.format(date, "yyyy-MM-dd HH:mm")}`);
index.logger.empty();
index.logger.println(`state: onair`);
index.logger.empty();
index.logger.println(`sync: true`);
index.logger.empty();
index.logger.println(`onair:`);
for (const anime of animes) {
const episodes = await index.context.episodeStore.listEpisodes(anime.bgmId);
index.logger.tab.println(`- title: ${anime.titleCN}`);
index.logger.tab.println(` bgmId: '${anime.bgmId}'`);
index.logger.tab.println(` fansub:`);
const map = index.groupBy(episodes, (ep) => ep.fansub);
for (const [key] of map) {
index.logger.tab.tab.println(` - ${key}`);
}
index.logger.empty();
}
}
async function promptSearch(anime, option) {
if (anime) {
const bgms = await searchInBgmdata(anime, option);
return await promptBgm(bgms);
} else {
const year = (/* @__PURE__ */ new Date()).getFullYear();
await prompts__default(
[
{
type: option.year ? null : "select",
name: "year",
message: "\u5E74\u4EFD?",
choices: new Array(5).fill(void 0).map((_v, i) => ({
title: String(year - i) + " \u5E74",
value: String(year - i)
})),
initial: 0,
onState({ value }) {
option.year = value;
}
},
{
type: option.month ? null : "select",
name: "month",
message: "\u5B63\u5EA6?",
choices: [
{ title: "1 \u6708", value: "1" },
{ title: "4 \u6708", value: "4" },
{ title: "7 \u6708", value: "7" },
{ title: "10 \u6708", value: "10" }
],
initial: 0,
onState({ value }) {
option.month = value;
}
}
],
{
onCancel: () => {
throw new Error("Operation cancelled");
}
}
);
const items = await importBgmdata(option);
return await promptBgm(items, false);
}
}
async function promptBgm(bgms, enableDate = true) {
const { value: bgm } = await prompts__default({
type: "multiselect",
name: "value",
message: "\u52A8\u753B?",
choices: bgms.map((bgm2) => {
const d = getBgmDate(bgm2);
return {
title: bgm2.titleCN + (enableDate ? ` (${d.year}-${d.month})` : ""),
value: bgm2
};
}),
hint: "- Space to select, Return to submit",
// @ts-ignore
instructions: false
});
return bgm ?? [];
}
async function searchInBgmdata(name, option, length = 5) {
const items = await importBgmdata(option);
const include = [];
const similar = [];
for (const item of items) {
const titles = getDefaultKeywords(item);
let included = false;
let dis = Number.MAX_SAFE_INTEGER;
for (const title of titles) {
const d = fastestLevenshtein.distance(name, title);
if (d === Math.abs(title.length - name.length)) {
included = true;
}
dis = Math.min(dis, d);
}
if (included) {
include.push(item);
if (include.length >= length) {
return include;
}
}
if (similar.length < length) {
similar.push({ item, dis });
} else {
let mxId = 0;
for (let i = 1; i < similar.length; i++) {
if (similar[i].dis > similar[mxId].dis) {
mxId = i;
}
}
if (similar[mxId].dis > dis) {
similar.splice(mxId);
similar.push({ item, dis });
}
}
}
if (include.length > 0) {
return include;
} else {
return similar.map(({ item }) => item);
}
}
async function importBgmdata(option = { type: "tv" }) {
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/animepaste.21394988.cjs', document.baseURI).href)));
const { load } = require$1("@animepaste/bangumi");
const bangumis = load("cli-data.json").bangumis;
debug("Load Bangumi data OK");
return bangumis.filter((bgm) => {
const d = getBgmDate(bgm);
if (option.year && +option.year !== d.year)
return false;
if (option.month && +option.month !== d.month)
return false;
return bgm.type === option.type;
});
}
function getBgmDate(bgm) {
const d = new Date(bgm.begin);
return {
year: d.getFullYear(),
month: d.getMonth() + 1,
date: d.getDate(),
weekday: d.getDay()
};
}
exports.daemonSearch = daemonSearch;
exports.search = search;
exports.userSearch = userSearch;