UNPKG

hexo-bilibili-bangumi

Version:
340 lines (323 loc) 14.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* * @Author : HCLonely * @Date : 2025-07-10 12:31:47 * @LastEditTime : 2025-07-10 15:05:14 * @LastEditors : HCLonely * @FilePath : /hexo-bilibili-bangumi/src/lib/get-simkl-data.js * @Description : SIMKL数据获取模块,通过REST API获取用户的动画收藏数据。 * 支持获取观看中、计划观看、已完成三种状态的动画数据, * 包含标题、封面、进度等信息。 */ var fs = require('hexo-fs'); var path = require('path'); var axios = require('axios'); var hexoLog = require('hexo-log'); // 常量定义 var SIMKL_API = 'https://api.simkl.com'; var CACHE_DIR = '_data'; var CACHE_FILE = 'bangumis.json'; var EXTRA_CACHE_FILE = 'extra_bangumis.json'; var USER_AGENT = 'HCLonely/hexo-bilibili-bangumi'; var MAX_RETRIES = 3; var RETRY_DELAY = 1000; // 状态映射 var STATUS_MAP = { plantowatch: 'wantWatch', watching: 'watching', completed: 'watched' }; // 日志实例 var log = typeof hexoLog["default"] === 'function' ? hexoLog["default"]({ debug: false, silent: false }) : hexoLog({ debug: false, silent: false }); /** * 延迟函数 * @param {number} ms 延迟时间(毫秒) * @returns {Promise<void>} */ var delay = function delay(ms) { return new Promise(function (resolve) { return setTimeout(resolve, ms); }); }; /** * 带重试的API请求 * @param {string} status 观看状态 * @param {Object} config 配置信息 * @param {number} retries 重试次数 * @returns {Promise<Object>} */ var _fetchWithRetry = /*#__PURE__*/function () { var _ref = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(status, config, type) { var retries, response, _args = arguments, _t; return _regenerator["default"].wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: retries = _args.length > 3 && _args[3] !== undefined ? _args[3] : MAX_RETRIES; _context.prev = 1; _context.next = 2; return axios({ method: 'GET', url: "".concat(SIMKL_API, "/sync/all-items/").concat(type, "/").concat(status, "?memos=yes"), headers: { 'Content-Type': 'application/json', Authorization: "Bearer ".concat(config.token), 'simkl-api-key': config.clientId, 'User-Agent': USER_AGENT } }); case 2: response = _context.sent; return _context.abrupt("return", response); case 3: _context.prev = 3; _t = _context["catch"](1); if (!(retries > 0)) { _context.next = 5; break; } _context.next = 4; return delay(RETRY_DELAY); case 4: return _context.abrupt("return", _fetchWithRetry(status, config, type, retries - 1)); case 5: throw _t; case 6: case "end": return _context.stop(); } }, _callee, null, [[1, 3]]); })); return function fetchWithRetry(_x, _x2, _x3) { return _ref.apply(this, arguments); }; }(); /** * 格式化番剧数据 * @param {Object} bangumi 原始数据 * @param {string} coverMirror 图片镜像 * @returns {Object} */ var formatBangumiData = function formatBangumiData(bangumi, coverMirror) { var _bangumi$show, _bangumi$show2, _bangumi$show3, _bangumi$show4, _bangumi$show5, _bangumi$show6, _bangumi$memo; return { title: (_bangumi$show = bangumi.show) === null || _bangumi$show === void 0 ? void 0 : _bangumi$show.title, type: bangumi.anime_type, cover: "".concat(coverMirror, "https://simkl.in/posters/").concat((_bangumi$show2 = bangumi.show) === null || _bangumi$show2 === void 0 ? void 0 : _bangumi$show2.poster, "_m.jpg"), totalCount: bangumi.total_episodes_count, id: (_bangumi$show3 = bangumi.show) === null || _bangumi$show3 === void 0 || (_bangumi$show3 = _bangumi$show3.ids) === null || _bangumi$show3 === void 0 ? void 0 : _bangumi$show3.simkl, des: ((_bangumi$show4 = bangumi.show) === null || _bangumi$show4 === void 0 || (_bangumi$show4 = _bangumi$show4.ids) === null || _bangumi$show4 === void 0 || (_bangumi$show4 = _bangumi$show4.wikien) === null || _bangumi$show4 === void 0 ? void 0 : _bangumi$show4.replaceAll('_', ' ')) || ((_bangumi$show5 = bangumi.show) === null || _bangumi$show5 === void 0 || (_bangumi$show5 = _bangumi$show5.ids) === null || _bangumi$show5 === void 0 ? void 0 : _bangumi$show5.wikijp) || '-', year: ((_bangumi$show6 = bangumi.show) === null || _bangumi$show6 === void 0 ? void 0 : _bangumi$show6.year) || '-', myStars: bangumi.user_rating, myComment: ((_bangumi$memo = bangumi.memo) === null || _bangumi$memo === void 0 ? void 0 : _bangumi$memo.text) || '-', progress: Math.round(bangumi.watched_episodes_count / (bangumi.total_episodes_count || 1) * 100), ep_status: bangumi.watched_episodes_count, new_ep: bangumi.total_episodes_count }; }; /** * 获取SIMKL数据 * @param {Object} config 配置信息 * @param {string} coverMirror 图片镜像 * @returns {Promise<Object>} */ var getData = /*#__PURE__*/function () { var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(config, coverMirror, type) { var $data, _loop, _i, _arr; return _regenerator["default"].wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: $data = { wantWatch: [], watching: [], watched: [] }; _loop = /*#__PURE__*/_regenerator["default"].mark(function _loop() { var status, _response$data, response, animeList, mappedStatus, _t2; return _regenerator["default"].wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: status = _arr[_i]; _context2.prev = 1; _context2.next = 2; return _fetchWithRetry(status, config, type); case 2: response = _context2.sent; if (!((response === null || response === void 0 ? void 0 : response.status) !== 200)) { _context2.next = 3; break; } throw new Error("API\u8BF7\u6C42\u5931\u8D25: ".concat(response === null || response === void 0 ? void 0 : response.status)); case 3: animeList = (response === null || response === void 0 || (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.anime) || []; mappedStatus = STATUS_MAP[status]; animeList.forEach(function (bangumi) { $data[mappedStatus].push(formatBangumiData(bangumi, coverMirror)); }); _context2.next = 5; break; case 4: _context2.prev = 4; _t2 = _context2["catch"](1); log.error("\u83B7\u53D6SIMKL ".concat(status, "\u6570\u636E\u5931\u8D25: ").concat(_t2.message)); throw _t2; case 5: case "end": return _context2.stop(); } }, _loop, null, [[1, 4]]); }); _i = 0, _arr = ['plantowatch', 'watching', 'completed']; case 1: if (!(_i < _arr.length)) { _context3.next = 3; break; } return _context3.delegateYield(_loop(), "t0", 2); case 2: _i++; _context3.next = 1; break; case 3: return _context3.abrupt("return", $data); case 4: case "end": return _context3.stop(); } }, _callee2); })); return function getData(_x4, _x5, _x6) { return _ref2.apply(this, arguments); }; }(); /** * 确保数据目录存在 * @param {string} sourceDir 源目录 * @param {string} dataDir 数据目录 */ var ensureDataDir = function ensureDataDir(sourceDir) { var dataDir = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CACHE_DIR; var dir = path.join(sourceDir, dataDir); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } return dir; }; /** * 写入数据到文件 * @param {string} filePath 文件路径 * @param {Object} data 数据 * @param {string} errorMsg 错误信息 * @param {string} successMsg 成功信息 */ var writeDataToFile = function writeDataToFile(filePath, data, errorMsg, successMsg) { try { fs.writeFileSync(filePath, JSON.stringify(data)); log.info(successMsg); } catch (err) { log.error(errorMsg); console.error(err); } }; /** * 合并额外数据 * @param {Object} bangumis 原数据 * @param {Object} extras 额外数据 * @param {number} order 顺序 * @returns {Object} */ var mergeExtraData = function mergeExtraData(bangumis, extras, order) { var result = _objectSpread({}, bangumis); var wantWatchExtra = extras.wantWatchExtra, watchingExtra = extras.watchingExtra, watchedExtra = extras.watchedExtra; ['wantWatch', 'watching', 'watched'].forEach(function (key) { var extra = { wantWatch: wantWatchExtra, watching: watchingExtra, watched: watchedExtra }[key]; if (extra) { result[key] = order === 1 ? [].concat((0, _toConsumableArray2["default"])(extra), (0, _toConsumableArray2["default"])(result[key])) : [].concat((0, _toConsumableArray2["default"])(result[key]), (0, _toConsumableArray2["default"])(extra)); } }); return result; }; /** * 获取SIMKL番剧数据 * @param {Object} options 配置选项 * @returns {Promise<void>} */ module.exports.getSimklData = /*#__PURE__*/function () { var _ref4 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3(_ref3) { var vmid, type, sourceDir, extraOrder, pagination, coverMirror, startTime, _vmid$split, _vmid$split2, clientId, token, bangumis, totalCount, endTime, mainDataPath, allBangumis, extraDataPath, extraData, paginationPath, _t3; return _regenerator["default"].wrap(function (_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: vmid = _ref3.vmid, type = _ref3.type, sourceDir = _ref3.sourceDir, extraOrder = _ref3.extraOrder, pagination = _ref3.pagination, coverMirror = _ref3.coverMirror; _context4.prev = 1; log.info('Getting SIMKL bangumi data, please wait...'); startTime = new Date().getTime(); _vmid$split = vmid.split('-'), _vmid$split2 = (0, _slicedToArray2["default"])(_vmid$split, 2), clientId = _vmid$split2[0], token = _vmid$split2[1]; // 获取数据 _context4.next = 2; return getData({ token: token, clientId: clientId }, coverMirror, type); case 2: bangumis = _context4.sent; totalCount = Object.values(bangumis).reduce(function (sum, arr) { return sum + arr.length; }, 0); endTime = new Date().getTime(); log.info("".concat(totalCount, " bangumis have been loaded in ").concat(endTime - startTime, " ms")); // 确保数据目录存在 ensureDataDir(sourceDir); // 写入主数据文件 mainDataPath = path.join(sourceDir, "/".concat(CACHE_DIR, "/").concat(CACHE_FILE)); writeDataToFile(mainDataPath, bangumis, 'Failed to write data to _data/bangumis.json', 'SIMKL bangumis data has been saved'); // 处理分页数据 if (pagination) { allBangumis = _objectSpread({}, bangumis); // 合并额外数据 extraDataPath = path.join(sourceDir, "/".concat(CACHE_DIR, "/").concat(EXTRA_CACHE_FILE)); if (fs.existsSync(extraDataPath)) { extraData = JSON.parse(fs.readFileSync(extraDataPath)); allBangumis = mergeExtraData(allBangumis, extraData, extraOrder); } // 写入分页数据 paginationPath = path.join(sourceDir, '/bangumis.json'); writeDataToFile(paginationPath, allBangumis, 'Failed to write data to bangumis.json', 'SIMKL bangumis data (with extras) has been saved'); } _context4.next = 4; break; case 3: _context4.prev = 3; _t3 = _context4["catch"](1); log.error("\u83B7\u53D6SIMKL\u6570\u636E\u5931\u8D25: ".concat(_t3.message)); throw _t3; case 4: case "end": return _context4.stop(); } }, _callee3, null, [[1, 3]]); })); return function (_x7) { return _ref4.apply(this, arguments); }; }();