Skip to content

Commit ad27ef4

Browse files
committed
1
1 parent f8d42bf commit ad27ef4

8 files changed

Lines changed: 2259 additions & 0 deletions

File tree

wxapp/aiguoyue.js

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/*
2+
------------------------------------------
3+
@Author: sm
4+
@Date: 2026.06.01
5+
@Description: 爱果乐之家微信小程序签到
6+
cron: 30 8 * * *
7+
------------------------------------------
8+
变量名:aiguoyue
9+
变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行
10+
依赖变量:wx_server_url、wx_auth
11+
------------------------------------------
12+
*/
13+
14+
const { Env } = require("../tools/env.js");
15+
const $ = new Env("爱果乐之家微信小程序签到");
16+
const axios = require("axios");
17+
const fs = require("fs");
18+
const path = require("path");
19+
const querystring = require("querystring");
20+
21+
const MINI_APP_ID = "wxa1086b8081476f46";
22+
const KDT_ID = "18774683";
23+
const USER_VERSION = "2.195.7.101";
24+
const CLIENT_ID = "4d65249d377b2c3ed8";
25+
const CLIENT_SECRET = "1cdc05151d64f3a4a6ebd0e9de64422a";
26+
const GRANT_TYPE = "yz_union";
27+
const API_BASE = "https://h5.youzan.com";
28+
const TOKEN_CACHE_FILE = path.join(__dirname, "aiguoyue_token_cache.json");
29+
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";
30+
31+
let ckName = "aiguoyue";
32+
33+
function readTokenCache() {
34+
try {
35+
if (!fs.existsSync(TOKEN_CACHE_FILE)) return {};
36+
return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {};
37+
} catch (e) {
38+
return {};
39+
}
40+
}
41+
42+
function writeTokenCache(cache) {
43+
try {
44+
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(cache, null, 2), "utf8");
45+
} catch (e) {
46+
$.log(`写入token缓存失败: ${e.message || e}`);
47+
}
48+
}
49+
50+
function maskPhone(phone = "") {
51+
return String(phone).replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2");
52+
}
53+
54+
function maskToken(token = "") {
55+
if (!token) return "";
56+
return token.length > 12 ? `${token.slice(0, 6)}***${token.slice(-6)}` : `${token.slice(0, 3)}***`;
57+
}
58+
59+
class Task {
60+
constructor(openid) {
61+
this.index = $.userIdx++;
62+
this.openid = String(openid || "").trim();
63+
this.token = {};
64+
this.checkinId = "";
65+
}
66+
67+
async run() {
68+
const cached = this.getCachedToken();
69+
if (cached) {
70+
this.token = cached;
71+
$.log(`账号[${this.index}] 使用缓存token: ${maskToken(this.accessToken)}`);
72+
if (!(await this.checkToken())) {
73+
this.removeCachedToken();
74+
$.log(`账号[${this.index}] 缓存token失效,重新code登录`);
75+
}
76+
}
77+
78+
if (!this.accessToken) {
79+
await this.loginByWxCode();
80+
}
81+
if (!this.accessToken) return;
82+
83+
await this.getPoints("签到前");
84+
await this.getCheckinInfo();
85+
await this.getActivityInfo();
86+
await this.doCheckin();
87+
await this.getPoints("签到后");
88+
}
89+
90+
getCachedToken() {
91+
const cache = readTokenCache();
92+
return cache[this.openid] || null;
93+
}
94+
95+
saveCachedToken() {
96+
if (!this.accessToken) return;
97+
const cache = readTokenCache();
98+
cache[this.openid] = {
99+
...this.token,
100+
updatedAt: new Date().toISOString(),
101+
};
102+
writeTokenCache(cache);
103+
}
104+
105+
removeCachedToken() {
106+
const cache = readTokenCache();
107+
if (cache[this.openid]) {
108+
delete cache[this.openid];
109+
writeTokenCache(cache);
110+
}
111+
this.token = {};
112+
}
113+
114+
get accessToken() {
115+
return this.token.accessToken || this.token.access_token || "";
116+
}
117+
118+
get sessionId() {
119+
return this.token.sessionId || this.token.session_id || "";
120+
}
121+
122+
buildExtraData() {
123+
return JSON.stringify({
124+
is_weapp: 1,
125+
sid: this.sessionId,
126+
version: USER_VERSION,
127+
client: "weapp",
128+
bizEnv: "wsc",
129+
});
130+
}
131+
132+
buildUrl(apiPath, query = {}) {
133+
const pathname = apiPath.replace(/^\/+/, "");
134+
const params = querystring.stringify({
135+
store_id: "",
136+
app_id: MINI_APP_ID,
137+
kdt_id: KDT_ID,
138+
access_token: this.accessToken,
139+
...query,
140+
});
141+
return `${API_BASE}/${pathname}?${params}`;
142+
}
143+
144+
getHeaders(extra = {}) {
145+
return {
146+
"User-Agent": USER_AGENT,
147+
"Referer": `https://servicewechat.com/${MINI_APP_ID}/3/page-frame.html`,
148+
"Extra-Data": this.buildExtraData(),
149+
"Accept": "application/json, text/plain, */*",
150+
...extra,
151+
};
152+
}
153+
154+
async request({ method = "GET", apiPath, query = {}, data = {}, skipToken = false }) {
155+
const oldToken = this.token;
156+
if (skipToken) this.token = {};
157+
const options = {
158+
method,
159+
url: this.buildUrl(apiPath, query),
160+
headers: this.getHeaders(method === "POST" ? { "Content-Type": "application/json" } : {}),
161+
timeout: 15000,
162+
validateStatus: () => true,
163+
};
164+
if (method !== "GET") options.data = data;
165+
if (skipToken) this.token = oldToken;
166+
167+
const { status, data: result } = await axios.request(options);
168+
if (status !== 200) throw new Error(`HTTP ${status}: ${typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result)}`);
169+
if (!result || result.code !== 0) {
170+
const err = new Error(result?.msg || result?.message || JSON.stringify(result));
171+
err.code = result?.code;
172+
throw err;
173+
}
174+
return result.data;
175+
}
176+
177+
async getWxCode() {
178+
const wxServerUrl = process.env.wx_server_url;
179+
const wxAuth = process.env.wx_auth;
180+
if (!wxServerUrl || !wxAuth) throw new Error("未配置 wx_server_url 或 wx_auth");
181+
182+
const { status, data } = await axios.post(
183+
`${wxServerUrl.replace(/\/$/, "")}/wx/operatedata`,
184+
{ appid: MINI_APP_ID, openid: this.openid },
185+
{
186+
headers: {
187+
auth: wxAuth,
188+
"content-type": "application/json",
189+
},
190+
timeout: 15000,
191+
validateStatus: () => true,
192+
}
193+
);
194+
const code = data?.code || data?.data?.code;
195+
if (status !== 200 || !code) throw new Error(`wx_server 获取code失败: HTTP ${status} ${JSON.stringify(data)}`);
196+
return code;
197+
}
198+
199+
async loginByWxCode() {
200+
try {
201+
const code = await this.getWxCode();
202+
const data = await this.request({
203+
method: "POST",
204+
apiPath: "wscshop/weapp/authorize.json",
205+
skipToken: true,
206+
data: {
207+
appId: MINI_APP_ID,
208+
clientId: CLIENT_ID,
209+
clientSecret: CLIENT_SECRET,
210+
grantType: GRANT_TYPE,
211+
code,
212+
},
213+
});
214+
this.token = data || {};
215+
this.saveCachedToken();
216+
$.log(`账号[${this.index}] 登录成功: ${data?.nick_name || data?.nickName || ""} ${maskPhone(data?.mobile)}`);
217+
} catch (e) {
218+
$.log(`账号[${this.index}] 登录失败: ${e.message || e}`);
219+
}
220+
}
221+
222+
async checkToken() {
223+
try {
224+
await this.getPoints("缓存校验");
225+
return true;
226+
} catch (e) {
227+
return false;
228+
}
229+
}
230+
231+
async getPoints(label = "积分") {
232+
const data = await this.request({ apiPath: "wscump/integral/user_points.json" });
233+
const points = data?.current_points ?? data?.real_points ?? data?.total_points ?? "未知";
234+
$.log(`账号[${this.index}] ${label}: ${points}积分`);
235+
return data;
236+
}
237+
238+
async getCheckinInfo() {
239+
const data = await this.request({ apiPath: "wscump/checkin/check-in-info.json" });
240+
this.checkinId = data?.checkInId || data?.checkinId || data?.check_in_id || "";
241+
$.log(`账号[${this.index}] 签到活动: checkinId=${this.checkinId || "未获取"} ownerKdtId=${data?.activityOwnerKdtId || ""}`);
242+
return data;
243+
}
244+
245+
async getActivityInfo() {
246+
if (!this.checkinId) return null;
247+
const data = await this.request({
248+
apiPath: "wscump/checkin/get_activity_by_yzuid_v2.json",
249+
query: { checkinId: this.checkinId },
250+
});
251+
this.isCheckin = !!data?.isCheckin;
252+
this.isOpen = data?.isOpen !== false;
253+
const reward = (data?.dailyRewards || []).map((item) => item?.desc).filter(Boolean).join(", ");
254+
$.log(`账号[${this.index}] 签到状态: ${this.isCheckin ? "已签" : "未签"} 连续${data?.continuesDay ?? 0}${reward ? ` 今日奖励${reward}` : ""}`);
255+
return data;
256+
}
257+
258+
async doCheckin() {
259+
if (!this.checkinId) {
260+
$.log(`账号[${this.index}] 未获取到签到活动,跳过`);
261+
return;
262+
}
263+
if (this.isCheckin) {
264+
$.log(`账号[${this.index}] 今日已签到`);
265+
return;
266+
}
267+
if (!this.isOpen) {
268+
$.log(`账号[${this.index}] 签到活动未开启`);
269+
return;
270+
}
271+
272+
try {
273+
const data = await this.request({
274+
apiPath: "wscump/checkin/checkinV2.json",
275+
query: { checkinId: this.checkinId },
276+
});
277+
const award = (data?.list || [])
278+
.map((item) => item?.infos?.title || item?.infos?.desc || "")
279+
.filter(Boolean)
280+
.join(", ");
281+
$.log(`账号[${this.index}] 签到成功: ${data?.desc || ""}${award ? ` ${award}` : ""}`);
282+
} catch (e) {
283+
const message = String(e.message || e);
284+
if (/|||.*|/.test(message)) {
285+
$.log(`账号[${this.index}] 今日已签到`);
286+
return;
287+
}
288+
$.log(`账号[${this.index}] 签到失败: ${message}`);
289+
if (e.code === -1 || e.code === 40010 || e.code === 40009 || /|token|access/i.test(message)) this.removeCachedToken();
290+
}
291+
}
292+
}
293+
294+
!(async () => {
295+
$.checkEnv(ckName);
296+
for (const openid of $.userList) {
297+
await new Task(openid).run();
298+
}
299+
})()
300+
.catch((e) => $.log(e.message || e))
301+
.finally(() => $.done());

0 commit comments

Comments
 (0)