├── 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 | --------------------------------------------------------------------------------