Files
smallfawnandClaude Opus 5 6f471a97fa feat(wxapp): 新增59个小程序签到脚本(YYB-GO 5仓迁移至smallcat取码)
从 5 个 YYB-GO 仓库(SuperNaiBA/villagebully8/juelianxyt/wyd3325/lcmovie)
按 appid 去重后迁移,统一改为 smallcat wx_server 取码 + 青龙 Env 结构。

实测签到成功(部分):三福/奈雪/唯品会/顺丰/名创优品/途虎养车/OPPO/酒仙/
倍轻松/美的会员/铛铛一下/绿蜜蜂/东风日产/海尔智家/捷停车/爱康/杰士邦/
DT生活/小紫有约/安吉尔/玛氏/骆驼/袋鼠妈妈/全棉时代/金典/宝妈上班等。

登录链已通、待账号注册即可签:都市甜心/海澜之家/美孚/绿动新球/OLE/
SM广场/深圳春茧/比亚迪海洋/洽洽/小米社区等。

已知服务端限制(脚本正确但当前签不了):携程(运行态cSign校验)、得物(滑块)、
拼多多果园(anti-content)、天机观(发奖接口运行态校验)、嘉立创(账号风控)、
骆驼户外(活动已结束)、红人库(商户OneCRM实例空)、蒙娜丽莎(需OCR验证码)。

另修复 fxh.js(积分查询404不阻断签到) 与 jyxe.js。
所有脚本仅含应用级常量,不含任何个人凭证。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:16:37 +08:00

206 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
------------------------------------------
@Description: 途虎养车 - 微信小程序静默登录 + 每日签到
cron: 24 8 * * *
------------------------------------------
变量名:thyc
变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行分隔(可加 #备注)
依赖变量:
wx_server_url 默认 http://192.168.31.196:8787
wx_auth 必填,wx_server 鉴权值
------------------------------------------
契约(appid wx27d20205249c56a3,host cl-gateway.tuhu.cn):
(迁移自 YYB-GO 系脚本,原脚本已 code 登录)
鉴权:纯 code 静默登录,无签名。登录换取 userSession,后续请求头 Authorization: Bearer <userSession>
登录 POST /cl-user-auth-login/login/authSilentSign {channel:"WXAPP",code}
-> code==10000,data.userSession(=后续 token)、data.nickName
状态 POST /cl-common-api/api/member/getSignInInfo {channel:"WXAPP"} 头 Authorization:Bearer
-> code==10000,data.signInStatus(已签)/userIntegral(积分)
签到 POST /cl-common-api/api/dailyCheckIn/userCheckIn {channel:"WXAPP"}
-> code==10000,data.rewardIntegral/continuousDays
authSilentSign 为静默登录,通常任意微信 code 均可自动建号,无需明文 openid/unionid。
------------------------------------------
*/
const { Env } = require("../tools/env.js");
const $ = new Env("途虎养车签到");
const axios = require("axios");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const WeChatServer = require("./wcs.js");
const ckName = "thyc";
const MINI_APP_ID = "wx27d20205249c56a3";
const PAGE_VERSION = "1319";
const API_HOST = "https://cl-gateway.tuhu.cn";
const TOKEN_CACHE_FILE = path.join(__dirname, "thyc_token_cache.json");
const 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";
const EP_LOGIN = "/cl-user-auth-login/login/authSilentSign";
const EP_SIGN_INFO = "/cl-common-api/api/member/getSignInInfo";
const EP_SIGN_SUBMIT = "/cl-common-api/api/dailyCheckIn/userCheckIn";
const OK_CODE = 10000;
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 readCache() {
try { if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; } catch (e) { return {}; }
}
function writeCache(c) {
try { fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(c, null, 2), "utf8"); } catch (e) { $.log(`写入缓存失败: ${e.message || e}`); }
}
function parseAccount(raw = "") {
const [id, remark] = String(raw).split("#").map((s) => (s || "").trim());
return { openid: id, remark: remark || "" };
}
function short(v, n = 300) {
const t = typeof v === "string" ? v : JSON.stringify(v);
return !t ? "" : t.length > n ? `${t.slice(0, n)}...` : t;
}
function toInt(v) { const n = parseInt(v, 10); return Number.isFinite(n) ? n : 0; }
class Task {
constructor(raw) {
this.index = $.userIdx++;
this.account = parseAccount(raw);
this.token = "";
// 设备指纹类字段每号随机生成,不复用原作者值
this.distinctId = $.uuid();
this.deviceId = `${Date.now()}-${$.randomNumber(7)}-0f6cb850fc64da-24853921`;
this.fingerprint = `sMPVY${Math.floor(Date.now() / 1000)}QPV2wLVhl8f`;
}
log(text) {
$.log(`账号[${this.index}]${this.account.remark ? `[${this.account.remark}]` : ""} ${text}`);
}
headers(withAuth) {
const h = {
Host: "cl-gateway.tuhu.cn",
Connection: "keep-alive",
xweb_xhr: "1",
distinct_id: this.distinctId,
currentPage: "memberMallPackage/pages/pointCenter/pointCenter",
deviceId: this.deviceId,
authType: "oauth",
api_level: "2",
vehicleClass: "CAR",
channel: "wechat-miniprogram",
"Content-Type": "application/json",
fingerprint: this.fingerprint,
"User-Agent": UA,
version: "7.62.8",
Accept: "*/*",
"Sec-Fetch-Site": "cross-site",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
Referer: `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`,
"Accept-Language": "zh-CN,zh;q=0.9",
};
if (withAuth && this.token) h.Authorization = `Bearer ${this.token}`;
return h;
}
async request(apiPath, data, { withAuth = true } = {}) {
const res = await axios.request({
method: "POST", url: `${API_HOST}${apiPath}`, data: data || {},
headers: this.headers(withAuth), timeout: 20000, validateStatus: () => true,
});
if (res.status !== 200) {
if (res.data && typeof res.data === "object") return res.data;
throw new Error(`${apiPath} HTTP ${res.status}: ${short(res.data)}`);
}
return res.data;
}
async getCode() {
const { data } = await wechat.getCode(this.account.openid);
if (data && data.status === false) throw new Error(`wx_server 取code失败: ${data.message || short(data)}`);
const code = data?.data?.code || data?.code;
if (!code || typeof code !== "string") throw new Error(`wx_server 未返回 code: ${short(data)}`);
return code;
}
async login() {
const code = await this.getCode();
const res = await this.request(EP_LOGIN, { channel: "WXAPP", code }, { withAuth: false });
if (Number(res?.code) !== OK_CODE) throw new Error(`登录失败: ${res?.message || res?.msg || short(res)}`);
const info = res.data || {};
this.token = String(info.userSession || "");
if (!this.token) {
// 静默登录未返回 session:多半是该微信号未在途虎注册/激活
this.unregistered = true;
throw new Error(`NO_ACCOUNT:登录未返回 userSession: ${short(res)}`);
}
const nick = info.nickName || "微信用户";
const cache = readCache();
cache[this.account.openid] = { token: this.token, updatedAt: new Date().toISOString() };
writeCache(cache);
this.log(`登录成功(${nick})`);
}
async getSignInfo() {
const res = await this.request(EP_SIGN_INFO, { channel: "WXAPP" });
if (Number(res?.code) !== OK_CODE) return { ok: false, res };
const d = res.data || {};
return { ok: true, signed: !!d.signInStatus, integral: toInt(d.userIntegral), res };
}
async submitSign() {
const res = await this.request(EP_SIGN_SUBMIT, { channel: "WXAPP" });
if (Number(res?.code) === OK_CODE) {
const d = res.data || {};
return { ok: true, reward: toInt(d.rewardIntegral), days: toInt(d.continuousDays), res };
}
return { ok: false, msg: res?.message || res?.msg || short(res), res };
}
async sign(retry = true) {
const info = await this.getSignInfo();
if (!info.ok) {
const msg = info.res?.message || info.res?.msg || short(info.res);
if (retry && /token|登录|未授权|失效|过期|未登录|鉴权|401/i.test(String(msg))) {
this.log("会话失效,重新登录后重试");
this.token = "";
await this.login();
return this.sign(false);
}
return this.log(`❌ 查询签到状态失败: ${msg}`);
}
if (info.signed) return this.log(`✅ 今日已签到,当前积分 ${info.integral}`);
this.log(`未签到,当前积分 ${info.integral},开始签到...`);
const r = await this.submitSign();
if (r.ok) return this.log(`✅ 签到成功 +${r.reward} 积分,连续签到 ${r.days} 天`);
if (/已签|签到过|重复|已完成/.test(String(r.msg))) return this.log(`✅ 今日已签到(${r.msg})`);
this.log(`❌ 签到失败: ${r.msg}`);
}
async ensureLogin() {
const cached = readCache()[this.account.openid] || {};
if (!this.token && cached.token) { this.token = cached.token; this.log("使用缓存token"); return; }
if (!this.token) await this.login();
}
async run() {
if (!this.account.openid) { this.log("跳过:变量值里没有 openid"); return; }
try {
await this.ensureLogin();
await this.sign();
} catch (e) {
if (String(e.message).startsWith("NO_ACCOUNT")) {
this.log("⚠️ 该微信号还没在途虎养车注册/激活,先在小程序里登录一次再跑");
return;
}
this.log(`执行失败: ${e.message || e}`);
}
}
}
!(async () => {
$.checkEnv(ckName);
if (!$.userCount) { $.log(`未找到变量 ${ckName}`); return; }
for (let i = 0; i < $.userList.length; i++) {
await new Task($.userList[i]).run();
if (i < $.userList.length - 1) await $.wait(1500, 3000);
}
})().catch((e) => $.log(e.message || e)).finally(() => $.done());