// ==UserScript== // @name b站数据采集 // @namespace http://tampermonkey.net/ // @license GPL-3.0 // @version 1.1 // @author byhgz // @icon https://static.hdslb.com/images/favicon.ico // @noframes // @description 采集页面数据并导出,目前只考虑用户的收藏夹,待后续完善其他 // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_unregisterMenuCommand // @grant GM_registerMenuCommand // @grant GM_openInTab // @exclude http://localhost:3001/ // @match *://localhost/* // @match https://space.bilibili.com/* // @require https://unpkg.com/vue@2.7.16/dist/vue.min.js // @require https://unpkg.com/element-ui@2.15.14/lib/index.js // ==/UserScript== "use strict"; (function(Vue){'use strict';var gmUtil = { setData(key, content) { GM_setValue(key, content); }, getData(key, defaultValue) { return GM_getValue(key, defaultValue); }, delData(key) { if (!this.isData(key)) { return false; } GM_deleteValue(key); return true; }, isData(key) { return this.getData(key) !== void 0; }, addStyle(style) { GM_addStyle(style); }, addGMMenu(text, func, shortcutKey = null) { return GM_registerMenuCommand(text, func, shortcutKey); }, openInTab(url, options = { active: true, insert: true, setParent: true }) { GM_openInTab(url, options); } };var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var _regularEvents, _callbackEvents, _EventEmitter_instances, handlePendingEvents_fn, executePreHandle_fn; class EventEmitter { constructor() { __privateAdd(this, _EventEmitter_instances); __privateAdd(this, _regularEvents, { events: {}, futures: {}, parametersDebounce: {}, preHandles: {} }); __privateAdd(this, _callbackEvents, { events: {}, callbackInterval: 1500 }); } on(eventName, callback, overrideEvents = false) { const events = __privateGet(this, _regularEvents).events; if (events[eventName]) { if (overrideEvents) { events[eventName] = callback; __privateMethod(this, _EventEmitter_instances, handlePendingEvents_fn).call(this, eventName, callback); } return this; } events[eventName] = callback; __privateMethod(this, _EventEmitter_instances, handlePendingEvents_fn).call(this, eventName, callback); return this; } onPreHandle(eventName, callback) { const preHandles = __privateGet(this, _regularEvents).preHandles; preHandles[eventName] = callback; return this; } handler(eventName, callback) { const handlerEvents = __privateGet(this, _callbackEvents).events; if (handlerEvents[eventName]) { throw new Error("该事件名已经存在,请更换事件名"); } handlerEvents[eventName] = callback; } invoke(eventName, ...data) { return new Promise((resolve) => { const handlerEvents = __privateGet(this, _callbackEvents).events; if (handlerEvents[eventName]) { resolve(handlerEvents[eventName](...data)); return; } const i1 = setInterval(() => { if (handlerEvents[eventName]) { clearInterval(i1); resolve(handlerEvents[eventName](...data)); } }, __privateGet(this, _callbackEvents).callbackInterval); }); } send(eventName, ...data) { const ordinaryEvents = __privateGet(this, _regularEvents); const events = ordinaryEvents.events; const event = events[eventName]; if (event) { const preHandleData = __privateMethod(this, _EventEmitter_instances, executePreHandle_fn).call(this, eventName, data); event.apply(null, preHandleData); return this; } const futures = ordinaryEvents.futures; if (futures[eventName]) { futures[eventName].push(data); return this; } futures[eventName] = []; futures[eventName].push(data); return this; } sendDebounce(eventName, ...data) { const parametersDebounce = __privateGet(this, _regularEvents).parametersDebounce; let timeOutConfig = parametersDebounce[eventName]; if (timeOutConfig) { clearTimeout(timeOutConfig.timeOut); timeOutConfig.timeOut = null; } else { timeOutConfig = parametersDebounce[eventName] = { wait: 1500, timeOut: null }; } timeOutConfig.timeOut = setTimeout( () => { this.send(eventName, ...data); }, timeOutConfig.wait ); return this; } setDebounceWaitTime(eventName, wait) { const timeOutConfig = __privateGet(this, _regularEvents).parametersDebounce[eventName]; if (timeOutConfig) { timeOutConfig.wait = wait; } else { __privateGet(this, _regularEvents).parametersDebounce[eventName] = { wait, timeOut: null }; } return this; } emit(eventName, ...data) { const callback = __privateGet(this, _regularEvents).events[eventName]; if (callback) { callback.apply(null, data); } return this; } off(eventName) { const events = __privateGet(this, _regularEvents).events; if (events[eventName]) { delete events[eventName]; return true; } const handlerEvents = __privateGet(this, _callbackEvents).events; if (handlerEvents[eventName]) { delete handlerEvents[eventName]; return true; } return false; } setInvokeInterval(interval) { __privateGet(this, _callbackEvents).callbackInterval = interval; } getEvents() { return { regularEvents: __privateGet(this, _regularEvents), callbackEvents: __privateGet(this, _callbackEvents) }; } } _regularEvents = new WeakMap(); _callbackEvents = new WeakMap(); _EventEmitter_instances = new WeakSet(); handlePendingEvents_fn = function(eventName, callback) { const futureEvents = __privateGet(this, _regularEvents).futures; if (futureEvents[eventName]) { for (const eventData of futureEvents[eventName]) { const preHandleData = __privateMethod(this, _EventEmitter_instances, executePreHandle_fn).call(this, eventName, eventData); callback.apply(null, preHandleData); } delete futureEvents[eventName]; } }; executePreHandle_fn = function(eventName, data) { const preHandles = __privateGet(this, _regularEvents).preHandles; const callback = preHandles[eventName]; if (callback) { return callback.apply(null, data); } return data; }; const eventEmitter = new EventEmitter();const group_url = "http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=tFU0xLt1uO5u5CXI2ktQRLh_XGAHBl7C&authKey=KAf4rICQYjfYUi66WelJAGhYtbJLILVWumOm%2BO9nM5fNaaVuF9Iiw3dJoPsVRUak&noverify=0&group_code=876295632"; const b_url = "https://space.bilibili.com/473239155"; const scriptCat_js_url = "https://scriptcat.org/zh-CN/script-show-page/4389"; const github_url = "https://github.com/hgztask/mk-MikuFansDataHive"; try { unsafeWindow["mk_favoritesdataacquisition"] = window; } catch (e) { console.warn("挂载脚本window环境到前端环境失败", e); } var globalValue = { group_url, b_url, scriptCat_js_url, github_url };gmUtil.addGMMenu("主面板", () => { eventEmitter.send("主面板开关"); }, "Q"); gmUtil.addGMMenu("脚本猫更新页", () => { gmUtil.openInTab("https://scriptcat.org/zh-CN/script-show-page/4389"); }, "Q"); gmUtil.addGMMenu("加入or反馈", () => { gmUtil.openInTab(globalValue.group_url); }, "T"); gmUtil.addGMMenu("关注作者", () => { gmUtil.openInTab(globalValue.b_url); }, "G");const wait = (milliseconds = 1e3) => { return new Promise((resolve) => setTimeout(resolve, milliseconds)); }; const fileDownload = (content, fileName) => { const element = document.createElement("a"); element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(content)); element.setAttribute("download", fileName); element.style.display = "none"; document.body.appendChild(element); element.click(); document.body.removeChild(element); }; function saveTextAsFile(text, filename = "data.txt") { const blob = new Blob([text], { type: "text/plain" }); const downloadLink = document.createElement("a"); downloadLink.href = URL.createObjectURL(blob); downloadLink.download = filename; document.body.appendChild(downloadLink); downloadLink.click(); setTimeout(() => { document.body.removeChild(downloadLink); URL.revokeObjectURL(downloadLink.href); }, 100); } const handleFileReader = (event) => { return new Promise((resolve, reject) => { const file = event.target.files[0]; if (!file) { reject("未读取到文件"); return; } let reader = new FileReader(); reader.onload = (e) => { const fileContent = e.target.result; resolve({ file, content: fileContent }); reader = null; }; reader.readAsText(file); }); }; const isIterable = (obj) => { return obj != null && typeof obj[Symbol.iterator] === "function"; }; const toTimeString = () => { return ( new Date()).toLocaleString(); }; function debounce(func, wait2 = 1e3) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait2); }; } function throttle(func, limit) { let inThrottle; return function(...args) { const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } const parseUrl = (urlString) => { const url = new URL(urlString); const pathSegments = url.pathname.split("/").filter((segment) => segment !== ""); const searchParams = new URLSearchParams(url.search.slice(1)); const queryParams = {}; for (const [key, value] of searchParams.entries()) { queryParams[key] = value; } return { protocol: url.protocol, hostname: url.hostname, port: url.port, pathname: url.pathname, pathSegments, search: url.search, queryParams, hash: url.hash }; }; const getLocalStorage = (key, isList = false, defaultValue = null) => { const item = localStorage.getItem(key); if (item === null) { return defaultValue; } if (isList) { try { return JSON.parse(item); } catch (e) { console.error(`读取localStorage时尝试转换${key}的值失败`, e); return defaultValue; } } return item; }; const formatTimestamp = (timestamp, options = {}) => { if (!timestamp || isNaN(timestamp)) return "Invalid Timestamp"; const ts = String(timestamp).length === 10 ? +timestamp * 1e3 : +timestamp; const timezoneOffset = (options.timezone || 0) * 60 * 60 * 1e3; const date = new Date(ts + timezoneOffset); if (isNaN(date.getTime())) return "Invalid Date"; const timeObj = { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(), hours: date.getUTCHours(), minutes: date.getUTCMinutes(), seconds: date.getUTCSeconds() }; if (options.returnObject) return timeObj; const format = options.format || "YYYY-MM-DD HH:mm:ss"; const pad = (n) => n.toString().padStart(2, "0"); return format.replace(/YYYY/g, timeObj.year).replace(/YY/g, String(timeObj.year).slice(-2)).replace(/MM/g, pad(timeObj.month)).replace(/M/g, timeObj.month).replace(/DD/g, pad(timeObj.day)).replace(/D/g, timeObj.day).replace(/HH/g, pad(timeObj.hours)).replace(/H/g, timeObj.hours).replace(/mm/g, pad(timeObj.minutes)).replace(/m/g, timeObj.minutes).replace(/ss/g, pad(timeObj.seconds)).replace(/s/g, timeObj.seconds); }; function initVueApp(el, App, props = {}) { return new Vue({ render: (h) => h(App, { props }) }).$mount(el); } const getUrlUID = (url) => { let uid; if (url.startsWith("http")) { const parse = parseUrl(url); uid = parse.pathSegments[0]?.trim(); return parseInt(uid); } const isDoYouHaveAnyParameters = url.indexOf("?"); const lastIndexOf = url.lastIndexOf("/"); if (isDoYouHaveAnyParameters === -1) { if (url.endsWith("/")) { const nTheIndexOfTheLastSecondOccurrenceOfTheSlash = url.lastIndexOf("/", url.length - 2); uid = url.substring(nTheIndexOfTheLastSecondOccurrenceOfTheSlash + 1, url.length - 1); } else { uid = url.substring(lastIndexOf + 1); } } else { uid = url.substring(lastIndexOf + 1, isDoYouHaveAnyParameters); } return parseInt(uid); }; const getUrlBV = (url) => { let match = url.match(/video\/(.+)\//); if (match === null) { match = url.match(/video\/(.+)\?/); } if (match === null) { match = url.match(/video\/(.+)/); } if (match !== null) { return match?.[1]?.trim(); } const { queryParams: { bvid = null } } = parseUrl(url); return bvid; }; var defUtil = { wait, fileDownload, toTimeString, getUrlUID, debounce, throttle, initVueApp, saveTextAsFile, parseUrl, handleFileReader, isIterable, getLocalStorage, formatTimestamp, getUrlBV };var script$7 = { data() { return { videoList: [], show: false }; }, methods: { handleClose() { this.videoList = []; } }, created() { eventEmitter.on("event:showVideoDataList", (list) => { this.videoList = list; this.show = true; }); } };function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { const options = typeof script === 'function' ? script.options : script; if (template && template.render) { options.render = template.render; options.staticRenderFns = template.staticRenderFns; options._compiled = true; } return script; } const __vue_script__$7 = script$7; var __vue_render__$7 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { visible: _vm.show, fullscreen: "" }, on: { "update:visible": function ($event) { _vm.show = $event; }, close: _vm.handleClose, }, scopedSlots: _vm._u([ { key: "title", fn: function () { return [_vm._v("收藏夹视频列表")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c( "div", [ _vm._v("\n 数量:\n "), _c("el-tag", [_vm._v(_vm._s(_vm.videoList.length))]), _vm._v(" "), _c( "el-table", { attrs: { data: _vm.videoList, border: "", stripe: "" } }, [ _c("el-table-column", { attrs: { label: "用户名", prop: "name", width: "200" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "标题", prop: "title", width: "700" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "时长", prop: "originalDuration" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "弹幕", prop: "bulletChat" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "播放", prop: "views" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "收藏名", prop: "favName" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "收藏Id", prop: "favId" }, }), _vm._v(" "), _c("el-table-column", { attrs: { label: "封面", width: "250" }, scopedSlots: _vm._u([ { key: "default", fn: function (scope) { return [ _c("el-image", { staticStyle: { width: "222px", height: "125px" }, attrs: { src: scope.row.imgSrc, fit: "cover" }, }), ] }, }, ]), }), ], 1 ), ], 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$7 = []; __vue_render__$7._withStripped = true; const __vue_inject_styles__$7 = undefined; const __vue_component__$7 = normalizeComponent( { render: __vue_render__$7, staticRenderFns: __vue_staticRenderFns__$7 }, __vue_inject_styles__$7, __vue_script__$7);const isDOMElement = (obj) => { return obj !== null && typeof obj === "object" && "nodeType" in obj; }; const inProgressCache = new Map(); const validationElFun = (config, selector) => { const element = config.doc.querySelector(selector); if (element === null) return null; return config.parseShadowRoot && element.shadowRoot ? element.shadowRoot : element; }; const __privateValidationElFun = (config, selector) => { const result = config.validationElFun(config, selector); return isDOMElement(result) ? result : null; }; const findElement = async (selector, config = {}) => { const defConfig = { doc: document, interval: 1e3, timeout: -1, parseShadowRoot: false, cacheInProgress: true, validationElFun }; config = { ...defConfig, ...config }; const result = __privateValidationElFun(config, selector); if (result !== null) return result; const cacheKey = `findElement:${selector}`; if (config.cacheInProgress) { const cachedPromise = inProgressCache.get(cacheKey); if (cachedPromise) { return cachedPromise; } } const p = new Promise((resolve) => { let timeoutId, IntervalId; IntervalId = setInterval(() => { const result2 = __privateValidationElFun(config, selector); if (result2 === null) return; resolve(result2); }, config.interval); const cleanup = () => { if (IntervalId) clearInterval(IntervalId); if (timeoutId) clearTimeout(timeoutId); if (config.cacheInProgress) { inProgressCache.delete(cacheKey); } }; if (config.timeout > 0) { timeoutId = setTimeout(() => { resolve(null); cleanup(); }, config.timeout); } }); if (config.cacheInProgress) { inProgressCache.set(cacheKey, p); } return p; }; const findElementChain = (selector, config = {}) => { const paths = []; const chainObj = { find(childSelector, childConfig = {}) { if (config.allparseShadowRoot) { childConfig.parseShadowRoot = true; } childSelector.trim(); if (childSelector === "" || childSelector.search(/^\d/) !== -1) { throw new Error("非法的元素选择器"); } const separator = config.separator ?? childConfig.separator; if (separator === void 0 || separator === null || separator.trim() === "") { paths.push({ selector: childSelector, config: childConfig }); } else { const selectorArr = childSelector.split(separator); if (selectorArr.length === 1) { paths.push({ selector: childSelector, config: childConfig }); } else { for (let s of selectorArr) { s = s.trim(); if (s === "") continue; childConfig.originalSelector = childSelector; paths.push({ selector: s, config: childConfig }); } } } return this; }, get() { return new Promise(async (resolve) => { let currentDoc = null; for ({ selector, config } of paths) { const resolvedConfig = { ...config }; if (config.doc === null || config.doc === void 0) { resolvedConfig.doc = currentDoc ?? document; } else { resolvedConfig.doc = config.doc; } const res = await findElement(selector, resolvedConfig); if (res === null) { continue; } currentDoc = res; } resolve(currentDoc); }); } }; chainObj.find(selector, config); return chainObj; }; const findElements = async (selector, config = {}) => { const defConfig = { doc: document, interval: 1e3, timeout: -1, parseShadowRoot: false }; config = { ...defConfig, ...config }; return new Promise((resolve) => { const i1 = setInterval(() => { const els = config.doc.querySelectorAll(selector); if (els.length > 0) { const list = []; for (const el of els) { if (config.parseShadowRoot) { const shadowRoot = el?.shadowRoot; list.push(shadowRoot ? shadowRoot : el); continue; } list.push(el); } resolve(list); clearInterval(i1); } }, config.interval); if (config.timeout > 0) { setTimeout(() => { clearInterval(i1); resolve([]); }, config.timeout); } }); }; const findElementsAndBindEvents = (css, callback, config = {}) => { config = { ...{ interval: 2e3, timeOut: 3e3 }, config }; setTimeout(() => { findElement(css, { interval: config.interval }).then((el) => { el.addEventListener("click", () => { callback(); }); }); }, config.timeOut); }; const hoverTimeoutEvents = new Map(); const addHoverTimeoutEvent = (el, callback, timeout = 2e3) => { if (typeof el === "string") { el = document.querySelector(el); } if (el === null) return false; const attribute = el.getAttribute("data-hover-timeout"); if (attribute !== null) return false; el.setAttribute("data-hover-timeout", ""); let time = null; const mouseenter = (e) => { time = setTimeout(() => { callback(e); }, timeout); }; const mouseleave = () => { if (time === null) { return; } clearTimeout(time); }; hoverTimeoutEvents.set(el, { mouseenter, mouseleave }); el.addEventListener("mouseenter", mouseenter); el.addEventListener("mouseleave", mouseleave); return true; }; const removeHoverTimeoutEvent = (el) => { const attribute = el.getAttribute("data-hover-timeout"); if (attribute === null) return false; el.removeEventListener("mouseenter", hoverTimeoutEvents.get(el).mouseenter); el.removeEventListener("mouseleave", hoverTimeoutEvents.get(el).mouseleave); return true; }; const updateCssVModal = () => { const styleEl = document.createElement("style"); styleEl.innerHTML = `.v-modal { z-index: auto !important; }`; document.head.appendChild(styleEl); }; const installStyle = (cssText, selector = ".mk-def-style") => { let styleEl = document.head.querySelector(selector); if (styleEl === null) { styleEl = document.createElement("style"); if (selector.startsWith("#")) { styleEl.id = selector.substring(1); } else { styleEl.className = selector.substring(1); } document.head.appendChild(styleEl); } styleEl.textContent = cssText; }; const createVueDiv = (el = null, cssTests = null) => { const panelDiv = document.createElement("div"); if (cssTests !== null) { panelDiv.style.cssText = cssTests; } const vueDiv = document.createElement("div"); panelDiv.appendChild(vueDiv); if (el !== null) { el.appendChild(panelDiv); } return { panelDiv, vueDiv }; }; var elUtil = { findElement, isDOMElement, addHoverTimeoutEvent, removeHoverTimeoutEvent, findElements, findElementChain, findElementsAndBindEvents, updateCssVModal, installStyle, createVueDiv };const toPlayCountOrBulletChat = (str) => { if (!str) { return -1; } str = str.split(/[\t\r\f\n\s]*/g).join(""); const replace = str.replace(/[^\d.]/g, ""); if (str.endsWith("万") || str.endsWith("万次") || str.endsWith("万弹幕")) { return parseFloat(replace) * 1e4; } if (str.endsWith("次") || str.endsWith("弹幕")) { return parseInt(replace); } return parseInt(str); }; const timeStringToSeconds = (timeStr) => { if (!timeStr) { return -1; } const parts = timeStr.split(":"); switch (parts.length) { case 1: return Number(parts[0]); case 2: return Number(parts[0]) * 60 + Number(parts[1]); case 3: return Number(parts[0]) * 3600 + Number(parts[1]) * 60 + Number(parts[2]); default: throw new Error("Invalid time format"); } }; var SFormatUtil = { toPlayCountOrBulletChat, timeStringToSeconds };const getCurrentFavName = async () => { const el = await elUtil.findElement(".favlist-main .vui_ellipsis.multi-mode"); return el.textContent.trim(); }; const getMyFollowAuthorInfo = async () => { const el = await elUtil.findElement(".favlist-info-detail__title a"); const text = el.textContent.trim(); const name = text.match(/UP主:(.+)\s/)[1]; const uid = defUtil.getUrlUID(el.href); return { name, uid }; }; const getVideoDataList = async () => { const els = await elUtil.findElements(".items>.items__item"); const favName = await getCurrentFavName(); const list = []; const queryParams = defUtil.parseUrl(location.href).queryParams; const favType = queryParams["ftype"]; const favId = queryParams["fid"]; for (const el of els) { const titleEl = el.querySelector(".bili-video-card__title"); const titleAEl = el.querySelector("a"); const imgEl = el.querySelector(".bili-cover-card__thumbnail>img"); const title = titleEl.textContent.trim(); const stats = el.querySelectorAll(".bili-cover-card__stats>div>span"); let name, uid; if (favType === "create") { const userEl = el.querySelector(".bili-video-card__author"); name = userEl.querySelector("span[title]").textContent.trim(); uid = defUtil.getUrlUID(userEl.href); } else { const authorInfo = await getMyFollowAuthorInfo(); name = authorInfo.name; uid = authorInfo.uid; } const nameMatch = name.match(/(.*?) · 收藏于/); if (nameMatch !== null) { name = nameMatch[1]; } const bv = defUtil.getUrlBV(titleAEl.href); let views, bulletChat, duration, originalDuration = "", imgSrc = null; if (stats.length === 0 && title === "已失效视频") { views = -1; bulletChat = -1; duration = -1; } else { views = SFormatUtil.toPlayCountOrBulletChat(stats[0].textContent.trim()); bulletChat = SFormatUtil.toPlayCountOrBulletChat(stats[1].textContent.trim()); originalDuration = stats[2].textContent.trim(); const durationStr = SFormatUtil.timeStringToSeconds(originalDuration); duration = parseInt(durationStr); if (imgEl !== null) { imgSrc = imgEl.src; } } list.push({ title, name, uid, bv, views, bulletChat, duration, imgSrc, originalDuration, favName, favId }); } return list; }; window.getVideoDataList = getVideoDataList; const getCurrenFavAllPageDataList = async () => { const list = []; const nextPageBut = await elUtil.findElement(".vui_pagenation--btn-side.vui_pagenation--btn:last-child"); while (nextPageBut.disabled === false) { await defUtil.wait(1500); const videoList = await getVideoDataList(); for (let data of videoList) { if (list.some((item) => item.bv === data.bv)) { continue; } list.push(data); } nextPageBut.click(); } return list; }; var personalFav = { getVideoDataList, getCurrenFavAllPageDataList, getCurrentFavName };const getLeftSidebarItemActive = () => { const el = document.querySelector(".vui_sidebar-item--active .vui_ellipsis.multi-mode"); if (el === null) return null; return el.textContent.trim(); }; const getNavTabItemActive = async () => { const el = await elUtil.findElement(".nav-tab a.active .nav-tab__item-text"); return el.textContent.trim(); }; var favCommon = { getLeftSidebarItemActive, getNavTabItemActive };var script$6 = { data() { return { appendModeV: false, videoList: [] }; }, methods: { addTo(list) { for (let data of list) { if (this.videoList.some((item) => item.bv === data.bv)) { continue; } this.videoList.push(data); } }, getCurrentDataListBut() { const itemActive = favCommon.getLeftSidebarItemActive(); if (itemActive === null || ["话题收藏", "图文收藏夹", "课程收藏夹"].includes(itemActive)) { this.$message.warning("请选择视频类收藏夹"); return; } const loading = this.$loading({ text: "获取当前页中,尽量不要进行任何操作" }); personalFav.getVideoDataList().then((list) => { loading.close(); console.log("当前页收藏视频列表数据", list); if (this.appendModeV) { this.addTo(list); } else { this.videoList = list; } this.$alert("已获取当前页收藏视频列表数据,共" + list.length + "个"); }); }, async getCurrenFavAllPageDataListBut() { const itemActive = favCommon.getLeftSidebarItemActive(); if (itemActive === null || ["话题收藏", "图文收藏夹", "课程收藏夹"].includes(itemActive)) { this.$message.warning("请选择视频类收藏夹"); return; } const loading = this.$loading({ text: "获取中,尽量不要进行任何操作" }); const favName = await personalFav.getCurrentFavName(); const list = await personalFav.getCurrenFavAllPageDataList(); console.log(`当前${favName}收藏夹所有页数据`, list); if (this.appendModeV) { this.addTo(list); } else { this.videoList = list; } loading.close(); await this.$alert(`已获取${favName}收藏夹所有页数据,共${list.length}个,可在面板中选择导出`, { type: "success" }); }, lookDataBut() { const tempList = this.videoList; if (tempList.length === 0) return; eventEmitter.send("event:showVideoDataList", tempList); }, clearDataListBut() { this.videoList = []; }, printConsoleBut() { if (this.videoList.length === 0) { this.$message.warning("请先获取收藏夹视频列表数据"); return; } console.log(JSON.parse(JSON.stringify(this.videoList))); this.$notify({ message: "已打印到控制台" }); }, outToFileBut() { if (this.videoList.length === 0) { this.$message.warning("请先获取收藏夹视频列表数据"); return; } personalFav.getCurrentFavName().then((favName) => { const filename = favName + "收藏夹视频列表.json"; defUtil.fileDownload(JSON.stringify(this.videoList), filename); this.$notify({ message: `${filename}已导出,需按需保存到本地.` }); }); } } }; const __vue_script__$6 = script$6; var __vue_render__$6 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("收藏夹数据获取")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-switch", { attrs: { "active-text": "追加模式" }, model: { value: _vm.appendModeV, callback: function ($$v) { _vm.appendModeV = $$v; }, expression: "appendModeV", }, }), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("el-button", { on: { click: _vm.getCurrentDataListBut } }, [ _vm._v("获取视频列表(当前页)"), ]), _vm._v(" "), _c( "el-button", { on: { click: _vm.getCurrenFavAllPageDataListBut } }, [_vm._v("获取所有页(当前收藏夹)")] ), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("导出")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-button", { on: { click: _vm.printConsoleBut } }, [ _vm._v("打印到控制台"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.outToFileBut } }, [ _vm._v("导出文件"), ]), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("当前数据量")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-tag", [_vm._v(_vm._s(_vm.videoList.length))]), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("el-button", { on: { click: _vm.clearDataListBut } }, [ _vm._v("清除登记数据"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.lookDataBut } }, [ _vm._v("查看数据"), ]), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("说明")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("div", [_vm._v("数据是去重的,即相同bv的视频只记录一次")]), _vm._v(" "), _c("div", [ _vm._v("勾选追加模式时,可追加数据, 否则记录数据量将覆盖"), ]), ] ), ], 1 ) }; var __vue_staticRenderFns__$6 = []; __vue_render__$6._withStripped = true; const __vue_inject_styles__$6 = undefined; const __vue_component__$6 = normalizeComponent( { render: __vue_render__$6, staticRenderFns: __vue_staticRenderFns__$6 }, __vue_inject_styles__$6, __vue_script__$6);const getDataList = async () => { const elList = await elUtil.findElements(".container>.item"); const list = []; for (let el of elList) { const imgEl = el.querySelector("img"); const titleEl = el.querySelector(".opus-card__title"); const subTitleEl = el.querySelector(".opus-card__subtitle"); const aEl = el.querySelector('a[href*="www.bilibili.com/opus/"]'); const stats = el.querySelectorAll(".bili-cover-card__stats span:not(:empty),.opus-card__icons>span"); let desc; if (imgEl === null) { const txtEl = el.querySelector(".opus-card__text"); desc = txtEl.textContent.trim(); } else { desc = titleEl.textContent.trim(); } const href = aEl.href; const subTitle = subTitleEl.textContent.trim(); const subTitleList = subTitle.split("·"); const name = subTitleList[0].trim(); const date = subTitleList[1].trim(); const imgSrc = imgEl ? imgEl.src : ""; const view = parseInt(stats[0].textContent.trim()); const like = parseInt(stats[1].textContent.trim()); list.push({ desc, name, date, imgSrc, view, like, href }); } return list; }; var graphicFav = { getDataList };var script$5 = { data() { return { list: [] }; }, methods: { getDataListBut() { const itemActive = favCommon.getLeftSidebarItemActive(); if (itemActive === null || "图文收藏夹" !== itemActive) { this.$message.warning("请选择图文收藏夹"); return; } const loading = this.$loading({ text: "获取中,尽量不要进行任何操作" }); graphicFav.getDataList().then((list) => { loading.close(); console.log(list); this.list = list; }); }, outToConsoleBut() { const tempList = this.list; if (tempList.length === 0) return; console.log("当前图文收藏夹数据", JSON.parse(JSON.stringify(tempList))); }, outToFIleBut() { const tempList = this.list; if (tempList.length === 0) return; defUtil.fileDownload(JSON.stringify(tempList), "图文收藏夹数据.json"); this.$alert("已导出,需按需保存到本地"); } } }; const __vue_script__$5 = script$5; var __vue_render__$5 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("图文数据获取")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-button", { on: { click: _vm.getDataListBut } }, [ _vm._v("获取"), ]), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("导出")] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("el-button", { on: { click: _vm.outToConsoleBut } }, [ _vm._v("导出控制台"), ]), _vm._v(" "), _c("el-button", { on: { click: _vm.outToFIleBut } }, [ _vm._v("导出文件"), ]), ], 1 ), _vm._v(" "), _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_vm._v("数据量")] }, proxy: true, }, ]), }, [_vm._v(" "), _c("el-tag", [_vm._v(_vm._s(_vm.list.length))])], 1 ), ], 1 ) }; var __vue_staticRenderFns__$5 = []; __vue_render__$5._withStripped = true; const __vue_inject_styles__$5 = undefined; const __vue_component__$5 = normalizeComponent( { render: __vue_render__$5, staticRenderFns: __vue_staticRenderFns__$5 }, __vue_inject_styles__$5, __vue_script__$5);var script$4 = { data() { return { group_url: globalValue.group_url, scriptCat_js_url: globalValue.scriptCat_js_url, b_url: globalValue.b_url, github_url: globalValue.github_url, update_log_url: globalValue.update_log_url, activeName: ["1", "2"] }; }, methods: { lookImgBut() { eventEmitter.send("显示图片对话框", { image: "https://www.mikuchase.ltd/img/qq_group_876295632.webp" }); } } }; const __vue_script__$4 = script$4; var __vue_render__$4 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-collapse", { model: { value: _vm.activeName, callback: function ($$v) { _vm.activeName = $$v; }, expression: "activeName", }, }, [ _c( "el-collapse-item", { attrs: { name: "1", title: "作者b站" } }, [ _c( "el-link", { attrs: { href: _vm.b_url, target: "_blank", type: "primary" }, }, [_vm._v("b站传送门")] ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "2", title: "反馈交流群" } }, [ _c( "el-link", { attrs: { href: _vm.group_url, target: "_blank", type: "primary", }, }, [_vm._v("====》Q群传送门《====\n ")] ), _vm._v(" "), _c( "el-tooltip", { attrs: { content: "点击查看群二维码" } }, [ _c("el-tag", { on: { click: _vm.lookImgBut } }, [ _vm._v("876295632"), ]), ], 1 ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "3", title: "脚本猫更新地址" } }, [ _vm._v("\n 目前仅在脚本猫平台上更新发布\n "), _c( "el-link", { attrs: { href: _vm.scriptCat_js_url, target: "_blank", type: "primary", }, }, [_vm._v("脚本猫更新地址")] ), ], 1 ), _vm._v(" "), _c( "el-collapse-item", { attrs: { name: "4", title: "开源地址" } }, [ _c("div", [_vm._v("本脚本源代码已开源,欢迎大家Star或提交PR")]), _vm._v(" "), _c( "el-link", { attrs: { href: _vm.github_url, target: "_blank", type: "primary", }, }, [_vm._v("github开源地址")] ), ], 1 ), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$4 = []; __vue_render__$4._withStripped = true; const __vue_inject_styles__$4 = undefined; const __vue_component__$4 = normalizeComponent( { render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 }, __vue_inject_styles__$4, __vue_script__$4);var script$3 = { data() { return { show: false, title: "图片查看", imgList: [], imgSrc: "", isModal: true }; }, created() { eventEmitter.on("显示图片对话框", ({ image, title, images, isModal }) => { this.imgSrc = image; if (title) { this.title = title; } if (images) { this.imgList = images; } else { this.imgList = [image]; } if (isModal) { this.isModal = isModal; } this.show = true; }); } }; const __vue_script__$3 = script$3; var __vue_render__$3 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-dialog", { attrs: { modal: _vm.isModal, title: _vm.title, visible: _vm.show, center: "", }, on: { "update:visible": function ($event) { _vm.show = $event; }, }, }, [ _c( "div", { staticClass: "el-vertical-center" }, [ _c("el-image", { attrs: { "preview-src-list": _vm.imgList, src: _vm.imgSrc }, }), ], 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$3 = []; __vue_render__$3._withStripped = true; const __vue_inject_styles__$3 = undefined; const __vue_component__$3 = normalizeComponent( { render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 }, __vue_inject_styles__$3, __vue_script__$3);const getDrawerShortcutKeyGm = () => { return gmUtil.getData("get_drawer_shortcut_key_gm", "`"); };var script$2 = { data() { return { drawerShortcutKeyVal: getDrawerShortcutKeyGm(), theKeyPressedKeyVal: "" }; }, methods: { setDrawerShortcutKeyBut() { const theKeyPressedKey = this.theKeyPressedKeyVal; const drawerShortcutKey = this.drawerShortcutKeyVal; if (drawerShortcutKey === theKeyPressedKey) { this.$message("不需要重复设置"); return; } gmUtil.setData("drawer_shortcut_key_gm", theKeyPressedKey); this.$notify({ message: "已设置打开关闭主面板快捷键", type: "success" }); this.drawerShortcutKeyVal = theKeyPressedKey; } }, created() { eventEmitter.on("event-keydownEvent", (event) => { this.theKeyPressedKeyVal = event.key; }); } }; const __vue_script__$2 = script$2; var __vue_render__$2 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_c("span", [_vm._v("快捷键")])] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("div", [ _vm._v("1.默认情况下,按键盘tab键上的~键为展开关闭主面板"), ]), _vm._v(" "), _c( "div", [ _vm._v("2.当前展开关闭主面板快捷键:\n "), _c("el-tag", [_vm._v(_vm._s(_vm.drawerShortcutKeyVal))]), ], 1 ), _vm._v("\n 当前按下的键\n "), _c("el-tag", [_vm._v(_vm._s(_vm.theKeyPressedKeyVal))]), _vm._v(" "), _c("el-button", { on: { click: _vm.setDrawerShortcutKeyBut } }, [ _vm._v("设置打开关闭主面板快捷键"), ]), ], 1 ), ], 1 ) }; var __vue_staticRenderFns__$2 = []; __vue_render__$2._withStripped = true; const __vue_inject_styles__$2 = undefined; const __vue_component__$2 = normalizeComponent( { render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 }, __vue_inject_styles__$2, __vue_script__$2);var script$1 = { data() { return { list: [ { name: "支付宝赞助", alt: "支付宝支持", src: "https://www.mikuchase.ltd/img/paymentCodeZFB.webp" }, { name: "微信赞助", alt: "微信支持", src: "https://www.mikuchase.ltd/img/paymentCodeWX.webp" }, { name: "QQ赞助", alt: "QQ支持", src: "https://www.mikuchase.ltd/img/paymentCodeQQ.webp" } ], dialogIni: { title: "打赏点猫粮", show: false, srcList: [] } }; }, methods: { showDialogBut() { this.dialogIni.show = true; } }, created() { this.dialogIni.srcList = this.list.map((x) => x.src); } }; const __vue_script__$1 = script$1; var __vue_render__$1 = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-card", { attrs: { shadow: "never" }, scopedSlots: _vm._u([ { key: "header", fn: function () { return [_c("span", [_vm._v("零钱赞助")])] }, proxy: true, }, ]), }, [ _vm._v(" "), _c("span", [_vm._v("1元不嫌少,10元不嫌多,感谢支持!")]), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("span", [_vm._v("生活不易,作者叹息")]), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c("span", [_vm._v("用爱发电不容易,您的支持是我最大的更新动力")]), ], 1 ), _vm._v(" "), _c("el-divider"), _vm._v(" "), _c( "div", { staticClass: "el-vertical-center" }, [ _c("el-avatar", { attrs: { size: "large", src: "//i0.hdslb.com/bfs/face/87e9c69a15f7d2b68294be165073c8e07a541e28.jpg@128w_128h_1c_1s.webp", }, }), ], 1 ), _vm._v(" "), _c( "div", { staticClass: "el-vertical-center" }, [ _c( "el-button", { attrs: { round: "", type: "primary" }, on: { click: _vm.showDialogBut }, }, [_vm._v("打赏点猫粮")] ), ], 1 ), _vm._v(" "), _c( "el-dialog", { attrs: { title: _vm.dialogIni.title, visible: _vm.dialogIni.show, center: "", }, on: { "update:visible": function ($event) { return _vm.$set(_vm.dialogIni, "show", $event) }, }, }, [ _c( "div", { staticClass: "el-vertical-center" }, _vm._l(_vm.list, function (item) { return _c("el-image", { key: item.name, staticStyle: { height: "300px" }, attrs: { "preview-src-list": _vm.dialogIni.srcList, src: item.src, }, }) }), 1 ), ] ), ], 1 ) }; var __vue_staticRenderFns__$1 = []; __vue_render__$1._withStripped = true; const __vue_inject_styles__$1 = undefined; const __vue_component__$1 = normalizeComponent( { render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 }, __vue_inject_styles__$1, __vue_script__$1);var script = { components: { ShowImgDialog: __vue_component__$3, GraphicFavTabView: __vue_component__$5, ShowVideoDataTableDialog: __vue_component__$7, VideoTabView: __vue_component__$6, AboutAndFeedbackView: __vue_component__$4, PanelSettingsView: __vue_component__$2, DonateLayoutView: __vue_component__$1 }, data() { return { drawer: false }; }, created() { eventEmitter.on("主面板开关", () => { this.drawer = !this.drawer; }); document.addEventListener("keydown", (event) => { eventEmitter.emit("event-keydownEvent", event); if (event.key === getDrawerShortcutKeyGm()) { this.drawer = !this.drawer; } }); } }; const __vue_script__ = script; var __vue_render__ = function () { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c( "div", [ _c( "el-drawer", { staticStyle: { position: "fixed" }, attrs: { modal: false, visible: _vm.drawer, direction: "rtl", size: "25%", title: "b站数据采集", }, on: { "update:visible": function ($event) { _vm.drawer = $event; }, }, }, [ _c( "el-tabs", { attrs: { "tab-position": "left", type: "border-card" } }, [ _c( "el-tab-pane", { attrs: { label: "视频收藏夹", lazy: "" } }, [_c("VideoTabView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { label: "图文收藏夹", lazy: "" } }, [_c("GraphicFavTabView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { label: "面板设置", lazy: "" } }, [_c("PanelSettingsView")], 1 ), _vm._v(" "), _c( "el-tab-pane", { attrs: { label: "关于与反馈", lazy: "" } }, [ _c("AboutAndFeedbackView"), _vm._v(" "), _c("DonateLayoutView"), ], 1 ), ], 1 ), _vm._v("\n " + _vm._s(_vm.keyArr) + "\n "), ], 1 ), _vm._v(" "), _c("ShowVideoDataTableDialog"), _vm._v(" "), _c("ShowImgDialog"), ], 1 ) }; var __vue_staticRenderFns__ = []; __vue_render__._withStripped = true; const __vue_inject_styles__ = undefined; const __vue_component__ = normalizeComponent( { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, __vue_inject_styles__, __vue_script__);if (document.head.querySelector("#element-ui-css") === null) { const linkElement = document.createElement("link"); linkElement.rel = "stylesheet"; linkElement.href = "https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css"; linkElement.id = "element-ui-css"; document.head.appendChild(linkElement); linkElement.addEventListener("load", () => { console.log("element-ui样式加载完成"); }); } const { vueDiv } = elUtil.createVueDiv(document.body); window.mk_vue_app = defUtil.initVueApp(vueDiv, __vue_component__);const staticRoute = (title, url) => { console.log("静态路由", title, url); }; const dynamicRouting = (title, url) => { console.log("动态路由", title, url); }; var router = { staticRoute, dynamicRouting };const addEventListenerUrlChange = (callback) => { let oldUrl = window.location.href; setInterval(() => { const newUrl = window.location.href; if (oldUrl === newUrl) return; oldUrl = newUrl; const title = document.title; callback(newUrl, oldUrl, title); }, 1e3); }; const addEventListenerNetwork = (callback) => { const performanceObserver = new PerformanceObserver(() => { const entries = performance.getEntriesByType("resource"); const windowUrl = window.location.href; const winTitle = document.title; for (let entry of entries) { const url = entry.name; const initiatorType = entry.initiatorType; if (initiatorType === "img" || initiatorType === "css" || initiatorType === "link" || initiatorType === "beacon") { continue; } try { callback(url, windowUrl, winTitle, initiatorType); } catch (e) { if (e.message === "stopPerformanceObserver") { performanceObserver.disconnect(); console.log("检测到当前页面在排除列表中,已停止性能观察器对象实例", e); break; } throw e; } } performance.clearResourceTimings(); }); performanceObserver.observe({ entryTypes: ["resource"] }); }; function watchElementListLengthWithInterval(selector, callback, config = {}) { const defConfig = {}; config = { ...defConfig, ...config }; let previousLength = -1; const timer = setInterval( () => { if (previousLength === -1) { previousLength = document.querySelectorAll(selector).length; return; } const currentElements = document.querySelectorAll(selector); const currentLength = currentElements.length; if (currentLength !== previousLength) { previousLength = currentLength; callback( { action: currentLength > previousLength ? "add" : "del", elements: currentElements, length: currentLength } ); } }, config.interval ); return stop = () => { clearInterval(timer); }; } var watch = { addEventListenerUrlChange, addEventListenerNetwork, watchElementListLengthWithInterval };window.globalElUtil = elUtil; window.addEventListener("load", () => { console.log("页面加载完成"); router.staticRoute(document.title, window.location.href); watch.addEventListenerUrlChange((newUrl, oldUrl, title) => { router.dynamicRouting(title, newUrl); }); });})(Vue);