// ==UserScript== // @name 鱼C论坛自动签到 // @namespace https://scriptcat.org/ // @version 2.1 // @description 脚本猫后台定时签到,基于页面签到链接 // @author Fiallsnu // @icon https://fishc.com.cn/favicon.ico // @crontab * 11-23 once * * // @connect fishc.com.cn // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // @grant GM_log // @license MIT // ==/UserScript== return new Promise((resolve, reject) => { // ==================== 配置 ==================== const SIGN_PAGE = 'https://fishc.com.cn/plugin.php?id=k_misign:sign'; const BASE_URL = 'https://fishc.com.cn'; const MAX_RETRY = 5; const TODAY = new Date().toISOString().slice(0, 10); let retryCount = 0; // ==================== 防重复签到 ==================== if (GM_getValue('fishc_checkin', '') === TODAY) { GM_log('[鱼C] 今日已签到,跳过'); GM_notification({ title: '鱼C论坛:今日已签到', text: '今天已经签到过了,无需重复操作', timeout: 5000 }); resolve('今日已签到'); return; } // ==================== 主流程 ==================== function performCheckin() { GM_log('[鱼C] 开始签到流程...'); GM_xmlhttpRequest({ method: 'GET', url: SIGN_PAGE, headers: { 'Accept': 'text/html,application/xhtml+xml,*/*', 'Accept-Language': 'zh-CN,zh;q=0.9' }, timeout: 15000, anonymous: false, onload: function (res) { if (res.status !== 200) { handleError('签到页请求失败,状态码:' + res.status); return; } var html = res.responseText || ''; GM_log('[鱼C] 签到页获取成功,长度:' + html.length); // ---- 检测未登录 ---- if (isNotLoggedIn(html)) { GM_notification({ title: '鱼C论坛:未登录', text: '请先登录鱼C论坛', timeout: 10000, onclick: function () { GM_openInTab(BASE_URL + '/member.php?mod=logging&action=login'); } }); reject('未登录'); return; } // ---- 检测已签到 ---- if (isAlreadySigned(html)) { GM_log('[鱼C] 今日已签到'); GM_setValue('fishc_checkin', TODAY); GM_notification({ title: '鱼C论坛:今日已签到', text: '今天已经签到过了', timeout: 5000 }); resolve('今日已签到'); return; } // ---- 提取签到链接 ---- var signUrl = extractSignUrl(html); if (signUrl) { GM_log('[鱼C] 找到签到链接:' + signUrl); executeSign(signUrl); } else { GM_log('[鱼C] 未找到签到链接,尝试备用方案...'); GM_log('[鱼C] 页面片段:' + html.substring(0, 500)); fallbackSign(html); } }, onerror: function () { handleError('访问签到页面失败(网络错误)'); }, ontimeout: function () { handleError('访问签到页面超时'); } }); } // ==================== 未登录检测 ==================== function isNotLoggedIn(html) { if (html.length < 500 && html.includes('登录')) { return true; } if (html.includes('需要先登录') || html.includes('您需要登录')) { return true; } return false; } // ==================== 已签到检测 ==================== function isAlreadySigned(html) { return /您的签到排名|已签到|今日已签|签到已得|已经签到|已连续签到/.test(html); } // ==================== 提取签到链接 ==================== function extractSignUrl(html) { var m1 = html.match(/]+id="JD_sign"[^>]+href="([^"]+)"/); if (m1) return resolveUrl(m1[1]); var m2 = html.match(/href="([^"]*k_misign[^"]*qiandao[^"]*)"/); if (m2) return resolveUrl(m2[1]); var m3 = html.match(/onclick="[^"]*window\.location=['"]([^'"]+)['"]/); if (m3 && m3[1].includes('sign')) return resolveUrl(m3[1]); var m4 = html.match(/href="([^"]*operation=qiandao[^"]*)"/); if (m4) return resolveUrl(m4[1]); var m5 = html.match(/href="([^"]*(?:sign|checkin)[^"]*(?:token|hash|key)[^"]*)"/i); if (m5) return resolveUrl(m5[1]); var m6 = html.match(/formhash=([a-f0-9]{8})/); if (m6) { return BASE_URL + '/plugin.php?id=k_misign:sign' + '&operation=qiandao' + '&formhash=' + m6[1]; } return null; } // ==================== 处理相对URL ==================== function resolveUrl(url) { if (!url) return null; if (url.startsWith('http')) return url; if (url.startsWith('//')) return 'https:' + url; if (url.startsWith('/')) return BASE_URL + url; return BASE_URL + '/' + url; } // ==================== 执行签到 ==================== function executeSign(signUrl) { GM_log('[鱼C] 正在签到:' + signUrl); GM_xmlhttpRequest({ method: 'GET', url: signUrl, headers: { 'Referer': SIGN_PAGE, 'Accept': 'text/html,application/xhtml+xml,*/*' }, timeout: 15000, anonymous: false, onload: function (res) { var text = res.responseText || ''; GM_log('[鱼C] 签到响应码:' + res.status); GM_log('[鱼C] 签到响应前300字:' + text.substring(0, 300)); if (isSignSuccess(text)) { GM_setValue('fishc_checkin', TODAY); var reward = extractReward(text); GM_notification({ title: '鱼C论坛:签到成功', text: reward, timeout: 5000 }); GM_log('[鱼C] ★ 签到成功:' + reward); resolve('签到成功'); } else if (isAlreadySigned(text)) { GM_setValue('fishc_checkin', TODAY); GM_notification({ title: '鱼C论坛:今日已签到', text: '今天已经签到过了', timeout: 5000 }); resolve('今日已签到'); } else if (text.includes('需要先登录') || text.includes('未登录')) { GM_notification({ title: '鱼C论坛:登录已失效', text: '请重新登录鱼C论坛', timeout: 8000, onclick: function () { GM_openInTab(BASE_URL); } }); reject('登录失效'); } else { GM_log('[鱼C] 未知签到结果'); handleError('签到返回未知内容'); } }, onerror: function () { handleError('签到请求网络错误'); }, ontimeout: function () { handleError('签到请求超时'); } }); } // ==================== 备用签到方案 ==================== function fallbackSign(html) { if (/签到成功|操作成功|已签到/.test(html)) { GM_setValue('fishc_checkin', TODAY); GM_notification({ title: '鱼C论坛:签到成功', text: '签到完成', timeout: 5000 }); resolve('签到成功'); return; } handleError('无法提取签到链接'); } // ==================== 签到成功判断 ==================== function isSignSuccess(text) { return /签到成功|签到已得|今日已签|恭喜|操作成功|已连续签到/.test(text); } // ==================== 提取奖励信息 ==================== function extractReward(text) { try { var json = JSON.parse(text); if (json.msg) return json.msg; } catch (e) {} var m; m = text.match(/签到已得[^\d]*(\d+)/); if (m) return '签到已得 ' + m[1] + ' 鱼币'; m = text.match(/(\d+)\s*个?鱼币/); if (m) return '获得 ' + m[1] + ' 鱼币'; m = text.match(/连续签到\s*(\d+)/); if (m) return '连续签到 ' + m[1] + ' 天'; return '签到成功'; } // ==================== 错误处理 ==================== function handleError(msg) { retryCount++; GM_log('[鱼C] 错误:' + msg + ' | 重试 ' + retryCount + '/' + MAX_RETRY); if (retryCount < MAX_RETRY) { var delay = 5000 + Math.random() * 5000; setTimeout(performCheckin, delay); } else { GM_notification({ title: '鱼C论坛:签到失败', text: '已重试 ' + MAX_RETRY + ' 次,原因:' + msg, timeout: 10000, onclick: function () { GM_openInTab(SIGN_PAGE); } }); reject('签到失败:' + msg); } } // ==================== 启动 ==================== performCheckin(); });