您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
吧啦吧啦
当前为
// ==UserScript== // @name 自用bilibili脚本 // @namespace mimiko/bilibili // @version 0.0.25 // @description 吧啦吧啦 // @author Mimiko // @license MIT // @match *://*.bilibili.com/* // @grant GM.addStyle // grant GM.registerMenuCommand // grant GM.xmlHttpRequest // @run-at document-end // ==/UserScript== // http://greasyfork.icu/zh-CN/scripts/436748-%E8%87%AA%E7%94%A8bilibili%E8%84%9A%E6%9C%AC // interface "use strict"; (() => { if (window.top !== window.self) return; // variables /** 工具类 */ const Z = { /** 添加样式 */ addStyle: (listContent, ...listItem) => { const content = listContent .map((it, i) => `${it}${listItem[i] ?? ""}`) .join(""); GM.addStyle(content); }, /** 防抖函数 */ debounce: (fn, delay) => { let timer = 0; return (...args) => { if (timer) window.clearTimeout(timer); timer = window.setTimeout(() => fn(...args), delay); }; }, /** 输出日志 */ echo: (...args) => console.log(...args), /** 获取元素;异步函数,会重复执行直至目标元素出现 */ getElements: ( selector, ) => new Promise((resolve) => { const fn = () => { if (document.hidden) return; const $el = document.querySelectorAll(selector); if (!$el.length) return; window.clearInterval(timer); resolve([...$el]); }; const timer = window.setInterval(fn, 50); fn(); }), /** 隐藏元素;通过在目标元素上写入内联样式实现 */ hideElements: ( ...listSelector ) => { Z.addStyle` ${listSelector .map((selector) => `${selector} { display: none !important; }`) .join("\n")} `; }, /** 从本地存储中读取数据 */ load: (name) => { try { const data = localStorage.getItem(`mimiko-gms/${name}`); if (!data) return null; return JSON.parse(data); } catch (e) { alert(`读取缓存失败:${e.message}`); return null; } }, /** 移除元素 */ removeElements: (selector) => document.querySelectorAll(selector).forEach(($it) => $it.remove()), /** 运行函数 */ run: (fn) => fn(), /** 保存数据到本地存储 */ save: (name, data) => localStorage.setItem(`mimiko-gms/${name}`, JSON.stringify(data)), /** 等待一段时间 */ sleep: (ts) => new Promise((resolve) => setTimeout(resolve, ts)), }; /** 本地存储;目前用于记录哪些视频已经在首页出现过 */ const Cache = { /** 记录视频上限 */ limit: 5e4, /** 视频列表 */ list: Z.load("cache-recommend") ?? [], /** 添加视频 */ add: (id) => { const it = Cache.get(id); const it2 = [id, it[1] + 1]; const list2 = Cache.list.filter((it) => it[0] !== id); list2.push(it2); Cache.list = list2.slice(-Cache.limit); }, /** 清空视频 */ clear: () => { Cache.list = []; Cache.save(); }, /** 获取视频 */ get: (id) => { return Cache.list.find((it) => it[0] === id) ?? [id, 0]; }, /** 保存记录 */ save: () => { Z.save("cache-recommend", Cache.list); }, }; /** 路由 */ const Router = { /** 计数 */ count: { index: 0, unknown: 0, video: 0, }, /** 路由表 */ map: { index: [], unknown: [], video: [], }, /** 路由 */ path: "", /** 初始化 */ init: () => { const fn = () => { const { pathname } = window.location; if (pathname === Router.path) return; Router.path = pathname; const name = Z.run(() => { if (Router.is("index")) return "index"; if (Router.is("video")) return "video"; return "unknown"; }); if (name === "unknown") return; Router.map[name].forEach(async (it) => { if (Router.count[name] === 0) it[1] = await it[0](); if (typeof it[2] === "function") it[2](); if (typeof it[1] === "function") it[2] = await it[1](); Router.count[name]++; }); }; window.setInterval(fn, 200); return Router; }, /** 判断路由 */ is: (name) => { const { pathname } = window.location; if (name === "index") return ["/", "/index.html"].includes(pathname); if (name === "video") return pathname.startsWith("/video/"); return true; }, /** 监听路由 */ watch: (name, fn) => { Router.map[name].push([fn, undefined, undefined]); return Router; }, }; // functions /** 首页 */ const asIndex = async () => { Z.addStyle` body { min-width: auto; } .container:first-child { display: none; } .feed-roll-btn button:first-of-type { height: ${Z.run(() => { const picture = document.querySelector(".feed-card picture"); if (!picture) return 135; const { height } = picture.getBoundingClientRect(); return height; })}px !important; } .feed-card { display: block !important; margin-top: 0 !important; } .feed-card.is-hidden { position: relative; } .feed-card.is-hidden .bili-video-card { transition: opacity 0.3s; opacity: 0.1; } .feed-card.is-hidden .bili-video-card:hover { opacity: 1; } .feed-card.is-hidden .reason { position: absolute; width: 160px; height: 32px; left: 50%; top: 32%; text-align: center; line-height: 32px; background-color: rgba(0, 0, 0, 0.8); color: #fff; font-size: 12px; border-radius: 4px; pointer-events: none; transform: translate(-50%, -50%); transition: opacity 0.3s; z-index: 1; } .feed-card.is-hidden:hover .reason { opacity: 0; } `; // container const [container] = await Z.getElements(".container"); const ctn = document.createElement("div"); container.classList.forEach((it) => ctn.classList.add(it)); container.parentNode?.append(ctn); // add cache clear button const [groupBtn] = await Z.getElements(".feed-roll-btn"); const btnClear = document.createElement("button"); btnClear.classList.add("primary-btn", "roll-btn"); btnClear.innerHTML = "<span>✖</span>"; btnClear.setAttribute("title", "清空缓存"); btnClear.addEventListener("click", () => { Cache.clear(); alert("缓存已清空"); }); btnClear.style.marginTop = "20px"; groupBtn.append(btnClear); return () => { const hide = () => { observer.disconnect(); const listItem = [...container.children].filter((it) => it.classList.contains("feed-card"), ); listItem.forEach((it) => { const check = () => { // play count too low const stats = it.querySelector(".bili-video-card__stats"); if (!stats) return "播放量不存在"; const text = stats.textContent; if (!text) return "播放量为空"; if (!text.includes("万")) return "播放量不足1万"; const amount = parseFloat(text.split("万")[0]); if (amount < 3) return "播放量不足3万"; // has been viewed const link = it.querySelector("a"); if (!link) return "链接不存在"; // info const info = it.querySelector(".bili-video-card__info--icon-text"); const id = link.href.replace(/.*\/BV/, "").replace(/\/\?.*/, ""); if (Cache.get(id)[1] > (info?.textContent === "已关注" ? 2 : 0)) return `已出现${Cache.get(id)[1]}次`; Cache.add(id); return ""; }; const reason = check(); if (!reason) { ctn.prepend(it); return; } if (reason === "播放量不存在") return; it.classList.add("is-hidden"); const tip = document.createElement("div"); tip.classList.add("reason"); tip.textContent = reason; it.append(tip); ctn.append(it); }); Cache.save(); observer.observe(container, { childList: true, }); }; hide(); const observer = new MutationObserver(hide); return () => observer.disconnect(); }; }; /** 视频页 */ const asVideo = () => { const isNarrow = window.innerWidth <= 1080; Z.addStyle` .video-share-popover { display: none; } `; // functions const execFS = async () => { const [btn] = await Z.getElements(".bpx-player-ctrl-web"); btn.click(); }; const execWS = async () => { const [btn] = await Z.getElements(".bpx-player-ctrl-wide"); btn.click(); }; // 自动全屏 const autoFS = Z.debounce(async () => { if (!isNarrow) return; const [player] = await Z.getElements("#bilibili-player"); if (player.classList.contains("mode-webscreen")) return; await execFS(); }, 1e3); // 注册热键 const bindKey = (e) => { if ( e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement ) return; if (["q", "w", "e", "d", "f", "m"].includes(e.key)) { e.preventDefault(); e.stopPropagation(); } if (e.key === "f") { execFS(); return; } if (e.key === "w") execWS(); }; return () => { autoFS(); window.addEventListener("resize", autoFS); document.addEventListener("keydown", bindKey); return () => { window.removeEventListener("resize", autoFS); document.removeEventListener("keydown", bindKey); }; }; }; // 执行 Z.run(() => { const { hostname } = window.location; if (hostname !== "www.bilibili.com") return; Router.watch("index", asIndex).watch("video", asVideo).init(); }); })();