// ==UserScript== // @name 油猴中文网-定时每日签到 // @namespace https://bbs.tampermonkey.net.cn/ // @version 1.1.1 // @description 油猴中文网-每天定时自动完成网站的每日签到功能! // @author Free-wang 完成主要代码、逻辑思路、以及整体架构的开发 // @contributor DeepSeek 提供部分代码、逻辑分析、以及语法设计的辅助 // @license MIT // @supportURL https://scriptcat.org/zh-CN/script-show-page/3675/issue // @website https://gitee.com/ihacker/UserScripts/ // @source https://gitee.com/ihacker/UserScripts/blob/master/crontab-user-script/%E6%B2%B9%E7%8C%B4%E4%B8%AD%E6%96%87%E7%BD%91-%E5%AE%9A%E6%97%B6%E6%AF%8F%E6%97%A5%E7%AD%BE%E5%88%B0.js // @icon https://img.icons8.com/keek/100/ok-hand.png // @crontab * 0-23 once * * // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @grant GM_info // @grant GM_log // @connect scriptcat.org // @connect bbs.tampermonkey.net.cn // @require https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js // @require https://scriptcat.org/lib/881/1.2.0/script-statistics.js // @require https://cdn.jsdelivr.net/npm/scriptcat-lib@1.1.6/dist/scsite.js // @antifeature tracking 统计用户安装使用情况 // @antifeature annoyances 每次都引导至脚本站评分 // @antifeature referral-link 自动引导至脚本站评分页 // ==/UserScript== /* ==UserConfig== SignConfig: configA: title: 签到模式选择 description: 今日最想说模式:1-自己填写、2-快速选择(默认)、3-不想填写 type: select default: 2 labels: ['自己填写','快速选择','不想填写'] values: [1,2,3] configB: title: 签到自定义语句 description: 我今天最想说 type: text default: 我今天最想说:我什么都不想说! min: 3 max: 50 password: false ==/UserConfig== */ return new Promise((resolve, reject) => { // 表情列表 const EMOTIONS = ['kx', 'ng', 'ym', 'wl', 'nu', 'ch', 'fd', 'yl', 'shuai']; // 快速选择模式的快速语句选择 const QUICK_PHRASE = ['0', '1', '2', '3', '4', '5', '6', '7']; // 默认配置 const DEFAULT_CONFIG = { signMode: GM_getValue("SignConfig.configA"), // 1=自己填写, 2=快速选择, 3=不想填写 customWantSaying: GM_getValue("SignConfig.configB") // 自己填写模式的内容 }; /** * 随机选择函数:用于随机选择EMOTIONS(表情)和QUICK_PHRASE(快速语句) */ function getRandomItem(array) { return array[Math.floor(Math.random() * array.length)]; } /** * 发送请求函数:用于请求[首页(判断是否未登录)]、[签到页(判断是否已签到)]、[签到提交(提交签到请求)] */ function fetchUrl(url, method = 'GET', data = null) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: method, url: url, data: data, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, onload: (response) => { if (response.status >= 200 && response.status < 400) { resolve({text: response.responseText, status: response.status}); } else { reject(new Error(`网络请求状态码异常-${response.status}`)); } }, onerror: (error) => { reject(new Error(`网络请求异常-${error}`)); } }); }); } /** * 检查是否未登录:因油猴中文网滑动验证无法实现自动登录,所以需要提前登录好帐号! * @return {boolean} 是否未登录:true-未登录、false-已登录 */ async function isNotLogin() { try { const result = await fetchUrl('https://bbs.tampermonkey.net.cn/'); const parser = new DOMParser(); const document = parser.parseFromString(result.text, 'text/html'); // 检查[登录链接]或[立即注册]按钮是否存在,判断方式:根据href链接判断 const loginLink = document.querySelector('a[href="member.php?mod=logging&action=login"], a[href="member.php?mod=register"]'); // 存在[登录链接]或[立即注册]按钮则代表未登录【注:双感叹号用于将结果隐式转换布尔类型,类似于`return Boolean(loginLink);`】 return !!loginLink; } catch (error) { throw new Error(`登录状态检查失败:${error}`); } } /** * 检查签到状态:用于判断是否已签到,已经签到的提示并结束脚本! * @return {boolean} 是否已签到:true-已签到、false-未签到 */ async function checkSignStatus() { let isSigned = false; try { const result = await fetchUrl('https://bbs.tampermonkey.net.cn/dsu_paulsign-sign.html'); const parser = new DOMParser(); const document = parser.parseFromString(result.text, 'text/html'); // 情况1:检查已签到标题 const signedTitle = document.querySelector('h1.mt'); if (signedTitle && signedTitle.textContent.includes('您今天已经签到过了')) { isSigned = true; } // 情况2:检查服务台区域 const serviceArea = document.getElementById('qdmsgt'); if (serviceArea && !serviceArea.textContent.includes('今天未签到')) { isSigned = true; } return isSigned; } catch (error) { throw new Error(`签到状态检查失败:${error}`); } } /** * 执行签到操作:填写表单、并发起签到请求 * @throws {Error} 抛出异常:签到执行失败 */ async function performSign() { try { // 获取签到页面获取formhash const signPage = await fetchUrl('https://bbs.tampermonkey.net.cn/dsu_paulsign-sign.html'); const parser = new DOMParser(); const document = parser.parseFromString(signPage.text, 'text/html'); // 获取formhash const formhashInput = document.querySelector('input[name="formhash"]'); const formhash = formhashInput.value; // 获取配置 const signMode = GM_getValue('signMode', DEFAULT_CONFIG.signMode); const customWantSaying = GM_getValue('customWantSaying', DEFAULT_CONFIG.customWantSaying); // 准备签到数据 let postData = `formhash=${encodeURIComponent(formhash)}`; // 添加表情参数 const randomEmotion = getRandomItem(EMOTIONS); postData += `&qdxq=${encodeURIComponent(randomEmotion)}`; // 添加模式参数 postData += `&qdmode=${encodeURIComponent(signMode)}`; // 添加模式内容 if (signMode === 1) { // 自己填写模式 postData += `&todaysay=${encodeURIComponent(customWantSaying.substring(0, 50))}`; } else if (signMode === 2) { // 快速选择模式 const randomPhrase = getRandomItem(QUICK_PHRASE); postData += `&fastreply=${encodeURIComponent(randomPhrase)}`; } // 执行签到请求 const signUrl = 'https://bbs.tampermonkey.net.cn/plugin.php?id=dsu_paulsign:sign&operation=qiandao&infloat=1'; await fetchUrl(signUrl, 'POST', postData); return true; } catch (error) { throw new Error(`签到执行失败:${error}`); } } /** * 主执行函数:用于脚本执行时启动 */ async function main() { try { // 【功能研究】弹出引导窗口,前往脚本站进行评分 const guide = new scsite.guide(1); guide.auto(); // 【功能研究】统计脚本的用户使用情况信息 try { new SC_Statistic({ key: "d2jvfy1rohhto9uu", }); } catch (error) { GM_log(`《${GM_info.script.name}》脚本的用户使用情况统计异常:${error}`, "warn"); console.warn("脚本的用户使用情况统计异常:", error); } // 1. 检查登录状态 const isNotLogged = await isNotLogin(); if (isNotLogged) { const prompt = "您还未登录,自动签到需手动提前登录好,请登录后脚本将在2小时后重新执行!"; GM_log(`《${GM_info.script.name}》脚本提示:${prompt}`, "info"); GM_notification({ title: `《${GM_info.script.name}》脚本提示`, text: prompt, requireInteraction: true }); // 未登录算脚本执行失败,以便脚本能够再次重新尝试执行!【当前设定为7200秒(即2小时后)后重试】 return reject(new CATRetryError("您还未登录油猴中文网,无法自动签到!", 7200)); } // 2. 检查签到状态 const hasSigned = await checkSignStatus(); if (hasSigned) { const prompt = "今日已签到,无需重复签到!"; GM_log(`《${GM_info.script.name}》脚本提示:${prompt}`, "info"); GM_notification({ title: `《${GM_info.script.name}》脚本提示`, text: prompt, requireInteraction: true }); return resolve("脚本运行完毕"); } // 3. 执行签到 const signResult = await performSign(); if (signResult) { // 验证签到结果 const verifySign = await checkSignStatus(); if (verifySign) { const prompt = "定时自动签到脚本执行成功!"; GM_log(`《${GM_info.script.name}》脚本提示:${prompt}`, "info"); GM_notification({ title: `《${GM_info.script.name}》脚本提示`, text: prompt, requireInteraction: true }); } else { const prompt = "网站有更新,导致【检查签到】或【执行签到】方法异常,需重新适配!"; GM_log(`《${GM_info.script.name}》脚本提示:${prompt}`, "info"); GM_notification({ title: `《${GM_info.script.name}》脚本提示`, text: prompt, requireInteraction: true }); } } resolve("脚本运行完毕"); } catch (error) { GM_log(`${GM_info.script.name}脚本错误:${error}`, "error"); GM_notification({ title: `《${GM_info.script.name}》脚本提示`, text: `${GM_info.script.name}脚本执行失败,请进入脚本猫中查看详细错误日志!`, requireInteraction: true }); reject(error); } } // 启动主函数 main().then(r => resolve(r)).catch(e => reject(e)); });