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=201905
2.部署教程
3.代码如下
Config:
klp_username:
title: 苦力怕论坛用户名
type: text
default: ""
klp_password:
title: 苦力怕论坛密码
type: text
password: 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
登录失败
苦力怕论坛签到错误
错误信息:登录失败:密码错误
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论