mirror of
https://github.com/smallfawn/QLScriptPublic.git
synced 2026-11-06 06:46:48 +08:00
从 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>
251 lines
10 KiB
JavaScript
251 lines
10 KiB
JavaScript
/*
|
||
------------------------------------------
|
||
@Description: 微盟onecrm(fansquan)会员 - 微信小程序静默登录 + 每日签到
|
||
cron: 45 8 * * *
|
||
------------------------------------------
|
||
变量名:yzyj
|
||
变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行分隔(可加 #备注)
|
||
|
||
依赖变量:
|
||
wx_server_url 默认 http://192.168.31.196:8787
|
||
wx_auth 必填,wx_server 鉴权值
|
||
------------------------------------------
|
||
契约(appid wxa61f98248d20178b,host xapi.weimob.com,微盟 onecrm/signgift):
|
||
(迁移自 YYB-GO 系抓包脚本,原脚本已 code 登录)
|
||
|
||
登录 POST /fe/mapi/user/loginX JSON {appid, code, basicInfo:{bosId,cid,tcode:"weimob",vid}, env:"production",
|
||
extendInfo:{source:1}, is_pre_fetch_open:true, parentVid:0, pid:"", storeId:"", queryAuthConfig:true}
|
||
-> errcode==0,token 取 token/accessToken/access_token/jwt(或 data.*);token 放头 X-WX-Token
|
||
签状态 POST /api3/onecrm/mactivity/sign/misc/sign/activity/c/signMainInfo -> data.isSign(true=今日已签)
|
||
签到 POST /api3/onecrm/mactivity/sign/misc/sign/activity/core/c/sign -> errcode==0 && data.isSign 成功
|
||
(两请求都带一组 weimob 商户头:x-wmsdk-vid / cloud-project-name:fansquan / x-component-is:onecrm/signgift / cloud-bosid 等)
|
||
|
||
vid/bosId/merchantId/productInstanceId/wid 等是这家小程序绑定的固定商户配置(原脚本硬编码,非个人凭证)。
|
||
未注册:loginX errcode!=0 或拿不到 token。
|
||
------------------------------------------
|
||
*/
|
||
|
||
const { Env } = require("../tools/env.js");
|
||
const $ = new Env("微盟会员签到");
|
||
const axios = require("axios");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const WeChatServer = require("./wcs.js");
|
||
|
||
const ckName = "yzyj";
|
||
const MINI_APP_ID = "wxa61f98248d20178b";
|
||
const BASE = "https://xapi.weimob.com";
|
||
const TOKEN_CACHE_FILE = path.join(__dirname, "yzyj_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 = "/fe/mapi/user/loginX";
|
||
const EP_SIGN_STATUS = "/api3/onecrm/mactivity/sign/misc/sign/activity/c/signMainInfo";
|
||
const EP_SIGN = "/api3/onecrm/mactivity/sign/misc/sign/activity/core/c/sign";
|
||
|
||
// —— 这家小程序绑定的固定商户配置(原脚本硬编码常量,非个人凭证)——
|
||
const VID = 6016741943359;
|
||
const BOS_ID = 4022115200359;
|
||
const MERCHANT_ID = 2000230069359;
|
||
const CID = 821033359;
|
||
const PRODUCT_INSTANCE_ID = 15532102359;
|
||
const WID = 11983225884;
|
||
const MERCHANT_HEADERS = {
|
||
"x-wmsdk-vid": String(VID),
|
||
"x-biz-id": "146",
|
||
"cloud-project-name": "fansquan",
|
||
"x-component-is": "onecrm/signgift",
|
||
"cloud-bosid": String(BOS_ID),
|
||
"weimob-bosId": String(BOS_ID),
|
||
};
|
||
const SIGN_BASIC_INFO = {
|
||
vid: VID, vidType: 2, bosId: BOS_ID, productId: 146,
|
||
productInstanceId: PRODUCT_INSTANCE_ID, productVersionId: "10003",
|
||
merchantId: MERCHANT_ID, tcode: "weimob", cid: CID,
|
||
};
|
||
|
||
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(cache) {
|
||
try {
|
||
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8");
|
||
} catch (e) {
|
||
$.log(`写入token缓存失败: ${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 = 200) {
|
||
const t = typeof v === "string" ? v : JSON.stringify(v);
|
||
return !t ? "" : t.length > n ? `${t.slice(0, n)}...` : t;
|
||
}
|
||
|
||
function extractToken(data) {
|
||
if (!data || typeof data !== "object") return "";
|
||
const d = data.data || {};
|
||
for (const key of ["token", "accessToken", "access_token", "jwt"]) {
|
||
const val = data[key] || d[key];
|
||
if (val && String(val) !== "null") return String(val);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
const isOk = (res) => Number(res?.errcode) === 0;
|
||
const msgOf = (res) => res?.errmsg || res?.msg || short(res);
|
||
const isAlreadyDone = (t) => /已签|已经签|签到过|重复|已完成|already/i.test(String(t || ""));
|
||
const isAuthError = (res) => /登录|token|未授权|失效|过期|未登录/i.test(msgOf(res));
|
||
|
||
class Task {
|
||
constructor(raw) {
|
||
this.index = $.userIdx++;
|
||
this.account = parseAccount(raw);
|
||
this.token = "";
|
||
}
|
||
log(text) {
|
||
$.log(`账号[${this.index}]${this.account.remark ? `[${this.account.remark}]` : ""} ${text}`);
|
||
}
|
||
headers(extra = {}) {
|
||
const h = {
|
||
Host: "xapi.weimob.com",
|
||
"User-Agent": UA,
|
||
"Content-Type": "application/json",
|
||
Accept: "*/*",
|
||
Referer: `https://servicewechat.com/${MINI_APP_ID}/109/page-frame.html`,
|
||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||
...extra,
|
||
};
|
||
if (this.token) h["X-WX-Token"] = this.token;
|
||
return h;
|
||
}
|
||
async request(apiPath, body, extra = {}) {
|
||
const res = await axios.request({
|
||
method: "POST", url: `${BASE}${apiPath}`, data: body || {},
|
||
headers: this.headers(extra), 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 body = {
|
||
appid: MINI_APP_ID,
|
||
basicInfo: { bosId: String(BOS_ID), cid: String(CID), tcode: "weimob", vid: String(VID) },
|
||
env: "production", extendInfo: { source: 1 },
|
||
is_pre_fetch_open: true, parentVid: 0, pid: "", storeId: "",
|
||
code, queryAuthConfig: true,
|
||
};
|
||
const res = await this.request(EP_LOGIN, body);
|
||
this.token = extractToken(res);
|
||
if (!isOk(res) || !this.token) {
|
||
this.unregistered = true;
|
||
throw new Error(`NO_TOKEN:${msgOf(res)}`);
|
||
}
|
||
const cache = readCache();
|
||
cache[this.account.openid] = { token: this.token, updatedAt: new Date().toISOString() };
|
||
writeCache(cache);
|
||
this.log("登录成功");
|
||
}
|
||
async checkSigned() {
|
||
const body = { appid: MINI_APP_ID, basicInfo: SIGN_BASIC_INFO, extendInfo: { wxTemplateId: 7930 } };
|
||
const res = await this.request(EP_SIGN_STATUS, body, MERCHANT_HEADERS);
|
||
if (isOk(res)) return !!(res.data || {}).isSign;
|
||
return false;
|
||
}
|
||
async doSign() {
|
||
const body = {
|
||
appid: MINI_APP_ID,
|
||
basicInfo: SIGN_BASIC_INFO,
|
||
extendInfo: {
|
||
wxTemplateId: 8105, analysis: [], bosTemplateId: 1000002154,
|
||
childTemplateIds: [
|
||
{ customId: 90004, version: "crm@0.1.81" },
|
||
{ customId: 90002, version: "ec@80.0" },
|
||
{ customId: 90006, version: "hudong@0.0.251" },
|
||
{ customId: 90008, version: "cms@0.0.524" },
|
||
{ customId: 90070, version: "1.0.12" },
|
||
],
|
||
quickdeliver: { enable: true }, youshu: { enable: false },
|
||
source: 1, channelsource: 5, refer: "onecrm-signgift", mpScene: 1005,
|
||
},
|
||
queryParameter: null,
|
||
i18n: { language: "zh", timezone: "8" },
|
||
pid: "", storeId: "",
|
||
customInfo: { source: 0, wid: WID },
|
||
};
|
||
return this.request(EP_SIGN, body, MERCHANT_HEADERS);
|
||
}
|
||
async sign(retry = true) {
|
||
if (await this.checkSigned()) return this.log("✅ 今日已签到");
|
||
const res = await this.doSign();
|
||
if (isOk(res)) {
|
||
const d = res.data || {};
|
||
const r = d.rewardInfo || {};
|
||
if (d.isSign) {
|
||
const pts = r.integral || r.score || 0;
|
||
return this.log(`✅ 签到成功:${r.rewardName || "签到奖励"}${pts ? ` +${pts}积分` : ""}`);
|
||
}
|
||
return this.log(`✅ 已签到(${short(d)})`);
|
||
}
|
||
if (isAlreadyDone(msgOf(res))) return this.log(`✅ 今日已签到(${msgOf(res)})`);
|
||
if (retry && isAuthError(res)) {
|
||
this.log("会话失效,重新登录后重试");
|
||
this.token = "";
|
||
try { await this.login(); } catch (e) { if (String(e.message).startsWith("NO_TOKEN")) { this.log(`⚠️ 未注册: ${e.message.slice(9)}`); return; } throw e; }
|
||
return this.sign(false);
|
||
}
|
||
this.log(`❌ 签到失败: ${msgOf(res)}`);
|
||
}
|
||
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_TOKEN")) {
|
||
this.log(`⚠️ 该微信号还没在此微盟商户注册会员(登录 ${e.message.slice(9)}),先在小程序里注册一次再跑`);
|
||
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());
|