// ==UserScript== // @name 聚影(jying.top)自动签到 // @namespace https://www.jying.top/ // @version 1.0.4 // @author OpenClaw Agent // @description 聚影网盘自动签到脚本,支持后台定时运行、账号密码本地保存、每日通知。配置一次,每天自动签到。 // @license MIT // @icon https://www.jying.top/favicon.ico // @supportURL https://github.com/openclaw/jying-checkin/issues // @homepageURL https://scriptcat.org/zh-CN/script-show-page/ // @match https://www.jying.top/* // @match https://jying.top/* // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @connect www.jying.top // @connect jying.top // @tag 签到 // @tag 自动化 // @tag 效率 // @crontab 5 16 * * * once // @cloudcat true // ==/UserScript== /** * 聚影自动签到脚本 - 公众版 v1.0.4 * * 更新日志: * v1.0.4 (2026-08-06): * - 双模式并存:@match + @crontab,页面模式弹配置面板,后台模式执行签到 * - 进入网站时未配置账号自动弹出配置面板 * - 定时任务北京时间 00:05 执行 * * v1.0.3 (2026-08-06): * - 添加 @tag 标签 * - 添加 @homepageURL * - 请求超时控制(15 秒) * * v1.0.2 (2026-08-06): * - 添加首次访问配置面板 * * v1.0.1 (2026-08-06): * - 符合 ScriptCat 后台脚本规范(直接返回 Promise) * * v1.0.0 (2026-08-06): * - 初始版本 */ // ═══════════════════════════════════════════════════════════ // 常量 // ═══════════════════════════════════════════════════════════ const SITE_URL = 'https://www.jying.top'; const API = { login: '/api/app/login/', stats: '/api/app/checkin/stats/', checkin:'/api/app/checkin/do/', csrf: '/api/csrf/' }; const STORAGE = { username: 'jying_username', password: 'jying_password_enc', token: 'jying_token', tokenExpiry:'jying_token_expiry', lastCheckin:'jying_last_checkin', configShown:'jying_config_shown' }; const RETRY = { maxRetries: 2, delayMs: 2000 }; // ═══════════════════════════════════════════════════════════ // 工具函数 // ═══════════════════════════════════════════════════════════ function b64enc(str) { try { return btoa(unescape(encodeURIComponent(str))); } catch (e) { return btoa(str); } } function b64dec(str) { try { return decodeURIComponent(escape(atob(str))); } catch (e) { return atob(str); } } function save(key, value) { try { GM_setValue(key, value); return true; } catch (e) { console.error('[聚影] 保存失败:', key, e); return false; } } function load(key) { try { return GM_getValue(key, null); } catch (e) { console.error('[聚影] 读取失败:', key, e); return null; } } function remove(key) { try { GM_deleteValue(key); } catch (e) { console.error('[聚影] 删除失败:', key, e); } } function delay(ms) { return new Promise(function(res) { setTimeout(res, ms); }); } function notify(title, text, timeout) { console.log('[聚影] ' + title + ': ' + text); try { GM_notification({ title: title, text: text, timeout: timeout || 5000 }); } catch (e) { console.error('[聚影] 通知失败:', e); } } function maskAccount(account) { if (!account) return '未配置'; if (account.indexOf('@') !== -1) { var parts = account.split('@'); return parts[0].substring(0, 2) + '***@' + parts[1]; } return account.substring(0, 2) + '***'; } async function retryAsync(fn, maxRetries, delayMs) { maxRetries = maxRetries || RETRY.maxRetries; delayMs = delayMs || RETRY.delayMs; var lastError; for (var i = 0; i < maxRetries; i++) { try { return await fn(); } catch (e) { lastError = e; if (i < maxRetries - 1) { console.warn('[聚影] 第 ' + (i + 1) + ' 次失败,' + delayMs + 'ms 后重试: ' + e.message); await delay(delayMs); } } } throw lastError; } // ═══════════════════════════════════════════════════════════ // 配置管理 // ═══════════════════════════════════════════════════════════ function getAccount() { var username = load(STORAGE.username); var passwordEnc = load(STORAGE.password); if (!username || !passwordEnc) return null; try { return { username: username, password: b64dec(passwordEnc) }; } catch (e) { console.error('[聚影] 密码解密失败:', e); return null; } } function saveAccount(username, password) { if (!username || !password) return false; save(STORAGE.username, username.trim()); save(STORAGE.password, b64enc(password.trim())); save(STORAGE.token, null); save(STORAGE.tokenExpiry, null); console.log('[聚影] 账号已保存: ' + maskAccount(username)); return true; } function isConfigured() { return getAccount() !== null; } function clearConfig() { remove(STORAGE.username); remove(STORAGE.password); remove(STORAGE.token); remove(STORAGE.tokenExpiry); remove(STORAGE.lastCheckin); remove(STORAGE.configShown); notify('配置已清除', '请重新配置账号密码'); console.log('[聚影] 所有配置已清除'); } // ═══════════════════════════════════════════════════════════ // API 请求 // ═══════════════════════════════════════════════════════════ function apiRequest(method, path, data, token) { return new Promise(function(resolve, reject) { var url = SITE_URL + path; var headers = { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }; if (token) headers['X-App-User-Token'] = token; var options = { method: method, url: url, headers: headers, timeout: 15000, onload: function(resp) { console.log('[聚影] ' + method + ' ' + path + ' → ' + resp.status); if (resp.status >= 200 && resp.status < 300) { try { resolve(JSON.parse(resp.responseText)); } catch (e) { reject(new Error('响应解析失败')); } } else { try { var json = JSON.parse(resp.responseText); reject(new Error(json.message || json.msg || 'HTTP ' + resp.status)); } catch (e2) { reject(new Error('HTTP ' + resp.status)); } } }, onerror: function(err) { reject(new Error('网络请求失败')); }, ontimeout: function() { reject(new Error('请求超时')); } }; if (data) options.data = JSON.stringify(data); try { options.withCredentials = true; } catch (e) {} console.log('[聚影] 请求: ' + method + ' ' + url); GM_xmlhttpRequest(options); }); } async function getCSRF() { try { await apiRequest('GET', API.csrf); } catch (e) { console.warn('[聚影] CSRF 获取失败(可忽略):', e.message); } return true; } async function login(username, password) { await getCSRF(); var result = await apiRequest('POST', API.login, { username: username, password: password }); if (result.status === 'success' && result.token) { save(STORAGE.token, result.token); save(STORAGE.tokenExpiry, Date.now() + 7 * 24 * 60 * 60 * 1000); console.log('[聚影] 登录成功,token 已保存'); return result.token; } throw new Error(result.message || result.msg || '登录失败'); } async function getToken() { var token = load(STORAGE.token); var expiry = load(STORAGE.tokenExpiry); if (token && expiry && Date.now() < expiry) { console.log('[聚影] 使用缓存 token'); return token; } console.log('[聚影] Token 过期,重新登录'); var account = getAccount(); if (!account) throw new Error('未配置账号密码'); return await login(account.username, account.password); } async function getStats(token) { var result = await apiRequest('GET', API.stats, null, token); if (result.status === 'success') { return { totalDays: result.my_total_days || 0, checkedToday:result.checked_today || false, rewardPoints:result.reward_points || 0 }; } throw new Error(result.message || '获取状态失败'); } async function doCheckin(token) { try { var result = await apiRequest('POST', API.checkin, null, token); if (result.status === 'success') { return { success: true, message: result.message || '签到成功', points: result.points || 5 }; } return { success: false, message: result.message || result.msg || '签到失败' }; } catch (e) { if (e.message.indexOf('已签') !== -1) { return { success: false, already: true, message: e.message }; } throw e; } } // ═══════════════════════════════════════════════════════════ // 签到主逻辑 // ═══════════════════════════════════════════════════════════ async function doCheckinTask() { console.log('[聚影] 开始执行签到任务'); if (!isConfigured()) { notify('请配置账号', '点击脚本菜单"配置账号"输入聚影账号密码'); throw new Error('未配置账号密码'); } var account = getAccount(); console.log('[聚影] 当前账号: ' + maskAccount(account.username)); try { var token = await retryAsync(getToken); var stats = await retryAsync(function() { return getStats(token); }); console.log('[聚影] 累计 ' + stats.totalDays + ' 天,今日已签: ' + stats.checkedToday); if (stats.checkedToday) { notify('今日已签到', '累计 ' + stats.totalDays + ' 天'); save(STORAGE.lastCheckin, new Date().toISOString()); return; } var result = await retryAsync(function() { return doCheckin(token); }); if (result.success) { notify('签到成功', '获得 ' + result.points + ' 积分,累计 ' + (stats.totalDays + 1) + ' 天'); save(STORAGE.lastCheckin, new Date().toISOString()); } else if (result.already) { notify('今日已签到', '累计 ' + stats.totalDays + ' 天'); } else { notify('签到失败', result.message); } } catch (e) { console.error('[聚影] 签到异常:', e); if (e.message.indexOf('登录') !== -1 || e.message.indexOf('密码') !== -1 || e.message.indexOf('未配置') !== -1) { save(STORAGE.token, null); save(STORAGE.tokenExpiry, null); } notify('签到失败', e.message); throw e; } } // ═══════════════════════════════════════════════════════════ // 配置面板 UI(仅页面模式可用) // ═══════════════════════════════════════════════════════════ function createConfigPanel() { if (typeof document === 'undefined') { console.warn('[聚影] 后台模式无法创建配置面板'); return; } // 注入样式(只注入一次) if (!document.getElementById('jying-config-style')) { var style = document.createElement('style'); style.id = 'jying-config-style'; style.textContent = [ '.jying-config-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:999999;display:flex;align-items:center;justify-content:center}', '.jying-config-panel{background:#fff;border-radius:12px;padding:24px;box-shadow:0 4px 20px rgba(0,0,0,.3);max-width:400px;width:90%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}', '.jying-config-title{font-size:20px;font-weight:700;margin-bottom:16px;color:#333;display:flex;align-items:center;gap:8px}', '.jying-config-title::before{content:"🎉";font-size:24px}', '.jying-config-desc{color:#666;font-size:14px;margin-bottom:20px;line-height:1.5}', '.jying-config-field{margin-bottom:16px}', '.jying-config-label{display:block;font-size:14px;font-weight:500;color:#333;margin-bottom:6px}', '.jying-config-input{width:100%;padding:10px 12px;border:1px solid #ddd;border-radius:6px;font-size:14px;box-sizing:border-box;transition:border-color .2s}', '.jying-config-input:focus{outline:none;border-color:#4CAF50}', '.jying-config-buttons{display:flex;gap:12px;margin-top:20px}', '.jying-config-btn{flex:1;padding:10px 16px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}', '.jying-config-btn-primary{background:#4CAF50;color:#fff}', '.jying-config-btn-primary:hover{background:#45a049}', '.jying-config-btn-secondary{background:#f5f5f5;color:#666}', '.jying-config-btn-secondary:hover{background:#e0e0e0}', '.jying-config-footer{margin-top:16px;padding-top:16px;border-top:1px solid #eee;color:#999;font-size:12px;text-align:center}' ].join(''); document.head.appendChild(style); } // 创建面板 var overlay = document.createElement('div'); overlay.className = 'jying-config-overlay'; overlay.innerHTML = [ '
' ].join(''); document.body.appendChild(overlay); var usernameInput = overlay.querySelector('#jying-username'); var passwordInput = overlay.querySelector('#jying-password'); var cancelBtn = overlay.querySelector('#jying-cancel'); var saveBtn = overlay.querySelector('#jying-save'); setTimeout(function() { usernameInput.focus(); }, 100); cancelBtn.addEventListener('click', function() { overlay.remove(); save(STORAGE.configShown, true); }); saveBtn.addEventListener('click', function() { var username = usernameInput.value.trim(); var password = passwordInput.value.trim(); if (!username || !password) { notify('配置失败', '账号和密码不能为空'); usernameInput.focus(); return; } if (saveAccount(username, password)) { overlay.remove(); save(STORAGE.configShown, true); notify('配置成功', '账号: ' + maskAccount(username) + ',每天 00:05 自动签到'); doCheckinTask(); } else { notify('配置失败', '请检查输入是否正确'); } }); passwordInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') saveBtn.click(); }); var escHandler = function(e) { if (e.key === 'Escape') { overlay.remove(); save(STORAGE.configShown, true); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); overlay.addEventListener('click', function(e) { if (e.target === overlay) { overlay.remove(); save(STORAGE.configShown, true); } }); } function showConfigPanelIfNeed() { if (typeof document === 'undefined') return false; if (isConfigured()) { console.log('[聚影] 已配置账号,不显示配置面板'); return false; } if (load(STORAGE.configShown)) { console.log('[聚影] 本次会话已显示过配置提示'); return false; } console.log('[聚影] 显示配置面板'); createConfigPanel(); return true; } function openConfigPanel() { if (typeof document === 'undefined') { alert('配置面板仅在页面模式下可用。请访问 https://www.jying.top 配置账号。'); return; } createConfigPanel(); } // ═══════════════════════════════════════════════════════════ // 脚本菜单 // ═══════════════════════════════════════════════════════════ function registerMenus() { try { GM_registerMenuCommand('\u23FA 立即签到', doCheckinTask); GM_registerMenuCommand('\u2699\uFE0F 配置账号', openConfigPanel); GM_registerMenuCommand('\uD83D\uDDD1\uFE0F 清除配置', clearConfig); if (isConfigured()) { var account = load(STORAGE.username); GM_registerMenuCommand('\uD83D\uDCCB 当前账号: ' + maskAccount(account), function() { alert('\u5F53\u524D\u914D\u7F6E\u8D26\u53F7: ' + maskAccount(account) + '\n\n\u70B9\u51FB"\u914D\u7F6E\u8D26\u53F7"\u53EF\u4FEE\u6539'); }); } } catch (e) { console.error('[聚影] 菜单注册失败:', e); } } // ═══════════════════════════════════════════════════════════ // 入口:双模式判断 // ═══════════════════════════════════════════════════════════ var hasWindow = typeof window !== 'undefined'; var hasDocument = typeof document !== 'undefined'; var isBackground = !hasWindow || !hasDocument; if (!isBackground) { console.log('[聚影] 页面加载模式'); registerMenus(); setTimeout(showConfigPanelIfNeed, 1000); } // ScriptCat 规范:返回 Promise return new Promise(function(resolve, reject) { if (isBackground) { console.log('[聚影] 后台定时任务模式'); doCheckinTask() .then(function() { resolve('签到完成'); }) .catch(function(err) { console.error('[聚影] 执行失败:', err); reject(err); }); } else { resolve('页面加载模式,菜单已注册'); } });