Skip to content

Commit a108574

Browse files
authored
Merge pull request #311 from fanhengkui/main
问题修复
2 parents 26d50db + 36500be commit a108574

2 files changed

Lines changed: 322 additions & 1 deletion

File tree

‎wxapp/pddgy-公众号版.js‎

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
/*
2+
------------------------------------------
3+
@Description: 拼多多果园公众号版(多多果园) - 微信小程序 code 静默登录 + 每日签到(+浇水)
4+
cron: 28 9,13 * * *
5+
------------------------------------------
6+
变量名:pddgy
7+
变量值:wx_server 里的 openid/账号标识,多账号用 & 或换行分隔(可加 #备注)
8+
9+
依赖变量:
10+
wx_server_url 默认 http://192.168.31.196:8787
11+
wx_auth 必填,wx_server 鉴权值
12+
------------------------------------------
13+
契约(appid wx839691cce7c102bb,重风控站点):
14+
15+
16+
登录 GET https://mobile.yangkeduo.com/garden_index_lz_0.html?_pdd_fs=1&_pdd_tc=676666&_pdd_sbs=1&fun_id=wechat_app_home&__wls_rt=1&__wls_lt=1&__wls_fm=b&code=${code}&state=BASE
17+
Cookie = PDDAccessToken=<access_token>; pdd_user_id=<uid>; pdd_user_uin=<uin>; <Set-Cookie>
18+
鉴权=纯 Cookie(业务请求无 HMAC/签名),业务 URL 追加 ?pdduid=<uid>
19+
风控:暂无
20+
首页 POST /proxy/api/api/manor-query/proxy/home/page {..., tubetoken}
21+
-> tubetoken(签到/浇水必需) / water_amount;error_code 40001 = Cookie 失效
22+
签到 POST /proxy/api/api/manor/common/apply/activity?pdduid=<uid>
23+
body = {type:201811, params:{ui_id:3,type:2}, fun_id:"wechat_app_home", tubetoken, fun_pl:2}
24+
-> error_code==0 / success==true 成功;已签常见 error_msg 含「已签/重复/已领取」
25+
浇水 POST /proxy/api/api/manor/water/cost {..., cost_water_amount:10, tubetoken}(附带,非核心,失败不影响签到判定)
26+
app_id=33 / xcx_version / activity type 201811 均为该小程序固定应用常量(原脚本硬编码,非个人凭证)。
27+
业务基址 https://mobile.yangkeduo.com/proxy/api/api ,登录基址 https://api.pinduoduo.com 。
28+
------------------------------------------
29+
*/
30+
31+
const { Env } = require("../tools/env.js");
32+
const $ = new Env("拼多多果园公众号版");
33+
const axios = require("axios");
34+
const fs = require("fs");
35+
const path = require("path");
36+
const WeChatServer = require("./wcs.js");
37+
38+
const ckName = "pddgy_gzh";
39+
const MINI_APP_ID = "wx839691cce7c102bb";
40+
const XCX_VERSION = "v8.6.21";
41+
const PDD_APP_ID = 33;
42+
const API_BASE = "https://api.pinduoduo.com";
43+
const ORCHARD_BASE = "https://mobile.yangkeduo.com";
44+
const MANOR_BASE = ORCHARD_BASE + "/proxy/api/api";
45+
const CHECKIN_TYPE = 201811;
46+
const MAX_WATER_TIMES = 20; // 附带浇水上限(非核心)
47+
const TOKEN_CACHE_FILE = path.join(__dirname, "pddgy_token_cache.json");
48+
const UA =
49+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 " +
50+
"MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF XWEB/19895 miniProgram/wx32540bd863b27570";
51+
52+
const wechat = new WeChatServer({
53+
url: process.env.wx_server_url || "http://192.168.31.196:8787",
54+
appid: MINI_APP_ID,
55+
auth: process.env.wx_auth || "",
56+
});
57+
58+
function readCache() {
59+
try { if (!fs.existsSync(TOKEN_CACHE_FILE)) return {}; return JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, "utf8")) || {}; } catch (e) { return {}; }
60+
}
61+
function writeCache(c) {
62+
try { fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify(c, null, 2), "utf8"); } catch (e) { $.log(`写入缓存失败: ${e.message || e}`); }
63+
}
64+
function parseAccount(raw = "") {
65+
const [id, remark] = String(raw).split("#").map((s) => (s || "").trim());
66+
return { openid: id, remark: remark || "" };
67+
}
68+
function short(v, n = 240) {
69+
const t = typeof v === "string" ? v : JSON.stringify(v);
70+
return !t ? "" : t.length > n ? `${t.slice(0, n)}...` : t;
71+
}
72+
function asObj(d) {
73+
if (d && typeof d === "object") return d;
74+
if (typeof d === "string") { try { return JSON.parse(d); } catch { return {}; } }
75+
return {};
76+
}
77+
function extractUid(cookieStr) {
78+
const m = /pdd_user_id=(\d+)/.exec(cookieStr || "");
79+
return m ? m[1] : "";
80+
}
81+
82+
function okCode(res) {
83+
if (!res) return false;
84+
if (res.success === true) return true;
85+
if (Number(res.error_code) === 0 && (res.error_code === 0 || res.error_code === "0")) return true;
86+
if (res.code === 0) return true;
87+
return false;
88+
}
89+
function respMsg(res) {
90+
if (!res) return "";
91+
return res.error_msg || res.msg || res.message || res.errorMsg || "";
92+
}
93+
94+
function get_accessToken(cookieStr) {
95+
const m = /PDDAccessToken=(\d+)/.exec(cookieStr || "");
96+
return m ? m[1] : "";
97+
}
98+
function get_pdd_user_id(cookieStr) {
99+
const m = /pdd_user_id=(\d+)/.exec(cookieStr || "");
100+
return m ? m[1] : "";
101+
}
102+
function get_pdd_user_uin(cookieStr) {
103+
const m = /pdd_user_uin=(\d+)/.exec(cookieStr || "");
104+
return m ? m[1] : "";
105+
}
106+
107+
class Task {
108+
constructor(raw) {
109+
this.index = $.userIdx++;
110+
this.account = parseAccount(raw);
111+
this.cookieStr = "";
112+
this.pdduid = "";
113+
this.tubetoken = "";
114+
this.water = 0;
115+
}
116+
log(text) {
117+
$.log(`账号[${this.index}]${this.account.remark ? `[${this.account.remark}]` : ""} ${text}`);
118+
}
119+
async getCode() {
120+
const { data } = await wechat.getCode(this.account.openid);
121+
if (data && data.status === false) throw new Error(`wx_server 取code失败: ${data.message || short(data)}`);
122+
const code = data?.data?.code || data?.code;
123+
if (!code || typeof code !== "string") throw new Error(`wx_server 未返回 code: ${short(data)}`);
124+
return code;
125+
}
126+
async login() {
127+
const code = await this.getCode();
128+
this.log(`获取code: ${short(code, 12)}...`);
129+
const loginBody = {
130+
code,
131+
has_auth: false,
132+
app_id: PDD_APP_ID,
133+
support_enhance_type: 3,
134+
xcx_version: XCX_VERSION,
135+
};
136+
const res = await axios.request({
137+
method: "GET",
138+
url: `https://mobile.yangkeduo.com/garden_index_lz_0.html?_pdd_fs=1&_pdd_tc=676666&_pdd_sbs=1&fun_id=wechat_app_home&__wls_rt=1&__wls_lt=1&__wls_fm=b&code=${code}&state=BASE`,
139+
//data: JSON.stringify(loginBody),
140+
headers: {
141+
"Content-Type": "application/json;charset=UTF-8",
142+
"User-Agent": UA,
143+
Accept: "*/*",
144+
Referer: `https://servicewechat.com/${MINI_APP_ID}/1840/page-frame.html`,
145+
"x-xcx-queries": `mini_program_name=pdd;mp_theme_version=${XCX_VERSION}`,
146+
xweb_xhr: "1",
147+
},
148+
timeout: 20000,
149+
validateStatus: () => true,
150+
});
151+
152+
let setCookieStr = "";
153+
const sc = res.headers && res.headers["set-cookie"];
154+
if (sc) {
155+
const arr = Array.isArray(sc) ? sc : [sc];
156+
setCookieStr = arr.map((c) => String(c).split(";")[0]).join("; ");
157+
}
158+
console.log(setCookieStr)
159+
// 使用示例
160+
const accessToken = get_accessToken(setCookieStr);
161+
const uid = get_pdd_user_id(setCookieStr);
162+
const uin = get_pdd_user_uin(setCookieStr);
163+
const acid = "";
164+
165+
const parts = [`PDDAccessToken=${accessToken}`, `pdd_user_id=${uid}`];
166+
if (uin) parts.push(`pdd_user_uin=${uin}`);
167+
if (acid) parts.push(`acid=${acid}`);
168+
if (setCookieStr) parts.push(setCookieStr);
169+
170+
this.cookieStr = parts.join("; ");
171+
this.pdduid = uid;
172+
const cache = readCache();
173+
cache[this.account.openid] = { cookieStr: this.cookieStr, uid, updatedAt: new Date().toISOString() };
174+
writeCache(cache);
175+
this.log(`登录成功 uid=${uid}`);
176+
}
177+
async manorPost(apiPath, body) {
178+
const url = `${MANOR_BASE}${apiPath}${apiPath.includes("?") ? "&" : "?"}pdduid=${this.pdduid}`;
179+
const res = await axios.request({
180+
method: "POST",
181+
url,
182+
data: JSON.stringify(body || {}),
183+
headers: {
184+
"User-Agent": UA,
185+
Accept: "application/json, text/plain, */*",
186+
"Content-Type": "application/json;charset=UTF-8",
187+
Origin: ORCHARD_BASE,
188+
Referer: `${ORCHARD_BASE}/garden_index_lz_0.html`,
189+
Cookie: this.cookieStr,
190+
},
191+
timeout: 20000,
192+
validateStatus: () => true,
193+
});
194+
return asObj(res.data);
195+
}
196+
// 首页:刷新 tubetoken + 水滴,同时用作 Cookie 有效性探测
197+
async getHomePage() {
198+
const body = {
199+
mission_type: 0, fun_id: "wechat_app_home", message_source: null,
200+
page_type: "HOME_PAGE", push_source_mission_type: 0, fruit_config_version: "",
201+
unlock_scene_version: "", app_home_click_icon_type: null, tubetoken: this.tubetoken,
202+
push_act_source: null, need_show_home_popup: true, fun_pl: 2,
203+
};
204+
const result = await this.manorPost("/manor-query/proxy/home/page", body);
205+
if (!result || !result.user_manor_info) { this.log(` 首页校验失败(40001),Cookie 可能已过期`); return false; }
206+
if (result.user_manor_info.tubetoken) this.tubetoken = result.user_manor_info.tubetoken;
207+
if (result.user_manor_info.water_amount != null) this.water = result.user_manor_info.water_amount;
208+
return !!this.tubetoken;
209+
}
210+
async getWater() {
211+
const result = await this.manorPost("/manor-gateway/manor/query/user/water?is_back=1", {});
212+
return (result && result.water_amount) || 0;
213+
}
214+
async checkin() {
215+
const body = {
216+
type: CHECKIN_TYPE,
217+
params: { ui_id: 3, type: 2 },
218+
fun_id: "wechat_app_home",
219+
tubetoken: this.tubetoken,
220+
fun_pl: 2,
221+
};
222+
const res = await this.manorPost("/manor/common/apply/activity", body);
223+
if (okCode(res)) {
224+
const reward = res.water || res.reward_amount || (res.data && (res.data.water || res.data.reward_amount)) || "";
225+
this.log(`✅ 签到成功${reward ? `,+${reward}水滴` : ""}`);
226+
return { ok: true };
227+
}
228+
const msg = respMsg(res) || short(res);
229+
if (/已签|签到过|重复|已领取|已完成|already|repeat/i.test(String(msg))) {
230+
this.log(`✅ 今日已签到(${msg})`);
231+
return { ok: true, already: true };
232+
}
233+
// 风控类反馈
234+
if (/验证|风控|拦截|滑块|verify|forbidden/i.test(String(msg)) || Number(res.error_code) === 54002) {
235+
this.blocked = true;
236+
this.log(`⛔ 签到被风控拦截: ${msg}`);
237+
return { ok: false, blocked: true, msg };
238+
}
239+
this.log(`❌ 签到失败: ${msg}`);
240+
return { ok: false, msg };
241+
}
242+
// 附带浇水(非核心:失败/风控都不影响签到判定)
243+
async waterTree() {
244+
try {
245+
let water = await this.getWater();
246+
if (water < 10) { this.log(` [浇水] 水滴 ${water} 不足10,跳过`); return; }
247+
const count = Math.min(MAX_WATER_TIMES, Math.floor(water / 10));
248+
let watered = 0;
249+
let curr = water;
250+
for (let i = 0; i < count; i++) {
251+
const body = {
252+
atw: true, location_auth: false, last_stay_time: Math.floor(Math.random() * 40) + 10,
253+
can_trigger_random_mission: false, product_scene: 0, minor: false,
254+
ext_params: { can_trigger201824: true }, mission_type: 0, cost_water_amount: 10,
255+
merge_cost: false, fun_id: "wechat_app_home", lower_end_device: false,
256+
cost_water_competition_in_scene_icon: false, is_small_screen: true,
257+
tubetoken: this.tubetoken, fun_pl: 2,
258+
};
259+
const r = await this.manorPost("/manor/water/cost", body);
260+
const left = r && r.now_water_amount;
261+
if (left != null && left < curr) { curr = left; watered++; if (left < 10) break; await $.wait(200, 400); }
262+
else break;
263+
}
264+
this.log(` [浇水] 完成 ${watered} 次,剩余水滴 ${curr}`);
265+
} catch (e) {
266+
this.log(` [浇水] 跳过: ${e.message || e}`);
267+
}
268+
}
269+
async ensureSession() {
270+
const cached = readCache()[this.account.openid] || {};
271+
if (!this.cookieStr && cached.cookieStr) {
272+
this.cookieStr = cached.cookieStr;
273+
this.pdduid = cached.uid || extractUid(this.cookieStr);
274+
this.log("使用缓存Cookie");
275+
if (await this.getHomePage()) { this.log(`缓存有效,水滴=${this.water}`); return; }
276+
this.log("缓存失效,重新登录");
277+
this.cookieStr = ""; this.pdduid = ""; this.tubetoken = "";
278+
}
279+
if (!this.cookieStr) {
280+
await this.login();
281+
if (!this.pdduid) this.pdduid = extractUid(this.cookieStr);
282+
if (!(await this.getHomePage())) throw new Error("登录后首页加载失败(可能风控/Cookie无效)");
283+
this.log(`当前水滴=${this.water}`);
284+
}
285+
}
286+
async run() {
287+
if (!this.account.openid) { this.log("跳过:变量值里没有 openid"); return; }
288+
try {
289+
await this.ensureSession();
290+
const r = await this.checkin();
291+
await $.wait(500, 1000);
292+
if (!r.blocked) await this.waterTree();
293+
} catch (e) {
294+
const em = String(e.message || e);
295+
if (em.startsWith("BLOCKED")) {
296+
this.log(`⛔ ${em.replace(/^BLOCKED:/, "")}(拼多多重风控,需在小程序内手动过验证,脚本无法绕过)`);
297+
return;
298+
}
299+
this.log(`执行失败: ${em}`);
300+
}
301+
}
302+
}
303+
304+
!(async () => {
305+
$.checkEnv(ckName);
306+
if (!$.userCount) { $.log(`未找到变量 ${ckName}`); return; }
307+
for (let i = 0; i < $.userList.length; i++) {
308+
await new Task($.userList[i]).run();
309+
if (i < $.userList.length - 1) await $.wait(1500, 3000);
310+
}
311+
})().catch((e) => $.log(e.message || e)).finally(() => $.done());

‎wxapp/youzan.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class Task {
106106
Accept: "application/json, text/plain, */*",
107107
"User-Agent": USER_AGENT,
108108
Referer: `https://servicewechat.com/${this.account.appid}/0/page-frame.html`,
109+
'Extra-Data': this.cred ? JSON.stringify(this.cred.extraData) : '',
109110
};
110111
}
111112

@@ -162,6 +163,15 @@ class Task {
162163
accessToken: d.accessToken,
163164
sid: d.sessionId,
164165
kdtId: String(d.kdtId || kdt),
166+
"extraData": {
167+
"is_weapp": 1,
168+
"sid": d.sessionId,
169+
"version": "2.216.4.101",
170+
"client": "weapp",
171+
"bizEnv": "wsc",
172+
"uuid": d.unionId,
173+
"ftime": Date.now()
174+
}
165175
};
166176
const cache = readCache();
167177
cache[`${this.account.openid}#${this.account.appid}`] = { ...this.cred, updatedAt: new Date().toISOString() };
@@ -195,7 +205,7 @@ class Task {
195205
const key = `${this.account.openid}#${this.account.appid}`;
196206
const cached = readCache()[key];
197207
if (!this.cred && cached && cached.accessToken) {
198-
this.cred = { accessToken: cached.accessToken, sid: cached.sid, kdtId: cached.kdtId };
208+
this.cred = { accessToken: cached.accessToken, sid: cached.sid, kdtId: cached.kdtId, extraData: cached.extraData };
199209
if (await this.checkSession()) {
200210
this.log("使用缓存ck");
201211
return;

0 commit comments

Comments
 (0)