diff --git a/bz公益.js b/bz公益.js new file mode 100644 index 0000000..4b749f7 --- /dev/null +++ b/bz公益.js @@ -0,0 +1,330 @@ +/* + * @File: lucky_sign_lottery.js + * @Description: 适用于 lucky.5202030.xyz 的全自动脚本 + * @Version: 2.0.0 + * @Features: + * 1. 每日自动签到 + * 2. 每日/每周/每月福利自动领取 (New) + * 3. 自动抽奖 (直到次数耗尽) + * 4. Token 过期/无效时自动使用账号密码登录 (New) + * * @Usage: + * 1. 在青龙面板 "依赖管理" -> "NodeJs" 中,确保安装 `axios`。 + * 2. 在 "环境变量" 中添加以下变量: + * - LUCKY_XYZ_USERNAME: 你的登录用户名 (必填,用于Token过期时自动登录) + * - LUCKY_XYZ_PASSWORD: 你的登录密码 (必填,用于Token过期时自动登录) + * - LUCKY_XYZ_TOKEN: (可选) 如果你不想填账号密码,可直接填 Bearer Token。 + * * 3. 设置定时任务,建议每天 00:10 执行: "10 0 * * *" + */ + +const axios = require('axios'); + +// ================= 配置区域 ================= + +// 环境变量名称 +const ENV = { + TOKEN: 'LUCKY_XYZ_TOKEN', + USER: 'LUCKY_XYZ_USERNAME', + PASS: 'LUCKY_XYZ_PASSWORD' +}; + +// API 端点 +const API = { + LOGIN: 'https://lucky.5202030.xyz/api/auth/login/password', + SIGN: 'https://lucky.5202030.xyz/api/sign/', + LOTTERY: 'https://lucky.5202030.xyz/api/lottery/', + CLAIM: 'https://lucky.5202030.xyz/api/lucky-code/privileges/claim/all' // 新增福利接口 +}; + +// 配置参数 +const CONFIG = { + LOTTERY_DELAY: 500, // 抽奖间隔(ms) + MAX_ATTEMPTS: 200, // 最大抽奖次数保护 + USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' +}; + +// 全局变量,用于存储运行时 Token +let CURRENT_TOKEN = process.env[ENV.TOKEN] || ''; + +// ================= 工具函数 ================= + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// 获取最新的请求头 +function getHeaders() { + return { + 'accept': 'application/json, text/plain, */*', + 'authorization': CURRENT_TOKEN.startsWith('Bearer') ? CURRENT_TOKEN : `Bearer ${CURRENT_TOKEN}`, + 'content-type': 'application/json', + 'origin': 'https://lucky.5202030.xyz', + 'referer': 'https://lucky.5202030.xyz/', + 'user-agent': CONFIG.USER_AGENT + }; +} + +// 格式化日志输出 +function log(msg, type = 'info') { + const icon = { + info: 'ℹ️', + success: '✅', + warn: '⚠️', + error: '❌', + gift: '🎁', + money: '💰' + }[type] || ''; + console.log(`${icon} ${msg}`); +} + +// ================= 核心逻辑 ================= + +/** + * 核心功能:登录获取 Token + */ +async function doLogin() { + const username = process.env[ENV.USER]; + const password = process.env[ENV.PASS]; + + if (!username || !password) { + log('未配置用户名或密码,无法执行自动登录。请检查环境变量。', 'error'); + return false; + } + + log(`正在尝试使用账号 [${username}] 登录...`, 'info'); + + try { + const response = await axios.post(API.LOGIN, { + username: username, + password: password + }, { + headers: { + 'content-type': 'application/json', + 'user-agent': CONFIG.USER_AGENT + } + }); + + const data = response.data; + if (data.success && data.data && data.data.access_token) { + // 更新全局 Token + CURRENT_TOKEN = `Bearer ${data.data.access_token}`; + log(`登录成功!当前余额: ${data.data.user.balance}`, 'success'); + return true; + } else { + log(`登录失败: ${data.message}`, 'error'); + return false; + } + } catch (error) { + log(`登录接口请求异常: ${error.message}`, 'error'); + return false; + } +} + +/** + * 通用请求包装器 (包含 401 自动重试逻辑) + * @param {Function} requestFn 实际的请求函数 + * @param {string} taskName 任务名称 + */ +async function requestWithRetry(requestFn, taskName) { + try { + // 如果没有 Token,先尝试登录 + if (!CURRENT_TOKEN) { + log('检测到 Token 为空,尝试自动登录...', 'warn'); + const loginSuccess = await doLogin(); + if (!loginSuccess) throw new Error('登录失败,无法继续执行任务'); + } + + return await requestFn(); + } catch (error) { + // 捕获 401 Unauthorized 错误 + if (error.response && error.response.status === 401) { + log(`Token 已过期或无效,正在尝试重新登录...`, 'warn'); + const loginSuccess = await doLogin(); + if (loginSuccess) { + log(`登录成功,重新执行任务: ${taskName}`, 'info'); + return await requestFn(); // 重试一次 + } else { + log(`重新登录失败,放弃任务: ${taskName}`, 'error'); + } + } else { + throw error; // 其他错误直接抛出 + } + } +} + +/** + * 任务1:每日签到 + */ +async function doSignIn() { + console.log('\n--- 🟢 任务1: 每日签到 ---'); + + const execute = async () => { + const response = await axios.post(API.SIGN, null, { headers: getHeaders() }); + const data = response.data; + + if (data.success) { + log(`签到成功: ${data.message}`, 'success'); + log(`获得奖励: ${data.reward}`, 'gift'); + log(`当前余额: ${data.current_balance}`, 'money'); + } else { + log(`签到结果: ${data.message}`, 'info'); + } + }; + + try { + await requestWithRetry(execute, '每日签到'); + } catch (error) { + log(`签到执行异常: ${error.message}`, 'error'); + } +} + +/** + * 任务2:领取福利 (每日/每周/每月) + */ +async function doClaimPrivileges() { + console.log('\n--- 🟢 任务2: 领取特权福利 ---'); + + const execute = async () => { + const response = await axios.post(API.CLAIM, null, { headers: getHeaders() }); + const data = response.data; + + if (data.success) { + log(`${data.message}`, 'success'); + + // 解析并打印详细获得的物品 + if (data.total_items && data.total_items.length > 0) { + console.log(' [获得物品清单]:'); + data.total_items.forEach(item => { + // 简单的字典翻译,可根据实际情况补充 + const itemNameMap = { + 'time_rewind': '时光倒流(补签卡)', + 'lucky_charm': '幸运符', + 'revive_ticket': '复活票' + }; + const name = itemNameMap[item.item_type] || item.item_type; + console.log(` - ${name} x ${item.quantity}`); + }); + } else { + console.log(' - 本次没有获得新的物品 (可能已领取)'); + } + } else { + log(`领取福利失败: ${data.message}`, 'warn'); + } + }; + + try { + await requestWithRetry(execute, '领取福利'); + } catch (error) { + log(`领取福利异常: ${error.message}`, 'error'); + } +} + +/** + * 任务3:自动抽奖 + */ +async function doLottery() { + console.log('\n--- 🟢 任务3: 自动抽奖 ---'); + + const stats = { + totalNet: 0, + draws: 0, + wins: 0, + losses: 0, + draws_0: 0, + endBalance: 0 + }; + + let remainingAttempts = -1; + + // 定义单次抽奖逻辑 + const executeOneDraw = async () => { + return axios.post(API.LOTTERY, null, { headers: getHeaders() }); + }; + + // 如果一开始就没 Token,先登录一次,避免循环里每次都判断 + if (!CURRENT_TOKEN) { + if (!await doLogin()) return; + } + + for (let i = 1; i <= CONFIG.MAX_ATTEMPTS; i++) { + try { + // 这里不使用 requestWithRetry 避免过于复杂的嵌套, + // 假设任务1和2通过后 Token 应该是有效的。 + // 如果中途过期 (极少见),catch 块会捕获。 + const response = await executeOneDraw(); + const data = response.data; + + if (data.success) { + stats.draws++; + stats.totalNet += data.net_change; + stats.endBalance = data.current_balance; + + if (data.net_change > 0) stats.wins++; + else if (data.net_change < 0) stats.losses++; + else stats.draws_0++; + + console.log(`[第${String(i).padStart(3, '0')}抽] ${data.message} | 变动: ${data.net_change} | 剩余: ${data.remaining_attempts}`); + + remainingAttempts = data.remaining_attempts; + if (remainingAttempts === 0) { + log('今日抽奖次数已耗尽', 'info'); + break; + } + } else { + log(`抽奖停止: ${data.message}`, 'warn'); + break; + } + + await delay(CONFIG.LOTTERY_DELAY); + + } catch (error) { + // 如果中途 401,尝试补救一次 + if (error.response && error.response.status === 401) { + log('抽奖途中 Token 失效,尝试重连...', 'warn'); + if (await doLogin()) { + i--; // 回退计数器,重试这一次 + continue; + } else { + break; // 登录失败,彻底退出 + } + } + log(`抽奖请求错误: ${error.message}`, 'error'); + break; + } + } + + // 统计报告 + console.log('\n📊 [抽奖统计]'); + if (stats.draws > 0) { + console.log(` - 总次数: ${stats.draws}`); + console.log(` - 盈利局: ${stats.wins}`); + console.log(` - 亏损局: ${stats.losses}`); + console.log(` - 最终余额: ${stats.endBalance}`); + console.log(` - 今日盈亏: ${stats.totalNet.toFixed(2)}`); + } else { + console.log(' - 未进行有效抽奖'); + } +} + +// ================= 主程序 ================= + +async function main() { + console.log(`🚀 Lucky.XYZ 自动脚本启动`); + + // 1. 签到 + await doSignIn(); + + // 2. 领福利 (中间间隔一下) + await delay(1000); + await doClaimPrivileges(); + + // 3. 抽奖 + await delay(1000); + await doLottery(); + + console.log('\n🏁 所有任务执行完毕。'); +} + +main().catch(error => { + console.error('❌ 脚本致命错误:', error); + process.exit(1); +}); \ No newline at end of file