// ==UserScript== // @name 查询bangumi贴中用户观看状态 // @namespace http://tampermonkey.net/ // @version 1.5.6.3 // @description 查询贴中所有用户观看某部作品的状态 // @author Hirasawa Yui // @run-at document-idle // @match https://bangumi.tv/group/topic/* // @match https://bangumi.tv/subject/topic/* // @match https://bangumi.tv/blog/* // @match https://bangumi.tv/ep/* // @match https://bgm.tv/group/topic/* // @match https://bgm.tv/subject/topic/* // @match https://bgm.tv/blog/* // @match https://bgm.tv/ep/* // @match https://chii.in/group/topic/* // @match https://chii.in/subject/topic/* // @match https://chii.in/blog/* // @match https://chii.in/ep/* // @match https://bangumi.tv/subject_search/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; let colorful = true; // 采用五彩配色方案 let userInfoMap = new Map(); // To store results for each user ID let validSubjectId = false; // Flag to track if the subject ID is valid let autoload = false; // Function to create or update an element with the fetched data function insertOrUpdateAdjacentToAnchor(anchor, siblingClass, userId, subjectId) { let key = `${userId}-${subjectId}`; let text = userInfoMap.get(key) || "No info available"; // Find existing element or create new one let newElement = anchor.parentNode.querySelector('.userData'); if (!newElement) { newElement = anchor.parentNode.parentNode.querySelector('.userData'); } if (!newElement) { newElement = document.createElement('span'); newElement.className = 'userData'; // Assign a class for easy identification let inserted = false; let nextElement = anchor.parentNode.nextSibling; while (nextElement) { if (nextElement.nodeType === 1 && nextElement.matches(siblingClass)) { nextElement.parentNode.insertBefore(newElement, nextElement.nextSibling); inserted = true; break; } nextElement = nextElement.nextSibling; } if (!inserted) { anchor.parentNode.insertBefore(newElement, anchor.nextSibling); } } // Update text content and color newElement.textContent = `【${text}】`; newElement.style.fontSize = '12px'; if (colorful) { let type = getDataFromInfoText(text).type; if (type === 1) { // 想看 newElement.style.color = '#ac9a92'; } else if (type === 2) { // 看过 newElement.style.color = '#3ac657'; } else if (type === 3) { // 在看 newElement.style.color = '#24a2e6'; } else if (type === 4) { // 搁置 newElement.style.color = '#bf7d1f'; } else if (type === 5) { // 抛弃 newElement.style.color = '#b22f9c'; } else { newElement.style.color = '#999'; } } else { newElement.style.color = '#999'; } newElement.style.fontWeight = 'bold'; } // Fetch collection data for a specific user ID and subject ID async function fetchUserInfo(userId, subjectId) { const url = `https://api.bgm.tv/v0/users/${userId}/collections/${subjectId}`; // Skip fetching if the user is already in the map with the same subjectId or subject ID is invalid if (!validSubjectId || userInfoMap.has(`${userId}-${subjectId}`)) return; try { const response = await fetch(url); if (!response.ok) { userInfoMap.set(`${userId}-${subjectId}`, "TA未看过/未公开收藏该作"); return; } const data = await response.json(); let infoText = getInfoTextFromData(data); userInfoMap.set(`${userId}-${subjectId}`, infoText); } catch (error) { console.error('Error fetching or processing data', error); userInfoMap.set(`${userId}-${subjectId}`, "Error fetching data"); } } // Convert fetched data to a user-friendly text function getInfoTextFromData(data) { if (data.type === 1) return "TA想看这部作品"; else if (data.type === 2) return data.rate ? `TA打了${data.rate}分` : 'TA看过这部作品'; else if (data.type === 3) return data.ep_status ? `TA看到了${data.ep_status}集` : 'TA在看这部作品'; else if (data.type === 4) return "TA搁置了这部作品"; else if (data.type === 5) return "TA抛弃了这部作品"; else return "未知状态"; } // convert text back to data type function getDataFromInfoText(text) { let data = { type: 0, rate: null, ep_status: null }; // default unknown data if (text === "TA想看这部作品") { data.type = 1; } else if (text.startsWith("TA打了") && text.endsWith("分")) { data.type = 2; data.rate = parseFloat(text.slice(3, -1)); // Extracting number from "TA打了X分" } else if (text.startsWith("TA看到了") && text.endsWith("集")) { data.type = 3; data.ep_status = parseInt(text.slice(4, -1)); // Extracting number from "TA看到了X集" } else if (text === "TA搁置了这部作品") { data.type = 4; } else if (text === "TA抛弃了这部作品") { data.type = 5; } else if (text === 'TA看过这部作品') { data.type = 2; // Assuming this corresponds to having seen the work without a rating } else if (text === 'TA在看这部作品') { data.type = 3; // Assuming this corresponds to watching the work without a specific episode status } else { // Unknown status, data.type remains 0 } return data; } // Fetch subject info by subject ID async function fetchSubjectInfo(subjectId) { const url = `https://api.bgm.tv/v0/subjects/${subjectId}`; try { const response = await fetch(url); const div = document.querySelector('.subjectInfo'); if (!response.ok) { div.textContent = "无效条目"; validSubjectId = false; // Mark subject ID as invalid userInfoMap.clear(); // Clear previous info if any return; } const data = await response.json(); div.textContent = `你当前正在查询所有用户观看 ${data.name} 的状态`; div.style.color = 'green'; validSubjectId = true; // Mark subject ID as valid } catch (error) { console.error('Error fetching subject data', error); } } // insert input elements below the specified div function insertInputElements(postTopicDiv) { if (postTopicDiv) { const placeholder = document.createElement('div'); const title = document.createElement('h3'); title.textContent = '查询观看状态'; const input = document.createElement('input'); input.type = 'text'; input.className = 'searchInputL'; input.placeholder = '输入条目ID'; input.style.maxWidth = '200px'; const button = document.createElement('button'); button.type = 'button'; button.textContent = '获取信息'; button.style.fontSize = '13px'; button.style.border = 'none'; button.style.background = '#4EB1D4'; button.style.color = '#FFF'; button.style.padding = '6px 15px'; button.style.borderRadius = '5px'; button.style.cursor = 'pointer'; const div = document.createElement('div'); div.className = 'subjectInfo'; // For displaying subject info button.onclick = async function() { const subjectId = input.value.trim(); if (!subjectId) return; // Do nothing if the subject ID is empty await fetchSubjectInfo(subjectId); if (validSubjectId) { // Only process users if the subject ID is valid processAllUsers(subjectId); } }; // Create a checkbox let checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = autoload; // Set the initial state of the checkbox let check_tag = document.createElement('span'); check_tag.textContent = '自动加载'; // Change listener for the checkbox checkbox.addEventListener('change', function() { autoload = checkbox.checked; // Update the switch variable GM_setValue('autoload', autoload); // Save the new state }); const quickSearch = document.createElement('a'); quickSearch.href = 'https://bangumi.tv/subject_search'; quickSearch.textContent = '快速查询条目ID'; quickSearch.style.color = 'pink'; quickSearch.cursor = 'pointer'; quickSearch.target = '_blank'; const container = document.createElement('div'); container.appendChild(placeholder); container.appendChild(title); container.appendChild(input); container.appendChild(button); container.appendChild(checkbox); container.appendChild(check_tag); container.appendChild(quickSearch); container.appendChild(div); // Append the div for subject info postTopicDiv.appendChild(container); } } // Initialize input box and button, and handle click event async function initInputAndButton() { autoload = GM_getValue('autoload', false); let container = document.querySelector('#columnInSubjectB'); if (!container) { container = document.querySelector('#columnB'); } if (!container) { container = document.querySelector('#columnEpB'); } insertInputElements(container); // auto load watching status for all users on page 'ep' and 'subject/topic' if (autoload && ['https://bangumi.tv/subject/topic/', 'https://bgm.tv/subject/topic/', 'https://chii.in/subject/topic/', 'https://bangumi.tv/ep/', 'https://bgm.tv/ep/', 'https://chii.in/ep'].some(prefix => document.URL.startsWith(prefix))) { let anchor = document.querySelector('#subject_inner_info .avatar'); const subjectId = anchor.href.split('/subject/')[1].split('/')[0]; await fetchSubjectInfo(subjectId); processAllUsers(subjectId); } // search page if (document.URL.startsWith('https://bangumi.tv/subject_search/')){ const anchorElements = document.querySelectorAll('a.l'); anchorElements.forEach(anchor => { if(anchor.href.includes('/subject/')) { const subjectId = anchor.href.split('/subject/')[1].split('/')[0]; let newElement = document.createElement('span'); newElement.style.cursor = 'pointer'; // Add an event listener for the click event newElement.addEventListener('click', function() { // Copy text to clipboard logic navigator.clipboard.writeText(newElement.textContent.replace(/^\(|\)$/g, '')).then(() => { console.log('Text copied to clipboard'); }).catch(err => { console.error('Error in copying text: ', err); }); }); newElement.textContent = `(${subjectId})`; newElement.style.color = 'red'; newElement.style.fontWeight = 'bold'; anchor.parentNode.insertBefore(newElement, anchor.nextSibling); } }); } } // Fetch user info for all users and then process anchor tags async function processAllUsers(subjectId) { const anchorElements = document.querySelectorAll('a.l'); let fetchPromises = []; anchorElements.forEach(anchor => { if(anchor.href.includes('/user/')) { const userId = anchor.href.split('/user/')[1].split('/')[0]; if (!userInfoMap.has(`${userId}-${subjectId}`)) { fetchPromises.push(fetchUserInfo(userId, subjectId)); } } }); await Promise.all(fetchPromises); anchorElements.forEach(anchor => { if(anchor.href.includes('/user/')) { const userId = anchor.href.split('/user/')[1].split('/')[0]; insertOrUpdateAdjacentToAnchor(anchor, 'span.sign.tip_j', userId, subjectId); } }); } initInputAndButton(); // Initialize and append input box and button })();