您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bangumi tools with mio
// ==UserScript== // @name Bangumi Mio Tools // @namespace remio/script-bangumi // @version 1.0.0 // @author kasuie // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=bgm.tv // @match https://bgm.tv/* // @grant GM.deleteValue // @grant GM.getValue // @grant GM.listValues // @grant GM.setValue // @grant GM.xmlHttpRequest // @description Bangumi tools with mio // ==/UserScript== (function () { 'use strict'; var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)(); const storage = { set(key, val) { return _GM.setValue(key, val); }, get(key, defaultValue) { return _GM.getValue(key, defaultValue); }, all() { return _GM.listValues(); }, del(key) { return _GM.deleteValue(key); }, async clear() { let keys = this.all(); for (let key of await keys) { _GM.deleteValue(key); } } }; const request = (data) => { return new Promise((resolve, reject) => { if (!data.method) { data.method = "get"; } if (!data.timeout) { data.timeout = 6e4; } data.onload = function(res) { try { resolve(JSON.parse(res.responseText)); } catch (error) { reject(error); } }; data.onerror = function(e) { reject(e); }; data.ontimeout = function() { reject("timeout"); }; _GM.xmlHttpRequest(data); }); }; let $message, $button, global; const BaseUrl = "https://example.com"; const onMessage = (text, type = "success", time = 3e3) => { if (text && $message) { let timer; if (timer) clearTimeout(timer); if (type == "success") { $message.css("color", "#4ef16a"); } else if (type == "error") { $message.css("color", "#f23939"); } else { $message.css("color", "#ffffff"); } $message.text(text); $message.css("top", "36px"); timer = setTimeout(() => { $message.css("top", "-36px"); $message.text(""); }, time); } }; const onLoading = (loading = true) => { const $submit = $("#apisubmit"); $button && $button.attr({ disabled: loading }); $submit && $submit.attr({ disabled: loading }); }; const onGetPathEnd = (type, _path = null) => { const path = _path || window.location.pathname; if (path && path.includes(type)) { const pathname = path.split("/"); if (pathname == null ? void 0 : pathname.length) { return _path ? pathname[pathname.length - 1] : +pathname[pathname.length - 1]; } else { return null; } } else { return null; } }; const init = () => { $button = $("<button>", { id: "mio-button", text: "Mio" }); $message = $("<div>", { id: "mio-message" }); const styles = ` .modal { display: none; /* 默认隐藏 */ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 420px; background-color: #010101; box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); padding: 20px; border-radius: 10px; z-index: 1000; } .modal-header { display: flex; justify-content: space-between; align-items: center; font-size: 18px; font-weight: bold; } .submit-btn { padding: 6px 16px; font-size: 12px; background-color: #f09199; color: white; border: none; border-radius: 8px; cursor: pointer; } .close-btn { cursor: pointer; font-size: 24px; } .modal-body { margin: 20px 0; min-height: 200px; } .modal-footer { display: flex; align-items: center; justify-content: flex-end; gap: 16px; } .overlay { display: none; /* 默认隐藏 */ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999; } .open-modal-btn { padding: 10px 20px; font-size: 16px; cursor: pointer; } #rank { outline: none; border: none; background-color: rgba(255, 255, 255, .3); width: 80px; border-radius: 8px; padding: 6px 8px; } #api { outline: none; border: none; background-color: rgba(255, 255, 255, .3); width: 180px; border-radius: 8px; padding: 6px 8px; } `; $("head").append(`<style>${styles}</style>`); $("body.bangumi").append(` <div class="overlay"></div> <div class="modal"> <div class="modal-header"> <span class="modal-title">添加</span> <span class="close-btn">×</span> </div> <div class="modal-body"> </div> <div class="modal-footer"> <input id="api" type="text" placeholder="提交接口" /> <input id="rank" type="number" placeholder="排名" /> <button id="apisubmit" class="submit-btn">提交</button> </div> </div> `); $(".close-btn").on("click", onCloseModal); $(".submit-btn").on("click", function() { const rank = +($("#rank").val() || -1); const { params, url } = global; if (rank) { onAppend("div.modal-body", "<p>获取到排名信息...</p>"); } onSubmit(url, { ...params, rank: rank ? rank : null }); }); $button.on("click", function() { onGetData(); $(".overlay, .modal").fadeIn(); }); $button.css({ padding: "6px 16px", "font-size": "12px", "background-color": "#f09199", color: "white", border: "none", "border-radius": "8px", cursor: "pointer", position: "fixed", top: "14px", right: "20px" }); $message.css({ position: "fixed", top: "-36px", left: "50%", transform: "translateX(-50%)", width: "200px", textAlign: "center", minWidth: "100px", minHeight: "36px", background: "rgba(0,0,0,.4)", borderRadius: "12px", transition: "all .3s ease-in-out", display: "flex", justifyContent: "center", alignItems: "center" }); $("body.bangumi").append($button); $("body.bangumi").append($message); }; const CID = onGetPathEnd("character"); const SID = onGetPathEnd("subject"); console.log("CID>>>", CID, "SID>>>", SID); const onAppend = (ele, data) => $(ele).append(data); const onClear = (ele) => $(ele).html(""); const onCloseModal = () => { $("#rank").val(""); onLoading(false); onClear("div.modal-body"); $(".overlay, .modal").fadeOut(); }; const onGetData = async () => { const CharApi = await storage.get("CharApi", `${BaseUrl}/bgm/saveChar`); const SubApi = await storage.get("SubApi", `${BaseUrl}/bgm/saveSub`); console.log(CharApi, SubApi, "CharApi, SubApi"); if (CID) { $("span.modal-title").text("新增角色"); onLoading(); onAppend("div.modal-body", "<p>开始加载数据...</p>"); const character = request({ method: "GET", url: `https://api.bgm.tv/v0/characters/${CID}` }); request({ method: "GET", url: `https://api.bgm.tv/v0/characters/${CID}/persons` }).then((cvs) => { let cv = null; const item = cvs == null ? void 0 : cvs.find((v) => v.subject_type === 2); if (item == null ? void 0 : item.id) { cv = request({ method: "GET", url: `https://api.bgm.tv/v0/persons/${item.id}` }); } Promise.all([character, cv]).then(([character2, cv2]) => { const cvData = cv2 && formatPerson(cv2, true); const params = { ...formatChar(character2), actors: cvData ? JSON.stringify({ name: cvData.name, id: cvData.id }) : null, cv: cvData }; global = { params, url: CharApi }; onLoading(false); onAppend("div.modal-body", "<p>加载成功</p>"); $("#api").val((global == null ? void 0 : global.url) || ""); }); }); } else if (SID) { $("span.modal-title").text("新增番剧"); onLoading(); onAppend("div.modal-body", "<p>开始加载数据...</p>"); const subject = request({ method: "GET", url: `https://api.bgm.tv/v0/subjects/${SID}` }); const characters = request({ method: "GET", url: `https://api.bgm.tv/v0/subjects/${SID}/characters` }); const persons = request({ method: "GET", url: `https://api.bgm.tv/v0/subjects/${SID}/persons` }); Promise.all([subject, characters, persons]).then( ([subject2, characters2, persons2]) => { const afterPersons = formatRoles(persons2); const { data, actors } = formatRoles(characters2, true); const personsId = [], resultPersons = []; const relCharacters = data.map((v) => ({ id: v.id, name: v.name, relation: v.relation })); const relPersons = afterPersons.map((v) => { if (!personsId.includes(v.id)) { personsId.push(v.id); resultPersons.push({ ...v, summary: null }); } return { id: v.id, name: v.name, position: v.summary }; }); const resultActors = (actors == null ? void 0 : actors.reduce( (prev, curr) => { if (!prev[0].includes(curr.id)) { prev[0].push(curr.id); prev[1].push(curr); } return prev; }, [[], []] )[1]) || []; const params = { ...formatSub(subject2), characters: data, persons: resultPersons.concat(resultActors), relPersons: JSON.stringify(relPersons), relCharacters: JSON.stringify(relCharacters) }; global = { params, url: SubApi }; onLoading(false); onAppend("div.modal-body", "<p>加载成功</p>"); $("#api").val((global == null ? void 0 : global.url) || ""); } ); } }; const onSubmit = (url, params, options = { method: "POST" }) => { onAppend("div.modal-body", "<p>开始上报数据...</p>"); const Api = $("#api").val(); if (Api !== url) { if (CID) { storage.set("CharApi", Api); } else if (SID) { storage.set("SubApi", Api); } } console.log("onSubmit:", params); request({ ...options, url, headers: { "Content-Type": "application/json" }, data: JSON.stringify(params) }).then((res) => { console.log(res, "请求结果~"); onAppend( "div.modal-body", `<p>上报数据结果:${res.success ? "成功" : "失败"}</p>` ); onMessage(res.message, res.success ? "success" : "error"); }).catch((e) => onMessage(`请求失败:${e}`, "error")).finally( () => setTimeout(() => { onCloseModal(); }, 300) ); }; const formatPerson = (person, isCv) => { let { career, blood_type, birth_day, birth_mon, birth_year, images, infobox, gender, stat, last_modified, img, ...others } = person; for (const key in images) { images[key] = images[key].replace("https://lain.bgm.tv", ""); } const infoKeys = [ { name: "中文名", field: "nameCn" }, { name: "别名", field: "aliasName" }, { name: "性别", field: "gender" }, { name: "生日", field: "birth" }, { name: "Twitter", field: "twitter" }, { name: "引用来源", field: "source" } ]; let aliasName, nameCn, twiAccount, birthDay; const info = infobox == null ? void 0 : infobox.reduce((prev, curr) => { const item = infoKeys.find((v) => curr.key.includes(v.name)); if (item) { if (item.field === "nameCn" && !nameCn) { nameCn = curr.value; } else if (item.field === "aliasName" && !aliasName) { aliasName = curr.value; } else if (item.field === "gender" && !gender) { gender = curr.value === "男" ? "male" : "female"; } else if (item.field === "twitter") { if (curr.value.includes(".com")) { twiAccount = onGetPathEnd(".com", curr.value); } else { twiAccount = curr.value; } } else if (item.field === "birth") { birthDay = curr.value; } } else { !prev && (prev = {}); prev[curr.key] = curr.value; } return prev; }, null); if (!birthDay) { birthDay = [birth_year, birth_mon, birth_day].reduce((prev, curr) => { return prev ? `${prev}-${curr || ""}` : curr; }, null); } return { ...others, twiAccount, birthDay, nameCn, gender, isCv, info: JSON.stringify(info), aliasName: aliasName && JSON.stringify(aliasName), bgmImages: JSON.stringify(images), career: career && career.join(",") }; }; const formatChar = (char) => { let { birth_day, birth_mon, birth_year, blood_type, images, infobox, stat, gender, ...others } = char; for (const key in images) { images[key] = images[key].replace("https://lain.bgm.tv", ""); if (key === "large") { images["avatar"] = images[key].replace("/pic/crt/l", "/pic/crt/g"); } } const infoKeys = [ { name: "中文名", field: "nameCn" }, { name: "别名", field: "aliasName" }, { name: "性别", field: "gender" }, { name: "生日", field: "birth" }, { name: "引用来源", field: "source" } ]; let aliasName, nameCn, birthDay, birthDesc; birthDay = [birth_year, birth_mon, birth_day].reduce((prev, curr) => { return prev ? `${prev}-${curr || ""}` : curr; }, null); const info = infobox == null ? void 0 : infobox.reduce((prev, curr) => { const item = infoKeys.find((v) => curr.key.includes(v.name)); if (item) { if (item.field === "nameCn" && !nameCn) { nameCn = curr.value; } else if (item.field === "aliasName" && !aliasName) { aliasName = curr.value; } else if (item.field === "gender" && !gender) { gender = curr.value === "男" ? "male" : "female"; } else if (item.field === "birth") { birthDesc = curr.value; } } else { !prev && (prev = {}); prev[curr.key] = curr.value; } return prev; }, null); return { ...others, info: info && JSON.stringify(info), nameCn, gender, birthDay, birthDesc, bloodType: blood_type, aliasName: aliasName && JSON.stringify(aliasName), bgmImages: JSON.stringify(images) }; }; const formatSub = (subject) => { var _a, _b; let { id, tags, images, name_cn, rating: { score, total }, date, infobox, total_episodes, collection, ...others } = subject; let aliasName, openTv, otherTv, startDate, endDate, copyright, official, original, director, charSetting; for (const key in images) { images[key] = (_a = images[key]) == null ? void 0 : _a.replace("https://lain.bgm.tv", ""); if (key === "large") { images["avatar"] = (_b = images[key]) == null ? void 0 : _b.replace("/pic/cover/l", "/pic/cover/g"); } } const tagsString = tags == null ? void 0 : tags.reduce((prev, curr, index) => { return index > 15 ? prev : prev ? `${prev},${curr.name}` : curr.name; }, ""); const infoBox = infobox == null ? void 0 : infobox.filter((item) => { if (item.key === "中文名") { !name_cn && (name_cn = item.value); return false; } else if (item.key === "别名") { aliasName = JSON.stringify(item.value); return false; } else if (item.key === "话数") { !total_episodes && (total_episodes = +item.value); return false; } else if (item.key === "放送开始") { startDate = onGetDate(item.value) || item.value; return false; } else if (item.key === "播放结束") { endDate = onGetDate(item.value) || item.value; return false; } else if (item.key === "官方网站") { official = item.value; return false; } else if (item.key === "播放电视台") { openTv = item.value; return false; } else if (item.key === "其他电视台") { otherTv = item.value; return false; } else if (item.key === "Copyright") { copyright = item.value; return false; } else if (item.key === "原作") { original = item.value; return false; } else if (item.key === "导演") { director = item.value; return false; } else if (item.key === "人物设定" || item.key === "角色设定") { charSetting = item.value; return false; } else { return true; } }); return { ...others, bgmId: id, nameCn: name_cn, tags: tagsString, bgmScore: score, bgmRaters: total, infoBox: JSON.stringify(infoBox), startDate, endDate, aliasName, broadcast: JSON.stringify({ openTv, otherTv }), info: JSON.stringify({ copyright, official, original, director, charSetting }), bgmImages: JSON.stringify(images), totalEpisodes: total_episodes }; }; const formatRoles = (list, isChar = false, isCv = false) => { if (!Array.isArray(list)) return null; let data = list; if (isChar) { let totalActors = []; data = list.map((v) => { let { actors, images, locked, ...others } = v; for (const key in images) { images[key] = images[key].replace("https://lain.bgm.tv", ""); if (key === "large") { images["avatar"] = images[key].replace("/pic/crt/l", "/pic/crt/g"); } } const itemCvs = formatRoles(actors, false, true); totalActors = totalActors.concat(itemCvs); return { ...others, locked: locked || false, isCv, actors: (itemCvs == null ? void 0 : itemCvs.length) ? JSON.stringify( itemCvs.map((v2) => ({ name: v2.name, id: v2.id })) ) : null, bgmImages: JSON.stringify(images) }; }); return { data, actors: totalActors }; } else { const whiteListByCount = ["作画监督", "副导演", "原画", "补间动画"]; const whiteList = [ "原作", "导演", "人物原案", "人物设定", "音乐", "总作画监督", "脚本", "系列构成", "美术监督", "摄影监督" ]; const relMaps = list.reduce((prev, curr) => { if (Object.hasOwn(prev, curr.relation)) { ++prev[curr.relation]; } else { prev[curr.relation] = 1; } return prev; }, {}); if (!isCv) { data = list.filter((v) => { if (!v.relation) return false; if (whiteList.includes(v.relation)) { return true; } else if (whiteListByCount.includes(v.relation)) { return relMaps[v.relation] < 7; } else { return false; } }); } data = data.map((v) => { let { career, images, short_summary, locked, relation, ...others } = v; for (const key in images) { images[key] = images[key].replace("https://lain.bgm.tv", ""); if (key === "large") { images["avatar"] = images[key].replace("/pic/crt/l", "/pic/crt/g"); } } return { ...others, locked: locked || false, isCv, summary: isCv ? short_summary : relation, bgmImages: JSON.stringify(images), career: career && career.join(",") }; }); return data; } }; const onGetDate = (dateString) => { if (!dateString) return null; const dateParts = dateString.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/); if (dateParts) { const year = parseInt(dateParts[1], 10); const month = parseInt(dateParts[2], 10) - 1; const day = parseInt(dateParts[3], 10); return new Date(year, month, day); } else { return null; } }; init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址