hexo-bilibili-bangumi
Version:
204 lines • 5.85 kB
JavaScript
const fs = require('hexo-fs');
const path = require('path');
const axios = require('axios');
const hexoLog = require('hexo-log');
const ANILIST_API = 'https://graphql.anilist.co';
const CACHE_DIR = '_data';
const CACHE_FILE = 'bangumis.json';
const EXTRA_CACHE_FILE = 'extra_bangumis.json';
const USER_AGENT = 'HCLonely/hexo-bilibili-bangumi';
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
const STATUS_MAP = {
PLANNING: 'wantWatch',
CURRENT: 'watching',
REPEATING: 'watching',
COMPLETED: 'watched'
};
const log = typeof hexoLog.default === 'function' ? hexoLog.default({
debug: false,
silent: false
}) : hexoLog({
debug: false,
silent: false
});
const MEDIA_LIST_QUERY = `
query($userId:Int,$userName:String,$type:MediaType){
MediaListCollection(userId:$userId,userName:$userName,type:$type){
lists{
entries{
...mediaListEntry
}
}
}
}
fragment mediaListEntry on MediaList{
id
mediaId
status
score
progress
notes
media{
id
title{
userPreferred
romaji
english
native
}
coverImage{
large
}
status(version:2)
episodes
averageScore
popularity
countryOfOrigin
genres
description(asHtml: false)
}
}
`;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const fetchWithRetry = async (postData, retries = MAX_RETRIES) => {
try {
const response = await axios.post(ANILIST_API, postData, {
headers: {
'User-Agent': USER_AGENT
}
});
return response;
} catch (error) {
if (retries > 0) {
await delay(RETRY_DELAY);
return fetchWithRetry(postData, retries - 1);
}
throw error;
}
};
const formatBangumiData = (bangumi, coverMirror) => ({
title: bangumi?.media?.title?.userPreferred || bangumi?.media?.title?.native || bangumi?.media?.title?.english || bangumi?.media?.title?.romaji,
type: bangumi?.media?.genres?.[0] || '-',
cover: coverMirror + bangumi?.media?.coverImage?.large,
totalCount: bangumi?.media?.episodes,
id: bangumi?.media?.id,
score: bangumi?.media?.averageScore ?? '-',
des: bangumi?.media?.description?.trim()?.split('\n')?.[0]?.replace(/<[^>]*>?/g, '') || '-',
collect: bangumi?.media?.popularity || '-',
area: bangumi?.media?.countryOfOrigin || '-',
myStars: bangumi.score || null,
myComment: bangumi.notes || null,
progress: Math.round((bangumi?.progress || 0) / (bangumi?.media?.episodes || 1) * 100),
ep_status: bangumi?.progress || 0
});
const getData = async (vmid, coverMirror, type = 'ANIME') => {
const variables = {
type,
...(typeof vmid === 'number' ? {
userId: vmid
} : {
userName: vmid
})
};
const postData = {
query: MEDIA_LIST_QUERY,
variables
};
try {
const response = await fetchWithRetry(postData);
if (response?.status !== 200) {
throw new Error(`API请求失败: ${response?.status}`);
}
const $data = {
wantWatch: [],
watching: [],
watched: []
};
const list = response?.data?.data?.MediaListCollection?.lists || [];
list.forEach(list => {
list.entries.forEach(bangumi => {
const status = STATUS_MAP[bangumi?.status];
if (status) {
$data[status]?.push(formatBangumiData(bangumi, coverMirror));
}
});
});
return $data;
} catch (error) {
log.error(`获取AniList数据失败: ${error.message}`);
throw error;
}
};
const ensureDataDir = (sourceDir, dataDir = CACHE_DIR) => {
const dir = path.join(sourceDir, dataDir);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return dir;
};
const writeDataToFile = (filePath, data, errorMsg, successMsg) => {
try {
fs.writeFileSync(filePath, JSON.stringify(data));
log.info(successMsg);
} catch (err) {
log.error(errorMsg);
console.error(err);
}
};
const mergeExtraData = (bangumis, extras, order) => {
const result = {
...bangumis
};
const {
wantWatchExtra,
watchingExtra,
watchedExtra
} = extras;
['wantWatch', 'watching', 'watched'].forEach(key => {
const extra = {
wantWatch: wantWatchExtra,
watching: watchingExtra,
watched: watchedExtra
}[key];
if (extra) {
result[key] = order === 1 ? [...extra, ...result[key]] : [...result[key], ...extra];
}
});
return result;
};
module.exports.getAnilistData = async ({
vmid,
type,
sourceDir,
extraOrder,
pagination,
coverMirror
}) => {
try {
log.info('Getting anilist bangumi data, please wait...');
const startTime = new Date().getTime();
const bangumis = await getData(vmid, coverMirror, type);
const totalCount = Object.values(bangumis).reduce((sum, arr) => sum + arr.length, 0);
const endTime = new Date().getTime();
log.info(`${totalCount} bangumis have been loaded in ${endTime - startTime} ms`);
ensureDataDir(sourceDir);
const mainDataPath = path.join(sourceDir, `/${CACHE_DIR}/${CACHE_FILE}`);
writeDataToFile(mainDataPath, bangumis, 'Failed to write data to _data/bangumis.json', 'Anilist bangumis data has been saved');
if (pagination) {
let allBangumis = {
...bangumis
};
const extraDataPath = path.join(sourceDir, `/${CACHE_DIR}/${EXTRA_CACHE_FILE}`);
if (fs.existsSync(extraDataPath)) {
const extraData = JSON.parse(fs.readFileSync(extraDataPath));
allBangumis = mergeExtraData(allBangumis, extraData, extraOrder);
}
const paginationPath = path.join(sourceDir, '/bangumis.json');
writeDataToFile(paginationPath, allBangumis, 'Failed to write data to bangumis.json', 'Anilist bangumis data has been saved');
}
} catch (error) {
log.error(`获取AniList数据失败: ${error.message}`);
throw error;
}
};