/* * 农夫山泉 - 全能版 (支持青龙/圈X/Surge/Loon) * * [功能说明] * 1. 自动完成浏览、分享、视频号等任务 * 2. 自动抽奖并统计 * 3. 支持 Quantumult X 抓包自动记录账号 * 4. 支持抓包后自动推送到青龙面板 (需配置面板信息) * * [环境变量 / 账号配置] * 变量名: NFSQ_ACCOUNTS * 格式: JSON 数组 * [ * { * "uid": "xxx", * "token": "xxx", * "body": { ... }, * "headers_json": { ... } * } * ] * * [Quantumult X 配置] * * [rewrite_local] * # 抓取账号信息 * ^https:\/\/sxs-consumer\.nfsq\.com\.cn\/geement\.marketinglottery\/api\/v1\/marketinglottery url script-request-body nfsq_ql.js * * [task_local] * # 定时运行 (每天 9:00 运行) * 0 9 * * * nfsq_ql.js, tag=农夫山泉, img-url=https://raw.githubusercontent.com/Orz-3/mini/master/Color/nfsq.png, enabled=true * * [青龙面板自动推送配置] * 如果需要抓包后自动推送到青龙,请在下方代码区域修改 QL_CONFIG 变量 * 或者在本地脚本管理器的“偏好设置”中存储相对应的数据 * */ // ================= 用户配置区域 ================= // 青龙面板对接配置 (如果不需要自动推送,可忽略) // 建议在 BoxJs 或 脚本编辑器中修改,避免硬编码泄露 const QL_CONFIG = { URL: "http://chickliu.store:5735", // 例如 http://192.168.1.5:5700 CLIENT_ID: "0g9wa9_Ulcud", // 应用管理 -> 新建应用 -> Client ID CLIENT_SECRET: "FWNsG34ttRRTKbCxsa9_2hxh" // 应用管理 -> 新建应用 -> Client Secret }; const SCRIPT_NAME = "农夫山泉"; const ENV_NAME = "NFSQ_ACCOUNTS"; // ================= 运行环境兼容 ================= // 初始化环境 const $ = new Env(SCRIPT_NAME); // 兼容 Node.js 的 fetch let nodeHeaders = {}; if ($.isNode()) { try { const https = require("https"); if (typeof fetch !== "function") { global.fetch = function (url, options = {}) { return new Promise((resolve, reject) => { const u = new URL(url); const req = https.request( { hostname: u.hostname, path: u.pathname + u.search, method: options.method || "GET", headers: options.headers || {}, }, (res) => { let rawData = ""; res.on("data", (chunk) => (rawData += chunk)); res.on("end", () => { resolve({ status: res.statusCode, ok: res.statusCode >= 200 && res.statusCode < 300, headers: res.headers, json: () => Promise.resolve(rawData ? JSON.parse(rawData) : {}), text: () => Promise.resolve(rawData) }); }); } ); req.on("error", reject); if (options.body) req.write(options.body); req.end(); }); }; } } catch (e) { } } const TASK_LIST = [ { name: "浏览得机会", id: "2510301515552" }, { name: "分享得机会", id: "2510301516431" }, { name: "观看视频号", id: "2510301517291" }, { name: "浏览公众号", id: "2510301518121" } ]; const RECORD_URL = "https://sxs-consumer.nfsq.com.cn/geement.actjextra/api/v1/act/win/goods/simple?act_codes=ACT2510301507191%2CACT2510301505581"; // ================= 入口逻辑 ================= (async () => { // 1. 抓包模式 if (typeof $request !== "undefined") { await handleRewrite(); } // 2. 运行模式 else { await main(); } $.done(); })(); // ================= 核心逻辑 ================= // 抓包处理 async function handleRewrite() { try { $.log("🔔 检测到抽奖请求,开始抓取账号信息..."); // 1. 获取请求头和体 const headers = $request.headers || {}; const bodyStr = $request.body || "{}"; // 兼容不同客户端 Header key 大小写 const getHeader = (key) => headers[key] || headers[key.toLowerCase()] || headers[Object.keys(headers).find(k => k.toLowerCase() === key.toLowerCase())]; const uid = getHeader('unique_identity'); const token = getHeader('apitoken') || getHeader('apiToken'); if (!uid || !token) { $.msg(SCRIPT_NAME, "抓包失败", "未找到 unique_identity 或 apitoken"); return; } // 2. 组装账号 let bodyObj = {}; try { bodyObj = JSON.parse(bodyStr); } catch (e) { } // 构建标准账号对象 (严格对齐用户提供的标准示例) const newAccount = { uid: uid, token: token, body: bodyObj, headers_json: headers }; // 3. 本地存储逻辑 (BoxJs 仍然保持本地全量备份) let localAccounts = []; const cachedData = $.getdata(ENV_NAME); if (cachedData) { try { const parsed = JSON.parse(cachedData); localAccounts = Array.isArray(parsed) ? parsed : [parsed]; } catch (e) { localAccounts = []; } } const idx = localAccounts.findIndex(u => u.uid === uid); if (idx > -1) { localAccounts[idx] = newAccount; // 更新 } else { localAccounts.push(newAccount); // 新增 } const saved = $.setdata(JSON.stringify(localAccounts, null, 2), ENV_NAME); if (saved) { $.msg(SCRIPT_NAME, `✅ 账号抓取成功`, `UID: ${uid.slice(0, 6)}... 已保存到本地`); } // 4. 推送到青龙 (智能合并模式) if (QL_CONFIG.URL && QL_CONFIG.CLIENT_ID && QL_CONFIG.CLIENT_SECRET) { await pushToQingLong(newAccount); } else { $.log("⚠️ 未配置青龙信息,跳过推送。(请在脚本 QL_CONFIG 中填写,或手动复制 BoxJs 数据)"); } } catch (e) { console.log("❌ 抓包处理异常:", e); $.msg(SCRIPT_NAME, "抓包异常", e.message); } } // 任务运行主函数 async function main() { // 优先读取本地 BoxJs/Prefs 数据,其次读取环境变量 let accountsStr = $.getdata(ENV_NAME); if (!accountsStr && $.isNode()) { accountsStr = process.env[ENV_NAME]; } let list = []; if (!accountsStr) { console.log(`❌ 未找到账号信息!`); console.log(`1. 青龙用户:请设置环境变量 ${ENV_NAME}`); console.log(`2. 圈X用户:请先运行一次抓包 (重写已配置)`); return; } try { list = JSON.parse(accountsStr); if (!Array.isArray(list)) list = [list]; } catch (e) { // 尝试修复单引号 try { list = JSON.parse(accountsStr.replace(/'/g, '"')); } catch (e2) { console.log("❌ JSON 解析失败,请检查数据格式"); return; } } console.log(`🚀 脚本启动: 检测到 ${list.length} 个账号`); for (let i = 0; i < list.length; i++) { let acc = list[i]; console.log(`\n======== 👤 账号 ${i + 1}/${list.length} (${acc.uid?.slice(0, 6) || '未知'}) ========`); if (!acc.token || !acc.uid) { console.log(" ⚠️ 账号信息缺失(token/uid),跳过"); continue; } let realUA = extractUA(acc.headers_json); // --- 1. 做任务 --- console.log("【1️⃣ 任务环节】"); for (let task of TASK_LIST) { await joinTask(acc.token, acc.uid, task, realUA); await $.wait(500); } // --- 2. 抽奖 --- console.log("\n【2️⃣ 抽奖环节】"); let count = 0; let running = true; while (running && count < 50) { // 限制最大50次防止死循环 count++; let status = await doLottery(acc.token, acc.uid, acc.body, acc.headers_json); if (status !== "CONTINUE") { running = false; } else { process.stdout.write("."); // 进度条效果 await $.wait(2000); // 间隔2秒 } } console.log(count >= 50 ? "\n ⚠️ 达到最大抽奖次数限制" : ""); // --- 3. 查奖 --- console.log("\n【3️⃣ 历史奖品】"); await checkAllPrizes(acc.token, acc.uid, realUA); await $.wait(1500); } console.log("\n🏁 所有任务执行结束"); } // ================= 业务函数 ================= async function doLottery(token, uid, body, headers_obj) { // 准备请求体 let payload = typeof body === 'string' ? body : JSON.stringify(body || {}); // 准备请求头 let headers = { 'apitoken': token, 'unique_identity': uid, 'Host': 'sxs-consumer.nfsq.com.cn', 'Content-Type': 'application/json', 'User-Agent': extractUA(headers_obj) || "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.18" }; try { const resp = await $.http.post({ url: 'https://sxs-consumer.nfsq.com.cn/geement.marketinglottery/api/v1/marketinglottery', headers: headers, body: payload }); const res = JSON.parse(resp.body || "{}"); if (res.code === 200) { let pName = res.data?.prizedto?.prize_name || res.data?.prize_name || "未知奖品"; console.log(` 🎉 中奖: ${pName}`); return "CONTINUE"; } else { // 失败或无资格 const msg = res.msg || "未知错误"; // 只有特定错误才停止,其他(如网络抖动)可以重试,但这里简单起见,非200一般都停 if (msg.includes("不足") || msg.includes("上限") || msg.includes("机会")) { console.log(` ⏹ 停止: ${msg}`); return "STOP"; } console.log(` 💨 未中/继续: ${msg}`); return "CONTINUE"; } } catch (e) { console.log(` ❌ 抽奖出错: ${e.message}`); return "STOP"; } } async function joinTask(token, uid, task, userAgent) { const timeStr = encodeURIComponent(getNowFormatDate()); const url = `https://sxs-consumer.nfsq.com.cn/geement.marketingplay/api/v1/task/join?action_time=${timeStr}&task_id=${task.id}`; const headers = { 'apitoken': token, 'unique_identity': uid, 'Host': 'sxs-consumer.nfsq.com.cn', 'Referer': 'https://servicewechat.com/wxd79ec05386a78727/100/page-frame.html', 'User-Agent': userAgent || "Mozilla/5.0" }; try { const resp = await $.http.get({ url, headers }); const res = JSON.parse(resp.body || "{}"); if (res.code === 200 || (res.msg && res.msg.includes("已参与"))) { console.log(` ✅ ${task.name}: 完成`); } else { console.log(` ⏺ ${task.name}: ${res.msg}`); } } catch (e) { console.log(` ❌ ${task.name}: 请求失败`); } } async function checkAllPrizes(token, uid, userAgent) { const headers = { 'apitoken': token, 'unique_identity': uid, 'Host': 'sxs-consumer.nfsq.com.cn', 'User-Agent': userAgent || "Mozilla/5.0" }; try { const resp = await $.http.get({ url: RECORD_URL, headers }); const res = JSON.parse(resp.body || "{}"); if (res.code === 200 && res.data && res.data.length > 0) { console.log(` 📚 共 ${res.data.length} 条记录:`); res.data.forEach(item => { console.log(` 🎁 [${item.scan_time}] ${item.win_prize_name}`); }); } else { console.log(" 💨 暂无中奖记录"); } } catch (e) { console.log(" ❌ 查询失败"); } } // ================= 青龙推送相关 ================= async function pushToQingLong(accountToPush) { $.log(`📤 正在尝试处理青龙推送 (UID: ${accountToPush.uid.slice(0, 6)}...)`); const { URL, CLIENT_ID, CLIENT_SECRET } = QL_CONFIG; try { // 1. 获取 Token const tokenUrl = `${URL}/open/auth/token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`; const tReq = await $.http.get({ url: tokenUrl }); const tBody = JSON.parse(tReq.body); if (tBody.code !== 200) { $.msg(SCRIPT_NAME, "青龙推送失败", "获取 Token 失败,请检查密钥"); return; } const qlToken = tBody.data.token; // 2. 获取现有环境变量 const envsUrl = `${URL}/open/envs?searchValue=${ENV_NAME}`; const eReq = await $.http.get({ url: envsUrl, headers: { 'Authorization': `Bearer ${qlToken}` } }); const eBody = JSON.parse(eReq.body); // 3. 准备数据:读取现有 -> 合并 let remoteList = []; let existingEnv = null; if (eBody.data && eBody.data.length > 0) { existingEnv = eBody.data[0]; try { // 尝试解析现有的值为 JSON 数组 // 注意:青龙里可能存的是字符串,也可能已经是 JSON let rawValue = existingEnv.value; // 有时候青龙返回的 value 是被转义过的字符串,尝试解析 let parsedValue = JSON.parse(rawValue); if (Array.isArray(parsedValue)) { remoteList = parsedValue; } else { // 如果不是数组,可能是单个对象,或者其他格式,清空 remoteList = []; } } catch (e) { console.log(" ⚠️ 解析青龙现有变量失败,将覆盖为新列表"); remoteList = []; } } // 4. 执行合并逻辑 const targetIdx = remoteList.findIndex(u => u.uid === accountToPush.uid); let action = ""; if (targetIdx > -1) { remoteList[targetIdx] = accountToPush; action = "更新"; } else { remoteList.push(accountToPush); action = "新增"; } const newValue = JSON.stringify(remoteList); // 保持紧凑格式,或者用 JSON.stringify(remoteList, null, 2) 美化 // 5. 推送回青龙 if (existingEnv) { // update const updateUrl = `${URL}/open/envs`; await $.http.put({ url: updateUrl, headers: { 'Authorization': `Bearer ${qlToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ id: existingEnv.id, name: ENV_NAME, value: newValue, remarks: existingEnv.remarks || (`自动抓包更新: ${new Date().toLocaleString()}`) }) }); $.msg(SCRIPT_NAME, `✅ 推送成功 [${action}]`, `UID: ${accountToPush.uid.slice(0, 6)}... 已同步至青龙`); } else { // create const addUrl = `${URL}/open/envs`; await $.http.post({ url: addUrl, headers: { 'Authorization': `Bearer ${qlToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify([{ name: ENV_NAME, value: newValue, remarks: "自动抓包创建" }]) }); $.msg(SCRIPT_NAME, `✅ 推送成功 [新建]`, `UID: ${accountToPush.uid.slice(0, 6)}... 已创建青龙变量`); } } catch (e) { $.log(" ❌ 推送异常: " + e.message); $.msg(SCRIPT_NAME, "青龙推送异常", e.message); } } // ================= 工具函数 & Env实现 ================= function extractUA(headers) { if (!headers) return null; if (typeof headers !== 'object') { try { headers = JSON.parse(headers); } catch (e) { return null; } } return headers['User-Agent'] || headers['user-agent']; } function getNowFormatDate() { let d = new Date(); let pad = (n) => n < 10 ? '0' + n : n; return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } // 兼容性封装 (Copy from common env implementations) function Env(name) { return new class { constructor(name) { this.name = name; this.logs = []; } isNode() { return "undefined" != typeof module && !!module.exports } log(...t) { if (t.length > 0) { this.logs = [...this.logs, ...t]; console.log(t.join(" ")); } } msg(t, e, s) { if (this.isNode()) { this.log(t, e, s); } else { if ("undefined" != typeof $notify) $notify(t, e, s); } } getdata(t) { if (this.isNode()) return process.env[t]; if ("undefined" != typeof $prefs) return $prefs.valueForKey(t); if ("undefined" != typeof $persistentStore) return $persistentStore.read(t); return null; } setdata(t, e) { if (this.isNode()) return false; if ("undefined" != typeof $prefs) return $prefs.setValueForKey(t, e); if ("undefined" != typeof $persistentStore) return $persistentStore.write(t, e); return false; } wait(t) { return new Promise(e => setTimeout(e, t)) } done() { if (this.isNode()) { } else { $done({}) } } // 简单封装 HTTP,消除平台差异 get http() { const that = this; return { get: (opts) => that.sendReq('GET', opts), post: (opts) => that.sendReq('POST', opts), put: (opts) => that.sendReq('PUT', opts), delete: (opts) => that.sendReq('DELETE', opts) } } sendReq(method, opts) { return new Promise((resolve, reject) => { const reqOpts = { url: opts.url, method: method, headers: opts.headers, body: opts.body }; if (this.isNode()) { // Node fetch 在外部已定义,这里直接用 fetch(reqOpts.url, reqOpts).then(res => res.text().then(txt => resolve({ body: txt, headers: res.headers }))).catch(reject); } else if ("undefined" != typeof $task) { $task.fetch(reqOpts).then(res => resolve({ body: res.body, headers: res.headers })).catch(reject); } else if ("undefined" != typeof $httpClient) { const client = method === 'POST' ? $httpClient.post : (method === 'PUT' ? $httpClient.put : $httpClient.get); client(reqOpts, (err, res, body) => { if (err) reject(err); else resolve({ body: body, headers: res.headers }); }); } else { reject("Env not supported"); } }); } }(name); }