1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/Bg6shY若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/Bg6shY若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d腾讯云:
https://curl.qcloud.com/wJpWmSfU若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=2019052.部署教程
3.代码如下
Config:klp_username:title: 苦力怕论坛用户名type: textdefault: ""klp_password:title: 苦力怕论坛密码type: textpassword: true==/UserConfig== */const MAX_RETRIES = 3;const RETRY_DELAY = 2000;function showNotification(type, title, details = null) {let notificationText = '';if (details) {for (const [key, value] of Object.entries(details)) {notificationText += `${key}: ${value}\n`;}// 移除最后一个换行符notificationText = notificationText.trim();}GM_notification({title: `${type === 'success' ? '' : type === 'error' ? '❌' : 'ℹ️'} ${title}`,text: notificationText,timeout: type === 'error' ? 0 : 0,highlight: type === 'error'});}async function retryWrapper(fn, retries = MAX_RETRIES, delay = RETRY_DELAY) {for (let i = 0; i < retries; i++) {try {return await fn();} catch (error) {if (i === retries - 1) throw error;await new Promise(resolve => setTimeout(resolve, delay));}}}function fetchAPI(url, method, data = null, headers = {}) {return new Promise((resolve, reject) => {const defaultHeaders = {'Content-Type': 'application/x-www-form-urlencoded','Referer': `https://klpbbs.com/`,'User-Agent': navigator.userAgent};const finalHeaders = {...defaultHeaders, ...headers};GM_xmlhttpRequest({method: method,url: url,data: data,headers: finalHeaders,onload: (res) => {if (res.status >= 200 && res.status < 300) {resolve({text: res.responseText,finalUrl: res.finalUrl,status: res.status});} else {reject(new Error(`请求失败:${res.status}`));}},onerror: (err) => reject(err)});});}function getFormhash(html) {const formhashSources = [/name="formhash" value="([a-f0-9]+)"/,/formhash=([a-f0-9]+)/,/k_misign-sign\.html\?formhash=([a-f0-9]+)/];for (const regex of formhashSources) {const match = html.match(regex);if (match) {return match[1];}}try {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');const formhashInput = doc.querySelector('input[name="formhash"]');if (formhashInput) {return formhashInput.value;}} catch (e) {// 忽略解析错误}return null;}async function detectVersion() {try {const indexUrl = `https://klpbbs.com/`;const response = await fetchAPI(indexUrl, 'GET');const isMobile = response.text.includes('comiis_app_block') ||response.text.includes('comiis_mh_') ||response.text.includes('mobile=2');const isDesktop = response.text.includes('id="toptb"') ||response.text.includes('id="fjump_menu"') ||response.text.includes('class="wp"');if (isMobile) {return {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`};} else if (isDesktop) {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`};}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`};} catch (error) {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) {try {const { profileUrl } = versionInfo;const profileRes = await fetchAPI(profileUrl, 'GET');const isLoggedIn = profileRes.text.includes('退出') ||profileRes.text.includes('我的中心') ||profileRes.text.includes('个人中心') ||profileRes.text.includes('修改头像') ||profileRes.text.includes('个人资料');return isLoggedIn;} catch (error) {return false;}}async function checkSignStatus(versionInfo) {try {const { signUrl } = versionInfo;const signPage = await fetchAPI(signUrl, 'GET');const html = signPage.text;// 针对手机版的专门检测if (versionInfo.type === 'mobile') {// 手机版未签到:有可点击的签到按钮const notSignedMobile = (html.includes('id="signresult"') &&html.includes('onclick="ajaxsign();"') &&html.includes('>签到<')) ||(html.includes('class="comiis_btn bg_c f_f"') &&html.includes('>签到<'));// 手机版已签到:按钮显示"已签到"且没有onclick事件const signedMobile = html.includes('>已签到<') &&(html.includes('class="comiis_btn bg_b f_c"') ||!html.includes('onclick="ajaxsign();"'));if (notSignedMobile) {return false;} else if (signedMobile) {return true;}}// 使用DOM解析进行更精确的检测const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');// 方法1: 检查签到按钮状态const signButton = doc.querySelector('input[type="submit"][value*="签到"], button[value*="签到"], a[href*="sign"][onclick*="签到"], .sign-btn, #sign-btn, #signresult, .comiis_btn');// 方法2: 检查已签到提示const signedText = doc.querySelector('.signed-text, .sign-success, .qiandao-success, .comiis_btn.bg_b');// 方法3: 检查按钮文本内容let buttonText = '';if (signButton) {buttonText = signButton.textContent || signButton.value || '';}// 逻辑判断优先级:明确迹象 > DOM元素 > 文本匹配if (signButton && !signButton.disabled &&(buttonText.includes('签到') && !buttonText.includes('已签到'))) {return false;}if (signedText || (signButton && buttonText.includes('已签到'))) {return true;}// 文本匹配(作为备用方案)const notSignedIndicators = [/您今天还没有签到/,/立即签到/,/class="signbtn"/,/id="signbtn"/,/qiandao.*?button/i];const signedIndicators = [/您的签到排名/,/今日已签/,/签到成功/,/已签到/,/class="signed"/,/id="signed"/,/今天已经签到/];let notSignedCount = notSignedIndicators.filter(indicator =>indicator.test(html)).length;let signedCount = signedIndicators.filter(indicator =>indicator.test(html)).length;if (notSignedCount > signedCount) {return false;} else if (signedCount > notSignedCount) {return true;}// 如果无法确定状态,保守地返回未签到GM_setValue('last_sign_page', html);return false;} catch (error) {// 出错时返回未签到return false;}}async function klpLogin(username, password, versionInfo) {try {const { loginUrl, type } = versionInfo;let formhash, loginhash, loginParams;const loginPage = await fetchAPI(loginUrl, 'GET');formhash = getFormhash(loginPage.text);if (!formhash) {throw new Error('获取登录令牌失败');}loginParams = new URLSearchParams();loginParams.append('formhash', formhash);loginParams.append('username', username);loginParams.append('password', password);if (type === 'mobile') {loginhash = loginPage.text.match(/loginhash=([a-zA-Z0-9]{4,})/)?.[1] || '';loginParams.append('referer', `https://klpbbs.com/?mobile=2`);loginParams.append('fastloginfield', 'username');loginParams.append('cookietime', '31104000');loginParams.append('questionid', '0');loginParams.append('answer', '');loginParams.append('submit', '登录');} else {loginParams.append('loginsubmit', 'true');loginParams.append('handlekey', 'login');loginParams.append('referer', `https://klpbbs.com/`);}let loginPostUrl;if (type === 'mobile') {loginPostUrl = `https://klpbbs.com/member.php?mod=logging&action=login&loginsubmit=yes&loginhash=${loginhash}&inajax=1`;} else {loginPostUrl = `https://klpbbs.com/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&handlekey=login&inajax=1`;}const loginRes = await fetchAPI(loginPostUrl,'POST',loginParams.toString());const isLoggedIn = await checkLoginStatus(versionInfo);if (!isLoggedIn) {if (loginRes.text.includes('密码错误')) throw new Error('登录失败:密码错误');if (loginRes.text.includes('登录受限')) throw new Error('登录失败:尝试次数过多');if (loginRes.text.includes('安全提问')) throw new Error('登录失败:需要安全提问');throw new Error('登录失败:未知错误');}return true;} catch (error) {throw new Error(`苦力怕论坛登录失败: ${error.message}`);}}async function klpSignIn(versionInfo) {try {const { signUrl, type } = versionInfo;const isAlreadySigned = await checkSignStatus(versionInfo);if (isAlreadySigned) {return 'already';}const signPage = await fetchAPI(signUrl, 'GET');let formhash = getFormhash(signPage.text);if (!formhash) {const formhashMatch = signPage.text.match(/k_misign-sign\.html\?formhash=([a-f0-9]+)/);if (formhashMatch) {formhash = formhashMatch[1];} else {throw new Error('获取签到令牌失败');}}let signPostUrl, signData;// 手机版和桌面版使用不同的签到参数if (type === 'mobile') {// 手机版使用ajax方式签到signPostUrl = `https://klpbbs.com/plugin.php?id=k_misign:sign&operation=qiandao&format=text&formhash=${formhash}&signsubmit=yes&inajax=1`;signData = `formhash=${formhash}&signsubmit=yes&inajax=1`;} else {// 桌面版signPostUrl = `${signUrl}&operation=qiandao&format=text`;signData = `formhash=${formhash}&signsubmit=yes`;}const signRes = await fetchAPI(signPostUrl,'POST',signData);// 增强的签到成功判断const successIndicators = [/签到成功/,/您的签到排名/,/已连续签到/,/已签到/,/window\.location\.reload/];const failIndicators = [/签到失败/,/您今天已经签到/,/formhash错误/,];const isSuccess = successIndicators.some(indicator => indicator.test(signRes.text));const isFail = failIndicators.some(indicator => indicator.test(signRes.text));// 无论响应如何,都重新获取签到页面来确认状态await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒让服务器处理const finalStatus = await checkSignStatus(versionInfo);if (finalStatus) {return 'success';} else if (isSuccess) {return 'success';} else if (isFail || isAlreadySigned) {return 'already';} else {return 'fail';}} catch (error) {throw new Error(`苦力怕论坛签到失败: ${error.message}`);}}async function klpGetCreditInfo(versionInfo) {try {const { creditUrl, creditLogUrl, type } = versionInfo;const creditHtml = await fetchAPI(creditUrl, 'GET');const creditAmount = parseCreditAmount(creditHtml.text, type);const creditLogHtml = await fetchAPI(creditLogUrl, 'GET');const signInfo = parseCreditLogPage(creditLogHtml.text, type);return {creditAmount: creditAmount,lastSignTime: signInfo.lastSignTime,lastSignReward: signInfo.lastSignReward};} catch (error) {throw new Error(`获取积分信息失败: ${error.message}`);}}function parseCreditLogPage(html, type) {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');let lastSignTime = '无记录';let 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) {const operation = cells[0].textContent.trim();if (operation === '每日签到') {const rewardSpan = cells[1].querySelector('.xi1');if (rewardSpan) {lastSignReward = rewardSpan.textContent.trim();}lastSignTime = cells[3].textContent.trim();break;}}}} else {const signRows = doc.querySelectorAll('tr');for (const row of signRows) {const cells = row.querySelectorAll('td');if (cells.length >= 4) {const operation = cells[0].textContent.trim();if (operation === '每日签到') {const rewardText = cells[1].textContent.trim();const rewardMatch = rewardText.match(/铁粒\s*([+-]\d+)/);if (rewardMatch) {lastSignReward = rewardMatch[1];} else {const rewardSpan = cells[1].querySelector('.xi1');if (rewardSpan) {lastSignReward = rewardSpan.textContent.trim();}}lastSignTime = cells[3].textContent.trim();break;}}}if (lastSignTime === '无记录') {const mobileItems = doc.querySelectorAll('li.b_b');for (const item of mobileItems) {const operationEl = item.querySelector('span.f_d:not(.y)');const timeEl = item.querySelector('span.y');const creditEl = item.querySelector('span.xi1');if (operationEl && timeEl && creditEl &&(operationEl.textContent.includes('打卡签到') || operationEl.textContent.includes('每日签到'))) {lastSignTime = timeEl.textContent.trim();lastSignReward = creditEl.textContent.trim();break;}}}}return {lastSignTime: lastSignTime,lastSignReward: lastSignReward};}function parseCreditAmount(html, type) {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');let creditAmount = '未知';if (type === 'desktop') {const creditLi = Array.from(doc.querySelectorAll('ul.creditl li')).find(li => li.textContent.includes('铁粒'));if (creditLi) {const match = creditLi.textContent.match(/铁粒:\s*(\d+)/);if (match) creditAmount = match[1];}} else {const creditText = Array.from(doc.querySelectorAll('*')).map(el => el.textContent).find(text => text.includes('铁粒') && /\d+/.test(text));if (creditText) {const match = creditText.match(/铁粒\s*[::]?\s*(\d+)/);if (match) creditAmount = match[1];}}return creditAmount;}(async function() {'use strict';const config = {klp_username: GM_getValue("Config.klp_username", ''),klp_password: GM_getValue("Config.klp_password", '')};const results = {klp: {login: false,sign: false,creditInfo: null,version: 'unknown',isAlreadyLoggedIn: false,isAlreadySigned: false,errors: []}};if (config.klp_username && config.klp_password) {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;} else {await retryWrapper(() => klpLogin(config.klp_username,config.klp_password,versionInfo));results.klp.login = true;}const isSigned = await checkSignStatus(versionInfo);results.klp.isAlreadySigned = isSigned;if (isSigned) {results.klp.sign = 'already';} 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';}}}const creditInfo = await retryWrapper(() => klpGetCreditInfo(versionInfo));results.klp.creditInfo = creditInfo;} catch (error) {results.klp.errors.push(error.message);}} else {results.klp.errors.push('苦力怕论坛账号密码配置不完整');}const klp = results.klp;// 错误信息单独通知if (klp.errors.length > 0) {showNotification('error', '苦力怕论坛签到错误', {错误信息: klp.errors.join('; ')});}// 正常状态通知(无论是否有错误都显示)let statusTitle = '苦力怕论坛签到';let statusType = 'info';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.isAlreadySigned || klp.sign === 'already') {statusTitle = '苦力怕论坛 - 今日已签到';statusType = 'info';} else if (klp.sign === 'success') {statusTitle = '苦力怕论坛 - 签到成功';statusType = 'success';} else if (klp.sign === 'uncertain') {statusTitle = '苦力怕论坛 - 签到状态不确定';statusType = 'info';} else if (klp.errors.length === 0) {statusTitle = '苦力怕论坛 - 签到失败';statusType = 'info';}// 只在没有错误或者有积分信息时显示状态通知if (klp.errors.length === 0 || klp.creditInfo) {showNotification(statusType, statusTitle, details);}})();
解析
该脚本用于自动登录并签到苦力怕论坛(klpbbs.com),同时获取当天签到奖励和账户积分信息。
GM_notification 实时弹出登录/签到/奖励状态提示。 | |
主要方法
| showNotification(type, title, details) | ||
| retryWrapper(fn, retries, delay) | ||
| fetchAPI(url, method, data, headers) | GM_xmlhttpRequest 发送 HTTP 请求,支持跨域访问。 | |
| getFormhash(html) | formhash | |
| detectVersion() | ||
| checkLoginStatus(versionInfo) | ||
| checkSignStatus(versionInfo) | ||
| klpLogin(username, password, versionInfo) | ||
| klpSignIn(versionInfo) | ||
| klpGetCreditInfo(versionInfo) | ||
| parseCreditLogPage(html, type) | ||
| parseCreditAmount(html, type) |
执行逻辑
启动脚本↓读取配置 (用户名+密码)↓detectVersion() → 判断手机版/电脑版↓checkLoginStatus() → 已登录? → 否 → klpLogin()↓checkSignStatus() → 已签到? → 否 → klpSignIn()↓klpGetCreditInfo() → 获取积分余额/签到时间/奖励↓showNotification() → 弹出结果通知
执行示例
签到成功
苦力怕论坛 - 签到成功
签到时间:2025-10-20
签到奖励:+20 铁粒
铁粒余额:5890
今日已签到
苦力怕论坛 - 今日已签到
签到时间:2025-10-19
签到奖励:+18 铁粒
铁粒余额:5870
登录失败
苦力怕论坛签到错误
错误信息:登录失败:密码错误
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论