斗鱼鱼塘商城兑换工具

斗鱼鱼塘商城自动兑换工具 (注意手动设置个人用户ID以及房间号)

< Feedback on 斗鱼鱼塘商城兑换工具

Review: Good - script works

§
Posted: 11.1.2025

【自动钓鱼】风控拦截,请联系客服~
请求次数太多会被拦截,不给钓了

ahmeng97Author
§
Posted: 12.1.2025

兄弟,我7个直播间都没事 为啥你..

§
Posted: 13.1.2025

【自动钓鱼】风控拦截,请联系客服~
请求次数太多会被拦截,不给钓了

应该是启动兑换跑完没关,被系统屏蔽了一会。。。

ahmeng97Author
§
Posted: 13.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

§
Posted: 13.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

ahmeng97Author
§
Posted: 13.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

§
Posted: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

§
Posted: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

今天好像斗鱼把奖励缩减了 刚刚我0.1的换了2鱼翅就兑换完毕了

ahmeng97Author
§
Posted: 14.1.2025
Edited: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

今天好像斗鱼把奖励缩减了 刚刚我0.1的换了2鱼翅就兑换完毕了

哎 狗鱼, 怕脚本有延迟 而且现在我的版本改到乱七八糟不确定效果 上班没空 我在控制台直接粘贴代码去自动兑换, 结果.. 一个都没成功 去看网络请求..请求返回的时间显示准点 00分00秒 返回的数据是: error: 30002 msg: "奖品库存不足~"
真是条狗鱼呜呜呜 你查看兑换记录呢 之前我兑换的时候是1鱼翅5个 0.5 10个 0.1 50个的哇.. (真不知道之前怎么清空的 明明并发了会被拒绝 拉黑 越研究兑越少.. 现在我就换了水手 哈哈哈哈哈哈哈哈哈哈或或或或)

async function main() {
    async function sendRequest(index) {
        try {
            console.log('正在发送请求...');
            const response = await fetch("https://www.douyu.com/japi/revenuenc/web/actfans/convert/convertOpt", {
                "headers": {
                    "accept": "application/json, text/plain, */*",
                    "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
                    "content-type": "application/x-www-form-urlencoded",
                    "priority": "u=1, i",
                    "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"Windows\"",
                    "sec-fetch-dest": "empty",
                    "sec-fetch-mode": "cors",
                    "sec-fetch-site": "same-origin"
                },
                "referrer": "https://www.douyu.com/pages/fish-act/shop",
                "referrerPolicy": "strict-origin-when-cross-origin",
                "body": `ctn=1&rid=11410656&index=${index}`,
                "method": "POST",
                "mode": "cors",
                "credentials": "include"
            });
            console.log('请求发送成功,状态码:', response.status);
            const data = await response.json();
            console.log('请求响应数据:', data);
        } catch (error) {
            console.error('请求发送失败:', error);
        }
    }

    setInterval(async () => {
        const now = new Date();
        if (now.getMinutes() === 0 && now.getSeconds() === 0) {
            const indexes = ['PROP_2', 'YC_TY_1','YC_TY_2']; // 不同的 index 值列表 //之前尝试多选循环兑换
            for (let i = 0; i < 5; i++) {
                await sendRequest('YC_TY_3');
            }
        }
    }, 1000);
}

main();
§
Posted: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

今天好像斗鱼把奖励缩减了 刚刚我0.1的换了2鱼翅就兑换完毕了

上午我还在抢1鱼翅,中午直接显示兑换完毕,我查了下就兑换3个1鱼翅,还是凌晨兑换的,真恶心啊,意思明天所有鱼翅要减一半

§
Posted: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

今天好像斗鱼把奖励缩减了 刚刚我0.1的换了2鱼翅就兑换完毕了

上午我还在抢1鱼翅,中午直接显示兑换完毕,我查了下就兑换3个1鱼翅,还是凌晨兑换的,真恶心啊,意思明天所有鱼翅要减一半

得明天在看看 本来一天能换15鱼翅 现在不知道剩多少了 狗鱼

§
Posted: 14.1.2025

那正常.. 手动兑换卡点开几秒就好了 提前开也会噶 😂完完全全暴力请求 啥都不检测的😂 一瞬间就请求频繁了
我在搞新脚本那个 打算整合钓鱼兑换一起 通过检测兑换状态才发送兑换, 尽量避免被拉黑.. 可是.. 太慢了 这两天1鱼翅都没兑完 其他都兑完了

我也是1鱼翅没换完 是因为被拉黑了吗

拉黑通常就几分钟, 你兑换之前F12->网络 兑换完后 看一下 convertOpt 的请求 Response应该会显示请求频繁😂 , 我还没摸到限制的频率 苦思冥想这么多要兑换的 要怎么整.. 一次性并发的话 就前面几条请求通过了 后面全被拉黑了 一定抢不到了.. 不并发那么多奖品怎么整 调延迟的话会被别人抢光.. 还有被拉黑后钓鱼也会挂 手机点开也会加载不了 所以又不能钓鱼一起整..

我也是,1鱼翅这两天一般能抢3个左右,其他的能兑换完,刚刚8点钟时提前3秒启动兑换跑到30时候手动关,结果一个没抢到......

今天好像斗鱼把奖励缩减了 刚刚我0.1的换了2鱼翅就兑换完毕了

上午我还在抢1鱼翅,中午直接显示兑换完毕,我查了下就兑换3个1鱼翅,还是凌晨兑换的,真恶心啊,意思明天所有鱼翅要减一半

得明天在看看 本来一天能换15鱼翅 现在不知道剩多少了 狗鱼

而且现在更容易报403 Forbidden,之前快跑完手动关一般不会报

§
Posted: 15.1.2025

现在一天只给换8鱼翅了,1鱼翅3个,0.5鱼翅6个,0.1鱼翅20个,合起来8个,感觉不太需要太多房间挂了,一天只需要挂两个房间就足够了,挂钓鱼的收益不算兑换的话和消耗鱼饵对比也是亏本的,没必要挂太多了

§
Posted: 15.1.2025

兄弟,我7个直播间都没事 为啥你..



把你这带奖励统计的版本更新一下呗,虽然明知单靠挂回不了本

§
Posted: 15.1.2025
Edited: 15.1.2025

是不是因为今天下午,兑换页面变了,导致兑换编码变了。我心动卡连着好几次兑换1个都换不到,反而1鱼翅我都换完了。。。
还有多房间钓鱼的那个脚本,为啥我窗口打开,没看到UI,也没看到油猴显示脚本被加载0.0

§
Posted: 15.1.2025

现在一天只给换8鱼翅了,1鱼翅3个,0.5鱼翅6个,0.1鱼翅20个,合起来8个,感觉不太需要太多房间挂了,一天只需要挂两个房间就足够了,挂钓鱼的收益不算兑换的话和消耗鱼饵对比也是亏本的,没必要挂太多了

还有鱼翅碎片也对等鱼翅,就是不知道现在给兑多少个了,估计和0.1和0.5的一样多

ahmeng97Author
§
Posted: 15.1.2025

是不是因为今天下午,兑换页面变了,导致兑换编码变了。我心动卡连着好几次兑换1个都换不到,反而1鱼翅我都换完了。。。
还有多房间钓鱼的那个脚本,为啥我窗口打开,没看到UI,也没看到油猴显示脚本被加载0.0

诶 你要修改脚本一开始那个
@MATCH 为你希望启动脚本的网址, 我怕使用通配符 只要打开任何钓鱼界面就加载的话 只要不小心多开几个标签 应该百分百被拉黑 拒绝请求 哈哈哈哈

ahmeng97Author
§
Posted: 15.1.2025

现在一天只给换8鱼翅了,1鱼翅3个,0.5鱼翅6个,0.1鱼翅20个,合起来8个,感觉不太需要太多房间挂了,一天只需要挂两个房间就足够了,挂钓鱼的收益不算兑换的话和消耗鱼饵对比也是亏本的,没必要挂太多了

还有鱼翅碎片也对等鱼翅,就是不知道现在给兑多少个了,估计和0.1和0.5的一样多

根据今天的兑换记录来看 心动卡 没兑成功(昨天是2张没错) 2 碎片现在还是5个了 5 碎片暂时还没兑换成功.. 之前是4个 鱼翅 0.1 20个 (本来50个) 鱼翅 0.5 6个 (本来10个) 鱼翅 1 (本来5个) 我还没兑成功 根据其他人反馈是 3个

至于为什么那么晚还没兑换成功.. 不知道是优化后的代码问题 还是被针对了 我查看网络请求 准点00秒发送的查询请求 全部显示状态为刷新中.. 只能10点再看情况了

"data": {
        "time": 1736942400, //这个时间戳代表着 晚上8点整
        "num": 151019,
 "index": "PROP_3",
                "awardInfo": {
                    "awardType": 10003,
                    "awardId": 2562,
                    "awardNum": 1,
                    "awardNum2": 0,
                    "awardName": "心动卡",
                    "awardPic": "https://gfs-op.douyucdn.cn/dygift/2019/01/23/d0f8e3bb5420eb11c66d301a3a2fa610.gif",
                    "awardSort": 1,
                    "awardNumShow": 1.0
                },
                "type": 1,
                "badgeLimit": 6,
                "consumeNum": 1500,
                "giftId": 0,
                "giftNum": 0,
                "giftPrice": 0.0,
                "status": 3, //3为刷新中
                "circle": 1,
§
Posted: 15.1.2025

现在一天只给换8鱼翅了,1鱼翅3个,0.5鱼翅6个,0.1鱼翅20个,合起来8个,感觉不太需要太多房间挂了,一天只需要挂两个房间就足够了,挂钓鱼的收益不算兑换的话和消耗鱼饵对比也是亏本的,没必要挂太多了

还有鱼翅碎片也对等鱼翅,就是不知道现在给兑多少个了,估计和0.1和0.5的一样多

根据今天的兑换记录来看
心动卡 没兑成功(昨天是2张没错)
2 碎片现在还是5个了
5 碎片暂时还没兑换成功.. 之前是4个
鱼翅 0.1 20个 (本来50个)
鱼翅 0.5 6个 (本来10个)
鱼翅 1 (本来5个) 我还没兑成功 根据其他人反馈是 3个

至于为什么那么晚还没兑换成功.. 不知道是优化后的代码问题 还是被针对了 我查看网络请求 准点00秒发送的查询请求 全部显示状态为刷新中.. 只能10点再看情况了

"data": {
        "time": 1736942400, //这个时间戳代表着 晚上8点整
        "num": 151019,
 "index": "PROP_3",
                "awardInfo": {
                    "awardType": 10003,
                    "awardId": 2562,
                    "awardNum": 1,
                    "awardNum2": 0,
                    "awardName": "心动卡",
                    "awardPic": "https://gfs-op.douyucdn.cn/dygift/2019/01/23/d0f8e3bb5420eb11c66d301a3a2fa610.gif",
                    "awardSort": 1,
                    "awardNumShow": 1.0
                },
                "type": 1,
                "badgeLimit": 6,
                "consumeNum": 1500,
                "giftId": 0,
                "giftNum": 0,
                "giftPrice": 0.0,
                "status": 3, //3为刷新中
                "circle": 1,

心动卡和碎片没变,只是减少鱼翅兑换数量了,至于你整点兑换不成功,是不是你时间取的本地时间,不是斗鱼的时间,从倒计时看,斗鱼的时间是比百度上的时间和本地网络同步的时间要快1秒+的

是不是因为今天下午,兑换页面变了,导致兑换编码变了。我心动卡连着好几次兑换1个都换不到,反而1鱼翅我都换完了。。。
还有多房间钓鱼的那个脚本,为啥我窗口打开,没看到UI,也没看到油猴显示脚本被加载0.0

诶 你要修改脚本一开始那个
@MATCH 为你希望启动脚本的网址, 我怕使用通配符 只要打开任何钓鱼界面就加载的话 只要不小心多开几个标签 应该百分百被拉黑 拒绝请求 哈哈哈哈

我看了一下= =,要这么写才能应用。
https://www.douyu.com/pages/fish-act/mine?rid=房间号&isAnchorSide=0
后边这个&isAnchorSide=0 不加,我这边脚本怎么都运行不起来emmm
然后那个物品获取统计要怎么调出来0.0,我只看到有个显示记录,显示的以往钓鱼获得的东西。
我这边今天还差一个心动卡没兑换完,其他都兑换完了:
心动卡 还是2个,但是很难抢,我今天就抢到1个
0.1鱼翅 只有20个(之前是50个)
0.5鱼翅 只有6个 (之前10个)
1鱼翅 只有3个 (之前5个)
2鱼翅碎片 只有5个 (还是5个)
5鱼翅碎片 只有4个 (还是4个)
——————————
初级水手 10个
精英水手 5个(咋感觉之前这个是10个来着?)

然后这个多选兑换,貌似时间有些问题= =,我Linux虚拟机挂着自动兑换T T,2晚上没换到东西,手动都正常

ahmeng97Author
§
Posted: 15.1.2025

然后这个多选兑换,貌似时间有些问题= =,我Linux虚拟机挂着自动兑换T T,2晚上没换到东西,手动都正常

可能你浏览器记忆了这个URL 每次输入自动填充, 因为我手动打是仅房间号, 你可以加 &... 也可以直接房间号后加一个 * 通配符 哈哈哈 因为match 不加通配符要完全符合URL才能运行

士官我测试的时候是5个 哈哈 10个水手=1鱼翅 5个士官=5鱼翅

linux虚拟机的话 是不是会自动锁屏之类的.. 离开虚拟机也不知道触发浏览器勤俭持家的机制.. 就像我之前说的那些 需要置顶 不可最小化钓鱼 不可最大化其他窗口 不能切别的标签页 要单独一个窗口 之类的一堆限制 , 不然浏览器就会开始勤俭持家 60秒才检测一次 你自动兑换的是1秒检测一次 如果浏览器在勤俭持家的时候不是00秒 例如是30秒 那就是每分钟30秒检测一次 永远不会触发自动兑换 哈哈哈哈哈

你试试在F12 控制台运行这个代码 然后按你平时的挂机方法 过半分钟 几分钟 打开虚拟机看看就知道结果了

let lastUpdateTime = null;

function timerFunction() {
    const startTime = performance.now();
    console.log(`定时器触发,当前时间: ${new Date().toISOString()}`);

    if (lastUpdateTime!== null) {
        const interval = startTime - lastUpdateTime;
        if (interval > 1500) {
            console.error(`定时器间隔超过 1500 毫秒,实际间隔为 ${interval} 毫秒`);
        }
    }
    lastUpdateTime = startTime;
}

setInterval(timerFunction, 1000);

然后这个多选兑换,貌似时间有些问题= =,我Linux虚拟机挂着自动兑换T T,2晚上没换到东西,手动都正常

可能你浏览器记忆了这个URL 每次输入自动填充, 因为我手动打是仅房间号, 你可以加 &... 也可以直接房间号后加一个 * 通配符 哈哈哈 因为match 不加通配符要完全符合URL才能运行

士官我测试的时候是5个 哈哈 10个水手=1鱼翅 5个士官=5鱼翅

linux虚拟机的话 是不是会自动锁屏之类的.. 离开虚拟机也不知道触发浏览器勤俭持家的机制.. 就像我之前说的那些 需要置顶 不可最小化钓鱼 不可最大化其他窗口 不能切别的标签页 要单独一个窗口 之类的一堆限制 , 不然浏览器就会开始勤俭持家 60秒才检测一次
你自动兑换的是1秒检测一次 如果浏览器在勤俭持家的时候不是00秒 例如是30秒 那就是每分钟30秒检测一次 永远不会触发自动兑换 哈哈哈哈哈

你试试在F12 控制台运行这个代码 然后按你平时的挂机方法 过半分钟 几分钟 打开虚拟机看看就知道结果了

let lastUpdateTime = null;

function timerFunction() {
    const startTime = performance.now();
    console.log(`定时器触发,当前时间: ${new Date().toISOString()}`);

    if (lastUpdateTime!== null) {
        const interval = startTime - lastUpdateTime;
        if (interval > 1500) {
            console.error(`定时器间隔超过 1500 毫秒,实际间隔为 ${interval} 毫秒`);
        }
    }
    lastUpdateTime = startTime;
}

setInterval(timerFunction, 1000);

emmmm,那个统计奖励的脚本能更新一下不,想看看钓鱼一直弄划算,还是就12点开始每个小时钓半个钟划算 T T,因为感觉有的时候运气特别好,光一天钓鱼回本了10多鱼翅

ahmeng97Author
§
Posted: 15.1.2025

emm 我自动兑换剩下心动卡和1鱼翅 还没测试完 你改改房间号用着吧

  1. Match的地址
  2. 房间号
  3. 兑换房间号
    后面我会丢到那个脚本更新

斗鱼单窗口多房间钓鱼脚本

目前版本测试兑换工具中


// ==UserScript==
// @name         斗鱼多房间钓鱼脚本+兑换工具v2.1
// @namespace    https://github.com/your_username
// @version      1.3
// @description  多房间自动钓鱼脚本,支持并发钓鱼与数据记录
// @match        https://www.douyu.com/pages/fish-act/mine?rid=11410656
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant       GM_addStyle
// ==/UserScript==

// 配置项
const CONFIG = {
    CHECK_INTERVAL: 1000,//检查间隔
    RETRY_DELAY: 1500,//重试间隔
    MIN_OPERATION_INTERVAL:500,//队列间隔
    STORAGE_KEYS: {
        AUTO_FISH: 'ExSave_AutoFish',
        EVENTAUTO_FISH: 'ExSave_EventAutoFish',
        FISH_RECORDS: 'ExSave_FishRecords',
        ERROR_LOGS: 'ExSave_ErrorLogs'
    },
    MAX_RECORDS: 1000, // 每个房间最多保存的记录数
    MAX_ERRORS: 100,   // 每个房间最多保存的错误数
    API_ENDPOINTS: {
        FISH_INFO: 'https://www.douyu.com/japi/revenuenc/web/actfans/achieve/accList',
        HOMEPAGE: 'https://www.douyu.com/japi/revenuenc/web/actfans/fishing/homePage',
        START_FISH: 'https://www.douyu.com/japi/revenuenc/web/actfans/fishing/fishing',
        END_FISH: 'https://www.douyu.com/japi/revenuenc/web/actfans/fishing/reelIn',
        FISH_SHOP: 'https://www.douyu.com/japi/revenuenc/web/actfans/convert/convertOpt'
    }
};

// 房间配置
const rids = [8514651, 11805787, 11410656, 6282878, 5045057, 10313462, 10767547];
// 固定一个粉丝牌等级大于3的直播间 用于兑换功能
const erid = 11410656;

const roomStates = {};
// 初始化房间状态
rids.forEach(rid => {
    roomStates[rid] = {
        baitId: null,
        nextFishEndTime: 0,
        isFishing: false,
        timer: null,
        lock:false
    };
});


// 工具函数
let lastUpdateTime = null;
const utils = {
    sleep: (time) => new Promise((resolve) => setTimeout(resolve, time)),

    setCookie: (name, value) => {
        const exp = new Date();
        exp.setTime(exp.getTime() + 3 * 60 * 60 * 1000);
        document.cookie = `${name}=${escape(value)}; path=/; expires=${exp.toGMTString()}`;
    },

    getCookie: (name) => {
        const reg = new RegExp(`(^| )${name}=([^;]*)(;|$)`);
        const arr = document.cookie.match(reg);
        return arr ? unescape(arr[2]) : null;
    },

    getCCN: () => {
        let ccn = utils.getCookie("acf_ccn");
        //console.log(`获取acf_ccn 数值为: ${ccn}`);
        if (!ccn) {
            utils.setCookie("acf_ccn", "1");
            ccn = "1";
            //console.log(`设置acf_ccn 数值为: ${ccn}`);
        }
        return ccn;
    },
    isActivityTime:() => {
        let now = new Date();
        let hour = now.getHours();
        let minute = now.getMinutes();
        // 判断是否在活动时间范围内(中午 12:00 到凌晨 00:30 每个准点开启半小时)
        return (hour >= 12 && hour < 24 && minute < 30) || (hour === 0 && minute < 30);
    },
    updateTime:() =>{
        // const timePanel = document.getElementById('time-panel');
        // const now = new Date();
        // timePanel.innerText = "最后检查时间: " + now.toLocaleString();
        const timePanel = document.getElementById('time-panel');
        const now = new Date();
        const currentTime = now.getTime();

        // 如果上次更新时间存在
        if (lastUpdateTime!== null) {
            // 计算时间差(毫秒)
            const timeDiff = currentTime - lastUpdateTime;

            // 如果时间差大于1秒
            if (timeDiff > 1500) {
                // 保存当前时间
                const errorLogDiv = document.getElementById('error-log');
                errorLogDiv.innerText += `Error: 时间间隔大于1秒${timeDiff},上次更新时间:${new Date(lastUpdateTime).toLocaleString()}\n`;

                // // 延迟1秒后更新时间(可根据需要调整延迟时间)
                // setTimeout(() => {
                    timePanel.innerText = "最后检查时间: " + now.toLocaleString();
                    lastUpdateTime = now.getTime();
                // }, 1000);
            } else {
                // 正常更新时间
                timePanel.innerText = "最后检查时间: " + now.toLocaleString();
                lastUpdateTime = now.getTime();
            }
        } else {
            // 首次更新时间
            timePanel.innerText = "最后检查时间: " + now.toLocaleString();
            lastUpdateTime = now.getTime();
        }
    }
};
// 1. 首先添加一个请求队列管理器类
class RequestQueueManager {
    constructor() {
        this.queue = [];
        this.isProcessing = false;
    }

    // 添加请求到队列
    async addRequest(requestFn, priority = 0) {
        return new Promise((resolve, reject) => {
            this.queue.push({
                requestFn,
                priority,
                resolve,
                reject,
                timestamp: Date.now()
            });

            // 按优先级和时间戳排序
            this.queue.sort((a, b) => {
                if (a.priority !== b.priority) {
                    return b.priority - a.priority;
                }
                return a.timestamp - b.timestamp;
            });

            this.processQueue();
        });
    }

    // 处理队列
    async processQueue() {
        if (this.isProcessing || this.queue.length === 0) return;

        this.isProcessing = true;

        try {
            const request = this.queue.shift();
            const result = await request.requestFn();
            request.resolve(result);
        } catch (error) {
            const request = this.queue[0];
            request.reject(error);
        } finally {
            this.isProcessing = false;
            // 添加延迟以控制请求频率
            await utils.sleep(CONFIG.MIN_OPERATION_INTERVAL);
            // 继续处理队列中的下一个请求
            if (this.queue.length > 0) {
                this.processQueue();
            }
        }
    }

    // 清空队列
    clearQueue() {
        this.queue = [];
    }

    // 获取队列长度
    get length() {
        return this.queue.length;
    }
}

class ApiManager {
    constructor(queueManager) {
        this.queueManager = queueManager;
    }

    async request(url, options = {}, priority = 0) {
        return this.queueManager.addRequest(async () => {
            try {
                const defaultOptions = {
                    mode: "no-cors",
                    cache: "default",
                    credentials: "include",
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded",
                    }
                };

                const response = await fetch(url, {
                    ...defaultOptions,
                    ...options,
                    headers: {
                        ...defaultOptions.headers,
                        ...options.headers
                    }
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                return await response.json();
            } catch (error) {
                console.error(`API请求失败: ${url}`, error);
                return { error: -1, msg: `请求失败: ${error.message}` };
            }
        }, priority);
    }

    async getFishInfo(rid) {
        return this.request(
            `${CONFIG.API_ENDPOINTS.FISH_INFO}?rid=${rid}&type=1&period=1`,
            {},
            1
        ).then(response => response.data?.accList || []);
    }

    async getHomepageData(rid) {
        return this.request(
            `${CONFIG.API_ENDPOINTS.HOMEPAGE}?rid=${rid}&opt=1`,
            {},
            1
        );
    }

    async startFish(rid, baitId) {
        return this.request(
            CONFIG.API_ENDPOINTS.START_FISH,
            {
                method: "POST",
                body: `ctn=${utils.getCCN()}&rid=${rid}&baitId=${baitId}`
            },
            2
        );
    }

    async endFish(rid) {
        return this.request(
            CONFIG.API_ENDPOINTS.END_FISH,
            {
                method: "POST",
                body: `ctn=${utils.getCCN()}&rid=${rid}`
            },
            2
        );
    }
    async startExchange(rid,selectedIndexValue) {
        return this.request(
            CONFIG.API_ENDPOINTS.FISH_SHOP,
            {
                method: "POST",
                body: `ctn=${utils.getCCN()}&rid=${rid}&index=${selectedIndexValue}`
            },
            2
        );
    }
}
// 数据存储管理
class StorageManager {
    constructor() {
        this.initializeStorage();
    }

    initializeStorage() {
        if (!localStorage.getItem(CONFIG.STORAGE_KEYS.FISH_RECORDS)) {
            localStorage.setItem(CONFIG.STORAGE_KEYS.FISH_RECORDS, JSON.stringify({}));
        }
        if (!localStorage.getItem(CONFIG.STORAGE_KEYS.ERROR_LOGS)) {
            localStorage.setItem(CONFIG.STORAGE_KEYS.ERROR_LOGS, JSON.stringify({}));
        }
    }

    saveFishRecord(rid, record) {
        const records = this.getFishRecords();
        if (!records[rid]) records[rid] = [];

        records[rid].unshift({
            ...record,
            timestamp: new Date().toISOString()
        });

        // 限制记录数量
        if (records[rid].length > CONFIG.MAX_RECORDS) {
            records[rid] = records[rid].slice(0, CONFIG.MAX_RECORDS);
        }

        localStorage.setItem(CONFIG.STORAGE_KEYS.FISH_RECORDS, JSON.stringify(records));
    }

    saveErrorLog(rid, error) {
        const errors = this.getErrorLogs();
        if (!errors[rid]) errors[rid] = [];

        errors[rid].unshift({
            error,
            timestamp: new Date().toISOString()
        });

        // 限制错误日志数量
        if (errors[rid].length > CONFIG.MAX_ERRORS) {
            errors[rid] = errors[rid].slice(0, CONFIG.MAX_ERRORS);
        }

        localStorage.setItem(CONFIG.STORAGE_KEYS.ERROR_LOGS, JSON.stringify(errors));
    }

    getFishRecords() {
        return JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEYS.FISH_RECORDS) || '{}');
    }

    getErrorLogs() {
        return JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEYS.ERROR_LOGS) || '{}');
    }

    clearRecords(rid) {
        const records = this.getFishRecords();
        if (rid) {
            delete records[rid];
        } else {
            Object.keys(records).forEach(key => delete records[key]);
        }
        localStorage.setItem(CONFIG.STORAGE_KEYS.FISH_RECORDS, JSON.stringify(records));
    }

    clearErrors(rid) {
        const errors = this.getErrorLogs();
        if (rid) {
            delete errors[rid];
        } else {
            Object.keys(errors).forEach(key => delete errors[key]);
        }
        localStorage.setItem(CONFIG.STORAGE_KEYS.ERROR_LOGS, JSON.stringify(errors));
    }
}


class FishingManager {
    constructor(storageManager, apiManager) {
        this.fishInfo = [];
        this.storageManager = storageManager;
        this.apiManager = apiManager;
    }
    async init() {
        try {
            // 获取鱼类信息
            this.fishInfo = await this.apiManager.getFishInfo(rids[0]);
            // 检查初始状态
            return this.checkInitialState();
        } catch (error) {
            console.error('初始化失败:', error);
            this.storageManager.saveErrorLog('system', '初始化失败: ' + error.message);
            return false;
        }
    }

    async checkInitialState() {
        try { //参考小淳大佬
            for (const rid of rids) {
                const homepageRes = await this.apiManager.getHomepageData(rid);
                if (!homepageRes.data) {
                    console.error(`【房间 ${rid}】未能获取活动信息`);
                    this.storageManager.saveErrorLog(rid, '未能获取活动信息');
                    return false;
                }

                // 检查鱼饵
                const baitData = homepageRes.data.baits.find(item => item.inUse);
                if (!baitData) {
                    console.error(`【房间 ${rid}】请设置鱼饵`);
                    this.storageManager.saveErrorLog(rid, '请设置鱼饵');
                    return false;
                }
                roomStates[rid].baitId = baitData.id;

                // 检查形象
                if (!homepageRes.data.myCh) {
                    console.error(`【房间 ${rid}】请设置形象`);
                    this.storageManager.saveErrorLog(rid, '请设置形象');
                    return false;
                }

                // 检查钓鱼状态
                await this.handleFishingState(rid, homepageRes.data.fishing);
            }
            return true;
        } catch (error) {
            console.error('检查初始状态失败:', error);
            this.storageManager.saveErrorLog('system', '检查初始状态失败: ' + error.message);
            return false;
        }
    }

    async handleFishingState(rid, fishingData) {
        const state = roomStates[rid];
        switch(fishingData.stat) {
            case 0: // 未开始
                state.isFishing = false;
                state.nextFishEndTime = 0;
                break;
            case 1: // 进行中
                state.isFishing = true;
                state.nextFishEndTime = fishingData.fishEtMs;
                break;
            case 2: // 未收杆
                await this.endFishing(rid);
                await utils.sleep(CONFIG.RETRY_DELAY);
                break;
        }
    }

    async startFishing(rid) {
        const eventAutoFish = JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEYS.EVENTAUTO_FISH))?.isAutoFish;
        if (eventAutoFish) {
            if (!utils.isActivityTime()) {
                //console.log("不在活动时间内,不执行钓鱼操作");
                return false;
            }
        }
        try {
            const state = roomStates[rid];
            state.lock = true;
            const fishRes = await this.apiManager.startFish(rid, state.baitId);
            state.lock = false;
            if (fishRes.error !== 0) {
                const errorMsg = `【startFishing 函数:房间 ${rid}】${fishRes.msg}`;
                console.error(errorMsg);
                this.storageManager.saveErrorLog(rid, errorMsg);
                //1001007 操作失败刷新重试
                // if (fishRes.error === 1001007) await this.endFishing(rid);
                // if (fishRes.error === 1001007) {
                //     setTimeout(async () => {
                //         await this.endFishing(rid);
                //     }, 1000);//1秒后重试
                // }; 不需要重试 定时器会自动重试
                //小淳大佬的错误代码 还不知道啥玩意 可能是没鱼饵了?
                if (fishRes.error === 1005003) this.stopFishing(rid);
                return false;
            }

            state.isFishing = true;
            state.nextFishEndTime = fishRes.data.fishing.fishEtMs;
            return true;
        } catch (error) {
            console.error(`【startFishing 函数:房间 ${rid}】开始钓鱼失败:`, error);
            this.storageManager.saveErrorLog(rid, '开始钓鱼失败: ' + error.message);
            return false;
        }
    }

    async endFishing(rid) {
        try {
            const state = roomStates[rid];
            state.lock = true;
            const fishRes = await this.apiManager.endFish(rid);
            state.lock = false;
            if (fishRes.error !== 0) {
                const errorMsg = `endFishing 函数1:房间 ${rid} 收杆失败: ${fishRes.msg || JSON.stringify(fishRes)}`;
                console.error(errorMsg);
                this.storageManager.saveErrorLog(rid, errorMsg);

                const homepageRes = await this.apiManager.getHomepageData(rid);
                if (homepageRes.data?.fishing.stat === 0) {
                    state.isFishing = false;
                    state.nextFishEndTime = 0;
                }
                return;
            }

            this.logFishingResult(rid, fishRes);
            state.isFishing = false;
        } catch (error) {
            console.error(`【endFishing 函数2:房间 ${rid}】收杆失败:`, error);
            this.storageManager.saveErrorLog(rid, '收杆失败: ' + error.message);
        }
    }

    logFishingResult(rid, fishRes) {
        try {
            const record = {
                fishId: fishRes.data.fish.id,
                weight: fishRes.data.fish.wei,
                awards: fishRes.data.awards || []
            };

            const fishData = this.fishInfo.find(item => item.fishId === record.fishId);
            if (fishData) {
                record.fishName = fishData.name;
            }

            // 保存记录到存储
            this.storageManager.saveFishRecord(rid, record);

            // 控制台输出
            const messages = [`【房间 ${rid} 钓鱼】`];
            if (fishData) {
                messages.push(`获得${fishData.name}${record.weight}斤`);
            }
            if (record.awards.length > 0) {
                const awards = record.awards.map(
                    award => `获得${award.awardName}x${award.awardNumShow}`
                );
                messages.push(...awards);
            }
            if (messages.length > 1) {
                console.log(messages.join(fishData ? "," : ""));
            }
        } catch (error) {
            console.error(`【房间 ${rid}】记录结果失败:`, error);
            this.storageManager.saveErrorLog(rid, '记录结果失败: ' + error.message);
        }
    }

    startAutoFishing(rid) {
        const state = roomStates[rid];
        state.timer = setInterval(async () => {
            utils.updateTime();
            try {
                if(!state.lock)
                {
                    if (state.isFishing) {
                        const now = new Date().getTime();
                        if (now <= state.nextFishEndTime) return;
                        // console.log(`${rid} 476开始收杆`);
                        await this.endFishing(rid);
                    } else {
                        // console.log(`${rid} 479开始抛竿`);
                        await this.startFishing(rid);
                    }
                }
            } catch (error) {
                console.error(`【startautoFishing 函数:房间 ${rid}】自动钓鱼异常:`, error);
                this.storageManager.saveErrorLog(rid, '自动钓鱼异常: ' + error.message);
            }
        }, CONFIG.CHECK_INTERVAL);
    }

    stopFishing(rid) {
        if (roomStates[rid].timer) {
            clearInterval(roomStates[rid].timer);
            roomStates[rid].timer = null;
        }
    }

    stopAllFishing() {
        rids.forEach(rid => this.stopFishing(rid));
    }
}
//鱼塘商城兑换
class ExchangeManager {
    constructor(apiManager) {
        this.apiManager = apiManager;
        this.isRunning = false;
        this.autoExchangeEnabled = false;
        this.autoExchangeCount = 0;
        this.selectedIndexValues = [];
        this.exchangeInterval = null;
        this.concurrentRequests = 0;
        this.maxConcurrentRequests = 5; // 最大并发请求数
        this.excludedIndexes = new Set(); // 用于存储需要排除的 index
        this.pauseAllRequests = false; // 用于暂停所有请求的标志
    }

    async checkExchangeStatus(rid) {
        return new Promise((resolve, reject) => {
            fetch(`https://www.douyu.com/japi/revenuenc/web/actfans/convert/convertPanel?rid=${rid}`, {
                "headers": {
                    "accept": "application/json, text/plain, */*",
                    "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
                    "priority": "u=1, i",
                    "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"Windows\"",
                    "sec-fetch-dest": "empty",
                    "sec-fetch-mode": "cors",
                    "sec-fetch-site": "same-origin"
                },
                "referrer": "https://www.douyu.com/pages/fish-act/shop",
                "referrerPolicy": "strict-origin-when-cross-origin",
                "body": null,
                "method": "GET",
                "mode": "cors",
                "credentials": "include"
            })
          .then(response => response.json())
          .then(data => {
                if (!Array.isArray(data.data.convertList)) {
                    console.error('convertList 不是一个数组', data);
                    reject(new Error('convertList 数据格式错误'));
                } else {
                    resolve(data.data.convertList);
                }
            })
          .catch(error => {
                console.error('获取兑换状态失败:', error);
                reject(error);
            });
        });
    }


    async directExchange(selectedIndexValue, rid) {

            try {
                await new Promise(resolve => setTimeout(resolve, 100));
                const response = await fetch(CONFIG.API_ENDPOINTS.FISH_SHOP, {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded"
                    },
                    body: `ctn=${utils.getCCN()}&rid=${rid}&index=${selectedIndexValue}`,
                    credentials: "include"
                });
                const data = await response.json();
                if (data.error === -1) {
                    console.log('操作频繁 等待500毫秒');
                    await new Promise(resolve => setTimeout(resolve, 500));
                }
            } catch (error) {
                console.error('兑换请求失败:', error);
            }
    }

    async runExchangeLoop(rid, updateUI) {
        let count = 1;
        const targetCount = 50;

        while (this.isRunning && count <= targetCount) {
            const promises = [];

            // 并发发送所有选中商品的兑换请求
            for (const selectedIndexValue of this.selectedIndexValues) {
                for (let i = 0; i < 1; i++) { // 每个商品发送1次请求 //本来是3 修改了不移除for备用
                    promises.push(this.directExchange(selectedIndexValue, rid));
                }
            }

            // 等待所有请求完成
            await Promise.all(promises);

            console.log(`当前兑换循环次数: ${count}`);
            count++;
            updateUI(this.isRunning, targetCount - count);

            if (count >= targetCount) {
                this.isRunning = false;
                updateUI(this.isRunning, 0);
                break;
            }

            // 短暂延迟避免请求过于密集
            await new Promise(resolve => setTimeout(resolve, 20));
        }
    }
    startAutoExchange(rid) {
    if (this.exchangeInterval) return;

    this.exchangeInterval = setInterval(async () => {
        const currentTime = this.getCurrentTime();

        if (this.isInTimeRange() && this.autoExchangeEnabled) {
            this.excludedIndexes.clear(); // 重置排除的 index 集合
            const loopTimesMap = {
                "PROP_1": 10,
                "PROP_2": 5,
                "PROP_3": 2,
                "FREE_PROP_1": 10,
                "FREE_PROP_2": 10,
                "FREE_PROP_3": 10,
                "YC_TY_1": 50,
                "YC_TY_2": 10,
                "YC_TY_3": 5,
                "YW_1": 10,
                "YW_2": 10,
                "YW_3": 10,
                "YC_CHIP_1": 5,
                "YC_CHIP_2": 4
            };
            console.log(`检查兑换状态 目前时间 ${currentTime.minutes}:${currentTime.seconds}`);
            const convertList = await this.checkExchangeStatus(rid);
            // const promises = [];
            const statusMap = {
                0: '积分不足',
                1: '可兑换',
                2: '无兑换次数',
                3: '刷新中'
            };
            for (const item of convertList) {
                if (item.status!== 1) {
                    this.excludedIndexes.add(item.index);
                }
            }
            for (const selectedIndexValue of this.selectedIndexValues) {
                console.log(`目前时间 ${currentTime.minutes}:${currentTime.seconds} 检查物品状态 ${selectedIndexValue} `);
                const itemStatus = convertList.find(item => item.index === selectedIndexValue)?.status;
                if (itemStatus === 1) {
                    const loopTimes = loopTimesMap[selectedIndexValue] || 1;
                    for (let i = 0; i < loopTimes; i++) {
                        console.log(`目前时间 ${currentTime.minutes}:${currentTime.seconds} 发送兑换请求(${selectedIndexValue}) `);
                        //await
                        this.executeWithDelayAndLimit(selectedIndexValue, rid);
                    }
                }else{
                    const statusDescription = statusMap[itemStatus] || '未知状态';
                    console.log(`目前时间 ${currentTime.minutes}:${currentTime.seconds} 物品:${selectedIndexValue},状态为${statusDescription}`);
                }
            }
            // console.log(`${currentTime.minutes}:${currentTime.seconds} 准备发送兑换请求`);

        } else if (this.autoExchangeEnabled) {
            this.autoExchangeCount++;
            if (this.autoExchangeCount % 60 === 0) {
                console.log(`自动兑换运行中 ${this.autoExchangeCount / 60}分钟 当前时间 ${currentTime.minutes}:${currentTime.seconds}`);
            }
        }
    }, 1000);

}
    executeWithDelayAndLimit(selectedIndexValue, rid) {
    //    setTimeout(() => {
        if (this.excludedIndexes.has(selectedIndexValue)) {
            console.log(`${selectedIndexValue} 已被标记为库存不足或不可兑换,不再发送请求`);
            return;
        }
        if (this.pauseAllRequests) {
            setTimeout(() => {
                this.executeWithDelayAndLimit(selectedIndexValue, rid);
            }, 50);
            return;
        }
        if (this.concurrentRequests >= this.maxConcurrentRequests) {
            setTimeout(() => {
                this.executeWithDelayAndLimit(selectedIndexValue, rid);
            }, 100); //如果达到最大并发数 等待100毫秒再重试发送
        } else {
            this.concurrentRequests++;
            fetch(CONFIG.API_ENDPOINTS.FISH_SHOP, {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                },
                body: `ctn=${utils.getCCN()}&rid=${rid}&index=${selectedIndexValue}`,
                credentials: "include"
            })
          .then(response => response.json())
          .then(data => {
                if (data.error === -1) {
                    console.log('操作频繁 等待500毫秒');
                    console.log(`${selectedIndexValue} 兑换失败`)
                    this.pauseAllRequests = true; // 标记暂停所有请求
                    setTimeout(() => {
                        this.concurrentRequests--;  //等待300毫秒再移除
                        this.pauseAllRequests = false; // 300 毫秒后恢复请求
                        //this.executeWithDelayAndLimit(selectedIndexValue, rid);
                    }, 300);
                } else if(data.error === 30002){
                    this.concurrentRequests--;
                    console.log(`${selectedIndexValue} 奖品库存不足`);
                    this.excludedIndexes.add(selectedIndexValue); // 标记该 index 为排除状态
                }
                else{
                    this.concurrentRequests--;
                    console.log(`${selectedIndexValue} 兑换成功`);
                }
            })
          .catch(error => {
                console.error('兑换请求失败:', error);
                this.concurrentRequests--;
            });
        }
    // }, 100);
}


    stopAutoExchange() {
        if (this.exchangeInterval) {
            clearInterval(this.exchangeInterval);
            this.exchangeInterval = null;
        }
    }

    getCurrentTime() {
        const now = new Date();
        return {
            minutes: now.getMinutes(),
            seconds: now.getSeconds()
        };
    }

    isInTimeRange() { //提前几秒开始检测兑换状态 免得误差
        const currentTime = this.getCurrentTime();
        return (currentTime.minutes === 59 && currentTime.seconds >= 57) ||
               (currentTime.minutes === 0 && currentTime.seconds <= 2);
    }

    setSelectedValues(values) {
        this.selectedIndexValues = values;
    }

    toggleRunning() {
        this.isRunning = !this.isRunning;
        return this.isRunning;
    }

    toggleAutoExchange() {
        this.autoExchangeEnabled = !this.autoExchangeEnabled;
        return this.autoExchangeEnabled;
    }
}

// UI管理器增强
class UIManager {
    constructor(fishingManager, storageManager,apiManager) {
        this.fishingManager = fishingManager;
        this.storageManager = storageManager;
        this.exchangeManager = new ExchangeManager(apiManager);
        this.createUI();
        this.createExchangeOverlay();
        this.loadSavedState();
    }

    createExchangeOverlay() {
        const overlay = document.createElement('div');
        overlay.id = 'exchange-overlay';

        GM_addStyle(`
        #exchange-overlay {
            position: fixed;
            top: 26%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(255, 255, 255, 0.9);
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
            display: none;
            z-index: 9999;
            cursor: move; /* 添加移动光标 */
        }
        .exchange-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
        }
        .exchange-title {
            font-weight: bold;
            font-size: 16px;
            margin: 0;
        }
        .exchange-close {
            background: none;
            border: none;
            font-size: 18px;
            cursor: pointer;
            color: #666;
            padding: 5px;
        }
        .exchange-close:hover {
            color: #333;
        }
        .exchange-button {
            background-color: red;
            color: white;
            margin: 5px;
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            border: none;
            border-radius: 5px;
        }
        .exchange-select {
            margin: 5px;
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ccc;
            border-radius: 5px;
            height: auto;
            overflow-y: auto;
            width: 300px;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
        }
    `);
        // 添加标题栏和关闭按钮
        const header = document.createElement('div');
        header.className = 'exchange-header';
        header.innerHTML = `
        <h3 class="exchange-title">商城兑换</h3>
        <button class="exchange-close">×</button>
    `;

        const controlExchangeButton = document.createElement('button');
        controlExchangeButton.textContent = '启动兑换 ✔';
        controlExchangeButton.className = 'exchange-button controlExchange';

        const autoExchangeButton = document.createElement('button');
        autoExchangeButton.textContent = '自动兑换 ✖';
        autoExchangeButton.className = 'exchange-button autoExchange';

        const selectMenu = document.createElement('select');
        selectMenu.className = 'exchange-select';
        selectMenu.setAttribute('multiple', 'multiple');

        const items = [
            { value: 'hahahahha', text: '请选择需要兑换的奖品' },
            { value: 'PROP_1', text: '3 级粉丝牌 初级水手 上限10' },
            { value: 'PROP_2', text: '3 级粉丝牌 精英士官 上限5' },
            { value: 'PROP_3', text: '6 级粉丝牌 心动卡 上限2' },
            { value: 'FREE_PROP_1', text: '10 陪伴印章' },
            { value: 'FREE_PROP_2', text: '30 陪伴印章' },
            { value: 'FREE_PROP_3', text: '50 陪伴印章' },
            { value: 'YC_TY_1', text: '0.1 鱼翅 上限50' },
            { value: 'YC_TY_2', text: '0.5 鱼翅 上限10' },
            { value: 'YC_TY_3', text: '1 鱼翅 上限5' },
            { value: 'YW_1', text: '100 鱼丸' },
            { value: 'YW_2', text: '200 鱼丸' },
            { value: 'YW_3', text: '500 鱼丸' },
            { value: 'YC_CHIP_1', text: '2 鱼翅碎片 上限5' },
            { value: 'YC_CHIP_2', text: '5 鱼翅碎片 上限4' }
        ];

        items.forEach(item => {
            const option = document.createElement('option');
            option.value = item.value;
            option.textContent = item.text;
            selectMenu.appendChild(option);
        });



        // 添加关闭按钮事件
        header.querySelector('.exchange-close').addEventListener('click', () => {
            overlay.style.display = 'none';
        });
        this.bindExchangeEvents(overlay, selectMenu, controlExchangeButton, autoExchangeButton);
        overlay.appendChild(header);

        overlay.appendChild(controlExchangeButton);
        overlay.appendChild(autoExchangeButton);
        overlay.appendChild(selectMenu);
        document.body.appendChild(overlay);
        //makeDraggable(overlay);

        this.makeOverlayDraggable(overlay);
    }

    makeOverlayDraggable(overlay) {
        let startX, startY, offsetX, offsetY;

        overlay.addEventListener('mousedown', (e) => {
            startX = e.pageX;
            startY = e.pageY;
            offsetX = overlay.offsetLeft;
            offsetY = overlay.offsetTop;

            const drag = (e) => {
                overlay.style.left = offsetX + e.pageX - startX + 'px';
                overlay.style.top = offsetY + e.pageY - startY + 'px';
            };

            const stopDrag = () => {
                document.removeEventListener('mousemove', drag);
                document.removeEventListener('mouseup', stopDrag);
            };

            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', stopDrag);
        });
    }

    bindExchangeEvents(overlay, selectMenu, controlExchangeButton, autoExchangeButton) {
        selectMenu.addEventListener('change', () => {
            const selectedValues = Array.from(selectMenu.selectedOptions).map(option => option.value);
            this.exchangeManager.setSelectedValues(selectedValues);

            console.log('目前选择的奖品:', selectedValues.map(value => {
                const option = selectMenu.querySelector(`option[value="${value}"]`);
                return option ? option.textContent : value;
            }));
        });

        controlExchangeButton.addEventListener('click', () => {
            const isRunning = this.exchangeManager.toggleRunning();
            if (isRunning) {
                controlExchangeButton.textContent = '停止兑换 ✖';
                controlExchangeButton.style.backgroundColor = 'green';
                this.exchangeManager.runExchangeLoop(erid, this.updateControlExchangeButton.bind(this));
            } else {
                controlExchangeButton.textContent = '启动兑换 ✔';
                controlExchangeButton.style.backgroundColor = 'red';
            }
        });

        autoExchangeButton.addEventListener('click', () => {
            const isAutoEnabled = this.exchangeManager.toggleAutoExchange();
            if (isAutoEnabled) {
                autoExchangeButton.textContent = '自动兑换中 ✔';
                autoExchangeButton.style.backgroundColor = 'green';
                this.exchangeManager.startAutoExchange(erid);
            } else {
                autoExchangeButton.textContent = '自动兑换 ✖';
                autoExchangeButton.style.backgroundColor = 'red';
                this.exchangeManager.stopAutoExchange();
            }
        });
    }

    updateControlExchangeButton(isRunning, countLeft) {
        const button = document.querySelector('.controlExchange');
        if (button) {
            if (isRunning) {
                button.textContent = `停止兑换 ✖ 剩余请求 ${countLeft}`;
                button.style.backgroundColor = 'green';
            } else {
                button.textContent = '启动兑换 ✔';
                button.style.backgroundColor = 'red';
            }
        }
    }

    createUI() {
        // 创建控制面板容器
        const controlPanel = document.createElement('div');
        Object.assign(controlPanel.style, {
            position: 'fixed',
            top: '50px',
            //right: '20px',
            zIndex: '9999',
            backgroundColor: '#f0f0f0',
            padding: '10px',
            border: '1px solid #ccc',
            borderRadius: '5px'
        });

        controlPanel.innerHTML = `
            <label>
                <input id="extool__autofish_start" type="checkbox" style="margin-top:5px;">
                无限自动钓鱼
            </label>
            <label>
                <input id="extool__eventautofish_start" type="checkbox" style="margin-top:10px;">
                大赛时间段自动钓鱼
            </label>
            <button id="extool__show_records" style="margin-left: 10px;">显示记录</button>
            <button id="extool__show_exchange" style="margin-left: 10px;">商城兑换</button>
            <div id="time-panel"></div>
            <div id="error-log"></div>
        `;

        document.body.appendChild(controlPanel);
        this.makeOverlayDraggable(controlPanel);

        // 创建记录查看窗口
        const recordsWindow = document.createElement('div');
        Object.assign(recordsWindow.style, {
            display: 'none',
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            zIndex: '10000',
            backgroundColor: 'white',
            padding: '20px',
            border: '1px solid #ccc',
            borderRadius: '5px',
            boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
            maxHeight: '80vh',
            width: '80vw',
            overflowY: 'auto'
        });

        recordsWindow.innerHTML = `
            <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
                <select id="extool__room_select" style="padding: 5px;">
                    <option value="">选择房间</option>
                    ${rids.map(rid => `<option value="${rid}">${rid}</option>`).join('')}
                </select>
                <div>
                    <button id="extool__clear_records">清除记录</button>
                    <button id="extool__close_records" style="margin-left: 10px;">关闭</button>
                </div>
            </div>
            <div id="extool__records_content"></div>
        `;

        document.body.appendChild(recordsWindow);

        this.bindEvents(recordsWindow);
    }

    bindEvents(recordsWindow) {
        // 绑定自动钓鱼开关事件
        const checkbox = document.getElementById("extool__autofish_start");
        const eventcheckbox = document.getElementById("extool__eventautofish_start");

        checkbox.addEventListener("click", async () => {
            const isStart = checkbox.checked;
            this.saveState(isStart);

            if (!isStart) {
                this.fishingManager.stopAllFishing();
                return;
            }else{ //如果checked 取消另一个checkbox勾选
                this.saveEventState(!isStart);
                eventcheckbox.checked = false;
            }

            console.log("【自动钓鱼】开始自动钓鱼");
            const initialized = await this.fishingManager.init();
            if (!initialized) {
                checkbox.checked = false;
                return;
            }
            //对每个房间开启计时器检查
            rids.forEach(rid => this.fishingManager.startAutoFishing(rid));
        });
        // 绑定自动钓鱼开关事件
        eventcheckbox.addEventListener("click", async () => {
            const isStart = eventcheckbox.checked;
            this.saveEventState(isStart);

            if (!isStart) {
                this.fishingManager.stopAllFishing();
                return;
            }else{ //如果checked 取消另一个checkbox勾选
                this.saveState(!isStart);
                checkbox.checked = false;
            }

            console.log("【大赛时间段自动钓鱼】开始自动钓鱼");
            const initialized = await this.fishingManager.init();
            if (!initialized) {
                eventcheckbox.checked = false;
                return;
            }

            rids.forEach(rid => this.fishingManager.startAutoFishing(rid));
        });

        // 绑定显示记录按钮事件
        document.getElementById("extool__show_records").addEventListener("click", () => {
            recordsWindow.style.display = 'block';
            this.updateRecordsDisplay();
        });

        // 绑定关闭按钮事件
        document.getElementById("extool__close_records").addEventListener("click", () => {
            recordsWindow.style.display = 'none';
        });

        // 绑定房间选择事件
        document.getElementById("extool__room_select").addEventListener("change", () => {
            this.updateRecordsDisplay();
        });

        // 绑定清除记录按钮事件
        document.getElementById("extool__clear_records").addEventListener("click", () => {
            const rid = document.getElementById("extool__room_select").value;
            this.storageManager.clearRecords(rid);
            this.storageManager.clearErrors(rid);
            this.updateRecordsDisplay();
        });

        document.getElementById("extool__show_exchange").addEventListener("click", () => {
            const exchangeOverlay = document.getElementById('exchange-overlay');
            exchangeOverlay.style.display = 'block';
        });
    }

    updateRecordsDisplay() {
        const rid = document.getElementById("extool__room_select").value;
        const records = this.storageManager.getFishRecords();
        const errors = this.storageManager.getErrorLogs();
        const content = document.getElementById("extool__records_content");

        let html = '';

        // 计算总奖励统计的函数
        const calculateTotalRewards = (recordsData) => {
            const total = {};
            Object.values(recordsData).flat().forEach(record => {
                record.awards.forEach(award => {
                    if (!total[award.awardName]) {
                        total[award.awardName] = 0;
                    }
                    total[award.awardName] += award.awardNumShow;
                });
            });
            return total;
        };

        // 生成奖励统计HTML的函数
        const generateRewardsHtml = (rewards) => `
    <div style="margin-bottom: 20px;">
        <h4 style="margin: 0 0 10px 0;">奖励统计:</h4>
        <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px; background-color: #f5f5f5;">
            <tr>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>心动卡:</strong>${rewards['心动卡'] || 0}
                </td>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>甜蜜卡丁车:</strong>${rewards['甜蜜卡丁车'] || 0}
                </td>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>陪伴印章:</strong>${rewards['陪伴印章'] || 0}
                </td>
            </tr>
            <tr>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>鱼翅:</strong>${rewards['鱼翅'] || 0}
                </td>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>鱼翅碎片:</strong>${rewards['鱼翅碎片'] || 0}
                </td>
                <td style="border: 1px solid #ddd; padding: 8px; width: 16.66%;">
                    <strong>鱼丸:</strong>${rewards['鱼丸'] || 0}
                </td>
            </tr>
        </table>
    </div>
    `;

        if (rid) {
            // 显示特定房间的记录
            html += '<h3>钓鱼记录</h3>';
            if (records[rid] && records[rid].length > 0) {
                const roomRewards = calculateTotalRewards({ [rid]: records[rid] });
                html += generateRewardsHtml(roomRewards);
                html += this.generateRecordsTable(records[rid]);
            } else {
                html += '<p>暂无钓鱼记录</p>';
            }

            html += '<h3>错误日志</h3>';
            if (errors[rid] && errors[rid].length > 0) {
                html += this.generateErrorsTable(errors[rid]);
            } else {
                html += '<p>暂无错误记录</p>';
            }
        } else {
            // 显示所有房间的统计信息
            const totalRewards = calculateTotalRewards(records);
            html += generateRewardsHtml(totalRewards);
            html += '<h3>房间统计</h3>';
            html += this.generateStatsTable(records, errors);
        }

        content.innerHTML = html;
    }

    generateRecordsTable(records) {
        return `
            <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
                <thead>
                    <tr>
                        <th style="border: 1px solid #ddd; padding: 8px;">时间</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">鱼类</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">重量</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">奖励</th>
                    </tr>
                </thead>
                <tbody>
                    ${records.map(record => `
                        <tr>
                            <td style="border: 1px solid #ddd; padding: 8px;">${new Date(record.timestamp).toLocaleString()}</td>
                            <td style="border: 1px solid #ddd; padding: 8px;">${record.fishName || '未知'}</td>
                            <td style="border: 1px solid #ddd; padding: 8px;">${record.weight}斤</td>
                            <td style="border: 1px solid #ddd; padding: 8px;">
                                ${record.awards.map(award =>
                                                    `${award.awardName}x${award.awardNumShow}`
                                                   ).join(', ') || '无'}
                            </td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
        `;
    }

    generateErrorsTable(errors) {
        return `
            <table style="width: 100%; border-collapse: collapse;">
                <thead>
                    <tr>
                        <th style="border: 1px solid #ddd; padding: 8px;">时间</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">错误信息</th>
                    </tr>
                </thead>
                <tbody>
                    ${errors.map(error => `
                        <tr>
                            <td style="border: 1px solid #ddd; padding: 8px;">${new Date(error.timestamp).toLocaleString()}</td><td style="border: 1px solid #ddd; padding: 8px;">${error.error}</td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
        `;
    }

    generateStatsTable(records, errors) {
        return `
            <table style="width: 100%; border-collapse: collapse;">
                <thead>
                    <tr>
                        <th style="border: 1px solid #ddd; padding: 8px;">房间号</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">钓鱼次数</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">总重量</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">错误次数</th>
                        <th style="border: 1px solid #ddd; padding: 8px;">最后活动</th>
                    </tr>
                </thead>
                <tbody>
                    ${rids.map(rid => {
            const roomRecords = records[rid] || [];
            const roomErrors = errors[rid] || [];
            const totalWeight = roomRecords.reduce((sum, record) => sum + parseFloat(record.weight), 0);
            const lastActivity = [...roomRecords, ...roomErrors]
            .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0]?.timestamp;

            return `
                            <tr>
                                <td style="border: 1px solid #ddd; padding: 8px;">${rid}</td>
                                <td style="border: 1px solid #ddd; padding: 8px;">${roomRecords.length}</td>
                                <td style="border: 1px solid #ddd; padding: 8px;">${totalWeight.toFixed(1)}斤</td>
                                <td style="border: 1px solid #ddd; padding: 8px;">${roomErrors.length}</td>
                                <td style="border: 1px solid #ddd; padding: 8px;">
                                    ${lastActivity ? new Date(lastActivity).toLocaleString() : '无记录'}
                                </td>
                            </tr>
                        `;
        }).join('')}
                </tbody>
            </table>
        `;
    }

    saveState(isAutoFish) {
        localStorage.setItem(CONFIG.STORAGE_KEYS.AUTO_FISH, JSON.stringify({ isAutoFish }));
    }
    saveEventState(isAutoFish) {
        localStorage.setItem(CONFIG.STORAGE_KEYS.EVENTAUTO_FISH, JSON.stringify({ isAutoFish }));
    }
    loadSavedState() {
        const saved = localStorage.getItem(CONFIG.STORAGE_KEYS.AUTO_FISH);
        if (saved) {
            const { isAutoFish } = JSON.parse(saved);
            if (isAutoFish) {
                document.getElementById("extool__autofish_start").click();
            }
        }
        const eventSaved = localStorage.getItem(CONFIG.STORAGE_KEYS.EVENTAUTO_FISH);
        if (eventSaved) {
            const { isAutoFish } = JSON.parse(eventSaved);
            if (isAutoFish) {
               document.getElementById("extool__eventautofish_start").click();
            }
        }
    }

}

// 初始化
(() => {
    const queueManager = new RequestQueueManager();
    const apiManager = new ApiManager(queueManager);
    const storageManager = new StorageManager();
    const fishingManager = new FishingManager(storageManager, apiManager);
    new UIManager(fishingManager, storageManager,apiManager);
})();
ahmeng97Author
§
Posted: 15.1.2025

哦对 奖励不知道会不会有问题 记录中钓鱼数量 斤 会不准确 少一些 多一些 不知道为啥 哈哈哈 反正图一乐 也比较难跟踪 我就没管了

§
Posted: 15.1.2025
Edited: 15.1.2025

哦对 奖励不知道会不会有问题 记录中钓鱼数量 斤 会不准确 少一些 多一些 不知道为啥 哈哈哈 反正图一乐 也比较难跟踪 我就没管了

感觉兑换速度没单个的那个脚本快T T,之前用单个脚本我调成50ms 尝试400次,大概剩余2 3秒开点,0点一波能抢空好几个来着T T
不知道新的脚本要改哪里。

Post reply

Sign in to post a reply.