From 641180c09f128d458edfa23ace2681f4c5c859a9 Mon Sep 17 00:00:00 2001 From: ztino <> Date: Fri, 1 Jan 2021 03:35:24 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 17 +++ LICENSE | 201 +++++++++++++++++++++++++++++++++++ README.md | 2 + common/lib.go | 111 ++++++++++++++++++++ conf.ini | 45 ++++++++ conf/main.go | 100 ++++++++++++++++++ jd_seckill/seckill.go | 236 ++++++++++++++++++++++++++++++++++++++++++ jd_seckill/user.go | 118 +++++++++++++++++++++ main.go | 118 +++++++++++++++++++++ service/email.go | 34 ++++++ 11 files changed, 984 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 common/lib.go create mode 100644 conf.ini create mode 100644 conf/main.go create mode 100644 jd_seckill/seckill.go create mode 100644 jd_seckill/user.go create mode 100644 main.go create mode 100644 service/email.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95f82d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +./.idea +./qr_code.png + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e6e2d5 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# jd_seckill + diff --git a/common/lib.go b/common/lib.go new file mode 100644 index 0000000..6a6a2d0 --- /dev/null +++ b/common/lib.go @@ -0,0 +1,111 @@ +package common + +import ( + "bytes" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "runtime" + "strconv" + "time" +) + +func Rand(min, max int) int { + if min > max { + panic("min: min cannot be greater than max") + } + if int31 := 1<<31 - 1; max > int31 { + panic("max: max can not be greater than " + strconv.Itoa(int31)) + } + if min == max { + return min + } + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return r.Intn(max+1-min) + min +} + +func GbkToUtf8(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder()) + d, e := ioutil.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +} + +func Utf8ToGbk(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder()) + d, e := ioutil.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +} + +func NewRandStr(length int) string { + s:=[]string{ + "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", + "s", "t", "u", "v", "w", "x", + "y", "z", "A", "B", "C", "D", + "E", "F", "G", "H", "I", "J", + "K", "L", "M", "N", "O", "P", + "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z", + } + str:="" + for i:=1;i<=length;i++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + str+=s[r.Intn(len(s)-1)] + } + return str +} + +func Substr(s string,start,end int) string { + strRune:=[]rune(s) + if start==-1 { + return string(strRune[:end]) + } + if end==-1 { + return string(strRune[start:]) + } + return string(strRune[start:end]) +} + +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +func Exists(path string) bool { + _, err := os.Stat(path) + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} + +func OpenImage(file string) { + if runtime.GOOS=="windows" { + cmd:=exec.Command("start",file) + _=cmd.Start() + }else{ + if runtime.GOOS=="linux" { + cmd:=exec.Command("eog",file) + _=cmd.Start() + }else{ + cmd:=exec.Command("open",file) + _=cmd.Start() + } + } +} \ No newline at end of file diff --git a/conf.ini b/conf.ini new file mode 100644 index 0000000..5970c46 --- /dev/null +++ b/conf.ini @@ -0,0 +1,45 @@ +#默认配置 +[config] +# eid, fp参数必须填写,具体请参考 wiki-常见问题 +# 随意填写可能导致订单无法提交等问题 +eid = +fp = +# 商品id +# 已经是茅台的sku_id了 +sku_id = 100012043978 +# 抢购数量 +seckill_num = 2 +# 设定时间 # 2020-12-09 10:00:00.100000 +buy_time = 2021-01-01 09:59:59 +# 默认UA +DEFAULT_USER_AGENT = Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 + +#账号信息 +[account] +# 支付密码 +# 如果你的账户中有可用的京券(注意不是东券)或 在上次购买订单中使用了京豆, +# 那么京东可能会在下单时自动选择京券支付 或 自动勾选京豆支付。 +# 此时下单会要求输入六位数字的支付密码。请在下方配置你的支付密码,如 123456 。 +# 如果没有上述情况,下方请留空。 +payment_pwd = + +#消息推送 +[messenger] +# 开启推送服务 +enable = false +# 目前只支持smtp邮箱推送 +type = none +# 消息接收人 +email = + +#smtp配置 +# 开启smtp消息推送必须填入 email_user、email_pwd,email_host 若不填则会自动判断,email_pwd 如何获取请自行百度。 +[smtp] +# 邮箱域名 smtp.xx.com +email_host = +# 通信端口 +port = +# 邮箱地址 xxxxxxxx@xx.com +email_user = +# 邮箱授权码(并不一定是邮箱密码) xxxxxxxxxxxxxxxx +email_pwd = \ No newline at end of file diff --git a/conf/main.go b/conf/main.go new file mode 100644 index 0000000..751038d --- /dev/null +++ b/conf/main.go @@ -0,0 +1,100 @@ + +package conf + +import ( + "bufio" + "io" + "os" + "strings" +) + +const middle = "=========" + +type Config struct { + Mymap map[string]string + strcet string +} + +func (c *Config) InitConfig(path string) { + c.Mymap = make(map[string]string) + + f, err := os.Open(path) + if err != nil { + panic(err) + } + defer f.Close() + + r := bufio.NewReader(f) + for { + b, _, err := r.ReadLine() + if err != nil { + if err == io.EOF { + break + } + panic(err) + } + + s := strings.TrimSpace(string(b)) + //fmt.Println(s) + if strings.Index(s, "#") == 0 { + continue + } + + n1 := strings.Index(s, "[") + n2 := strings.LastIndex(s, "]") + if n1 > -1 && n2 > -1 && n2 > n1+1 { + c.strcet = strings.TrimSpace(s[n1+1 : n2]) + continue + } + + if len(c.strcet) == 0 { + continue + } + index := strings.Index(s, "=") + if index < 0 { + continue + } + + frist := strings.TrimSpace(s[:index]) + if len(frist) == 0 { + continue + } + second := strings.TrimSpace(s[index+1:]) + + pos := strings.Index(second, "\t#") + if pos > -1 { + second = second[0:pos] + } + + pos = strings.Index(second, " #") + if pos > -1 { + second = second[0:pos] + } + + pos = strings.Index(second, "\t//") + if pos > -1 { + second = second[0:pos] + } + + pos = strings.Index(second, " //") + if pos > -1 { + second = second[0:pos] + } + + if len(second) == 0 { + continue + } + + key := c.strcet + middle + frist + c.Mymap[key] = strings.TrimSpace(second) + } +} + +func (c Config) Read(node, key string) string { + key = node + middle + key + v, found := c.Mymap[key] + if !found { + return "" + } + return v +} diff --git a/jd_seckill/seckill.go b/jd_seckill/seckill.go new file mode 100644 index 0000000..b4d590e --- /dev/null +++ b/jd_seckill/seckill.go @@ -0,0 +1,236 @@ +package jd_seckill + +import ( + "../common" + "../conf" + "../service" + "errors" + "fmt" + "github.com/Albert-Zhan/httpc" + "github.com/PuerkitoBio/goquery" + "github.com/tidwall/gjson" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +type Seckill struct { + client *httpc.HttpClient + conf *conf.Config +} + +func NewSeckill(client *httpc.HttpClient,conf *conf.Config) *Seckill { + return &Seckill{client: client,conf: conf} +} + +func (this *Seckill) SkuTitle() (string,error) { + skuId:=this.conf.Read("config","sku_id") + req:=httpc.NewRequest(this.client) + resp,body,err:=req.SetUrl(fmt.Sprintf("https://item.jd.com/%s.html",skuId)).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("访问商品详情失败") + return "",errors.New("访问商品详情失败") + } + html:=strings.NewReader(body) + doc,_:=goquery.NewDocumentFromReader(html) + return strings.TrimSpace(doc.Find(".sku-name").Text()),nil +} + +func (this *Seckill) MakeReserve() { + shopTitle,err:=this.SkuTitle() + if err!=nil { + log.Println("获取商品信息失败") + }else{ + log.Println("商品名称:"+shopTitle) + } + skuId:=this.conf.Read("config","sku_id") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Referer",fmt.Sprintf("https://item.jd.com/%s.html",skuId)) + resp,body,err:=req.SetUrl("https://yushou.jd.com/youshouinfo.action?callback=fetchJSON&sku="+skuId+"&_="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("预约商品失败") + }else{ + reserveUrl:=gjson.Get(body,"url").String() + req=httpc.NewRequest(this.client) + _,_,_=req.SetUrl("https:"+reserveUrl).SetMethod("get").Send().End() + log.Println("预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约") + } +} + +func (this *Seckill) getSeckillUrl() (string,error) { + skuId:=this.conf.Read("config","sku_id") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Host","itemko.jd.com") + req.SetHeader("Referer",fmt.Sprintf("https://item.jd.com/%s.html",skuId)) + resp,body,err:=req.SetUrl("https://itemko.jd.com/itemShowBtn?callback=jQuery{}"+strconv.Itoa(common.Rand(1000000,9999999))+"&skuId="+skuId+"&from=pc&_="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("抢购链接获取失败,稍后自动重试") + return "",errors.New("抢购链接获取失败,稍后自动重试") + } + url:=gjson.Get(body,"url").String() + if url=="" { + log.Println("抢购链接获取失败,稍后自动重试") + return "",errors.New("抢购链接获取失败,稍后自动重试") + } + //https://divide.jd.com/user_routing?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc + url=strings.ReplaceAll(url,"divide","marathon") + //https://marathon.jd.com/captcha.html?skuId=8654289&sn=c3f4ececd8461f0e4d7267e96a91e0e0&from=pc + url=strings.ReplaceAll(url,"user_routing","captcha.html") + return url,nil +} + +func (this *Seckill) RequestSeckillUrl() { + user:=NewUser(this.client,this.conf) + userInfo,err:=user.GetUserInfo() + if err!=nil { + log.Println("获取用户信息失败") + }else{ + log.Println("用户:"+userInfo) + } + shopTitle,err:=this.SkuTitle() + if err!=nil { + log.Println("获取商品信息失败") + }else{ + log.Println("商品名称:"+shopTitle) + } + url,_:=this.getSeckillUrl() + skuId:=this.conf.Read("config","sku_id") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Host","marathon.jd.com") + req.SetHeader("Referer",fmt.Sprintf("https://item.jd.com/%s.html",skuId)) + _,_,_=req.SetUrl(url).SetMethod("get").Send().End() +} + +func (this *Seckill) SeckillPage() { + log.Println("访问抢购订单结算页面...") + skuId:=this.conf.Read("config","sku_id") + seckillNum:=this.conf.Read("config","seckill_num") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Host","marathon.jd.com") + req.SetHeader("Referer",fmt.Sprintf("https://item.jd.com/%s.html",skuId)) + _,_,_=req.SetUrl("https://marathon.jd.com/seckill/seckill.action?skuId="+skuId+"&num="+seckillNum+"&rid="+strconv.Itoa(int(time.Now().Unix()))).SetMethod("get").Send().End() +} + +func (this *Seckill) SeckillInitInfo() (string,error) { + log.Println("获取秒杀初始化信息...") + skuId:=this.conf.Read("config","sku_id") + seckillNum:=this.conf.Read("config","seckill_num") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Host","marathon.jd.com") + req.SetData("sku",skuId) + req.SetData("num",seckillNum) + req.SetData("isModifyAddress","false") + resp,body,err:=req.SetUrl("https://marathon.jd.com/seckillnew/orderService/pc/init.action").SetMethod("post").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("初始化秒杀信息失败") + return "",errors.New("初始化秒杀信息失败") + } + return body,nil +} + +func (this *Seckill) SubmitSeckillOrder() bool { + eid:=this.conf.Read("config","eid") + fp:=this.conf.Read("config","fp") + skuId:=this.conf.Read("config","sku_id") + seckillNum:=this.conf.Read("config","seckill_num") + paymentPwd:=this.conf.Read("account","payment_pwd") + initInfo,_:=this.SeckillInitInfo() + address:=gjson.Get(initInfo,"addressList").Array() + defaultAddress:=address[0] + isinvoiceInfo:=gjson.Get(initInfo,"invoiceInfo").Exists() + invoiceTitle:="-1" + invoiceContentType:="-1" + invoicePhone:="" + invoicePhoneKey:="" + if isinvoiceInfo { + invoiceTitle=gjson.Get(initInfo,"invoiceInfo.invoiceTitle").String() + invoiceContentType=gjson.Get(initInfo,"invoiceInfo.invoiceContentType").String() + invoicePhone=gjson.Get(initInfo,"invoiceInfo.invoicePhone").String() + invoicePhoneKey=gjson.Get(initInfo,"invoiceInfo.invoicePhoneKey").String() + } + invoiceInfo:="false" + if isinvoiceInfo { + invoiceInfo="true" + } + token:=gjson.Get(initInfo,"token").String() + log.Println("提交抢购订单...") + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Host","marathon.jd.com") + req.SetHeader("Referer",fmt.Sprintf("https://marathon.jd.com/seckill/seckill.action?skuId=%s&num=%s&rid=%d",skuId,seckillNum,int(time.Now().Unix()))) + req.SetData("skuId",skuId) + req.SetData("num",seckillNum) + req.SetData("addressId",defaultAddress.Get("id").String()) + req.SetData("yuShou","true") + req.SetData("isModifyAddress","false") + req.SetData("name",defaultAddress.Get("name").String()) + req.SetData("provinceId",defaultAddress.Get("provinceId").String()) + req.SetData("cityId",defaultAddress.Get("cityId").String()) + req.SetData("countyId",defaultAddress.Get("countyId").String()) + req.SetData("townId",defaultAddress.Get("townId").String()) + req.SetData("addressDetail",defaultAddress.Get("addressDetail").String()) + req.SetData("mobile",defaultAddress.Get("mobile").String()) + req.SetData("mobileKey",defaultAddress.Get("mobileKey").String()) + req.SetData("email",defaultAddress.Get("email").String()) + req.SetData("postCode","") + req.SetData("invoiceTitle",invoiceTitle) + req.SetData("invoiceCompanyName","") + req.SetData("invoiceContent",invoiceContentType) + req.SetData("invoiceTaxpayerNO","") + req.SetData("invoiceEmail","") + req.SetData("invoicePhone",invoicePhone) + req.SetData("invoicePhoneKey",invoicePhoneKey) + req.SetData("invoice",invoiceInfo) + req.SetData("password",paymentPwd) + req.SetData("codTimeType","3") + req.SetData("paymentType","4") + req.SetData("areaCode","") + req.SetData("overseas","0") + req.SetData("phone","") + req.SetData("eid",eid) + req.SetData("fp",fp) + req.SetData("token",token) + req.SetData("pru","") + resp,body,err:=req.SetUrl("https://marathon.jd.com/seckillnew/orderService/pc/submitOrder.action?skuId="+skuId).SetMethod("post").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("抢购失败,网络错误") + if this.conf.Read("messenger","enable")=="true" && this.conf.Read("messenger","type")=="smtp" { + email:=service.NerEmail(this.conf) + _=email.SendMail([]string{this.conf.Read("messenger","email")},"茅台抢购通知","抢购失败,网络错误") + } + return false + } + if !gjson.Valid(body) { + log.Println("抢购失败,返回信息:"+common.Substr(body,0,128)) + if this.conf.Read("messenger","enable")=="true" && this.conf.Read("messenger","type")=="smtp" { + email:=service.NerEmail(this.conf) + _=email.SendMail([]string{this.conf.Read("messenger","email")},"茅台抢购通知","抢购失败,返回信息:"+common.Substr(body,0,128)) + } + return false + } + if gjson.Get(body,"success").Bool() { + orderId:=gjson.Get(body,"orderId").String() + totalMoney:=gjson.Get(body,"totalMoney").String() + payUrl:="https:"+gjson.Get(body,"pcUrl").String() + log.Println(fmt.Sprintf("抢购成功,订单号:%s, 总价:%s, 电脑端付款链接:%s",orderId,totalMoney,payUrl)) + if this.conf.Read("messenger","enable")=="true" && this.conf.Read("messenger","type")=="smtp" { + email:=service.NerEmail(this.conf) + _=email.SendMail([]string{this.conf.Read("messenger","email")},"茅台抢购通知",fmt.Sprintf("抢购成功,订单号:%s, 总价:%s, 电脑端付款链接:%s",orderId,totalMoney,payUrl)) + } + return true + }else{ + log.Println("抢购失败,返回信息:"+body) + if this.conf.Read("messenger","enable")=="true" && this.conf.Read("messenger","type")=="smtp" { + email:=service.NerEmail(this.conf) + _=email.SendMail([]string{this.conf.Read("messenger","email")},"茅台抢购通知","抢购失败,返回信息:"+body) + } + return false + } +} \ No newline at end of file diff --git a/jd_seckill/user.go b/jd_seckill/user.go new file mode 100644 index 0000000..d0399ac --- /dev/null +++ b/jd_seckill/user.go @@ -0,0 +1,118 @@ +package jd_seckill + +import ( + "../common" + "../conf" + "errors" + "fmt" + "github.com/Albert-Zhan/httpc" + "github.com/tidwall/gjson" + "log" + "net/http" + "os" + "strconv" + "time" +) + +type User struct { + client *httpc.HttpClient + conf *conf.Config +} + +func NewUser(client *httpc.HttpClient,conf *conf.Config) *User { + return &User{client: client,conf:conf } +} + +func (this *User) loginPage() { + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Connection","keep-alive") + req.SetHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") + _,_,_=req.SetUrl("https://passport.jd.com/new/login.aspx").SetMethod("get").Send().End() +} + +func (this *User) QrLogin() (string,error) { + //登录页面 + this.loginPage() + //二维码登录 + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Referer","https://passport.jd.com/new/login.aspx") + resp,err:=req.SetUrl("https://qr.m.jd.com/show?appid=133&size=300&t="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().EndFile("./","qr_code.png") + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("获取二维码失败") + return "",errors.New("获取二维码失败") + } + cookies:=resp.Cookies() + wlfstkSmdl:="" + for _,cookie:= range cookies { + if cookie.Name=="wlfstk_smdl" { + wlfstkSmdl=cookie.Value + break + } + } + log.Println("二维码获取成功,请打开京东APP扫描") + dir,_:=os.Getwd() + common.OpenImage(dir+"/qr_code.png") + return wlfstkSmdl,nil +} + +func (this *User) QrcodeTicket(wlfstkSmdl string) (string,error) { + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Referer","https://passport.jd.com/new/login.aspx") + resp,body,err:=req.SetUrl("https://qr.m.jd.com/check?appid=133&callback=jQuery"+strconv.Itoa(common.Rand(1000000,9999999))+"&token="+wlfstkSmdl+"&_="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("获取二维码扫描结果异常") + return "",errors.New("获取二维码扫描结果异常") + } + if gjson.Get(body,"code").Int()!=200 { + log.Printf("Code: %s, Message: %s",gjson.Get(body,"code").String(),gjson.Get(body,"msg").String()) + return "",errors.New(fmt.Sprintf("Code: %s, Message: %s",gjson.Get(body,"code").String(),gjson.Get(body,"msg").String())) + } + log.Println("已完成手机客户端确认") + return gjson.Get(body,"ticket").String(),nil +} + +func (this *User) TicketInfo(ticket string) (string,error) { + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Referer","https://passport.jd.com/uc/login?ltype=logout") + resp,body,err:=req.SetUrl("https://passport.jd.com/uc/qrCodeTicketValidation?t="+ticket).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("二维码信息校验失败") + return "",errors.New("二维码信息校验失败") + } + if gjson.Get(body,"returnCode").Int()==0 { + log.Println("二维码信息校验成功") + return "",nil + }else{ + log.Println("二维码信息校验失败") + return "",errors.New("二维码信息校验失败") + } +} + +func (this *User) RefreshStatus() error { + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + resp,_,err:=req.SetUrl("https://order.jd.com/center/list.action?rid="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().End() + if err==nil && resp.StatusCode==http.StatusOK { + return nil + }else{ + return errors.New("登录失效") + } +} + +func (this *User) GetUserInfo() (string,error) { + req:=httpc.NewRequest(this.client) + req.SetHeader("User-Agent",this.conf.Read("config","DEFAULT_USER_AGENT")) + req.SetHeader("Referer","https://order.jd.com/center/list.action") + resp,body,err:=req.SetUrl("https://passport.jd.com/user/petName/getUserInfoForMiniJd.action?callback="+strconv.Itoa(common.Rand(1000000,9999999))+"&_="+strconv.Itoa(int(time.Now().Unix()*1000))).SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("获取用户信息失败") + return "",errors.New("获取用户信息失败") + }else{ + b,_:=common.GbkToUtf8([]byte(gjson.Get(body,"nickName").String())) + return string(b), nil + } +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..e432876 --- /dev/null +++ b/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "./common" + "./conf" + "./jd_seckill" + "errors" + "fmt" + "github.com/Albert-Zhan/httpc" + "github.com/tidwall/gjson" + "log" + "net/http" + "os" + "runtime" + "sync" + "time" +) + +var client *httpc.HttpClient + +var cookieJar *httpc.CookieJar + +var config *conf.Config + +var wg *sync.WaitGroup + +func init() { + //客户端设置初始化 + client=httpc.NewHttpClient() + cookieJar=httpc.NewCookieJar() + client.SetCookieJar(cookieJar) + client.SetRedirect(func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }) + //配置文件初始化 + confFile:="./conf.ini" + if !common.Exists(confFile) { + log.Println("配置文件不存在,程序退出") + os.Exit(0) + } + config=&conf.Config{} + config.InitConfig(confFile) + + wg=new(sync.WaitGroup) + wg.Add(1) +} + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + //用户登录 + user:=jd_seckill.NewUser(client,config) + wlfstkSmdl,err:=user.QrLogin() + if err!=nil{ + os.Exit(0) + } + ticket:="" + for { + ticket,err=user.QrcodeTicket(wlfstkSmdl) + if err==nil && ticket!=""{ + break + } + time.Sleep(2*time.Second) + } + _,err=user.TicketInfo(ticket) + if err==nil { + log.Println("登录成功") + //刷新用户状态和获取用户信息 + if status:=user.RefreshStatus();status==nil { + userInfo,_:=user.GetUserInfo() + log.Println("用户:"+userInfo) + //开始预约,预约过的就重复预约 + seckill:=jd_seckill.NewSeckill(client,config) + seckill.MakeReserve() + //等待抢购/开始抢购 + nowLocalTime:=time.Now().UnixNano()/1e6 + jdTime,_:=getJdTime() + buyDate:=config.Read("config","buy_time") + loc, _ := time.LoadLocation("Local") + t,_:=time.ParseInLocation("2006-01-02 15:04:05",buyDate,loc) + buyTime:=t.UnixNano()/1e6 + diffTime:=nowLocalTime-jdTime + log.Println(fmt.Sprintf("正在等待到达设定时间:%s,检测本地时间与京东服务器时间误差为【%d】毫秒",buyDate,diffTime)) + timerTime:=(buyTime+diffTime)-jdTime + if timerTime<=0 { + log.Println("请设置抢购时间") + os.Exit(0) + } + time.Sleep(time.Duration(timerTime)*time.Millisecond) + //开启任务 + log.Println("时间到达,开始执行……") + start(seckill,5) + wg.Wait() + } + }else{ + log.Println("登录失败") + } +} + +func getJdTime() (int64,error) { + req:=httpc.NewRequest(client) + resp,body,err:=req.SetUrl("https://a.jd.com//ajax/queryServerData.html").SetMethod("get").Send().End() + if err!=nil || resp.StatusCode!=http.StatusOK { + log.Println("获取京东服务器时间失败") + return 0,errors.New("获取京东服务器时间失败") + } + return gjson.Get(body,"serverTime").Int(),nil +} + +func start(seckill *jd_seckill.Seckill,taskNum int) { + for i:=1;i<=taskNum;i++ { + go func(seckill *jd_seckill.Seckill) { + seckill.RequestSeckillUrl() + seckill.SeckillPage() + seckill.SubmitSeckillOrder() + }(seckill) + } +} \ No newline at end of file diff --git a/service/email.go b/service/email.go new file mode 100644 index 0000000..387f9a9 --- /dev/null +++ b/service/email.go @@ -0,0 +1,34 @@ +package service + +import ( + "../conf" + "gopkg.in/gomail.v2" + "strconv" +) + +type Email struct { + host string + port string + user string + pass string +} + +func NerEmail(conf *conf.Config) *Email { + host:=conf.Read("smtp","email_host") + port:=conf.Read("smtp","port") + user:=conf.Read("smtp","email_user") + pass:=conf.Read("smtp","email_pwd") + return &Email{host: host,port: port,user: user,pass: pass} +} + +func (this *Email) SendMail(mailTo []string,subject,body string) error { + port, _ := strconv.Atoi(this.port) + m:=gomail.NewMessage() + m.SetHeader("From", "<" + this.user + ">") + m.SetHeader("To", mailTo...) + m.SetHeader("Subject",subject) + m.SetBody("text/html",body) + d := gomail.NewDialer(this.host,port,this.user,this.pass) + err:=d.DialAndSend(m) + return err +} \ No newline at end of file