Silent auto check-in for bbs.bt.sb (silent API call, no popups)
// ==UserScript==
// @name MaidCheckin Pro
// @name:zh-CN 女仆论坛自动签到
// @namespace http://tampermonkey.net/
// @version 7.2
// @description Silent auto check-in for bbs.bt.sb (silent API call, no popups)
// @description:zh-CN 自动完成 bbs.bt.sb 每日签到(静默 API 调用,无弹窗)
// @author mochu
// @match *://bbs.bt.sb/*
// @icon https://bbs.bt.sb/uploads/icon/icon-99224e973faa6f7d.png
// @grant none
// @license MIT
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ═══════════════════════════════════════════
// 配 置 区
// ═══════════════════════════════════════════
const CONFIG = {
apiUrl: 'https://bbs.bt.sb/api/check-in', // 签到 API 地址
pollMs: 3000, // 轮询间隔(毫秒)
debug: false // 开启后在 console 输出详细日志
};
// ═══════════════════════════════════════════
// 工 具 函 数
// ═══════════════════════════════════════════
const TAG = '[MaidCheckin Pro]';
const log = (...args) => console.log(TAG, ...args);
const dbg = (...args) => CONFIG.debug && console.log(TAG, '[DBG]', ...args);
function todayKey() {
const d = new Date();
return `nv_checkin_${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
function showToast(msg, color = '#10B981') {
const el = document.createElement('div');
Object.assign(el.style, {
position: 'fixed', top: '16px', right: '16px', zIndex: '99999',
padding: '12px 22px', borderRadius: '10px',
background: color, color: '#fff',
fontSize: '14px', fontWeight: '600',
boxShadow: '0 4px 16px rgba(0,0,0,.18)',
opacity: '0', transform: 'translateY(-12px)',
transition: 'all .35s ease', pointerEvents: 'none'
});
el.textContent = msg;
document.body.appendChild(el);
requestAnimationFrame(() => {
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
});
setTimeout(() => {
el.style.opacity = '0';
el.style.transform = 'translateY(-12px)';
setTimeout(() => el.remove(), 400);
}, 3500);
}
/** 检测用户是否已登录 */
function isLoggedIn() {
const loginBtn = document.querySelector('a[href="/login"]');
if (loginBtn && loginBtn.offsetParent !== null) {
return false;
}
const avatar = document.querySelector('[class*="avatar"], [class*="Avatar"], img[alt*="avatar"]');
if (avatar) return true;
return !loginBtn;
}
// ═══════════════════════════════════════════
// 核 心:直 接 调 API 签 到
// ═══════════════════════════════════════════
let isRunning = false;
async function tryCheckin() {
if (isRunning) return;
if (localStorage.getItem(todayKey())) {
dbg('今日已标记完成,跳过');
return;
}
if (!isLoggedIn()) {
dbg('未检测到登录状态,跳过');
return;
}
isRunning = true;
try {
log('📡 正在调用签到 API...');
const resp = await fetch(CONFIG.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // 携带 cookie(登录凭证)
body: JSON.stringify({ action: 'check-in' })
});
if (resp.ok) {
const data = await resp.json().catch(() => ({}));
const payload = data?.data || data; // 适配 apiSuccess 包装
localStorage.setItem(todayKey(), '1');
if (payload.alreadyCheckedIn) {
log('📌 今日已签到');
showToast('📌 今日已签到', '#3B82F6');
} else {
const reward = payload.points || payload.reward || '';
const streak = payload.currentStreak ? `连续${payload.currentStreak}天` : '';
log(`✅ 签到成功!+${reward}`, streak, payload);
showToast(`✅ 签到成功${reward ? `(+${reward})` : ''}${streak ? ` ${streak}` : ''}`, '#10B981');
}
} else if (resp.status === 401 || resp.status === 403) {
dbg('未登录或无权限,等待下次重试');
} else {
log('⚠️ 签到请求异常:', resp.status, resp.statusText);
}
} catch (err) {
log('❌ 签到请求失败:', err.message);
} finally {
isRunning = false;
}
}
// ═══════════════════════════════════════════
// SPA 路 由 监 听 + 轮 询
// ═══════════════════════════════════════════
function startWatcher() {
// 启动后延迟 2 秒首次尝试
setTimeout(tryCheckin, 2000);
// 持续轮询直到当日签到成功
const watcher = setInterval(() => {
if (localStorage.getItem(todayKey())) {
dbg('今日签到已完成,停止轮询');
clearInterval(watcher);
return;
}
tryCheckin();
}, CONFIG.pollMs);
// 监听 SPA 路由变化(Next.js pushState)
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
dbg('SPA 路由跳转:', lastUrl);
setTimeout(tryCheckin, 1500);
}
});
urlObserver.observe(document.body, { childList: true, subtree: true });
// 监听浏览器前进/后退
window.addEventListener('popstate', () => {
dbg('popstate 导航');
setTimeout(tryCheckin, 1500);
});
}
// ═══════════════════════════════════════════
// 入 口
// ═══════════════════════════════════════════
if (document.readyState === 'complete') {
startWatcher();
} else {
window.addEventListener('load', startWatcher);
}
})();