Greasy Fork is available in English.

看帖:PAUSE EA 持久版

勋章触发奖励时停+发帖回帖奖励账本查询!

// ==UserScript==
// @name         看帖:PAUSE EA 持久版
// @namespace    https://www.gamemale.com/space-uid-687897.html
// @version      0.8.8.10
// @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(4))])
            )
        }

        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"的元素')
        }
    }
})()