585 lines
16 KiB
JavaScript
585 lines
16 KiB
JavaScript
// # Source: https://github.com/DearSong15/ql-scripts/blob/main/zsp_video.js
|
||
// # Raw: https://raw.githubusercontent.com/DearSong15/ql-scripts/main/zsp_video.js
|
||
// # Repo: DearSong15/ql-scripts
|
||
// # Path: zsp_video.js
|
||
// # UploadedAt: 2026-05-11T15:03:30Z
|
||
// # SHA256: 960b2e28799c40a00eb4bd75dcda1e61b3ab747374716073995c75053c3083ee
|
||
// # Category: web版/账密
|
||
// # Evidence: web/H5关键词 + username/password/login
|
||
//
|
||
|
||
//走个码吧,谢谢兄弟们了
|
||
//后台注册地址https://zsp.99panel.top/#/register?inviteCode=lzwbaleD
|
||
//前台https://a.zsp55.app/
|
||
//变量格式:备注#SecretId#SecretKey#deviceld
|
||
const ENV_NAME = "中视频";
|
||
const USER_AGENT = "Mozilla/5.0 (Linux; Android 15; 23013RK75C Build/AQ3A.250226.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.260 Mobile Safari/537.36 (Immersed/39.42857) Html5Plus/1.0";
|
||
const BASE_URL = "https://x1.zsptv.online";
|
||
|
||
// 工具函数
|
||
function wait(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
// HTTP请求函数
|
||
async function httpRequest(options) {
|
||
return new Promise((resolve, reject) => {
|
||
const url = new URL(options.url);
|
||
const https = url.protocol === 'https:' ? require('https') : require('http');
|
||
|
||
const req = https.request({
|
||
hostname: url.hostname,
|
||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||
path: url.pathname + url.search,
|
||
method: options.method || 'GET',
|
||
headers: options.headers || {}
|
||
}, (res) => {
|
||
let data = '';
|
||
res.on('data', (chunk) => {
|
||
data += chunk;
|
||
});
|
||
res.on('end', () => {
|
||
// 调试:记录响应
|
||
if (res.statusCode === 403) {
|
||
console.log(`🔍 响应体: ${data.substring(0, 200)}...`);
|
||
}
|
||
resolve({
|
||
statusCode: res.statusCode,
|
||
headers: res.headers,
|
||
body: data
|
||
});
|
||
});
|
||
});
|
||
|
||
req.on('error', (err) => {
|
||
reject(err);
|
||
});
|
||
|
||
if (options.body) {
|
||
req.write(options.body);
|
||
}
|
||
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// Unicode解码函数
|
||
function decodeUnicode(str) {
|
||
if (!str) return '';
|
||
return str.replace(/\\u[\dA-F]{4}/gi,
|
||
match => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)));
|
||
}
|
||
|
||
// 主函数
|
||
async function main() {
|
||
console.log(`🔔 脚本开始运行,时间: ${new Date().toLocaleString()}\n`);
|
||
|
||
// 读取环境变量
|
||
const accounts = loadAccounts();
|
||
|
||
if (accounts.length === 0) {
|
||
console.log("❌ 未找到有效的账号配置,请检查环境变量格式!");
|
||
return;
|
||
}
|
||
|
||
console.log(`✅ 找到 ${accounts.length} 个账号\n`);
|
||
|
||
// 遍历所有账号
|
||
for (let i = 0; i < accounts.length; i++) {
|
||
const account = accounts[i];
|
||
console.log(`\n📱 开始处理账号 ${i+1}: ${account.remark}`);
|
||
console.log("=".repeat(30));
|
||
|
||
try {
|
||
await processAccount(account);
|
||
} catch (error) {
|
||
console.log(`❌ 处理账号 ${account.remark} 时出错: ${error.message}`);
|
||
}
|
||
|
||
// 账号间延迟
|
||
if (i < accounts.length - 1) {
|
||
console.log("\n⏳ 等待5秒后处理下一个账号...");
|
||
await wait(5000);
|
||
}
|
||
}
|
||
|
||
console.log(`\n🎉 所有账号处理完成!`);
|
||
}
|
||
|
||
// 加载账号信息
|
||
function loadAccounts() {
|
||
const accounts = [];
|
||
|
||
// 尝试从不同环境变量名称读取
|
||
const envNames = ['ZSP', 'AD_WATCH_ACCOUNTS'];
|
||
let envValue = '';
|
||
|
||
for (const envName of envNames) {
|
||
if (process.env[envName]) {
|
||
envValue = process.env[envName];
|
||
console.log(`📋 从环境变量 ${envName} 读取配置`);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!envValue) {
|
||
console.log("⚠️ 请设置环境变量 ZSP 或 AD_WATCH_ACCOUNTS");
|
||
return accounts;
|
||
}
|
||
|
||
const accountStrs = envValue.split("\n").filter(str => str.trim());
|
||
|
||
for (const str of accountStrs) {
|
||
const parts = str.split("#");
|
||
if (parts.length >= 4) {
|
||
accounts.push({
|
||
remark: parts[0] || "未命名账号",
|
||
secretId: parts[1],
|
||
secretKey: parts[2],
|
||
deviceId: parts[3]
|
||
});
|
||
console.log(`✅ 加载账号: ${parts[0] || "未命名账号"}`);
|
||
} else {
|
||
console.log(`⚠️ 忽略格式错误的环境变量: ${str}`);
|
||
}
|
||
}
|
||
|
||
return accounts;
|
||
}
|
||
|
||
// 处理单个账号
|
||
async function processAccount(account) {
|
||
// 1. 登录获取token
|
||
const token = await login(account);
|
||
if (!token) {
|
||
console.log("❌ 登录失败,跳过此账号");
|
||
return;
|
||
}
|
||
console.log(`✅ 登录成功,token获取完成`);
|
||
|
||
// 2. 检测并执行签到
|
||
const signed = await checkAndSign(token, account);
|
||
if (!signed) {
|
||
console.log("❌ 签到失败,跳过广告任务");
|
||
return;
|
||
}
|
||
|
||
console.log(`\n📺 开始执行广告观看任务`);
|
||
console.log("-".repeat(30));
|
||
|
||
let successCount = 0;
|
||
let failCount = 0;
|
||
let totalReward = 0;
|
||
let consecutiveErrors = 0;
|
||
const maxAds = 50; // 最多执行50次广告任务
|
||
|
||
for (let adCount = 0; adCount < maxAds; adCount++) {
|
||
console.log(`\n🔄 第 ${adCount + 1}/${maxAds} 次广告任务`);
|
||
|
||
// 如果连续出现3次错误,重新登录获取新token
|
||
if (consecutiveErrors >= 3) {
|
||
console.log("⚠️ 连续3次错误,尝试重新登录...");
|
||
const newToken = await login(account);
|
||
if (newToken) {
|
||
token = newToken;
|
||
console.log("✅ 重新登录成功,继续任务");
|
||
consecutiveErrors = 0;
|
||
} else {
|
||
console.log("❌ 重新登录失败,跳过此账号");
|
||
break;
|
||
}
|
||
}
|
||
|
||
try {
|
||
// 获取广告信息
|
||
const adInfo = await getNextAd(token, account);
|
||
if (!adInfo) {
|
||
console.log(` ⚠️ 获取广告失败`);
|
||
failCount++;
|
||
consecutiveErrors++;
|
||
await wait(3000);
|
||
continue;
|
||
}
|
||
|
||
console.log(` 📱 广告标题: ${adInfo.title}`);
|
||
console.log(` ⏱️ 需要观看: ${adInfo.duration}秒`);
|
||
console.log(` 🪙 预期奖励: ${adInfo.reward || 0}`);
|
||
|
||
// 开始播放广告
|
||
console.log(` ▶️ 开始播放广告...`);
|
||
const playResult = await claimReward(token, account, adInfo.id, adInfo.duration);
|
||
|
||
if (playResult && playResult.success) {
|
||
console.log(` ✅ 广告观看成功!`);
|
||
console.log(` 💰 获得奖励: ${playResult.reward || 0}`);
|
||
|
||
successCount++;
|
||
totalReward += parseInt(playResult.reward) || 0;
|
||
consecutiveErrors = 0; // 重置错误计数
|
||
|
||
// 任务间延迟 (避免请求过快)
|
||
if (adCount < maxAds - 1) {
|
||
const randomDelay = Math.floor(Math.random() * 3000) + 3000; // 3-6秒随机延迟
|
||
console.log(` ⏳ 等待${Math.round(randomDelay/1000)}秒后处理下一个广告...`);
|
||
await wait(randomDelay);
|
||
}
|
||
} else {
|
||
console.log(` ❌ 广告观看失败`);
|
||
failCount++;
|
||
consecutiveErrors++;
|
||
await wait(3000);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(` ❌ 广告任务出错: ${error.message}`);
|
||
failCount++;
|
||
consecutiveErrors++;
|
||
await wait(3000);
|
||
}
|
||
}
|
||
|
||
console.log("\n" + "=".repeat(30));
|
||
console.log(`📊 任务完成统计:`);
|
||
console.log(` ✅ 成功: ${successCount} 次`);
|
||
console.log(` ❌ 失败: ${failCount} 次`);
|
||
console.log(` 💰 总计奖励: ${totalReward}`);
|
||
console.log(` 📈 成功率: ${((successCount/maxAds)*100).toFixed(1)}%`);
|
||
}
|
||
|
||
// 登录接口
|
||
async function login(account) {
|
||
const url = `${BASE_URL}/api/app/v1/auth/secretKeyLogin`;
|
||
|
||
const body = {
|
||
secretId: account.secretId,
|
||
secretKey: account.secretKey
|
||
};
|
||
|
||
const headers = {
|
||
"Accept": "*/*",
|
||
"User-Agent": USER_AGENT,
|
||
"app-device": JSON.stringify({
|
||
"id": account.deviceId,
|
||
"brand": "xiaomi",
|
||
"model": "23013RK75C",
|
||
"platform": "android",
|
||
"system": "Android 15"
|
||
}),
|
||
"Content-Type": "application/json",
|
||
"Host": "x1.zsptv.online"
|
||
};
|
||
|
||
try {
|
||
const response = await httpRequest({
|
||
url: url,
|
||
method: "POST",
|
||
headers: headers,
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
if (response.statusCode !== 200) {
|
||
console.log(`❌ 登录请求失败,状态码: ${response.statusCode}`);
|
||
return null;
|
||
}
|
||
|
||
const data = JSON.parse(response.body);
|
||
|
||
if (data.code === 0 && data.data && data.data.token) {
|
||
return data.data.token;
|
||
} else {
|
||
console.log(`❌ 登录失败: ${data.message || "未知错误"}`);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.log(`❌ 登录请求失败: ${error.message}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 检测并执行签到
|
||
async function checkAndSign(token, account) {
|
||
const url = `${BASE_URL}/api/app/v1/device/userSign`;
|
||
|
||
const headers = {
|
||
"Accept": "*/*",
|
||
"User-Agent": USER_AGENT,
|
||
"Authorization": `Bearer ${token}`,
|
||
"app-device": JSON.stringify({
|
||
"id": account.deviceId,
|
||
"brand": "xiaomi",
|
||
"model": "23013RK75C",
|
||
"platform": "android",
|
||
"system": "Android 15"
|
||
}),
|
||
"Content-Type": "application/json",
|
||
"Host": "x1.zsptv.online"
|
||
};
|
||
|
||
try {
|
||
const response = await httpRequest({
|
||
url: url,
|
||
method: "POST",
|
||
headers: headers,
|
||
body: "{}"
|
||
});
|
||
|
||
if (response.statusCode !== 200) {
|
||
console.log(`❌ 签到请求失败,状态码: ${response.statusCode}`);
|
||
return false;
|
||
}
|
||
|
||
const data = JSON.parse(response.body);
|
||
|
||
if (data.code === 0) {
|
||
const message = decodeUnicode(data.message);
|
||
console.log(`✅ 签到结果: ${message}`);
|
||
|
||
if (data.data) {
|
||
console.log(` 🪙 获得签到金币: ${data.data.qiandao_money || 0}`);
|
||
console.log(` 📅 连续签到天数: ${data.data.continuousDays || 1}`);
|
||
}
|
||
|
||
return true;
|
||
} else {
|
||
const errorMsg = decodeUnicode(data.message || "未知错误");
|
||
console.log(`❌ 签到失败: ${errorMsg}`);
|
||
|
||
// 如果已签到,也返回true,继续执行广告任务
|
||
if (errorMsg && errorMsg.includes("已签到")) {
|
||
console.log(`ℹ️ 今日已签到,继续执行广告任务`);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
console.log(`❌ 签到请求失败: ${error.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 获取下一个广告
|
||
async function getNextAd(token, account) {
|
||
const url = `${BASE_URL}/api/app/v1/ad/next`;
|
||
|
||
const headers = {
|
||
"Accept": "*/*",
|
||
"User-Agent": USER_AGENT,
|
||
"Authorization": `Bearer ${token}`,
|
||
"app-device": JSON.stringify({
|
||
"id": account.deviceId,
|
||
"brand": "xiaomi",
|
||
"model": "23013RK75C",
|
||
"platform": "android",
|
||
"system": "Android 15"
|
||
}),
|
||
"Content-Type": "application/json",
|
||
"Host": "x1.zsptv.online"
|
||
};
|
||
|
||
try {
|
||
const response = await httpRequest({
|
||
url: url,
|
||
method: "GET",
|
||
headers: headers
|
||
});
|
||
|
||
if (response.statusCode !== 200) {
|
||
console.log(` ❌ 获取广告失败,状态码: ${response.statusCode}`);
|
||
if (response.body) {
|
||
const errorData = JSON.parse(response.body);
|
||
console.log(` 🔍 错误信息: ${errorData.message || "未知错误"}`);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
const data = JSON.parse(response.body);
|
||
|
||
if (data.code === 0 && data.data && data.data.result) {
|
||
const ad = data.data.result;
|
||
return {
|
||
id: ad.id,
|
||
title: decodeUnicode(ad.title),
|
||
description: decodeUnicode(ad.description),
|
||
duration: parseInt(ad.video?.duration || 30), // 默认30秒
|
||
videoUrl: ad.video?.url || "",
|
||
playUrl: ad.video?.play_url || "",
|
||
reward: ad.reward
|
||
};
|
||
} else {
|
||
const errorMsg = decodeUnicode(data.message || "未知错误");
|
||
console.log(` ❌ 获取广告失败: ${errorMsg}`);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.log(` ❌ 获取广告请求失败: ${error.message}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 观看广告并获取奖励(整合播放和结束确认)
|
||
async function claimReward(token, account, adId, duration) {
|
||
const startTime = new Date().toISOString();
|
||
|
||
// 1. 开始播放广告
|
||
console.log(` 📅 播放开始时间: ${startTime}`);
|
||
const playResult = await startVideoPlay(token, account, adId, startTime);
|
||
|
||
if (!playResult || !playResult.playRecordId) {
|
||
console.log(` ❌ 广告播放开始失败`);
|
||
return { success: false };
|
||
}
|
||
|
||
console.log(` ✅ 播放开始成功! 记录ID: ${playResult.playRecordId}`);
|
||
console.log(` 💰 初始奖励: ${playResult.initialReward || 0}`);
|
||
|
||
// 2. 等待广告播放时间
|
||
const waitTime = duration * 1000;
|
||
console.log(` ⌛ 广告播放中,等待 ${duration} 秒...`);
|
||
await wait(waitTime);
|
||
|
||
// 3. 广告播放结束确认
|
||
console.log(` ⏹️ 广告播放完成,确认结束...`);
|
||
const endResult = await endVideoPlay(token, account, playResult.playRecordId);
|
||
|
||
if (endResult.success) {
|
||
console.log(` 🎉 广告观看完整流程完成!`);
|
||
return {
|
||
success: true,
|
||
reward: playResult.reward || 0,
|
||
playRecordId: playResult.playRecordId
|
||
};
|
||
} else {
|
||
console.log(` ⚠️ 广告观看完成,但结束确认失败`);
|
||
// 即使结束确认失败,也视为成功(因为已经播放完成)
|
||
return {
|
||
success: true,
|
||
reward: playResult.reward || 0,
|
||
playRecordId: playResult.playRecordId
|
||
};
|
||
}
|
||
}
|
||
|
||
// 开始播放广告
|
||
async function startVideoPlay(token, account, adId, playTime) {
|
||
const url = `${BASE_URL}/api/app/v1/ad/video/play`;
|
||
|
||
const body = {
|
||
clientIp: "",
|
||
deviceInfo: {
|
||
deviceId: account.deviceId,
|
||
platform: "android"
|
||
},
|
||
id: adId.toString(),
|
||
playTime: playTime
|
||
};
|
||
|
||
const headers = {
|
||
"Accept": "*/*",
|
||
"User-Agent": USER_AGENT,
|
||
"Authorization": `Bearer ${token}`,
|
||
"app-device": JSON.stringify({
|
||
"id": account.deviceId,
|
||
"brand": "xiaomi",
|
||
"model": "23013RK75C",
|
||
"platform": "android",
|
||
"system": "Android 15"
|
||
}),
|
||
"Content-Type": "application/json",
|
||
"Host": "x1.zsptv.online"
|
||
};
|
||
|
||
try {
|
||
const response = await httpRequest({
|
||
url: url,
|
||
method: "POST",
|
||
headers: headers,
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
if (response.statusCode !== 200) {
|
||
console.log(` ❌ 开始播放广告失败,状态码: ${response.statusCode}`);
|
||
return null;
|
||
}
|
||
|
||
const data = JSON.parse(response.body);
|
||
|
||
if (data.code === 0 && data.data) {
|
||
return {
|
||
playRecordId: data.data.id,
|
||
initialReward: data.data.reward || 0,
|
||
reward: data.data.reward || 0
|
||
};
|
||
} else {
|
||
console.log(` ❌ 开始播放广告失败: ${data.message || "未知错误"}`);
|
||
return null;
|
||
}
|
||
} catch (error) {
|
||
console.log(` ❌ 开始播放广告请求失败: ${error.message}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 结束广告播放
|
||
async function endVideoPlay(token, account, playRecordId) {
|
||
const url = `${BASE_URL}/api/app/v1/ad/video/ended`;
|
||
const endTime = new Date().toISOString();
|
||
|
||
const body = {
|
||
clientIp: "",
|
||
deviceInfo: {
|
||
deviceId: account.deviceId,
|
||
platform: "android"
|
||
},
|
||
id: playRecordId.toString(),
|
||
playTime: endTime
|
||
};
|
||
|
||
const headers = {
|
||
"Accept": "*/*",
|
||
"User-Agent": USER_AGENT,
|
||
"Authorization": `Bearer ${token}`,
|
||
"app-device": JSON.stringify({
|
||
"id": account.deviceId,
|
||
"brand": "xiaomi",
|
||
"model": "23013RK75C",
|
||
"platform": "android",
|
||
"system": "Android 15"
|
||
}),
|
||
"Content-Type": "application/json",
|
||
"Host": "x1.zsptv.online"
|
||
};
|
||
|
||
try {
|
||
const response = await httpRequest({
|
||
url: url,
|
||
method: "POST",
|
||
headers: headers,
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
if (response.statusCode !== 200) {
|
||
console.log(` ⚠️ 广告结束确认失败,状态码: ${response.statusCode}`);
|
||
// 即使结束确认失败,也返回成功(避免403错误导致任务中断)
|
||
return { success: true };
|
||
}
|
||
|
||
const data = JSON.parse(response.body);
|
||
|
||
if (data.code === 0) {
|
||
return { success: true };
|
||
} else {
|
||
console.log(` ⚠️ 广告结束确认返回异常: ${data.message || "未知错误"}`);
|
||
return { success: false };
|
||
}
|
||
} catch (error) {
|
||
console.log(` ⚠️ 广告结束确认请求失败: ${error.message}`);
|
||
return { success: false };
|
||
}
|
||
}
|
||
|
||
// 执行主函数
|
||
if (require.main === module) {
|
||
main().catch(console.error);
|
||
}
|