介绍

支付宝2021集福卡活动与其余50多个频道进行合作,可以在每个不同频道获取额外1张福卡,但是这个额外领取福卡的页面指向的是一个网页,只是URL参数有所不同。在不影响相关活动的两年后,以学习态度分享一下当时的分析情况。

分析页面

活动URL为 https://render.alipay.com/p/c/17yq18lq3slc

参加活动的流程为:输入手机号 -> 获取验证码 -> 输入验证码 -> 领取福卡

每个频道的URL会带上频道的ID用于识别,例如天猫精灵为:https://render.alipay.com/p/c/17yq18lq3slc?source=JING_LING

频道的所有信息在主页面中存放,例如天猫精灵:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"channelList": [
{
"startTime": 1611763200000,
"endTime": 1613052000000,
"giftImage": {
"img": "https://gw.alipayobjects.com/os/c/cms/img/6273f0c6-dbd6-45f7-beeb-5c199f93f390_w600_h482.png",
"width": 648,
"height": 477
},
"channelLogo": {
"img": "https://gw.alipayobjects.com/os/c/cms/img/f63cace2-8f84-4807-bd3c-363f95f7a9fd_w278_h35.png",
"width": 278,
"height": 36
},
"channelBanner": {
"link": "",
"img": "",
"width": 690,
"height": 221
},
"title": "天猫精灵助你更快集五福",
"endTitle": "天猫精灵助你更快集五福",
"sourceList": [
{
"source": "JING_LING",
"name": "天猫精灵"
}
],
"client": "JING_LING",
"music": ""
},
]
}

里面存放着活动开始结束时间、页面中礼物的图案、合作频道的LOGO等信息。

获取验证码分析

输入手机号后,点击获取验证码

此时有两个请求是关键,一个是 POST https://rds.alipay.com/captcha.htm, 一个是 GET https://mobilegw.alipay.com/mgw.htm。

首先看第一个请求的POST参数:

1
2
3
4
5
6
7
8
9
10
{
"type": "silence",
"bizNo": "0e41806e19cb4895814f06c0faa4402705cd13ad164448a3ace21620021ebc7a",
"mobile": "13888888888",
"useragent": "Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 10 Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.23",
"refer": "",
"data": "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45",
"appid": "blessingprod_wufu_otp",
"scene": "DO_NOTHING"
}

通过参数bizNo在js中找到相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Zm = function(e, t) {
return Dm(void 0, void 0, void 0, function() {
return Im(this, function(n) {
return [2, new Promise(function(n) {
var r = window.antcap.initValidator({
params: {
type: "silence",
appId: Fm,
bizNo: e,
mobile: t,
useragent: navigator.userAgent,
refer: document.referrer
},
success: function(e) {
n(e)
},
fail: function(e) {
n(e)
}
});
r.validate()
}
)]
})
})
}

其中e和t是外传参数,t为手机号,继续查找Zm函数的调用:

1
2
3
4
5
6
7
8
9
case 0:
return !0 === id ? [2] : (id = !0,
setTimeout(function() {
id = !1
}, 1e3),
0 !== b ? [2] : "" === l ? (Em.fail("\u8bf7\u8f93\u5165\u624b\u673a\u53f7"),
[2]) : /1\d{10}/.test("" + l) ? (e = ("" + V() + V()).replace(/-/g, ""),
[4, Zm(e, l)]) : (Em.fail("\u8bf7\u8f93\u5165\u6b63\u786e\u7684\u624b\u673a\u53f7"),
[2]));

可以看到Zm中的e = ("" + V() + V()).replace(/-/g, "")
那么 V() 作用是什么呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function L(e, t, n) {
e = e || {};
var r = e.random || (e.rng || A)();
if (r[6] = 15 & r[6] | 64,
r[8] = 63 & r[8] | 128,
t) {
n = n || 0;
for (var a = 0; a < 16; ++a)
t[n + a] = r[a];
return t
}
return R(r)
}
var V = L

其中的A呢

1
2
3
4
5
6
7
P = new Uint8Array(16);
function A() {
if (!d && (d = "undefined" !== typeof crypto && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || "undefined" !== typeof msCrypto && "function" === typeof msCrypto.getRandomValues && msCrypto.getRandomValues.bind(msCrypto),
!d))
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
return d(P)
}

其中提到了一个开源库,一看是在JS中生成UUID的库,再结合bizNo是64位字符的形式,一个V()则是32个字符(除了-),再结合e = ("" + V() + V()).replace(/-/g, ""),把V()的”-“都去除掉

所以可以大胆猜测V()就是这个库的uuidv4()

1
2
import { v4 as uuidv4 } from 'uuid';
uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

1
e = ("" + uuidv4() + uuidv4()).replace(/-/g, "") // ⇨ '0e41806e19cb4895814f06c0faa4402705cd13ad164448a3ace21620021ebc7a'

可以看一下实际情况,其中的A返回了d(P),经过断点可以发现d为 getRandomValues()

在控制台输出 crypto.getRandomValues(new Uint8Array(16));

[85, 86, 174, 111, 221, 244, 132, 185, 204, 36, 173, 125, 124, 231, 26, 172]

然后返回到L函数中,最终这个数组传入到R()函数中,经过断点,R()为:

1
2
3
4
5
6
7
function I(e) {
var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0
, n = (M[e[t + 0]] + M[e[t + 1]] + M[e[t + 2]] + M[e[t + 3]] + "-" + M[e[t + 4]] + M[e[t + 5]] + "-" + M[e[t + 6]] + M[e[t + 7]] + "-" + M[e[t + 8]] + M[e[t + 9]] + "-" + M[e[t + 10]] + M[e[t + 11]] + M[e[t + 12]] + M[e[t + 13]] + M[e[t + 14]] + M[e[t + 15]]).toLowerCase();
if (!z(n))
throw TypeError("Stringified UUID is invalid");
return n
}

其中M是什么,再看一下:

1
2
for (var z = j, M = [], D = 0; D < 256; ++D)
M.push((D + 256).toString(16).substr(1));

这个逻辑就很清晰了,把数字转为16进制并转为字符串,为了补齐到两位,先加256(16进制为100),然后砍掉第一位。最终D为00到ff的256个16进制字符串数组。

最终在I函数中,将传进来的16个数的数组进行转换并添加”-“进行拼接,最终形成一个UUID。这样也验证了我们之前猜测的正确性,这个的确是生成UUID的函数。

也就是 bizNo 为两个UUID去除”-“的结果。

接着我们看data参数,这个参数很长,可能是一段数据经过一些加密算法最终得到的结果。

"data": "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45"

前面的Zm函数中只有:

type: "silence",
appId: Fm,
bizNo: e,
mobile: t,
useragent: navigator.userAgent,
refer: document.referrer

这几个参数,而最终的post请求则多了

"data": "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45",
"scene": "DO_NOTHING"

两个参数,我们尝试搜索"DO_NOTHING",然后在另外一个js文件中找到了这个原始发出的请求代码:

1
2
3
4
5
6
u({
method: "POST",
contentType: "application/json",
url: r && decodeURIComponent(r) || "https://rds.alipay.com/captcha.htm",
data: JSON.stringify(n)
}

然后去找n:

1
2
3
4
5
6
try {
t = antcap.fnGetRdsData()
} catch (c) {}
var n = u({}, d.cfg.params, {
data: t
}, c.params);

最终t = antcap.fnGetRdsData(),在网上进行查找后,发现 antcap.fnGetRdsData()支付宝验证码的组件,而本次获取手机验证码时,没有进行验证的操作

1
2
3
4
5
6
n.scene = {
click: "CLICK_BUTTON",
slide: "STRIKE_BUTTON",
drag: "SHOOT",
silence: "DO_NOTHING"
}[c.type];

"DO_NOTHING"属于silence,可见本次无需验证,所以就尝试去除data参数,是否可以重放此api,结果是成功的,如果不成功,也可以按照上文的思路,把这个变成黑盒,作为函数直接进行调用即可。

继续回到我们的这个HTTP请求
这个请求收到的响应则为:

1
2
3
4
5
6
7
8
9
10
{
"data": {
"result": "pass",
"extra": {
"token": "2b4c9e20c673410fb7583e9019073b8cbaee909f93fa4e7a87d6b05083d5cc75"
}
},
"message": "Analyze success.",
"success": true
}

从这个发送的请求与相应的内容来看,这个接口负责检查用户目前UA环境和验证情况是否正常,正常则返回一个token供其他接口使用。

接着看第二个接口发送的参数:

Name Value
_fli_online true
operationType alipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode
requestData [{“mobile”:”13888888888”,”source”:”JING_LING”,”rdsBizNo”:”972450e015d0446db73a43b75d6d50c179fac497577345a7899035bb086d000b”,”rdsToken”:”6e2ab8060f5c4881b07566cfa6da7c6aa1ccb55465b640f2b5a64c983faae214”}]
_ 1612182118381
callback jsonp1612181787797

值得注意的是,上个接口获取的Token在这里作为rdsToken进行了请求,callback请求jsonp的方式为了解决跨域问题。

接着收到的响应:

jsonp1612181787797({
    "resultStatus": 1000,
    "result": {
        "code": "5101",
        "resultView": "人气太旺了,请稍后再试",
        "success": true
    }
})

其中json的"result"->"success"代表着获取验证码是否成功,如果失败的话,resultView会给出失败原因。

领取福卡分析

接着我们将收到的验证码输入,并点击“立即领取”,会有一个请求格外显眼:GET https://mobilegw.alipay.com/mgw.htm

这和我们获取验证码结果的请求URL是一样的。

发出的参数为:

Name Value
_fli_online true
operationType alipay.tradecsa.biz.blessingprod.wufu2021.outPrize
requestData [{“mobile”:”13888888888”,”source”:”JING_LING”,”ackCode”:”123445”}]
_ 1612356116283
callback jsonp1612356024360

可以发现 operationTypealipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode 变成了 alipay.tradecsa.biz.blessingprod.wufu2021.outPrize

其中 requestData 参数有手机号、频道ID、验证码。

接受的响应为:

jsonp1612356828462({
    "resultStatus": 1000,
    "result": {
        "code": "5101",
        "hasPrized": false,
        "hasUserId": false,
        "prizeList": [
            {
                "collected": false,
                "collectionNum": 0,
                "needCollectionNum": 0,
                "prizeDescImageUrl": "https://gw.alipayobjects.com/mdn/rms_44d4e1/afts/img/A*8Z2ERZpmZ0cAAAAAAAAAAAAAARQnAQ",
                "prizeDownDescText": "万能福卡"
            },
            {
                "collected": false,
                "collectionNum": 0,
                "needCollectionNum": 0,
                "prizeDescImageUrl": "https://gw.alipayobjects.com/mdn/rms_44d4e1/afts/img/A*WNaQQ5tNJpQAAAAAAAAAAAAAARQnAQ",
                "prizeDownDescText": "10元红包"
            }
        ],
        "resultView": "人气太旺了,请稍后再试",
        "success": true
    }
})

其中hasPrized应该是是否抽中现金红包的提示,如果hasUserIdtrue,则会有logonId参数为用户邮箱,success是是否成功领取福的关键。

利用分析结果编写批量领取脚本

使用Python编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import requests
import os
import codecs
import sys
import time
import json
import re

class WebRequests:

def __init__(self):
self.dirPath = ''
self.getCaptchaUrl = 'https://rds.alipay.com/captcha.htm'

self.getResultUrl = 'https://mobilegw.alipay.com/mgw.htm'

self.operationType = {
'sendVerifyCode': 'alipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode',
'outPrize': 'alipay.tradecsa.biz.blessingprod.wufu2021.outPrize'
}

self.s = requests.Session()

self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 9 Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.6',
'DNT': '1'
}

def loads_jsonp(self, _jsonp):
try:
return json.loads(re.match(".*?({.*}).*", _jsonp, re.S).group(1))
except:
raise ValueError('Invalid Input')

def getCaptcha(self, mobile, source):
digits = 32
hex = codecs.encode(os.urandom(digits), 'hex').decode()
data = {
'appid': "blessingprod_wufu_otp",
'bizNo': hex,
'mobile': mobile,
'refer': "",
'scene': "DO_NOTHING",
'type': "silence",
'useragent': "Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 8 UD Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.6"
}
self.s.options(self.getCaptchaUrl)
try:
r = self.s.post(self.getCaptchaUrl, json=data,
headers=self.headers)

rdsToken = json.loads(r.content)['data']['extra']['token']
requestData = [{"mobile": mobile, "source": source,
"rdsBizNo": hex, "rdsToken": rdsToken}]
getResultData = {
'_fli_online': True,
'operationType': self.operationType['sendVerifyCode'],
'requestData': str(requestData),
'_': int(round(time.time() * 1000)),
'callback': 'jsonp' + str(int(round(time.time() * 1000)))
}

re = self.s.get(self.getResultUrl,
params=getResultData, headers=self.headers)
re_json = self.loads_jsonp(re.text)
if re_json['result']['success'] == True:
return {"code": 1000, "info": f'成功获取验证码,请注意查收'}
else:
resultView = re_json['result']['resultView']
return {"code": 1001, "info": f'获取验证码失败,原因为{resultView}'}
except Exception as e:
return {"code": 1001, "info": f'获取验证码失败,原因为 {e}'}

def getResult(self, mobile, source, ackCode):
requestData = [
{"mobile": mobile, "source": source, "ackCode": str(ackCode)}]
getResultData = {
'_fli_online': True,
'operationType': self.operationType['outPrize'],
'requestData': str(requestData),
'_': int(round(time.time() * 1000)),
'callback': 'jsonp' + str(int(round(time.time() * 1000)))
}

try:

re = self.s.get(self.getResultUrl,
params=getResultData, headers=self.headers)
re_json = self.loads_jsonp(re.text)
if re_json['result']['success'] == True:
return {"code": 1000, "info": f'成功领取'}
else:
resultView = re_json['result']['resultView']
return {"code": 1001, "info": f'领取失败,原因为 {resultView}'}
except Exception as e:
return {"code": 1001, "info": f'领取失败,原因为 {e}'}

def getSiteNum(self):
path = os.path.join(self.dirPath, "site.json")
with open(path, 'r', encoding='utf8')as fp:
json_data = json.load(fp)
return len(json_data['channelList'])

def getSiteInfo(self, num):
path = os.path.join(self.dirPath, "site.json")
with open(path, 'r', encoding='utf8')as fp:
json_data = json.load(fp)
length = len(json_data['channelList'])
if num > length:
print(f"站点的长度为{length}{num}已经超出这个长度")
return None
return json_data['channelList'][num-1]

def getAllSiteInfo(self):
path = os.path.join(self.dirPath, "site.json")
with open(path, 'r', encoding='utf8')as fp:
json_data = json.load(fp)
return json_data['channelList']

def getSiteName(self, siteInfo):
return siteInfo['sourceList'][0]['name']

def getSiteSource(self, siteInfo):
return siteInfo['sourceList'][0]['source']

def addSuccessSite(self, siteInfo):
path = os.path.join(self.dirPath, "success.json")
add = self.isSuccessSite(siteInfo)
if add == False:
with open(path, 'r+', encoding='utf8')as fp:
json_data = json.load(fp)
with open(path, 'w', encoding='utf8')as fp:
json_data['channelList'].append(siteInfo)
fp.write(json.dumps(json_data, indent=4))

def isSuccessSite(self, siteInfo):
path = os.path.join(self.dirPath, "success.json")
with open(path, 'r+', encoding='utf8')as fp:
json_data = json.load(fp)
if siteInfo in list(json_data['channelList']):
return True
else:
return False

def main(path):
webRequests = WebRequests()
webRequests.dirPath = path
print(f"总共有{webRequests.getSiteNum()}个站点可以领取福卡")
for i in range(1, webRequests.getSiteNum()+1):
siteInfo = webRequests.getSiteInfo(i)
siteName = webRequests.getSiteName(siteInfo)
print(f"{i}{siteName}")

startSite = int(input("您要从第几个站点开始向后领取?"))
mobile = input("请输入您的手机号:")

for i in range(startSite, webRequests.getSiteNum()+1):
siteInfo = webRequests.getSiteInfo(i)
siteName = webRequests.getSiteName(siteInfo)
siteSource = webRequests.getSiteSource(siteInfo)
if webRequests.isSuccessSite(siteInfo):
print(f"{i}{siteName} 已经成功领取,跳过")
continue
print(f"{i}{siteName} 正在领取中")
result = webRequests.getCaptcha(mobile, siteSource)
print(result['info'])
if result['code'] == 1001:
if str(result['info']).find("验证码发送过频繁") != -1:
print("验证码需等待60s后才能获取,正在等待..")
time.sleep(60)
result = webRequests.getCaptcha(mobile, siteSource)
if str(result['info']).find("人气太旺啦,稍候再试试") != -1:
print("您的手机号在近期已经获得了多次支付宝验证码,已被支付宝限制,24小时内无法再获得验证码,程序终止。")
break
elif str(result['info']).find("人气太旺啦,稍候再试试") == -1 and str(result['info']).find("验证码发送过频繁") == -1:
continue
ackCode = input("请输入验证码:")
result = webRequests.getResult(mobile, siteSource, ackCode)
print(result['info'])

if result['code'] == 1000 or result['info'].find("已经领取过奖品") != -1:
webRequests.addSuccessSite(siteInfo)

print("验证码需等待60s后才能获取,正在等待..")
time.sleep(60)

input("程序已结束,您可以关闭此程序了")

if __name__ == '__main__':
path = os.path.dirname(os.path.realpath(sys.argv[0]))
main(path)