// ==UserScript== // @name 聚影(jying.top)自动签到 // @namespace https://www.jying.top/ // @version 1.0.0 // @author OpenClaw Agent // @description 聚影网盘自动签到脚本,支持后台定时运行、账号密码保存、签到通知。无需手动登录,配置一次即可自动运行。 // @license MIT // @icon https://www.jying.top/favicon.ico // @homepageURL https://scriptcat.org/zh-CN/script-show-page/ // @supportURL https://github.com/openclaw/jying-checkin/issues // @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 // @grant GM_unregisterMenuCommand // @grant GM_openInTab // @grant GM_cookie // @connect www.jying.top // @connect jying.top // @crontab 0 5 0 * * * // @cloudcat true // @run-at document-end // ==/UserScript== /** * 聚影自动签到脚本 - 公众版 * * 功能特点: * - ✅ 后台定时运行(每天00:05自动签到) * - ✅ 账号密码本地加密保存(首次运行需配置) * - ✅ 自动检测今日是否已签到 * - ✅ 签到成功/失败浏览器通知 * - ✅ 支持手动触发签到(脚本菜单) * - ✅ 支持云端运行(ScriptCat CloudCat) * * 使用方法: * 1. 安装 ScriptCat 浏览器扩展 * 2. 安装本脚本 * 3. 首次运行会弹出配置面板,输入聚影账号密码 * 4. 之后每天自动签到,无需手动操作 * * 隐私声明: * - 账号密码仅保存在浏览器本地(ScriptCat Storage) * - 不向任何第三方服务器发送数据 * - 仅访问 jying.top 官方 API */ (function() { 'use strict'; // ═══════════════════════════════════════════════════════════ // 配置常量 // ═══════════════════════════════════════════════════════════ 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', // Base64 加密存储 token: 'jying_token', tokenExpiry: 'jying_token_expiry', lastCheckin: 'jying_last_checkin' }; // 通知配置 const NOTIFY = { success: { title: '✅ 聚影签到成功', icon: '🎉' }, already: { title: '✓ 今日已签到', icon: '✓' }, error: { title: '❌ 聚影签到失败', icon: '❌' }, config: { title: '⚙️ 请配置聚影账号', icon: '⚙️' } }; // ═══════════════════════════════════════════════════════════ // 工具函数 // ═══════════════════════════════════════════════════════════ /** * Base64 编码(简单加密,防止明文存储) */ function b64enc(str) { try { return btoa(unescape(encodeURIComponent(str))); } catch (e) { return btoa(str); } } /** * Base64 解码 */ 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 notify(type, message, details = '') { const config = NOTIFY[type] || NOTIFY.error; const text = details ? `${message}\n${details}` : message; console.log(`[聚影签到] ${config.title}: ${message}`); try { GM_notification({ title: config.title, text: text, icon: config.icon, timeout: 5000 }); } catch (e) { console.error('[聚影签到] 通知发送失败:', e); } } /** * 脱敏邮箱/账号 */ function maskAccount(account) { if (!account) return '未配置'; if (account.includes('@')) { const [local, domain] = account.split('@'); const masked = local.substring(0, 2) + '***@' + domain; return masked; } return account.substring(0, 2) + '***'; } // ═══════════════════════════════════════════════════════════ // 配置管理 // ═══════════════════════════════════════════════════════════ /** * 获取已保存的账号 */ function getAccount() { const username = load(STORAGE.username); const passwordEnc = load(STORAGE.password); if (!username || !passwordEnc) { return null; } try { const password = b64dec(passwordEnc); return { username, password }; } catch (e) { console.error('[聚影签到] 密码解密失败:', e); return null; } } /** * 保存账号密码 */ function saveAccount(username, password) { if (!username || !password) { console.error('[聚影签到] 账号密码不能为空'); return false; } save(STORAGE.username, username.trim()); save(STORAGE.password, b64enc(password.trim())); save(STORAGE.token, null); // 清除旧 token save(STORAGE.tokenExpiry, null); console.log('[聚影签到] 账号已保存:', maskAccount(username)); return true; } /** * 检查是否已配置 */ function isConfigured() { const account = getAccount(); return account !== null; } /** * 打开配置面板(使用浏览器原生 prompt) */ function openConfigPanel() { const currentAccount = load(STORAGE.username) || ''; const username = prompt( '请输入聚影账号(用户名或邮箱):\n\n当前账号: ' + (currentAccount ? maskAccount(currentAccount) : '未配置'), currentAccount ); if (!username) { // 用户取消 return; } const password = prompt('请输入聚影密码:'); if (!password) { // 用户取消 return; } if (saveAccount(username, password)) { notify('success', '账号配置成功!', `账号: ${maskAccount(username)}`); // 立即测试签到 main(); } else { notify('error', '账号配置失败', '请检查输入是否正确'); } } /** * 清除配置 */ function clearConfig() { remove(STORAGE.username); remove(STORAGE.password); remove(STORAGE.token); remove(STORAGE.tokenExpiry); remove(STORAGE.lastCheckin); notify('success', '配置已清除', '请重新配置账号密码'); console.log('[聚影签到] 所有配置已清除'); } // ═══════════════════════════════════════════════════════════ // API 交互 // ═══════════════════════════════════════════════════════════ /** * 发送 HTTP 请求(封装 GM_xmlhttpRequest) */ function request(method, path, data = null, token = null) { return new Promise((resolve, reject) => { const url = SITE_URL + path; const headers = { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }; if (token) { headers['X-App-User-Token'] = token; } const options = { method: method, url: url, headers: headers, onload: function(response) { console.log(`[聚影签到] ${method} ${path} → ${response.status}`); if (response.status >= 200 && response.status < 300) { try { const json = JSON.parse(response.responseText); resolve(json); } catch (e) { reject(new Error('响应解析失败: ' + e.message)); } } else { try { const json = JSON.parse(response.responseText); reject(new Error(json.message || json.msg || `HTTP ${response.status}`)); } catch (e) { reject(new Error(`HTTP ${response.status}`)); } } }, onerror: function(error) { console.error('[聚影签到] 请求失败:', error); reject(new Error('网络请求失败')); }, ontimeout: function() { reject(new Error('请求超时')); } }; if (data) { options.data = JSON.stringify(data); } // Cookie 支持 try { options.withCredentials = true; } catch (e) {} console.log(`[聚影签到] 请求: ${method} ${url}`); GM_xmlhttpRequest(options); }); } /** * 获取 CSRF Token */ async function getCSRF() { try { await request('GET', API.csrf); // CSRF 会自动设置 cookie console.log('[聚影签到] CSRF token 已获取'); return true; } catch (e) { console.warn('[聚影签到] CSRF 获取失败(可能不需要):', e.message); return false; } } /** * 登录获取 Token */ async function login(username, password) { try { // 先获取 CSRF await getCSRF(); const result = await request('POST', API.login, { username: username, password: password }); if (result.status === 'success' && result.token) { // 保存 token(7天有效期) const expiry = Date.now() + 7 * 24 * 60 * 60 * 1000; save(STORAGE.token, result.token); save(STORAGE.tokenExpiry, expiry); console.log('[聚影签到] 登录成功,token 已保存'); return result.token; } else { throw new Error(result.message || result.msg || '登录失败'); } } catch (e) { console.error('[聚影签到] 登录失败:', e.message); throw e; } } /** * 获取有效 Token(自动续期) */ async function getToken() { const token = load(STORAGE.token); const expiry = load(STORAGE.tokenExpiry); // 如果 token 存在且未过期 if (token && expiry && Date.now() < expiry) { console.log('[聚影签到] 使用缓存的 token'); return token; } // Token 过期或不存在,重新登录 console.log('[聚影签到] Token 过期或不存在,重新登录'); const account = getAccount(); if (!account) { throw new Error('未配置账号密码'); } return await login(account.username, account.password); } /** * 获取签到状态 */ async function getStats(token) { try { const result = await request('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 }; } else { throw new Error(result.message || '获取状态失败'); } } catch (e) { console.error('[聚影签到] 获取状态失败:', e.message); throw e; } } /** * 执行签到 */ async function doCheckin(token) { try { const result = await request('POST', API.checkin, null, token); if (result.status === 'success') { return { success: true, message: result.message || '签到成功', points: result.points || 5 }; } else { // 可能是今日已签到 return { success: false, message: result.message || result.msg || '签到失败' }; } } catch (e) { // 今日已签到返回 400 if (e.message.includes('已签到')) { return { success: false, already: true, message: e.message }; } throw e; } } // ═══════════════════════════════════════════════════════════ // 主逻辑 // ═══════════════════════════════════════════════════════════ /** * 主函数:签到流程 */ async function main() { console.log('[聚影签到] 开始执行签到任务...'); // 检查配置 if (!isConfigured()) { notify('config', '请先配置聚影账号密码', '点击脚本菜单中的"配置账号"'); return; } const account = getAccount(); console.log(`[聚影签到] 当前账号: ${maskAccount(account.username)}`); try { // 获取 Token const token = await getToken(); // 获取签到状态 const stats = await getStats(token); console.log(`[聚影签到] 累计天数: ${stats.totalDays}, 今日已签: ${stats.checkedToday}`); // 如果今日已签到 if (stats.checkedToday) { notify('already', `累计签到 ${stats.totalDays} 天`, `账号: ${maskAccount(account.username)}`); save(STORAGE.lastCheckin, new Date().toISOString()); return; } // 执行签到 const result = await doCheckin(token); if (result.success) { notify('success', `获得 ${result.points} 积分,累计 ${stats.totalDays + 1} 天`, `账号: ${maskAccount(account.username)}` ); save(STORAGE.lastCheckin, new Date().toISOString()); } else if (result.already) { notify('already', `累计签到 ${stats.totalDays} 天`, `账号: ${maskAccount(account.username)}`); } else { notify('error', result.message, `账号: ${maskAccount(account.username)}`); } } catch (e) { console.error('[聚影签到] 签到失败:', e); // 登录失败时清除 token if (e.message.includes('登录') || e.message.includes('密码')) { save(STORAGE.token, null); save(STORAGE.tokenExpiry, null); } notify('error', e.message, `账号: ${maskAccount(account.username)}`); } } // ═══════════════════════════════════════════════════════════ // 脚本菜单 // ═══════════════════════════════════════════════════════════ /** * 注册脚本菜单命令 */ function registerMenus() { try { GM_registerMenuCommand('🔄 立即签到', main); GM_registerMenuCommand('⚙️ 配置账号', openConfigPanel); GM_registerMenuCommand('🗑️ 清除配置', clearConfig); if (isConfigured()) { const account = load(STORAGE.username); GM_registerMenuCommand(`📋 当前账号: ${maskAccount(account)}`, () => { alert(`当前配置账号: ${maskAccount(account)}\n\n点击"配置账号"可修改`); }); } } catch (e) { console.error('[聚影签到] 菜单注册失败:', e); } } // ═══════════════════════════════════════════════════════════ // 初始化 // ═══════════════════════════════════════════════════════════ /** * 页面加载完成后执行 */ function init() { console.log('[聚影签到] 脚本已加载'); // 注册菜单 registerMenus(); // 如果是定时任务触发(后台脚本),直接执行 // 如果是页面加载,延迟 2 秒检查是否需要配置 const isBackground = typeof window === 'undefined' || !window.location; if (isBackground) { // 后台定时任务 main(); } else { // 页面加载模式 setTimeout(() => { if (!isConfigured()) { console.log('[聚影签到] 检测到未配置,提示用户'); // 不自动弹窗,避免干扰用户浏览 // 用户需要手动点击菜单"配置账号" } }, 2000); } } // 启动 init(); })();