您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
显示影刀社区用户的优质标记
// ==UserScript== // @name 影刀社区助手-极简版 // @namespace DLjun // @version 1.0 // @description 显示影刀社区用户的优质标记 // @author 过客&DLjun // @match https://www.yingdao.com/community/* // @match https://www.yingdao.com/community/discuss // @connect yingdao.com // @grant GM_xmlhttpRequest // @license GPLv3 // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // 存储数据 const dataStore = { creatorIdsMap: {}, userPublishDict: {}, userAcceptRateDict: {} }; // 添加CSS样式 function addStyles() { const style = document.createElement('style'); style.textContent = ` .yd-toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 4px; z-index: 10000; font-size: 14px; text-align: center; } .yd-tag-container { display: inline-block; margin-left: 10px; } .yd-tag { display: inline-block; margin-right: 8px; padding: 2px 6px; border-radius: 4px; font-size: 12px; color: white; line-height: 1.5; white-space: nowrap; box-shadow: 0 1px 2px rgba(0,0,0,0.1); } .yd-full { background-color: #52c41a; } .yd-high { background-color: #95de64; } .yd-medium { background-color: rgb(231, 194, 29); } .yd-low { background-color: #ff7875; } .yd-none { background-color: #f5222d; } .yd-newbie { background-color: rgb(241, 127, 207); } `; document.head.appendChild(style); } // Toast提示管理 const Toast = { element: null, create(message, duration = 3000) { document.getElementById('yd-toast')?.remove(); this.element = document.createElement('div'); this.element.id = 'yd-toast'; this.element.className = 'yd-toast'; this.element.textContent = message; document.body.appendChild(this.element); if (duration > 0) { setTimeout(() => this.element?.remove(), duration); } return this.element; }, update(message) { if (!this.element || !this.element.parentNode) { return this.create(message); } this.element.textContent = message; return this.element; } }; // 获取当前页面的页码 function getCurrentPageNumber() { try { const urlParams = new URLSearchParams(window.location.search); const pageFromUrl = urlParams.get('page'); if (pageFromUrl) return parseInt(pageFromUrl, 10); const activeItem = document.querySelector('.ant-pagination-item.ant-pagination-item-active'); if (activeItem) return parseInt(activeItem.textContent.trim(), 10); } catch (error) { console.error('获取页码失败', error); } return 1; } // API请求 const API = { async fetchQuestionData(page = 1, size = 20) { const toast = Toast.create("正在获取数据..."); try { const url = `https://api.yingdao.com/api/noauth/v1/sns/forum/question/query?page=${page}&size=${size}&tags=%E9%97%AE%E7%AD%94&sort=createTime`; const response = await fetch(url, { method: 'GET', headers: { 'accept': 'application/json', 'content-type': 'application/json;charset=UTF-8', 'origin': 'https://www.yingdao.com', 'referer': 'https://www.yingdao.com/community' }, credentials: 'include' }); if (!response.ok) throw new Error(`请求失败: ${response.status}`); const data = await response.json(); Toast.update("数据获取成功,正在处理...", toast); if (!data) throw new Error('数据为空'); // 提取问题列表 let questionList = []; if (data.data && Array.isArray(data.data)) { questionList = data.data; } else if (data.data && data.data.list && Array.isArray(data.data.list)) { questionList = data.data.list; } else if (data.list && Array.isArray(data.list)) { questionList = data.list; } else if (Array.isArray(data)) { questionList = data; } else { throw new Error('无法提取问题列表'); } // 重置数据 dataStore.creatorIdsMap = {}; dataStore.userPublishDict = {}; dataStore.userAcceptRateDict = {}; // 提取创建者ID questionList.forEach(item => { if (item?.creator && item?.uuid) { dataStore.creatorIdsMap[item.creator] = { creatorName: item.creatorName || '未知' }; } }); if (Object.keys(dataStore.creatorIdsMap).length === 0) { throw new Error('未找到创建者信息'); } Toast.update(`正在获取用户数据...`, toast); // 获取所有创建者的发布列表 await this.fetchAllUserPublishList(Object.keys(dataStore.creatorIdsMap), toast); } catch (error) { console.error('获取数据出错', error); Toast.update(`获取数据出错: ${error.message}`, toast); } }, async fetchAllUserPublishList(creatorIds, toast) { if (!creatorIds?.length) { Toast.update("没有找到用户ID", toast); return; } // 批处理用户请求,每次处理5个 const batchSize = 5; const batches = []; for (let i = 0; i < creatorIds.length; i += batchSize) { batches.push(creatorIds.slice(i, i + batchSize)); } let completedCount = 0; const totalCount = creatorIds.length; for (const batch of batches) { await Promise.all(batch.map(async (creatorId) => { try { const data = await this.fetchUserPublishListPromise(creatorId); completedCount++; const percentage = Math.round((completedCount / totalCount) * 100); Toast.update(`正在处理数据 ${completedCount}/${totalCount} (${percentage}%)`, toast); } catch (error) { completedCount++; console.error(`获取用户 ${creatorId} 数据失败`, error); } })); } Toast.update(`数据处理完成,成功:${Object.keys(dataStore.userPublishDict).length}/${creatorIds.length}`, toast); // 计算采纳率并添加标签 UserTagManager.calculateUserRates(); // 关闭提示 setTimeout(() => toast?.remove(), 2000); }, async fetchUserPublishListPromise(userUuid) { const url = 'https://api.yingdao.com/api/noauth/v1/sns/forum/question/queryUserPublishList'; const response = await fetch(url, { method: 'POST', headers: { 'accept': 'application/json', 'content-type': 'application/json;charset=UTF-8', 'origin': 'https://www.yingdao.com', 'referer': 'https://www.yingdao.com/' }, credentials: 'include', body: JSON.stringify({ userUuid: userUuid, tags: "问答", behavior: "publish", sort: "createTime", page: 1, size: 50 }) }); if (!response.ok) throw new Error(`请求失败: ${response.status}`); const responseData = await response.json(); if (responseData && responseData.data && Array.isArray(responseData.data)) { dataStore.userPublishDict[userUuid] = responseData.data; } else { dataStore.userPublishDict[userUuid] = responseData; } return responseData; } }; // 用户标签管理 const UserTagManager = { // 计算所有用户的采纳率 calculateUserRates() { dataStore.userAcceptRateDict = {}; Object.keys(dataStore.userPublishDict).forEach(userUuid => { const userData = dataStore.userPublishDict[userUuid]; if (!userData) return; let publishList = []; if (Array.isArray(userData)) { publishList = userData; } else if (userData.data && Array.isArray(userData.data)) { publishList = userData.data; } else { return; } if (publishList.length > 0) { const totalQuestions = publishList.length; // 计算采纳的问题数量 const acceptedQuestions = publishList.filter(item => item.isAccept === true || item.status === 'accepted' || item.status === 'ACCEPTED' ).length; // 计算采纳率 const acceptRate = totalQuestions > 0 ? (acceptedQuestions / totalQuestions) : 0; // 存储采纳率信息 dataStore.userAcceptRateDict[userUuid] = { userName: dataStore.creatorIdsMap[userUuid]?.creatorName || '未知', totalQuestions, acceptedQuestions, acceptRate }; } }); // 添加标签到页面 this.addUserTags(); }, // 为用户添加标记 addUserTags() { if (Object.keys(dataStore.userAcceptRateDict).length === 0) return; // 获取所有已知用户名 const knownUserNames = new Set(); for (const [, data] of Object.entries(dataStore.creatorIdsMap)) { if (data.creatorName) knownUserNames.add(data.creatorName); } // 查找可能的用户元素 const selectors = [ '.creator___12fdW > span:not([data-yd-tagged])', '.discuss___27Ane .creator___12fdW > span:not([data-yd-tagged])' ]; let userElements = selectors.flatMap(sel => [...document.querySelectorAll(sel)]); // 如果没有通过选择器找到,则使用通用匹配方法 if (userElements.length === 0) { const allTextElements = document.querySelectorAll('a:not([data-yd-tagged]), span:not([data-yd-tagged]), div:not([data-yd-tagged])'); userElements = Array.from(allTextElements).filter(element => { const text = element.textContent.trim(); return text.length >= 2 && text.length <= 20 && knownUserNames.has(text); }); } // 处理找到的用户名元素 userElements.forEach(element => { try { const userName = element.textContent.trim(); // 查找对应的用户ID let userUuid = Object.entries(dataStore.creatorIdsMap) .find(([, data]) => data.creatorName === userName)?.[0]; if (userUuid) { this.addTagToElement(element, userUuid); } } catch (error) { console.error('处理用户名元素出错', error); } }); // 如果没有找到任何用户名或添加了标签,延迟再尝试一次 if (userElements.length === 0 || document.querySelectorAll('.yd-tag-container').length === 0) { setTimeout(() => this.addUserTags(), 3000); } }, // 向元素添加标记 addTagToElement(element, userUuid) { try { if (element.getAttribute('data-yd-tagged')) return false; const askRateInfo = dataStore.userAcceptRateDict[userUuid] || {}; const tagContainer = document.createElement('div'); tagContainer.className = 'yd-tag-container'; element.setAttribute('data-user-uuid', userUuid); // 计算质量标记 const qualityInfo = this.calculateQualityTag(userUuid); if (qualityInfo) { const qualityTag = document.createElement('span'); qualityTag.className = `yd-tag ${qualityInfo.className}`; qualityTag.textContent = `${qualityInfo.text}: ${qualityInfo.rate} (${askRateInfo.acceptedQuestions}/${askRateInfo.totalQuestions})`; tagContainer.appendChild(qualityTag); } if (tagContainer.children.length > 0) { // 尝试插入标签 try { if (element.nextSibling) { element.parentNode.insertBefore(tagContainer, element.nextSibling); } else { element.parentNode.appendChild(tagContainer); } element.setAttribute('data-yd-tagged', 'true'); return true; } catch (insertError) { try { element.insertAdjacentElement('afterend', tagContainer); element.setAttribute('data-yd-tagged', 'true'); return true; } catch (alternativeError) { return false; } } } } catch (error) { return false; } return false; }, // 计算用户标记 calculateQualityTag(userUuid) { const askRateInfo = dataStore.userAcceptRateDict[userUuid] || {}; // 提问数量和采纳率 const askCount = askRateInfo.totalQuestions || 0; const askRate = askRateInfo.acceptRate || 0; // 检查是否为新人(只进行过一次提问) if (askCount === 1) { return { level: 'newbie', text: '新人', rate: (askRate * 100).toFixed(0) + '%', className: 'yd-newbie' }; } // 计算标记等级 const percentage = askRate * 100; if (percentage === 100) { return { level: 'full', text: '全采纳', rate: percentage.toFixed(0) + '%', className: 'yd-full' }; } else if (percentage >= 90) { return { level: 'high', text: '高采纳', rate: percentage.toFixed(0) + '%', className: 'yd-high' }; } else if (percentage >= 70) { return { level: 'medium', text: '中采纳', rate: percentage.toFixed(0) + '%', className: 'yd-medium' }; } else if (percentage > 50 && percentage < 70) { return { level: 'medium', text: '中采纳', rate: percentage.toFixed(0) + '%', className: 'yd-medium' }; } else if (percentage > 10 && percentage <= 50) { return { level: 'low', text: '低采纳', rate: percentage.toFixed(0) + '%', className: 'yd-low' }; } else { return { level: 'none', text: '不采纳', rate: percentage.toFixed(0) + '%', className: 'yd-none' }; } } }; // 页面监控 function setupPageMonitoring() { // 初始加载 setTimeout(() => { API.fetchQuestionData(getCurrentPageNumber()); }, 1000); // 监听翻页 let currentPage = getCurrentPageNumber(); // 检查页码变化 const checkPageChange = () => { const newPage = getCurrentPageNumber(); if (newPage !== currentPage) { currentPage = newPage; // 清除之前的标签 document.querySelectorAll('.yd-tag-container').forEach(el => el.remove()); document.querySelectorAll('[data-yd-tagged]').forEach(el => el.removeAttribute('data-yd-tagged')); // 重置数据并获取新数据 API.fetchQuestionData(newPage); Toast.create(`正在获取数据...`, 2000); } }; // 定期检查页码变化 const pageInterval = setInterval(checkPageChange, 1000); // 5分钟后清除定时器,减少资源消耗 setTimeout(() => clearInterval(pageInterval), 300000); // 节流函数 function throttle(fn, delay) { let lastCall = 0; return function(...args) { const now = new Date().getTime(); if (now - lastCall < delay) return; lastCall = now; return fn(...args); }; } // 监听点击事件(使用事件委托和节流) document.addEventListener('click', throttle((e) => { let target = e.target; // 检查是否点击了分页元素 while (target && target !== document) { if ( target.classList?.contains('ant-pagination-item') || target.classList?.contains('ant-pagination-next') || target.classList?.contains('ant-pagination-prev') || target.classList?.contains('ant-pagination-jump-next') || target.classList?.contains('ant-pagination-jump-prev') ) { setTimeout(checkPageChange, 300); break; } target = target.parentElement; } }, 200)); // 使用 MutationObserver 监听DOM变化(降低频率) const observer = new MutationObserver(throttle(() => { UserTagManager.addUserTags(); checkPageChange(); }, 500)); observer.observe(document.body, { childList: true, subtree: true }); // 定期尝试添加标记,确保不遗漏,使用递减间隔 let retryCount = 0; const maxRetries = 5; function retryAddTags() { if (retryCount < maxRetries) { UserTagManager.addUserTags(); retryCount++; setTimeout(retryAddTags, 3000 - retryCount * 500); // 递减间隔 } } // 初始加载后尝试添加标签 setTimeout(retryAddTags, 3000); } // 初始化 if (window.location.href.includes('https://www.yingdao.com/community')) { addStyles(); Toast.create("影刀社区助手已启动", 2000); setupPageMonitoring(); } })();