// ==UserScript== // @name 苦力怕论坛自动登录签到 // @namespace https://bbs.tampermonkey.net.cn/ // @version 1.0.0 // @description 自动登录苦力怕论坛并执行签到操作,支持企业微信和喵推送,智能静默通知 // @author ShixmSimon // @crontab * once * * * // @icon https://klpbbs.com/favicon.ico // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_log // @connect klpbbs.com // @connect qyapi.weixin.qq.com // @connect api.chuckfang.com // @connect * // @match https://klpbbs.com/* // @tag 自动签到 // ==/UserScript== /* ==UserConfig== Account: klp_username: title: 苦力怕论坛用户名 type: text default: "" klp_password: title: 苦力怕论坛密码 type: text password: true default: "" Notify: EnableDeduplication: title: 静默通知去重 description: 开启后,每种通知每天只发送一次;关闭后每次运行都会发送(根据下方推送条件) type: checkbox default: true Notice: title: 浏览器通知 default: 总是 values: [总是, 仅失败时, 首次签到时, 关闭] PushCondition: title: 推送条件 default: 关闭 values: [总是, 仅失败时, 首次签到时, 关闭] meow_id: title: meow用户ID description: 在 https://api.chuckfang.com/ 中使用的用户ID,填写后自动启用喵推送 type: text password: true default: "" wework_key: title: 企业微信机器人Key description: 群机器人的 webhook key,填写后自动启用企业微信推送 type: text password: true default: "" Debug: EnableDebugLog: title: 启用调试日志 description: 开启后输出详细日志到控制台(GM_log),关闭后仅输出关键信息 type: checkbox default: false ==/UserConfig== */ const MAX_RETRIES = 2; const RETRY_DELAY = 2000; // ==================== 日志辅助函数(支持调试开关) ==================== function getCurrentTimestamp() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } function isDebugEnabled() { return GM_getValue("Debug.EnableDebugLog", false); } function logWithMeta(message, component = "GM_log", force = false) { const debug = isDebugEnabled(); if (!debug && !force) return; const timestamp = getCurrentTimestamp(); const meta = { env: typeof GM_info !== 'undefined' && GM_info.scriptHandler ? GM_info.scriptHandler : "unknown", uuid: (typeof GM_info !== 'undefined' && GM_info.uuid) || "local", name: (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.name) || "苦力怕论坛自动登录签到", component: component }; const logMessage = `${timestamp} ${message}🔚 ${JSON.stringify(meta)}`; GM_log(logMessage); } // ==================== 每日通知去重(使用本地日期) ==================== function getLocalDateString() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function shouldSendNotificationToday(typeKey) { const dedupEnabled = GM_getValue("Notify.EnableDeduplication", true); if (!dedupEnabled) { logWithMeta(`去重开关已关闭,允许发送通知 (${typeKey})`, "NotifyFilter"); return true; } const today = getLocalDateString(); const storageKey = `notify_last_${typeKey}_date`; const lastDate = GM_getValue(storageKey, ''); if (lastDate === today) { logWithMeta(`通知类型 ${typeKey} 今日已发送过,跳过`, "NotifyFilter"); return false; } GM_setValue(storageKey, today); logWithMeta(`通知类型 ${typeKey} 今日首次发送`, "NotifyFilter"); return true; } // 检查当天是否已经发送过“签到成功”或“今日已签到”两种关键通知之一 function hasSentSuccessOrAlreadyToday() { const today = getLocalDateString(); const successKey = `notify_last_sign_success_date`; const alreadyKey = `notify_last_already_signed_date`; const lastSuccess = GM_getValue(successKey, ''); const lastAlready = GM_getValue(alreadyKey, ''); return lastSuccess === today || lastAlready === today; } // ==================== 旧配置迁移(一次性) ==================== function migrateOldConfig() { const migrationFlag = GM_getValue("_config_migrated_v2", false); if (migrationFlag) return; let migrated = false; const oldToNew = { "Config.klp_username": "Account.klp_username", "Config.klp_password": "Account.klp_password", "Config.Notice": "Notify.Notice", "Config.PushCondition": "Notify.PushCondition", "Config.meow_id": "Notify.meow_id", "Config.wework_key": "Notify.wework_key", "Config.EnableDeduplication": "Notify.EnableDeduplication" }; for (const [oldKey, newKey] of Object.entries(oldToNew)) { const oldVal = GM_getValue(oldKey, null); if (oldVal !== null && GM_getValue(newKey, null) === null) { GM_setValue(newKey, oldVal); logWithMeta(`已迁移 ${oldKey} -> ${newKey}`, "Migration"); migrated = true; } } if (migrated) { // 清理旧键 Object.keys(oldToNew).forEach(oldKey => GM_setValue(oldKey, undefined)); logWithMeta("已清理旧配置键", "Migration"); } GM_setValue("_config_migrated_v2", true); if (migrated) logWithMeta("旧配置迁移完成", "Migration"); } // ==================== 工具函数 ==================== function parseHTML(html) { const parser = new DOMParser(); return parser.parseFromString(html, 'text/html'); } // 可重试错误类型(网络错误、超时等),维护错误不可重试 function isRetryableError(error) { if (error.type === 'maintenance') return false; const message = error.message || String(error); return message.includes('网络') || message.includes('超时') || message.includes('fetch') || message.includes('请求失败'); } async function retryWrapper(fn, retries = MAX_RETRIES, delay = RETRY_DELAY) { let lastError; for (let i = 0; i < retries; i++) { try { const result = await fn(); if (i > 0) logWithMeta(`重试第 ${i} 次成功`, "Retry"); return result; } catch (error) { lastError = error; if (!isRetryableError(error)) { logWithMeta(`不可重试错误,直接抛出: ${error.message || error}`, "Retry"); throw error; } if (i === retries - 1) throw error; logWithMeta(`操作失败,${delay/1000}秒后重试 (${i+1}/${retries}): ${error.message || error}`, "Retry"); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } function fetchAPI(url, method, data = null, headers = {}) { return new Promise((resolve, reject) => { const defaultHeaders = { 'Referer': `https://klpbbs.com/`, 'User-Agent': navigator.userAgent }; if (method.toUpperCase() === 'POST' && data) { defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; } const finalHeaders = { ...defaultHeaders, ...headers }; GM_xmlhttpRequest({ method: method, url: url, data: data, headers: finalHeaders, onload: (res) => { if (res.status >= 200 && res.status < 300) { if (checkMaintenancePage(res.responseText)) { const maintenanceInfo = parseMaintenanceInfo(res.responseText); reject({ type: 'maintenance', message: '论坛正在维护中', info: maintenanceInfo, html: res.responseText }); } else { resolve({ text: res.responseText, finalUrl: res.finalUrl, status: res.status }); } } else { reject(new Error(`请求失败:${res.status}`)); } }, onerror: (err) => reject(new Error(`网络错误: ${err}`)), ontimeout: () => reject(new Error('请求超时')) }); }); } // 检测论坛维护 function checkMaintenancePage(html) { const maintenanceIndicators = [ /维护公告.*苦力怕论坛/i, /苦力怕论坛临时关闭维护/i, /维护升级进行中/i, /状态:.*临时关闭维护/i, /class="badge".*维护升级进行中/i, /class="pill".*临时关闭维护/i, /论坛将进行一段时间的长期维护升级/i, /系统维护升级工作/i ]; const isMaintenance = maintenanceIndicators.some(indicator => indicator.test(html)); if (isMaintenance) logWithMeta(`检测到维护页面`, "Maintenance"); return isMaintenance; } function parseMaintenanceInfo(html) { try { const doc = parseHTML(html); let title = '论坛维护中'; let startTime = '未知', endTime = '未知', reason = '系统升级与性能优化'; let contactInfo = []; const titleEl = doc.querySelector('h1, .title h1'); if (titleEl) title = titleEl.textContent.trim() || title; const times = html.match(/(\d{4}年\d{1,2}月\d{1,2}日)/g); if (times) { startTime = times[0]; if (times[1]) endTime = times[1]; } const reasonMatch = html.match(/为(.*?),论坛/g) || html.match(/由于(.*?),/g) || html.match(/原因[::](.*?)[。\n]/g); if (reasonMatch && reasonMatch[0]) { let extracted = reasonMatch[0].replace(/为|由于|原因[::]/, '').replace(/,论坛|,/, '').trim(); if (extracted) reason = extracted; } const emails = [...doc.querySelectorAll('a[href^="mailto:"]')].map(el => el.textContent.trim()).filter(e => e.includes('@')); if (emails.length) contactInfo = emails; else { const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g; const found = html.match(emailRegex); if (found) contactInfo = found; } return { isMaintenance: true, title, startTime, endTime, reason, contactInfo: contactInfo.slice(0,3) }; } catch (error) { logWithMeta(`维护信息解析异常: ${error.message}`, "Maintenance"); return { isMaintenance: true, title: '论坛维护中', startTime: '未知', endTime: '未知', reason: '系统升级与维护', contactInfo: [] }; } } // 获取 formhash function getFormhash(html) { const regexes = [/name="formhash" value="([a-f0-9]+)"/, /formhash=([a-f0-9]+)/, /k_misign-sign\.html\?formhash=([a-f0-9]+)/]; for (const regex of regexes) { const match = html.match(regex); if (match) return match[1]; } try { const doc = parseHTML(html); const input = doc.querySelector('input[name="formhash"]'); if (input) return input.value; } catch (e) {} return null; } // 检测论坛版本(PC/移动) async function detectVersion() { logWithMeta("开始检测论坛版本", "Version"); try { const res = await fetchAPI('https://klpbbs.com/', 'GET'); const html = res.text; const isMobile = /comiis_app_block|comiis_mh_|mobile=2/.test(html); const isDesktop = /id="toptb"|id="fjump_menu"|class="wp"/.test(html); let versionInfo; if (isMobile) { versionInfo = { type: 'mobile', loginUrl: 'https://klpbbs.com/member.php?mod=logging&action=login&mobile=2', signUrl: 'https://klpbbs.com/k_misign-sign.html?mobile=2', creditUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=base&mobile=2', creditLogUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=log&mobile=2', profileUrl: 'https://klpbbs.com/home.php?mod=space&do=profile&mobile=2' }; logWithMeta("检测到移动端版本", "Version"); } else { versionInfo = { type: 'desktop', loginUrl: 'https://klpbbs.com/member.php?mod=logging&action=login', signUrl: 'https://klpbbs.com/k_misign-sign.html', creditUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=base', creditLogUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=log', profileUrl: 'https://klpbbs.com/home.php?mod=space&do=profile' }; logWithMeta("检测到桌面端版本", "Version"); } return versionInfo; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`版本检测失败,使用默认桌面端: ${error.message}`, "Version"); return { type: 'desktop', loginUrl: 'https://klpbbs.com/member.php?mod=logging&action=login', signUrl: 'https://klpbbs.com/k_misign-sign.html', creditUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=base', creditLogUrl: 'https://klpbbs.com/home.php?mod=spacecp&ac=credit&op=log', profileUrl: 'https://klpbbs.com/home.php?mod=space&do=profile' }; } } // 登录状态检查 async function checkLoginStatus(versionInfo) { logWithMeta("开始检查登录状态", "Login"); try { const res = await fetchAPI(versionInfo.profileUrl, 'GET'); const logged = /退出|我的中心|个人中心|修改头像|个人资料/.test(res.text); logWithMeta(`登录状态: ${logged ? '已登录' : '未登录'}`, "Login"); return logged; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`登录状态检查异常: ${error.message}`, "Login"); return false; } } // 签到状态检查(增强鲁棒性) async function checkSignStatus(versionInfo) { logWithMeta("开始检查签到状态", "Sign"); try { const res = await fetchAPI(versionInfo.signUrl, 'GET'); const html = res.text; const doc = parseHTML(html); // 通用文本匹配 const notSigned = /您今天还没有签到|立即签到|class="signbtn"|id="signbtn"|qiandao.*?button/i.test(html) && !/今天已经签到|已签到|签到成功|您的签到排名/i.test(html); const signed = /您的签到排名|今日已签|签到成功|已签到|class="signed"|id="signed"|今天已经签到/i.test(html); if (signed && !notSigned) return true; if (notSigned && !signed) return false; // 进一步分析按钮状态 const signBtn = doc.querySelector('input[type="submit"][value*="签到"], button[value*="签到"], a[href*="sign"][onclick*="签到"], .sign-btn, #sign-btn, #signresult, .comiis_btn'); if (signBtn) { const btnText = (signBtn.textContent || signBtn.value || '').toLowerCase(); if (btnText.includes('已签到')) return true; if (btnText.includes('签到') && !btnText.includes('已签到')) return false; } // 默认未签到(保守) logWithMeta("签到状态不确定,默认未签到", "Sign"); return false; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`签到状态检查异常: ${error.message},默认未签到`, "Sign"); return false; } } // 登录操作 async function klpLogin(username, password, versionInfo) { logWithMeta(`尝试登录: ${username}`, "Login"); try { const loginPage = await fetchAPI(versionInfo.loginUrl, 'GET'); let formhash = getFormhash(loginPage.text); if (!formhash) throw new Error('获取登录令牌失败'); const params = new URLSearchParams(); params.append('formhash', formhash); params.append('username', username); params.append('password', password); let loginPostUrl; if (versionInfo.type === 'mobile') { const loginhash = loginPage.text.match(/loginhash=([a-zA-Z0-9]{4,})/)?.[1] || ''; params.append('referer', 'https://klpbbs.com/?mobile=2'); params.append('fastloginfield', 'username'); params.append('cookietime', '31104000'); params.append('questionid', '0'); params.append('answer', ''); params.append('submit', '登录'); loginPostUrl = `https://klpbbs.com/member.php?mod=logging&action=login&loginsubmit=yes&loginhash=${loginhash}&inajax=1`; } else { params.append('loginsubmit', 'true'); params.append('handlekey', 'login'); params.append('referer', 'https://klpbbs.com/'); loginPostUrl = 'https://klpbbs.com/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&handlekey=login&inajax=1'; } await fetchAPI(loginPostUrl, 'POST', params.toString()); const logged = await checkLoginStatus(versionInfo); if (!logged) throw new Error('登录后验证未通过'); logWithMeta("登录成功", "Login"); return true; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`登录失败: ${error.message}`, "Login"); throw new Error(`苦力怕论坛登录失败: ${error.message}`); } } // 签到操作 async function klpSignIn(versionInfo) { logWithMeta("尝试签到", "Sign"); try { const already = await checkSignStatus(versionInfo); if (already) { logWithMeta("今日已签到,无需重复", "Sign"); return 'already'; } const signPage = await fetchAPI(versionInfo.signUrl, 'GET'); let formhash = getFormhash(signPage.text); if (!formhash) { const match = signPage.text.match(/k_misign-sign\.html\?formhash=([a-f0-9]+)/); if (match) formhash = match[1]; else throw new Error('获取签到令牌失败'); } const signUrl = `https://klpbbs.com/plugin.php?id=k_misign:sign&operation=qiandao&formhash=${formhash}&format=empty&inajax=1`; const signRes = await fetchAPI(signUrl, 'GET'); await new Promise(resolve => setTimeout(resolve, 1000)); const finalStatus = await checkSignStatus(versionInfo); if (finalStatus) return 'success'; if (signRes.text.includes('签到成功') || signRes.text.includes('您的签到排名') || signRes.text.includes('window.location.reload')) return 'success'; if (signRes.text.includes('今天已经签到')) return 'already'; return 'fail'; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`签到异常: ${error.message}`, "Sign"); throw new Error(`苦力怕论坛签到失败: ${error.message}`); } } // 积分信息获取 async function klpGetCreditInfo(versionInfo) { logWithMeta("获取积分信息", "Credit"); try { const creditHtml = await fetchAPI(versionInfo.creditUrl, 'GET'); const creditAmount = parseCreditAmount(creditHtml.text, versionInfo.type); const creditLogHtml = await fetchAPI(versionInfo.creditLogUrl, 'GET'); const signInfo = parseCreditLogPage(creditLogHtml.text, versionInfo.type); logWithMeta(`积分: 铁粒 ${creditAmount}, 最后签到 ${signInfo.lastSignTime}, 奖励 ${signInfo.lastSignReward}`, "Credit"); return { creditAmount, lastSignTime: signInfo.lastSignTime, lastSignReward: signInfo.lastSignReward }; } catch (error) { if (error.type === 'maintenance') throw error; logWithMeta(`获取积分信息失败: ${error.message}`, "Credit"); throw new Error(`获取积分信息失败: ${error.message}`); } } function parseCreditAmount(html, type) { const doc = parseHTML(html); if (type === 'desktop') { const li = Array.from(doc.querySelectorAll('ul.creditl li')).find(l => l.textContent.includes('铁粒')); if (li) { const match = li.textContent.match(/铁粒:\s*(\d+)/); if (match) return match[1]; } } else { const texts = Array.from(doc.querySelectorAll('*')).map(el => el.textContent); for (const text of texts) { if (text.includes('铁粒')) { const match = text.match(/铁粒\s*[::]?\s*(\d+)/); if (match) return match[1]; } } } return '未知'; } function parseCreditLogPage(html, type) { const doc = parseHTML(html); let lastSignTime = '无记录', lastSignReward = '无记录'; if (type === 'desktop') { const rows = doc.querySelectorAll('table tbody tr'); for (let i = 1; i < rows.length; i++) { const cells = rows[i].querySelectorAll('td'); if (cells.length >= 4 && cells[0].textContent.trim() === '每日签到') { const rewardSpan = cells[1].querySelector('.xi1'); if (rewardSpan) lastSignReward = rewardSpan.textContent.trim(); lastSignTime = cells[3].textContent.trim(); break; } } } else { const rows = doc.querySelectorAll('tr'); for (const row of rows) { const cells = row.querySelectorAll('td'); if (cells.length >= 4 && cells[0].textContent.trim() === '每日签到') { const rewardText = cells[1].textContent.trim(); const match = rewardText.match(/铁粒\s*([+-]\d+)/); if (match) lastSignReward = match[1]; else { const span = cells[1].querySelector('.xi1'); if (span) lastSignReward = span.textContent.trim(); } lastSignTime = cells[3].textContent.trim(); break; } } if (lastSignTime === '无记录') { const items = doc.querySelectorAll('li.b_b'); for (const item of items) { const op = item.querySelector('span.f_d:not(.y)'); const time = item.querySelector('span.y'); const credit = item.querySelector('span.xi1'); if (op && time && credit && (op.textContent.includes('打卡签到') || op.textContent.includes('每日签到'))) { lastSignTime = time.textContent.trim(); lastSignReward = credit.textContent.trim(); break; } } } } return { lastSignTime, lastSignReward }; } // ==================== 推送函数(不含去重,由调用方控制) ==================== async function pushToWeWork(title, content) { const key = GM_getValue("Notify.wework_key", ""); if (!key) return false; const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${key}`; const payload = { msgtype: "text", text: { content: `${title}\n${content}`.trim() } }; return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(payload), timeout: 10000, onload: (res) => { try { const result = JSON.parse(res.responseText); if (result.errcode === 0) { logWithMeta(`企业微信推送成功: ${title}`, "WeWork"); resolve(true); } else { logWithMeta(`企业微信推送失败: ${result.errmsg}`, "WeWork"); resolve(false); } } catch (e) { logWithMeta(`企业微信响应解析错误: ${e}`, "WeWork"); resolve(false); } }, onerror: (err) => { logWithMeta(`企业微信推送错误: ${err}`, "WeWork"); resolve(false); }, ontimeout: () => { logWithMeta(`企业微信推送超时`, "WeWork"); resolve(false); } }); }); } async function pushToMeow(title, content, imgUrl = "https://klpbbs.com/favicon.ico") { const meowId = GM_getValue("Notify.meow_id", ""); if (!meowId) return false; const apiUrl = `https://api.chuckfang.com/${encodeURIComponent(meowId)}`; const payload = { title, msg: content, imgUrl }; return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'POST', url: apiUrl, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(payload), timeout: 10000, onload: (res) => { try { const result = JSON.parse(res.responseText); if (res.status >= 200 && res.status < 300 && result.status === 200) { logWithMeta(`喵推送成功: ${title}`, "Meow"); resolve(true); } else { logWithMeta(`喵推送失败: ${res.status}`, "Meow"); resolve(false); } } catch (e) { logWithMeta(`喵响应解析错误: ${e}`, "Meow"); resolve(false); } }, onerror: (err) => { logWithMeta(`喵推送错误: ${err}`, "Meow"); resolve(false); }, ontimeout: () => { logWithMeta(`喵推送超时`, "Meow"); resolve(false); } }); }); } // 发送通知(不进行任何去重,调用前需自行做去重判断) async function sendNotificationNoDedup(type, title, details = null, isFirstSignSuccess = false) { let notificationText = ''; if (details) { for (const [key, value] of Object.entries(details)) notificationText += `${key}: ${value}\n`; notificationText = notificationText.trim(); } const finalText = notificationText || title; const isError = (type === 'error'); const isFirstSuccess = (isFirstSignSuccess === true); const notice = GM_getValue('Notify.Notice', '总是'); const pushCondition = GM_getValue('Notify.PushCondition', '关闭'); let shouldShowNotice = false; if (notice === '总是') shouldShowNotice = true; else if (notice === '仅失败时' && isError) shouldShowNotice = true; else if (notice === '首次签到时' && isFirstSuccess) shouldShowNotice = true; else if (notice === '关闭') shouldShowNotice = false; if (shouldShowNotice) { GM_notification({ title: `${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'} ${title}`, text: finalText, timeout: 0, highlight: isError }); logWithMeta(`浏览器通知已发送: ${title}`, "Notification"); } let shouldPush = false; if (pushCondition === '总是') shouldPush = true; else if (pushCondition === '仅失败时' && isError) shouldPush = true; else if (pushCondition === '首次签到时' && isFirstSuccess) shouldPush = true; else if (pushCondition === '关闭') shouldPush = false; if (shouldPush) { logWithMeta(`满足推送条件,开始推送: ${title}`, "Push"); await Promise.allSettled([pushToMeow(title, finalText, "https://s41.ax1x.com/2026/01/08/pZwdp79.png"), pushToWeWork(title, finalText)]); } else { logWithMeta(`不满足推送条件,跳过推送: ${title}`, "Push"); } } // ==================== 主流程 ==================== async function runScript() { const startTime = Date.now(); logWithMeta("脚本开始运行", "Main", true); const username = GM_getValue("Account.klp_username", ''); const password = GM_getValue("Account.klp_password", ''); if (!username || !password) { logWithMeta("账号或密码未配置,发送配置缺失通知", "Main", true); await sendNotificationNoDedup('error', '苦力怕论坛 - 配置缺失', {提示: '请在脚本配置中填写用户名和密码'}, false); logWithMeta("脚本结束 (配置缺失)", "Main", true); return; } const results = { klp: { login: false, sign: false, creditInfo: null, version: 'unknown', isAlreadyLoggedIn: false, isAlreadySigned: false, errors: [] } }; let isFirstSignSuccess = false; try { const versionInfo = await retryWrapper(() => detectVersion()); results.klp.version = versionInfo.type; const isLoggedIn = await checkLoginStatus(versionInfo); results.klp.isAlreadyLoggedIn = isLoggedIn; if (isLoggedIn) { results.klp.login = true; logWithMeta("已保持登录状态", "Login"); } else { await retryWrapper(() => klpLogin(username, password, versionInfo)); results.klp.login = true; } const isSigned = await checkSignStatus(versionInfo); results.klp.isAlreadySigned = isSigned; if (isSigned) { results.klp.sign = 'already'; logWithMeta("今日已签到,跳过", "Sign"); } else { const signResult = await retryWrapper(() => klpSignIn(versionInfo)); results.klp.sign = signResult; if (signResult === 'success') { const finalCheck = await checkSignStatus(versionInfo); if (!finalCheck) results.klp.sign = 'uncertain'; else isFirstSignSuccess = true; logWithMeta(`签到结果: ${signResult}`, "Sign"); } else { logWithMeta(`签到结果: ${signResult}`, "Sign"); } } const creditInfo = await retryWrapper(() => klpGetCreditInfo(versionInfo)); results.klp.creditInfo = creditInfo; } catch (error) { if (error.type === 'maintenance') { const info = error.info; const details = { '状态': '论坛正在维护中', '开始时间': info.startTime, '预计结束': info.endTime, '维护原因': info.reason }; if (info.contactInfo.length) details['联系方式'] = info.contactInfo.join(', '); if (shouldSendNotificationToday('maintenance')) { await sendNotificationNoDedup('error', info.title, details, false); } const elapsed = ((Date.now() - startTime) / 1000).toFixed(3); logWithMeta(`脚本结束 (维护模式) 用时 ${elapsed} 秒`, "Main", true); return; } results.klp.errors.push(error.message); logWithMeta(`捕获异常: ${error.message}`, "Error"); } const klp = results.klp; let details = {}; if (klp.creditInfo) { if (klp.creditInfo.lastSignTime !== '无记录') details['📅 签到时间'] = klp.creditInfo.lastSignTime; if (klp.creditInfo.lastSignReward !== '无记录') details['🎁 签到奖励'] = klp.creditInfo.lastSignReward; details['💰 铁粒余额'] = klp.creditInfo.creditAmount; } // 通知发送(优化后逻辑:一旦当天已发送成功或已签到,则不再发送其他类型通知) if (klp.errors.length) { // 错误通知(例如登录失败、积分获取失败等) if (!hasSentSuccessOrAlreadyToday() && shouldSendNotificationToday('failure')) { await sendNotificationNoDedup('error', '苦力怕论坛签到错误', {错误信息: klp.errors.join('; ')}, false); } } else { if (klp.sign === 'success' && isFirstSignSuccess) { // 签到成功:仅在当天未发送过成功/已签到通知时发送 if (!hasSentSuccessOrAlreadyToday() && shouldSendNotificationToday('sign_success')) { await sendNotificationNoDedup('success', '苦力怕论坛 - 签到成功', details, true); } } else if (klp.isAlreadySigned || klp.sign === 'already') { // 今日已签到 if (!hasSentSuccessOrAlreadyToday() && shouldSendNotificationToday('already_signed')) { await sendNotificationNoDedup('info', '苦力怕论坛 - 今日已签到', details, false); } } else if (klp.sign === 'uncertain') { // 签到状态不确定 if (!hasSentSuccessOrAlreadyToday() && shouldSendNotificationToday('uncertain')) { await sendNotificationNoDedup('info', '苦力怕论坛 - 签到状态不确定', details, false); } } else if (klp.sign === 'fail') { // 签到失败 if (!hasSentSuccessOrAlreadyToday() && shouldSendNotificationToday('fail')) { await sendNotificationNoDedup('info', '苦力怕论坛 - 签到失败', details, false); } } } const elapsed = ((Date.now() - startTime) / 1000).toFixed(3); logWithMeta(`脚本执行完成,用时 ${elapsed} 秒`, "Main", true); } // 手动触发入口 function manualRun() { GM_log("手动触发签到流程"); runScript().catch(err => GM_log(`手动执行出错: ${err}`)); } // 注册菜单命令 GM_registerMenuCommand("运行苦力怕论坛签到", manualRun); // 自动执行 (async function() { 'use strict'; migrateOldConfig(); await runScript(); })();