diff --git a/.gitignore b/.gitignore index 92f6292..16b3bd0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ package*.json .czrc Tools/ wxapp/*_token_cache.json +daily/*_token_cache.json diff --git a/daily/BREO.py b/daily/BREO.py index 82ea9a3..9ab62c9 100644 --- a/daily/BREO.py +++ b/daily/BREO.py @@ -1,6 +1,7 @@ #by:哆啦A梦 #入口:http://mx.qrurl.net/h5/wxa/link?sid=26407uif5Oq -#抓包breoplus.breo.cn的域名下的token,多账号换行分割 +#BREO变量填写wx_server里的openid/账号标识,多账号换行分割 +#需要配置wx_server_url、wx_auth,用于获取wx.login code #账号变量名:BREO #new Env("BREO") #cron 8 9,10,11 * * * @@ -9,7 +10,182 @@ import requests import json import os +import sys import time +from pathlib import Path + +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") +if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + +MINI_APP_ID = "wx61457400e4212cec" +WX_SERVER_URL = os.getenv("wx_server_url", "http://192.168.31.196:8787").rstrip("/") +WX_AUTH = os.getenv("wx_auth", "") +TOKEN_CACHE_PATH = Path(__file__).with_name("BREO_token_cache.json") +LOGIN_BASE = "https://breoplus.breo.cn/app/minic" +APP_BASE = "https://breoplus.breo.cn/breo-app" +DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF" + +session = requests.Session() + +def read_token_cache(): + try: + if not TOKEN_CACHE_PATH.exists(): + return {} + return json.loads(TOKEN_CACHE_PATH.read_text(encoding="utf-8")) or {} + except Exception: + return {} + +def write_token_cache(cache): + try: + TOKEN_CACHE_PATH.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding="utf-8") + except Exception as e: + print(f"⚠️ 写入token缓存失败: {e}") + +def mask_token(token): + if not token: + return "" + return f"{token[:6]}***{token[-6:]}" + +def is_success(result): + return result.get("success") is True or str(result.get("code")) in ("0000", "200", "200.0") + +def is_token_error(message): + text = str(message) + return any(key in text for key in ["40101", "40102", "token", "登录", "授权", "过期", "失效"]) + +def wx_headers(): + return { + "auth": WX_AUTH, + "User-Agent": DEFAULT_UA, + "Content-Type": "application/json", + } + +def app_headers(token=None): + headers = { + "User-Agent": DEFAULT_UA, + "content-Type": "application/json", + "Content-Type": "application/json", + "deviceInfo": "{}", + "Referer": "https://servicewechat.com/wx61457400e4212cec/390/page-frame.html", + } + if token: + headers["token"] = token + return headers + +def breo_task_headers(token): + return { + "token": token, + "device-type": "Xiaomi", + "device-version": "10", + "channel": "Breo", + "version_code": "30201", + "version": "3.2.1", + "encrypt": "1", + "Content-Type": "application/json; charset=UTF-8", + "Referer": "https://servicewechat.com/wx61457400e4212cec/390/page-frame.html", + "User-Agent": DEFAULT_UA, + } + +def get_wx_code(account_id): + if not WX_AUTH: + raise RuntimeError("缺少wx_auth,无法从wx_server获取code") + resp = session.post( + f"{WX_SERVER_URL}/wx/code", + json={"appid": MINI_APP_ID, "openid": account_id}, + headers=wx_headers(), + timeout=30, + ) + resp.raise_for_status() + data = resp.json() + code = data.get("code") or (data.get("data") or {}).get("code") + if not code: + raise RuntimeError(f"wx_server未返回code: {data}") + return code + +def login_with_code(account_id): + code = get_wx_code(account_id) + login_resp = session.get( + f"{LOGIN_BASE}/login/{code}", + headers=app_headers(), + timeout=30, + ) + login_resp.raise_for_status() + login_data = login_resp.json() + if str(login_data.get("code")) != "200" or not login_data.get("data"): + raise RuntimeError(f"code登录失败: {login_data}") + user_info = login_data["data"] + uid = user_info.get("uid") + open_id = user_info.get("openId") + union_id = user_info.get("unionId") + if not uid: + raise RuntimeError(f"登录响应缺少uid: {login_data}") + + token_resp = session.post( + f"{APP_BASE}/customer/loginByUid", + json={"uid": uid, "openId": open_id, "unionId": union_id}, + headers=app_headers(), + timeout=30, + ) + token_resp.raise_for_status() + token_data = token_resp.json() + if not is_success(token_data) or not (token_data.get("result") or {}).get("token"): + raise RuntimeError(f"业务token登录失败: {token_data}") + return { + "token": token_data["result"]["token"], + "uid": uid, + "openId": open_id, + "unionId": union_id, + "userInfo": user_info, + "customer": token_data.get("result", {}), + "updatedAt": int(time.time()), + } + +def get_cached_token(account_id): + return read_token_cache().get(account_id) + +def save_cached_token(account_id, auth_info): + cache = read_token_cache() + cache[account_id] = auth_info + write_token_cache(cache) + +def remove_cached_token(account_id): + cache = read_token_cache() + if account_id in cache: + del cache[account_id] + write_token_cache(cache) + +def validate_token(token): + try: + resp = session.post( + f"{APP_BASE}/getUserLevelInfoByUid", + headers=app_headers(token), + timeout=15, + ) + if resp.status_code != 200: + return False + data = resp.json() + if is_success(data): + return True + return not is_token_error(data) + except Exception: + return False + +def get_token_for_account(account_id, index): + cached = get_cached_token(account_id) + if cached and cached.get("token"): + print(f"账号 {index} 使用缓存token: {mask_token(cached['token'])}") + if validate_token(cached["token"]): + return cached["token"] + print(f"账号 {index} 缓存token失效,重新登录") + remove_cached_token(account_id) + + auth_info = login_with_code(account_id) + save_cached_token(account_id, auth_info) + user_info = auth_info.get("userInfo") or {} + print(f"账号 {index} code登录成功: {user_info.get('nickname') or user_info.get('telephone') or auth_info.get('uid')}") + return auth_info["token"] def get_random_one_word(): try: @@ -54,16 +230,7 @@ def get_proclamation(): def post_to_breo(token, content, title): url = "https://breoplus.breo.cn/breo-app/communityBaseInfo/releasePost" - headers = { - "token": token, - "device-type": "Xiaomi", - "device-version": "10", - "channel": "Breo", - "version_code": "30201", - "version": "3.2.1", - "encrypt": "1", - "Content-Type": "application/json; charset=UTF-8" - } + headers = breo_task_headers(token) data = { "anonymoused": 1, "content": content, @@ -94,16 +261,7 @@ def post_to_breo(token, content, title): def collect_post(token, post_id): url = "https://breoplus.breo.cn/breo-app/communityBaseInfo/collect" - headers = { - "token": token, - "device-type": "Xiaomi", - "device-version": "10", - "channel": "Breo", - "version_code": "30201", - "version": "3.2.1", - "encrypt": "1", - "Content-Type": "application/json; charset=UTF-8" - } + headers = breo_task_headers(token) data = { "postId": post_id } @@ -113,8 +271,9 @@ def collect_post(token, post_id): result = response.json() if result.get("success", False): print("✅ 收藏成功!") - print(f"获得点数: {result['result']['point']}") - print(f"成长值: {result['result']['grow']}") + reward = result.get("result") or {} + print(f"获得点数: {reward.get('point', 0)}") + print(f"成长值: {reward.get('grow', 0)}") else: print(f"❌ 收藏失败,错误信息:{result.get('message', '未知错误')}") else: @@ -126,16 +285,7 @@ def comment_post(token, post_id): for _ in range(2): # 评论2次 comment_content = get_random_one_word() # 使用随机一言作为评论内容 url = "https://breoplus.breo.cn/breo-app/communityBaseInfo/comment" - headers = { - "token": token, - "device-type": "Xiaomi", - "device-version": "10", - "channel": "Breo", - "version_code": "30201", - "version": "3.2.1", - "encrypt": "1", - "Content-Type": "application/json; charset=UTF-8" - } + headers = breo_task_headers(token) data = { "anonymoused": 0, "commentText": comment_content, @@ -147,9 +297,11 @@ def comment_post(token, post_id): result = response.json() if result.get("success", False): print("✅ 评论成功!") - print(f"评论内容: {result['result']['rootOutVO']['commentText']}") - print(f"获得点数: {result['result']['point']}") - print(f"成长值: {result['result']['grow']}") + reward = result.get("result") or {} + root = reward.get("rootOutVO") or {} + print(f"评论内容: {root.get('commentText', comment_content)}") + print(f"获得点数: {reward.get('point', 0)}") + print(f"成长值: {reward.get('grow', 0)}") else: print(f"❌ 评论失败,错误信息:{result.get('message', '未知错误')}") else: @@ -160,23 +312,16 @@ def comment_post(token, post_id): def browse_mall(token): url = "https://breoplus.breo.cn/breo-app/user/po-task-info/mall" - headers = { - "token": token, - "device-type": "Xiaomi", - "device-version": "10", - "channel": "Breo", - "version_code": "30201", - "version": "3.2.1", - "encrypt": "1" - } + headers = breo_task_headers(token) try: response = requests.post(url, headers=headers) if response.status_code == 200: result = response.json() if result.get("success", False): print("✅ 浏览商城成功!") - print(f"获得点数: {result['result']['point']}") - print(f"成长值: {result['result']['grow']}") + reward = result.get("result") or {} + print(f"获得点数: {reward.get('point', 0)}") + print(f"成长值: {reward.get('grow', 0)}") else: print(f"❌ 浏览商城失败,错误信息:{result.get('message', '未知错误')}") else: @@ -186,25 +331,16 @@ def browse_mall(token): def punch_in(token): url = "https://breoplus.breo.cn/breo-app/user/po-task-info/punch" - headers = { - "Host": "breoplus.breo.cn", - "Connection": "keep-alive", - "Content-Length": "0", - "content-type": "application/json", - "token": token, - "charset": "utf-8", - "Referer": "https://servicewechat.com/wx61457400e4212cec/304/page-frame.html", - "User-Agent": "Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.136 Mobile Safari/537.36 XWEB/1340043 MMWEBSDK/20241202 MMWEBID/3628 MicroMessenger/8.0.56.2800(0x2800385E) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android", - "Accept-Encoding": "gzip, deflate, br" - } + headers = breo_task_headers(token) try: response = requests.post(url, headers=headers) if response.status_code == 200: result = response.json() if result.get("success", False): print("✅ 签到成功!") - print(f"获得点数: {result['result']['point']}") - print(f"成长值: {result['result']['grow']}") + reward = result.get("result") or {} + print(f"获得点数: {reward.get('point', 0)}") + print(f"成长值: {reward.get('grow', 0)}") else: print(f"❌ 签到失败,错误信息:{result.get('message', '未知错误')}") else: @@ -216,19 +352,28 @@ if __name__ == "__main__": # 获取公告 #get_proclamation() - # 从环境变量读取 token - tokens = os.getenv("BREO", "").splitlines() + # 从环境变量读取 wx_server 账号标识/openid + accounts = [item.strip() for item in os.getenv("BREO", "").replace("&", "\n").splitlines() if item.strip()] - if not tokens: + if not accounts: print("❌ 未检测到 账号信息,退出脚本。") else: + skip_community = os.getenv("BREO_SKIP_COMMUNITY", "").lower() in ("1", "true", "yes") print("=============== 开始执行任务 ===============") - for i, token in enumerate(tokens, 1): - if token.strip(): # 跳过空行 - print(f"\n-------------- 账号 {i} 开始 --------------") - print("🚀 正在签到...") - punch_in(token) + for i, account in enumerate(accounts, 1): + print(f"\n-------------- 账号 {i} 开始 --------------") + try: + token = get_token_for_account(account, i) + except Exception as e: + print(f"❌ 账号 {i} 登录失败: {e}") + continue + print("🚀 正在签到...") + punch_in(token) + + if skip_community: + print("\n📝 已跳过发帖/收藏/评论任务") + else: print("\n📝 正在发布帖子...") post_id = post_to_breo(token, "这是一个自动发布的帖子", "自动化测试") if post_id: @@ -240,9 +385,9 @@ if __name__ == "__main__": else: print("❌ 发帖失败,跳过后续操作。") - print("\n🛒 正在浏览商城...") - browse_mall(token) + print("\n🛒 正在浏览商城...") + browse_mall(token) - print(f"-------------- 账号 {i} 结束 --------------") + print(f"-------------- 账号 {i} 结束 --------------") - print("\n=============== 所有任务执行完毕 ===============") \ No newline at end of file + print("\n=============== 所有任务执行完毕 ===============") diff --git a/daily/aima.js b/daily/aima.js index a69c89a..a5b3ae4 100644 --- a/daily/aima.js +++ b/daily/aima.js @@ -1,122 +1,281 @@ /* -爱玛会员俱乐部 - 自动签到脚本(2026年2月修复版) -✅ 修复点:使用域名替代失效IP,更新活动ID为100001180 -✅ 支持环境:Node.js -✅ 变量名:aima -✅ 变量值:access-token(支持多账号,用 & 或换行分隔) +爱玛会员俱乐部 - 自动签到脚本 +变量名:aima +变量值:账号标识/openid(支持多账号,用 & 或换行分隔) +CODE登录依赖:wx_server_url、wx_auth */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env"); const $ = new Env("爱玛会员俱乐部"); -const axios = require('axios') +const axios = require("axios"); +const crypto = require("crypto"); +const fs = require("fs"); +const path = require("path"); // ================== 配置区 ================== -const ACTIVITY_ID = "100001192"; // 2026年2月活动ID -const BASE_URL = "https://scrm.aimatech.com"; // 使用官方域名,不再硬编码IP +const ACTIVITY_ID = "100001192"; +const BASE_URL = "https://scrm.aimatech.com"; +const WXCLIENT_URL = `${BASE_URL}/aima/wxclient`; +const MINI_APPID = "wx2dcfb409fd5ddfb4"; const APP_ID = "scrm"; -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/142.0.7444.173 Mobile Safari/537.36 XWEB/1420229 MMWEBSDK/20251101 MMWEBID/6369 MicroMessenger/8.0.67.3000(0x28004333) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android"; +const TOKEN_CACHE_FILE = path.join(__dirname, "aima_token_cache.json"); +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/142.0.7444.173 Mobile Safari/537.36 XWEB/1420229 MMWEBSDK/20251101 MMWEBID/6369 MicroMessenger/8.0.67.3000(0x28004333) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64 MiniProgramEnv/android"; // ================== 工具函数 ================== function generateUUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } function md5(str) { - const crypto = require('crypto'); - return crypto.createHash('md5').update(str).digest('hex'); + return crypto.createHash("md5").update(str).digest("hex"); +} + +function maskToken(token = "") { + if (!token) return ""; + if (token.length <= 12) return `${token.slice(0, 3)}***`; + return `${token.slice(0, 6)}***${token.slice(-6)}`; +} + +function isToken(value = "") { + return /^eyJ/.test(value.trim()) || value.length > 120; +} + +function readCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")); + } catch (e) { + return {}; + } +} + +function writeCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2)); + } catch (e) { + $.log(`⚠️ token缓存写入失败: ${e.message}`); + } +} + +function updateCachedToken(account, token) { + if (!account || !token || isToken(account)) return; + const cache = readCache(); + cache[account] = { + token, + updatedAt: new Date().toISOString(), + }; + writeCache(cache); +} + +function getResponseToken(headers = {}) { + return ( + headers["set-access-token"] || + headers["Set-Access-Token"] || + headers["SET-ACCESS-TOKEN"] || + "" + ); +} + +function buildHeaders(token = "") { + const timestamp = Date.now().toString(); + const traceLogId = generateUUID(); + const signToken = token ? token.substring(50, 80) : ""; + const signStr = `App-Id${APP_ID}Time-Stamp${timestamp}TraceLog-Id${traceLogId}Access-Token${signToken}AimaScrm321_^`; + + return { + "App-Id": APP_ID, + "Time-Stamp": timestamp, + "TraceLog-Id": traceLogId, + "Access-Token": token, + Sign: md5(signStr).toLowerCase(), + "content-type": "application/json", + charset: "utf-8", + Referer: "https://servicewechat.com/wx2dcfb409fd5ddfb4/223/page-frame.html", + "User-Agent": USER_AGENT, + }; +} + +async function request(method, url, token, options = {}) { + const res = await axios({ + method, + url, + headers: buildHeaders(token), + timeout: 15000, + validateStatus: () => true, + ...options, + }); + + const newToken = getResponseToken(res.headers); + if (newToken && options.account) { + options.onToken?.(newToken); + updateCachedToken(options.account, newToken); + } + + return res; +} + +async function getWxCode(account) { + const wxServerUrl = process.env.wx_server_url; + const wxAuth = process.env.wx_auth; + if (!wxServerUrl || !wxAuth) { + throw new Error("未配置 wx_server_url 或 wx_auth,无法获取code登录"); + } + + const res = await axios.post( + `${wxServerUrl.replace(/\/$/, "")}/wx/operatedata`, + { + appid: MINI_APPID, + openid: account, + }, + { + headers: { + auth: wxAuth, + "content-type": "application/json", + }, + timeout: 15000, + validateStatus: () => true, + } + ); + + const code = res.data?.code || res.data?.data?.code; + if (res.status !== 200 || !code) { + throw new Error(`获取code失败: HTTP ${res.status} ${JSON.stringify(res.data)}`); + } + + return code; +} + +async function loginByCode(account) { + $.log("🔐 正在获取code并登录..."); + const code = await getWxCode(account); + const res = await request("post", `${WXCLIENT_URL}/user/members:login`, "", { + data: { code }, + account, + }); + + const token = getResponseToken(res.headers); + if (res.status !== 200 || res.data?.code !== 200 || !token) { + throw new Error(`登录失败: HTTP ${res.status} ${JSON.stringify(res.data)}`); + } + + updateCachedToken(account, token); + $.log(`✅ 登录成功,已缓存token: ${maskToken(token)}`); + return token; +} + +async function validateToken(account, token) { + const res = await request("get", `${WXCLIENT_URL}/member/IndexInfo`, token, { + account, + }); + const ok = res.status === 200 && res.data?.code === 200; + if (!ok) { + $.log(`⚠️ 缓存token失效: HTTP ${res.status} ${JSON.stringify(res.data)}`); + } + return ok; +} + +async function getAccessToken(account) { + const value = account.trim(); + if (isToken(value)) { + $.log(`ℹ️ 检测到旧token变量,将直接使用: ${maskToken(value)}`); + return value; + } + + const cache = readCache(); + const cachedToken = cache[value]?.token; + if (cachedToken) { + $.log(`🔎 使用缓存token校验: ${maskToken(cachedToken)}`); + if (await validateToken(value, cachedToken)) { + $.log("✅ 缓存token有效"); + return cachedToken; + } + } + + return loginByCode(value); } // ================== 核心逻辑 ================== -async function signIn(token, index) { - try { - const timestamp = Date.now(); - const traceLogId = generateUUID(); +async function signIn(account, index) { + let token = await getAccessToken(account); + const setToken = (newToken) => { + token = newToken; + }; - // 构造通用请求头 - const headers = { - "App-Id": APP_ID, - "Time-Stamp": timestamp.toString(), - "TraceLog-Id": traceLogId, - "Access-Token": token.trim(), - "content-type": "application/json", - "User-Agent": USER_AGENT, - "charset": "utf-8", - "Referer": "https://servicewechat.com/wx2dcfb409fd5ddfb4/215/page-frame.html" - }; - - // 生成签名(按规则拼接) - const signStr = `${APP_ID}${timestamp}${traceLogId}${token.trim()}AimaScrm321_^`; - headers["Sign"] = md5(signStr).toLowerCase(); - - // 1. 查询签到状态 - $.log(`🚀 账号【${index}】查询签到状态...`); - const searchRes = await axios.post( - `${BASE_URL}/aima/wxclient/mkt/activities/sign:search`, - { activityId: ACTIVITY_ID }, - { headers, timeout: 10000 } - ); - - const data = searchRes.data; - if (data.content && data.content.signStatus === 1) { - $.log(`✅ 账号【${index}】今日已签到!`); - return; + $.log(`🚀 账号【${index}】查询签到状态...`); + const searchRes = await request( + "post", + `${WXCLIENT_URL}/mkt/activities/sign:search`, + token, + { + data: { activityId: ACTIVITY_ID }, + account, + onToken: setToken, } + ); - // 2. 执行签到 - $.log(`⏳ 账号【${index}】正在签到...`); - const joinRes = await axios.post( - `${BASE_URL}/aima/wxclient/mkt/activities/sign:join`, - { activityId: ACTIVITY_ID, activitySceneId: null }, - { headers, timeout: 10000 } - ); + if (searchRes.status !== 200 || searchRes.data?.code !== 200) { + throw new Error(`查询签到状态失败: HTTP ${searchRes.status} ${JSON.stringify(searchRes.data)}`); + } - if (joinRes.data.code === 200 || joinRes.data.code === 0) { - const point = joinRes.data.content?.point || 10; - $.log(`🎉 账号【${index}】签到成功!获得 ${point} 积分`); - } else { - throw new Error(`签到失败: ${JSON.stringify(joinRes.data)}`); + const signStatus = searchRes.data?.content?.signStatus; + if (signStatus === 1) { + $.log(`✅ 账号【${index}】今日已签到!`); + return; + } + + $.log(`⏳ 账号【${index}】正在签到...`); + const joinRes = await request( + "post", + `${WXCLIENT_URL}/mkt/activities/sign:join`, + token, + { + data: { activityId: ACTIVITY_ID, activitySceneId: null }, + account, + onToken: setToken, } + ); - } catch (e) { - throw new Error(e.message || e); + if (joinRes.status === 200 && joinRes.data?.code === 200) { + const point = joinRes.data.content?.point || joinRes.data.content?.points || 0; + $.log(`🎉 账号【${index}】签到成功!${point ? `获得 ${point} 积分` : ""}`); + } else { + throw new Error(`签到失败: HTTP ${joinRes.status} ${JSON.stringify(joinRes.data)}`); } } // ================== 主函数 ================== !(async () => { - console.log(`\n🔔 爱玛会员俱乐部, 开始!`); + console.log("\n🔔 爱玛会员俱乐部, 开始!"); - // 获取 access-token(支持多账号) - let tokens = []; + let accounts = []; if ($.isNode()) { const env = process.env.aima; if (env) { - tokens = env.split(/&|\n/).filter(t => t.trim()); + accounts = env.split(/&|\n/).map((t) => t.trim()).filter(Boolean); } } - if (tokens.length === 0) { - $.msg("❌ 未找到 access-token,请配置变量 'aima'"); + if (accounts.length === 0) { + $.msg("❌ 未找到账号标识,请配置变量 'aima'"); return; } - console.log(`共找到${tokens.length}个账号`); + console.log(`共找到${accounts.length}个账号`); - for (let i = 0; i < tokens.length; i++) { + for (let i = 0; i < accounts.length; i++) { try { console.log(`\n🚀 user:【${i + 1}】 start work`); - await signIn(tokens[i], i + 1); + await signIn(accounts[i], i + 1); } catch (e) { console.log(`❌ 账号【${i + 1}】执行失败: ${e.message}`); } } - // 发送通知 - //await $.sendMsg($.logs.join("\n")); + // await $.sendMsg($.logs.join("\n")); })() .catch((e) => console.log(e)) .finally(() => $.done()); - diff --git a/daily/babycare.js b/daily/babycare.js deleted file mode 100644 index 935be13..0000000 --- a/daily/babycare.js +++ /dev/null @@ -1,124 +0,0 @@ -/* ------------------------------------------- -@Author: sm -@Date: 2024.06.07 19:15 -@Description: babycare -cron: 30 8 * * * ------------------------------------------- -#Notice: -微信小程序抓包的https://api.bckid.com.cn请求头authorization - -⚠️【免责声明】 ------------------------------------------- -1、此脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 -2、由于此脚本仅用于学习研究,您必须在下载后 24 小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。 -3、请勿将此脚本用于任何商业或非法目的,若违反规定请自行对此负责。 -4、此脚本涉及应用与本人无关,本人对因此引起的任何隐私泄漏或其他后果不承担任何责任。 -5、本人对任何脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 -6、如果任何单位或个人认为此脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此脚本。 -7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 -*/ - -const { Env } = require("../tools/env") -const $ = new Env("babycare"); -let ckName = `babycare`; -const strSplitor = "#"; -const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" - - -class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; - - } - - async run() { - - await this.info() - await this.signIn() - } - - async signIn() { - let options = { - method: 'POST', - url: `https://api.bckid.com.cn/operation/front/bonus/userSign/v3/sign`, - headers: { - 'Host': 'api.bckid.com.cn', - 'authorization': this.token, - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - - } - }; - let { data: result } = await axios.request(options); - if (result?.code == '200') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `🕊当前已签到${result.body.signDaysCountMod}天🎉`); - } else { - $.log(`🌸账号[${this.index}] 签到-失败:${result.message}❌`) - } - - - - - } - async info() { - let options = { - url: 'https://api.bckid.com.cn/operation/front/bonus/userBonus/getUserBonus', - headers: { - 'Host': 'api.bckid.com.cn', - 'authorization': this.token, - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - method: 'POST', - data: {} - } - let { data: result } = await axios.request(options); - if (result?.code == '200') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `🕊账户当前积分[${result.body.userBonus}],历史积分[${result.body.sumBonus}]💰`); - } else { - $.log(`🌸账号[${this.index}]积分查询失败:${result.message}❌`) - } - } - - - - - - - -} - -!(async () => { - await getNotice() - $.checkEnv(ckName); - - for (let user of $.userList) { - await new Task(user).run(); - } -})() - .catch((e) => console.log(e)) - .finally(() => $.done()); - -async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - -} \ No newline at end of file diff --git a/daily/colorful.js b/daily/colorful.js index 5cb8175..a776cfa 100644 --- a/daily/colorful.js +++ b/daily/colorful.js @@ -1,15 +1,18 @@ /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: 七彩虹微信小程序 签到&点赞 cron: 30 10 * * * ------------------------------------------ -#Notice: +#Notice: 变量名:colorful -抓取 -https://interface.skycolorful.com/api 请求头的authorization去掉Bearer # x-authorization去掉Bearer -多账户&或换行 +变量值:wx_server 里的 openid/账号标识,多账户&或换行 +需要配置:wx_server_url、wx_auth + +说明:小程序当前登录链路为 wx.login code -> /api/User/OnLogin 获取 OpenId, +首次换取业务 Token 还需要微信手机号授权 code;现有 wx_server 只能提供 wx.login code。 +脚本会复用本地 token 缓存,缓存失效或首次运行时会自动验证 code 登录能力并给出明确原因。 ⚠️【免责声明】 ------------------------------------------ @@ -22,200 +25,316 @@ https://interface.skycolorful.com/api 请求头的authorization去掉Bearer # x- 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("colorful七彩虹"); -let ckName = `colorful`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const crypto = require("crypto"); +const fs = require("fs"); +const path = require("path"); +const WeChatServer = require("../wxapp/wcs.js"); +const ckName = "colorful"; +const strSplitor = "#"; +const MINI_APP_ID = "wx49018277e65fc3e1"; +const PAGE_VERSION = "91"; +const API_BASE = "https://interface.skycolorful.com"; +const TOKEN_CACHE_FILE = path.join(__dirname, "colorful_token_cache.json"); +const defaultUserAgent = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027"; + +const wechat = new WeChatServer({ + url: process.env.wx_server_url || "http://192.168.31.196:8787", + appid: MINI_APP_ID, + auth: process.env.wx_auth || "", +}); + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token || "").replace(/^Bearer\s+/i, ""); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function maskPhone(phone = "") { + return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); +} + +function md5(str) { + return crypto.createHash("md5").update(str).digest("hex"); +} + +function isTokenError(message) { + return /401|403|token|登录|授权|未登录|无效|过期|失效/i.test(String(message || "")); +} + +function isSuccess(result) { + return Number(result?.Code) === 0 && result?.Success !== false; +} class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; - this.token2 = this.user[1]; - this.signStatus = false; - this.expireFlag = false; - } + constructor(env) { + this.index = $.userIdx++; + this.raw = String(env || "").trim(); + this.user = this.raw.split(strSplitor); + this.accountId = this.user[0].trim(); + this.token = ""; + this.refreshToken = ""; + this.openId = ""; + this.userInfo = {}; + this.signStatus = false; + this.isLegacyToken = this.user.length >= 2; + if (this.isLegacyToken) { + this.token = this.user[0].trim(); + this.refreshToken = this.user[1].trim(); + this.accountId = `legacy:${shortToken(this.token)}`; + } + } - async run() { - await this.getTokenExpireTime() - await this.getUserInfo() - await this.getSignInfo() + async run() { + if (this.isLegacyToken) { + $.log(`账号[${this.index}] 检测到旧版 token 格式,将仅用于迁移缓存`); + this.saveCachedToken(); + } else { + const cached = this.getCachedToken(); + if (cached?.token) { + this.applyToken(cached); + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新尝试CODE登录`); + } + } - if (this.signStatus) { - $.log(`账号[${this.index}]已签到`) - return - } - await this.signInV2() - } - async getTokenExpireTime() { - let options = { - method: 'POST', - url: `https://interface.skycolorful.com/api/User/RefreshLoginTime`, - headers: { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "sec-fetch-dest": "empty", - }, - data: { "phone": "" } - } - let { data: result } = await this.request(options); - if (result.Code != 0) { - this.expireFlag = true - } + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } + } - } - async refreshToken(DATA) { - let options = { - method: 'POST', - url: `https://interface.skycolorful.com/api/User/DecryptPhoneNumber`, - headers: { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "sec-fetch-dest": "empty", - }, - data: DATA - } - let { data: result } = await this.request(options); - if (result.Code != 0) { - this.expireFlag = true - } else { - this.token = result.Data.Token - } - } - MD5(str) { - const crypto = require('crypto'); - return crypto.createHash('md5').update(str).digest('hex'); - } - request(options) { - let appid = "815d8026-9a52-4445-a42c-a5443134232e" - let uuid = $.uuid() - let timestamp = Date.now() - let sign = this.MD5(appid + timestamp + uuid + '2b5c01fb-7640-401a-8188-43a13190a626') - let baseHeaders = { - "User-Agent": defaultUserAgent, - "requestid": uuid, - "appid": appid, - "ticks": "" + timestamp, - "sign": sign, - "authorization": "Bearer " + this.token, - "x-authorization": "Bearer " + this.token2, - "source": "Wx", - "ucsource": "30", - "user-from": "xcx", + await this.getUserInfo(); + await this.getSignInfo(); + if (this.signStatus) { + $.log(`账号[${this.index}] 今日已签到`); + return; + } + await this.signInV2(); + } - } - options.headers = Object.assign(baseHeaders, options.headers) - return axios.request(options) - } + cacheKey() { + return this.isLegacyToken ? this.accountId : this.raw; + } - async signInV2() { - let options = { - method: 'POST', - url: `https://interface.skycolorful.com/api/User/SignV2`, - headers: { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "cross-site", - "xweb_xhr": "1" - }, - data: { + getCachedToken() { + const cache = readTokenCache(); + return cache[this.cacheKey()] || null; + } - } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `🕊签到${result.Message}🎉`); - } else { - $.log(`🌸账号[${this.index}] 签到-失败:${result.Message}❌`) - } + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.cacheKey()] = { + token: this.token, + refreshToken: this.refreshToken, + openId: this.openId, + userInfo: this.userInfo, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.cacheKey()]) { + delete cache[this.cacheKey()]; + writeTokenCache(cache); + } + this.token = ""; + this.refreshToken = ""; + this.userInfo = {}; + } + applyToken(data = {}) { + this.token = data.token || data.Token || ""; + this.refreshToken = data.refreshToken || data.RefreshToken || ""; + this.openId = data.openId || data.OpenId || ""; + this.userInfo = data.userInfo || data.UserInfo || {}; + } + signedHeaders(extra = {}, auth = true) { + const appid = "815d8026-9a52-4445-a42c-a5443134232e"; + const requestId = crypto.randomUUID ? crypto.randomUUID() : $.uuid(); + const ticks = Date.now(); + const headers = { + "User-Agent": defaultUserAgent, + "Referer": `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, + "Accept": "*/*", + "Accept-Language": "zh-CN,zh;q=0.9", + "Content-Type": "application/json", + "requestId": requestId, + "AppId": appid, + "Ticks": String(ticks), + "Sign": md5(appid + ticks + requestId + "2b5c01fb-7640-401a-8188-43a13190a626"), + "source": "Wx", + "UcSource": "30", + "User-from": "xcx", + "version": "2.0.0", + "xweb_xhr": "1", + ...extra, + }; + if (auth) { + headers.Authorization = this.token ? `Bearer ${this.token}` : ""; + headers["X-Authorization"] = this.refreshToken ? `Bearer ${this.refreshToken}` : ""; + } + return headers; + } - } - async getSignInfo() { - let options = { - method: 'GET', - url: `https://interface.skycolorful.com/api/User/IsSignV2`, - headers: { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - } - } - let { data: result } = await this.request(options); - if (result?.Code == '0') { - //打印签到结果 - this.signStatus = result.Data.IsSign + async request(apiPath, { method = "GET", data, params, auth = true } = {}) { + const options = { + method, + url: new URL(apiPath, API_BASE).toString(), + params, + headers: this.signedHeaders({}, auth), + timeout: 15000, + validateStatus: () => true, + }; + if (data !== undefined) options.data = data; + const { data: result, status, headers } = await axios.request(options); + if (headers?.["access-token"]) this.token = headers["access-token"]; + if (headers?.["x-access-token"]) this.refreshToken = headers["x-access-token"]; + if (status === 401 || status === 403) throw new Error(`HTTP ${status}: ${result?.Message || JSON.stringify(result)}`); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (!isSuccess(result)) throw new Error(`${result?.Code ?? ""} ${result?.Message || result?.msg || JSON.stringify(result)}`.trim()); + if (this.token) this.saveCachedToken(); + return result; + } - } else { - } - } - async getUserInfo() { - let options = { - method: 'GET', - url: `https://interface.skycolorful.com/api/User/GetUserInfo`, - headers: { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "sec-fetch-dest": "empty", - } - } - let { data: result } = await this.request(options); - if (result?.Code == '0') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `昵称:${result.Data.NickName}` + `积分:${result.Data.Point}`) - } - } + async getLoginCode() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取 code"); + const { data } = await wechat.getCode(this.raw); + const code = data?.code || data?.data?.code; + if (!code) throw new Error(`wx_server 未返回 code: ${JSON.stringify(data)}`); + return code; + } + + async loginByWxCode() { + try { + const code = await this.getLoginCode(); + const result = await this.request("/api/User/OnLogin", { + method: "POST", + auth: false, + data: { Code: code }, + }); + this.openId = result?.Data?.OpenId || ""; + const token = result?.Data?.Token || result?.Data?.token || ""; + const refreshToken = result?.Data?.RefreshToken || result?.Data?.refreshToken || ""; + if (token) { + this.token = token; + this.refreshToken = refreshToken; + this.saveCachedToken(); + $.log(`账号[${this.index}] CODE登录成功: ${shortToken(this.token)}`); + return; + } + throw new Error(`OnLogin仅返回OpenId=${this.openId || "空"},首次业务登录仍需要微信手机号授权code`); + } catch (e) { + $.log(`账号[${this.index}] CODE登录失败: ${e.message || e}`); + } + } + + async checkToken() { + try { + await this.request("/api/User/RefreshLoginTime", { + method: "POST", + data: { phone: "" }, + }); + return true; + } catch (e) { + return false; + } + } + + async signInV2() { + try { + const result = await this.request("/api/User/SignV2", { + method: "POST", + data: {}, + }); + $.log(`🌸账号[${this.index}]🕊签到${result.Message || "成功"}🎉`); + } catch (e) { + const message = String(e.message || e); + if (/已签到|已签|重复/.test(message)) { + $.log(`🌸账号[${this.index}] 今日已签到`); + return; + } + $.log(`🌸账号[${this.index}] 签到失败:${message}❌`); + if (isTokenError(message)) this.removeCachedToken(); + } + } + + async getSignInfo() { + try { + const result = await this.request("/api/User/IsSignV2"); + this.signStatus = Boolean(result?.Data?.IsSign); + } catch (e) { + const message = String(e.message || e); + $.log(`账号[${this.index}] 查询签到状态失败: ${message}`); + if (isTokenError(message)) this.removeCachedToken(); + } + } + + async getUserInfo() { + try { + const result = await this.request("/api/User/GetUserInfo"); + this.userInfo = result?.Data || {}; + this.saveCachedToken(); + $.log(`🌸账号[${this.index}]昵称:${this.userInfo.NickName || maskPhone(this.userInfo.Mobile) || "未知"} 积分:${this.userInfo.Point ?? "未知"}`); + } catch (e) { + const message = String(e.message || e); + $.log(`账号[${this.index}] 查询用户失败: ${message}`); + if (isTokenError(message)) this.removeCachedToken(); + } + } } - - - - - - - - -!(async () => { - await getNotice() - $.checkEnv(ckName); - - for (let user of $.userList) { - await new Task(user).run(); - } -})() - .catch((e) => console.log(e)) - .finally(() => $.done()); +// 暂停主运行:当前七彩虹后端首次换取业务 Token 需要微信手机号授权 code, +// 现有 wx_server 只能提供 wx.login code,恢复前不要自动执行任务。 +// !(async () => { +// await getNotice(); +// $.checkEnv(ckName); +// +// for (const user of $.userList) { +// await new Task(user).run(); +// } +// })() +// .catch((e) => console.log(e)) +// .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout: 3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) { } - + try { + const options = { + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { + "User-Agent": defaultUserAgent, + }, + timeout: 3000, + }; + const { data: res } = await axios.request(options); + $.log(res); + return res; + } catch (e) {} } diff --git a/daily/fsdlb.js b/daily/fsdlb.js index 3d507dd..3f85dae 100644 --- a/daily/fsdlb.js +++ b/daily/fsdlb.js @@ -1,15 +1,13 @@ /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: fsdlb 微信小程序逢三得利吧 签到积分 cron: 30 9 * * * ------------------------------------------ -#Notice: -https://xiaodian.miyatech.com 小程序逢三得利吧签到积分,使用前请先获取token,方法如下: -打开微信,进入逢三得利吧小程序,点击右下角“我的”, -打开抓包软件抓取https://xiaodian.miyatech.com 请求头的Authorization -抓取到的Authorization值即为token,去掉前面的"bearer ",将剩余部分作为token使用。 +变量名:fsdlb +变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行 +需要配置:wx_server_url、wx_auth ⚠️【免责声明】 ------------------------------------------ @@ -22,113 +20,269 @@ https://xiaodian.miyatech.com 小程序逢三得利吧签到积分,使用前 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("逢三得利吧小程序"); -let ckName = `fsdlb`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const fs = require("fs"); +const path = require("path"); +const WeChatServer = require("../wxapp/wcs.js"); +let ckName = "fsdlb"; +const MINI_APP_ID = "wxb33ed03c6c715482"; +const MERCHANT_APP_NAME = "20230130307725"; +const TEMPLATE_VERSION = "0.6.4"; +const X_VERSION = "2.3.5"; +const CLIENT_ID = "saas-wechat-app"; +const APP_PUBLISH_TYPE = 1; +const MINIAPP_ID = 159; +const API_BASE = "https://xiaodian.miyatech.com/api"; +const TOKEN_CACHE_FILE = path.join(__dirname, "fsdlb_token_cache.json"); +const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) MicroMessenger/3.9.12 MiniProgramEnv/Windows WindowsWechat/WMPF"; +const defaultUserAgent = USER_AGENT; + +const wechat = new WeChatServer({ + url: process.env.wx_server_url || "http://192.168.31.196:8787", + appid: MINI_APP_ID, + auth: process.env.wx_auth || "", +}); + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token).replace(/^bearer\s+/i, ""); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function maskPhone(phone = "") { + return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); +} + +function pickToken(data = {}) { + return data?.tokenInfo?.access_token || data.accessToken || data.access_token || data.token || ""; +} + +function isSuccess(result) { + return String(result?.code) === "200"; +} + +function isTokenError(message) { + return /401|403|M401|M403|token|登录|授权|未登录|无效|过期/i.test(String(message || "")); +} class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = "bearer " + this.user[0]; - + constructor(openid) { + this.index = $.userIdx++; + this.openid = String(openid || "").trim(); + this.token = ""; + this.userInfo = {}; } async run() { - await this.info() - await this.signIn() + const cached = this.getCachedToken(); + if (cached) { + this.applyToken(cached); + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新登录`); + } + } + + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } + + await this.info(); + await this.signIn(); + } + + getCachedToken() { + const cache = readTokenCache(); + return cache[this.openid] || null; + } + + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.openid] = { + accessToken: this.token, + userInfo: this.userInfo, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.openid]) { + delete cache[this.openid]; + writeTokenCache(cache); + } + this.token = ""; + this.userInfo = {}; + } + + applyToken(data = {}) { + this.token = pickToken(data); + this.userInfo = data.userInfo || data.thirdUsrIf || {}; + } + + getHeaders(extra = {}, auth = true) { + const headers = { + componentSend: "1", + "HH-FROM": MERCHANT_APP_NAME, + "HH-APP": MINI_APP_ID, + "HH-VERSION": TEMPLATE_VERSION, + "X-VERSION": X_VERSION, + "Content-Type": "application/json", + appPublishType: APP_PUBLISH_TYPE, + "HH-CI": CLIENT_ID, + "MARKETING-PLAN-NO": "", + "USER-ENTRANCE-CHANNEL": "", + "USER-ENTRANCE-CHANNEL-KEY": "", + "ONE-ID": this.userInfo.oneId || "", + groupPosId: "", + "User-Agent": USER_AGENT, + ...extra, + }; + if (auth && this.token) headers.Authorization = `bearer ${this.token}`; + return headers; + } + + async request({ method = "GET", path: apiPath, data, params, auth = true }) { + const options = { + method, + url: `${API_BASE}${apiPath.startsWith("/") ? apiPath : `/${apiPath}`}`, + headers: this.getHeaders({}, auth), + timeout: 15000, + validateStatus: () => true, + }; + if (params) options.params = params; + if (data !== undefined) options.data = data; + + const { data: result, status } = await axios.request(options); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (!isSuccess(result)) throw new Error(result?.msg || JSON.stringify(result)); + return result.data; + } + + async getLoginCode() { + const { data } = await wechat.getCode(this.openid); + const code = data?.code || data?.data?.code; + if (!code) throw new Error(`wx_server 未返回 code: ${JSON.stringify(data)}`); + return code; + } + + async loginByWxCode() { + try { + const code = await this.getLoginCode(); + const data = await this.request({ + method: "POST", + path: "/user/login/wx-jc", + auth: false, + data: { + jsCode: code, + clientId: CLIENT_ID, + myUnionId: "", + appPublishType: APP_PUBLISH_TYPE, + }, + }); + this.token = pickToken(data); + this.userInfo = data?.thirdUsrIf || {}; + if (!this.token) throw new Error(`登录响应未返回token: ${JSON.stringify(data)}`); + this.saveCachedToken(); + $.log(`账号[${this.index}] 登录成功: ${maskPhone(this.userInfo.phone) || this.userInfo.oid || ""}`); + } catch (e) { + $.log(`账号[${this.index}] 登录失败: ${e.message || e}`); + } + } + + async checkToken() { + try { + await this.request({ + path: "/user/auth/member/integral/union/flow/list", + params: { pageNo: 1, pageSize: 1, dataType: "SCORE" }, + }); + return true; + } catch (e) { + return false; + } } async signIn() { - let options = { - method: 'POST', - url: `https://xiaodian.miyatech.com/api/coupon/auth/signIn`, - headers: { - "X-VERSION": "2.1.3", - "Authorization": `${this.token}`, - "HH-VERSION": "0.2.8", - "HH-FROM": "20230130307725", - "HH-APP": "wxb33ed03c6c715482", - "HH-CI": "saas-wechat-app", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36", - }, - data: { - "miniappId": 159 + try { + const data = await this.request({ + method: "POST", + path: "/coupon/auth/signIn", + data: { + miniappId: MINIAPP_ID, + }, + }); + $.log(`🕊账号[${this.index}] 签到成功:${data?.integralToastText || "成功"}🎉`); + } catch (e) { + const message = String(e.message || e); + if (/已签到|已签|重复/.test(message)) { + $.log(`🕊账号[${this.index}] 今日已签到`); + return; } + $.log(`🕊账号[${this.index}] 签到失败:${message}🚫`); + if (isTokenError(message)) this.removeCachedToken(); } - let { data: result } = await axios.request(options) - if (result?.code == 200) { - //打印签到结果 - $.log(`🕊账号[${this.index}] 签到成功:[${result.data.integralToastText}]🎉`); - } else { - $.log(`🕊账号[${this.index}] 签到失败:${result.msg}🚫`) - } - - - - } + async info() { - let options = { - method: 'GET', - url: `https://xiaodian.miyatech.com/api/user/auth/member/integral/union/flow/list?pageNo=1&pageSize=10&dataType=SCORE`, - headers: { - "X-VERSION": "2.1.3", - "Authorization": `${this.token}`, - "HH-VERSION": "0.2.8", - "HH-FROM": "20230130307725", - "HH-APP": "wxb33ed03c6c715482", - "HH-CI": "saas-wechat-app", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36", - }, - - } - let { data: result } = await axios.request(options) - if (result?.code == 200) { - $.log(`🕊账号[${this.index}] 查询成功:总积分[${result.data.totalScore}]🎉`); - - } else { - $.log(`🕊账号[${this.index}] 查询失败:${result.msg}🚫`) + try { + const data = await this.request({ + path: "/user/auth/member/integral/union/flow/list", + params: { pageNo: 1, pageSize: 10, dataType: "SCORE" }, + }); + $.log(`🕊账号[${this.index}] 查询成功:总积分[${data?.totalScore ?? "未知"}]🎉`); + } catch (e) { + const message = String(e.message || e); + $.log(`🕊账号[${this.index}] 查询失败:${message}🚫`); + if (isTokenError(message)) this.removeCachedToken(); } } - - - - - - - } !(async () => { - await getNotice() + await getNotice(); $.checkEnv(ckName); - for (let user of $.userList) { - await new Task(user).run(); + for (const openid of $.userList) { + await new Task(openid).run(); } })() - .catch((e) => console.log(e)) + .catch((e) => $.log(e.message || e)) .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - -} \ No newline at end of file + try { + const options = { + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { + "User-Agent": defaultUserAgent, + }, + timeout: 3000, + }; + const { data: res } = await axios.request(options); + $.log(res); + return res; + } catch (e) { } +} diff --git a/daily/gyjj.js b/daily/gyjj.js index 6ee7174..328a7c3 100644 --- a/daily/gyjj.js +++ b/daily/gyjj.js @@ -1,18 +1,15 @@ - - /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: 国乐酱酒小程序 cron: 30 9 * * * ------------------------------------------ -#Notice: -变量名:gyjj -国乐酱酒小程序签到,使用前请先获取token,方法如下: -打开微信,进入国乐酱酒小程序,点击右下角“我的”, -打开抓包软件抓取https://member.guoyuejiu.com/api/sign/daily/sign 请求头的Authorization -抓取到的Authorization值即为token +#Notice: +变量名: gyjj +变量值:wx_server 里的 openid/账号标识,多账号&或换行 +需要配置:wx_server_url、wx_auth + ⚠️【免责声明】 ------------------------------------------ 1、此脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 @@ -24,94 +21,254 @@ cron: 30 9 * * * 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("国乐酱酒"); -let ckName = `gyjj`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const fs = require("fs"); +const path = require("path"); +const ckName = "gyjj"; +const strSplitor = "#"; +const MINI_APP_ID = "wxeff120e4d11594c0"; +const PAGE_VERSION = "87"; +const API_BASE = "https://member.guoyuejiu.com/api"; +const TOKEN_CACHE_FILE = path.join(__dirname, "gyjj_token_cache.json"); +const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.70(0x1800462d) NetType/WIFI Language/zh_CN"; + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token || "").replace(/^Mer/i, ""); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function isTokenError(message) { + return /1011|401|403|token|登录|授权|失效|过期/i.test(String(message || "")); +} class Task { constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; - + this.index = $.userIdx++; + this.raw = String(env || "").trim(); + this.user = this.raw.split(strSplitor); + this.accountId = this.user[0].trim(); + this.token = ""; + this.sessionKey = ""; + this.userId = ""; + this.userInfo = {}; + this.isLegacyToken = /^Mer|eyJ/i.test(this.accountId) || this.user.length >= 2; + if (this.isLegacyToken) { + this.token = this.accountId.replace(/^Mer/i, ""); + this.accountId = `legacy:${shortToken(this.token)}`; + } } async run() { + if (this.isLegacyToken) { + $.log(`账号[${this.index}] 检测到旧版token格式,先迁移到缓存`); + this.saveCachedToken(); + } else { + const cached = this.getCachedToken(); + if (cached?.token) { + this.applyToken(cached); + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新CODE登录`); + } + } - await this.sign() - await this.account() + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } + } + + await this.sign(); + await this.account(); + } + + cacheKey() { + return this.isLegacyToken ? this.accountId : this.raw; + } + + getCachedToken() { + const cache = readTokenCache(); + return cache[this.cacheKey()] || null; + } + + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.cacheKey()] = { + token: this.token, + sessionKey: this.sessionKey, + userId: this.userId, + userInfo: this.userInfo, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.cacheKey()]) { + delete cache[this.cacheKey()]; + writeTokenCache(cache); + } + this.token = ""; + this.sessionKey = ""; + this.userId = ""; + this.userInfo = {}; + } + + applyToken(data = {}) { + this.token = String(data.token || data.authorization || "").replace(/^Mer/i, ""); + this.sessionKey = data.sessionKey || ""; + this.userId = data.userId || ""; + this.userInfo = data.userInfo || {}; + } + + headers(extra = {}) { + return { + "content-Type": "application/json", + Authorization: `Mer${this.token}`, + "User-Agent": defaultUserAgent, + Referer: `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, + ...extra, + }; + } + + async request(pathname, { method = "GET", data, params, auth = true, headers = {} } = {}) { + const options = { + method, + url: `${API_BASE}${pathname}`, + params, + data, + headers: auth ? this.headers(headers) : { + "content-Type": "application/json", + "User-Agent": defaultUserAgent, + Referer: `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, + ...headers, + }, + timeout: 20000, + validateStatus: () => true, + }; + const { data: result, status } = await axios.request(options); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (Number(result?.code) !== 0) throw new Error(`${result?.code ?? ""} ${result?.message || JSON.stringify(result)}`.trim()); + return result; + } + + async getOperateData() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取登录数据"); + const url = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, ""); + const { data } = await axios.post(`${url}/wx/operatedata`, { + appid: MINI_APP_ID, + openid: this.raw, + }, { + headers: { auth: process.env.wx_auth }, + timeout: 45000, + validateStatus: () => true, + }); + const result = data?.data || {}; + if (!data?.status || !result.code) throw new Error(`wx_server 未返回code: ${JSON.stringify(data)}`); + let userInfo = {}; + try { + userInfo = JSON.parse(result.data || "{}"); + } catch (e) {} + return { code: result.code, userInfo }; + } + + async loginByWxCode() { + try { + const wxData = await this.getOperateData(); + const user = wxData.userInfo || {}; + const payload = { + avatarUrl: user.avatarUrl || "", + city: user.city || "", + country: user.country || "", + gender: user.gender || 0, + nickName: user.nickName || user.nickname || "微信用户", + province: user.province || "", + code: wxData.code, + source: 2, + }; + const result = await this.request("/user/wxLogin", { + method: "POST", + data: payload, + auth: false, + }); + const data = result?.data || {}; + if (!data.authorization) throw new Error(`登录响应未返回authorization: ${JSON.stringify(result)}`); + this.token = data.authorization; + this.sessionKey = data.sessionKey || ""; + this.userId = data.userId || ""; + this.saveCachedToken(); + $.log(`账号[${this.index}] CODE登录成功: ${shortToken(this.token)}`); + } catch (e) { + $.log(`账号[${this.index}] CODE登录失败: ${e.message || e}`); + } + } + + async checkToken() { + try { + await this.request("/user/info"); + return true; + } catch (e) { + return false; + } } async sign() { try { - - let options = { - method: 'get', - url: `https://member.guoyuejiu.com/api/sign/daily/sign`, - headers: { - "Authorization": this.token, - "User-Agent": 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.70(0x1800462d) NetType/WIFI Language/zh_CN', - "Referer": "https://servicewechat.com/wxeff120e4d11594c0/85/page-frame.html" - }, - - } - let { data: result } = await axios.request(options) - if (result?.code == 0) { - //打印签到结果 - $.log(`🕊账号[${this.index}] 签到成功:签到[${result.data.spanSumDays}]天🎉`); - this.mist = result.data.spanSumDays - } else { - $.log(`🕊账号[${this.index}] 签到失败:${result.message}⛔`) - } + const result = await this.request("/sign/daily/sign"); + $.log(`🕊账号[${this.index}] 签到成功:签到[${result.data?.spanSumDays ?? "未知"}]天🎉`); } catch (e) { - console.log(e); + const message = String(e.message || e); + if (/已签|重复|今日/.test(message)) { + $.log(`🕊账号[${this.index}] 今日已签到`); + return; + } + $.log(`🕊账号[${this.index}] 签到失败:${message}⛔`); + if (isTokenError(message)) this.removeCachedToken(); } } - // async account() { try { - let options = { - method: 'get', - url: `https://member.guoyuejiu.com/api/user/info`, - headers: { - "Authorization": this.token, - "User-Agent": 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.70(0x1800462d) NetType/WIFI Language/zh_CN', - "Referer": "https://servicewechat.com/wxeff120e4d11594c0/85/page-frame.html" - } - - - } - let { data: result } = await axios.request(options) - if (result?.code == 0) { - //打印签到结果 - $.log(`🕊账号[${this.index}] 查询成功:总积分[${result.data.score}]🎉`); - - } else { - $.log(`🕊账号[${this.index}] 查询失败:${result.message}🚫`) - } + const result = await this.request("/user/info"); + this.userInfo = result.data || {}; + this.saveCachedToken(); + $.log(`🕊账号[${this.index}] 查询成功:总积分[${result.data?.score ?? "未知"}]🎉`); } catch (e) { - console.log(e); + const message = String(e.message || e); + $.log(`🕊账号[${this.index}] 查询失败:${message}🚫`); + if (isTokenError(message)) this.removeCachedToken(); } } - - - - - - - } !(async () => { - await getNotice() + await getNotice(); $.checkEnv(ckName); - for (let user of $.userList) { + for (const user of $.userList) { await new Task(user).run(); } })() @@ -119,19 +276,13 @@ class Task { .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - -} \ No newline at end of file + try { + const { data: res } = await axios.request({ + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { "User-Agent": defaultUserAgent }, + timeout: 3000, + }); + $.log(res); + return res; + } catch (e) {} +} diff --git a/daily/haitian.js b/daily/haitian.js index 329ecf4..1541478 100644 --- a/daily/haitian.js +++ b/daily/haitian.js @@ -1,13 +1,14 @@ /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: 海天美味馆小程序 cron: 30 11 * * * ------------------------------------------ -#Notice: -抓取https://cmallapi.xkmm.cn请求头的authorization和uuid参数,填入环境变量,格式如下: -authorization#uuid 多账号&或换行 +#Notice: +变量名 haitian +变量值:wx_server 里的 openid/账号标识,多账号&或换行 +需要配置:wx_server_url、wx_auth ⚠️【免责声明】 ------------------------------------------ @@ -17,235 +18,447 @@ authorization#uuid 多账号&或换行 4、此脚本涉及应用与本人无关,本人对因此引起的任何隐私泄漏或其他后果不承担任何责任。 5、本人对任何脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 6、如果任何单位或个人认为此脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此脚本。 -7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 +7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("海天美味馆小程序"); -let ckName = `haitian`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const fs = require("fs"); +const path = require("path"); +const ckName = "haitian"; +const strSplitor = "#"; +const MINI_APP_ID = "wx7a890ea13f50d7b6"; +const PAGE_VERSION = "772"; +const API_BASE = "https://cmallapi.xkmm.cn/buyer-api"; +const BASE_API = "https://cmallapi.xkmm.cn/base-api"; +const TOKEN_CACHE_FILE = path.join(__dirname, "haitian_token_cache.json"); +const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.70(0x1800462d) NetType/WIFI Language/zh_CN"; + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token || ""); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function maskPhone(phone = "") { + return String(phone || "").replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); +} + +function randomUuid(len = 20) { + const chars = "abcdef0123456789"; + let value = ""; + for (let i = 0; i < len; i++) value += chars[Math.floor(Math.random() * chars.length)]; + return value; +} + +function isTokenError(message) { + return /401|403|token|登录|授权|失效|过期|no-show-toast/i.test(String(message || "")); +} class Task { constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; + this.index = $.userIdx++; + this.raw = String(env || "").trim(); + this.user = this.raw.split(strSplitor); + this.accountId = this.user[0].trim(); + this.token = ""; + this.refreshToken = ""; + this.communityToken = ""; + this.uuid = randomUuid(); this.activity_code = ""; this.userFlag = false; - this.headers = {}; - this.uuid = this.user[1] - this.headers = { - "Connection": "keep-alive", - "envVersion": "release", - "content-type": "application/json", - "Authorization": "" + this.token, - "uuid": "" + this.uuid, - "Accept-Encoding": "gzip,compress,br,deflate", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.70(0x1800462d) NetType/WIFI Language/zh_CN", - "Referer": "https://servicewechat.com/wx7a890ea13f50d7b6/765/page-frame.html" + this.userInfo = {}; + this.isLegacyToken = this.user.length >= 2; + if (this.isLegacyToken) { + this.token = this.user[0].trim(); + this.uuid = this.user[1].trim() || this.uuid; + this.accountId = `legacy:${shortToken(this.token)}`; } } async run() { - - await this.getUserInfo() - - if (this.userFlag) { - await this.task_1() - - await this.task_2() - await this.getSignActivity() - - if (this.activity_code) { - await this.signIn() - await this.getLotteryTaskList() - await this.getLotteryNum() - } - } - } - async getUserInfo() { - let options = { - method: 'GET', - url: `https://cmallapi.xkmm.cn/buyer-api/members`, - headers: this.headers - } - let { data: result } = await axios.request(options); - if (result?.member_id) { - $.log(`🌸账号[${this.index}] [${result.member_id}][${result.mobile}] 当前积分[${result.consum_point}]` + `🎉`); - this.userFlag = true; + if (this.isLegacyToken) { + $.log(`账号[${this.index}] 检测到旧版 authorization#uuid 格式,先迁移到缓存`); + this.saveCachedToken(); } else { - $.log(`🌸账号[${this.index}] 查询-失败:${JSON.stringify(result)}❌`) - } - } - async getSignActivity() { - let options = { - method: 'GET', - url: `https://cmallapi.xkmm.cn/buyer-api/sign/activity/code?activityCode=`, - headers: this.headers - } - let { data: result } = await axios.request(options); - if (result?.activity_code) { - $.log(`获取活动信息成功,活动ID:${result.activity_code}`) - this.activity_code = result.activity_code - } - else { - $.log(`获取活动信息失败:${JSON.stringify(result)}❌`) - } - } - async signIn() { - try { - let options = { - url: `https://cmallapi.xkmm.cn/buyer-api/sign/activity/sign`, - method: "POST", - headers: this.headers, - data: JSON.stringify({ "activity_code": this.activity_code, "fill_date": "" }) - } - let { data: result } = await axios.request(options) - if (result.is_sign == true) { - $.log(`签到成功`) - } else { - $.log(`签到失败 [${result.message}]`) - } - } catch (e) { - $.log(`签到失败 [${JSON.stringify(e.message)}]`) - } - - - } - //浏览页面 - async task_1() { - let options = { - url: `https://cmallapi.xkmm.cn/buyer-api/members/browsePage`, - method: "POST", - headers: this.headers, - data: JSON.stringify({}) - } - let { data: result } = await axios.request(options) - if (result?.code == 200) { - $.log(`浏览界面 操作成功`) - } else { - $.log(`浏览界面 操作失败[${JSON.stringify(result)}]`) - } - - } - //浏览社区 - async task_2() { - let options = { - url: `https://cmallapi.xkmm.cn/buyer-api/members/commnity/brosing/duration/add?seconds=10`, - method: "POST", - headers: this.headers, - data: JSON.stringify({}) - } - let { data: result } = await axios.request(options) - if (result?.statusCode == 200) { - $.log(`浏览社区 操作成功`) - } else { - $.log(`浏览社区 操作失败 貌似是正常的 [${result}]`) - } - } - async doLottery() { - let options = { - method: 'GET', - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/activity/extract?activityCode=jfcj${this.activity_code}`, - headers: this.headers - } - let { data: result } = await axios.request(options); - if (result?.lucky_record_vo) { - $.log(`🌸账号[${this.index}]` + `🕊抽奖结果:${result.lucky_record_vo.prize_name}🎉`); - } else { - $.log(`🌸账号[${this.index}] 抽奖-失败:${JSON.stringify(result)}❌`) - } - } - async getLotteryTaskList() { - let options = { - method: 'GET', - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/task/package/jfcj${this.activity_code}`, - headers: this.headers - } - let { data: result } = await axios.request(options); - if (result?.member_id) { - $.log(`🌸账号[${this.index}]` + `🕊抽奖次数任务🎉`); - for (let task of result.task_list) { - if (task.today_available_task_number >= 1 && task.today_obtained_task_number < task.today_available_task_number) { - - if (task.task_key == "LOGIN") { - $.log(`🌸账号[${this.index}]` + `🕊正在完成任务:${task.task_name}🎉`); - - let options = { - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/task/getLoginOpporturnity/jfcj${this.activity_code}`, - method: "PUT", - headers: this.headers, - data: JSON.stringify({}) - } - let { data: result } = await axios.request(options) - $.log(result) - } - if (task.task_key == "SHARE") { - - } - if (task.task_key == "BROWSE_PAGE_TASK") { - $.log(`🌸账号[${this.index}]` + `🕊正在完成任务:${task.task_name}🎉`); - - await this.lotteryTaskBrowser(task.link) - - } + const cached = this.getCachedToken(); + if (cached?.accessToken) { + this.applyToken(cached); + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新CODE登录`); } } - } else { - $.log(`🌸账号[${this.index}] 抽奖次数任务-失败:${JSON.stringify(result)}❌`) - } - } - async lotteryTaskBrowser(link) { - let optionsStart = { - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/task/browse/page/start/jfcj${this.activity_code}?pageUrl=${link}`, - method: "GET", - headers: this.headers - } - await axios.request(optionsStart) - await $.wait(20 * 1000) - let optionsEnd = { - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/task/browse/page/end/jfcj${this.activity_code}?pageUrl=${link}`, - method: "GET", - headers: this.headers - } - await axios.request(optionsEnd) - } - async getLotteryNum() { - let options = { - url: `https://cmallapi.xkmm.cn/buyer-api/lucky/activity/opporturnity?activityCode=jfcj${this.activity_code}`, - method: "GET", - headers: this.headers - } - let { data: result } = await axios.request(options) - if (result.can_use > 0) { - $.log(`当前剩余抽奖次数:${result.can_use}`) - for (let can = 0; can < result.can_use; can++) { - await this.doLottery() + + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; } - } else { - $.log(`当前剩余抽奖次数0`) } + await this.getUserInfo(); + if (!this.userFlag) return; + + await this.task_1(); + await this.task_2(); + await this.getSignActivity(); + + if (this.activity_code) { + await this.signIn(); + await this.getLotteryTaskList(); + await this.getLotteryNum(); + } } + cacheKey() { + return this.isLegacyToken ? this.accountId : this.raw; + } + getCachedToken() { + const cache = readTokenCache(); + return cache[this.cacheKey()] || null; + } + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.cacheKey()] = { + accessToken: this.token, + refreshToken: this.refreshToken, + communityToken: this.communityToken, + uuid: this.uuid, + userInfo: this.userInfo, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.cacheKey()]) { + delete cache[this.cacheKey()]; + writeTokenCache(cache); + } + this.token = ""; + this.refreshToken = ""; + this.communityToken = ""; + this.userInfo = {}; + } + applyToken(data = {}) { + this.token = data.accessToken || data.token || ""; + this.refreshToken = data.refreshToken || ""; + this.communityToken = data.communityToken || ""; + this.uuid = data.uuid || this.uuid; + this.userInfo = data.userInfo || {}; + } + headers(extra = {}, auth = true, isJson = false) { + const headers = { + "Connection": "keep-alive", + "envVersion": "release", + "Content-Type": isJson ? "application/json" : "application/x-www-form-urlencoded", + "uuid": this.uuid, + "Accept-Encoding": "gzip,compress,br,deflate", + "User-Agent": defaultUserAgent, + "Referer": `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, + ...extra, + }; + if (auth) { + headers.Authorization = this.token; + if (this.communityToken) headers["X-Haday-Token"] = this.communityToken; + } + return headers; + } + async request(pathname, { method = "GET", params, data, auth = true, isJson = false, base = API_BASE } = {}) { + const options = { + method, + url: `${base}${pathname}`, + params, + headers: this.headers({}, auth, isJson), + timeout: 20000, + validateStatus: () => true, + }; + if (data !== undefined) options.data = isJson ? data : new URLSearchParams(data).toString(); + const { data: result, status } = await axios.request(options); + if (status === 401 || status === 403) throw new Error(`HTTP ${status}: ${result?.message || JSON.stringify(result)}`); + if (status < 200 || status >= 300) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (result && typeof result === "object" && result.code && !["200", "0"].includes(String(result.code))) { + throw new Error(`${result.code} ${result.message || result.msg || JSON.stringify(result)}`.trim()); + } + return result; + } + async getOperateData() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取登录数据"); + const url = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, ""); + const { data } = await axios.post(`${url}/wx/operatedata`, { + appid: MINI_APP_ID, + openid: this.accountId, + }, { + headers: { auth: process.env.wx_auth }, + timeout: 45000, + validateStatus: () => true, + }); + const result = data?.data || {}; + if (!data?.status || !result.code || !result.encryptedData || !result.iv) { + throw new Error(`wx_server 未返回完整登录数据: ${JSON.stringify(data)}`); + } + return result; + } + + async getArticleIds() { + try { + const result = await this.request("/pages/REGISTRATION_AGREEMENT,PRIVACY_PROTECTION_CLAUSE/articles/list", { + base: BASE_API, + auth: false, + }); + const ids = Array.isArray(result) ? result.map(item => item.article_id).filter(Boolean) : []; + return ids.join(","); + } catch (e) { + $.log(`账号[${this.index}] 获取协议ID失败,继续登录: ${e.message || e}`); + return ""; + } + } + + async loginByWxCode() { + try { + const wxData = await this.getOperateData(); + const articleIds = await this.getArticleIds(); + const params = { + edata: wxData.encryptedData, + iv: wxData.iv, + code: wxData.code, + uuid: this.uuid, + article_ids: articleIds, + app_versions: "1.0.0", + }; + const result = await this.request("/wechat/mini/phoneNew/login", { + method: "POST", + params, + auth: false, + }); + const token = result?.access_token || result?.accessToken || ""; + if (!result?.success || !token) throw new Error(`登录响应未返回token: ${JSON.stringify(result)}`); + this.token = token; + this.refreshToken = result.refresh_token || result.refreshToken || ""; + await this.loginCommunityToken().catch(() => {}); + this.saveCachedToken(); + $.log(`账号[${this.index}] CODE登录成功: ${shortToken(this.token)}`); + } catch (e) { + $.log(`账号[${this.index}] CODE登录失败: ${e.message || e}`); + } + } + + async loginCommunityToken() { + if (!this.token) return; + const result = await this.request("/wx/auth/loginByToken", { + method: "POST", + base: "https://cmallwap.xkmm.cn/haday", + data: { access_token: this.token }, + auth: false, + isJson: true, + }); + this.communityToken = result?.data || result || ""; + this.saveCachedToken(); + } + + async checkToken() { + try { + await this.request("/members"); + return true; + } catch (e) { + return false; + } + } + + async getUserInfo() { + try { + const result = await this.request("/members"); + if (result?.member_id) { + this.userInfo = result; + this.saveCachedToken(); + $.log(`🌸账号[${this.index}] [${result.member_id}][${maskPhone(result.mobile)}] 当前积分[${result.consum_point}]🎉`); + this.userFlag = true; + } else { + $.log(`🌸账号[${this.index}] 查询-失败:${JSON.stringify(result)}❌`); + } + } catch (e) { + const message = e.message || e; + $.log(`🌸账号[${this.index}] 查询-失败:${message}❌`); + if (isTokenError(message)) this.removeCachedToken(); + } + } + + async getSignActivity() { + try { + const result = await this.request("/sign/activity/code", { + params: { activityCode: "" }, + }); + if (result?.activity_code) { + $.log(`获取活动信息成功,活动ID:${result.activity_code}`); + this.activity_code = result.activity_code; + } else { + $.log(`获取活动信息失败:${JSON.stringify(result)}❌`); + } + } catch (e) { + $.log(`获取活动信息失败:${e.message || e}❌`); + } + } + + async signIn() { + try { + const result = await this.request("/sign/activity/sign", { + method: "POST", + isJson: true, + data: { activity_code: this.activity_code, fill_date: "" }, + }); + if (result?.is_sign === true || result?.success === true) { + $.log("签到成功"); + } else { + $.log(`签到失败 [${result?.message || JSON.stringify(result)}]`); + } + } catch (e) { + const message = String(e.message || e); + if (/已签|重复/.test(message)) { + $.log("今日已签到"); + return; + } + $.log(`签到失败 [${message}]`); + } + } + + async task_1() { + try { + const result = await this.request("/members/browsePage", { + method: "POST", + data: {}, + }); + if (result?.code == 200 || result?.statusCode == 200 || result === "") { + $.log("浏览界面 操作成功"); + } else { + $.log(`浏览界面 操作失败[${JSON.stringify(result)}]`); + } + } catch (e) { + $.log(`浏览界面 操作失败[${e.message || e}]`); + } + } + + async task_2() { + try { + const result = await this.request("/members/commnity/brosing/duration/add", { + method: "POST", + params: { seconds: 10 }, + data: {}, + }); + if (result?.statusCode == 200 || result?.code == 200 || result === "") { + $.log("浏览社区 操作成功"); + } else { + $.log(`浏览社区 操作失败 貌似是正常的 [${JSON.stringify(result)}]`); + } + } catch (e) { + $.log(`浏览社区 操作失败 貌似是正常的 [${e.message || e}]`); + } + } + + async doLottery() { + try { + const result = await this.request("/lucky/activity/extract", { + params: { activityCode: `jfcj${this.activity_code}` }, + }); + if (result?.lucky_record_vo) { + $.log(`🌸账号[${this.index}]🕊抽奖结果:${result.lucky_record_vo.prize_name}🎉`); + } else { + $.log(`🌸账号[${this.index}] 抽奖-失败:${JSON.stringify(result)}❌`); + } + } catch (e) { + $.log(`🌸账号[${this.index}] 抽奖-失败:${e.message || e}❌`); + } + } + + async getLotteryTaskList() { + try { + const result = await this.request(`/lucky/task/package/jfcj${this.activity_code}`); + if (result?.member_id) { + $.log(`🌸账号[${this.index}]🕊抽奖次数任务🎉`); + for (const task of result.task_list || []) { + if (task.today_available_task_number >= 1 && task.today_obtained_task_number < task.today_available_task_number) { + if (task.task_key === "LOGIN") { + $.log(`🌸账号[${this.index}]🕊正在完成任务:${task.task_name}🎉`); + const res = await this.request(`/lucky/task/getLoginOpporturnity/jfcj${this.activity_code}`, { + method: "PUT", + data: {}, + }); + $.log(JSON.stringify(res)); + } + if (task.task_key === "BROWSE_PAGE_TASK") { + $.log(`🌸账号[${this.index}]🕊正在完成任务:${task.task_name}🎉`); + await this.lotteryTaskBrowser(task.link); + } + } + } + } else { + $.log(`🌸账号[${this.index}] 抽奖次数任务-失败:${JSON.stringify(result)}❌`); + } + } catch (e) { + $.log(`🌸账号[${this.index}] 抽奖次数任务-失败:${e.message || e}❌`); + } + } + + async lotteryTaskBrowser(link) { + await this.request(`/lucky/task/browse/page/start/jfcj${this.activity_code}`, { + params: { pageUrl: link }, + }).catch(() => {}); + await $.wait(20 * 1000); + await this.request(`/lucky/task/browse/page/end/jfcj${this.activity_code}`, { + params: { pageUrl: link }, + }).catch(() => {}); + } + + async getLotteryNum() { + try { + const result = await this.request("/lucky/activity/opporturnity", { + params: { activityCode: `jfcj${this.activity_code}` }, + }); + if (result?.can_use > 0) { + $.log(`当前剩余抽奖次数:${result.can_use}`); + for (let can = 0; can < result.can_use; can++) { + await this.doLottery(); + } + } else { + $.log("当前剩余抽奖次数0"); + } + } catch (e) { + $.log(`查询抽奖次数失败:${e.message || e}`); + } + } } !(async () => { - await getNotice() + await getNotice(); $.checkEnv(ckName); - - for (let user of $.userList) { + for (const user of $.userList) { await new Task(user).run(); } })() @@ -253,19 +466,13 @@ class Task { .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - -} \ No newline at end of file + try { + const { data: res } = await axios.request({ + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { "User-Agent": defaultUserAgent }, + timeout: 3000, + }); + $.log(res); + return res; + } catch (e) {} +} diff --git a/daily/hezj.js b/daily/hezj.js index a091ddc..1d5e49d 100644 --- a/daily/hezj.js +++ b/daily/hezj.js @@ -7,7 +7,8 @@ cron: 30 7 * * * ------------------------------------------ #Notice: 变量名hezj -抓取https://zjrs.haier.net请求头accounttoken 多账户&或换行 +变量值填写 wx_server 里的 openid/账号标识,多账户&或换行 +需要配置 wx_server_url、wx_auth ⚠️【免责声明】 ------------------------------------------ @@ -25,47 +26,144 @@ const $ = new Env("海尔智家"); let ckName = `hezj`; const strSplitor = "#"; const axios = require("axios"); +const crypto = require("crypto"); +const WeChatCodeServer = require("../wxapp/wcs"); const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const MINI_APP_ID = "wxe24b2f1f4e378891"; +const PAGE_VERSION = "475"; +const HA_APP_ID = "MB-SHEZJAPPWXXCX-0000"; +const HA_APP_KEY = "79ce99cc7f9804663939676031b8a427"; +const API_HOST = "https://zj.haier.net"; + +const wechat = new WeChatCodeServer({ + url: process.env.wx_server_url || "http://192.168.31.196:8787", + appid: MINI_APP_ID, + auth: process.env.wx_auth || "", +}); + +function sign256(path, body, timestamp) { + const bodyStr = body ? JSON.stringify(body) : ""; + return crypto.createHash("sha256").update(path + bodyStr + HA_APP_ID + HA_APP_KEY + timestamp).digest("hex"); +} + +function maskToken(token = "") { + if (!token) return ""; + return token.length > 14 ? `${token.slice(0, 6)}...${token.slice(-6)}` : token; +} class Task { constructor(env) { this.index = $.userIdx++ this.user = env.split(strSplitor); - this.token = this.user[0]; + this.openid = this.user[0].trim(); + this.token = ""; + this.clientId = `${Date.now()}${$.randomString(12)}`; } request(options) { + const path = options.path || new URL(options.url).pathname; + const timestamp = Date.now(); let baseHeaers = { "host": "zj.haier.net", - "content-type": "application/json", - "accounttoken": this.token, - "appid": "MB-UZHSH-0001", + "Content-Type": "application/json;charset=UTF-8", + "appId": HA_APP_ID, + "appKey": HA_APP_KEY, + "timestamp": timestamp, + "platForm": "sc-mp-wx-zjapp", + "ENV": "", + "accessToken": options.isHeaderDelToken ? "" : this.token, + "accountToken": options.isHeaderDelToken ? "" : this.token, + "ak": options.isHeaderDelToken ? "" : this.token, + "clientId": this.clientId, "accept": "*/*", - "sec-fetch-site": "same-site", - "appversion": "10.18.0", - //"clientid": "5D6FD623-11A7-4B62-88AD-D3D62D07B72F", "accept-language": "zh-CN,zh-Hans;q=0.9", - "accept-encoding": "gzip, deflate, br", - "sec-fetch-mode": "cors", - "origin": "https://zjrs.haier.net", - "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_15 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Hainer/Haier Simulation/Nebula App/Uplus Nebula mPaaSClient", - "referer": "https://zjrs.haier.net/", - "sec-fetch-dest": "empty" + "user-agent": defaultUserAgent, + "referer": `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, } - options.headers = Object.assign(baseHeaers, options.headers) + if (options.isSign256) { + baseHeaers.sign = sign256(path, options.data, timestamp); + } + options.headers = Object.assign(baseHeaers, options.headers || {}); + delete options.path; + delete options.isSign256; + delete options.isHeaderDelToken; return axios.request(options) } async run() { - + await this.loginByWxCode(); + if (!this.token) return; await this.pointInfo() await this.signIn() } + + async getLoginCode() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取 code"); + const { data } = await wechat.getCode(this.openid); + const code = data?.code || data?.data?.code; + if (!code) throw new Error(`wx_server 未返回 code: ${JSON.stringify(data)}`); + return code; + } + + async loginByWxCode() { + try { + const code = await this.getLoginCode(); + const tokenInfo = await this.jscode2session(code); + const accountToken = tokenInfo?.accountToken; + if (!accountToken) throw new Error(`登录响应未返回 accountToken: ${JSON.stringify(tokenInfo)}`); + await this.queryUserInfo(accountToken); + this.token = accountToken; + $.log(`账号[${this.index}] CODE登录成功: ${maskToken(this.token)}`); + } catch (e) { + $.log(`账号[${this.index}] CODE登录失败: ${e.message || e}`); + } + } + + async jscode2session(code) { + const path = "/api-gw/oauthserver/applet/v1/jscode2session"; + let options = { + method: "POST", + url: API_HOST + path, + path, + isSign256: true, + isHeaderDelToken: true, + data: { code }, + }; + const { data: result } = await this.request(options); + if (result?.retCode !== "00000" && result?.code !== 200 && !result?.success) { + throw new Error(result?.retInfo || result?.message || JSON.stringify(result)); + } + return result?.data?.tokenInfo || result?.data || {}; + } + + async queryUserInfo(accountToken) { + const path = "/api-gw/oauthserver/applet/v1/userinfo/query"; + let options = { + method: "POST", + url: API_HOST + path, + path, + isSign256: true, + isHeaderDelToken: true, + data: { accountToken }, + }; + const { data: result } = await this.request(options); + if (result?.retCode !== "00000" && result?.code !== 200 && !result?.success) { + throw new Error(result?.retInfo || result?.message || JSON.stringify(result)); + } + const info = result?.data?.userinfo || {}; + const name = info.nickName || info.nickname || info.userName || "未知用户"; + const phone = info.mobile || info.phoneNumber || ""; + $.log(`账号[${this.index}] 用户: ${name}${phone ? ` ${phone.slice(0, 3)}****${phone.slice(-4)}` : ""}`); + return info; + } + async pointInfo() { + const path = "/zjapi/zjBaseServer/signDetail/getUserPointsAndWallet"; let options = { method: 'POST', - url: `https://zj.haier.net/zjapi/zjBaseServer/signDetail/getUserPointsAndWallet`, + url: API_HOST + path, + path, headers: {}, data: { @@ -82,9 +180,11 @@ class Task { } async signIn() { + const path = "/api-gw/zjBaseServer/daily/sign"; let options = { method: 'POST', - url: `https://zj.haier.net/api-gw/zjBaseServer/daily/sign`, + url: API_HOST + path, + path, headers: {}, data: { diff --git a/daily/iqoo.js b/daily/iqoo.js index 060216d..43e077c 100644 --- a/daily/iqoo.js +++ b/daily/iqoo.js @@ -1,15 +1,14 @@ - /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: IQOO社区小程序 积分脚本 cron: 30 8 * * * ------------------------------------------ -#Notice: -变量名iqoo -抓取方法:https://bbs-api.iqoo.com请求头authorization 去掉Bearer -多账户&或换行 +#Notice: +变量名 iqoo +变量值:wx_server 里的 openid/账号标识,多账户&或换行 +需要配置:wx_server_url、wx_auth ⚠️【免责声明】 ------------------------------------------ @@ -22,274 +21,362 @@ cron: 30 8 * * * 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("iqoo社区"); -let ckName = `iqoo`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const crypto = require("crypto"); +const fs = require("fs"); +const path = require("path"); +const ckName = "iqoo"; +const strSplitor = "#"; +const MINI_APP_ID = "wxcf4266fbc9463132"; +const PAGE_VERSION = "253"; +const API_BASE = "https://bbs-api.iqoo.com/api/"; +const TOKEN_CACHE_FILE = path.join(__dirname, "iqoo_token_cache.json"); +const SIGN_SECRET = "2618194b0ebb620055e19cf9811d3c13"; +const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027"; +const defaultUserAgent = USER_AGENT; + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token || "").replace(/^Bearer\s+/i, ""); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function isSuccess(result) { + return Number(result?.Code) === 0; +} + +function isTokenError(message) { + return /-4011|401|403|token|登录|授权|请先登录|跳转到登录页|访客没有权限/i.test(String(message || "")); +} + +function maskName(value = "") { + const text = String(value || ""); + return text.length > 10 ? `${text.slice(0, 4)}***${text.slice(-3)}` : text; +} class Task { constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; - this.postId = '' - this.threadId = '' - + this.index = $.userIdx++; + this.user = String(env || "").trim().split(strSplitor); + this.openid = this.user[0].trim(); + this.token = ""; + this.refreshToken = ""; + this.userId = ""; + this.userInfo = {}; + this.postId = ""; + this.threadId = ""; + this.visitor = crypto.createHash("md5").update(this.openid || String(Date.now())).digest("hex"); } - request(options) { - let baseHeaders = { - sign: this.getSign(options.method, options.url.split('https://bbs-api.iqoo.com')[1].split("?")[0], options.data ? options.data : ''), - 'authorization': 'Bearer ' + this.token, - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027", - "x-platform": "mini", - "x-visitor": "b89d8b0ffa920e96f3cb4c1f69ec2c66" - } - options.headers = Object.assign(baseHeaders, options.headers) - - - return axios.request(options) - } - async userInfo() { - let options = { - method: 'GET', - url: `https://bbs-api.iqoo.com/api/v3/v3/user?userId=${this.id}`, - headers: { - - }, - data: { - userId: this.id - } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 获取用户信息成功`) - } - } async run() { - await this.getTreadList() + const cached = this.getCachedToken(); + if (cached?.accessToken) { + this.applyToken(cached); + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新登录`); + } + } + + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } + + await this.userInfoRequest(); + await this.getTreadList(); if (this.threadId && this.postId) { - await this.likePost(this.threadId, this.postId) - await $.wait(5000) - await this.sharePost(this.threadId) - await $.wait(5000) - - await this.viewPost(this.threadId) - await $.wait(5000) - - await this.commonPost(this.threadId) + await this.likePost(this.threadId, this.postId); + await $.wait(5000); + await this.sharePost(this.threadId); + await $.wait(5000); + await this.viewPost(this.threadId); + await $.wait(5000); + await this.commonPost(this.threadId); } - await this.getDrawNum() - await this.signIn() + await this.getDrawNum(); + await this.signIn(); } + + getCachedToken() { + const cache = readTokenCache(); + return cache[this.openid] || null; + } + + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.openid] = { + accessToken: this.token, + refreshToken: this.refreshToken, + userId: this.userId, + userInfo: this.userInfo, + visitor: this.visitor, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.openid]) { + delete cache[this.openid]; + writeTokenCache(cache); + } + this.token = ""; + this.refreshToken = ""; + this.userId = ""; + this.userInfo = {}; + } + + applyToken(data = {}) { + this.token = data.accessToken || data.token || ""; + this.refreshToken = data.refreshToken || ""; + this.userId = data.userId || data.uid || data.userInfo?.userId || data.userInfo?.uid || ""; + this.userInfo = data.userInfo || data.user || {}; + this.visitor = data.visitor || this.visitor; + } + + queryString(data = {}) { + const list = []; + for (const [key, value] of Object.entries(data || {})) { + if (value === undefined || value === null) continue; + list.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + } + return list.join("&"); + } + + getSign(method, apiPath, data = {}) { + const upperMethod = method.toUpperCase(); + const timestamp = Math.floor(Date.now() / 1000); + const query = upperMethod === "GET" ? this.queryString(data) : ""; + const body = upperMethod === "GET" ? "" : JSON.stringify(data || {}); + const raw = `${upperMethod}&/api/${apiPath}&${query}&${body}&appid=1002×tamp=${timestamp}`; + const signature = crypto.createHmac("sha256", SIGN_SECRET).update(raw).digest("base64"); + return `IQOO-HMAC-SHA256 appid=1002,timestamp=${timestamp},signature=${signature}`; + } + + headers(method, apiPath, data, extra = {}) { + return { + Authorization: this.token ? `Bearer ${this.token}` : "Bearer ", + "X-Visitor": this.visitor, + "X-Platform": "mini", + SIGN: this.getSign(method, apiPath, data), + "Content-Nonce": "", + "User-Agent": USER_AGENT, + Referer: `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`, + ...extra, + }; + } + + async request(apiPath, data = {}, options = {}) { + const method = (options.method || "POST").toUpperCase(); + const req = { + method, + url: new URL(apiPath, API_BASE).toString(), + headers: this.headers(method, apiPath, data, options.headers || {}), + timeout: 20000, + validateStatus: () => true, + }; + if (method === "GET") req.params = data; + else req.data = data || {}; + + const { data: result, status } = await axios.request(req); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (!isSuccess(result)) throw new Error(`${result?.Code ?? ""} ${result?.Message || JSON.stringify(result)}`.trim()); + return result; + } + + async getOperateData() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取登录数据"); + const url = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, ""); + const { data } = await axios.post(`${url}/wx/operatedata`, { + appid: MINI_APP_ID, + openid: this.openid, + }, { + headers: { auth: process.env.wx_auth }, + timeout: 45000, + }); + const result = data?.data || {}; + if (!data?.status || !result.code || !result.encryptedData || !result.iv) { + throw new Error(`wx_server 未返回完整登录数据: ${JSON.stringify(data)}`); + } + return result; + } + + async loginByWxCode() { + try { + const wxData = await this.getOperateData(); + const payload = { + code: wxData.code, + encryptedData: wxData.encryptedData, + iv: wxData.iv, + from: 46, + }; + const result = await this.request("v3/users/vivo/mini", payload); + const data = result?.Data || {}; + const token = data.accessToken || data.token || ""; + if (!token) throw new Error(`登录响应未返回token: ${JSON.stringify(result)}`); + this.token = token; + this.refreshToken = data.refreshToken || ""; + this.userId = data.userId || data.uid || ""; + this.userInfo = data.user || data; + this.saveCachedToken(); + $.log(`账号[${this.index}] CODE登录成功: ${maskName(this.userInfo?.nickname || this.userInfo?.username || this.userId)}`); + } catch (e) { + $.log(`账号[${this.index}] CODE登录失败: ${e.message || e}`); + } + } + + async checkToken() { + try { + if (!this.userId) return false; + await this.request("v3/user", { userId: this.userId }, { method: "GET" }); + return true; + } catch (e) { + return false; + } + } + + async userInfoRequest() { + if (!this.userId) return; + try { + const result = await this.request("v3/user", { userId: this.userId }, { method: "GET" }); + this.userInfo = result?.Data || this.userInfo; + this.saveCachedToken(); + const name = this.userInfo?.nickname || this.userInfo?.username || this.userInfo?.userName || this.userId; + const score = this.userInfo?.score ?? this.userInfo?.points ?? this.userInfo?.coolCoin ?? "未知"; + $.log(`账号[${this.index}] 用户:${maskName(name)} 酷币:${score}`); + } catch (e) { + const message = String(e.message || e); + $.log(`账号[${this.index}] 获取用户信息失败:${message}`); + if (isTokenError(message)) this.removeCachedToken(); + } + } + async getDrawNum() { - let options = { - method: 'GET', - url: `https://bbs-api.iqoo.com/api/v3/today.draw.count`, - headers: { - - }, - data: {} - }; - let { data: result } = await this.request(options); - if (result?.Code == '0' && result.Data.count == 0) { - //可以免费抽一次 - await this.draw() - } else { - //已签到 - $.log(`${this.index}账号[${this.index}] 抽奖过`) - + try { + const result = await this.request("v3/today.draw.count", {}, { method: "GET" }); + if (result?.Data?.count === 0) { + await this.draw(); + } else { + $.log(`账号[${this.index}] 今日已抽奖`); + } + } catch (e) { + $.log(`账号[${this.index}] 查询抽奖次数失败:${e.message || e}`); } } + async draw() { - let options = { - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/luck.draw`, - headers: { - - }, - data: {} - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 抽奖成功 获得${result.Data.prize_name}`) + try { + const result = await this.request("v3/luck.draw", {}); + $.log(`账号[${this.index}] 抽奖成功 获得${result?.Data?.prize_name || "奖励"}`); + } catch (e) { + $.log(`账号[${this.index}] 抽奖失败:${e.message || e}`); } } + async signIn() { - let options = { - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/sign`, - headers: { - - }, - data: { "from": "group" } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `🕊当前已签到${result.Data.serialDays}天🎉 获得积分${result.Data.score} 当前积分${result.Data.scoreCount}`); - } else { - $.log(`🌸账号[${this.index}] 签到-失败:${result.Message}❌`) + try { + const result = await this.request("v3/sign", { from: "group" }); + $.log(`🌸账号[${this.index}]🕊当前已签到${result.Data.serialDays}天🎉 获得积分${result.Data.score} 当前积分${result.Data.scoreCount}`); + } catch (e) { + const message = String(e.message || e); + if (/已签|重复|今日|13006/.test(message)) { + $.log(`🌸账号[${this.index}] 今日已签到`); + return; + } + $.log(`🌸账号[${this.index}] 签到失败:${message}❌`); + if (isTokenError(message)) this.removeCachedToken(); } - - - - } + async likePost(threadId, postId) { - let { data: likeResult } = await this.request({ - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/posts.update`, - headers: { - - }, - data: { "id": threadId, "postId": postId, "data": { "attributes": { "isLiked": true } } } - }); - if (likeResult?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 帖子点赞成功`) - } else { - $.log(`${this.index}账号[${this.index}] 帖子点赞失败:${likeResult.Message}❌`) + try { + await this.request("v3/posts.update", { id: threadId, postId, data: { attributes: { isLiked: true } } }); + $.log(`账号[${this.index}] 帖子点赞成功`); + } catch (e) { + $.log(`账号[${this.index}] 帖子点赞失败:${e.message || e}❌`); } - let { data: unlikeResult } = await this.request({ - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/posts.update`, - headers: { - }, - data: { "id": threadId, "postId": postId, "data": { "attributes": { "isLiked": false } } } - }); - if (unlikeResult?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 帖子取消点赞成功`) - } else { - $.log(`${this.index}账号[${this.index}] 帖子取消点赞失败:${unlikeResult.Message}❌`) + try { + await this.request("v3/posts.update", { id: threadId, postId, data: { attributes: { isLiked: false } } }); + $.log(`账号[${this.index}] 帖子取消点赞成功`); + } catch (e) { + $.log(`账号[${this.index}] 帖子取消点赞失败:${e.message || e}❌`); } } + async sharePost(threadId) { - let options = { - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/thread.share`, - headers: { - - }, - data: { "threadId": threadId } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 帖子分享成功`) - } else { - $.log(`${this.index}账号[${this.index}] 帖子分享失败:${result.Message}❌`) + try { + await this.request("v3/thread.share", { threadId }); + $.log(`账号[${this.index}] 帖子分享成功`); + } catch (e) { + $.log(`账号[${this.index}] 帖子分享失败:${e.message || e}❌`); } - } + async viewPost(threadId) { - let options = { - method: 'GET', - url: `https://bbs-api.iqoo.com/api/v3/view.count?threadId=` + threadId + `&type=0`, - headers: { - - }, - data: { "threadId": threadId, "type": 0 } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 帖子浏览成功`) - } else { - $.log(`${this.index}账号[${this.index}] 帖子浏览失败:${result.Message}❌`) + try { + await this.request("v3/view.count", { threadId, type: 0 }, { method: "GET" }); + $.log(`账号[${this.index}] 帖子浏览成功`); + } catch (e) { + $.log(`账号[${this.index}] 帖子浏览失败:${e.message || e}❌`); } - } + async commonPost(threadId) { - let options = { - method: 'POST', - url: `https://bbs-api.iqoo.com/api/v3/posts.create`, - headers: { - - }, - data: { "id": threadId, "type": 0, "content": "666", "source": "", "attachments": [] } - }; - let { data: result } = await this.request(options); - if (result?.Code == '0') { - $.log(`${this.index}账号[${this.index}] 帖子评论成功`) - } else { - $.log(`${this.index}账号[${this.index}] 帖子评论失败:${result.Message}❌`) + try { + await this.request("v3/posts.create", { id: threadId, type: 0, content: "666", source: "", attachments: [] }); + $.log(`账号[${this.index}] 帖子评论成功`); + } catch (e) { + $.log(`账号[${this.index}] 帖子评论失败:${e.message || e}❌`); } - } + async getTreadList() { - let options = { - method: 'GET', - url: `https://bbs-api.iqoo.com/api/v3/thread.list?scope=5&page=1&perPage=10&filter[sort]=4&filter[essence]=1&sequence=0`, - headers: { - - }, - data: { "filter[essence]": 1, "filter[sort]": 4, "page": 1, "perPage": 10, "scope": 5, "sequence": 0 } - }; - let { data: result } = await this.request(options); - - this.threadId = result.Data.pageData[0].threadId - this.postId = result.Data.pageData[0].postId - return - + try { + const result = await this.request("v3/thread.list", { + scope: 5, + page: 1, + perPage: 10, + "filter[sort]": 4, + "filter[essence]": 1, + sequence: 0, + }, { method: "GET" }); + const first = result?.Data?.pageData?.[0] || {}; + this.threadId = first.threadId || ""; + this.postId = first.postId || ""; + } catch (e) { + $.log(`账号[${this.index}] 获取帖子列表失败:${e.message || e}`); + } } - - /** - * - * @param {*} e method - * @param {*} t path - * @param {*} n data - * @returns - */ - getSign(e, t, n) { - - const time = Math.floor(Date.now() / 1e3); - const crypto = require("crypto-js"); - var o, s, r, a = `${time}`, - c = "GET" == e ? function (e) { - var t, o = "", r; - if (typeof e === 'object' && e !== null) { - if (Array.isArray(e)) { - r = e.entries(); - } else { - r = Object.entries(e); - } - try { - for (var entry of r) { - var a = encodeURIComponent(entry[0]), c = encodeURIComponent(entry[1]); - o += "".concat(a, "=").concat(c, "&"); - } - } catch (e) { - console.error(e); - } - return o.slice(0, -1); - } else { - return ''; - } - }(n) : ""; - let l = "GET" == e ? "" : JSON.stringify(n); - o = "2618194b0ebb620055e19cf9811d3c13" - s = e + "&" + t + "&" + c + "&" + l + "&appid=1002×tamp=" + a - r = crypto.HmacSHA256(s, o) - let sign = crypto.enc.Base64.stringify(r) - return `IQOO-HMAC-SHA256 appid=1002,timestamp=${time},signature=${sign}` - } - - - - - - - - - } !(async () => { - await getNotice() + await getNotice(); $.checkEnv(ckName); - for (let user of $.userList) { + for (const user of $.userList) { await new Task(user).run(); } })() @@ -297,20 +384,16 @@ class Task { .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - + try { + const options = { + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { + "User-Agent": defaultUserAgent, + }, + timeout: 3000, + }; + const { data: res } = await axios.request(options); + $.log(res); + return res; + } catch (e) {} } - diff --git a/daily/kangshifu.js b/daily/kangshifu.js index 318515f..1e4a61e 100644 --- a/daily/kangshifu.js +++ b/daily/kangshifu.js @@ -1,14 +1,16 @@ /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: 康师傅畅饮社 cron: 30 10 * * * ------------------------------------------ -#Notice: +#Notice: 康师傅畅饮社小程序 变量名 ksfcys -账号格式:抓https://club.biqr.cn/api/请求头token 多账户&或换行 +变量值:wx_server 里的 openid/账号标识,多账户&或换行 +需要配置:wx_server_url、wx_auth + ⚠️【免责声明】 ------------------------------------------ 1、此脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 @@ -20,92 +22,275 @@ cron: 30 10 * * * 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("康师傅畅饮社"); -let ckName = `ksfcys`; -const strSplitor = "#"; - const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const fs = require("fs"); +const path = require("path"); +const WeChatServer = require("../wxapp/wcs.js"); +const ckName = "ksfcys"; +const MINI_APP_ID = "wx54f3e6a00f7973a7"; +const API_BASE = "https://club.biqr.cn/"; +const FORUM_BASE = "https://nclub.gdshcm.com/pro/"; +const TOKEN_CACHE_FILE = path.join(__dirname, "kangshifu_token_cache.json"); +const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027"; +const defaultUserAgent = USER_AGENT; + +const wechat = new WeChatServer({ + url: process.env.wx_server_url || "http://192.168.31.196:8787", + appid: MINI_APP_ID, + auth: process.env.wx_auth || "", +}); + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function maskPhone(phone = "") { + return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); +} + +function isSuccess(result) { + return Number(result?.code) === 0; +} + +function isTokenError(message) { + return /(^|[^0-9])600([^0-9]|$)|4002|token|登录|授权|未登录|无效|过期/i.test(String(message || "")); +} + +function formBody(data = {}) { + return new URLSearchParams(data).toString(); +} class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; + constructor(openid) { + this.index = $.userIdx++; + this.openid = String(openid || "").trim(); + this.token = ""; + this.member = {}; + } - } + async run() { + const cached = this.getCachedToken(); + if (cached?.token) { + this.token = cached.token; + this.member = cached.member || {}; + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新登录`); + } + } - async run() { + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } - await this.signIn() - } + await this.memberInfo(); + await this.signIn(); + await this.memberInfo("签到后"); + } - async signIn() { - let options = { - url: `https://club.biqr.cn/api/signIn/integralSignIn`, - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027', - 'Accept': 'application/json, text/plain, */*', - 'xweb_xhr': '1', - 'Content-Type': 'application/x-www-form-urlencoded;', - 'Token': '' + this.token, - 'Sec-Fetch-Site': 'cross-site', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Dest': 'empty', - 'Referer': 'https://servicewechat.com/wx54f3e6a00f7973a7/795/page-frame.html', - 'Accept-Language': 'zh-CN,zh;q=0.9' - }, - method: 'POST', - data: { + getCachedToken() { + const cache = readTokenCache(); + return cache[this.openid] || null; + } - } - } - let { data: result } = await axios.request(options); - if (result.code == 0) { - $.log(`账号[${this.index}]【${this.name}】 签到成功`); - } else { - $.log(result); + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.openid] = { + token: this.token, + member: this.member, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } - } - } + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.openid]) { + delete cache[this.openid]; + writeTokenCache(cache); + } + this.token = ""; + this.member = {}; + } + headers(extra = {}) { + const headers = { + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + "xweb_xhr": "1", + "Referer": "https://servicewechat.com/wx54f3e6a00f7973a7/816/page-frame.html", + "Accept-Language": "zh-CN,zh;q=0.9", + ...extra, + }; + if (this.token) headers.token = this.token; + return headers; + } + async request(baseURL, apiPath, { method = "GET", data, params, auth = true, form = false } = {}) { + const options = { + method, + url: new URL(apiPath, baseURL).toString(), + params, + headers: this.headers(form ? { + "Content-Type": "application/x-www-form-urlencoded;", + } : {}), + timeout: 15000, + validateStatus: () => true, + }; + if (!auth) delete options.headers.token; + if (data !== undefined) options.data = form ? formBody(data) : data; + const { data: result, status } = await axios.request(options); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (!isSuccess(result)) throw new Error(`${result?.code ?? ""} ${result?.msg || result?.error || JSON.stringify(result)}`.trim()); + return result; + } + async getLoginCode() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取 code"); + const { data } = await wechat.getCode(this.openid); + const code = data?.code || data?.data?.code; + if (!code) throw new Error(`wx_server 未返回 code: ${JSON.stringify(data)}`); + return code; + } + async loginByWxCode() { + try { + const code = await this.getLoginCode(); + const result = await this.request(FORUM_BASE, "whale-member/api/login/login", { + method: "POST", + auth: false, + data: { + code, + inviterId: "", + inviterType: "", + inviterMatchUserId: "", + spUrl: "", + }, + }); + const token = result?.data?.token || ""; + if (!token) throw new Error(`登录响应未返回token: ${JSON.stringify(result)}`); + this.token = token; + this.member = result?.data?.member || {}; + this.saveCachedToken(); + $.log(`账号[${this.index}] 登录成功: ${this.member.nickname || maskPhone(this.member.phone) || this.member.id || ""}`); + } catch (e) { + $.log(`账号[${this.index}] 登录失败: ${e.message || e}`); + } + } + async checkToken() { + try { + await this.getMemberInfo(); + return true; + } catch (e) { + return false; + } + } + async getMemberInfo() { + const result = await this.request(FORUM_BASE, "whale-member/api/member/getMemberInfo", { + params: { token: this.token }, + }); + this.member = result?.data || this.member || {}; + this.saveCachedToken(); + return this.member; + } + async memberInfo(prefix = "当前") { + try { + const data = await this.getMemberInfo(); + const name = data?.nickname || maskPhone(data?.phone) || data?.id || ""; + const integral = data?.integral ?? data?.point ?? data?.points ?? data?.score ?? "未知"; + $.log(`账号[${this.index}] ${prefix}用户: ${name},积分: ${integral}`); + } catch (e) { + const message = String(e.message || e); + $.log(`账号[${this.index}] 查询用户失败: ${message}`); + if (isTokenError(message)) this.removeCachedToken(); + } + } + async getSignStatus() { + const result = await this.request(API_BASE, "api/signIn/integralSignInList", { + method: "POST", + data: { token: this.token }, + form: true, + }); + return result?.data || {}; + } + + async signIn() { + try { + const status = await this.getSignStatus(); + if (status?.signIs) { + $.log(`账号[${this.index}] 今日已签到`); + return; + } + + const result = await this.request(API_BASE, "api/signIn/integralSignIn", { + method: "POST", + data: {}, + form: true, + }); + $.log(`账号[${this.index}] 签到成功: ${result?.msg || "成功"}`); + } catch (e) { + const message = String(e.message || e); + if (/已签到|已签|重复/.test(message)) { + $.log(`账号[${this.index}] 今日已签到`); + return; + } + $.log(`账号[${this.index}] 签到失败: ${message}`); + if (isTokenError(message)) this.removeCachedToken(); + } + } } !(async () => { - await getNotice() - $.checkEnv(ckName); + await getNotice(); + $.checkEnv(ckName); - for (let user of $.userList) { - await new Task(user).run(); - } + for (const user of $.userList) { + await new Task(user).run(); + } })() - .catch((e) => console.log(e)) - .finally(() => $.done()); + .catch((e) => console.log(e)) + .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout: 3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) { } - + try { + const options = { + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { + "User-Agent": defaultUserAgent, + }, + timeout: 3000, + }; + const { data: res } = await axios.request(options); + $.log(res); + return res; + } catch (e) {} } diff --git a/daily/maisiweier.js b/daily/maisiweier.js deleted file mode 100644 index f331f58..0000000 --- a/daily/maisiweier.js +++ /dev/null @@ -1,387 +0,0 @@ -/* ------------------------------------------- -@Author: sm -@Date: 2024.06.07 19:15 -@Description: 麦斯威尔福利社小程序 签到 浇水 积分 -cron: 30 9 * * * ------------------------------------------- -#Notice: 麦斯威尔福利社小程序 -抓jde.mtbcpt.com/api的POST请求中的openId 多账户&或换行 -⚠️【免责声明】 ------------------------------------------- -1、此脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 -2、由于此脚本仅用于学习研究,您必须在下载后 24 小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。 -3、请勿将此脚本用于任何商业或非法目的,若违反规定请自行对此负责。 -4、此脚本涉及应用与本人无关,本人对因此引起的任何隐私泄漏或其他后果不承担任何责任。 -5、本人对任何脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 -6、如果任何单位或个人认为此脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此脚本。 -7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 -*/ - -const { Env } = require("../tools/env") -const $ = new Env("麦斯威尔福利社小程序"); -let ckName = `mswe`; -const strSplitor = "#"; -const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" - - -class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.openId = this.user[0]; - - } - - async run() { - await this.getMemberTaskInfo() - await this.getUserFarmGetWaterList() - for (let i = 0; i < 3; i++) { - await this.farmWatering(); - } - } - - md5(str) { - const crypto = require("crypto") - return crypto.createHash("md5").update(str).digest('hex'); - } - // ============================================================================================================================= - - async getMemberTaskInfo() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/GetMemberTaskInfo', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - } - }; - - let { data: result } = await axios.request(options) - - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `查询任务成功🎉`); - if (result.data.dailySign != 1) { - $.log(`${this.index}账号[${this.index}]` + `未签到`); - await this.signDailyScore() - - } else { - $.log(`${this.index}账号[${this.index}]` + `已签到`); - - - } - if (result.data.shareSign != 1) { - $.log(`${this.index}账号[${this.index}]` + `未分享`); - //分享 - await this.shareDailyScore() - } else { - $.log(`${this.index}账号[${this.index}]` + `已分享`); - - } - - } else { - $.log(`🌸账号[${this.index}]查询任务-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - async signDailyScore() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/SignInDailyScore', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - } - }; - - let { data: result } = await axios.request(options) - - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `🕊${result.msg}🎉`); - } else { - $.log(`🌸账号[${this.index}]签到-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - //分享加积分 - async shareDailyScore() { - try { - let timestamp = Date.now(); - let sign = md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: `https://jde.mtbcpt.com/api/JDEMaxwellApi/ShareDailyScore`, - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - "through": true, - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - } - - } - // - let { data: result } = await axios.request(options) - - if (result?.state == true) { - //打印签到结果 - $.log(`🌸账号[${user.index}]` + `🕊${result.msg}🎉`); - } if (result?.state == false) { - $.log(`🌸账号[${user.index}]分享-失败:${result.msg}❌`) - } - } catch (e) { - - } - } - async getUserFarmGetWaterList() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/getUserFarmGetWaterList', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - } - }; - - let { data: result } = await axios.request(options) - - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `查询农场任务成功🎉`); - for (let task of result.data.list) { - if (task.status != 3) { - $.log(`${this.index}账号[${this.index}][${task.taskName}]` + `未执行`); - if (task.ptype == 1) { - await this.farmSign() - } - if (task.ptype == 2) { - await this.farmShare() - } - } else { - $.log(`${this.index}账号[${this.index}][${task.taskName}]` + `已执行`); - - } - - } - - - - - - } else { - $.log(`🌸账号[${this.index}]查询任务-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - //浇水签到 - async farmSign() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/UserSign', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - - } - }; - let { data: result } = await axios.request(options) - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `🕊水滴签到-领取成功${result.msg}🎉`); - } else { - $.log(`🌸账号[${this.index}]水滴签到-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - //分享领水滴 - async farmShare() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/UserShare', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - - } - }; - - let { data: result } = await axios.request(options) - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `🕊任务分享领水滴-领取成功${result.msg}🎉`); - } else { - $.log(`🌸账号[${this.index}]任务分享领水滴-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - //浇水 - async farmWatering() { - try { - let timestamp = Date.now(); - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMaxwellApi/UserWatering', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - } - }; - - let { data: result } = await axios.request(options) - if (result?.state == true) { - - $.log(`🌸账号[${this.index}]` + `🕊浇水-成功${result.msg}🎉`); - } else { - $.log(`🌸账号[${this.index}]浇水-失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - async getUserPoint() { - try { - let timestamp = Date.now() - let sign = this.md5(`timestamp=${timestamp}&openid=${this.openId}&key=JDEMaxwellminiapp#2021!`).toUpperCase(); - let options = { - method: 'post', - url: 'https://jde.mtbcpt.com/api/JDEMWMall/GetUserPoint', - headers: { - 'Host': 'jde.mtbcpt.com', - 'content-type': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090b11) XWEB/9129', - }, - data: { - - "openId": this.openId, - "timestamp": timestamp, - "sign": sign, - - } - }; - - let { data: result } = await axios.request(options) - if (result?.state == true) { - this.userFlag = false - $.log(`🌸账号[${this.index}]` + `🕊${result.msg}为[${result.data}]💰`); - } else { - $.log(`🌸账号[${this.index}]积分查询失败:${result.msg}❌`) - } - - } catch (e) { - - } - } - - - - - - - -} - -!(async () => { - await getNotice() - $.checkEnv(ckName); - - for (let user of $.userList) { - await new Task(user).run(); - } -})() - .catch((e) => console.log(e)) - .finally(() => $.done()); - -async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - -} diff --git a/daily/zbs.js b/daily/zbs.js index 1b9b90f..755b339 100644 --- a/daily/zbs.js +++ b/daily/zbs.js @@ -1,13 +1,14 @@ /* ------------------------------------------ @Author: sm -@Date: 2024.06.07 19:15 +@Date: 2026.06.01 @Description: 植白说小程序 cron: 30 8 * * * ------------------------------------------ -#Notice: +#Notice: 变量名称:zbs -值抓取https://www.kozbs.com/demo 请求头x-dts-token +变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行 +需要配置:wx_server_url、wx_auth ⚠️【免责声明】 ------------------------------------------ @@ -20,93 +21,235 @@ cron: 30 8 * * * 7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 */ -const { Env } = require("../tools/env") +const { Env } = require("../tools/env.js"); const $ = new Env("植白说小程序"); -let ckName = `zbs`; -const strSplitor = "#"; const axios = require("axios"); -const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" +const fs = require("fs"); +const path = require("path"); +const WeChatServer = require("../wxapp/wcs.js"); +const ckName = "zbs"; +const MINI_APP_ID = "wx6b6c5243359fe265"; +const API_BASE = "https://www.kozbs.com/demo/wx/"; +const TOKEN_CACHE_FILE = path.join(__dirname, "zbs_token_cache.json"); +const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf254173b) XWEB/19027"; +const defaultUserAgent = USER_AGENT; + +const wechat = new WeChatServer({ + url: process.env.wx_server_url || "http://192.168.31.196:8787", + appid: MINI_APP_ID, + auth: process.env.wx_auth || "", +}); + +function readTokenCache() { + try { + if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; + return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; + } catch (e) { + return {}; + } +} + +function writeTokenCache(cache) { + try { + fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8"); + } catch (e) { + $.log(`写入token缓存失败: ${e.message || e}`); + } +} + +function shortToken(token = "") { + const value = String(token); + return value ? `${value.slice(0, 6)}***${value.slice(-6)}` : ""; +} + +function maskPhone(phone = "") { + return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); +} + +function isTokenError(message) { + return /(^|[^0-9])501([^0-9]|$)|token|登录|授权|未登录|失效|过期/i.test(String(message || "")); +} class Task { - constructor(env) { - this.index = $.userIdx++ - this.user = env.split(strSplitor); - this.token = this.user[0]; - + constructor(openid) { + this.index = $.userIdx++; + this.openid = String(openid || "").trim(); + this.token = ""; + this.userInfo = {}; } - request(options) { - let baseHeraders = { - "accept": "*/*", - "accept-language": "zh-CN,zh;q=0.9", - "content-type": "application/json", - "priority": "u=1, i", - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "cross-site", - "x-dts-token": "" + this.token, - "xweb_xhr": "1", - - "User-Agent": defaultUserAgent, - } - options.headers = Object.assign(baseHeraders, options.headers) - return axios.request(options) - } async run() { + const cached = this.getCachedToken(); + if (cached?.token) { + this.token = cached.token; + this.userInfo = cached.userInfo || {}; + $.log(`账号[${this.index}] 使用缓存token: ${shortToken(this.token)}`); + if (!(await this.checkToken())) { + this.removeCachedToken(); + $.log(`账号[${this.index}] 缓存token失效,重新登录`); + } + } - await this.signIn() + if (!this.token) { + await this.loginByWxCode(); + if (!this.token) return; + } + + await this.getPoints("当前"); + await this.signIn(); + await this.getPoints("签到后"); + } + + get userId() { + return this.userInfo?.userId || this.userInfo?.id || 1; + } + + getCachedToken() { + const cache = readTokenCache(); + return cache[this.openid] || null; + } + + saveCachedToken() { + if (!this.token) return; + const cache = readTokenCache(); + cache[this.openid] = { + token: this.token, + userInfo: this.userInfo, + updatedAt: new Date().toISOString(), + }; + writeTokenCache(cache); + } + + removeCachedToken() { + const cache = readTokenCache(); + if (cache[this.openid]) { + delete cache[this.openid]; + writeTokenCache(cache); + } + this.token = ""; + this.userInfo = {}; + } + + headers(extra = {}) { + const headers = { + "Content-Type": "application/json", + "User-Agent": USER_AGENT, + "Accept": "*/*", + "Accept-Language": "zh-CN,zh;q=0.9", + "Referer": "https://servicewechat.com/wx6b6c5243359fe265/171/page-frame.html", + "xweb_xhr": "1", + ...extra, + }; + if (this.token) headers["X-Dts-Token"] = this.token; + return headers; + } + + async request(apiPath, { method = "GET", data = {}, auth = true } = {}) { + const options = { + method, + url: new URL(apiPath, API_BASE).toString(), + headers: this.headers(), + timeout: 15000, + validateStatus: () => true, + }; + if (!auth) delete options.headers["X-Dts-Token"]; + if (method.toUpperCase() === "GET") options.params = data; + else options.data = data; + + const { data: result, status } = await axios.request(options); + if (status !== 200) throw new Error(`HTTP ${status}: ${JSON.stringify(result)}`); + if (Number(result?.errno) !== 0) throw new Error(`${result?.errno ?? ""} ${result?.errmsg || result?.message || JSON.stringify(result)}`.trim()); + return result; + } + + async getLoginCode() { + if (!process.env.wx_auth) throw new Error("缺少 wx_auth,无法从 wx_server 获取 code"); + const { data } = await wechat.getCode(this.openid); + const code = data?.code || data?.data?.code; + if (!code) throw new Error(`wx_server 未返回 code: ${JSON.stringify(data)}`); + return code; + } + + async loginByWxCode() { + try { + const code = await this.getLoginCode(); + const result = await this.request("auth/login_by_weixin", { + method: "POST", + auth: false, + data: { + code, + userInfo: {}, + shareUserId: 1, + }, + }); + const token = result?.data?.token || ""; + if (!token) throw new Error(`登录响应未返回token: ${JSON.stringify(result)}`); + this.token = token; + this.userInfo = result?.data?.userInfo || {}; + this.saveCachedToken(); + $.log(`账号[${this.index}] 登录成功: ${this.userInfo.nickname || maskPhone(this.userInfo.mobile) || this.userId}`); + } catch (e) { + $.log(`账号[${this.index}] 登录失败: ${e.message || e}`); + } + } + + async checkToken() { + try { + await this.request("user/getUserIntegral", { + data: { userId: this.userId }, + }); + return true; + } catch (e) { + return false; + } + } + + async getPoints(prefix = "当前") { + try { + const result = await this.request("user/getUserIntegral", { + data: { userId: this.userId }, + }); + const integer = result?.data?.integer ?? result?.data?.integral ?? result?.data?.point ?? "未知"; + $.log(`🌸账号[${this.index}] ${prefix}积分: ${integer}🎉`); + } catch (e) { + const message = String(e.message || e); + $.log(`🌸账号[${this.index}] 获取积分失败: ${message}❌`); + if (isTokenError(message)) this.removeCachedToken(); + } } async signIn() { - let options = { - method: 'GET', - url: "https://www.kozbs.com/demo/wx/home/sign?userId=", - headers: {}, + try { + const status = await this.request("home/signDay", { + data: { userId: this.userId }, + }); + if (status?.data?.isSign) { + $.log(`🌸账号[${this.index}] 今日已签到`); + return; + } - }; - let { data: result } = await this.request(options); - if (result?.errno == '0') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `签到成功🎉`); - } else { - $.log(`🌸账号[${this.index}] 签到-失败:${result.errmsg}❌`) - } - - - - - } - async getPoints() { - let options = { - method: 'GET', - url: "https://www.kozbs.com/demo/wx/user/getUserIntegral?userId=", - headers: {}, - - }; - let { data: result } = await this.request(options); - if (result?.errno == '0') { - //打印签到结果 - $.log(`🌸账号[${this.index}]` + `当前积分` + `${result.data.integer}🎉`); - } else { - $.log(`🌸账号[${this.index}] 获取积分-失败:${result.errmsg}❌`) + await this.request("home/sign", { + data: { userId: this.userId }, + }); + $.log(`🌸账号[${this.index}] 签到成功🎉`); + } catch (e) { + const message = String(e.message || e); + if (/已签到|已签|重复/.test(message)) { + $.log(`🌸账号[${this.index}] 今日已签到`); + return; + } + $.log(`🌸账号[${this.index}] 签到失败: ${message}❌`); + if (isTokenError(message)) this.removeCachedToken(); } } - - - - - - - - } !(async () => { - await getNotice() + await getNotice(); $.checkEnv(ckName); - for (let user of $.userList) { + for (const user of $.userList) { await new Task(user).run(); } })() @@ -114,19 +257,16 @@ class Task { .finally(() => $.done()); async function getNotice() { - try { - let options = { - url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, - headers: { - "User-Agent": defaultUserAgent, - }, - timeout:3000 - } - let { - data: res - } = await axios.request(options); - $.log(res) - return res - } catch (e) {} - + try { + const options = { + url: "https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json", + headers: { + "User-Agent": defaultUserAgent, + }, + timeout: 3000, + }; + const { data: res } = await axios.request(options); + $.log(res); + return res; + } catch (e) {} } diff --git a/wxapp/fafa.js b/wxapp/fafa.js index 45fdb80..4260488 100644 --- a/wxapp/fafa.js +++ b/wxapp/fafa.js @@ -2,7 +2,7 @@ ------------------------------------------ @Author: sm @Date: 2026.05.31 -@Description: fafa 小程序签到 +@Description: 发发藏宝洞 小程序签到 cron: 38 8 * * * ------------------------------------------ 变量名:fafa @@ -12,7 +12,7 @@ cron: 38 8 * * * */ const { Env } = require("../tools/env.js"); -const $ = new Env("fafa小程序签到"); +const $ = new Env("发发藏宝洞小程序签到"); const axios = require("axios"); const fs = require("fs"); const path = require("path");