您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
也可以干别的
// ==UserScript== // @name 条目讨论页显示用户评价 // @namespace https://bgm.tv/group/topic/411796 // @version 1.1.3 // @description 也可以干别的 // @author mmv // @include /^https?://(bangumi\.tv|bgm\.tv|chii\.in)/(subject/topic|blog|ep|character|person|group/topic)/* // @icon https://www.google.com/s2/favicons?sz=64&domain=bgm.tv // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; let userLinks = []; const ongoingRequests = new Map(); let accessToken; const fallbackNames = {}; const sfwSubjects = new Set(JSON.parse(sessionStorage.getItem('ccf_sfw') || '[]')); const sfwq = ',受限条目?'; const styleSheet = document.createElement("style"); styleSheet.innerText = /* css */` .ccf-wrapper ~ .ccf-wrapper::before { /* 用 ~ 不用 + 避免与其他组件冲突 */ content: "·"; color: #999; font-size: 10px; margin-left: 5px; } .ccf-star { margin-left: 5px; } .ccf-status { margin-left: 5px; color: #999; font-size: 12px; font-weight: normal; } button.ccf-status { background: none; border: none; padding: 0; cursor: pointer; user-select: text; } button.ccf-status[disabled] { cursor: text; } .ccf-comment { margin-left: 5px; position: relative; cursor: help; } .ccf-comment-popup { position: absolute; top: 100%; left: 0; background-color: rgba(254, 254, 254, 0.9); box-shadow: inset 0 1px 1px hsla(0, 0%, 100%, 0.3), inset 0 -1px 0 hsla(0, 0%, 100%, 0.1), 0 2px 4px hsla(0, 0%, 0%, 0.2); backdrop-filter: blur(5px); border-radius: 5px; padding: 5px; max-width: 100dvw; width: 240px; z-index: 1000; font-weight: normal; font-size: 12px; color: rgba(0, 0, 0, .7); cursor: auto; display: none; } .ccf-comment:hover .ccf-comment-popup, .ccf-comment:focus .ccf-comment-popup, .ccf-comment:active .ccf-comment-popup { display: block; } html[data-theme="dark"] .ccf-comment-popup { background: rgba(80, 80, 80, 0.7); color: rgba(255, 255, 255, .7); } .loader { margin-left: 5px; border: 2px solid transparent; border-top: 2px solid #F09199; border-radius: 50%; width: 10px; height: 10px; animation: spin 2s linear infinite; display: inline-block; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(styleSheet); if (location.pathname.startsWith('/subject/topic') || location.pathname.startsWith('/ep')) { userLinks = document.querySelectorAll('.inner strong a'); const subject_id = document.querySelector('#subject_inner_info a').href.split('/').pop(); if (!userLinks || !subject_id) return; lazyRender(userLinks, subject_id); } else if (location.pathname.startsWith('/blog')) { userLinks = [document.querySelector('#pageHeader a'), ...document.querySelectorAll('#columnA .inner strong a')]; const relatedSubjects = document.querySelectorAll('#related_subject_list .ll a'); if (!userLinks || !relatedSubjects) return; multiSubjectsRender(userLinks, relatedSubjects); } else if (location.pathname.startsWith('/character') || location.pathname.startsWith('/person')) { userLinks = document.querySelectorAll('.inner strong a'); const castSubjects = document.querySelectorAll('.browserList .inner a[href^="/subject/"]'); if (!userLinks || !castSubjects) return; multiSubjectsRender(userLinks, castSubjects); } else if (location.pathname.startsWith('/group/topic')) { userLinks = document.querySelectorAll('#columnInSubjectA .inner strong a'); if (!userLinks) return; } window.ccf = async (subject_id) => lazyRender(userLinks, subject_id, true); document.addEventListener('click', e => { if (e.target.classList.contains('ccf-comment')) e.target.focus(); }, true); async function getUserData(username, subject_id) { const cacheKey = `userData_${username}_${subject_id}`; const cachedData = sessionStorage.getItem(cacheKey); if (cachedData) { const data = JSON.parse(cachedData); return data; } if (ongoingRequests.has(cacheKey)) return ongoingRequests.get(cacheKey); const requestPromise = (async() => { const headers = {}; if (accessToken) headers.Authorization = `Bearer ${accessToken}`; const response = await fetch(`https://api.bgm.tv/v0/users/${username}/collections/${subject_id}`, { headers }); if (response.ok) { const data = await response.json(); if (!accessToken) updSfw(subject_id); sessionStorage.setItem(cacheKey, JSON.stringify(data)); return data; } else if (response.status === 404) { const data = { notFound: true, borne: !!accessToken }; sessionStorage.setItem(cacheKey, JSON.stringify(data)); return data; } else if (response.status === 401) { const data = { authFailed: true }; sessionStorage.setItem(cacheKey, JSON.stringify(data)); return data; } else { throw new Error(`API request ${ response.status } ${ response.statusText }`); } })(); ongoingRequests.set(cacheKey, requestPromise); try { return await requestPromise; } finally { ongoingRequests.delete(cacheKey); } } async function renderUserData(userLink, subject_id, showName=false) { const username = userLink.href.split('/').pop(); const cacheKey = `userData_${username}_${subject_id}`; const loader = document.createElement('div'); loader.classList.add('loader'); userLink.after(loader); const wrapper = document.createElement('span'); wrapper.classList.add('ccf-wrapper'); userLink.after(wrapper); const subjectHTML = showName ? `<a href="/subject/${subject_id}" class="l${ fallbackNames[subject_id] ? '' : ` ccf-name-tbd-${subject_id}` }" target="_blank">${ fallbackNames[subject_id] || subject_id }</a>` : ''; try { const data = await getUserData(username, subject_id); if (data.notFound || data.authFailed) { const needBear = !sfwSubjects.has(subject_id) && data.notFound && !data.borne; const needToken = needBear || data.authFailed; const tag = needToken ? 'button' : 'span'; const status = document.createElement(tag); status.classList.add('ccf-status'); status.innerHTML = `未找到公开${subjectHTML}收藏`; if (needToken) { status.classList.add('ccf-unborne'); status.dataset.cacheKey = cacheKey; if (needBear) { status.innerHTML += sfwq; status.classList.add(`ccf-sfw-tbd-${subject_id}`); } else if (data.authFailed) { status.textContent = '个人令牌认证失败'; } status.onclick = e => { if (e.target !== e.currentTarget) return; status.insertAdjacentHTML('afterend', `<span class="ccf-status ccf-at-${subject_id}">试试<a class="l" href="https://next.bgm.tv/demo/access-token/create" target="_blank">创建</a>并<a class="l ccf-at" href="javascript:">填写</a>个人令牌?</span>`); status.disabled = true; status.nextElementSibling?.querySelector('.ccf-at')?.addEventListener('click', () => { accessToken = prompt('请填写个人令牌'); if (!accessToken) return; if (!accessToken.match(/^[a-zA-Z0-9]+$/)) { accessToken = null; alert('格式错误,请重新填写'); return; } const unbornes = document.querySelectorAll('.ccf-unborne'); const [idLinkMap, cacheKeys2Rm] = [...unbornes].reduce(([map, keys], unborne) => { const subject_id = unborne.dataset.cacheKey.split('_').pop(); const link = unborne.parentNode.parentNode.querySelector('a'); if (map[subject_id]) { map[subject_id].push(link); } else { map[subject_id] = [link]; } keys.add(unborne.dataset.cacheKey); unborne.parentNode.remove(); return [map, keys]; }, [{}, new Set()]); for (const cacheKeyRm of cacheKeys2Rm) sessionStorage.removeItem(cacheKeyRm); for (const [subject_id, links] of Object.entries(idLinkMap)) { lazyRender(links, subject_id, showName); } }); }; } wrapper.append(status); } else { const { subject_type, rate, type, ep_status, vol_status, comment, subject } = data; const name = subject?.name; if (showName && name) updName(subject_id, name); const verb = ['读', '看', '听', '玩', '', '看'][subject_type - 1]; let html = ''; if (rate && rate !== 0) { html += `<span class="ccf-star starstop-s"><span class="starlight stars${rate}"></span></span>`; } if (type) { html += `<span class="ccf-status">${[`想${verb}`, `${verb}过`, `在${verb}`, '搁置', '抛弃'][type - 1]}${ showName && `<a href="/subject/${subject_id}" class="l" target="_blank">${name}</a>` || '' }</span>`; } if (ep_status) { html += `<span class="ccf-status">${verb}到ep${ep_status}</span>`; } if (vol_status) { html += `<span class="ccf-status">${ ep_status ? '' : `${verb}到` }vol${vol_status}</span>`; } wrapper.innerHTML = html; if (comment) { const popupBtn = document.createElement('span'); popupBtn.classList.add('ccf-comment'); popupBtn.role = 'button'; popupBtn.tabIndex = 0; popupBtn.innerHTML = '💬'; const popup = document.createElement('div'); popup.classList.add('ccf-comment-popup'); popup.innerHTML = comment; popupBtn.append(popup); wrapper.append(popupBtn); popupBtn.addEventListener('mouseenter', () => { const rect = popupBtn.getBoundingClientRect(); const popupRect = popup.getBoundingClientRect(); const windowWidth = window.innerWidth; if (rect.left + popupRect.width > windowWidth) { popup.style.left = `${windowWidth - rect.left - popupRect.width}px`; } else { popup.style.left = '0'; } }); } } } catch (error) { console.error('Error fetching user data:', error); const reloadBtn = document.createElement('button'); reloadBtn.classList.add('ccf-status'); reloadBtn.innerHTML = `${subjectHTML}加载失败`; reloadBtn.addEventListener('click', e => { if (e.target !== e.currentTarget) return; sessionStorage.removeItem(cacheKey); reloadBtn.parentElement.remove(); renderUserData(userLink, subject_id); }); wrapper.append(reloadBtn); } finally { loader.remove(); } } function inView(elem) { const isHidden = elem.closest('.sub_reply_bg')?.style.display === 'none' // (折叠长楼层)(https://bgm.tv/dev/app/2214) || elem.closest('.row_reply')?.hidden; // (只看好友/自己参与的评论)[https://bgm.tv/dev/app/3587] const rect = elem.getBoundingClientRect(); return rect.top < window.innerHeight && rect.bottom >= 0 && !isHidden; } function lazyRender(userLinks, subject_id, showName) { const observer = new IntersectionObserver(async (entries) => { if (entries[0].intersectionRatio <= 0) return; for (const { isIntersecting, target } of entries) { if (!isIntersecting || !inView(target)) continue; if (target.closest('.sub_reply_bg')?.style.overflow === 'hidden') { // (折叠长楼层)(https://bgm.tv/dev/app/2214) await new Promise(resolve => setTimeout(resolve, 600)); } observer.unobserve(target); renderUserData(target, subject_id, showName); } }); for (const userLink of userLinks) { if (inView(userLink)) { renderUserData(userLink, subject_id, showName); } else { observer.observe(userLink); } } } function multiSubjectsRender(userLinks, subjectLinks) { if (subjectLinks.length === 1) { lazyRender(userLinks, subjectLinks[0].href.split('/').pop()); return; } for (const subjectLink of subjectLinks) { const br = document.createElement('br'); const btn = document.createElement('a'); btn.href = 'javascript:;'; btn.textContent = '显示评价'; btn.classList.add('l'); btn.addEventListener('click', () => { const status = document.createElement('span'); status.classList.add('ccf-status'); status.textContent = '已显示本作评价'; btn.replaceWith(status); const subject_id = subjectLink.href.split('/').pop(); const fallbackName = subjectLink.textContent; fallbackNames[subject_id] = fallbackName; lazyRender(userLinks, subject_id, true); }); subjectLink.after(br, btn); } } function updSfw(subject_id) { if (sfwSubjects.has(subject_id)) return; sfwSubjects.add(subject_id); sessionStorage.setItem('ccf_sfw', JSON.stringify([...sfwSubjects])); document.querySelectorAll(`.ccf-sfw-tbd-${subject_id}`).forEach(status => { status.innerHTML = status.innerHTML.slice(0, -sfwq.length); status.disabled = true; status.classList.remove('ccf-unborne'); }); document.querySelectorAll(`.ccf-at-${subject_id}`).forEach(status => status.remove()); } function updName(subject_id, name) { if (fallbackNames[subject_id]) return; fallbackNames[subject_id] = name; document.querySelectorAll(`.ccf-name-tbd-${subject_id}`).forEach(status => { status.textContent = name; }); } })();