// ==UserScript== // @name Bilibili - 在未登录的情况下照常加载评论 // @namespace https://bilibili.com/ // @version 0.4 // @description 在未登录的情况下照常加载评论 | V0.4 修复文本转换为超链接时的一个bug // @license GPL-3.0 // @author DD1969 // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @require https://lib.baomitu.com/viewerjs/1.11.4/viewer.min.js // @grant none // @downloadURL none // ==/UserScript== (async function() { 'use strict'; // no need to continue this script if user has logged in if (document.cookie.includes('DedeUserID')) return; // reload page when url changed const re = /https:\/\/www\.bilibili\.com\/video\/.*/; const oldHref = window.location.href; const timer4Url = setInterval(() => { const newHref = window.location.href; if (newHref === oldHref) return; if (re.test(newHref) || re.test(oldHref)) { clearInterval(timer4Url); window.location.reload(); } }, 200); // get hash and make sure reply list appears const { rootReplyHash, subReplyHash, replyList } = await new Promise(resolve => { const timer = setInterval(() => { const rootReplyHashMatchResult = document.head.innerHTML.match(/\.reply-item\[(?data-v-[a-z0-9]{8})\]/); const subReplyHashMatchResult = document.head.innerHTML.match(/\.sub-reply-item\[(?data-v-[a-z0-9]{8})\]/); const replyList = document.querySelector('.reply-list'); if (rootReplyHashMatchResult && subReplyHashMatchResult && replyList) { replyList.innerHTML = ''; clearInterval(timer); resolve({ rootReplyHash: rootReplyHashMatchResult.groups.rootReplyHash, subReplyHash: subReplyHashMatchResult.groups.subReplyHash, replyList }); } }, 200); }); // style patch await addStyle(); let paginationCounter = 1; const replyPool = {}; const oid = window.__INITIAL_STATE__.aid; // get data of first pagination const firstPaginationData = await getPaginationData(paginationCounter); const createrID = firstPaginationData.upper.mid; console.log(firstPaginationData); // load the top reply if it exists if (firstPaginationData.top_replies && firstPaginationData.top_replies.length !== 0) { const topReplyData = firstPaginationData.top_replies[0]; appendReplyItem(topReplyData, true); } // script ends here if there is no reply of this video if (firstPaginationData.replies.length === 0) { console.log('All replies are loaded.'); return; } // load normal replies for (const replyData of firstPaginationData.replies) { appendReplyItem(replyData); } // add anchor addAnchor(); // get reply data according to pagination number async function getPaginationData(paginationNumber) { const paginationString = `{"offset":"{\\\"type\\\":1,\\\"direction\\\":1,\\\"data\\\":{\\\"pn\\\":${paginationNumber}}}"}`; return await fetch(`https://api.bilibili.com/x/v2/reply/main?oid=${oid}&pagination_str=${encodeURIComponent(paginationString)}&type=1&mode=3`).then(res => res.json()).then(json => json.data); } function appendReplyItem(replyData, isTopReply) { if (replyPool[replyData.rpid_str]) return; const replyItemElement = document.createElement('div'); replyItemElement.classList.add('reply-item'); replyItemElement.innerHTML = `
${ replyData.member.pendant.image ? `
` : '' }
${ replyData.member.user_sailing?.cardbg ? `
NO.
${replyData.member.user_sailing.cardbg.fan.number.toString().padStart(6, '0')}
` : '' }
${isTopReply? '置顶': ''}${replyData.content.pictures ? `
笔记
` : ''}${getConvertedMessage(replyData.content)}
${ replyData.content.pictures ? `
${getImageItems(replyData.content.pictures)}
` : '' }
${getFormattedTime(replyData.ctime)} ${replyData.like} 回复
${ replyData.up_action.like ? `
UP主觉得很赞
` : '' }
${getSubReplyItems(replyData.replies) || ''} ${ replyData.rcount > 3 ? `
共${replyData.rcount}条回复, 点击查看
` : '' }
`; replyList.appendChild(replyItemElement); replyPool[replyData.rpid_str] = true; // setup image viewer const previewImageContainer = replyItemElement.querySelector('.preview-image-container'); if (previewImageContainer) new Viewer(previewImageContainer, { title: false, toolbar: false, tooltip: false, keyboard: false }); // setup view more button const subReplyList = replyItemElement.querySelector('.sub-reply-list'); const viewMoreBtn = replyItemElement.querySelector('.view-more-btn'); viewMoreBtn && viewMoreBtn.addEventListener('click', () => loadAllSubReplies(replyData.rpid, subReplyList)); } function getFormattedTime(ms) { const time = new Date(ms * 1000); const year = time.getFullYear(); const month = (time.getMonth() + 1).toString().padStart(2, '0'); const day = time.getDate().toString().padStart(2, '0'); const hour = time.getHours().toString().padStart(2, '0'); const minute = time.getMinutes().toString().padStart(2, '0'); return `${year}-${month}-${day} ${hour}:${minute}`; } function getMemberLevelColor(level) { return ({ 1: '#BBBBBB', 2: '#8BD29B', 3: '#7BCDEF', 4: '#FEBB8B', 5: '#EE672A', 6: '#F04C49' })[level]; } function getConvertedMessage(content) { let result = content.message; // convert emote tag to image if (content.emote) { for (const [key, value] of Object.entries(content.emote)) { const imageElementHTML = `${key}`; result = result.replaceAll(key, imageElementHTML); } } // convert timestamp to link result = result.replaceAll(/\d{1,2}(:|:)\d{1,2}/g, (timestamp) => { const [minute, second] = timestamp.replace(':', ':').split(':'); const totalSecond = parseInt(minute) * 60 + parseInt(second); if (Number.isNaN(totalSecond)) return timestamp; return `${timestamp.replace(':', ':')}`; }); // convert url to link if (Object.keys(content.jump_url).length) { for (const [key, value] of Object.entries(content.jump_url)) { const href = key.startsWith('BV') ? `https://www.bilibili.com/video/${key}` : (value.pc_url || key); const linkElementHTML = `${value.title}`; result = result.replaceAll(key, linkElementHTML); } } // convert @ user if (content.at_name_to_mid) { for (const [key, value] of Object.entries(content.at_name_to_mid)) { const linkElementHTML = `@${key}`; result = result.replaceAll(`@${key}`, linkElementHTML); } } return result; } function getImageItems(images) { images = images.slice(0, 3); const imageSizeConfig = ({ 1: 'max-width: 280px; max-height: 180px;', 2: 'width: 128px; height: 128px;', 3: 'width: 96px; height: 96px;', })[images.length]; let result = ''; for (const image of images) { result += `
`; } return result; } function getSubReplyItems(subReplies) { if (!subReplies || subReplies.length === 0) return; let result = ''; for (const replyData of subReplies) { result += `
${getConvertedMessage(replyData.content)}
${getFormattedTime(replyData.ctime)} ${replyData.like} 回复
`; } return result; } async function loadAllSubReplies(rootReplyID, subReplyList) { let subPaginationCounter = 1; while(true){ const subReplyData = await fetch(`https://api.bilibili.com/x/v2/reply/reply?oid=${oid}&pn=${subPaginationCounter++}&ps=20&root=${rootReplyID}&type=1`).then(res => res.json()).then(json => json.data); if (subPaginationCounter - 1 === 1) subReplyList.innerHTML = ''; if (subReplyData.replies) subReplyList.innerHTML += getSubReplyItems(subReplyData.replies); else break; } } async function addStyle() { // normal style const commonCSS = document.createElement('style'); commonCSS.textContent = ` .reply-item .root-reply-avatar .avatar .bili-avatar { width: 48px; height: 48px; } .sub-reply-item .sub-reply-avatar .avatar .bili-avatar { width: 30px; height: 30px; } .sub-reply-container .view-more-btn:hover { color: #00AEEC; } @media screen and (max-width: 1620px) { .reply-item .root-reply-avatar .avatar .bili-avatar { width: 40px; height: 40px; } .sub-reply-item .sub-reply-avatar .avatar .bili-avatar { width: 24px; height: 24px; } } `; document.head.appendChild(commonCSS); // viewerjs const viewerjsCSS = document.createElement('style'); viewerjsCSS.textContent = await fetch('https://lib.baomitu.com/viewerjs/1.11.4/viewer.min.css').then(res => res.text()); document.head.appendChild(viewerjsCSS); } function addAnchor() { const anchorElement = document.createElement('div'); anchorElement.textContent = '正在加载...'; anchorElement.style = ` width: calc(100% - 22px); height: 40px; margin-left: 22px; display: flex; justify-content: center; align-items: center; transform: translateY(-60px); color: #61666d; `; document.querySelector('#comment .reply-warp').appendChild(anchorElement); const ob = new IntersectionObserver(async (entries) => { if (!entries[0].isIntersecting) return; const newPaginationData = await getPaginationData(++paginationCounter); if (newPaginationData.replies.length === 0) { anchorElement.textContent = '所有评论已加载完毕'; ob.disconnect(); return; } for (const replyData of newPaginationData.replies) { appendReplyItem(replyData); } }); ob.observe(anchorElement); } })();