您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mikan 蜜柑计划首页显示 Bangumi 评分及跳转链接。
当前为
// ==UserScript== // @name mikananiBgmScore // @namespace https://github.com/kjtsune/UserScripts // @version 0.6 // @description Mikan 蜜柑计划首页显示 Bangumi 评分及跳转链接。 // @author kjtsune // @match https://mikanani.me/ // @match https://mikanani.me/Home/MyBangumi // @match https://mikanani.tv/ // @match https://mikanani.tv/Home/MyBangumi // @icon https://www.google.com/s2/favicons?sz=64&domain=mikanani.me // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @license MIT // ==/UserScript== 'use strict'; let config = { // 若 minScore 的值大于0.1,会隐藏低于该评分的条目。 minScore: 0, // 清除无效标签的正则匹配规则 tagsRegex: /\d{4}|TV|动画|小说|漫|轻改|游戏改|原创|[a-zA-Z]/, // 标签数量限制,填0禁用标签功能。 tagsNum: 3, logLevel: 2, }; let logger = { error: function (...args) { if (config.logLevel >= 1) { console.log('%cerror', 'color: yellow; font-style: italic; background-color: blue;', ...args); } }, info: function (...args) { if (config.logLevel >= 2) { console.log('%cinfo', 'color: yellow; font-style: italic; background-color: blue;', ...args); } }, debug: function (...args) { if (config.logLevel >= 3) { console.log('%cdebug', 'color: yellow; font-style: italic; background-color: blue;', ...args); } }, } function createElementFromHTML(htmlString) { let div = document.createElement('div'); div.innerHTML = htmlString.trim(); return div.firstElementChild; } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function getJSON(url) { try { const response = await fetch(url); logger.info(`fetch ${url}`) if (response.status >= 200 && response.status < 400) return await response.json(); console.error(`Error fetching ${url}:`, response.status, response.statusText, await response.text()); } catch (e) { console.error(`Error fetching ${url}:`, e); } } async function getBgmJson(bgmId) { let url = `https://api.bgm.tv/v0/subjects/${bgmId}` return await getJSON(url) } async function cleanBgmTags(tags) { tags = tags.filter(item => item.count >= 10 && !(config.tagsRegex.test(item.name))); let namesList = tags.map(item => item.name); return namesList; } async function getParsedBgmInfo(bgmId, stringify = false) { let bgmJson = await getBgmJson(bgmId); let score = (bgmJson) ? bgmJson.rating.score : 0.1; let summary = (bgmJson) ? bgmJson.summary : "18x or network error"; let date = (bgmJson) ? bgmJson.date : new Date(); let tags = (bgmJson) ? await cleanBgmTags(bgmJson.tags) : []; let res = { score: score, summary: summary, date: date, tags: tags }; res = (stringify) ? JSON.stringify(res) : res; return res } function queryAllForArray(seletor, elementArray) { let result = []; for (const element of elementArray) { let res = element.querySelectorAll(seletor); if (!res) logger.error("queryAllForArray not result", seletor, element); result.push(...res); } return result } function multiTimesSeletor(storage = null, seletorAll = false, ...cssSeletor) { const seletor = cssSeletor[0] const restSeletor = cssSeletor.slice(1) if (!seletor) return storage; if (seletorAll) { storage = storage || [document] let res = queryAllForArray(seletor, storage); if (res) storage = res; storage && logger.debug('storage', storage.length, seletor, restSeletor); if (!restSeletor) { return storage; } else { return multiTimesSeletor(storage, true, ...restSeletor); } } else { storage = storage || document; const lastRes = storage; storage = storage.querySelector(seletor); storage && logger.debug('storage', storage, seletor); if (!storage) logger.error("not result", seletor, lastRes); if (!restSeletor) { return storage; } else { return multiTimesSeletor(storage, false, ...restSeletor); } } } async function myFetch(url, selector = null, selectAll = false) { let response = await fetch(url); let text = await response.text(); const parser = new DOMParser(); const htmlDocument = parser.parseFromString(text, "text/html"); const element = htmlDocument.documentElement; if (!selector) return element; if (selectAll) { return element.querySelectorAll(selector); } else { return element.querySelector(selector); } } async function getBgmId(mikanUrl) { let selector = "p.bangumi-info > a[href*='tv/subject']"; let bgm = await myFetch(mikanUrl, selector); if (bgm) bgm = bgm.href.split("/").slice(-1)[0]; return bgm } class MyStorage { constructor(prefix, splitStr = '|', expireDay = 0, useGM = false) { this.prefix = prefix; this.splitStr = splitStr; this.expireDay = expireDay; this.expireMs = expireDay * 864E5; this._getItem = (useGM) ? GM_getValue : localStorage.getItem.bind(localStorage); this._setItem = (useGM) ? GM_setValue : localStorage.setItem.bind(localStorage); this._removeItem = (useGM) ? GM_deleteValue : localStorage.removeItem.bind(localStorage); } _dayToMs(day) { return day * 864E5; } _msToDay(ms) { return ms / 864E5; } _keyGenerator(key) { return `${this.prefix}${this.splitStr}${key}` } get(key, defalut = null) { key = this._keyGenerator(key); let res = this._getItem(key); if (this.expireMs && res) { res = JSON.parse(this._getItem(key)).value; } res = res || defalut; return res } set(key, value) { key = this._keyGenerator(key); if (this.expireMs) { value = JSON.stringify({ timestamp: Date.now(), value: value }) } this._setItem(key, value) } del(key) { key = this._keyGenerator(key); try { this._removeItem(key); } catch (error) { // pass } } checkIsExpire(key, expireDay = null) { key = this._keyGenerator(key); let exists = this.useGM ? (this._getItem(key) !== undefined) : (key in localStorage) if (!exists) return true; if (!this.expireMs && exists) { return false }; let data = JSON.parse(this._getItem(key)) let timestamp = data.timestamp; if (!timestamp) throw `checkIsExpire not work , not timestamp, key: ${key}`; expireDay = (expireDay !== null) ? expireDay : this.expireDay; let expireMs = (expireDay !== null) ? expireDay * 864E5 : this.expireMs; if (timestamp + expireMs < Date.now()) { logger.info(key, "IsExpire, old:", new Date(timestamp).toLocaleDateString(), "expireDay:", expireDay); return true; } else { return false; } } } class BgmStorage extends MyStorage { constructor(prefix, splitStr = '|', expireDay = 0, useGM = false) { super(prefix, splitStr, expireDay, useGM); } bgmIsExpire(key) { let expireDay = 15; let airDate = this.get(key, Object).date; if (!airDate) { return true }; let airedDay = this._msToDay(new Date().getTime() - new Date(airDate).getTime()); switch (true) { case (airedDay < 10): expireDay = 1; break; case (airedDay < 20): expireDay = 2; break; case (airedDay < 180): expireDay = 5; break; default: expireDay = 15; break; } return this.checkIsExpire(key, expireDay); // return this.checkIsExpire(key, 0); } } async function addScoreSummaryToHtml(mikanElementList) { let bgmIco = `<img style="width:16px;" src="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJu+f//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn//////7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvnAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn/sm75////////////sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+cCwaPn/sGj5/9iz/P///////////////////////////////////////////////////////////9iz/P+waPn/rF/6/6xf+v//////////////////////////////////////////////////////////////////////rF/6/6lW+/+pVvv/////////////////////////////////zXn2/////////////////////////////////6lW+/+lTfz/pU38///////Nefb/zXn2/8159v//////zXn2///////Nefb//////8159v/Nefb/zXn2//////+lTfz/okT8/6JE/P//////////////////////2bb8/8159v/Nefb/zXn2/9m2/P//////////////////////okT8/546/f+eOv3//////8159v/Nefb/zXn2////////////////////////////zXn2/8159v/Nefb//////546/f+bMf7/mzH+//////////////////////////////////////////////////////////////////////+bMf7/lyj+wJco/v/Mk/7////////////////////////////////////////////////////////////Mk///lyj+wJQf/xCUH//AlB///5Qf//+UH///lB///5Qf//+aP///mj///5o///+UH///lB///5Qf//+UH///lB//wJQf/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/5o////Nefb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAM159v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNefb/AAAAAAAAAAAAAAAA+f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/j8AAP3fAAD77wAA9/cAAA==">` for (const element of mikanElementList) { let scoreElement = element.nextElementSibling; if (scoreElement) continue; let mikanUrl = element.href; let mikanId = mikanUrl.split('/').slice(-1)[0]; let bgmId = mikanBgmStorage.get(mikanId); let bgmInfo = bgmInfoStorage.get(bgmId); if (!bgmId || !bgmInfo) continue; let bgmUrl = `https://bgm.tv/subject/${bgmId}` let score = bgmInfo.score; let summary = bgmInfo.summary; let title = element.textContent; let pathName = element.pathname; let tags = bgmInfo.tags; let tasgWithSummary = summary; let tagsHtml = ''; if (tags.length > 0 && config.tagsNum > 0 ) { tags = tags.filter(name => !config.tagsRegex.test(name)); tagsHtml = `<br>${tags.slice(0, config.tagsNum)}`; tasgWithSummary = `tags: ${tags}\n\n${summary}` element.insertAdjacentHTML("afterend", tagsHtml); } let bgmHtml = `<a href="${bgmUrl}" target="_blank" title="${tasgWithSummary}" id="bgmScore">${bgmIco} ${score}</a>` element.insertAdjacentHTML("afterend", bgmHtml); let minScore = config.minScore; let lowScore = (score <= minScore && score > 0.1 && minScore > 0.1) ? true : false; let mobileFatherElement = document.querySelectorAll(`a[href="${pathName}"`)[1]; if (lowScore) { logger.info('delete', title, score, minScore); element.parentElement.parentElement.parentElement.remove(); } if (!mobileFatherElement) continue; mobileFatherElement = mobileFatherElement.parentElement; let mobileElement = mobileFatherElement.querySelector('div'); if (!mobileElement) continue; let mobileHtml = `<a href="${bgmUrl}" target="_blank" title="${tasgWithSummary}" id="bgmScore">${title} ${score}</a>` let newMobileElement = createElementFromHTML(mobileHtml); mobileElement.replaceWith(newMobileElement); if (tagsHtml) { newMobileElement.insertAdjacentHTML("afterend", tagsHtml); } // mobileFatherElement.replaceChild(newMobileElement, mobileElement); if (lowScore) { logger.info('delete', title, score, minScore); newMobileElement.parentElement.parentElement.remove(); } } } let mikanBgmStorage = new MyStorage("mikan"); let bgmInfoStorage = new BgmStorage("bgm", undefined, 7); async function storeMikanBgm(mikanElementList, storeBgmInfo = false) { let count = 0; async function checkBgmInfoExist(mkId) { let bgmId = mikanBgmStorage.get(mkId); if (!bgmId) return; if (bgmInfoStorage.bgmIsExpire(bgmId)) { bgmInfoStorage.set(bgmId, await getParsedBgmInfo(bgmId)); count++; } } for (const element of mikanElementList) { let mikanUrl = element.href; let mikanId = mikanUrl.split('/').slice(-1)[0]; let bgmId = mikanBgmStorage.get(mikanId) if (!bgmId) { bgmId = await getBgmId(mikanUrl); logger.info("fetch mikan", mikanId); mikanBgmStorage.set(mikanId, bgmId); logger.info(`set ${mikanId} to ${bgmId}`); await sleep(1000); count++; } if (storeBgmInfo) await checkBgmInfoExist(mikanId); await addScoreSummaryToHtml([element]); } count && logger.info('fetch count', count); } function backupMikanBgm() { let result = {}; for (let key in localStorage) { if (key.indexOf('mikan|') != -1) { result[key] = localStorage.getItem(key); } } if (result) { result = JSON.stringify(result); console.log(result); } } function restoreMikanBgm(text) { let data = JSON.parse(text); for (let key in data) { if (key.indexOf('mikan|') != -1) { localStorage.setItem(key, data[key]) } } } function countMikanBgm() { let count = 0; for (let key in localStorage) { if (key.indexOf('mikan|') != -1) { count++; } } console.log('mikan bgm count: ', count) } async function main() { let animeList = multiTimesSeletor(null, true, "div.sk-bangumi", "a[href^='/Home/Bangumi']"); // animeList = animeList.slice(0, 10); await storeMikanBgm(animeList, true); await addScoreSummaryToHtml(animeList); logger.info(animeList) } (function loop() { setTimeout(async function () { let start = Date.now() await main(); let usedSec = (Date.now() - start) / 1000; if (usedSec > 0.01) logger.info(`used time ${usedSec}`); loop(); }, 2000); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址