mirror of
https://github.com/XiaoGe-LiBai/yangmao.git
synced 2025-12-16 19:59:30 +08:00
330 lines
10 KiB
JavaScript
330 lines
10 KiB
JavaScript
/*
|
||
* @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);
|
||
}); |