Files
dxxw/dxxw.js
T

1464 lines
54 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { AsyncLocalStorage } = require('async_hooks');
let SocksProxyAgent = null;
try {
({ SocksProxyAgent } = require('socks-proxy-agent'));
} catch (e) {
// 未配置代理时不需要安装;配置 SOCKS5 代理时会给出明确提示。
}
// ============================================
// 大象新闻积分脚本 - Token缓存版
// ============================================
//
// 功能说明:
// 1. Token缓存机制 - 优先使用缓存token,失效后自动重新登录
// 2. 智能任务检测 - 自动检测任务完成情况,只做未完成的任务
// 3. 支持任务:
// - 每日签到 (eventCode: 3)
// - 使用时长 (eventCode: 24)
// - 浏览内容 (eventCode: 26) - 最多15次
// - 点赞文章 (eventCode: 11) - 最多8次
// - 分享文章 (eventCode: 9) - 最多8次
// - 微剧打卡签到 (兑吧 sign/component/doSign9天抽奖)
//
// 环境变量配置:
// export dxxwhd="账号&密码" # 单账号
// export dxxwhd="账号1&密码1@账号2&密码2" # 多账号用@分隔
// export dxxwhd="账号1&密码1
// 账号2&密码2" # 多账号也可用换行分隔
// export dxxwhd="账号&密码&支付宝账号&姓名&socks5://127.0.0.1:7890" # 可选账号级 SOCKS5 代理
//
// 账号字段:账号&密码&支付宝账号&姓名&socks5代理
// - 支付宝账号/姓名/代理均可选;如果只想配置代理但不配置支付宝,可写:账号&密码&&&socks5://host:port
// - 代理支持 socks5:// / socks5h://,可带认证:socks5h://user:pass@host:port
// - 该账号配置代理后,登录、积分、文章、微剧/兑吧等所有 axios 请求均走该代理;未配置则使用本地网络
//
// 脚本配置(在脚本中修改):
// ENABLE_NOTIFY = true # 开启通知(默认false,需要青龙面板环境)
// ENABLE_CONCURRENT = true # 开启并发模式(默认false,多账号同时执行)
//
// ============================================
let dxxwhd = (process.env.dxxwhd) || "";
let dxxwhdArr = [];
let msg = '';
// ========== 配置项 ==========
// 通知配置 - 设置为true开启通知
const ENABLE_NOTIFY = false; // 默认关闭,需要时改为true
// 并发模式 - 设置为true开启多账号并发执行
const ENABLE_CONCURRENT = false; // 默认关闭,需要时改为true
// Token缓存文件路径
const TOKEN_CACHE_FILE = path.join(__dirname, 'dxxw_token_cache.json');
const maxReadCount = 15; // 每天最多阅读篇数
const maxLikeCount = 8; // 每天最多点赞篇数
const maxShareCount = 8; // 每天最多分享次数
// 微剧打卡签到(兑吧活动页)
const ENABLE_WEIJU_SIGN = true; // false可关闭微剧签到
const WEIJU_SIGN_OPERATING_ID = (process.env.dxxw_weiju_sign_id || '327411516979156').trim();
const WEIJU_WEB_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148';
const AXIOS_COMMON_OPTS = {
timeout: 15000,
maxBodyLength: Infinity,
maxContentLength: Infinity
};
// ========== 账号级 SOCKS5 代理 ==========
const accountContext = new AsyncLocalStorage();
const socksAgentCache = new Map();
const axiosRequestRaw = axios.request.bind(axios);
function normalizeSocksProxy(proxyUrl) {
const raw = String(proxyUrl || '').trim();
if (!raw) return '';
if (/^socks5h?:\/\//i.test(raw)) return raw;
// 兼容只填 host:port 的写法,默认按 socks5:// 处理。
if (/^[^\s:/]+:\d+$/i.test(raw)) return `socks5://${raw}`;
throw new Error(`SOCKS5代理格式不正确: ${raw}`);
}
function maskProxyUrl(proxyUrl) {
if (!proxyUrl) return '未配置';
try {
const u = new URL(proxyUrl);
if (u.username || u.password) {
u.username = '***';
u.password = '***';
}
return u.toString();
} catch (_) {
return String(proxyUrl).replace(/:\/\/([^:@/]+):([^@/]+)@/, '://***:***@');
}
}
function getSocksAgent(proxyUrl) {
const normalized = normalizeSocksProxy(proxyUrl);
if (!normalized) return null;
if (!SocksProxyAgent) {
throw new Error('已配置SOCKS5代理,但缺少依赖 socks-proxy-agent,请先执行: npm install');
}
if (!socksAgentCache.has(normalized)) {
socksAgentCache.set(normalized, new SocksProxyAgent(normalized));
}
return socksAgentCache.get(normalized);
}
function applyCurrentAccountProxy(config = {}) {
const ctx = accountContext.getStore();
const proxyUrl = ctx && ctx.socksProxy;
if (!proxyUrl) return config;
const agent = getSocksAgent(proxyUrl);
return {
...config,
proxy: false,
httpAgent: config.httpAgent || agent,
httpsAgent: config.httpsAgent || agent
};
}
axios.request = function patchedAxiosRequest(config) {
return axiosRequestRaw(applyCurrentAccountProxy(config || {}));
};
axios.interceptors.request.use(config => applyCurrentAccountProxy(config || {}));
function runWithAccountProxy(socksProxy, fn) {
const normalized = normalizeSocksProxy(socksProxy);
return accountContext.run({ socksProxy: normalized }, fn);
}
function addNotifyStr(str, is_log = true) {
if (is_log) console.log(str);
msg += `${str}\n`;
}
async function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const COMMON_UA = 'DXXW/5.10.2 (iPhone; iOS 26.1; Scale/3.00)';
// ========== 通知功能 ==========
// 发送通知
async function sendNotify(title, content) {
if (!ENABLE_NOTIFY) {
return;
}
console.log(`\n📢 通知: ${title}\n${content}`);
// 如果有青龙面板的通知环境变量,使用青龙通知
try {
// 尝试加载青龙面板的通知模块
const notify = require('./sendNotify');
await notify.sendNotify(title, content);
} catch (e) {
// 如果没有青龙面板,只在控制台显示
console.log(`(通知功能需要青龙面板环境)`);
}
}
// ========== Token缓存管理 ==========
// 读取缓存的token
function loadTokenCache() {
try {
if (fs.existsSync(TOKEN_CACHE_FILE)) {
const data = fs.readFileSync(TOKEN_CACHE_FILE, 'utf8');
return JSON.parse(data);
}
} catch (e) {
console.log(`读取token缓存失败: ${e.message}`);
}
return {};
}
// 保存token到缓存(同时缓存 alipay / realname,便于下次抽奖直接拿到提现信息)
function saveTokenCache(username, tokenData, extra = {}) {
try {
let cache = loadTokenCache();
const old = cache[username] || {};
cache[username] = {
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
expires_in: tokenData.expires_in,
token_type: tokenData.token_type,
scope: tokenData.scope,
saved_time: Date.now(),
// 保留之前缓存里的 alipay/realname;新值优先
alipay: extra.alipay || old.alipay || '',
realname: extra.realname || old.realname || ''
};
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), 'utf8');
console.log(`✅ Token已缓存到文件`);
} catch (e) {
console.log(`保存token缓存失败: ${e.message}`);
}
}
// 把 alipay / realname 单独写回缓存(环境变量里有就更新,没有就保留旧值)
function updateAlipayCache(username, alipay, realname) {
if (!alipay && !realname) return;
try {
let cache = loadTokenCache();
if (!cache[username]) return;
if (alipay) cache[username].alipay = alipay;
if (realname) cache[username].realname = realname;
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), 'utf8');
} catch (e) { /* ignore */ }
}
// 从缓存读取支付宝信息(环境变量没填时回退用这个)
function getCachedAlipay(username) {
const cache = loadTokenCache();
if (cache[username]) {
return { alipay: cache[username].alipay || '', realname: cache[username].realname || '' };
}
return { alipay: '', realname: '' };
}
// 获取缓存的token
function getCachedToken(username) {
const cache = loadTokenCache();
if (cache[username]) {
console.log(`📦 找到缓存的token,尝试使用...`);
return cache[username].access_token;
}
return null;
}
// 验证token是否有效
async function validateToken(token) {
try {
const options = {
method: 'GET',
url: 'https://integration.hntv.tv/integration/user/integration/info/',
headers: {
'Host': 'integration.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'Content-Type': 'application/json',
'Accept': '*/*, application/json'
}
};
const res = await axios.request(options);
if (res.data && res.data.code === 0) {
console.log(`✅ Token验证成功,可以使用`);
return true;
} else {
console.log(`❌ Token验证失败: ${JSON.stringify(res.data)}`);
return false;
}
} catch (e) {
console.log(`❌ Token验证异常: ${e.response ? e.response.status : e.message}`);
return false;
}
}
// ========== 登录相关 ==========
async function login(username, password) {
try {
console.log(`🔐 开始登录账号: ${username}`);
const encodedData = Buffer.from(password).toString('base64');
const pwd1 = Buffer.from(`em${encodedData}`).toString('base64').replace(/=/g, '%3D');
const options = {
method: 'POST',
url: 'https://pubmod.hntv.tv/mobile/uaa-app/oauth/token',
headers: {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'pubmod.hntv.tv',
'User-Agent': COMMON_UA,
'tenant_id': '1',
'Accept-Language': 'zh-Hans-CN;q=1',
'Referer': 'https://pubmod.hntv.tv/app'
},
data: `grant_type=password&password=${pwd1}&username=%7B%22authenticationType%22%3A%22mobile%22%2C%22principal%22%3A%22${username}%22%7D`
};
const res = await axios.request(options);
if (res.data && res.data.scope === 'read') {
console.log(`✅ 登录成功!`);
return res.data;
} else {
addNotifyStr(`[登录失败]: ${JSON.stringify(res.data)}`);
return null;
}
} catch (e) {
addNotifyStr(`[登录异常]: ${e.response ? JSON.stringify(e.response.data) : e.message}`);
return null;
}
}
// 获取有效的token(优先使用缓存,失效则重新登录)
async function getValidToken(username, password, extra = {}) {
// 0. 把这次环境变量里读到的 alipay/realname 同步到缓存(哪怕缓存 token 还能用也写一次,方便后续读取)
updateAlipayCache(username, extra.alipay, extra.realname);
// 1. 尝试使用缓存的token
let cachedToken = getCachedToken(username);
if (cachedToken) {
const isValid = await validateToken(cachedToken);
if (isValid) {
addNotifyStr(`✅ 使用缓存Token成功`);
return cachedToken;
} else {
addNotifyStr(`⚠️ 缓存Token已失效,重新登录...`);
}
} else {
console.log(`📭 未找到缓存Token,需要登录`);
}
// 2. Token失效或不存在,重新登录
const loginData = await login(username, password);
if (!loginData) {
return null;
}
// 3. 保存新token到缓存(连带 alipay/realname 一起)
saveTokenCache(username, loginData, extra);
addNotifyStr(`✅ 新Token已保存到缓存`);
return loginData.access_token;
}
// ========== 业务功能 ==========
async function getPointsInfo(token, isStart = true) {
try {
const options = {
method: 'GET',
url: 'https://integration.hntv.tv/integration/user/integration/info/',
headers: {
'Host': 'integration.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'Content-Type': 'application/json',
'Accept': '*/*, application/json'
}
};
const res = await axios.request(options);
if (res.data && res.data.code === 0) {
const result = res.data.result;
const haveScore = result.haveScore || 0;
const allScore = result.allScore || 0;
const usedScore = result.usedScore || 0;
if (isStart) {
addNotifyStr(`️ 账号积分: 当前积分[${haveScore}] 总获得[${allScore}] 已使用[${usedScore}]`);
} else {
addNotifyStr(`️ 账号积分: 当前积分[${haveScore}]`);
}
return result;
} else {
console.log(`查询积分失败: ${JSON.stringify(res.data)}`);
return null;
}
} catch (e) {
console.log(`查询积分异常: ${e.message}`);
return null;
}
}
// 获取任务完成情况
async function getTaskStatus(token) {
try {
const options = {
method: 'GET',
url: 'https://integration.hntv.tv/integration/user/integration/info/',
headers: {
'Host': 'integration.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'Content-Type': 'application/json',
'Accept': '*/*, application/json'
}
};
const res = await axios.request(options);
if (res.data && res.data.code === 0 && res.data.result.userIntegrationDailyAbtainDTOList) {
const eventList = res.data.result.userIntegrationDailyAbtainDTOList;
const taskStatus = {};
eventList.forEach(event => {
taskStatus[event.eventCode] = {
name: event.eventName,
completed: event.haveFinishedTimes || 0,
total: event.eventLimit || 0,
remaining: (event.eventLimit || 0) - (event.haveFinishedTimes || 0)
};
});
console.log(`\n📋 今日任务完成情况:`);
Object.keys(taskStatus).forEach(code => {
const task = taskStatus[code];
console.log(` [${code}] ${task.name}: ${task.completed}/${task.total} (剩余${task.remaining}次)`);
});
return taskStatus;
}
return {};
} catch (e) {
console.log(`获取任务状态失败: ${e.message}`);
return {};
}
}
async function doTask(token, eventCode, articleId, taskName) {
try {
const timestamp = Date.now();
const options = {
method: 'POST',
url: `https://integration.hntv.tv/integration/user/integration/add?t=${timestamp}&v=5.10.2`,
headers: {
'Host': 'integration.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'Content-Type': 'application/json'
},
data: {
articleId: articleId.toString(),
eventCode: eventCode.toString()
}
};
const res = await axios.request(options);
if (res.data && (res.data.code === 0 || res.data.success)) {
addNotifyStr(`✅ [${taskName}] 获得积分: ${res.data.result ? res.data.result : '成功'}`);
return true;
} else {
addNotifyStr(`❌ [${taskName}] 失败: ${res.data.msg || res.data.message || JSON.stringify(res.data)}`);
return false;
}
} catch (e) {
addNotifyStr(`❌ [${taskName}] 异常: ${e.response ? JSON.stringify(e.response.data) : e.message}`);
return false;
}
}
// 获取文章列表
async function getArticleList(token, pageNo = 1) {
try {
const timestamp = Date.now();
const options = {
method: 'GET',
url: `https://pubmod.hntv.tv/mobile/cms/articles?t=${timestamp}&v=5.10.2&businessId=DXXWAPP_LY&channelId=1691274444750712834&commentVersion=v2&pageNo=${pageNo}&pageSize=15&tenantId=1&version=v3`,
headers: {
'Host': 'pubmod.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'tenant_id': '1'
}
};
const res = await axios.request(options);
if (res.data && res.data.code === 0) {
// result是对象,包含content数组
if (res.data.result && res.data.result.content) {
console.log(`✅ 获取到 ${res.data.result.content.length} 篇文章`);
return res.data.result.content;
}
return [];
}
return [];
} catch (e) {
console.log(`获取文章列表失败: ${e.message}`);
return [];
}
}
// 阅读统计上报
async function statArticle(token, articleId) {
try {
const timestamp = Date.now();
await axios.get(`https://pubmod.hntv.tv/mobile/newcms/statistics/${articleId}?t=${timestamp}&v=5.10.2`, {
headers: {
'Host': 'pubmod.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'tenant_id': '1'
}
});
} catch (e) { }
}
// 点赞/取消点赞操作
// type: DZ=点赞, QXC=取消点赞 (或者再发一次DZ也会取消)
async function doOperation(token, articleId, type) {
try {
const timestamp = Date.now();
const options = {
method: 'POST',
url: `https://pubmod.hntv.tv/dx-bridge/operation?t=${timestamp}&v=5.10.2`,
headers: {
'Host': 'pubmod.hntv.tv',
'Authorization': `Bearer ${token}`,
'User-Agent': COMMON_UA,
'tenant_id': '1',
'Content-Type': 'application/json'
},
data: {
applicationId: "1390195608019869697",
objectId: articleId.toString(),
objectInfo: "{}",
operationType: type,
objectType: "WZ",
spm: "大象$##$主编精选$##$精选流$##$详情页"
}
};
const res = await axios.request(options);
if (res.data && (res.data.code === 0 || res.data.code === -5)) {
return true;
} else {
console.log(`[操作 ${type}] 失败文章: ${articleId} 原因: ${res.data.msg || ''}`);
return false;
}
} catch (e) {
return false;
}
}
// ========== 微剧打卡签到(兑吧) ==========
function escapeRegExp(str) {
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function parseDuibaTokenFromScript(tokenScript, tokenKey = '67666cft') {
// 抓包前端真实逻辑:eval(res.token) 后取 window[key]
const reg = new RegExp(`window\\['${escapeRegExp(tokenKey)}'\\]\\s*=\\s*\"([a-zA-Z0-9]+)\"`);
const direct = tokenScript.match(reg);
if (direct) {
return direct[1];
}
try {
const ctx = {
window: {},
Math,
Date,
String,
Number,
console: { log: () => {} }
};
ctx.window = ctx;
ctx.eval = (code) => vm.runInContext(code, ctx, { timeout: 2000 });
vm.createContext(ctx);
vm.runInContext(tokenScript, ctx, { timeout: 5000 });
const tk = ctx[tokenKey];
if (typeof tk === 'string' && /^[a-z0-9]{5,32}$/i.test(tk)) {
return tk;
}
return null;
} catch (e) {
return null;
}
}
function parseDuibaTokenKeyFromPageHtml(pageHtml) {
try {
// 取包含“获取token”的script块
const scripts = [];
const reg = /<script[^>]*>([\s\S]*?)<\/script>/ig;
let m;
while ((m = reg.exec(pageHtml)) !== null) {
scripts.push(m[1] || '');
}
// 优先含特定字样的脚本;否则用最大那块(兑吧把代码塞在最大的混淆 script 里)
let tokenBlock = scripts.find(s => s.includes('获取token') || s.includes('ctoken/getToken') || s.includes('__8g8Q0kX1'));
if (!tokenBlock) tokenBlock = scripts.slice().sort((a, b) => b.length - a.length)[0] || '';
if (!tokenBlock) return null;
const captured = [];
const ctx = {
window: {},
Math,
Date,
String,
Number,
document: { getElementById: () => null, querySelector: () => null },
console: { log: () => {} }
};
ctx.window = ctx;
ctx.eval = (code) => {
if (typeof code === 'string') captured.push(code);
return undefined;
};
vm.createContext(ctx);
try { vm.runInContext(tokenBlock, ctx, { timeout: 8000 }); } catch (_) {}
// 在所有被 eval 的字符串里找 var key='xxxx'
for (const code of captured) {
const m = code.match(/var\s+key\s*=\s*['\"]([a-zA-Z0-9_]+)['\"]/);
if (m) return m[1];
}
// 兜底:直接在 block 文本里找
const direct = tokenBlock.match(/var\s+key\s*=\s*['\"]([a-zA-Z0-9_]+)['\"]/);
if (direct) return direct[1];
return null;
} catch (e) {
return null;
}
}
async function getWeijuSignPageHtml(cookie, signOperatingId) {
const res = await axios.get('https://90580-activity.dexfu.cn/sign/component/page', {
params: {
signOperatingId,
from: 'login',
spm: '90580.1.1.1'
},
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Referer': 'https://90580-activity.dexfu.cn/',
'Cookie': cookie
},
timeout: 15000
});
return (typeof res.data === 'string') ? res.data : '';
}
function mergeSetCookie(cookieMap, setCookieArr = []) {
const map = { ...cookieMap };
for (const item of setCookieArr) {
const kv = item.split(';')[0] || '';
const idx = kv.indexOf('=');
if (idx <= 0) continue;
const k = kv.slice(0, idx).trim();
const v = kv.slice(idx + 1).trim();
if (!k) continue;
if (v === '') {
delete map[k];
} else {
map[k] = v;
}
}
return map;
}
function cookieMapToString(cookieMap) {
return Object.entries(cookieMap)
.filter(([k, v]) => k && v !== undefined && v !== null && v !== '')
.map(([k, v]) => `${k}=${v}`)
.join('; ');
}
async function getWeijuSignCookie(token, signOperatingId) {
const dbredirect = `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}`;
const autoLoginUrlApi = `https://integration.hntv.tv/integration/p/duiba/autoLoginUrl?dbredirect=${encodeURIComponent(dbredirect)}`;
const res = await axios.get(autoLoginUrlApi, {
headers: {
'Authorization': `Bearer ${token}`,
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://static.hntv.tv',
'Referer': 'https://static.hntv.tv/'
},
timeout: 15000
});
if (!(res.data && (res.data.code === 0 || res.data.success) && res.data.result)) {
throw new Error(`获取兑吧自动登录地址失败: ${JSON.stringify(res.data)}`);
}
const autoLoginUrl = res.data.result;
// 需要把整条重定向链上的cookie合并起来,不能只取第一跳
const cookieMap = {};
const step1 = await axios.get(autoLoginUrl, {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
},
maxRedirects: 0,
validateStatus: status => status >= 200 && status < 400,
timeout: 15000
});
let merged = mergeSetCookie(cookieMap, step1.headers['set-cookie'] || []);
let nextUrl = step1.headers.location || '';
// 最多再追2跳,持续合并cookie
for (let i = 0; i < 2 && nextUrl; i++) {
const step = await axios.get(nextUrl, {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Cookie': cookieMapToString(merged)
},
maxRedirects: 0,
validateStatus: status => status >= 200 && status < 400,
timeout: 15000
});
merged = mergeSetCookie(merged, step.headers['set-cookie'] || []);
nextUrl = step.headers.location || '';
}
const cookie = cookieMapToString(merged);
if (!cookie) {
throw new Error('未拿到兑吧cookie(合并后为空)');
}
return cookie;
}
async function getWeijuSignIndex(cookie, signOperatingId) {
const res = await axios.get('https://90580-activity.dexfu.cn/sign/component/index', {
params: {
signOperatingId,
preview: 'false',
_: Date.now()
},
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'Referer': `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}&from=login&spm=90580.1.1.1`,
'Cookie': cookie
},
timeout: 15000
});
if (!(res.data && res.data.success && res.data.data)) {
throw new Error(`查询微剧签到状态失败: ${JSON.stringify(res.data)}`);
}
return res.data.data;
}
async function getWeijuCtoken(cookie, signOperatingId) {
// 第一步:从活动页脚本解析当前期真实tokenKey(不是固定67666cft
const pageHtml = await getWeijuSignPageHtml(cookie, signOperatingId);
const tokenKey = parseDuibaTokenKeyFromPageHtml(pageHtml) || '67666cft';
const res = await axios.post(
'https://90580-activity.dexfu.cn/chw/ctoken/getToken',
`timestamp=${Date.now()}`,
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': '*/*',
'Origin': 'https://90580-activity.dexfu.cn',
'Referer': `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}&from=login&spm=90580.1.1.1`,
'Cookie': cookie
},
timeout: 15000
}
);
if (!(res.data && res.data.success && res.data.token)) {
throw new Error(`获取微剧ctoken失败: ${JSON.stringify(res.data)}`);
}
const tk = parseDuibaTokenFromScript(res.data.token, tokenKey);
if (!tk) {
throw new Error(`解析ctoken脚本失败:未拿到 window[${tokenKey}]`);
}
return { token: tk, tokenKey };
}
// 9天抽奖触发:POST /sign/component/doJoin
// 返回:{ success:true, data:{ times, orderNum } }
async function doWeijuJoin(cookie, signOperatingId, ctoken) {
const body = new URLSearchParams({
signOperatingId: String(signOperatingId),
token: ctoken
}).toString();
const res = await axios.post(
`https://90580-activity.dexfu.cn/sign/component/doJoin?_=${Date.now()}`,
body,
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://90580-activity.dexfu.cn',
'Referer': `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}&from=login&spm=90580.1.1.1`,
'Cookie': cookie
},
timeout: 15000
}
);
return (res && typeof res.data !== 'undefined') ? res.data : null;
}
// 用 doJoin 返回的 orderNum 反查中奖记录,从 lottery.link 里抠出 recordId
async function getLotteryRecordId(cookie, signOperatingId, orderNum) {
const res = await axios.get(
`https://90580-activity.dexfu.cn/plugin/getOrderStatus`,
{
params: { orderId: orderNum, _: Date.now() },
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'Referer': `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}&from=login&spm=90580.1.1.1`,
'Cookie': cookie
},
timeout: 15000
}
);
const data = res && res.data;
if (!(data && data.success && data.lottery && data.lottery.link)) {
throw new Error(`查询中奖记录失败: ${JSON.stringify(data)}`);
}
const m = String(data.lottery.link).match(/recordId=(\d+)/);
if (!m) throw new Error(`lottery.link 里没找到 recordId: ${data.lottery.link}`);
return {
recordId: m[1],
prizeTitle: data.lottery.title || '',
prizeType: data.lottery.type || ''
};
}
// 模拟用户从"中奖记录"页点进领奖页的行为:访问 /crecord/record 并打 click 埋点
async function warmupBeforeTakePrize(cookie) {
// 1) 访问中奖记录页
await axios.get('https://90580-activity.dexfu.cn/crecord/record?dbnewopen', {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Cookie': cookie
},
timeout: 15000,
validateStatus: () => true
});
// 2) 拉一次 record 列表(兑吧前端会调)
await axios.get(`https://90580-activity.dexfu.cn/crecord/getrecord?page=1&_=${Date.now()}`, {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://90580-activity.dexfu.cn/crecord/record?dbnewopen',
'Cookie': cookie
},
timeout: 15000,
validateStatus: () => true
});
// 3) 打 click 埋点(这步可能是服务端校验"用户点过提现"的关键)
await axios.get('https://90580-activity.dexfu.cn/log/click', {
params: {
dpm: '90580.26.0.1',
domain: '//90580-activity.dexfu.cn',
dcm: '102.214709196407064.0.0',
appId: '90580'
},
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': '*/*',
'Referer': 'https://90580-activity.dexfu.cn/crecord/record?dbnewopen',
'Cookie': cookie
},
timeout: 15000,
validateStatus: () => true
});
}
// 从 takePrize 页面 HTML 解析出 ctoken 用的 tokenKey(页面里有 var key="xxxxx"
async function getTakePrizePageTokenKey(cookie, recordId) {
const r = await axios.get(
`https://90580-activity.dexfu.cn/activity/takePrizeNew?recordId=${recordId}&dpm=90580.26.0.1&dcm=102.214709196407064.0.0&dbnewopen`,
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Cookie': cookie
},
timeout: 15000
}
);
const html = (typeof r.data === 'string') ? r.data : '';
// 兑吧标准模板:每个 takePrize 页都会有一段含 "获取token" / "ctoken/getToken" 的 script
// 里头会 eval 一个"var key='xxxxx';..."字符串
return parseDuibaTokenKeyFromPageHtml(html);
}
// 提现页专用 ctokenPOST /ctoken/getToken.do(注意路径跟签到那个不一样)
async function getTakePrizeCtoken(cookie, recordId) {
// 1) 先去拿到本期 tokenKey(拿不到就用枚举 fallback
let tokenKey = null;
try { tokenKey = await getTakePrizePageTokenKey(cookie, recordId); } catch (e) { /* fallthrough */ }
const res = await axios.post(
`https://90580-activity.dexfu.cn/ctoken/getToken.do`,
'',
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
'Origin': 'https://90580-activity.dexfu.cn',
'Referer': `https://90580-activity.dexfu.cn/activity/takePrizeNew?recordId=${recordId}&dpm=90580.26.0.1&dcm=102.214709196407064.0.0&dbnewopen`,
'Cookie': cookie
},
timeout: 15000
}
);
if (!(res.data && res.data.success && res.data.token)) {
throw new Error(`获取领奖ctoken失败: ${JSON.stringify(res.data)}`);
}
// 2) 优先按精确 tokenKey 解析
if (tokenKey) {
const v = parseDuibaTokenFromScript(res.data.token, tokenKey);
if (v) return v;
}
// 3) fallback:跑完后枚举像 token 的字符串
const v2 = evalAndExtractDuibaToken(res.data.token);
if (v2) return v2;
throw new Error('解析领奖ctoken失败');
}
// 通用:把兑吧返回的 token 脚本跑一遍,扫所有看起来像 token 的字符串值(5~32位字母数字)
function evalAndExtractDuibaToken(tokenScript) {
try {
const ctx = {
window: {},
Math, Date, String, Number,
console: { log: () => {} }
};
ctx.window = ctx;
ctx.eval = (code) => vm.runInContext(code, ctx, { timeout: 2000 });
vm.createContext(ctx);
vm.runInContext(tokenScript, ctx, { timeout: 8000 });
// 优先 window 上的字符串属性,跳过我们自己注入的内置对象
const skip = new Set(['window', 'Math', 'Date', 'String', 'Number', 'console', 'eval']);
const re = /^[a-z0-9]{5,32}$/i;
for (const k of Object.keys(ctx)) {
if (skip.has(k)) continue;
const v = ctx[k];
if (typeof v === 'string' && re.test(v)) return v;
}
return null;
} catch (e) {
return null;
}
}
// 提现:POST /activity/doTakePrize
async function doTakePrize(cookie, recordId, alipay, realname, ctoken) {
const body = new URLSearchParams({
alipay,
realname,
recordId: String(recordId),
token: ctoken
}).toString();
const res = await axios.post(
`https://90580-activity.dexfu.cn/activity/doTakePrize`,
body,
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Origin': 'https://90580-activity.dexfu.cn',
'Referer': `https://90580-activity.dexfu.cn/activity/takePrizeNew?recordId=${recordId}&dpm=90580.26.0.1&dcm=102.214709196407064.0.0&dbnewopen`,
'Cookie': cookie
},
timeout: 15000
}
);
return res && res.data;
}
async function doWeijuSign(cookie, signOperatingId, ctoken) {
const body = new URLSearchParams({
signOperatingId: String(signOperatingId),
token: ctoken
}).toString();
const res = await axios.post(
`https://90580-activity.dexfu.cn/sign/component/doSign?_=${Date.now()}`,
body,
{
headers: {
'User-Agent': WEIJU_WEB_UA,
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json, text/plain, */*',
'Origin': 'https://90580-activity.dexfu.cn',
'Referer': `https://90580-activity.dexfu.cn/sign/component/page?signOperatingId=${signOperatingId}&from=login&spm=90580.1.1.1`,
'Cookie': cookie
},
timeout: 15000
}
);
return (res && typeof res.data !== 'undefined') ? res.data : null;
}
// 抽奖+提现:在签到完成后调用。alipay/realname 任一为空就只抽奖、不提现;都为空则连抽奖都不做
async function doWeijuLotteryAndTakePrize(cookie, signOperatingId, indexData, alipay, realname) {
// index.times 表示当前可抽奖次数;为 0 直接跳过
const times = (indexData && typeof indexData.times === 'number') ? indexData.times : 0;
// 不管有没有新抽奖机会,先扫一遍历史"待领奖"的奖品试着领(救之前失败的记录)
try { await scanAndClaimPendingPrizes(cookie, alipay, realname); } catch (_) {}
if (times <= 0) {
addNotifyStr(`ℹ️ 微剧抽奖次数: 0,本次无新抽奖资格(不到9天)`);
return;
}
if (!alipay || !realname) {
addNotifyStr(`⏭️ 抽奖剩余 ${times} 次,但未配置支付宝账号/姓名 → 跳过抽奖`);
return;
}
addNotifyStr(`🎁 检测到 ${times} 次抽奖资格,开始抽奖...`);
// 1) 取 ctoken(同签到那套 chw/ctoken/getToken
const ctokenInfo = await getWeijuCtoken(cookie, signOperatingId);
let ctoken = ctokenInfo.token;
// 2) doJoin 抽奖
let joinRes;
try {
joinRes = await doWeijuJoin(cookie, signOperatingId, ctoken);
} catch (e) {
addNotifyStr(`⚠️ 抽奖请求异常,重新拉ctoken重试: ${e.message}`);
ctoken = (await getWeijuCtoken(cookie, signOperatingId)).token;
joinRes = await doWeijuJoin(cookie, signOperatingId, ctoken);
}
if (!(joinRes && joinRes.success && joinRes.data && joinRes.data.orderNum)) {
addNotifyStr(`❌ 抽奖失败: ${JSON.stringify(joinRes)}`);
return;
}
const orderNum = joinRes.data.orderNum;
addNotifyStr(`✅ 抽奖完成,orderNum=${orderNum}`);
// 3) 查中奖记录拿 recordId(兑吧延迟落库,最多重试3次)
let recordInfo;
for (let i = 0; i < 3; i++) {
try {
recordInfo = await getLotteryRecordId(cookie, signOperatingId, orderNum);
if (recordInfo && recordInfo.recordId) break;
} catch (e) {
if (i === 2) {
addNotifyStr(`❌ 查中奖记录失败: ${e.message}`);
return;
}
}
await wait(1500);
}
addNotifyStr(`🎯 中奖: ${recordInfo.prizeTitle}${recordInfo.prizeType}),recordId=${recordInfo.recordId}`);
// 4) 兑吧服务端要求:奖品要"在中奖记录里能查到"才能领(doJoin 后入库有延迟,最久能到几十秒)
// 轮询 /crecord/getrecord 直到看到这个 recordId 出现且状态可领,再去领
const ok = await waitUntilRecordReady(cookie, recordInfo.recordId, 30);
if (!ok) {
addNotifyStr(`⏭️ recordId=${recordInfo.recordId} 30秒内未在中奖记录里就绪,下次再试`);
return;
}
// 5) 提现(含 ctoken + doTakePrize,最多重试 3 次)
const finalRes = await tryClaimPrize(cookie, recordInfo.recordId, alipay, realname);
if (finalRes && finalRes.success) {
addNotifyStr(`💰 提现成功: ${finalRes.message || '领奖成功'}recordId=${recordInfo.recordId}, alipay=${alipay}`);
} else {
addNotifyStr(`❌ 提现失败: ${JSON.stringify(finalRes)}`);
}
}
// 扫描所有"待领奖"的支付宝奖品并尝试领走(用于补救历史未领的)
async function scanAndClaimPendingPrizes(cookie, alipay, realname) {
if (!alipay || !realname) return;
let pageRes;
try {
pageRes = await axios.get(`https://90580-activity.dexfu.cn/crecord/getrecord?page=1&_=${Date.now()}`, {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://90580-activity.dexfu.cn/crecord/record?dbnewopen',
'Cookie': cookie
},
timeout: 15000
});
} catch (e) { return; }
const records = (pageRes.data && pageRes.data.records) || [];
const pending = records.filter(r => /待领奖/.test(String(r.statusText || '')) && /takePrizeNew/.test(String(r.url || '')));
if (!pending.length) return;
addNotifyStr(`🔎 发现 ${pending.length} 条待领奖品,尝试自动领取...`);
for (const r of pending) {
const m = String(r.url).match(/recordId=(\d+)/);
if (!m) continue;
const recId = m[1];
const res = await tryClaimPrize(cookie, recId, alipay, realname);
if (res && res.success) {
addNotifyStr(`💰 历史奖品领取成功: ${r.title}recordId=${recId}`);
} else {
addNotifyStr(`⏭️ recordId=${recId} 暂不可领: ${(res && res.message) || '未知'}`);
}
await wait(2000);
}
}
// 拉 /crecord/getrecord 看这条 recordId 是否已经入库且状态待领;最多 maxSec 秒
async function waitUntilRecordReady(cookie, recordId, maxSec = 30) {
const deadline = Date.now() + maxSec * 1000;
let tried = 0;
while (Date.now() < deadline) {
tried++;
try {
// warmup once at the start (record page + log/click 埋点)
if (tried === 1) {
try { await warmupBeforeTakePrize(cookie); } catch (_) {}
}
const r = await axios.get(`https://90580-activity.dexfu.cn/crecord/getrecord?page=1&_=${Date.now()}`, {
headers: {
'User-Agent': WEIJU_WEB_UA,
'Accept': 'application/json, text/plain, */*',
'X-Requested-With': 'XMLHttpRequest',
'Referer': 'https://90580-activity.dexfu.cn/crecord/record?dbnewopen',
'Cookie': cookie
},
timeout: 15000
});
const records = (r.data && r.data.records) || [];
const hit = records.find(it => String(it.url || '').includes(`recordId=${recordId}`));
if (hit) return true;
} catch (_) {}
await wait(2000);
}
return false;
}
// 真正提现:拿 ctoken → POST /activity/doTakePrize;遇到 (24) 重试
async function tryClaimPrize(cookie, recordId, alipay, realname) {
let lastRes = null;
for (let i = 0; i < 3; i++) {
let prizeCtoken;
try {
prizeCtoken = await getTakePrizeCtoken(cookie, recordId);
} catch (e) {
addNotifyStr(`⚠️ 领奖ctoken获取失败 (第${i + 1}次): ${e.message}`);
await wait(3000);
continue;
}
const res = await doTakePrize(cookie, recordId, alipay, realname, prizeCtoken);
lastRes = res;
if (res && res.success) return res;
// 失败:log 一次,等会儿重试
addNotifyStr(`⚠️ 提现第 ${i + 1} 次失败: ${JSON.stringify(res)} (token=${prizeCtoken})`);
await wait(5000);
}
return lastRes;
}
async function doWeijuSignTask(token, alipay = '', realname = '') {
if (!ENABLE_WEIJU_SIGN) {
console.log('⏭️ 微剧打卡签到已关闭');
return;
}
const signOperatingId = WEIJU_SIGN_OPERATING_ID;
try {
addNotifyStr(`\n🎬 开始微剧打卡签到(9天抽奖)...`);
const cookie = await getWeijuSignCookie(token, signOperatingId);
const before = await getWeijuSignIndex(cookie, signOperatingId);
const beforeTotal = before.totalCount || 0;
const beforeTarget = before.targetCount || 0;
addNotifyStr(`️ 微剧打卡进度: ${beforeTotal}/${beforeTarget},标题: ${before.title || '未知活动'}`);
// signResult=true 时表示今天已签:仍尝试一次抽奖(可能9天已满还没领)
if (before.signResult === true) {
addNotifyStr(`⏭️ 微剧签到今日已完成`);
try {
await doWeijuLotteryAndTakePrize(cookie, signOperatingId, before, alipay, realname);
} catch (e) {
addNotifyStr(`❌ 抽奖/提现流程异常: ${e.message}`);
}
return;
}
const ctokenInfo = await getWeijuCtoken(cookie, signOperatingId);
const ctoken = ctokenInfo.token;
const tokenKey = ctokenInfo.tokenKey;
addNotifyStr(`️ 微剧ctoken: ${ctoken} (key:${tokenKey})`);
let signRes = null;
let usedToken = ctoken;
let usedKey = tokenKey;
try {
signRes = await doWeijuSign(cookie, signOperatingId, ctoken);
} catch (e) {
addNotifyStr(`⚠️ 微剧签到请求异常(token:${ctoken}): ${e.message}`);
signRes = null;
}
// 如果返回为空,按抓包实际流程:重新拉一次ctoken再试一次
if (!signRes || typeof signRes !== 'object') {
addNotifyStr(`⚠️ 微剧签到首次返回空结果,重新获取ctoken重试一次`);
const ctokenInfo2 = await getWeijuCtoken(cookie, signOperatingId);
const ctoken2 = ctokenInfo2.token;
usedToken = ctoken2;
usedKey = ctokenInfo2.tokenKey;
try {
signRes = await doWeijuSign(cookie, signOperatingId, ctoken2);
} catch (e) {
addNotifyStr(`⚠️ 微剧签到重试异常(token:${ctoken2}): ${e.message}`);
signRes = null;
}
}
if (!(signRes && signRes.success && signRes.data)) {
addNotifyStr(`❌ 微剧签到失败: ${JSON.stringify(signRes || { msg: 'doSign返回空或非JSON对象', token: usedToken, tokenKey: usedKey })}`);
return;
}
const signResult = signRes.data.signResult;
const orderNum = signRes.data.orderNum || '';
// 100: 成功提交;可再查一次进度确认
let afterIndex = null;
if (signResult === 100 || signResult === 2 || signResult === 1) {
afterIndex = await getWeijuSignIndex(cookie, signOperatingId);
addNotifyStr(`✅ 微剧签到成功: 进度 ${afterIndex.totalCount || 0}/${afterIndex.targetCount || 0}orderNum:${orderNum || '无'}token:${usedToken || '未知'}key:${usedKey || '未知'}`);
} else {
addNotifyStr(`⚠️ 微剧签到返回异常状态: signResult=${signResult} 原始=${JSON.stringify(signRes.data)}`);
}
// 签完检查是否够9天有抽奖资格 → 抽奖+提现
try {
if (!afterIndex) afterIndex = await getWeijuSignIndex(cookie, signOperatingId);
await doWeijuLotteryAndTakePrize(cookie, signOperatingId, afterIndex, alipay, realname);
} catch (e) {
addNotifyStr(`❌ 抽奖/提现流程异常: ${e.message}`);
}
} catch (e) {
addNotifyStr(`❌ 微剧签到异常: ${e.message}`);
}
}
// ========== 单个账号任务执行 ==========
async function executeAccount(accountInfo, index) {
const username = accountInfo[0];
const password = accountInfo[1];
// 账号格式:账号&密码&支付宝账号&姓名&socks5代理(后三项可选;代理配置后该账号所有请求均走代理)
let alipay = (accountInfo[2] || '').trim();
let realname = (accountInfo[3] || '').trim();
const socksProxy = normalizeSocksProxy(accountInfo.slice(4).join('&'));
addNotifyStr(`\n==== 开始【第 ${index + 1} 个账号: ${username}】====`);
addNotifyStr(`🌐 SOCKS5代理: ${socksProxy ? maskProxyUrl(socksProxy) : '未配置,使用本地网络'}`);
return runWithAccountProxy(socksProxy, async () => {
// 使用新的token获取方法(优先缓存,失效则登录),同时把 alipay/realname 写入缓存
const token = await getValidToken(username, password, { alipay, realname });
if (!token) {
addNotifyStr(`❌ 无法获取有效Token,跳过该账号`);
return;
}
// 1. 查询初始积分和任务状态
const startInfo = await getPointsInfo(token, true);
const startScore = startInfo ? startInfo.haveScore : 0;
const taskStatus = await getTaskStatus(token);
// 1.5 微剧打卡签到 + 9天抽奖 + 提现(与主积分任务并行存在)
// 环境变量没填 alipay/realname 时回退用缓存里的(之前填过保留下来的)
if (!alipay || !realname) {
const cached = getCachedAlipay(username);
if (!alipay) alipay = cached.alipay;
if (!realname) realname = cached.realname;
}
await doWeijuSignTask(token, alipay, realname);
await wait(getRandom(1200, 2200));
// 2. 每日签到 (eventCode: 3)
if (taskStatus['3'] && taskStatus['3'].remaining > 0) {
await doTask(token, 3, "1", "每日签到");
await wait(getRandom(2000, 3000));
} else {
console.log(`⏭️ 每日签到已完成,跳过`);
}
// 3. App使用时长 (eventCode: 24)
if (taskStatus['24'] && taskStatus['24'].remaining > 0) {
await doTask(token, 24, "1", "使用时长(5min)");
await wait(getRandom(2000, 3000));
} else {
console.log(`⏭️ 使用时长已完成,跳过`);
}
// 4. 计算需要完成的任务数量
const needReadCount = taskStatus['26'] ? Math.min(taskStatus['26'].remaining, maxReadCount) : 0; // 浏览内容
const needLikeCount = taskStatus['11'] ? Math.min(taskStatus['11'].remaining, maxLikeCount) : 0; // 点赞
const needShareCount = taskStatus['9'] ? Math.min(taskStatus['9'].remaining, maxShareCount) : 0; // 分享
console.log(`\n📝 本次需要完成: 阅读[${needReadCount}] 点赞[${needLikeCount}] 分享[${needShareCount}]`);
if (needReadCount === 0 && needLikeCount === 0 && needShareCount === 0) {
console.log(`✅ 所有任务已完成,无需执行`);
await getPointsInfo(token, false);
return;
}
// 5. 获取文章列表进行阅读、点赞和分享
let articles = [];
let page = 1;
const totalNeeded = Math.max(needReadCount, needLikeCount, needShareCount);
console.log(`\n开始获取文章列表...`);
// 获取更多页面的文章,避免重复
const maxPages = 10; // 增加到10页
while (articles.length < totalNeeded * 2 && page <= maxPages) {
let list = await getArticleList(token, page);
if (list.length > 0) {
articles = articles.concat(list);
} else {
break; // 没有更多文章了
}
page++;
await wait(1000);
}
// 去重:根据文章ID去重
const uniqueArticles = [];
const seenIds = new Set();
for (let article of articles) {
if (article && article.id && !seenIds.has(article.id)) {
seenIds.add(article.id);
uniqueArticles.push(article);
}
}
articles = uniqueArticles;
console.log(`✅ 共获取 ${articles.length} 篇不重复文章`);
let readCount = 0;
let likeCount = 0;
let shareCount = 0;
for (let j = 0; j < articles.length; j++) {
if (readCount >= needReadCount && likeCount >= needLikeCount && shareCount >= needShareCount) break;
let article = articles[j];
let articleId = article.id;
let title = article.title ? article.title.substring(0, 10) + '..' : articleId;
console.log(`\n--- 处理文章 [${title}] ID:${articleId} ---`);
// 模拟阅读 (eventCode: 26 浏览内容)
if (readCount < needReadCount) {
await statArticle(token, articleId);
await wait(getRandom(5000, 8000)); // 增加等待时间,模拟真实阅读
let readRes = await doTask(token, 26, articleId, "浏览内容");
if (readRes) readCount++;
await wait(getRandom(2000, 3000)); // 额外等待
}
// 模拟分享 (eventCode: 9)
if (shareCount < needShareCount) {
await wait(getRandom(1000, 2000));
let shareRes = await doTask(token, 9, articleId, "分享文章");
if (shareRes) shareCount++;
}
// 模拟点赞并取消 (eventCode: 11)
if (likeCount < needLikeCount) {
await wait(getRandom(1000, 2000));
console.log(` -> 尝试点赞...`);
let opRes = await doOperation(token, articleId, 'DZ');
if (opRes) {
let likeRes = await doTask(token, 11, articleId, "点赞文章");
if (likeRes) likeCount++;
// 等待一会儿后取消点赞
await wait(getRandom(2000, 3000));
console.log(` -> 尝试取消点赞...`);
await doOperation(token, articleId, 'QXDZ');
}
}
}
console.log(`\n今日任务统计:阅读[${readCount}/${needReadCount}] 点赞[${likeCount}/${needLikeCount}] 分享[${shareCount}/${needShareCount}]`);
// 6. 任务结束后再次查询积分
const endInfo = await getPointsInfo(token, false);
const endScore = endInfo ? endInfo.haveScore : 0;
const earnedScore = endScore - startScore;
if (earnedScore > 0) {
addNotifyStr(`🎉 本次获得积分: +${earnedScore}`);
}
});
}
// ========== 主程序 ==========
async function start() {
if (!dxxwhd) {
console.log(`未找到环境变量 dxxwhd,请设置。格式: 账号&密码`);
return;
}
if (dxxwhd.indexOf('@') > -1) {
dxxwhdArr = dxxwhd.split('@');
} else if (dxxwhd.indexOf('\n') > -1) {
dxxwhdArr = dxxwhd.split('\n');
} else {
dxxwhdArr = [dxxwhd];
}
console.log(`\n=================== 共找到 ${dxxwhdArr.length} 个账号 ===================`);
console.log(`执行模式: ${ENABLE_CONCURRENT ? '并发模式 🚀' : '顺序模式 ⏭️'}`);
const accounts = dxxwhdArr.map(item => item.split('&'));
if (ENABLE_CONCURRENT) {
// 并发模式:所有账号同时执行
console.log(`\n🚀 开始并发执行所有账号任务...\n`);
await Promise.all(accounts.map((account, index) => executeAccount(account, index)));
} else {
// 顺序模式:一个账号执行完再执行下一个
for (let i = 0; i < accounts.length; i++) {
await executeAccount(accounts[i], i);
}
}
console.log(`\n🎉 所有账号任务执行完毕!`);
// 发送通知
if (ENABLE_NOTIFY && msg) {
await sendNotify('大象新闻积分任务', msg);
}
}
start();