您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
勋章触发奖励时停+发帖回帖奖励账本查询!
// ==UserScript== // @name 看帖:PAUSE EA 持久版 // @namespace https://www.gamemale.com/space-uid-687897.html // @version 0.8.8.11 // @description 勋章触发奖励时停+发帖回帖奖励账本查询! // @author 瓦尼 // @match https://www.gamemale.com/* // @match https://www.gamemale.com/forum.php // @grant GM_registerMenuCommand // @icon https://www.gamemale.com/template/mwt2/extend/img/favicon.ico // @exclude https://www.gamemale.com/*inajax=1* // @license GPL // ==/UserScript== // 下载地址 https://greasyfork.org/zh-CN/scripts/517953 // R语言计算灵魂概率 https://www.gamemale.com/blog-723150-117070.html /** * 基于瓦尼开发的 勋章触发记录他来了-本地发回帖账本PAUSE https://www.gamemale.com/thread-136471-1-1.html * 基于星之子修改的 勋章触发记录 PAUSE(EA) 账本界面皮肤 https://www.gamemale.com/thread-145044-1-1.html * 回帖限制请参考你需要知道的回帖发帖收益及各版版规 https://www.gamemale.com/thread-114869-1-1.html * 回帖限制请参考关于各版块的回帖数限制的回答 https://www.gamemale.com/forum.php?mod=redirect&goto=findpost&ptid=150652&pid=4647886&fromuid=723150 * 重构表格生成代码,使其更为直观。 * 新增回帖分区统计,避免超出回帖上限 * 新增【开启提示框暂停】,0为关闭,1为开启 * 新增【发帖灵魂统计】,0为关闭,1为开启 */ // DONE 区别账号 // TODO 区域合并 // DONE 全区监控之后,有一些报错暂时未处理 // DONE 仅在发帖和回帖时,记录区域位置 // DONE 仅在发帖和回帖时,弹出提示框 // TODO 定制颜色样式 // TODO 如果某条回复打到阈值,突出显示 // TODO 合并灵魂期望和回帖期望 (function () { 'use strict' // 0为关闭,1为开启 let Config = { 开启提示框暂停: 1, 显示默认区域: 1, 发帖灵魂统计: 0 } if (localStorage.getItem('账本配置')) { Config = JSON.parse(localStorage.getItem('账本配置')) } // 自动获取用户uid,请勿随意修改 // 目前仅与主题配色相关 const uid = discuz_uid /////////////////////////快速设置//////////////////////////////// // 抽卡音乐开关,值为0时关闭,值为1时开启 //const gachaSound = 0; // 抽卡音乐链接 // 崩铁抽卡音效 网抑云源 //const gachaMusicUrl = 'https://music.163.com/song/media/outer/url?id=2034614721.mp3'; //PAUSE EX-AID时停音效 使用Gimhoy音乐盘源 https://music.gimhoy.com/ //const gachaMusicUrl = 'https://dlink.host/musics/aHR0cHM6Ly9vbmVkcnYtbXkuc2hhcmVwb2ludC5jb20vOnU6L2cvcGVyc29uYWwvc3Rvcl9vbmVkcnZfb25taWNyb3NvZnRfY29tL0VVR1R6WlZJeEhoSnBJNnpPclVRcXNBQkN4ZkkwNlh5M25sZmNkV2ZSVzBqc1E.mp3'; // Firefox火狐浏览器失效保护设置,默认为0,使用firefox浏览器却无法打开账本,可以尝试将此项值设为1,其他浏览器请勿修改! const firefoxBrowser = 0 /////////////////////////快速设置部分结束/////////////////////// // 使用的浏览器检测 var brwoserType = "" const userAgent = navigator.userAgent if ((userAgent.indexOf('Firefox') > -1) || firefoxBrowser) { brwoserType = "Firefox" } else if (userAgent.indexOf('Chrome') > -1) { brwoserType = "Chrome" } else { brwoserType = "Others" } // 播放抽卡音效的函数 // 由于音效的缓存需要时间,触发暂停时可能无法及时播放 function playSound(sound) { sound.addEventListener("canplaythrough", event => { console.log("获取成功,开始播放") sound.play() }) return 0 } // 主要负责暂停和记录的主函数 function pauseAndSave() { // 获取内容并暂停 var creditElement = document.getElementById("creditpromptdiv") // 获取分区元素 const area = (() => { const ele = document.querySelector("#pt > div") return ele ? ele.textContent.split('›').map(item => item.trim()).slice(-2, -1)[0] : undefined })() // 保存内容 extractAndSave(creditElement, area) console.log("记录器工作中...") return 0 } // 持续监听页面,当目标节点发生变化时,调用检测函数 function startObserve() { const targetNode = document.getElementById('append_parent') if (!targetNode) return // 观察器配置 const config = { attributes: false, childList: true, subtree: false } // 设置计数器,防止出现无限循环 let changeCount = 0 let lastSuccessTime = new Date(0) // 当检测到变化时调用的回调函数 const callback = function () { // 提前加载音效 //if(gachaSound == 1){ //console.log("正在获取音效...."); //var sound = new Audio(); //sound.src = gachaMusicUrl; //sound.load(); //} // 如果检测到奖励内容再执行函数 if (document.getElementById("creditpromptdiv")) { // 检查和上一次的间隔毫秒 let curTime = new Date() let timeDiff = curTime.getTime() - lastSuccessTime.getTime() // 如果小于一定间隔则不执行 if (timeDiff >= 10000) { // 播放音效 //if(gachaSound == 1){ //playSound(sound); //} // 执行主函数 pauseAndSave() // 计数器加一并更新最新时间 lastSuccessTime = curTime changeCount++ console.log(`PAUSE账本第 ${changeCount} 次记录完成`) // 如果变化次数达到一定次,断开观察!防止无限循环。 if (changeCount >= 10) { console.log('达到设定的变更次数,停止观察。') observer.disconnect() } } else { //alert("成功拦截重复记录"); console.log("成功拦截重复记录") } } } // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback) // 开始观察目标节点 observer.observe(targetNode, config) console.log("PAUSE账本正在运行中···") return 0 } startObserve() function extractAndSave(divElement, area) { let curTime = new Date() // 获取灵魂期望 const linghunExpectations = JSON.parse(localStorage.getItem('灵魂期望')) const result = { creditType: '', badgeActivated: '否', area: '', lvCheng: 0, jinBi: 0, xueYe: 0, zhuiSui: 0, zhouShu: 0, zhiShi: 0, lingHun: 0, duoLuo: 0, acquiredAt: curTime, linghunExpectations } const keyMap = { '旅程': 'lvCheng', '金币': 'jinBi', '血液': 'xueYe', '追随': 'zhuiSui', '咒术': 'zhouShu', '知识': 'zhiShi', '灵魂': 'lingHun', '堕落': 'duoLuo' } // 提取奖励类型 const creditTypeNode = divElement.querySelector('i') console.log(creditTypeNode, divElement.outerHTML) //creditTypeNode 可能为空 当他是赠礼或者花钱的时候 if (!creditTypeNode) return var parts = creditTypeNode.textContent.trim().split(' ') // 出现以下关键词则代表第一个部分不是类型 var keywords = ['金币', '血液', '咒术', '知识', '灵魂', '堕落', '旅程', '追随'] var reason = parts[0] // 检查原因是否包含关键词,如果包含则替换为"无" if (keywords.some(keyword => reason.includes(keyword))) { console.log("无奖励类型") reason = '无' } result.creditType = reason // 奖励类型为发表回复或发表主题时,记录区域 if (result.creditType === '发表回复' || result.creditType === '发表主题') { result.area = area } // 检查是否触发勋章 if (creditTypeNode.textContent.includes('勋章功能触发')) { result.badgeActivated = '是' } // 提取积分变化 const spans = divElement.querySelectorAll('span') spans.forEach(span => { const text = span.textContent let match if ((match = text.match(/(旅程|金币|血液|追随|咒术|知识|灵魂|堕落)\+(\d+)/))) { const key = keyMap[match[1]] result[key] = parseInt(match[2], 10) } else if ((match = text.match(/(旅程|金币|血液|追随|咒术|知识|灵魂|堕落)\-(\d+)/))) { const key = keyMap[match[1]] result[key] = -parseInt(match[2], 10) } }) // 保存记录 var historyArrayEx if (localStorage.getItem("extractedCreditHistory")) { historyArrayEx = JSON.parse(localStorage.getItem("extractedCreditHistory")) } else { historyArrayEx = new Array() } historyArrayEx.push(result) console.log(result) localStorage.setItem('extractedCreditHistory', JSON.stringify(historyArrayEx)) // 最后弹框提示 if (Config.开启提示框暂停) { if (result.creditType === '发表回复' || result.creditType === '发表主题') { setTimeout(function () { alert(divElement.textContent) }, 500) } } return 0 } //////以下是菜单部分///////// // 在新窗口通过调整样式来显示消息 // alert弹窗只会在原窗口弹出,不容易注意到,因此需通过此方法提示 function showMsg(msgID, pageContent) { var targetMsg = pageContent.getElementById(msgID) // 确保元素存在 if (targetMsg) { // 显示元素 targetMsg.style.display = 'block' // 设置5秒后隐藏元素 setTimeout(function () { targetMsg.style.display = 'none' }, 5000) } else { console.error('无法找到ID为' + msgID + '的元素') } } // 重新生成右侧表格,并将结果返回 function generateRightHTML() { //重新获取记录 var creditHistoryStr = localStorage.getItem('extractedCreditHistory') var creditHistory = JSON.parse(creditHistoryStr) //创建行号 var rowNumber = 0 var tempLvCheng = 0 var temmpJinBi = 0 var tempXueYe = 0 var tempZhouShu = 0 var tempZhiShi = 0 var tempLingHun = 0 var tempDuoLuo = 0 const checkCreditHistory = [] if (creditHistory) { creditHistory.forEach(function (item) { //检查过滤条件,不满足条件的item,返回true跳过执行 if (checkItem(item)) { return } rowNumber++ checkCreditHistory.push({ ...item, rowNumber }) tempLvCheng += item.lvCheng temmpJinBi += item.jinBi tempXueYe += item.xueYe tempZhouShu += item.zhouShu tempZhiShi += item.zhiShi tempLingHun += item.lingHun tempDuoLuo += item.duoLuo }) } // 格式化日期和时间 // 解析ISO 8601时间字符串为UTC时间,然后转为本地时间 var formattedDateTime = (date) => { var date = new Date(date) return date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2) } const settings = JSON.parse(localStorage.getItem("filterSettings")) const 显示灵魂期望 = Config.发帖灵魂统计 && settings.showFaTie const 显示回帖期望 = settings.showHuiTie const settingsDays = settings.days const headers = [ '行号', '奖励类型', '是否触发', '分区', '旅程', '金币', '血液', '咒术', '知识', '灵魂', '堕落', '时间', ...(显示灵魂期望 ? ['灵魂期望'] : []), ] const dataKeys = [ 'rowNumber', 'creditType', 'badgeActivated', 'area', 'lvCheng', 'jinBi', 'xueYe', 'zhouShu', 'zhiShi', 'lingHun', 'duoLuo', 'acquiredAt', ...(显示灵魂期望 ? ['linghunExpectations'] : []), ] const dataFormat = { acquiredAt: val => formattedDateTime(val), ...(显示灵魂期望 ? { linghunExpectations: (val, item) => item.creditType === '发表主题' ? linghunExpectationsFormat(val) : '' } : {}), } const mainTable = generateTable(checkCreditHistory, headers, dataKeys, dataFormat, true) const qiwangFormat = (obj, all) => { const format = (val) => all ? (val / all).toFixed(2) : 0 // return Object.fromEntries(Object.entries(obj).map(([key, val]) => [key, key === 'rowNumber' ? val : format(val)])) const num = Math.min(all, 30) return Object.fromEntries( Object.entries(obj).map(([key, val]) => { if (key === 'rowNumber') { return [key, val] } else if (key === 'temmpJinBi' && Number(settingsDays) === 1) { // 只在当天-30*2期望,跨天算起来就得每天判断是否满30回复,不能简单*30再减,因此干脆不算 return [key, `${format(val)}(${format(val - num * 2)})`] } else { return [key, format(val)] } })) } const allExpectations = JSON.parse(localStorage.getItem('回帖期望')) const summaryTableData = [ { rowNumber: rowNumber, tempLvCheng, temmpJinBi, tempXueYe, tempZhouShu, tempZhiShi, tempLingHun, tempDuoLuo }, qiwangFormat({ rowNumber: '实际期望', tempLvCheng, temmpJinBi, tempXueYe, tempZhouShu, tempZhiShi, tempLingHun, tempDuoLuo }, rowNumber), ...(allExpectations ? [{ rowNumber: '理论期望', ...allExpectations }] : []) ] // 小计表格 const summaryTable = generateSummaryTable(显示回帖期望 ? summaryTableData : summaryTableData.slice(0, 1)) // 计算分区 并 生成回帖数表格 const areaNum = getAreaNum(checkCreditHistory) const areaTable = generateAreaTable(areaNum) // 整合页面HTML var rightHTML = '<h3>当前记录汇总</h3>' + summaryTable + areaTable + mainTable return rightHTML } // 根据设置检查功能 function checkItem(item) { var showItem = false var catCheck = false var daysCheck = false var settings if (localStorage.getItem("filterSettings")) { settings = JSON.parse(localStorage.getItem("filterSettings")) } else { settings = { showHuiTie: true, showFaTie: true, showQiTa: false, days: 1 } localStorage.setItem('filterSettings', JSON.stringify(settings)) } //检查类型 if (settings.showHuiTie) { catCheck = ((catCheck) || (item.creditType == "发表回复")) } if (settings.showFaTie) { catCheck = ((catCheck) || (item.creditType == "发表主题")) } if (settings.showQiTa) { catCheck = ((catCheck) || ((item.creditType != "发表主题") && (item.creditType != "发表回复"))) } // 检查时间 if (settings.days != 0) { // 转换格式 var curDate = new Date() curDate.setHours(0, 0, 0, 0) //console.log(curDate); var recordDate = new Date(item.acquiredAt) recordDate.setHours(0, 0, 0, 0) //console.log(recordDate); // 获取目标日期 var targetDate = new Date(curDate.setDate(curDate.getDate() - settings.days + 1)) targetDate.setHours(0, 0, 0, 0) //console.log(targetDate); // 记录日期大于等于目标日期则显示,小于则返回跳过 if (recordDate.getTime() >= targetDate.getTime()) { daysCheck = true } } else { daysCheck = true } //类型筛选和时间筛选同时满足才显示 showItem = daysCheck && catCheck return !showItem } /////////////////脚本菜单主部分////////////// //创建查看数据菜单 GM_registerMenuCommand('查看账本', () => { viewLedger() }) function viewLedger() { // 创建一个隐藏的iframe var iframe = document.createElement('iframe') iframe.style.display = 'none' document.body.appendChild(iframe) // 读取localStorage中的creditHistory数据 var creditHistoryStr = localStorage.getItem('extractedCreditHistory') try { // 解析JSON字符串为对象数组 var creditHistory = JSON.parse(creditHistoryStr) //创建功能区 var fixedHTML = '<div id="fixedBox"><h3>记录工具箱</h3>' //创建记录存档操作区 var toolHTML = '<div id="toolBox">' // 添加导出按钮 toolHTML += '<button id="exportBtn">导出本地记录为.txt</button>' // 添加导入按钮和文件输入框 toolHTML += '<input type="file" id="importFile" accept=".txt" style="display:none;">' toolHTML += '<button id="importBtn">从.txt导入本地记录</button>' // 添加删除按钮 toolHTML += '<button id="deleteBtn">删除所有本地记录</button>' //提示信息 toolHTML += '<p id="exportNull" style="display: none;">没有数据可以导出。</p>' toolHTML += '<p id="importSuccess" style="display: none;">导入成功!</p>' toolHTML += '<p id="importFail" style="display: none;">导入失败,文件内容不是有效的JSON数组。</p>' toolHTML += '<p id="importError" style="display: none;">导入失败,无法解析文件内容,请前往主页面弹窗查看原因。</p>' toolHTML += '<div id="customConfirmModal" style="display: none;"><p>确定要删除所有本地积分记录吗?此操作不可逆!</p><div class="buttonContainer"><button id="confirmYes">确定</button><button id="confirmNo">取消</button></div></div>' toolHTML += '<p id="deleteSuccess" style="display: none;">已删除所有记录</p>' toolHTML += '</div>' //创建右侧记录筛选区 var filterHTML = '<div id="toolBox"><h3>筛选记录</h3>' filterHTML += ` <fieldset> <legend>奖励类型</legend> <div> <input type="checkbox" id="showHuiTie" name="showHuiTie" checked /> <label for="showHuiTie">回帖奖励</label> </div> <div> <input type="checkbox" id="showFaTie" name="showFaTie" checked /> <label for="showFaTie">发帖奖励</label> </div> <div> <input type="checkbox" id="showQiTa" name="showQiTa" checked /> <label for="showQiTa">其他奖励</label> </div> </fieldset> <fieldset> <legend>时间范围</legend> <input type="radio" id="option1" name="timeRange" value="1"> <label for="option1">当天</label><br> <input type="radio" id="option2" name="timeRange" value="custom" checked> <label for="option2">自定义 <input type="number" min="0" id="customDays" value="0"></label><br> <small>(N:过去N天内 1:当天 0:全部)</small> </fieldset> ` filterHTML += '</div>' //将左侧的模块都塞进fixed部分 fixedHTML += toolHTML fixedHTML += filterHTML fixedHTML += '</div>' //最后塞入用于显示/隐藏的按钮 fixedHTML += '<button id="toggleToolBoxBtn"></button>' //通过函数生成右侧表格内容(方便后期更新表格) var rightHTML = '<div id="tableBox">' + generateRightHTML() + '</div>' //总结构 var overallHTML = '<div class="container">' + fixedHTML + rightHTML + '</div>' // 插入到新窗口的文档中 iframe.contentDocument.body.innerHTML += overallHTML //////////////////// 添加css///////////////////////// // 获取IFrame的内容文档对象 var iframeDoc = iframe.contentDocument || iframe.contentWindow.document // 创建一个新的<style>元素 var styleTag = iframeDoc.createElement('style') // 定义CSS样式内容 var styles = ` #fixedBox { position: fixed; width: auto; height: 96vh; background: #eeeeee; float: left; min-width: 100px; overflow-y: auto; border-radius: 8px; font-family: Noto Sans SC, Microsoft Yahei, Arial, sans-serif; } .container { display: flex; } #toolBox { padding: 10px; } #exportBtn, #importBtn { margin: 6px auto 20px; } #deleteBtn { margin: auto; } #exportBtn, #importBtn, #deleteBtn { display: block; background-color: transparent; border: 2px solid #1A1A1A; border-radius: 0.6em; color: #3B3B3B; font-weight: 600; font-size: 14.4px; padding: 0.4em 1.2em; text-align: center; text-decoration: none; transition: all 300ms cubic-bezier(.23, 1, 0.32, 1); font-family: Noto Sans SC, Microsoft Yahei, Arial, sans-serif; } #exportBtn:hover, #importBtn:hover, #deleteBtn:hover { color: #fff; background-color: #1A1A1A; box-shadow: rgba(0, 0, 0, 0.25) 0 8px 15px; transform: translateY(-2px); } #exportBtn:active, #importBtn:active, #deleteBtn:active { box-shadow: none; transform: translateY(0); } h3 { display: block; text-align: center; font-size: 2em; margin: 36px auto 12px; } #customConfirmModal { border-style: solid; border-width: 3px; border-color: red; } #customConfirmModal p { color: red; margin: 20px; } #confirmYes { margin: 0 20% 20px 20%; } #customDays { width: 4em; font-family: Noto Sans SC, Microsoft Yahei, Arial, sans-serif; border-radius: 6px; border: 1px solid #333; } #tableBox { width: 80%; float: right; margin-left: 20%; } #tableBox table { margin: 20px auto 40px auto; border: 2px solid #333; border-radius: 6px; border-spacing: 0; overflow: hidden; } th, td { padding: 4px 8px; text-align: center; transition: all 0.2s; border: none; } th {background-color: #f2f2f2;} tr:nth-child(even) {background-color: #f2f2f2; transition: all 0.2s;} tr:hover {background-color: #d3d3d3; transition: all 0.2s;} #toggleToolBoxBtn { position: fixed; width: 16px; height: 16px; margin-top: 8px; margin-left: 8px; z-index: 1000; border: 1px solid #333; padding: 4px; border-radius: 50%; } fieldset { border: 2px solid #333; /* 设置边框 */ padding: 8px 12px 16px 12px; /* 设置内边距 */ margin: 20px 10px; /* 设置外边距 */ background-color: #f9f9f9; /* 设置背景颜色 */ border-radius: 6px; /* 设置圆角 */ } legend { font-weight: bold; /* 设置字体加粗 */ color: #000; /* 设置字体颜色 */ } label { font-size:14.4px; } ` // 将样式内容赋值给<style>元素的textContent属性 styleTag.textContent = styles // 将<style>元素添加到IFrame的<head>中 var head = iframeDoc.head || iframeDoc.getElementsByTagName('head')[0] head.appendChild(styleTag) //////////////添加网页标题//////////////// var titleElement = document.createElement('title') titleElement.innerText = 'PAUSE账本' head.appendChild(titleElement) ////////////根据浏览器类型选择是否更改iframe的src属性//////// if (brwoserType == "Firefox") { iframe.src = "PAUSE" console.log("检测到使用Firefox浏览器,已为iframe添加src属性") } } catch (e) { console.error('解析localStorage中的creditHistory失败:', e) // 如果解析失败,可以在这里处理错误,比如显示一个错误消息 iframe.contentDocument.body.textContent = '数据加载失败,请检查浏览器的localStorage设置。' } // 将iframe的内容复制到新窗口 var newWindow = window.open('', '_blank') newWindow.document.replaceChild( newWindow.document.importNode(iframe.contentDocument.documentElement, true), newWindow.document.documentElement ) //根据存储数据初始化界面 if (localStorage.getItem('filterSettings')) { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) newWindow.document.getElementById('showHuiTie').checked = filterSettings.showHuiTie newWindow.document.getElementById('showFaTie').checked = filterSettings.showFaTie newWindow.document.getElementById('showQiTa').checked = filterSettings.showQiTa newWindow.document.getElementById('option2').checked = true newWindow.document.getElementById('customDays').value = filterSettings.days console.log("初始化界面完成!") } // 显示/隐藏工具箱 // 名为toolBox 实际上是对外层fixedBox进行操作 newWindow.document.getElementById('toggleToolBoxBtn').addEventListener('click', function () { var toolBox = newWindow.document.getElementById('fixedBox') if (toolBox.style.display === 'none') { toolBox.style.display = 'block' } else { toolBox.style.display = 'none' } }) // 导出数据 // 给exportBtn添加点击事件监听器 newWindow.document.getElementById('exportBtn').addEventListener('click', function () { var creditHistoryStr = localStorage.getItem('extractedCreditHistory') if (creditHistoryStr) { var blob = new Blob([creditHistoryStr], { type: 'text/plain;charset=utf-8' }) var url = URL.createObjectURL(blob) var link = document.createElement('a') link.href = url link.download = 'extractedCreditHistory.txt' document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } else { //alert('没有数据可以导出。'); showMsg("exportNull", newWindow.document) } }) // 导入数据 // 绑定导入按钮的点击事件,触发文件选择对话框 newWindow.document.getElementById('importBtn').addEventListener('click', function () { newWindow.document.getElementById('importFile').click() }) // 绑定文件输入框的change事件,处理文件读取 newWindow.document.getElementById('importFile').addEventListener('change', function (e) { var file = e.target.files[0] if (!file) return var reader = new FileReader() reader.onload = function (e) { var content = e.target.result try { var parsedData = JSON.parse(content) if (Array.isArray(parsedData)) { localStorage.setItem('extractedCreditHistory', JSON.stringify(parsedData)) showMsg("importSuccess", newWindow.document) } else { //alert('导入失败,文件内容不是有效的JSON数组。'); showMsg("importFail", newWindow.document) } } catch (error) { alert('导入失败,无法解析文件内容: ' + error) showMsg("importError", newWindow.document) } } reader.readAsText(file) }) // 删除数据 // 绑定删除按钮的点击事件 newWindow.document.getElementById('deleteBtn').addEventListener('click', function () { var customConfirmModal = newWindow.document.getElementById('customConfirmModal') // 如果已经显示了,再次点击则隐藏提示框 if (customConfirmModal.style.display == 'block') { customConfirmModal.style.display = 'none' } else { customConfirmModal.style.display = 'block' } // 绑定自定义对话框内的确认和取消按钮事件 newWindow.document.getElementById('confirmYes').addEventListener('click', function () { customConfirmModal.style.display = 'none' localStorage.removeItem('extractedCreditHistory') localStorage.removeItem('filterSettings') showMsg("deleteSuccess", newWindow.document) }) newWindow.document.getElementById('confirmNo').addEventListener('click', function () { customConfirmModal.style.display = 'none' }) }) // 奖励类型 newWindow.document.getElementById('showHuiTie').addEventListener('change', function () { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.showHuiTie = this.checked localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改奖励类型筛选条件") }) newWindow.document.getElementById('showFaTie').addEventListener('change', function () { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.showFaTie = this.checked localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改奖励类型筛选条件") }) newWindow.document.getElementById('showQiTa').addEventListener('change', function () { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.showQiTa = this.checked localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改奖励类型筛选条件") }) // 天数筛选 newWindow.document.getElementById('option1').addEventListener('click', function () { if (this.checked) { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.days = 1 localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改时间范围条件") } }) newWindow.document.getElementById('option2').addEventListener('click', function () { if (this.checked) { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.days = newWindow.document.getElementById('customDays').value localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改时间范围条件") } }) newWindow.document.getElementById('customDays').addEventListener('change', function () { var option2 = newWindow.document.getElementById('option2') if (newWindow.document.getElementById('option2').checked) { var filterSettings = JSON.parse(localStorage.getItem('filterSettings')) filterSettings.days = this.value localStorage.setItem('filterSettings', JSON.stringify(filterSettings)) newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML() console.log("已更改时间范围条件") } }) // 重置筛选 // newWindow.document.getElementById('showAllBtn').addEventListener('click', function () { // var defaultSettings = { // showHuiTie: true, // showFaTie: true, // showQiTa: true, // days: 0 // }; // newWindow.document.getElementById('showHuiTie').checked = defaultSettings.showHuiTie; // newWindow.document.getElementById('showFaTie').checked = defaultSettings.showFaTie; // newWindow.document.getElementById('showQiTa').checked = defaultSettings.showQiTa; // newWindow.document.getElementById('option2').checked = true; // newWindow.document.getElementById('customDays').value = defaultSettings.days; // localStorage.setItem('filterSettings', JSON.stringify(defaultSettings)); // newWindow.document.getElementById('tableBox').innerHTML = generateRightHTML(); // console.log("已重置筛选条件"); // }); // 清理创建的iframe document.body.removeChild(iframe) } // 计算分区回帖数 function getAreaNum(historyArray) { let result = {} if (Config.显示默认区域) { result = { 'C G A I': 0, '生活爆照': 0, '和谐动漫': 0, '汉化游戏': 0, '和谐游戏': 0, } } historyArray.forEach(e => { if (e.area) { if (!result[e.area]) { result[e.area] = 1 } else { result[e.area] += 1 } } }) return result } // 生成计算分区回帖数表格 function generateAreaTable(data) { const newdata = [{ '回帖数': '', ...data }] const headers = Object.keys(data) const dataKeys = headers return generateTable(newdata, headers, dataKeys, {}, true) } // 生成总计 function generateSummaryTable(data) { const headers = ['行数', '旅程', '金币', '血液', '咒术', '知识', '灵魂', '堕落'] const dataKeys = ['rowNumber', 'tempLvCheng', 'temmpJinBi', 'tempXueYe', 'tempZhouShu', 'tempZhiShi', 'tempLingHun', 'tempDuoLuo'] return generateTable(data, headers, dataKeys, {}, true) } GM_registerMenuCommand('更新所有期望', () => { fetchData() .then(doc => { const linghunResult = processLinghun(doc) const huiResult = processHui(doc) localStorage.setItem('灵魂期望', JSON.stringify(linghunResult)) localStorage.setItem('回帖期望', JSON.stringify(huiResult)) alert('期望更新成功') }) .catch(error => { console.error('发生错误:', error) }) }) // 公共数据获取函数 function fetchData() { return fetch('https://www.gamemale.com/wodexunzhang-showxunzhang.html?action=my') .then(response => response.text()) .then(html => new DOMParser().parseFromString(html, 'text/html')) } // 灵魂期望处理 function processLinghun(doc) { const result = {} doc.querySelectorAll('.my_fenlei .myblok').forEach(element => { const linghun = [...element.querySelectorAll('.jiage.shuxing')].find(p => p.textContent.includes('灵魂')) const triggerProbability = [...element.querySelectorAll('.jiage')].find(p => p.textContent.includes('触发几率')) if (linghun && triggerProbability) { const probabilityMatch = triggerProbability.textContent.match(/触发几率 (\d+)%/) if (probabilityMatch) { const probability = parseFloat(probabilityMatch[1]) / 100 const countMatch = linghun.textContent.match(/发帖\s*[\u00A0]*灵魂\s*\+\s*(\d+)/) const count = countMatch ? parseInt(countMatch[1], 10) : 0 result[probability] = (result[probability] || 0) + count } } }) return result } // 回帖期望处理 function processHui(doc) { const xunzhangList = doc.querySelectorAll('.my_fenlei .myblok') const qiwang = pattern => { const result = { 金币: 0, 血液: 0, 咒术: 0, 知识: 0, 旅程: 0, 堕落: 0, 灵魂: 0 }; [...xunzhangList].forEach(block => { const text = block.textContent if (text.includes("已寄售")) return const probMatch = text.match(/几率 (\d+)%/i) if (!probMatch) return const probability = parseInt(probMatch[1], 10) / 100 const matches = Array.from(text.matchAll(pattern)) for (const match of matches) { const [, type, sign, value] = match result[type] += probability * parseInt(sign + value, 10) } }) return Object.fromEntries( Object.entries(result).map(([k, v]) => [k, Number(v).toFixed(2)]) ) } const hui = qiwang(/回帖\s+(.+?)\s([+-])(\d+)/gi) return { tempLvCheng: hui.旅程, temmpJinBi: hui.金币, tempXueYe: hui.血液, tempZhouShu: hui.咒术, tempZhiShi: hui.知识, tempLingHun: hui.灵魂, tempDuoLuo: hui.堕落 } } // 格式化函数保持原样 function linghunExpectationsFormat(result) { if (!result) return '暂无数据' const total = Object.entries(result).reduce((sum, [prob, count]) => sum + prob * count, 0) return `${total.toFixed(2)} = ${Object.entries(result).map(([k, v]) => `${k}(${v})`).join(' + ')}` } /** * 生成一个 HTML 表格。 * * @param {Array<Object>} data - 表格数据的数组,每个对象代表一行数据。 * @param {Array<string>} headers - 表头的名称数组,用于定义表格的列标题。 * @param {Array<string>} dataKeys - 数据对象中对应的键名数组,用于从数据中提取显示的值。 * @param {Object} [dataFormat] - 可选参数,包含格式化函数的对象,格式化每个字段的显示值。 * @param {boolean} [inHTML=false] - 可选参数,若为 true,则返回 HTML 字符串形式的表格;否则返回 DOM 元素。 * * @returns {HTMLElement|string} 返回生成的表格元素或 HTML 字符串,具体取决于 `inHTML` 参数的值。 * * @example * const data = [{lvcheng: 1, jinbi: 10}]; * const headers = ['旅程', '金币']; * const dataKeys = ['lvcheng', 'jinbi']; * const tableElement = generateTable(data, headers, dataKeys); * document.body.appendChild(tableElement); */ function generateTable(data, headers, dataKeys, dataFormat, inHTML) { if (dataFormat) { data = data.map(item => { const formattedItem = {} for (const key of dataKeys) { if (dataFormat[key]) { formattedItem[key] = dataFormat[key](item[key], item) } else { formattedItem[key] = item[key] } } return formattedItem }) } // 根据 uid 获取颜色配置,如果没有则使用默认配置(全黑) const colorMap = uid ? colorMapByUid[uid] || defaultColorMap : defaultColorMap let tableHTML = '<table><thead><tr>' // 生成表头,并应用颜色 headers.forEach(header => { const colorConfig = colorMap[header] tableHTML += `<th>${applyColor(header, colorConfig)}</th>` }) tableHTML += '</tr></thead><tbody>' // 生成表格数据行,并应用颜色 data.forEach(item => { tableHTML += '<tr>' dataKeys.forEach((key, index) => { const value = item[key] !== undefined ? item[key] : '' const header = headers[index] const colorConfig = colorMap[header] tableHTML += `<td>${applyColor(value, colorConfig)}</td>` }) tableHTML += '</tr>' }) tableHTML += '</tbody></table>' if (inHTML) { return tableHTML // 返回 HTML 字符串 } else { const table = document.createElement('div') table.innerHTML = tableHTML // 将 HTML 字符串插入到一个 div 中 return table.firstChild // 返回生成的 table 元素 } } /** * 应用颜色样式到 HTML 元素。 * * @param {string} content - 要显示的内容。 * @param {string|Object} colorConfig - 颜色配置,可以是字符串(颜色值或渐变)或对象(包含样式属性)。 * @returns {string} 返回应用了样式的 HTML 字符串。 */ function applyColor(content, colorConfig) { function camelToKebab(camelCase) { return camelCase.replace(/([A-Z])/g, '-$1').toLowerCase() } if (!colorConfig) return content let style = '' if (typeof colorConfig === 'object') { // 遍历对象属性,动态生成 style 字符串 for (const [key, value] of Object.entries(colorConfig)) { if (value) { const cssProperty = camelToKebab(key) // 转换驼峰式为短横线分隔 style += `${cssProperty}: ${value};` } } } else if (colorConfig.startsWith('linear-gradient')) { style = `background: ${colorConfig}; -webkit-background-clip: text; background-clip: text; color: transparent;` } else { style = `color: ${colorConfig};` } return `<span style="${style}">${content}</span>` } const colorMapByUid = { 694610: { '行数': 'black', // 行数:黑色 '旅程': { color: '#99FF00', // 文字颜色 textShadow: '#6666FF 0px 1px 3px, #6666FF 1px 0px 3px, #6666FF 0px -1px 3px, #6666FF -1px 0px 3px', // 发光效果 filter: 'glow(color=#6666FF, strength=3)' // 发光滤镜 }, '金币': { color: '#FF6666', // 文字颜色 textShadow: '#FFFF00 0px 1px 3px, #FFFF00 1px 0px 3px, #FFFF00 0px -1px 3px, #FFFF00 -1px 0px 3px', // 发光效果 filter: 'glow(color=#FFFF00, strength=3)' // 发光滤镜 }, '血液': { color: '#000000', // 文字颜色 textShadow: '#FF0000 0px 1px 3px, #FF0000 1px 0px 3px, #FF0000 0px -1px 3px, #FF0000 -1px 0px 3px', // 发光效果 filter: 'glow(color=#FF0000, strength=3)' // 发光滤镜 }, '咒术': { color: '#FFFFFF', // 文字颜色 textShadow: '#0000FF 0px 1px 3px, #0000FF 1px 0px 3px, #0000FF 0px -1px 3px, #0000FF -1px 0px 3px', // 发光效果 filter: 'glow(color=#0000FF, strength=3)' // 发光滤镜 }, '知识': { color: '#FFFFFF', // 文字颜色 textShadow: '#0099FF 0px 1px 3px, #0099FF 1px 0px 3px, #0099FF 0px -1px 3px, #0099FF -1px 0px 3px', // 发光效果 filter: 'glow(color=#0099FF, strength=3)' // 发光滤镜 }, '灵魂': { color: '#66FFFF', // 文字颜色 textShadow: '#0000FF 0px 1px 3px, #0000FF 1px 0px 3px, #0000FF 0px -1px 3px, #0000FF -1px 0px 3px', // 发光效果 filter: 'glow(color=#0000FF, strength=3)' // 发光滤镜 }, '堕落': { color: '#FFFFFF', // 文字颜色 textShadow: '#000000 0px 1px 3px, #000000 1px 0px 3px, #000000 0px -1px 3px, #000000 -1px 0px 3px', // 发光效果 filter: 'glow(color=#000000, strength=3)' // 发光滤镜 } }, 723150: { '行数': 'black', // 行数:黑色 '旅程': 'linear-gradient(to bottom, #90EE90, #008000)', // 旅程:从上到下的浅绿到绿色渐变 '金币': 'orange', // 金币:橙色 '血液': 'red', // 血液:红色 '咒术': 'purple', // 咒术:紫色 '知识': 'blue', // 知识:蓝色 '灵魂': 'linear-gradient(to bottom, #6A11CB, #2575FC)', // 灵魂:从上到下的紫色到蓝色渐变 '堕落': 'black' // 堕落:黑色 } } // 默认颜色配置(全黑) const defaultColorMap = { '行数': 'black', '旅程': 'black', '金币': 'black', '血液': 'black', '咒术': 'black', '知识': 'black', '灵魂': 'black', '堕落': 'black' } // 调用函数 addLedgerLink() // 插入链接 function addLedgerLink() { // 图片URL const icon = 'https://img.gamemale.com/album/202412/30/145538ebff949ooy6ybfo4.png.thumb.jpg' // 创建一个按钮元素 const div = document.createElement('div') // 设置按钮的样式 div.style.position = 'absolute' div.style.right = '0px' // div.style.left = '40px' // div.style.top = '-20px' div.style.background = 'none' // 去掉默认背景 div.style.cursor = 'pointer' // 鼠标悬停时显示为手型 // 创建图片元素 const img = document.createElement('img') img.src = icon img.width = 30 img.height = 30 div.appendChild(img) // 获取目标元素 const targetElement = document.querySelector("#um > div.u-info.col_2a > div.u-info-credit.col_3.sdw") // 将按钮添加到目标元素中 if (targetElement) { targetElement.insertBefore(div, targetElement.firstChild) // 插入到最前面 div.addEventListener('click', () => { viewLedger() }) } else { console.error('未找到class为"div.u-info-credit.col_3.sdw"的元素') } } })()