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

356 lines
13 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.08.16
@Description: 丸丫甄选(有赞微商城)每日签到得积分
cron: 18 8 * * *
------------------------------------------
变量名:wyzx
变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行
依赖变量:wx_server_url、wx_auth
------------------------------------------
接口契约(h5.youzan.com,有赞 SaaS 通用签到,与本仓库 yz19.js 同一套):
静默登录 POST /wscshop/weapp/authorize.json {appId, clientBiz, code}
-> {accessToken|access_token, sessionId, kdtId, ...}
签到活动 GET /wscump/checkin/show_checkin_page_v2.json -> {checkinId, isShow}
执行签到 GET /wscump/checkin/checkinV2.json?checkinId=<id>
-> {desc, list[{infos:{title}}]};重复签到报「已达最大参与次数」
积分余额 GET /wscump/integral/user_points.json -> {current_points|real_points}
公共 query:app_id / kdt_id / access_token;公共头:Extra-Data(sid/version/...)
统一响应:code==0 成功,否则 msg 为错误原因
------------------------------------------
常量来源(反编译 wx35322299c6492f6e 主包):
app-config.json /ext -> kdtId=100939936、userVersion=2.219.11.101
app.js authorize.json(静默登录,登录失败码 135000025/160210092 会重试)
app.js / c.js wscump/integral/user_points.json(getPoints)
说明:签到页 packages/ump/sign-in/index 等均在分包内,smallcat 的
/wx/downloadurl 按文档只返回主包(参数仅 openid/appid/version_type),
分包 js 解包后为空壳。故签到接口不是从该小程序分包里读出来的,而是沿用
本仓库已验证可用的有赞通用签到契约(wxapp/yz19.js、yz9d.js),并已在主包中
确认 authorize.json 与 user_points.json 存在,接口族一致,未做接口盲猜。
------------------------------------------
*/
const { Env } = require("../tools/env.js");
const $ = new Env("丸丫甄选签到");
const axios = require("axios");
const fs = require("fs");
const path = require("path");
const MINI_APP_ID = "wx35322299c6492f6e";
const CLIENT_BIZ = "weapp_wsc";
const KDT_ID = "100939936";
const USER_VERSION = "2.219.11.101";
const PAGE_VERSION = "0"; // Referer 里的版本号,仅用于伪装来源,不参与校验
const API_BASE = "https://h5.youzan.com";
const TOKEN_CACHE_FILE = path.join(__dirname, "wanyazhenxuan_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 ckName = "wyzx";
const WX_SERVER_URL = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, "");
const WX_AUTH = process.env.wx_auth || "";
function short(value, max = 200) {
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 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 maskPhone(phone = "") {
return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2");
}
function pickToken(data = {}) {
return data.accessToken || data.access_token || "";
}
function isTokenError(message) {
return /access_token|token|登录|invalid session|session/i.test(String(message || ""));
}
function isRepeatCheckin(message) {
return /已达最大参与次数|已签到|重复签到|今日已参与|已经签到/.test(String(message || ""));
}
// smallcat 偶发 "获取失败"(运行时会话抖动):刷新会话后间隔重试,最多 4 次
async function getWxCode(openid) {
if (!WX_AUTH) throw new Error("未配置 wx_auth");
const headers = { auth: WX_AUTH, "Content-Type": "application/json" };
const body = { appid: MINI_APP_ID, openid };
let lastMsg = "";
for (let attempt = 0; attempt < 4; attempt++) {
if (attempt) {
try {
await axios.request({
method: "POST",
url: `${WX_SERVER_URL}/wx/refresh`,
headers,
data: body,
timeout: 30000,
validateStatus: () => true,
});
} catch (e) {
// 刷新失败不阻断,继续重试取 code
}
await new Promise((r) => setTimeout(r, 3000));
}
const { status, data } = await axios.request({
method: "POST",
url: `${WX_SERVER_URL}/wx/code`,
headers,
data: body,
timeout: 30000,
validateStatus: () => true,
});
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 Task {
constructor(openid) {
this.index = $.userIdx++;
this.openid = String(openid || "").trim();
this.token = "";
this.sessionId = "";
this.cookie = "";
this.kdtId = KDT_ID;
this.userInfo = {};
this.checkinId = "";
this.isShow = false;
this.signedBefore = false;
}
async run() {
const cached = this.getCachedToken();
if (cached) {
this.applyToken(cached);
$.log(`账号[${this.index}] 使用缓存token`);
if (!(await this.checkToken())) {
this.removeCachedToken();
$.log(`账号[${this.index}] 缓存token失效,重新登录`);
}
}
if (!this.token) {
await this.loginByWxCode();
if (!this.token) return;
}
await this.showCheckinPage();
await this.doCheckin();
await this.getPoints();
}
getCachedToken() {
const cache = readTokenCache();
return cache[this.openid] || null;
}
saveCachedToken() {
if (!this.token) return;
const cache = readTokenCache();
cache[this.openid] = {
accessToken: this.token,
sessionId: this.sessionId,
kdtId: this.kdtId,
cookie: this.cookie,
mobile: this.userInfo.mobile || "",
nickName: this.userInfo.nick_name || this.userInfo.nickName || "",
updatedAt: new Date().toISOString(),
};
writeTokenCache(cache);
}
removeCachedToken() {
const cache = readTokenCache();
if (cache[this.openid]) {
delete cache[this.openid];
writeTokenCache(cache);
}
this.token = "";
this.sessionId = "";
this.cookie = "";
}
applyToken(data = {}) {
this.token = pickToken(data);
this.sessionId = data.sessionId || data.session_id || "";
this.kdtId = String(data.kdtId || data.kdt_id || KDT_ID);
// 登录响应本身不带 cookie 字段,此时不要覆盖 request() 从 Set-Cookie 抓到的值
if (data.cookie) this.cookie = data.cookie;
}
getHeaders(extra = {}) {
const headers = {
"User-Agent": USER_AGENT,
"Referer": `https://servicewechat.com/${MINI_APP_ID}/${PAGE_VERSION}/page-frame.html`,
"Accept": "*/*",
"Extra-Data": JSON.stringify({
sid: this.sessionId || "",
version: USER_VERSION,
clientType: "weapp-miniprogram",
client: "weapp",
bizEnv: "wsc",
}),
...extra,
};
if (this.cookie) headers.Cookie = this.cookie;
return headers;
}
getBaseParams(params = {}) {
return {
app_id: MINI_APP_ID,
kdt_id: this.kdtId,
access_token: this.token,
...params,
};
}
async request({ method = "GET", path: apiPath, params = {}, data = {}, skipToken = false }) {
const options = {
method,
url: `${API_BASE}${apiPath.startsWith("/") ? apiPath : `/${apiPath}`}`,
headers: this.getHeaders(method === "POST" ? { "Content-Type": "application/json" } : {}),
timeout: 15000,
validateStatus: () => true,
};
options.params = skipToken ? params : this.getBaseParams(params);
if (method !== "GET") options.data = data;
const { data: result, status, headers } = await axios.request(options);
if (headers && headers["set-cookie"]) {
this.cookie = headers["set-cookie"].map((item) => item.split(";")[0]).join("; ");
}
if (status !== 200) throw new Error(`HTTP ${status}: ${short(result)}`);
if (!result || result.code !== 0) throw new Error(result?.msg || short(result));
return result.data;
}
async loginByWxCode() {
try {
const code = await getWxCode(this.openid);
const data = await this.request({
method: "POST",
path: "/wscshop/weapp/authorize.json",
skipToken: true,
data: {
appId: MINI_APP_ID,
clientBiz: CLIENT_BIZ,
code,
},
});
this.applyToken(data);
this.userInfo = data || {};
if (!this.token) throw new Error(`登录响应未包含 accessToken: ${short(data)}`);
this.saveCachedToken();
$.log(
`账号[${this.index}] 登录成功: ${data.nick_name || data.nickName || ""} ${maskPhone(data.mobile) || ""}`
);
} catch (e) {
$.log(`账号[${this.index}] 登录失败: ${e.message || e}`);
}
}
async checkToken() {
try {
await this.request({ path: "/wscump/integral/user_points.json" });
return true;
} catch (e) {
return false;
}
}
async showCheckinPage() {
try {
const data = await this.request({ path: "/wscump/checkin/show_checkin_page_v2.json" });
this.checkinId = data?.checkinId || "";
this.isShow = !!data?.isShow;
// 幂等预检:仅当服务端明确给出「今日已签」标记时才跳过提交
this.signedBefore =
data?.todayCheckin === true || data?.isCheckin === true || data?.hasCheckinToday === true;
$.log(
`账号[${this.index}] 签到活动: checkinId=${this.checkinId || "未获取"} isShow=${this.isShow}` +
(this.signedBefore ? " 今日已签" : "")
);
} catch (e) {
$.log(`账号[${this.index}] 获取签到活动失败: ${e.message || e}`);
if (isTokenError(e.message || e)) this.removeCachedToken();
}
}
async doCheckin() {
if (!this.checkinId) {
$.log(`账号[${this.index}] 未获取到 checkinId,跳过签到`);
return;
}
if (this.signedBefore) {
$.log(`账号[${this.index}] 今日已签到,跳过提交`);
return;
}
try {
const data = await this.request({
path: "/wscump/checkin/checkinV2.json",
params: { checkinId: this.checkinId },
});
const awards = (data?.list || [])
.map((item) => item?.infos?.title)
.filter(Boolean)
.join(", ");
$.log(`账号[${this.index}] 签到成功: ${data?.desc || ""}${awards ? ` ${awards}` : ""}`);
} catch (e) {
const message = String(e.message || e);
if (isRepeatCheckin(message)) {
$.log(`账号[${this.index}] 今日已签到`);
return;
}
$.log(`账号[${this.index}] 签到失败: ${message}`);
if (isTokenError(message)) this.removeCachedToken();
}
}
async getPoints() {
try {
const data = await this.request({ path: "/wscump/integral/user_points.json" });
$.log(`账号[${this.index}] 当前积分: ${data?.current_points ?? data?.real_points ?? "未知"}`);
} catch (e) {
$.log(`账号[${this.index}] 查询积分失败: ${e.message || e}`);
}
}
}
!(async () => {
$.checkEnv(ckName);
for (const openid of $.userList) {
await new Task(openid).run();
await $.wait(800);
}
})()
.catch((e) => $.log(e.message || e))
.finally(() => $.done());