├── LICENSE
├── README.md
├── api
├── api.go
└── api_test.go
├── conf
└── conf.yaml
├── config
└── config.go
├── doc
├── 丽芝士_1179553.json
└── 显卡_10023774354580.json
├── go.mod
├── main.go
├── models
├── cart.go
├── messenger.go
├── models_test.go
├── qrcode.go
├── stocks.go
├── submitOrder.go
└── userinfo.go
└── utils
├── utils.go
└── utils_test.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Tsung
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JDPurchaser
--------------------------------------------------------------------------------
/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "JD_Purchase/config"
5 | "JD_Purchase/models"
6 | "JD_Purchase/utils"
7 | "bytes"
8 | "errors"
9 | "fmt"
10 | "github.com/PuerkitoBio/goquery"
11 | jsoniter "github.com/json-iterator/go"
12 | "golang.org/x/net/publicsuffix"
13 | "golang.org/x/xerrors"
14 | "io/ioutil"
15 | "log"
16 | "math/rand"
17 | "net/http"
18 | "net/http/cookiejar"
19 | "net/url"
20 | "os"
21 | "path"
22 | "regexp"
23 | "strconv"
24 | "strings"
25 | "sync"
26 | "time"
27 | )
28 |
29 | const (
30 | DEFAULT_TIMEOUT = 10
31 | DEFAULT_USER_AGENT = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36`
32 | useRandomUa = ""
33 | QRCodeFile = "./QRCode.png"
34 | retryTimes = 85
35 | )
36 |
37 | func init() {
38 | log.SetFlags(log.LstdFlags | log.Lshortfile)
39 | rand.Seed(time.Now().UnixNano())
40 | }
41 |
42 | type Api struct {
43 | Messenger *models.Messenger
44 | Client *http.Client
45 | UserAgent string
46 | Headers http.Header
47 | EID string
48 | Fp string
49 | TrackID string
50 | RiskControl string
51 | Timeout time.Duration
52 | SendMessage bool
53 | ItemCat map[string]string
54 | ItemVenderIDs map[string]string
55 | SeckillInitInfo map[string]string
56 | SeckillOrderData map[string]string
57 | SeckillUrl map[string]string
58 | Username string
59 | Nickname string
60 | IsLogin bool
61 | }
62 |
63 | func NewApi(client *http.Client) (*Api, error) {
64 | var err error
65 | api := new(Api)
66 | api.Client = client
67 | if config.Get().IsRandomUserAgent {
68 | api.UserAgent = utils.GetRandomUserAgent()
69 | } else {
70 | api.UserAgent = DEFAULT_USER_AGENT
71 | }
72 | header := map[string][]string{
73 | "User-Agent": {api.UserAgent},
74 | }
75 | api.Headers = header
76 | api.EID = config.Get().EID
77 | api.Fp = config.Get().FP
78 | api.TrackID = config.Get().TrackID
79 | api.RiskControl = config.Get().RiskControl
80 | if api.EID == "" || api.Fp == "" || api.TrackID == "" || api.RiskControl == "" {
81 | return nil, xerrors.Errorf("请在 config.ini 中配置 eid, fp, track_id, risk_control 参数")
82 | }
83 | if config.Get().Timeout == 0 {
84 | api.Timeout = time.Duration(DEFAULT_TIMEOUT)
85 | } else {
86 | api.Timeout = time.Duration(config.Get().Timeout)
87 | }
88 | api.SendMessage = config.Get().EnableSendMessage
89 | if api.SendMessage {
90 | api.Messenger = models.NewMessenger(config.Get().Messenger.Sckey)
91 | }
92 | api.ItemCat = make(map[string]string)
93 | api.ItemVenderIDs = make(map[string]string)
94 | api.SeckillInitInfo = make(map[string]string)
95 | api.SeckillOrderData = make(map[string]string)
96 | api.SeckillUrl = make(map[string]string)
97 | api.Username = ""
98 | api.Nickname = "JD_Purchase"
99 | api.IsLogin = false
100 | api.Client.Jar, err = cookiejar.New(&cookiejar.Options{
101 | PublicSuffixList: publicsuffix.List,
102 | })
103 | if err != nil {
104 | return nil, xerrors.Errorf("%w", err)
105 | }
106 | err = api.loadCookies()
107 | if err != nil {
108 | if xerrors.Is(err, os.ErrNotExist) {
109 | log.Printf("未找到Cookies,将重新登陆!")
110 | } else {
111 | return nil, xerrors.Errorf("%w", err)
112 | }
113 | }
114 | return api, nil
115 | }
116 |
117 | func (a *Api) loadCookies() error {
118 | var cookies []*http.Cookie
119 | _, err := os.Stat("./cookies")
120 | if err != nil {
121 | if os.IsNotExist(err) {
122 | err = os.Mkdir("./cookies", os.ModePerm)
123 | if err != nil {
124 | return xerrors.Errorf("%w", err)
125 | }
126 | } else {
127 | return xerrors.Errorf("%w", err)
128 | }
129 | }
130 | cookiesFile := path.Join("./cookies", fmt.Sprintf("%s.json", a.Nickname))
131 | cookiesByte, err := ioutil.ReadFile(cookiesFile)
132 | if err != nil {
133 | return xerrors.Errorf("%w", err)
134 | }
135 | u, err := url.Parse("https://www.jd.com")
136 | err = jsoniter.Unmarshal(cookiesByte, &cookies)
137 | if err != nil {
138 | return xerrors.Errorf("%w", err)
139 | }
140 | a.Client.Jar.SetCookies(u, cookies)
141 | a.IsLogin, err = a.validateCookies()
142 | if err != nil {
143 | return xerrors.Errorf("%w", err)
144 | }
145 | return nil
146 | }
147 |
148 | func (a *Api) saveCookies(cookies []*http.Cookie) error {
149 | _, err := os.Stat("./cookies")
150 | if err != nil {
151 | if os.IsNotExist(err) {
152 | err = os.Mkdir("./cookies", os.ModePerm)
153 | if err != nil {
154 | return xerrors.Errorf("%w", err)
155 | }
156 | } else {
157 | return xerrors.Errorf("%w", err)
158 | }
159 | }
160 | cookiesFile := path.Join("./cookies", fmt.Sprintf("%s.json", a.Nickname))
161 | f, err := os.Create(cookiesFile)
162 | if err != nil {
163 | return xerrors.Errorf("%w", err)
164 | }
165 | defer f.Close()
166 | for _, cookie := range cookies {
167 | cookie.Domain = ".jd.com"
168 | cookie.Path = "/"
169 | }
170 | cookiesByte, err := jsoniter.Marshal(cookies)
171 | if err != nil {
172 | return xerrors.Errorf("%w", err)
173 | }
174 | _, err = f.Write(cookiesByte)
175 | if err != nil {
176 | return xerrors.Errorf("%w", err)
177 | }
178 | return nil
179 | }
180 |
181 | func (a *Api) validateCookies() (bool, error) {
182 | u := "https://order.jd.com/center/list.action"
183 | req, err := http.NewRequest(http.MethodGet, u, nil)
184 | if err != nil {
185 | return false, xerrors.Errorf("%w", err)
186 | }
187 | req.Header = a.Headers
188 |
189 | clientPool := sync.Pool{New: func() interface{} {
190 | return *a.Client
191 | }}
192 | newClient := clientPool.Get()
193 | defer clientPool.Put(newClient)
194 | v, ok := newClient.(http.Client)
195 | if !ok {
196 | return false, xerrors.Errorf("%w", "not http client!")
197 | }
198 | v.CheckRedirect = func(req *http.Request, via []*http.Request) error {
199 | return http.ErrUseLastResponse
200 | }
201 | resp, err := v.Do(req)
202 | if err != nil {
203 | return false, xerrors.Errorf("%w", err)
204 | }
205 | defer resp.Body.Close()
206 | if resp.StatusCode != http.StatusOK {
207 | return false, nil
208 | }
209 | return true, nil
210 | }
211 |
212 | func (a *Api) LoginByQRCode() (bool, error) {
213 | var ticket string
214 | if a.IsLogin {
215 | log.Println("登陆成功")
216 | return true, nil
217 | }
218 | a.GetLoginPage()
219 | ok, err := a.GetQRCode()
220 | if err != nil {
221 | return false, err
222 | }
223 | if !ok {
224 | return false, errors.New("二维码下载失败")
225 | }
226 | for i := 0; i < retryTimes; i++ {
227 | ticket, err = a.GetQRCodeTicket()
228 | if err != nil {
229 | return false, xerrors.Errorf("%w", err)
230 | }
231 | if ticket != "" {
232 | break
233 | }
234 | time.Sleep(time.Second * 2)
235 | if i == 85 {
236 | return false, xerrors.Errorf("二维码过期,请重新获取扫描")
237 | }
238 | }
239 | ok, err = a.ValidateQRCodeTicket(ticket)
240 | if err != nil {
241 | return false, xerrors.Errorf("%w", err)
242 | }
243 | if !ok {
244 | return false, xerrors.Errorf("二维码信息校验失败")
245 | }
246 | log.Println("二维码登陆成功")
247 | a.IsLogin = true
248 | a.Nickname, err = a.GetUserInfo(a.saveCookies)
249 | if err != nil {
250 | return false, xerrors.Errorf("%w", err)
251 | }
252 | return true, nil
253 | }
254 |
255 | func (a *Api) GetLoginPage() {
256 | u := "https://passport.jd.com/new/login.aspx"
257 | req, err := http.NewRequest(http.MethodGet, u, nil)
258 | if err != nil {
259 | return
260 | }
261 | req.Header = a.Headers
262 | resp, err := a.Client.Do(req)
263 | if err != nil {
264 | return
265 | }
266 | defer resp.Body.Close()
267 | log.Println(resp)
268 | }
269 |
270 | func (a *Api) GetQRCode() (bool, error) {
271 | u := "https://qr.m.jd.com/show?"
272 | args := url.Values{}
273 | args.Add("appid", "133")
274 | args.Add("size", "147")
275 | args.Add("t", strconv.FormatInt(time.Now().Unix()*1e3, 10))
276 | u = u + args.Encode()
277 | req, err := http.NewRequest(http.MethodGet, u, nil)
278 | if err != nil {
279 | return false, xerrors.Errorf("%w", err)
280 | }
281 | req.Header.Set("User-Agent", a.UserAgent)
282 | req.Header.Set("Referer", "https://passport.jd.com/new/login.aspx")
283 | resp, err := a.Client.Do(req)
284 | if err != nil {
285 | log.Println("获取二维码失败")
286 | return false, xerrors.Errorf("%w", err)
287 | }
288 | defer resp.Body.Close()
289 | err = utils.SaveImage(resp, QRCodeFile)
290 | if err != nil {
291 | return false, xerrors.Errorf("%w", err)
292 | }
293 | log.Println("获取二维码成功,请打开京东APP扫描")
294 | return true, nil
295 | }
296 |
297 | func (a *Api) GetQRCodeTicket() (string, error) {
298 | var token string
299 | u := "https://qr.m.jd.com/check?"
300 | args := url.Values{}
301 | args.Add("appid", "133")
302 | args.Add("callback", fmt.Sprintf("jQuery%v", rand.Intn(9999999-1000000)+1000000))
303 | cookieUrl, err := url.Parse("https://qr.m.jd.com")
304 | if err != nil {
305 | return "", xerrors.Errorf("%w", err)
306 | }
307 | cookies := a.Client.Jar.Cookies(cookieUrl)
308 | for _, v := range cookies {
309 | if v.Name == "wlfstk_smdl" {
310 | token = v.Value
311 | break
312 | }
313 | }
314 | if token == "" {
315 | return "", xerrors.Errorf("获取token失败")
316 | }
317 | args.Add("token", token)
318 | args.Add("_", strconv.FormatInt(time.Now().Unix()*1e3, 10))
319 | u = u + args.Encode()
320 | req, err := http.NewRequest(http.MethodGet, u, nil)
321 | if err != nil {
322 | return "", xerrors.Errorf("%w", err)
323 | }
324 | req.Header.Set("User-Agent", a.UserAgent)
325 | req.Header.Set("Referer", "https://passport.jd.com/new/login.aspx")
326 | resp, err := a.Client.Do(req)
327 | if err != nil {
328 | log.Println("获取二维码扫描结果异常")
329 | return "", xerrors.Errorf("%w", err)
330 | }
331 | defer resp.Body.Close()
332 | data, err := ioutil.ReadAll(resp.Body)
333 | if err != nil {
334 | return "", xerrors.Errorf("%w", err)
335 | }
336 | ret := new(models.QRCodeTicketBody)
337 | err = jsoniter.Unmarshal(data[14:len(data)-1], ret)
338 | if err != nil {
339 | return "", xerrors.Errorf("%w", err)
340 | }
341 | if ret.Code != 200 {
342 | log.Printf("Code: %v,message: %s", ret.Code, ret.Msg)
343 | return "", nil
344 | }
345 | log.Println("已完成手机客户端确认!")
346 | return ret.Ticket, nil
347 | }
348 |
349 | func (a *Api) ValidateQRCodeTicket(ticket string) (bool, error) {
350 | u := "https://passport.jd.com/uc/qrCodeTicketValidation?"
351 | args := url.Values{}
352 | args.Add("t", ticket)
353 | u = u + args.Encode()
354 | req, err := http.NewRequest(http.MethodGet, u, nil)
355 | if err != nil {
356 | return false, xerrors.Errorf("%w", err)
357 | }
358 | req.Header.Set("User-Agent", a.UserAgent)
359 | req.Header.Set("Referer", "https://passport.jd.com/uc/login?ltype=logout")
360 | resp, err := a.Client.Do(req)
361 | if err != nil {
362 | return false, xerrors.Errorf("%w", err)
363 | }
364 | defer resp.Body.Close()
365 | data, err := ioutil.ReadAll(resp.Body)
366 | if err != nil {
367 | return false, xerrors.Errorf("%w", err)
368 | }
369 | ret := new(models.ValidateQRCodeBody)
370 | err = jsoniter.Unmarshal(data, ret)
371 | if err != nil {
372 | return false, err
373 | }
374 | if ret.ReturnCode != 0 {
375 | return false, xerrors.Errorf("%w", ret.ReturnCode)
376 | }
377 | return true, nil
378 | }
379 |
380 | func (a *Api) GetUserInfo(cookiesHandle func([]*http.Cookie) error) (string, error) {
381 | u := "https://passport.jd.com/user/petName/getUserInfoForMiniJd.action?"
382 | args := url.Values{}
383 | args.Add("callback", fmt.Sprintf("jQuery%v", rand.Intn(9999999-1000000)+1000000))
384 | args.Add("_", fmt.Sprintf("%v", time.Now().Unix()*1e3))
385 | u = u + args.Encode()
386 | req, err := http.NewRequest(http.MethodGet, u, nil)
387 | if err != nil {
388 | return "", xerrors.Errorf("%w", err)
389 | }
390 | req.Header.Set("User-Agent", a.UserAgent)
391 | req.Header.Set("Referer", "https://order.jd.com/center/list.action")
392 | resp, err := a.Client.Do(req)
393 | if err != nil {
394 | return "", xerrors.Errorf("%w", err)
395 | }
396 | defer resp.Body.Close()
397 | data, err := ioutil.ReadAll(resp.Body)
398 | if err != nil {
399 | return "", xerrors.Errorf("%w", err)
400 | }
401 | ret := new(models.UserInfo)
402 | err = jsoniter.Unmarshal(data[14:len(data)-1], ret)
403 | if err != nil {
404 | return "", xerrors.Errorf("%w", err)
405 | }
406 | if ret.NickName == "" {
407 | return "jd", nil
408 | }
409 | err = cookiesHandle(req.Cookies())
410 | if err != nil {
411 | return "", xerrors.Errorf("%w", err)
412 | }
413 | return ret.NickName, nil
414 | }
415 |
416 | func (a *Api) BuyItemInStock(skuIDs string, area string, waitAll bool, stockInterval int, submitRetry int, submitInterval int) error {
417 | itemList := make([]string, 4)
418 | itemsMap := utils.ParseSkuID(skuIDs)
419 | areaID := utils.ParseAreaID(area)
420 | for k := range itemsMap {
421 | itemList = append(itemList, k)
422 | }
423 | if !waitAll {
424 | log.Printf("下单模式:%v 任一商品有货并且未下架均会尝试下单", itemList)
425 | for {
426 | for k, v := range itemsMap {
427 | ok, err := a.GetSigleItemStock(k, v, areaID)
428 | if err != nil {
429 | return xerrors.Errorf("%w", err)
430 | }
431 | if !ok {
432 | log.Printf("%s 不满足下单条件,%vs后进行下一次查询", k, stockInterval)
433 | continue
434 | }
435 | log.Printf("%s 满足下单条件,开始执行", k)
436 | _, _ = a.CancelSelectAllCartItem()
437 | a.AddOrChangeCartItem(map[string]string{}, k, v)
438 | ok, err = a.SubmitOrderWithRetry(submitRetry, submitInterval)
439 | if err != nil {
440 | return xerrors.Errorf("%w", err)
441 | }
442 | if ok {
443 | return nil
444 | }
445 | time.Sleep(time.Duration(stockInterval))
446 | }
447 | }
448 | } else {
449 | log.Printf("下单模式:%s 所有都商品同时有货并且未下架才会尝试下单", itemList)
450 | for {
451 | //todo
452 | }
453 | }
454 | }
455 |
456 | func (a *Api) GetMultiItemStockNew(itemMap map[string]string, area string) (bool, error) {
457 | u := "https://c0.3.cn/stocks?"
458 | keys := make([]string, 0)
459 | for k := range itemMap {
460 | keys = append(keys, k)
461 | }
462 | skuIds := strings.Join(keys, ",")
463 | args := url.Values{}
464 | args.Add("callback", fmt.Sprintf("jQuery%d", rand.Intn(9999999-1000000)+1000000))
465 | args.Add("type", "getstocks")
466 | args.Add("skuIds", skuIds)
467 | args.Add("area", area)
468 | args.Add("_", fmt.Sprintf("%d", time.Now().Unix()*1e3))
469 | req, err := http.NewRequest(http.MethodGet, u+args.Encode(), nil)
470 | if err != nil {
471 | return false, xerrors.Errorf("%w", err)
472 | }
473 | req.Header.Set("User-Agent", a.UserAgent)
474 | resp, err := a.Client.Do(req)
475 | if err != nil {
476 | return false, xerrors.Errorf("%w", err)
477 | }
478 | defer resp.Body.Close()
479 | stock := true
480 | data, err := ioutil.ReadAll(resp.Body)
481 | if err != nil {
482 | return false, xerrors.Errorf("%w", err)
483 | }
484 | for _, key := range keys {
485 | skuState := jsoniter.Get(data[14:len(data)-1], key).Get("skuState").ToInt64()
486 | stockState := jsoniter.Get(data[14:len(data)-1], key).Get("StockState").ToInt()
487 | if skuState == 1 && (stockState >= 33 && stockState < 40) {
488 | continue
489 | } else {
490 | stock = false
491 | break
492 | }
493 | }
494 | return stock, nil
495 | }
496 |
497 | func (a *Api) GetSigleItemStock(skuID string, num string, area string) (bool, error) {
498 | var cat string
499 | var venderID string
500 | for k, v := range a.ItemCat {
501 | if k == skuID {
502 | cat = v
503 | break
504 | }
505 | }
506 | for k, v := range a.ItemVenderIDs {
507 | if k == skuID {
508 | venderID = v
509 | break
510 | }
511 | }
512 | if cat == "" {
513 | page, err := a.GetItemDetailPage(skuID)
514 | if err != nil {
515 | return false, xerrors.Errorf("%w", err)
516 | }
517 | reg := regexp.MustCompile(`cat: \[(.*?)\]`)
518 | cats := reg.FindStringSubmatch(page)
519 | cat = cats[1]
520 | a.ItemCat[skuID] = cat
521 | reg = regexp.MustCompile(`venderId:(\d*?),`)
522 | venderIDs := reg.FindStringSubmatch(page)
523 | venderID = venderIDs[1]
524 | a.ItemVenderIDs[skuID] = venderID
525 | }
526 | u := "https://c0.3.cn/stock?"
527 | args := url.Values{}
528 | args.Add("callback", fmt.Sprintf("jQuery%v", rand.Intn(9999999-1000000)+1000000))
529 | args.Add("buyNum", num)
530 | args.Add("skuId", skuID)
531 | args.Add("area", area)
532 | args.Add("_", fmt.Sprintf("%v", time.Now().Unix()*1e3))
533 | args.Add("ch", "1")
534 | args.Add("extraParam", "{\"originid\":\"1\"}")
535 | args.Add("cat", cat)
536 | args.Add("venderId", venderID)
537 | u = u + args.Encode()
538 | req, err := http.NewRequest(http.MethodGet, u, nil)
539 | if err != nil {
540 | return false, xerrors.Errorf("%w", err)
541 | }
542 | req.Header.Set("User-Agent", a.UserAgent)
543 | req.Header.Set("Referer", fmt.Sprintf("https://item.jd.com/%s.html", skuID))
544 | a.Client.Timeout = time.Second * a.Timeout
545 | resp, err := a.Client.Do(req)
546 | if err != nil {
547 | return false, xerrors.Errorf("%w", err)
548 | }
549 | defer resp.Body.Close()
550 | data, err := ioutil.ReadAll(resp.Body)
551 | if err != nil {
552 | return false, xerrors.Errorf("%w", err)
553 | }
554 | skuState := jsoniter.Get(data[14:len(data)-1], "stock").Get("skuState").ToInt()
555 | stockState := jsoniter.Get(data[14:len(data)-1], "stock").Get("StockState").ToInt()
556 | if skuState != 1 || (stockState > 33 && stockState < 40) {
557 | return false, nil
558 | }
559 | return true, nil
560 | }
561 |
562 | func (a *Api) GetItemDetailPage(skuID string) (string, error) {
563 | u := fmt.Sprintf("https://item.jd.com/%s.html", skuID)
564 | req, err := http.NewRequest(http.MethodGet, u, nil)
565 | if err != nil {
566 | return "", xerrors.Errorf("%w", err)
567 | }
568 | req.Header = a.Headers
569 | resp, err := a.Client.Do(req)
570 | if err != nil {
571 | return "", xerrors.Errorf("%w", err)
572 | }
573 | defer resp.Body.Close()
574 | data, err := ioutil.ReadAll(resp.Body)
575 | if err != nil {
576 | return "", xerrors.Errorf("%w", err)
577 | }
578 | return string(data), nil
579 | }
580 |
581 | func (a *Api) CancelSelectAllCartItem() (bool, error) {
582 | u := "https://cart.jd.com/cancelAllItem.action"
583 | reqBody := new(models.CancelCartBody)
584 | reqBody.T = 0
585 | reqBody.OutSkus = ""
586 | reqBody.Random = rand.Intn(1)
587 | reqBodyByte, err := jsoniter.Marshal(reqBody)
588 | if err != nil {
589 | return false, xerrors.Errorf("%w", err)
590 | }
591 | req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(reqBodyByte))
592 | if err != nil {
593 | return false, xerrors.Errorf("%w", err)
594 | }
595 | req.Header.Set("User-Agent", a.UserAgent)
596 | resp, err := a.Client.Do(req)
597 | if err != nil {
598 | return false, xerrors.Errorf("%w", err)
599 | }
600 | defer resp.Body.Close()
601 | if resp.StatusCode != http.StatusOK {
602 | log.Panicf("Status: %d, Url: %s", resp.StatusCode, u)
603 | return false, nil
604 | }
605 | return true, nil
606 | }
607 |
608 | func (a *Api) GetCartDetail() (map[string]string, error) {
609 | catDetail := make(map[string]string)
610 | u := "https://cart.jd.com/cart.action"
611 | req, err := http.NewRequest(http.MethodGet, u, nil)
612 | if err != nil {
613 | return nil, xerrors.Errorf("%w", err)
614 | }
615 | req.Header.Set("User-Agent", a.UserAgent)
616 | resp, err := a.Client.Do(req)
617 | if err != nil {
618 | return nil, xerrors.Errorf("%w", err)
619 | }
620 | defer resp.Body.Close()
621 | doc, err := goquery.NewDocumentFromReader(resp.Body)
622 | if err != nil {
623 | return nil, xerrors.Errorf("%w", err)
624 | }
625 | doc.Find("div[class=item-item]").Each(func(i int, s *goquery.Selection) {
626 | // For each item found, get the band and title
627 | band := s.Find("a").Text()
628 | title := s.Find("i").Text()
629 | fmt.Printf("Review %d: %s - %s\n", i, band, title)
630 | })
631 | return catDetail, xerrors.Errorf("%w", err)
632 | }
633 | func (a *Api) AddOrChangeCartItem(cart map[string]string, skuId string, count string) {
634 | for k, v := range cart {
635 | if skuId == v {
636 | log.Panicf("%s 已在购物车中,调整数量为 %s", skuId, count)
637 | cartItem := v
638 | log.Println(k, cartItem)
639 | //todo:
640 | }
641 | }
642 | log.Printf("%s 不在购物车中,开始加入购物车,数量 %s", skuId, count)
643 | a.AddItemToCart(skuId, count)
644 | }
645 |
646 | func (a *Api) AddItemToCart(skuId string, count string) {
647 | var result bool
648 | u := "https://cart.jd.com/gate.action?"
649 | args := url.Values{}
650 | args.Add("pid", skuId)
651 | args.Add("pcount", count)
652 | args.Add("ptype", "1")
653 | u = u + args.Encode()
654 | req, err := http.NewRequest(http.MethodGet, u, nil)
655 | if err != nil {
656 | //return xerrors.Errorf("%w",err)
657 | }
658 | req.Header.Set("User-Agent", a.UserAgent)
659 | resp, err := a.Client.Do(req)
660 | defer resp.Body.Close()
661 | doc, err := goquery.NewDocumentFromReader(resp.Body)
662 | if err != nil {
663 | //return nil, xerrors.Errorf("%w", err)
664 | }
665 | doc.Find("h3[class=ftx-02]").Each(func(i int, s *goquery.Selection) {
666 | result = s.Text() == "商品已成功加入购物车!"
667 | })
668 | if result {
669 | log.Printf("%s x %s已成功加入购物车", skuId, count)
670 | } else {
671 | log.Printf("%s 添加到购物车失败", skuId)
672 | }
673 | }
674 |
675 | func (a *Api) SubmitOrderWithRetry(submitRetry, interval int) (bool, error) {
676 | for i := 1; i < submitRetry+1; i++ {
677 | log.Printf("第[%d/%d]次尝试提交订单", i, submitRetry)
678 | a.GetCheckoutPageDetail()
679 | result, err := a.SubmitOrder()
680 | if err != nil {
681 | return false, xerrors.Errorf("%w", err)
682 | }
683 | if result {
684 | log.Printf("第%d次提交订单成功", i)
685 | return true, nil
686 | } else {
687 | if i < retryTimes {
688 | log.Printf("第%d次提交失败,%ds后重试", i, interval)
689 | time.Sleep(time.Second * time.Duration(interval))
690 | }
691 | }
692 | }
693 | log.Printf("重试提交%d次结束", retryTimes)
694 | return false, nil
695 | }
696 |
697 | func (a *Api) GetCheckoutPageDetail() (map[string]string, error) {
698 | orderDetail := make(map[string]string)
699 | u := "http://trade.jd.com/shopping/order/getOrderInfo.action?"
700 | args := url.Values{}
701 | args.Add("rid", fmt.Sprintf("%d", time.Now().Unix()*1e3))
702 | u = u + args.Encode()
703 | req, err := http.NewRequest(http.MethodGet, u, nil)
704 | if err != nil {
705 | return nil, xerrors.Errorf("%w", err)
706 | }
707 | req.Header.Set("User-Agent", a.UserAgent)
708 | resp, err := a.Client.Do(req)
709 | if err != nil {
710 | return nil, xerrors.Errorf("%w", err)
711 | }
712 | defer resp.Body.Close()
713 | if resp.StatusCode != http.StatusOK {
714 | log.Printf("获取订单结算页信息失败")
715 | return nil, xerrors.Errorf("%w", err)
716 | }
717 | doc, err := goquery.NewDocumentFromReader(resp.Body)
718 | if err != nil {
719 | return nil, xerrors.Errorf("%w", err)
720 | }
721 | orderDetail["address"] = strings.Trim(doc.Find("span[id=sendAddr]").Text(), "寄送至: ")
722 | orderDetail["receiver"] = strings.Trim(doc.Find("span[id=sendMobile]").Text(), "收货人:")
723 | orderDetail["total_price"] = strings.Trim(doc.Find("span[id=sumPayPriceId]").Text(), "¥")
724 | //orderDetail["items"]=[]string{}
725 | log.Printf("下单信息:%v", orderDetail)
726 | return orderDetail, nil
727 | }
728 |
729 | func (a *Api) SubmitOrder() (bool, error) {
730 | var orderId int64
731 | u := "https://trade.jd.com/shopping/order/submitOrder.action"
732 | args := url.Values{}
733 | args.Add("overseaPurchaseCookies", "")
734 | args.Add("vendorRemarks", "[]")
735 | args.Add("submitOrderParam.sopNotPutInvoice", "false")
736 | args.Add("submitOrderParam.trackID", "TestTrackId")
737 | args.Add("submitOrderParam.ignorePriceChange", "0")
738 | args.Add("submitOrderParam.btSupport", "0")
739 | args.Add("riskControl", a.RiskControl)
740 | args.Add("submitOrderParam.isBestCoupon", "1")
741 | args.Add("submitOrderParam.jxj", "1")
742 | args.Add("submitOrderParam.trackId", a.TrackID)
743 | args.Add("submitOrderParam.eid", a.EID)
744 | args.Add("submitOrderParam.fp", a.Fp)
745 | args.Add("submitOrderParam.needCheck", "1")
746 |
747 | req, err := http.NewRequest(http.MethodPost, u, strings.NewReader(args.Encode()))
748 | if err != nil {
749 | return false, xerrors.Errorf("%w", err)
750 | }
751 | req.Header.Add("User-Agent", a.UserAgent)
752 | req.Header.Add("Host", "trade.jd.com")
753 | req.Header.Add("Referer", "http://trade.jd.com/shopping/order/getOrderInfo.action")
754 | resp, err := a.Client.Do(req)
755 | if err != nil {
756 | return false, xerrors.Errorf("%w", err)
757 | }
758 | defer resp.Body.Close()
759 | data, err := ioutil.ReadAll(resp.Body)
760 | if err != nil {
761 | return false, xerrors.Errorf("%w", err)
762 | }
763 | if jsoniter.Get(data, "success").ToBool() {
764 | orderId = jsoniter.Get(data, "orderId").ToInt64()
765 | log.Printf("订单提交成功!订单号:%d", orderId)
766 | if a.SendMessage {
767 | //todo:
768 |
769 | }
770 | return true, nil
771 | } else {
772 | message := jsoniter.Get(data, "message").ToString()
773 | resultCode := jsoniter.Get(data, "resultCode").ToInt64()
774 | switch resultCode {
775 | case 0:
776 | a.SaveInvoice()
777 | message = message + "(下单商品可能为第三方商品,将切换为普通发票进行尝试)"
778 | case 60077:
779 | message = message + "(可能是购物车为空 或 未勾选购物车中商品)"
780 | case 60123:
781 | message = message + "(需要在config.ini文件中配置支付密码)"
782 | default:
783 | log.Println(resultCode)
784 | }
785 | log.Printf("订单提交失败, 错误码:%d, 返回信息:%s", resultCode, message)
786 | return false, nil
787 | }
788 | }
789 |
790 | func (a *Api) SaveInvoice() {
791 | u := "https://trade.jd.com/shopping/dynamic/invoice/saveInvoice.action"
792 | args := url.Values{}
793 | args.Add("invoiceParam.selectedInvoiceType", "1")
794 | args.Add("invoiceParam.companyName", "个人")
795 | args.Add("invoiceParam.invoicePutType", "0")
796 | args.Add("invoiceParam.selectInvoiceTitle", "4")
797 | args.Add("invoiceParam.selectBookInvoiceContent", "")
798 | args.Add("invoiceParam.selectNormalInvoiceContent", "1")
799 | args.Add("invoiceParam.vatCompanyName", "")
800 | args.Add("invoiceParam.code", "")
801 | args.Add("invoiceParam.regAddr", "")
802 | args.Add("invoiceParam.regPhone", "")
803 | args.Add("invoiceParam.regBank", "")
804 | args.Add("invoiceParam.regBankAccount", "1")
805 | args.Add("invoiceParam.hasCommon", "true")
806 | args.Add("invoiceParam.hasBook", "false")
807 | args.Add("invoiceParam.consigneeName", "")
808 | args.Add("invoiceParam.consigneePhone", "")
809 | args.Add("invoiceParam.consigneeAddress", "")
810 | args.Add("invoiceParam.consigneeProvince", "请选择:")
811 | args.Add("invoiceParam.consigneeProvinceId", "NaN")
812 | args.Add("invoiceParam.consigneeCity", "请选择")
813 | args.Add("invoiceParam.consigneeCityId", "NaN")
814 | args.Add("invoiceParam.consigneeCounty", "请选择")
815 | args.Add("invoiceParam.consigneeCountyId", "NaN")
816 | args.Add("invoiceParam.consigneeTown", "请选择")
817 | args.Add("invoiceParam.consigneeTownId", "0")
818 | args.Add("invoiceParam.sendSeparate", "false")
819 | args.Add("invoiceParam.usualInvoiceId", "")
820 | args.Add("invoiceParam.selectElectroTitle", "4")
821 | args.Add("invoiceParam.electroCompanyName", "undefined")
822 | args.Add("invoiceParam.electroInvoiceEmail", "")
823 | args.Add("invoiceParam.electroInvoicePhone", "")
824 | args.Add("invokeInvoiceBasicService", "true")
825 | args.Add("invoice_ceshi1", "")
826 | args.Add("invoiceParam.showInvoiceSeparate", "false")
827 | args.Add("invoiceParam.invoiceSeparateSwitch", "1")
828 | args.Add("invoiceParam.invoiceCode", "")
829 | args.Add("invoiceParam.saveInvoiceFlag", "1")
830 | req, err := http.NewRequest(http.MethodPost, u, strings.NewReader(args.Encode()))
831 | if err != nil {
832 | return
833 | }
834 | req.Header.Set("User-Agent", a.UserAgent)
835 | req.Header.Set("Referer", "https://trade.jd.com/shopping/dynamic/invoice/saveInvoice.action")
836 | resp, err := a.Client.Do(req)
837 | if err != nil {
838 | return
839 | }
840 | defer resp.Body.Close()
841 | }
842 |
--------------------------------------------------------------------------------
/api/api_test.go:
--------------------------------------------------------------------------------
1 | package api_test
2 |
3 | import (
4 | "JD_Purchase/api"
5 | "crypto/tls"
6 | "encoding/gob"
7 | "os"
8 |
9 | //"encoding/gob"
10 | "log"
11 | "net/http"
12 | "net/url"
13 | //"os"
14 | "testing"
15 | )
16 |
17 | func TestNewApi(t *testing.T) {
18 | proxy := func(_ *http.Request) (*url.URL, error) {
19 | return url.Parse("socks5://127.0.0.1:8889")
20 | }
21 | transport := &http.Transport{
22 | Proxy: proxy,
23 | TLSClientConfig: &tls.Config{
24 | InsecureSkipVerify: true,
25 | }}
26 | client := &http.Client{
27 | Transport: transport,
28 | }
29 | api, err := api.NewApi(client)
30 | if err != nil {
31 | t.Errorf("%+v", err)
32 | return
33 | }
34 | t.Log(api)
35 | }
36 |
37 | func TestApi_GetQRCode(t *testing.T) {
38 | test := new(api.Api)
39 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
40 | proxy := func(_ *http.Request) (*url.URL, error) {
41 | return url.Parse("socks5://127.0.0.1:8889")
42 | }
43 | transport := &http.Transport{
44 | Proxy: proxy,
45 | TLSClientConfig: &tls.Config{
46 | InsecureSkipVerify: true,
47 | }}
48 | test.Client = &http.Client{
49 | Transport: transport,
50 | }
51 | ret, err := test.GetQRCode()
52 | if err != nil {
53 | log.Println(err)
54 | }
55 | log.Println(ret)
56 | }
57 |
58 | func TestApi_GetQRCodeTicket(t *testing.T) {
59 | test := new(api.Api)
60 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
61 | proxy := func(_ *http.Request) (*url.URL, error) {
62 | return url.Parse("socks5://127.0.0.1:8889")
63 | }
64 | transport := &http.Transport{
65 | Proxy: proxy,
66 | TLSClientConfig: &tls.Config{
67 | InsecureSkipVerify: true,
68 | }}
69 | test.Client = &http.Client{
70 | Transport: transport,
71 | }
72 | ret, err := test.GetQRCodeTicket()
73 | if err != nil {
74 | log.Println(err)
75 | }
76 | log.Println(ret)
77 | }
78 |
79 | func TestApi_LoginByQRCode(t *testing.T) {
80 | proxyUrl, _ := url.Parse("socks5://127.0.0.1:8889")
81 | proxy := http.ProxyURL(proxyUrl)
82 | transport := &http.Transport{
83 | Proxy: proxy,
84 | TLSClientConfig: &tls.Config{
85 | InsecureSkipVerify: true,
86 | }}
87 | client := &http.Client{
88 | Transport: transport,
89 | }
90 | test, err := api.NewApi(client)
91 | if err != nil {
92 | t.Logf("%+v", err)
93 | return
94 | }
95 | ret, err := test.LoginByQRCode()
96 | if err != nil {
97 | log.Printf("%+v", err)
98 | }
99 | log.Println(ret)
100 | }
101 |
102 | func TestApi_GetItemDetailPage(t *testing.T) {
103 | test := new(api.Api)
104 | test.Headers = make(http.Header, 1)
105 | test.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
106 | //test.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
107 | proxy := func(_ *http.Request) (*url.URL, error) {
108 | return url.Parse("socks5://127.0.0.1:8889")
109 | }
110 | transport := &http.Transport{
111 | Proxy: proxy,
112 | TLSClientConfig: &tls.Config{
113 | InsecureSkipVerify: true,
114 | }}
115 | test.Client = &http.Client{
116 | Transport: transport,
117 | }
118 | ret, err := test.GetItemDetailPage("1179553")
119 | if err != nil {
120 | log.Printf("+%v", err)
121 | }
122 | log.Println(string(ret))
123 | }
124 |
125 | func TestApi_GetSigleItemStock(t *testing.T) {
126 | test := new(api.Api)
127 | test.ItemCat = make(map[string]string)
128 | test.ItemVenderIDs = make(map[string]string)
129 | test.Headers = make(http.Header, 1)
130 | test.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
131 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
132 | proxy := func(_ *http.Request) (*url.URL, error) {
133 | return url.Parse("socks5://127.0.0.1:8889")
134 | }
135 | transport := &http.Transport{
136 | Proxy: proxy,
137 | TLSClientConfig: &tls.Config{
138 | InsecureSkipVerify: true,
139 | }}
140 | test.Client = &http.Client{
141 | Transport: transport,
142 | }
143 | ret, err := test.GetSigleItemStock("10023774354580", "1", "2_2834_51993_0")
144 | if err != nil {
145 | log.Printf("+%v", err)
146 | return
147 | }
148 | log.Println(ret)
149 | }
150 |
151 | func TestApi_CancelSelectAllCartItem(t *testing.T) {
152 | test := new(api.Api)
153 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
154 | test.Headers = make(http.Header, 1)
155 | test.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
156 | //test.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
157 | proxy := func(_ *http.Request) (*url.URL, error) {
158 | return url.Parse("socks5://127.0.0.1:8889")
159 | }
160 | transport := &http.Transport{
161 | Proxy: proxy,
162 | TLSClientConfig: &tls.Config{
163 | InsecureSkipVerify: true,
164 | }}
165 | test.Client = &http.Client{
166 | Transport: transport,
167 | }
168 | ret, err := test.CancelSelectAllCartItem()
169 | if err != nil {
170 | log.Printf("%+v", err)
171 | }
172 | log.Println(ret)
173 | }
174 |
175 | func TestApi_AddItemToCart(t *testing.T) {
176 | var cookiee []*http.Cookie
177 | test := new(api.Api)
178 |
179 | f, _ := os.Open("cookies")
180 | bi := gob.NewDecoder(f)
181 | err := bi.Decode(&cookiee)
182 | if err != nil {
183 | log.Println(err)
184 | }
185 | log.Println(cookiee[1].Value)
186 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
187 | proxy := func(_ *http.Request) (*url.URL, error) {
188 | return url.Parse("socks5://127.0.0.1:8889")
189 | }
190 | transport := &http.Transport{
191 | Proxy: proxy,
192 | TLSClientConfig: &tls.Config{
193 | InsecureSkipVerify: true,
194 | }}
195 | test.Client = &http.Client{
196 | Transport: transport,
197 | }
198 | //ret,err:=test.LoginByQRCode()
199 | //if err!=nil{
200 | // log.Println(err)
201 | //}
202 | //log.Println(ret)
203 | ////f,_:=os.Create("./cookies")
204 | ////bi:=gob.NewEncoder(f)
205 | ////u,_:=url.Parse("https://qr.m.jd.com")
206 | ////err=bi.Encode(test.Sess.Cookies(u))
207 | ////if err != nil {
208 | //// fmt.Println("编码错误", err.Error())
209 | ////} else {
210 | //// fmt.Println("编码成功")
211 | ////}
212 | //test.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
213 | test.Headers = make(http.Header, 1)
214 | test.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
215 | //test.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
216 | test.AddItemToCart("100001324422", "1")
217 | //if err!=nil{
218 | // log.Printf("%+v",err)
219 | //}
220 | //log.Println(ret)
221 | }
222 |
223 | func TestApi_GetMultiItemStockNew(t *testing.T) {
224 | itemMap := make(map[string]string)
225 | itemMap["730618"] = "1"
226 | itemMap["4080291"] = "2"
227 | test := new(api.Api)
228 | test.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
229 | test.Headers = make(http.Header, 1)
230 | test.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
231 | //test.UserAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
232 | proxy := func(_ *http.Request) (*url.URL, error) {
233 | return url.Parse("socks5://127.0.0.1:8889")
234 | }
235 | transport := &http.Transport{
236 | Proxy: proxy,
237 | TLSClientConfig: &tls.Config{
238 | InsecureSkipVerify: true,
239 | }}
240 | test.Client = &http.Client{
241 | Transport: transport,
242 | }
243 | ret, err := test.GetMultiItemStockNew(itemMap, "18_1482_48938_52586")
244 | if err != nil {
245 | log.Printf("%+v", err)
246 | }
247 | log.Println(ret)
248 | }
249 |
250 | func TestApi_(t *testing.T) {
251 |
252 | }
253 |
--------------------------------------------------------------------------------
/conf/conf.yaml:
--------------------------------------------------------------------------------
1 | isRandomUseragent:
2 | eid: ''
3 | fp: ''
4 | track_id: ''
5 | risk_control: ''
6 | timeout: 10
7 | enableSendMessage: false
8 | messenger:
9 | sckey: ''
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "gopkg.in/yaml.v2"
5 | "io/ioutil"
6 | "log"
7 | "sync"
8 | )
9 |
10 | var once sync.Once
11 | var config = &Config{}
12 |
13 | type Config struct {
14 | IsRandomUserAgent bool `yaml:"isRandomUseragent"`
15 | EID string `yaml:"eid"`
16 | FP string `yaml:"fp"`
17 | TrackID string `yaml:"track_id"`
18 | RiskControl string `yaml:"risk_control"`
19 | Timeout int64 `yaml:"timeout"`
20 | EnableSendMessage bool `yaml:"enableSendMessage"`
21 | Messenger struct {
22 | Sckey string `yaml:"sckey"`
23 | } `yaml:"messenger"`
24 | }
25 |
26 | func Get() *Config {
27 | once.Do(func() {
28 | conf, err := ioutil.ReadFile("./conf/conf.yaml")
29 | if err != nil {
30 | log.Fatalln("read conf file: ", err)
31 | }
32 | err = yaml.Unmarshal(conf, config)
33 | if err != nil {
34 | log.Fatalln("unmarshal conf file: ", err)
35 | }
36 | })
37 | return config
38 | }
39 |
--------------------------------------------------------------------------------
/doc/丽芝士_1179553.json:
--------------------------------------------------------------------------------
1 | {
2 | "stock": {
3 | "freshEdi": null,
4 | "Ext": "isdangergoods:0,thwa:2,nationallySetWare:5,SoldOversea:1,isFlashPurchase:0,isCanUseJQ,tsfw:G,isXfpDept,isSupportCard:1,isCanUseDQ,is7ToReturn:1",
5 | "realSkuId": 1179553,
6 | "pr": {
7 | "promiseResult": "23:10前下单,预计明天(03月02日)送达",
8 | "resultCode": 1
9 | },
10 | "promiseResult": "23:10前下单,预计明天(03月02日)送达",
11 | "nationallySetWare": "5",
12 | "sidDely": "34",
13 | "channel": 1,
14 | "serviceInfo": "由 京东 发货, 并提供售后服务. ",
15 | "rid": null,
16 | "weightValue": "0.26kg",
17 | "isSopUseSelfStock": "0",
18 | "IsPurchase": true,
19 | "areaLevel": 4,
20 | "cla": [
21 | ],
22 | "eb": "0",
23 | "ec": "-1",
24 | "skuId": 1179553,
25 | "Dc": [
26 | ],
27 | "yjhxMap": [
28 | ],
29 | "isWalMar": false,
30 | "area": {
31 | "townName": "",
32 | "cityName": "松江区",
33 | "success": true,
34 | "provinceName": "上海",
35 | "countyName": "小昆山镇"
36 | },
37 | "ab": "-1",
38 | "ac": "-1",
39 | "ad": "-1",
40 | "ae": "-1",
41 | "skuState": 1,
42 | "PopType": 0,
43 | "af": "-1",
44 | "promiseMark": "支持7天无理由退货(拆封后不支持)",
45 | "ag": "-1",
46 | "isSam": false,
47 | "ir": [
48 | {
49 | "resultCode": 1,
50 | "showName": "京准达",
51 | "helpLink": "//help.jd.com/user/issue/103-983.html",
52 | "iconTip": "选择京准达服务,可指定精确时间点收货;若京东责任超时,即时赔付",
53 | "picUrl": "http://m.360buyimg.com/mobilecms/jfs/t3172/266/1698067915/1634/64a0c40e/57d25fcfNd9c62bb7.png",
54 | "iconCode": "sendpay_zhun",
55 | "iconType": 0,
56 | "iconServiceType": 1,
57 | "iconSrc": "京准达"
58 | },
59 | {
60 | "resultCode": 1,
61 | "showName": "京尊达",
62 | "helpLink": "https://help.jd.com/user/issue/936-3526.html",
63 | "iconTip": "轻奢尊贵礼遇的高端派送服务",
64 | "picUrl": "http://img13.360buyimg.com/cms/jfs/t6205/122/368144256/449/d25406e6/593e505dN5424f50e.png",
65 | "iconCode": "service_jingzunda",
66 | "iconType": 0,
67 | "iconServiceType": 6,
68 | "iconSrc": "京尊达"
69 | },
70 | {
71 | "resultCode": 1,
72 | "showName": "预约送货",
73 | "helpLink": "//help.jd.com/user/issue/103-983.html",
74 | "iconTip": "京东物流为该商品提供预约送货服务",
75 | "picUrl": "https://m.360buyimg.com/babel/jfs/t1/116316/15/7402/1031/5ec22ca4E713f857c/dd49784b20933cf5.png",
76 | "iconCode": "service_yysh",
77 | "iconType": 0,
78 | "iconServiceType": 6,
79 | "iconSrc": "预约送货"
80 | },
81 | {
82 | "resultCode": 1,
83 | "showName": "部分收货",
84 | "helpLink": "https://help.jd.com/user/issue/103-983.html",
85 | "iconTip": "如果收件人收货时发现部分货物存在缺少配件、物流损等情形,京东物流提供订单半收服务",
86 | "picUrl": "https://m.360buyimg.com/babel/jfs/t1/108073/34/18517/1071/5ec22ce0E11a3b1c5/f8ffea5f4cafa0f9.png",
87 | "iconCode": "service_bfsh",
88 | "iconType": 0,
89 | "iconServiceType": 6,
90 | "iconSrc": "部分收货"
91 | },
92 | {
93 | "resultCode": 1,
94 | "showName": "送货上门",
95 | "helpLink": "https://help.jd.com/user/issue/254-4130.html",
96 | "iconTip": "京东快递为您提供送货上门服务",
97 | "picUrl": "https://m.360buyimg.com/babel/jfs/t1/115738/37/12143/1066/5f0c7d11E4faee520/de3879572e2b2014.png",
98 | "iconCode": "service_sssm",
99 | "iconType": 0,
100 | "iconServiceType": 6,
101 | "iconSrc": "送货上门"
102 | },
103 | {
104 | "resultCode": 1,
105 | "showName": "99元免基础运费",
106 | "helpLink": "//help.jd.com/user/issue/103-983.html",
107 | "iconTip": "所选地址自营订单满99元免基础运费(20kg内),超出重量加收1元/kg续重运费。",
108 | "picUrl": "//static.360buyimg.com/item/assets/picon/mianyunfei.png",
109 | "iconCode": "free_delivery_zhong",
110 | "iconType": 0,
111 | "iconServiceType": 4,
112 | "iconSrc": "99元免基础运费"
113 | },
114 | {
115 | "resultCode": 1,
116 | "showName": "京东物流",
117 | "helpLink": "https://help.jd.com/user/issue/list-81.html",
118 | "iconTip": "京东物流为您提供仓配一体供应链服务",
119 | "picUrl": "https://m.360buyimg.com/babel/jfs/t1/130756/9/11972/4289/5f8674d3Eabfebbef/bb964241bd789a72.png",
120 | "iconCode": "service_jdwl",
121 | "iconType": 0,
122 | "iconServiceType": 4,
123 | "iconSrc": "京东物流"
124 | },
125 | {
126 | "resultCode": 1,
127 | "showName": "上门换新",
128 | "helpLink": "https://pro.jd.com/mall/active/2LqxyJe519jNFrTb1kBF591jqU4K/index.html",
129 | "iconTip": "签收15天内因商品性能故障换货的,上门取件时直接换新。",
130 | "picUrl": "https://img10.360buyimg.com/cms/jfs/t1/5561/27/2386/1346/5b972c05E22ccb531/1845e3a3540ed86b.png",
131 | "iconCode": "service_shangmenhx",
132 | "iconType": 0,
133 | "iconServiceType": 3,
134 | "iconSrc": "上门换新"
135 | },
136 | {
137 | "resultCode": 1,
138 | "showName": "破损包退换",
139 | "helpLink": "https://pro.jd.com/mall/active/3Fa3djcUdGfd3C3rrHe1XCgBf7ot/index.html",
140 | "iconTip": " 签收后72小时内发现商品破损问题,提供售后服务。",
141 | "picUrl": "https://img12.360buyimg.com/cms/jfs/t1/3879/4/2691/1414/5b972bfeE68df6212/fba3cd7b26b3ac1c.png",
142 | "iconCode": "service_psbth",
143 | "iconType": 0,
144 | "iconServiceType": 3,
145 | "iconSrc": "破损包退换"
146 | },
147 | {
148 | "resultCode": 1,
149 | "showName": "闪电退款",
150 | "helpLink": "https://pro.jd.com/mall/active/3PgiUVJbrXf3gPdcxmzSbgPiSKVC/index.html",
151 | "iconTip": "签收7天内退货的,提供先退款后退货服务。",
152 | "picUrl": "https://img12.360buyimg.com/cms/jfs/t1/3626/32/2623/1391/5b972bfeE80722121/7ad525caf1422321.png",
153 | "iconCode": "service_shantui",
154 | "iconType": 0,
155 | "iconServiceType": 3,
156 | "iconSrc": "闪电退款"
157 | },
158 | {
159 | "resultCode": 1,
160 | "showName": "自提",
161 | "helpLink": "//help.jd.com/user/issue/103-983.html",
162 | "iconTip": "我们提供多种自提服务,包括京东自提点、自助提货柜、京东校园派、京东星配站、京东便民站等服务",
163 | "picUrl": "//static.360buyimg.com/item/assets/picon/shoutixiang.png",
164 | "iconCode": "special_ziti",
165 | "iconType": 0,
166 | "iconServiceType": 5,
167 | "iconSrc": "自提"
168 | }],
169 | "vd": null,
170 | "rfg": 0,
171 | "Dti": null,
172 | "jdPrice": {
173 | "p": "10.80",
174 | "op": "10.80",
175 | "id": "1179553",
176 | "m": "15.20"
177 | },
178 | "rn": -1,
179 | "support": [
180 | ],
181 | "venderType": null,
182 | "PlusFlagInfo": "",
183 | "code": 1,
184 | "isPlus": false,
185 | "afsCode": 0,
186 | "stockDesc": "有货",
187 | "sid": "34",
188 | "isJDexpress": "0",
189 | "dcId": "3",
190 | "sr": null,
191 | "StockState": 33,
192 | "StockStateName": "现货",
193 | "dcashDesc": "",
194 | "m": "0",
195 | "self_D": {
196 | "vid": 1000077352,
197 | "df": null,
198 | "cg": "1320:5019:5020",
199 | "colType": 0,
200 | "deliver": "丽芝士(Richeese)京东自营旗舰店",
201 | "id": 1000077352,
202 | "type": 0,
203 | "vender": "丽芝士(Richeese)京东自营旗舰店",
204 | "linkphone": "",
205 | "url": "http://mall.jd.com/index-1000077352.html",
206 | "po": "false"
207 | },
208 | "ArrivalDate": "",
209 | "v": "0",
210 | "promiseYX": {
211 | "resultCode": 1,
212 | "showName": "7天无理由退货(拆封后不支持)",
213 | "helpLink": "http://help.jd.com/user/issue/126-3780.html ",
214 | "iconTip": "支持7天无理由退货(拆封后不支持)",
215 | "picUrl": "\\",
216 | "iconCode": "service_qitiantuihuo",
217 | "iconType": 0,
218 | "iconServiceType": 3,
219 | "iconSrc": "7天无理由退货(拆封后不支持)"
220 | }
221 | },
222 | "choseSuit": [
223 | ]
224 | }
--------------------------------------------------------------------------------
/doc/显卡_10023774354580.json:
--------------------------------------------------------------------------------
1 | {
2 | "stock": {
3 | "freshEdi": null,
4 | "Ext": "YuShou,fhsx:1,isFlashPurchase:2,thwa:2,tsfw:h,platform,fare:115872,is7ToReturn:1",
5 | "realSkuId": 10023774354580,
6 | "promiseResult": "",
7 | "nationallySetWare": null,
8 | "popFxgCode": "0",
9 | "sidDely": "-1",
10 | "channel": 1,
11 | "serviceInfo": "由武极电脑DIY旗舰店从 湖北武汉市 发货, 并提供售后服务. ",
12 | "rid": null,
13 | "isSopUseSelfStock": "0",
14 | "IsPurchase": false,
15 | "areaLevel": 4,
16 | "cla": [
17 | ],
18 | "eb": "99",
19 | "ec": "-1",
20 | "skuId": 10023774354580,
21 | "Dc": [
22 | {
23 | "freihtType": 1,
24 | "ordermin": 0.0,
25 | "dcash": 0.0,
26 | "dtype": 3
27 | },
28 | {
29 | "freihtType": 1,
30 | "ordermin": 0.0,
31 | "dcash": 0.0,
32 | "dtype": 2
33 | }],
34 | "yjhxMap": {
35 | "IsSXQJ": {
36 | "type": 1,
37 | "attr": "IsSXQJ",
38 | "exceptAttr": "",
39 | "urlName": "高价回收,极速到账",
40 | "url": "//huishou.jd.com?s=1",
41 | "tips": "免费提供闲置物品回收服务,完成回收后可获得京东E卡或现金。"
42 | }
43 | },
44 | "isWalMar": false,
45 | "area": {
46 | "townName": "",
47 | "cityName": "松江区",
48 | "success": true,
49 | "provinceName": "上海",
50 | "countyName": "小昆山镇"
51 | },
52 | "ab": "-1",
53 | "ac": "-1",
54 | "ad": "-1",
55 | "ae": "-1",
56 | "skuState": 1,
57 | "PopType": 0,
58 | "af": "-1",
59 | "ag": "-1",
60 | "isSam": false,
61 | "vd": null,
62 | "rfg": 0,
63 | "Dti": null,
64 | "jdPrice": {
65 | "p": "5799.00",
66 | "op": "5799.00",
67 | "id": "10023774354580",
68 | "m": "9999.00"
69 | },
70 | "rn": -1,
71 | "support": [
72 | {
73 | "helpLink": "//huishou.jd.com?s=1&skuId=10023774354580",
74 | "showName": "高价回收,极速到账",
75 | "id": "old2new",
76 | "iconTip": "免费提供闲置物品回收服务,完成回收后可获得京东E卡或现金。"
77 | }],
78 | "venderType": "0",
79 | "PlusFlagInfo": "",
80 | "code": 1,
81 | "isPlus": false,
82 | "afsCode": 1,
83 | "D": {
84 | "vid": 84607,
85 | "df": "湖北武汉市",
86 | "cg": "670:677:679",
87 | "colType": 0,
88 | "deliver": "武极电脑DIY旗舰店",
89 | "id": 81477,
90 | "type": 0,
91 | "vender": "武极电脑DIY旗舰店",
92 | "linkphone": "400-610-1360转113839",
93 | "url": "//vgame.jd.com",
94 | "po": "true"
95 | },
96 | "stockDesc": "无货,此商品暂时售完",
97 | "sid": "-1",
98 | "isJDexpress": "0",
99 | "dcId": "-1",
100 | "StockState": 34,
101 | "StockStateName": "无货",
102 | "dcashDesc": "在线支付免运费 ",
103 | "m": "0",
104 | "ArrivalDate": "",
105 | "v": "0"
106 | },
107 | "choseSuit": [
108 | ]
109 | }
110 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module JD_Purchase
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/PuerkitoBio/goquery v1.6.1
7 | github.com/json-iterator/go v1.1.10
8 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2
9 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
10 | gopkg.in/yaml.v2 v2.4.0
11 | )
12 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "JD_Purchase/api"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | skuIDs := "730618,4080291:2"
11 | area := "18_1482_48938_52586"
12 | client := &http.Client{}
13 | purchaser, err := api.NewApi(client)
14 | if err != nil {
15 | log.Printf("%+v", err)
16 | return
17 | }
18 | result, err := purchaser.LoginByQRCode()
19 | if err != nil {
20 | log.Printf("%+v", err)
21 | return
22 | }
23 | log.Println(result)
24 | err = purchaser.BuyItemInStock(skuIDs, area, false, 5, 3, 5)
25 | if err != nil {
26 | log.Printf("%+v", err)
27 | return
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/models/cart.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type CancelCartBody struct {
4 | T int `json:"t"`
5 | OutSkus string `json:"outSkus"`
6 | Random int `json:"random"`
7 | }
8 |
--------------------------------------------------------------------------------
/models/messenger.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 | jsoniter "github.com/json-iterator/go"
6 | "golang.org/x/xerrors"
7 | "io/ioutil"
8 | "log"
9 | "net/http"
10 | "net/url"
11 | "time"
12 | )
13 |
14 | type Messenger struct {
15 | ScKey string
16 | }
17 |
18 | func NewMessenger(ScKey string) *Messenger {
19 | messenger := new(Messenger)
20 | messenger.ScKey = ScKey
21 | return messenger
22 | }
23 |
24 | func (m *Messenger) Send(client *http.Client, text string, desp string) error {
25 | nowTime := time.Now().Format(time.RFC3339)
26 | if desp != "" {
27 | desp = fmt.Sprintf("%s [%s]", desp, nowTime)
28 | } else {
29 | desp = fmt.Sprintf("[%s]", nowTime)
30 | }
31 | u := fmt.Sprintf("https://sc.ftqq.com/%s.send?text=%s&desp=%s", m.ScKey, url.QueryEscape(text), url.QueryEscape(desp))
32 | req, err := http.NewRequest(http.MethodGet, u, nil)
33 | if err != nil {
34 | return xerrors.Errorf("%w", err)
35 | }
36 | resp, err := client.Do(req)
37 | if err != nil {
38 | return xerrors.Errorf("%w", err)
39 | }
40 | data, err := ioutil.ReadAll(resp.Body)
41 | if err != nil {
42 | return xerrors.Errorf("%w", err)
43 | }
44 | defer resp.Body.Close()
45 | errno := jsoniter.Get(data, "errno").ToInt64()
46 | if errno == 0 {
47 | log.Printf("Message sent successfully [text: %s, desp: %s]", text, desp)
48 | } else {
49 | log.Printf("Fail to send message, reason: %s", string(data))
50 | }
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/models/models_test.go:
--------------------------------------------------------------------------------
1 | package models_test
2 |
3 | import (
4 | "JD_Purchase/models"
5 | "crypto/tls"
6 | "log"
7 | "net/http"
8 | "net/url"
9 | "testing"
10 | )
11 |
12 | func TestMessenger_Send(t *testing.T) {
13 | proxy := func(_ *http.Request) (*url.URL, error) {
14 | return url.Parse("socks5://127.0.0.1:8889")
15 | }
16 | transport := &http.Transport{
17 | Proxy: proxy,
18 | TLSClientConfig: &tls.Config{
19 | InsecureSkipVerify: true,
20 | }}
21 | client := http.Client{
22 | Transport: transport,
23 | }
24 | messenger := models.NewMessenger("")
25 | err := messenger.Send(&client, "jd-assistant 订单提交成功", "订单号:%s % order_id")
26 | if err != nil {
27 | log.Println(err)
28 | t.Fail()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/models/qrcode.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type QRCodeBody struct {
4 | Appid int
5 | Size int
6 | T string
7 | }
8 |
9 | type QRCodeTicketBody struct {
10 | Code int `json:"code"`
11 | Ticket string `json:"ticket"`
12 | Msg string `json:"msg"`
13 | }
14 |
15 | type ValidateQRCodeBody struct {
16 | ReturnCode int `json:"returnCode"`
17 | Url string `json:"url"`
18 | }
19 |
--------------------------------------------------------------------------------
/models/stocks.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type Stock struct {
4 | Stock struct {
5 | FreshEdi interface{} `json:"freshEdi"`
6 | Ext string `json:"Ext"`
7 | RealSkuID int `json:"realSkuId"`
8 | Pr struct {
9 | PromiseResult string `json:"promiseResult"`
10 | ResultCode int `json:"resultCode"`
11 | } `json:"pr"`
12 | PromiseResult string `json:"promiseResult"`
13 | NationallySetWare string `json:"nationallySetWare"`
14 | SidDely string `json:"sidDely"`
15 | Channel int `json:"channel"`
16 | ServiceInfo string `json:"serviceInfo"`
17 | Rid interface{} `json:"rid"`
18 | WeightValue string `json:"weightValue"`
19 | IsSopUseSelfStock string `json:"isSopUseSelfStock"`
20 | IsPurchase bool `json:"IsPurchase"`
21 | AreaLevel int `json:"areaLevel"`
22 | Cla []interface{} `json:"cla"`
23 | Eb string `json:"eb"`
24 | Ec string `json:"ec"`
25 | SkuID int `json:"skuId"`
26 | Dc []interface{} `json:"Dc"`
27 | YjhxMap []interface{} `json:"yjhxMap"`
28 | IsWalMar bool `json:"isWalMar"`
29 | Area struct {
30 | TownName string `json:"townName"`
31 | CityName string `json:"cityName"`
32 | Success bool `json:"success"`
33 | ProvinceName string `json:"provinceName"`
34 | CountyName string `json:"countyName"`
35 | } `json:"area"`
36 | Ab string `json:"ab"`
37 | Ac string `json:"ac"`
38 | Ad string `json:"ad"`
39 | Ae string `json:"ae"`
40 | SkuState int `json:"skuState"`
41 | PopType int `json:"PopType"`
42 | Af string `json:"af"`
43 | PromiseMark string `json:"promiseMark"`
44 | Ag string `json:"ag"`
45 | IsSam bool `json:"isSam"`
46 | Ir []struct {
47 | ResultCode int `json:"resultCode"`
48 | ShowName string `json:"showName"`
49 | HelpLink string `json:"helpLink"`
50 | IconTip string `json:"iconTip"`
51 | PicURL string `json:"picUrl"`
52 | IconCode string `json:"iconCode"`
53 | IconType int `json:"iconType"`
54 | IconSrc string `json:"iconSrc"`
55 | IconServiceType int `json:"iconServiceType"`
56 | } `json:"ir"`
57 | Vd interface{} `json:"vd"`
58 | Rfg int `json:"rfg"`
59 | Dti interface{} `json:"Dti"`
60 | JdPrice struct {
61 | P string `json:"p"`
62 | Op string `json:"op"`
63 | ID string `json:"id"`
64 | M string `json:"m"`
65 | } `json:"jdPrice"`
66 | Rn int `json:"rn"`
67 | Support []interface{} `json:"support"`
68 | VenderType interface{} `json:"venderType"`
69 | PlusFlagInfo string `json:"PlusFlagInfo"`
70 | Code int `json:"code"`
71 | IsPlus bool `json:"isPlus"`
72 | AfsCode int `json:"afsCode"`
73 | StockDesc string `json:"stockDesc"`
74 | Sid string `json:"sid"`
75 | IsJDexpress string `json:"isJDexpress"`
76 | DcID string `json:"dcId"`
77 | Sr interface{} `json:"sr"`
78 | StockState int `json:"StockState"`
79 | StockStateName string `json:"StockStateName"`
80 | DcashDesc string `json:"dcashDesc"`
81 | M string `json:"m"`
82 | SelfD struct {
83 | Vid int `json:"vid"`
84 | Df interface{} `json:"df"`
85 | Cg string `json:"cg"`
86 | ColType int `json:"colType"`
87 | Deliver string `json:"deliver"`
88 | ID int `json:"id"`
89 | Type int `json:"type"`
90 | Vender string `json:"vender"`
91 | Linkphone string `json:"linkphone"`
92 | URL string `json:"url"`
93 | Po string `json:"po"`
94 | } `json:"self_D"`
95 | ArrivalDate string `json:"ArrivalDate"`
96 | V string `json:"v"`
97 | PromiseYX struct {
98 | ResultCode int `json:"resultCode"`
99 | ShowName string `json:"showName"`
100 | HelpLink string `json:"helpLink"`
101 | IconTip string `json:"iconTip"`
102 | PicURL string `json:"picUrl"`
103 | IconCode string `json:"iconCode"`
104 | IconType int `json:"iconType"`
105 | IconSrc string `json:"iconSrc"`
106 | IconServiceType int `json:"iconServiceType"`
107 | } `json:"promiseYX"`
108 | } `json:"stock"`
109 | ChoseSuit []interface{} `json:"choseSuit"`
110 | }
111 |
--------------------------------------------------------------------------------
/models/submitOrder.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type SubmitOrderBody struct {
4 | }
5 |
--------------------------------------------------------------------------------
/models/userinfo.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type UserInfo struct {
4 | HouseholdAppliance int `json:"householdAppliance"`
5 | ImgURL string `json:"imgUrl"`
6 | LastLoginTime string `json:"lastLoginTime"`
7 | NickName string `json:"nickName"`
8 | PlusStatus string `json:"plusStatus"`
9 | RealName string `json:"realName"`
10 | UserLevel int `json:"userLevel"`
11 | UserScoreVO struct {
12 | AccountScore int `json:"accountScore"`
13 | ActivityScore int `json:"activityScore"`
14 | ConsumptionScore int `json:"consumptionScore"`
15 | Default bool `json:"default"`
16 | FinanceScore int `json:"financeScore"`
17 | Pin string `json:"pin"`
18 | RiskScore int `json:"riskScore"`
19 | TotalScore int `json:"totalScore"`
20 | } `json:"userScoreVO"`
21 | }
22 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io/ioutil"
5 | "math/rand"
6 | "net/http"
7 | "os"
8 | "strings"
9 | )
10 |
11 | var userAgents = []string{"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
12 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
13 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
14 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
15 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
16 | "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
17 | "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
18 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
19 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
20 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
21 | "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
22 | "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
23 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
24 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
25 | "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
26 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
27 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
28 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
29 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
30 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
31 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
32 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
33 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
34 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36",
35 | "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
36 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
37 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36",
38 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
39 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36",
40 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36",
41 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
42 | "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36",
43 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36",
44 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36",
45 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1467.0 Safari/537.36",
46 | "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36",
47 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36",
48 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
49 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
50 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
51 | "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
52 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
53 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
54 | "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.36",
55 | "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
56 | "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
57 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17",
58 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17",
59 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15",
60 | "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.14"}
61 |
62 | func SaveImage(resp *http.Response, imagePath string) error {
63 | f, err := os.Create(imagePath)
64 | if err != nil {
65 | return err
66 | }
67 | defer f.Close()
68 | data, err := ioutil.ReadAll(resp.Body)
69 | if err != nil {
70 | return err
71 | }
72 | _, err = f.Write(data)
73 | if err != nil {
74 | return err
75 | }
76 | return nil
77 | }
78 |
79 | func ParseSkuID(skuIDs string) map[string]string {
80 | skuIDs = strings.TrimSpace(skuIDs)
81 | skuIDList := strings.Split(skuIDs, ",")
82 | ret := make(map[string]string)
83 | for _, v := range skuIDList {
84 | if strings.Contains(v, ":") {
85 | skuIDsSplit := strings.Split(v, ":")
86 | ret[skuIDsSplit[0]] = skuIDsSplit[1]
87 | continue
88 | }
89 | ret[v] = "1"
90 | }
91 | return ret
92 | }
93 |
94 | func ParseAreaID(area string) string {
95 | area = strings.TrimSpace(area)
96 | areas := strings.FieldsFunc(area, func(r rune) bool {
97 | return r == '-' || r == '_'
98 | })
99 | lenA := len(areas)
100 | if lenA < 4 {
101 | for i := 0; i < 4-lenA; i++ {
102 | areas = append(areas, "0")
103 | }
104 | }
105 | return strings.Join(areas, "_")
106 | }
107 |
108 | func GetRandomUserAgent() string {
109 | indexes := len(userAgents)
110 | index := rand.Intn(indexes - 1)
111 | return userAgents[index]
112 | }
113 |
--------------------------------------------------------------------------------
/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | package utils_test
2 |
3 | import (
4 | "JD_Purchase/utils"
5 | "log"
6 | "testing"
7 | )
8 |
9 | func TestParseAreaID(t *testing.T) {
10 | ret := utils.ParseAreaID("28_2487_")
11 | log.Println(ret)
12 | }
13 |
14 | func TestParseSkuID(t *testing.T) {
15 | ret := utils.ParseSkuID("123456:2,123789")
16 | log.Println(ret)
17 | }
18 |
--------------------------------------------------------------------------------