Files
smallfawn-QLScriptPublic/wxapp/txdt.js
T
2026-06-10 15:19:00 +08:00

366 lines
13 KiB
JavaScript
Raw 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: 18 8 * * *
------------------------------------------
变量名:txdt
变量值:wx_server 里的 openid,多账号用 & 或换行
也支持 JSON:
{"openid":"xxx"}
依赖变量:
wx_server_url 默认 http://192.168.31.196:8787
wx_auth 必填,wx_server 鉴权值
------------------------------------------
*/
const { Env } = require("../tools/env.js");
const $ = new Env("腾讯地图");
const axios = require("axios");
const crypto = require("crypto");
const CK_NAME = "txdt";
const APP = { name: "腾讯地图", appid: "wx7643d5f831302ab0", version: 545 };
const WX_SERVER_URL = (process.env.wx_server_url || "http://192.168.31.196:8787").replace(/\/$/, "");
const WX_AUTH = process.env.wx_auth || "";
const MINI_LOGIN_BASE = "https://miniapp.map.qq.com";
const MAP_BASE = "https://mmapgwh.map.qq.com";
const LOGIN_ACCESS_KEY = "1";
const LOGIN_SECRET_KEY = "4300eec60bedec22a73408a0d76b03ec";
const TMAP_SECRET = "3a9875e795c3ecff15f617085e72d4cc";
const CHECKIN_TOKEN = "e643d512f085d621bf6c9e80310d0498";
const ACTIVITY_ID = 1721983577;
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 splitAccounts(value = "") {
return String(value)
.split(/\n|&/)
.map((item) => item.trim())
.filter(Boolean);
}
function short(value, max = 320) {
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 md5(value) {
return crypto.createHash("md5").update(String(value)).digest("hex");
}
function sha256(value) {
return crypto.createHash("sha256").update(String(value)).digest("hex");
}
function uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
const n = (Math.random() * 16) | 0;
return (char === "x" ? n : (n & 3) | 8).toString(16);
});
}
function sortedQuery(data) {
const normalized = {};
Object.keys(data)
.sort()
.forEach((key) => {
if (data[key] !== undefined && data[key] !== null) normalized[key] = data[key];
});
return Object.keys(normalized)
.map((key) => `${key}=${normalized[key]}`)
.join("&");
}
function formatCoin(value) {
const num = Number(value || 0);
return `${num}(${(num / 100).toFixed(2)})`;
}
function parseAccount(raw) {
const text = String(raw || "").trim();
if (!text) return {};
if (text.startsWith("{")) {
const data = JSON.parse(text);
return { raw: text, openid: data.openid || data.openId || "", remark: data.remark || data.name || "" };
}
const [openid, remark] = text.split("#").map((item) => item.trim());
return { raw: text, openid, remark };
}
async function request(options) {
const res = await axios.request({
timeout: 20000,
validateStatus: () => true,
...options,
headers: {
"User-Agent": USER_AGENT,
Accept: "application/json, text/plain, */*",
Referer: `https://servicewechat.com/${APP.appid}/${APP.version}/page-frame.html`,
...(options.headers || {}),
},
});
return { status: res.status, headers: res.headers || {}, data: res.data };
}
async function getWxCode(openid) {
if (!WX_AUTH) throw new Error("未配置 wx_auth,无法从 wx_server 获取 code");
const { status, data } = await request({
method: "POST",
url: `${WX_SERVER_URL}/wx/code`,
headers: { auth: WX_AUTH, "content-type": "application/json" },
data: { appid: APP.appid, openid },
});
const code = data?.data?.code || data?.code;
if (status !== 200 || !code) throw new Error(`获取code失败 HTTP ${status}: ${short(data)}`);
return code;
}
function loginSign({ appId, sessionId = "-1", openId, userId, postBody }) {
const reqId = md5(`${Math.random()} ${Date.now()}`);
const reqTime = Date.now().toString().slice(0, 10);
const signParams = {
appId,
reqId,
reqTime,
userId,
openID: openId,
sessionID: sessionId,
accessKey: LOGIN_ACCESS_KEY,
businessStr: JSON.stringify(postBody),
};
const signText = `${sortedQuery(signParams)}&secretKey=${LOGIN_SECRET_KEY}`;
const headers = {
"mapservice-sign-version": "v2",
"mapservice-sign": sha256(signText),
"mapservice-reqid": reqId,
"mapservice-reqtime": reqTime,
"mapservice-appid": appId,
"mapservice-accesskey": LOGIN_ACCESS_KEY,
"mapservice-sessionid": sessionId,
};
if (sessionId && sessionId !== "-1") {
headers["mapservice-openid"] = openId;
headers["mapservice-userid"] = userId;
}
return headers;
}
function mapH5Sign(apiPath, user) {
const reqId = uuid();
const reqTime = Date.now();
const normalizedPath = apiPath.split("?")[0];
const signBase = `mapinst=0&mapnonce=0&reqid=${reqId}&reqtime=${reqTime}`;
const defaultSign = md5(`${signBase}${normalizedPath}0${TMAP_SECRET}`);
const headers = {
"tmap-reqid": reqId,
"tmap-reqtime": reqTime,
"tmap-userid": Number(user.user_id) || Number(user.userId) || 0,
"tmap-login-ssid": user.session_id || user.sessionId || 0,
"tmap-imei": 0,
"tmap-qimei": 0,
"tmap-qimei36": 0,
"tmap-nonce": 0,
"tmap-install-id": 0,
"tmap-sign": 0,
"tmap-default-sign": defaultSign,
"tmap-app-version": 0,
"tmap-channel": 0,
"tmap-engine": "web",
"tmap-mini-login-ssid": user.map_session_id || user.mapSessionId || "",
"tmap-app-id": user.appId || APP.appid,
};
if (user.openid || user.openId) headers["tmap-openid"] = user.openid || user.openId;
return headers;
}
function checkinHeader(user) {
const requestId = uuid();
const timestamp = Math.floor(Date.now() / 1000);
const signText = `request_id=${requestId}&from_source=${APP.appid}&timestamp=${timestamp}&token=${CHECKIN_TOKEN}`;
return {
user_id: user.openid || user.openId,
from_source: APP.appid,
request_id: requestId,
timestamp,
sign: sha256(signText).toUpperCase(),
};
}
class TencentMap {
constructor(rawAccount, index) {
this.index = index;
this.account = parseAccount(rawAccount);
this.loginInfo = {};
this.userInfo = {};
}
async miniLogin() {
if (!this.account.openid) throw new Error("账号格式错误,请配置 wx_server 里的 openid");
const code = await getWxCode(this.account.openid);
const body = {
seqid: uuid(),
app_id: APP.appid,
auth_code: code,
devHeader: {},
};
const { status, data } = await request({
method: "POST",
url: `${MINI_LOGIN_BASE}/minLogin/v2/login`,
headers: {
"content-type": "application/json",
...loginSign({ appId: APP.appid, postBody: body }),
},
data: body,
});
if (status !== 200 || Number(data?.err_code) !== 0) throw new Error(`登录失败 HTTP ${status}: ${short(data)}`);
this.loginInfo = { ...data, appId: APP.appid };
$.log(`登录:成功 userId=${data.user_id || "未知"},openid=${data.openid || "未知"}`);
}
async queryUser() {
const user = this.loginInfo;
const body = {
seqid: uuid(),
app_id: APP.appid,
userId: user.user_id,
openId: user.openid,
source: "mini-tencentmap",
};
const { status, data } = await request({
method: "POST",
url: `${MINI_LOGIN_BASE}/minLogin/v2/getUserInfo`,
headers: {
"content-type": "application/json",
...loginSign({
appId: APP.appid,
sessionId: user.session_id,
userId: user.user_id,
openId: user.openid,
postBody: body,
}),
},
data: body,
});
if (status !== 200 || Number(data?.err_code) !== 0) {
$.log(`用户信息:查询失败 HTTP ${status}: ${short(data)}`);
return;
}
this.userInfo = data || {};
$.log(`用户信息:${data.nickname || "微信用户"},userId=${data.userid || user.user_id}`);
}
async mapApi(apiPath, data) {
const { status, data: body } = await request({
method: "POST",
url: `${MAP_BASE}${apiPath}`,
headers: {
"content-type": "application/json",
...checkinHeader(this.loginInfo),
...mapH5Sign(apiPath, this.loginInfo),
},
data,
});
if (status !== 200 || Number(body?.code) !== 0) throw new Error(`${apiPath} HTTP ${status}: ${short(body)}`);
return body.data || {};
}
async queryBalance(prefix = "现金余额") {
const data = await this.mapApi("/activity/v1/withdraw/home", {
activity_id: ACTIVITY_ID,
game_id: 4,
rule_id: "tencent_map_withdraw",
});
$.log(
`${prefix}:金币=${formatCoin(data.coins)},可提现=${formatCoin(data.withdrawable_amount)},门槛=${formatCoin(data.current_withdraw_threshold)},奖池=${formatCoin(data.jackpot_amount)}`
);
return data;
}
async queryAssets() {
const data = await this.mapApi("/activity/v1/assert/home", { activity_id: ACTIVITY_ID });
$.log(
`资产信息:金币=${formatCoin(data.coins)},优惠券=${data.coupons_total || 0},抽奖券=${data.lottery_ticket_total || 0}`
);
return data;
}
todayKey() {
const now = new Date();
const year = now.getFullYear();
const month = `${now.getMonth() + 1}`.padStart(2, "0");
const day = `${now.getDate()}`.padStart(2, "0");
return `${year}${month}${day}`;
}
async queryCalendar(prefix = "签到状态") {
const data = await this.mapApi("/activity/v1/checkin/calendar", {
activity_id: ACTIVITY_ID,
game_id: 1,
rule_id: "tencent_map_checkin",
});
const today = data.calendar?.[this.todayKey()] || {};
const prizes = Array.isArray(today.prizes)
? today.prizes.map((item) => `${item.name || item.type || "奖励"}:${item.amount ?? ""}`).join(",")
: "";
$.log(`${prefix}:今日${today.checkin ? "已签" : "未签"},周期已签=${data.checkin_days || 0}/${data.period || 0}${prizes ? `,奖励=${prizes}` : ""}`);
return { data, today };
}
async checkin() {
const { today } = await this.queryCalendar("签到前");
if (today.checkin) {
$.log("签到:今日已签到");
return;
}
const data = await this.mapApi("/activity/v1/checkin", {
activity_id: ACTIVITY_ID,
game_id: 1,
rule_id: "tencent_map_checkin",
nick: this.userInfo.nickname || "微信用户",
});
const prizes = Array.isArray(data.prizes)
? data.prizes.map((item) => `${item.name || item.type || "奖励"}:${item.amount ?? ""}`).join(",")
: short(data);
$.log(`签到:成功${prizes ? `,${prizes}` : ""}`);
}
async run() {
$.log(`\n========== ${APP.name} 账号[${this.index}] ${this.account.remark || this.account.openid} ==========`);
await this.miniLogin();
await this.queryUser();
await this.queryBalance("签到前现金余额");
await this.queryAssets();
await this.checkin();
await this.queryBalance("签到后现金余额");
await this.queryCalendar("签到后");
}
}
(async () => {
const accounts = splitAccounts(process.env[CK_NAME] || process.env.tencentmap || process.env.wx_openid || "");
if (!accounts.length) {
$.log(`未配置 ${CK_NAME}`);
await $.done();
return;
}
$.log(`共找到${accounts.length}个账号`);
for (let i = 0; i < accounts.length; i++) {
const runner = new TencentMap(accounts[i], i + 1);
try {
await runner.run();
} catch (e) {
$.log(`账号[${i + 1}] 执行失败:${e.message || e}`);
}
await $.wait(800);
}
await $.done();
})().catch(async (e) => {
$.log(`脚本异常:${e.stack || e.message || e}`);
await $.done();
});