Greasy Fork

Greasy Fork is available in English.

bilibili - 查看历史评论/弹幕

文明交流,人人有责。

当前为 2025-12-09 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         bilibili - 查看历史评论/弹幕
// @description  文明交流,人人有责。
// @version      1.5.0
// @author       会飞的蛋蛋面
// @license      All Rights Reserved - 未经授权禁止复制、修改或分发
// @match        https://www.bilibili.com/video/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      api.aicu.cc
// @connect      apibackup2.aicu.cc
// @run-at       document-idle
// @namespace http://greasyfork.icu/users/751952
// ==/UserScript==

(() => {
    // 避免滥用
    "use strict";const t="history-reply-panel",e="https://www.bilibili.com/video",o="https://live.bilibili.com",n=["https://api.aicu.cc/api/v3/search","https://apibackup2.aicu.cc:88/api/v3/search"],r={reply:"/getreply",danmu:"/getvideodm",live:"/getlivedm"},i=[{key:"reply",name:"评论"},{key:"danmu",name:"视频弹幕"},{key:"live",name:"直播弹幕"}];class a{constructor(t){this.code=t?.code??-1,this.message=t?.message||"",this.ttl=t?.ttl||1,this.data=t?.data||null}get success(){return 0===this.code}}class s{constructor(t){this.time=t.time||0,this.message=t.message||"",this.oid=t.dyn?.oid||"",this.rpid=t.rpid||""}get link(){return this.oid?`${e}/av${this.oid}/#reply${this.rpid}`:""}}class c{constructor(t){this.ctime=t.ctime||0,this.content=t.content||"",this.oid=t.oid||""}get link(){return this.oid?`${e}/av${this.oid}`:""}}class d{constructor(t,e){this.roomId=t.roomid||"",this.roomName=t.roomname||"",this.upName=t.upname||"",this.text=e.text||"",this.ts=e.ts||0}get link(){return this.roomId?`${o}/${this.roomId}`:""}}const l=[],u=new Map;let p=!1,m=null,b=1,f="reply",h=!1,y=0;GM_addStyle(`\n        #${t} { position: absolute; width: 380px; max-height: 70vh; overflow: auto; background: #fff; color: #333; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 6px 24px rgba(0,0,0,.18); z-index: 99999; padding: 12px; display: none; font-family: inherit; }\n        body.dark #${t} { background: #1f1f1f; color: #e9eaec; border-color: #333; }\n        #${t} .header { display: flex; justify-content: space-between; align-items: center; font-weight: 700; margin-bottom: 8px; }\n        #${t} .close { padding: 4px 8px; border: 0; background: #bbb; color: #fff; border-radius: 4px; cursor: pointer; }\n        body.dark #${t} .close { background: #444; color: #e9eaec; }\n        #${t} .tabs { display: flex; gap: 6px; margin-bottom: 8px; }\n        #${t} .tabs button { flex: 1; padding: 6px; border: 1px solid #ddd; background: #f5f5f5; border-radius: 4px; cursor: pointer; font-size: 12px; }\n        #${t} .tabs button.active { background: #00a1d6; color: #fff; border-color: #00a1d6; }\n        body.dark #${t} .tabs button { background: #333; border-color: #444; color: #e9eaec; }\n        body.dark #${t} .tabs button.active { background: #00a1d6; border-color: #00a1d6; }\n        #${t} .item { margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #f2f2f2; }\n        body.dark #${t} .item { border-color: #2c2c2c; }\n        #${t} .meta { font-size: 12px; color: #666; margin-bottom: 4px; }\n        #${t} .meta a { color: #00a1d6; text-decoration: none; }\n        body.dark #${t} .meta { color: #9ca3af; }\n        #${t} .text { font-size: 14px; white-space: pre-wrap; word-break: break-all; }\n        #${t} .room { font-size: 12px; color: #00a1d6; margin-bottom: 2px; }\n        #${t} .info { font-size: 12px; color: #999; margin-bottom: 8px; }\n        #${t} .pager { display: flex; justify-content: space-between; margin-top: 8px; }\n        #${t} .pager button { padding: 4px 12px; border: 1px solid #ddd; background: #f5f5f5; border-radius: 4px; cursor: pointer; }\n        #${t} .pager button:disabled { opacity: 0.5; cursor: not-allowed; }\n        body.dark #${t} .pager button { background: #333; border-color: #444; color: #e9eaec; }\n    `);let v=location.href,g=null;async function $(){g&&(g.disconnect(),g=null);const t=await E(document,"bili-comments");await k(t),S(t),g=R(t)}function w(){window.addEventListener("popstate",x);const t=history.pushState,e=history.replaceState;history.pushState=function(...e){t.apply(this,e),x()},history.replaceState=function(...t){e.apply(this,t),x()}}function x(){location.href!==v&&(v=location.href,setTimeout(()=>$(),1e3))}async function k(t){const e=await E(t.shadowRoot,"bili-comment-thread-renderer"),o=await E(e.shadowRoot,"bili-comment-renderer");await E(o.shadowRoot,"#body")}function E(t,e){return new Promise(o=>{const n=()=>{const r=t.querySelector(e);r?o(r):setTimeout(n,500)};n()})}function S(t){const e=void 0;t.shadowRoot.querySelectorAll("bili-comment-thread-renderer").forEach(t=>L(t))}function R(t){const e=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)"BILI-COMMENT-THREAD-RENDERER"===t.nodeName&&q(t).then(()=>L(t))});return e.observe(t.shadowRoot,{childList:!0,subtree:!0}),e}async function q(t){await E(t.shadowRoot,"bili-comment-renderer")}function L(t){if(t.dataset.processed)return;t.dataset.processed="true";const e=t.shadowRoot.querySelector("bili-comment-renderer");e&&M(e);const o=t.shadowRoot.querySelector("bili-comment-replies-renderer"),n=void 0;(o?.shadowRoot?.querySelectorAll("bili-comment-reply-renderer")||[]).forEach(M),o?.shadowRoot&&N(o)}function N(t){if(t.dataset.observed)return;t.dataset.observed="true";const e=void 0;new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)"BILI-COMMENT-REPLY-RENDERER"===t.nodeName&&M(t)}).observe(t.shadowRoot,{childList:!0,subtree:!0})}function M(t){if(t.dataset.btnAdded)return;const e=t.shadowRoot?.querySelector("#body"),o=t.shadowRoot?.querySelector("bili-comment-action-buttons-renderer"),n=o?.shadowRoot?.querySelector("#reply");if(!e||!n)return void setTimeout(()=>M(t),200);t.dataset.btnAdded="true";const r=e.querySelector('a#user-avatar[href*="space.bilibili.com"]'),i=r?.href.match(/space\.bilibili\.com\/(\d+)/)?.[1],a=void 0;T(n,i,e.querySelector("#user-name a")?.textContent?.trim()||`UID${i}`)}function T(t,e,o){const n=document.createElement("div"),r=document.createElement("button");r.className="history-reply-btn",r.textContent="查看成分",r.onclick=t=>I(t,e,o),n.appendChild(r),t.after(n),l.push(r)}async function I(e,o,n){if(p)return;let r=document.getElementById(t);r||(r=document.createElement("div"),r.id=t,document.body.appendChild(r));const a=e.target.getBoundingClientRect();r.style.left=a.left+window.scrollX+"px",r.style.top=a.bottom+window.scrollY+5+"px";const s=i.map(t=>`<button class="tab-${t.key} ${"reply"===t.key?"active":""}">${t.name}</button>`).join("");r.innerHTML=`\n            <div class="header">${n}<button class="close">关闭</button></div>\n            <div class="tabs">${s}</div>\n            <div class="body">加载中...</div>\n        `,r.style.display="block",r.querySelector(".close").onclick=()=>r.style.display="none",i.forEach(t=>{r.querySelector(`.tab-${t.key}`).onclick=()=>_(r,n,t.key)}),m=o,b=1,f="reply",h=!1,y=0,await z(r,n)}function _(t,e,o){p||f===o||(f=o,b=1,h=!1,y=0,i.forEach(e=>{t.querySelector(`.tab-${e.key}`).classList.toggle("active",e.key===o)}),z(t,e))}function C(t,e,o){return`${t}_${e}_${o}`}function D(t,e){t.querySelectorAll(".tabs button").forEach(t=>{t.disabled=e,t.style.opacity=e?"0.5":"",t.style.pointerEvents=e?"none":""})}async function z(t,e){const o=C(m,f,b);if(u.has(o)){const n=u.get(o);return y=n.total,h=n.isEnd,void B(t,n.list,e)}p=!0,j(!0),D(t,!0),t.querySelector(".body").innerHTML="加载中...";try{const n=await H(m,b,f);if(!n.success)throw new Error(n.message||`接口异常: code=${n.code}`);const r=A(n,f);y=n.data?.cursor?.all_count||y,h=n.data?.cursor?.is_end||!r.length,u.set(o,{list:r,total:y,isEnd:h}),B(t,r,e)}catch(e){t.querySelector(".body").textContent=`获取失败:${e.message}`}finally{p=!1,j(!1),D(t,!1)}}function A(t,e){const o=t.data;if("reply"===e)return(o?.replies||[]).map(t=>new s(t));if("danmu"===e)return(o?.videodmlist||[]).map(t=>new c(t));if("live"===e){const t=[],e=o?.list||[];for(const o of e){const e=o.danmu||[];for(const n of e)t.push(new d(o.roominfo,n))}return t}return[]}$(),w();const j=t=>l.forEach(e=>{e.disabled=t,e.style.opacity=t?"0.5":"",e.style.pointerEvents=t?"none":""});function H(t,e,o){return O(t,e,o,0)}function O(t,e,o,i){return new Promise((s,c)=>{const d=void 0;GM_xmlhttpRequest({method:"GET",url:`${n[i]+r[o]}?uid=${t}&pn=${e}&ps=100&mode=0&keyword=`,headers:{Origin:"https://www.aicu.cc",Referer:"https://www.aicu.cc/"},responseType:"json",onload:r=>{const d=new a(r.response);d.success?s(d):i<n.length-1?O(t,e,o,i+1).then(s).catch(c):s(d)},onerror:()=>{i<n.length-1?O(t,e,o,i+1).then(s).catch(c):c(new Error("网络错误"))}})})}function B(t,e,o){const n=t.querySelector(".body");if(!e.length&&1===b)return void(n.innerHTML="暂无记录");const r=i.find(t=>t.key===f)?.name||"",a=`<div class="info">共 ${y} 条${r} · 第 ${b} 页</div>`,s=e.map(t=>G(t)).join(""),c=`<div class="pager">\n            <button class="prev" ${b<=1?"disabled":""}>上一页</button>\n            <button class="next" ${h?"disabled":""}>下一页</button>\n        </div>`;n.innerHTML=a+s+c,n.querySelector(".prev").onclick=()=>{b>1&&(b--,z(t,o))},n.querySelector(".next").onclick=()=>{h||(b++,z(t,o))}}function G(t){if(t instanceof s){const e=void 0,o=void 0;return`<div class="item"><div class="meta">${t.time?new Date(1e3*t.time).toLocaleString():""} ${t.link?`<a href="${t.link}" target="_blank">跳转</a>`:""}</div><div class="text">${P(t.message)}</div></div>`}if(t instanceof c){const e=void 0,o=void 0;return`<div class="item"><div class="meta">${t.ctime?new Date(1e3*t.ctime).toLocaleString():""} ${t.link?`<a href="${t.link}" target="_blank">跳转</a>`:""}</div><div class="text">${P(t.content)}</div></div>`}if(t instanceof d){const e=t.ts?new Date(1e3*t.ts).toLocaleString():"",o=void 0;return`<div class="item"><div class="room">${t.link?`<a href="${t.link}" target="_blank">${P(t.roomName)}</a>`:P(t.roomName)} (${P(t.upName)})</div><div class="meta">${e}</div><div class="text">${P(t.text)}</div></div>`}return""}function P(t){return t.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[t]))}
})();