Files
smallfawnandClaude Opus 4.8 64a5a63efb feat(wxapp): 新增 6 个脚本、修复 9 个、删除 6 个失效脚本
新增(均自行下包核对登录链路 + 实跑验证):
- xinxianghui.js/py 芯享汇、liquanpijiu.py 力全啤酒、wanyazhenxuan.js 丸丫甄选
- bjhqbwz.py 北京环球度假区、hongsehuojian.py 红色火箭、wuyingyundiannao.py 无影云电脑

修复:
- haitian 把用户资料的 encryptedData 误喂给手机号登录接口,改用
  /wx/getphonenumber 的 data.raw(此前被误判成服务端 5xx)
- colorful 恢复被作者注释掉的主流程,登录改为 手填token -> 缓存 -> OnLogin
  -> 手机号授权首登;另修取码传错变量值、取码失败无分支、成功码只认 0、幂等正则漏项
- wx_xlxyh 骁龙骁友会 改为自动登录(原先要手填 sessionKey#userId),
  补齐点赞/阅读5分钟/VLOG1分钟三个每日任务;签到与抽奖被服务端账号态/风控拦下,
  已在文件头记录 10 轮变量法的排除过程,不做无谓重登
- guyu / nwdjg / fyzq / tclx_lc / fhxmh / feihe / ykb_all / quanmianshidai
  活动 id 改为运行时从只读接口发现、补签名、修幂等文案与取码失败分支

删除(服务端已下线或功能迁走,无从修复):
- airui hezj jsbhuiyuan miaoyouHome niuniuduanju qiaqia

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 22:58:41 +08:00

256 lines
10 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.
/*
------------------------------------------
@Author: sm
@Date: 2026.06.09
@Description: 同程旅行里程签到 登录/查询/签到
cron: 21 8 * * *
------------------------------------------
变量名:tclx_lc
变量值:wx_server里的openid/账号标识,多账号用 & 或换行
依赖变量:wx_server_url、wx_auth
------------------------------------------
接口契约(wx.17u.cn / tcmobileapi.17usoft.com):
登录 POST /wechatappapi/wxUser/login {code, scene:1001}
-> content.{openId, memberId, sectoken};sectoken 用于后续鉴权头
日历 POST /wxmpsign/sign/signCalendar {beginDate, endDate}
-> data.{canSign, today, todaySigned, dateInfo[{date, mileage, isSigned}]}
状态 POST /wxmpsign/sign/getSignInfo {}
-> data.{todaySigned, canSign, signMileage, signDays,
periodContinuedSignDays, tomorrowMileage}
签到 POST /wxmpsign/sign/saveSignInfo {} -> code==200
里程 POST /mallgatewayapi/userApi/mileages/remain {osType:2}
-> data.{remainMileageTitle(里程余额), memberGrade, exchangeRate}
说明:签到成功后小程序内签到按钮会切换为「做任务,赚里程积分」,该任务中心页
位于分包 page/AC/sign/ 内。smallcat 的 /wx/downloadurl 仅返回主包(文档参数
只有 openid/appid/version_type,无分包选项),分包 js 解包后为空壳,任务中心
的接口无法从源码还原;不做接口盲猜。故本脚本只完成每日签到(里程由签到直接
发放:今日 signMileage,次日 tomorrowMileage),任务中心请在小程序内手动完成。
------------------------------------------
*/
const { Env } = require("../tools/env.js");
const $ = new Env("同程旅行里程签到");
const axios = require("axios");
const CK_NAME = "tclx_lc";
const APP = { name: "同程旅行里程签到", appid: "wx336dcaf6a1ecf632" };
const WX_SERVER_URL = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, "");
const WX_AUTH = process.env.wx_auth || "";
const DEFAULT_OPENID = process.env.wx_openid || "";
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";
function short(value, max = 220) {
if (value === undefined || value === null) return "";
const text = typeof value === "string" ? value : JSON.stringify(value);
return text.length > max ? `${text.slice(0, max)}...` : text;
}
function formatDate(date = new Date()) {
const pad = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
function getFiveDays() {
const days = [];
for (let i = -2; i <= 2; i++) {
const d = new Date();
d.setDate(d.getDate() + i);
days.push(formatDate(d));
}
return days;
}
async function request(options) {
const res = await axios.request({
timeout: 20000,
validateStatus: () => true,
...options,
headers: {
"User-Agent": USER_AGENT,
Accept: "application/json, text/plain, */*",
...(options.headers || {}),
},
});
return { status: res.status, headers: res.headers || {}, data: res.data };
}
async function getWxCode(appid, openid) {
if (!WX_AUTH) throw new Error("未配置 wx_auth");
const headers = { auth: WX_AUTH, "content-type": "application/json" };
let lastMsg = "";
for (let attempt = 0; attempt < 4; attempt++) {
if (attempt) {
// smallcat 偶发 "获取失败"(会话抖动),刷新会话后间隔重试
try {
await request({ method: "POST", url: `${WX_SERVER_URL}/wx/refresh`, headers, data: { appid, openid } });
} catch (e) {
// 刷新失败不阻断,继续重试取 code
}
await new Promise((r) => setTimeout(r, 3000));
}
const { status, data } = await request({
method: "POST",
url: `${WX_SERVER_URL}/wx/code`,
headers,
data: { appid, openid },
});
if (data && data.status === false) {
lastMsg = data.message || "获取失败";
continue;
}
const code = data?.code || data?.data?.code;
if (status === 200 && code) return code;
lastMsg = `HTTP ${status}: ${short(data)}`;
}
throw new Error(`获取code失败(已重试): ${lastMsg}`);
}
class Tongcheng {
constructor(openid) {
this.openid = openid;
this.loginInfo = {};
}
headers(extra = {}) {
const sectoken = this.loginInfo.sectoken || "";
return {
apmat: `${this.loginInfo.openId || this.openid}|${new Date().toISOString().slice(0, 16).replace(/[-T:]/g, "")}|${Math.floor(Math.random() * 1000000)}`,
TCSecTk: sectoken,
TCxcxVersion: "10.8.7",
platform: "WX_MP",
osType: "2",
secToken: sectoken,
"TC-MALL-PLATFORM-CODE": "WX_MP",
"TC-MALL-USER-TOKEN": sectoken,
...extra,
};
}
async login() {
const code = await getWxCode(APP.appid, this.openid);
const res = await request({
method: "POST",
url: "https://wx.17u.cn/wechatappapi/wxUser/login",
headers: { "content-type": "application/json" },
data: { code, scene: 1001 },
});
const content = res.data?.content || res.data?.data || {};
if (res.status !== 200 || !content.openId) throw new Error(`登录失败 HTTP ${res.status}: ${short(res.data)}`);
this.loginInfo = {
openId: content.openId,
encryOpenId: content.encryOpenId,
aesOpenId: content.aesOpenId,
unionId: content.unionId,
aesUnionId: content.aesUnionId,
memberId: content.memberId,
sectoken: content.sectoken,
};
return `openId=${content.openId} memberId=${content.memberId || ""}`;
}
async query() {
const mileage = await request({
method: "POST",
url: "https://tcmobileapi.17usoft.com/mallgatewayapi/userApi/mileages/remain",
headers: this.headers({
"content-type": "application/json",
"TC-MALL-DEPT-CODE": "iH3PGf9ZucSMMEYi4keylA==",
"TC-MALL-CLIENT": "API_CLIENT",
"TC-MALL-OS-TYPE": "Android",
}),
data: { osType: 2 },
});
const d = mileage.data?.data || {};
const remain = d.remainMileageTitle;
if (remain === undefined) return `里程查询失败: ${short(mileage.data, 120)}`;
return `里程余额=${remain} 会员等级=${d.memberGrade || "未知"} 兑换比例=${d.exchangeRate ?? "未知"}`;
}
async sign() {
const days = getFiveDays();
const calendar = await request({
method: "POST",
url: "https://wx.17u.cn/wxmpsign/sign/signCalendar",
headers: this.headers({ "content-type": "application/json" }),
data: { beginDate: days[0], endDate: days[4] },
});
const signInfo = await request({
method: "POST",
url: "https://wx.17u.cn/wxmpsign/sign/getSignInfo",
headers: this.headers({ "content-type": "application/json" }),
data: {},
});
const info = signInfo.data?.data || {};
const cal = calendar.data?.data || {};
const today = cal.today || formatDate();
const todayInfo = (cal.dateInfo || []).find((x) => x.date === today) || {};
const streak = info.periodContinuedSignDays ?? cal.periodContinuedSignDays ?? "未知";
// 幂等预检:今日已签则不再提交
if (info.todaySigned || cal.todaySigned || todayInfo.isSigned) {
return `今日已签到,连续=${streak}天 今日里程=${todayInfo.mileage ?? info.signMileage ?? "未知"}`;
}
if (info.canSign === 0 && cal.canSign === 0) {
return `当前不可签到(canSign=0),请在小程序内查看活动状态`;
}
const sign = await request({
method: "POST",
url: "https://wx.17u.cn/wxmpsign/sign/saveSignInfo",
headers: this.headers({ "content-type": "application/json" }),
data: {},
});
if (sign.data?.code !== 200) {
return `签到失败: ${sign.data?.msg || short(sign.data, 160)}`;
}
// 复核签到结果(服务端为准),并提示任务中心需手动完成
const after = await request({
method: "POST",
url: "https://wx.17u.cn/wxmpsign/sign/getSignInfo",
headers: this.headers({ "content-type": "application/json" }),
data: {},
});
const a = after.data?.data || {};
if (!a.todaySigned) return `签到接口返回成功但状态未更新: ${short(sign.data, 160)}`;
return (
`签到成功,获得里程=${todayInfo.mileage ?? info.signMileage ?? "未知"} ` +
`连续=${a.periodContinuedSignDays ?? streak}天 明日里程=${a.tomorrowMileage ?? "未知"}` +
`(签到后按钮切换的「做任务,赚里程积分」任务中心在分包内,接口无法还原,请在小程序内手动完成)`
);
}
}
async function runAccount(openid, index) {
$.log(`\n========== ${APP.name} 账号[${index}] ${openid} ==========`);
const runner = new Tongcheng(openid);
try {
$.log(`登录:${await runner.login()}`);
$.log(`查询:${await runner.query()}`);
$.log(`签到:${await runner.sign()}`);
} catch (e) {
$.log(`执行失败:${e.message || e}`);
}
}
(async () => {
const accounts = (process.env[CK_NAME] || DEFAULT_OPENID || "")
.split((process.env[CK_NAME] || "").includes("\n") ? "\n" : "&")
.map((x) => x.trim())
.filter(Boolean);
if (!accounts.length) {
$.log(`未配置 ${CK_NAME}`);
await $.done();
return;
}
$.log(`共找到${accounts.length}个账号`);
for (let i = 0; i < accounts.length; i++) {
await runAccount(accounts[i], i + 1);
await $.wait(800);
}
await $.done();
})().catch(async (e) => {
$.log(`脚本异常:${e.stack || e.message || e}`);
await $.done();
});