// ==UserScript== // @name 查询bangumi贴中用户观看状态 // @namespace http://tampermonkey.net/ // @version 1.5.4 // @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 none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; let userInfoMap = new Map(); // To store results for each user ID let validSubjectId = false; // Flag to track if the subject ID is valid // 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.color = 'red'; 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 "未知状态"; } // 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 input = document.createElement('input'); input.type = 'text'; input.className = 'searchInputL'; input.placeholder = '输入条目ID'; 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); } }; 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'; postTopicDiv.appendChild(placeholder); postTopicDiv.appendChild(input); postTopicDiv.appendChild(button); postTopicDiv.appendChild(quickSearch); postTopicDiv.appendChild(div); // Append the div for subject info } } // Initialize input box and button, and handle click event async function initInputAndButton() { if (['https://bangumi.tv/group/', 'https://bangumi.tv/subject/', 'https://bgm.tv/group/', 'https://bgm.tv/subject/', 'https://chii.in/group/', 'https://chii.in/subject/',].some(prefix => document.URL.startsWith(prefix))) { const postTopicDiv = document.querySelector('.postTopic'); insertInputElements(postTopicDiv); } else if (['https://bangumi.tv/blog/', 'https://bgm.tv/blog/', 'https://chii.in/blog/'].some(prefix => document.URL.startsWith(prefix))) { const postTopicDiv = document.querySelector('#viewEntry'); insertInputElements(postTopicDiv); } else if (['https://bangumi.tv/ep/', 'https://bgm.tv/ep/', 'https://chii.in/ep/'].some(prefix => document.URL.startsWith(prefix))) { const postTopicDiv = document.querySelector('.epDesc'); insertInputElements(postTopicDiv); } // auto load watching status for all users on page 'ep' and 'subject/topic' if (['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 })();