QLScriptPublic/wxapp/wugenvboshi.js
smallfawn ad27ef4059 1
2026-06-01 16:01:09 +08:00

279 lines
9.2 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.01
@Description: 五个女博士微信小程序签到
cron: 30 8 * * *
------------------------------------------
变量名wugenvboshi
变量值wx_server 里的 openid/账号标识,多账号用 & 或换行
依赖变量wx_server_url、wx_auth
------------------------------------------
*/
const { Env } = require("../tools/env.js");
const $ = new Env("五个女博士微信小程序签到");
const axios = require("axios");
const fs = require("fs");
const path = require("path");
const querystring = require("querystring");
const MINI_APP_ID = "wx271704a01704744b";
const KDT_ID = "44519665";
const USER_VERSION = "2.228.701";
const CLIENT_ID = "4d65249d377b2c3ed8";
const CLIENT_SECRET = "1cdc05151d64f3a4a6ebd0e9de64422a";
const GRANT_TYPE = "yz_union";
const API_BASE = "https://h5.youzan.com";
const TOKEN_CACHE_FILE = path.join(__dirname, "wugenvboshi_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";
let ckName = "wugenvboshi";
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 maskToken(token = "") {
if (!token) return "";
return token.length > 12 ? `${token.slice(0, 6)}***${token.slice(-6)}` : `${token.slice(0, 3)}***`;
}
class Task {
constructor(openid) {
this.index = $.userIdx++;
this.openid = String(openid || "").trim();
this.token = {};
this.checkinId = "";
}
async run() {
const cached = this.getCachedToken();
if (cached) {
this.token = cached;
$.log(`账号[${this.index}] 使用缓存token: ${maskToken(this.token.accessToken || this.token.access_token)}`);
if (!(await this.checkToken())) {
this.removeCachedToken();
$.log(`账号[${this.index}] 缓存token失效重新code登录`);
}
}
if (!this.token.accessToken && !this.token.access_token) {
await this.loginByWxCode();
}
if (!this.token.accessToken && !this.token.access_token) return;
await this.getPoints("签到前");
await this.getCheckinInfo();
await this.doCheckin();
await this.getPoints("签到后");
}
getCachedToken() {
const cache = readTokenCache();
return cache[this.openid] || null;
}
saveCachedToken() {
if (!this.token.accessToken && !this.token.access_token) return;
const cache = readTokenCache();
cache[this.openid] = {
...this.token,
updatedAt: new Date().toISOString(),
};
writeTokenCache(cache);
}
removeCachedToken() {
const cache = readTokenCache();
if (cache[this.openid]) {
delete cache[this.openid];
writeTokenCache(cache);
}
this.token = {};
}
get accessToken() {
return this.token.accessToken || this.token.access_token || "";
}
get sessionId() {
return this.token.sessionId || this.token.session_id || "";
}
buildExtraData() {
return JSON.stringify({
is_weapp: 1,
sid: this.sessionId,
version: USER_VERSION,
client: "weapp",
bizEnv: "wsc",
});
}
buildUrl(apiPath, query = {}) {
const pathname = apiPath.replace(/^\/+/, "");
const params = querystring.stringify({
store_id: "",
app_id: MINI_APP_ID,
kdt_id: KDT_ID,
access_token: this.accessToken,
...query,
});
return `${API_BASE}/${pathname}?${params}`;
}
getHeaders(extra = {}) {
return {
"User-Agent": USER_AGENT,
"Referer": `https://servicewechat.com/${MINI_APP_ID}/142/page-frame.html`,
"Extra-Data": this.buildExtraData(),
"Accept": "application/json, text/plain, */*",
...extra,
};
}
async request({ method = "GET", apiPath, query = {}, data = {}, skipToken = false }) {
const oldToken = this.token;
if (skipToken) this.token = {};
const options = {
method,
url: this.buildUrl(apiPath, query),
headers: this.getHeaders(method === "POST" ? { "Content-Type": "application/json" } : {}),
timeout: 15000,
validateStatus: () => true,
};
if (method !== "GET") options.data = data;
if (skipToken) this.token = oldToken;
const { status, data: result } = await axios.request(options);
if (status !== 200) throw new Error(`HTTP ${status}: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result)}`);
if (!result || result.code !== 0) {
const err = new Error(result?.msg || result?.message || JSON.stringify(result));
err.code = result?.code;
throw err;
}
return result.data;
}
async getWxCode() {
const wxServerUrl = process.env.wx_server_url;
const wxAuth = process.env.wx_auth;
if (!wxServerUrl || !wxAuth) throw new Error("未配置 wx_server_url 或 wx_auth");
const { status, data } = await axios.post(
`${wxServerUrl.replace(/\/$/, "")}/wx/operatedata`,
{ appid: MINI_APP_ID, openid: this.openid },
{
headers: {
auth: wxAuth,
"content-type": "application/json",
},
timeout: 15000,
validateStatus: () => true,
}
);
const code = data?.code || data?.data?.code;
if (status !== 200 || !code) throw new Error(`wx_server 获取code失败: HTTP ${status} ${JSON.stringify(data)}`);
return code;
}
async loginByWxCode() {
try {
const code = await this.getWxCode();
const data = await this.request({
method: "POST",
apiPath: "wscshop/weapp/authorize.json",
skipToken: true,
data: {
appId: MINI_APP_ID,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
grantType: GRANT_TYPE,
code,
},
});
this.token = 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.getPoints("缓存校验");
return true;
} catch (e) {
return false;
}
}
async getPoints(label = "积分") {
const data = await this.request({ apiPath: "wscump/integral/user_points.json" });
const points = data?.current_points ?? data?.real_points ?? data?.total_points ?? "未知";
$.log(`账号[${this.index}] ${label}: ${points}积分`);
return data;
}
async getCheckinInfo() {
const data = await this.request({ apiPath: "wscump/checkin/show_checkin_page_v2.json" });
this.checkinId = data?.checkinId || data?.checkin_id || "";
$.log(`账号[${this.index}] 签到活动: checkinId=${this.checkinId || "未获取"} isShow=${data?.isShow} showPage=${data?.showPage}`);
return data;
}
async doCheckin() {
if (!this.checkinId) {
$.log(`账号[${this.index}] 未获取到签到活动,跳过`);
return;
}
try {
const data = await this.request({
apiPath: "wscump/checkin/checkinV2.json",
query: { checkinId: this.checkinId },
});
const award = (data?.list || [])
.map((item) => item?.infos?.title || item?.infos?.desc || "")
.filter(Boolean)
.join(", ");
$.log(`账号[${this.index}] 签到成功: ${data?.desc || ""}${award ? ` ${award}` : ""}`);
} catch (e) {
const message = String(e.message || e);
if (/已签到|已经签到|重复|今日.*签|参与次数/.test(message)) {
$.log(`账号[${this.index}] 今日已签到`);
return;
}
$.log(`账号[${this.index}] 签到失败: ${message}`);
if (e.code === -1 || e.code === 40010 || e.code === 40009 || /登录|token|access/i.test(message)) this.removeCachedToken();
}
}
}
!(async () => {
$.checkEnv(ckName);
for (const openid of $.userList) {
await new Task(openid).run();
}
})()
.catch((e) => $.log(e.message || e))
.finally(() => $.done());