diff --git a/.gitignore b/.gitignore index 95f82d7..0369067 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ # Dependency directories (remove the comment below to include it) # vendor/ +/*.log +/.idea +/qr_code.png +/cookie.txt +*.bak diff --git a/README.md b/README.md index 7d15fb5..10bca33 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ go get github.com/ztino/jd_seckill ## 待办 -- 日志目前还未输出到本地日志文件保存 -- 自动化抢购支持,无需设置抢购时间 - 跨平台桌面端支持,打算使用:https://github.com/therecipe/qt ## 使用 @@ -44,7 +42,8 @@ jd_seckill login ### 自动获取eid,fp -> ⚠依赖谷歌浏览器,请安装谷歌浏览器,获取到的eid和fp请手动填入配置文件 +> ⚠依赖谷歌浏览器,请安装谷歌浏览器,windows下请将安全目录加入系统变量Path +> ⚠【重要提醒】自动获取eid和fp期间,建议鼠标跟随页面跳转,滑动到【加入购物车】【去购车结算】【去结算】按钮,但不要点击,可以提升获取成功率!(经验之谈!) 执行以下命令按照提示操作: ```shell @@ -113,9 +112,9 @@ $ zbarimg qr_code.png > qrcode.txt && qrencode -r qrcode.txt -o - -t UTF8 # 解 ## 抢购流程/抢购结果 -- 程序开始抢购总时间为两分钟,不管有无抢购成功,都会停止,抢购详情请查阅日志和自己配置的第三方推送服务。 +- 程序开始抢购总时间为两分钟,不管有无抢购成功,都会停止,抢购详情请查阅日志和自己配置的第三方推送服务(目前支持:钉钉机器人、SMTP邮件、Server酱) -- 第二天抢购需要修改抢购时间和重新开始抢购任务。 +- 抢购开始时间设定,格式:09:59:59,如果小于当前时间,则表示明天这个时间点开始;大于当前时间,则为今天 - 先写这么多。。。 diff --git a/cmd/jdTdudfp.go b/cmd/jdTdudfp.go index 329efd3..e55a494 100644 --- a/cmd/jdTdudfp.go +++ b/cmd/jdTdudfp.go @@ -2,16 +2,20 @@ package cmd import ( "context" + "fmt" "github.com/chromedp/cdproto/cdp" "github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/target" "github.com/chromedp/chromedp" "github.com/spf13/cobra" "github.com/tidwall/gjson" + "github.com/unknwon/goconfig" "github.com/ztino/jd_seckill/common" "github.com/ztino/jd_seckill/jd_seckill" "log" "net/url" + "os" + "strconv" "time" ) @@ -22,26 +26,26 @@ func init() { var jdTdudfpCmd = &cobra.Command{ Use: "jdTdudfp", Short: "auto get jd eid and fp", - Run: startJdTdudfp, + Run: startJdTdudfp, } -func startJdTdudfp(cmd *cobra.Command, args []string) { - session:=jd_seckill.NewSession(common.CookieJar) - err:=session.CheckLoginStatus() - if err!=nil { +func startJdTdudfp(cmd *cobra.Command, args []string) { + session := jd_seckill.NewSession(common.CookieJar) + err := session.CheckLoginStatus() + if err != nil { log.Println("自动获取eid和fp失败,请重新登录") - }else { - log.Println("开始自动获取eid和fp,如遇卡住请结束进程,重新启动") + } else { + retryTimes, _ := strconv.Atoi(common.Config.MustValue("config", "retry_times", "5")) + options := []chromedp.ExecAllocatorOption{ - chromedp.Flag("headless", false), - //chromedp.Flag("blink-settings", "imagesEnabled=false"), - chromedp.Flag("start-maximized", true), - chromedp.Flag("no-sandbox",true), - chromedp.Flag("disable-setuid-sandbox",true), - chromedp.Flag("no-default-browser-check",true), - chromedp.Flag("disable-plugins",true), - chromedp.WindowSize(1920,1080), - chromedp.UserAgent(common.Config.MustValue("config","default_user_agent","")), + chromedp.Flag("headless", false), //debug使用 + chromedp.Flag("blink-settings", "imagesEnabled=false"), //禁用图片加载 + chromedp.Flag("start-maximized", true), //最大化窗口 + chromedp.Flag("no-sandbox", true), //禁用沙盒, 性能优先 + chromedp.Flag("disable-setuid-sandbox", true), //禁用setuid沙盒, 性能优先 + chromedp.Flag("no-default-browser-check", true), //不检查默认浏览器 + chromedp.Flag("disable-plugins", true), //禁用扩展 + chromedp.UserAgent(common.Config.MustValue("config", "default_user_agent", "")), } options = append(chromedp.DefaultExecAllocatorOptions[:], options...) @@ -49,9 +53,10 @@ func startJdTdudfp(cmd *cobra.Command, args []string) { defer cc() ctx, cancel := chromedp.NewContext(c) - ch := addNewTabListener(ctx) + _ = addNewTabListener(ctx) defer cancel() + //设置cookie u, _ := url.Parse("http://jd.com") cookies := common.CookieJar.Cookies(u) err = chromedp.Run(ctx, @@ -67,61 +72,74 @@ func startJdTdudfp(cmd *cobra.Command, args []string) { } return nil }), - chromedp.Navigate("https://jd.com"), - chromedp.Sleep(2*time.Second), - chromedp.Click(".cate_menu_lk"), - chromedp.Sleep(2*time.Second), }, ) if err != nil { + log.Println("设置cookie出错了") log.Fatal(err) } - newCtx, cancel2 := chromedp.NewContext(ctx, chromedp.WithTargetID(<-ch)) - ch2 := addNewTabListener(newCtx) - defer cancel2() - - err = chromedp.Run(newCtx, - chromedp.Click(".goods_item_link"), - chromedp.Sleep(4*time.Second), + RETRY: + retryTimes-- + log.Println("【重要提醒】自动获取eid和fp期间,建议鼠标跟随页面跳转,滑动到【加入购物车】【去购车结算】【去结算】按钮,但不要点击,可以提升获取成功率!") + log.Println(fmt.Sprintf("开始自动获取eid和fp,如遇卡住请耐心等待,重试次数剩余: %v 次", retryTimes)) + var res []byte + testSkuId := common.Config.MustValue("config", "test_sku_id", "") + err = chromedp.Run(ctx, + chromedp.Tasks{ + chromedp.Navigate(fmt.Sprintf("http://item.jd.com/%s.html", testSkuId)), + chromedp.WaitVisible("#InitCartUrl"), //加入购物车 + chromedp.Sleep(2 * time.Second), + chromedp.Click("#InitCartUrl"), + chromedp.WaitVisible(".btn-addtocart"), //去购车结算 + chromedp.Sleep(2 * time.Second), + chromedp.Click(".btn-addtocart"), + chromedp.WaitVisible(".common-submit-btn"), //去结算 + chromedp.Sleep(2 * time.Second), + chromedp.Click(".common-submit-btn"), + chromedp.Sleep(3 * time.Second), + chromedp.Evaluate("_JdTdudfp", &res), + }, ) if err != nil { + log.Println("chromedp 出错了") log.Fatal(err) } - newCtx2, cancel3 := chromedp.NewContext(ctx, chromedp.WithTargetID(<-ch2)) - defer cancel3() + value := string(res) + if !gjson.Valid(value) || gjson.Get(value, "eid").String() == "" || gjson.Get(value, "fp").String() == "" { + log.Println("获取失败,请重新尝试,返回信息:" + value) + if retryTimes > 0 { + goto RETRY + } + } else { + eid := gjson.Get(value, "eid").String() + fp := gjson.Get(value, "fp").String() + log.Println("eid:" + eid) + log.Println("fp:" + fp) - var res []byte - err = chromedp.Run(newCtx2, - chromedp.Click("#InitCartUrl"), - chromedp.Sleep(2*time.Second), - chromedp.Click(".btn-addtocart"), - chromedp.Sleep(2*time.Second), - chromedp.Click(".common-submit-btn"), - chromedp.Sleep(3*time.Second), - chromedp.Evaluate("_JdTdudfp", &res), - ) - if err != nil { - log.Fatal(err) - } + //修改配置文件 + confFile := "./conf.ini" + cfg, err := goconfig.LoadConfigFile(confFile) + if err != nil { + log.Println("配置文件不存在,程序退出") + os.Exit(0) + } - value:=string(res) - if !gjson.Valid(value) || gjson.Get(value,"eid").String()=="" || gjson.Get(value,"fp").String()=="" { - log.Println("获取失败,请重新尝试,返回信息:"+value) - }else{ - log.Println("获取成功,请手动填入配置文件") - log.Println("eid:"+gjson.Get(value,"eid").String()) - log.Println("fp:"+gjson.Get(value,"fp").String()) + cfg.SetValue("config", "eid", eid) + cfg.SetValue("config", "fp", fp) + if err := goconfig.SaveConfigFile(cfg, confFile); err != nil { + log.Println("保存配置文件失败,请手动填入配置文件") + } + + log.Println("eid, fp参数已经自动填入配置文件") } + } } func addNewTabListener(ctx context.Context) <-chan target.ID { -/* mux := http.NewServeMux() - ts := httptest.NewServer(mux) - defer ts.Close()*/ return chromedp.WaitNewTarget(ctx, func(info *target.Info) bool { return true }) -} \ No newline at end of file +} diff --git a/common/lib.go b/common/lib.go index 6a6a2d0..58aa2fe 100644 --- a/common/lib.go +++ b/common/lib.go @@ -108,4 +108,8 @@ func OpenImage(file string) { _=cmd.Start() } } -} \ No newline at end of file +} + +func Hour2Unix(hour string) (time.Time, error) { + return time.ParseInLocation(DateTimeFormatStr, time.Now().Format(DateFormatStr) + " " + hour, time.Local) +} diff --git a/common/var.go b/common/var.go index 023779c..69a776f 100644 --- a/common/var.go +++ b/common/var.go @@ -5,9 +5,12 @@ import ( "github.com/unknwon/goconfig" ) -const SoftName = "jd_seckill" - -const Version = "0.1.7" +const ( + SoftName = "jd_seckill" + Version = "0.1.7" + DateTimeFormatStr = "2006-01-02 15:04:05" + DateFormatStr = "2006-01-02" +) var Client *httpc.HttpClient diff --git a/conf.ini b/conf.ini index d258da9..f9e2541 100644 --- a/conf.ini +++ b/conf.ini @@ -4,13 +4,16 @@ # 随意填写可能导致订单无法提交等问题 eid = fp = +# 自动获取eid, fp的重试次数,测试商品sku_id +retry_times = 5 +test_sku_id = 100016034372 # 商品id # 已经是茅台的sku_id了 sku_id = 100012043978 # 抢购数量 seckill_num = 2 -# 抢购开始时间设定 2021-01-01 09:59:59 -buy_time = 2021-01-01 09:59:59 +# 抢购开始时间设定,格式:09:59:59,如果小于当前时间,则表示明天这个时间点开始;大于当前时间,则为今天。 +buy_time = 09:59:59 # 抢购总时间,单位:分钟,默认两分钟 seckill_time = # 抢购任务数量,默认5个 @@ -50,4 +53,11 @@ port = # 邮箱地址 xxxxxxxx@xx.com email_user = # 邮箱授权码(并不一定是邮箱密码) xxxxxxxxxxxxxxxx -email_pwd = \ No newline at end of file +email_pwd = + +#钉钉机器人配置 +[dingtalk] +# 机器人的Webhook地址(https://oapi.dingtalk.com/robot/send?access_token=XXXXXX)的access_token +access_token = +# 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串 +secret = \ No newline at end of file diff --git a/go.mod b/go.mod index e87c3fc..2d16b81 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,18 @@ go 1.15 require ( github.com/Albert-Zhan/httpc v0.0.0-20190712132051-aed72924b5e6 github.com/PuerkitoBio/goquery v1.6.0 + github.com/blinkbean/dingtalk v0.0.0-20201231030509-45a553a84503 github.com/chromedp/cdproto v0.0.0-20201204063249-be40c824ad18 github.com/chromedp/chromedp v0.5.4 github.com/gobwas/httphead v0.1.0 // indirect + github.com/json-iterator/go v1.1.10 // indirect github.com/spf13/cobra v1.1.1 github.com/tidwall/gjson v1.6.7 github.com/unknwon/goconfig v0.0.0-20200908083735-df7de6a44db8 + golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect golang.org/x/text v0.3.4 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/jd_seckill/seckill.go b/jd_seckill/seckill.go index e72d9bb..74e8524 100644 --- a/jd_seckill/seckill.go +++ b/jd_seckill/seckill.go @@ -63,7 +63,9 @@ func (this *Seckill) MakeReserve() { reserveUrl := gjson.Get(body, "url").String() req = httpc.NewRequest(this.client) _, _, _ = req.SetUrl("https:" + reserveUrl).SetMethod("get").Send().End() - log.Println("预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约") + msg := "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约![我的预约](https://yushou.jd.com/member/qualificationList.action)" + _ = service.SendMessage(this.conf, "茅台抢购通知", msg) + log.Println(msg) } } diff --git a/main.go b/main.go index 1dd52b9..097731c 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,26 @@ import ( "github.com/unknwon/goconfig" "github.com/ztino/jd_seckill/cmd" "github.com/ztino/jd_seckill/common" + "io" "log" "os" "runtime" + "time" ) func init() { + //将日志同时输出到控制台和文件 + file := "./" + "jd_seckill_" + time.Now().Format("20060102") + ".log" + logFile, logErr := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if logErr != nil { + panic(logErr) + } + defer logFile.Close() + mw := io.MultiWriter(os.Stdout, logFile) + log.SetOutput(mw) + log.SetPrefix("[jd_seckill]") + log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC) + //客户端设置初始化 common.Client=httpc.NewHttpClient() common.CookieJar=httpc.NewCookieJar() @@ -26,6 +40,7 @@ func init() { //抢购状态管道 common.SeckillStatus=make(chan bool) + log.Println("jd_seckill 程序启动成功,祝您成功!") } func main() { diff --git a/service/dingtalk.go b/service/dingtalk.go new file mode 100644 index 0000000..7de4c74 --- /dev/null +++ b/service/dingtalk.go @@ -0,0 +1,35 @@ +package service + +import ( + "github.com/blinkbean/dingtalk" + "github.com/unknwon/goconfig" + "log" +) + +type Dingtalk struct { + conf *goconfig.ConfigFile +} + +func NewDingtalk(conf *goconfig.ConfigFile) *Dingtalk { + return &Dingtalk{conf: conf} +} + +func (this *Dingtalk) Send(title, msg string) error { + cli := dingtalk.InitDingTalkWithSecret( + this.conf.MustValue("dingtalk", "access_token", ""), + this.conf.MustValue("dingtalk", "secret", ""), + ) + markdown := []string{ + "### " + title, + "---------", + msg, + } + err := cli.SendMarkDownMessageBySlice(title, markdown) + if err != nil { + log.Println(err) + } else { + log.Println("钉钉机器人推送成功") + } + + return nil +} diff --git a/service/service.go b/service/service.go index 83b17af..35c6d4c 100644 --- a/service/service.go +++ b/service/service.go @@ -2,20 +2,26 @@ package service import "github.com/unknwon/goconfig" -func SendMessage(conf *goconfig.ConfigFile,title,msg string) error { - if conf.MustValue("messenger","enable","false")=="true" { +func SendMessage(conf *goconfig.ConfigFile, title, msg string) error { + if conf.MustValue("messenger", "enable", "false") == "true" { + //钉钉机器人 + if conf.MustValue("messenger", "type", "none") == "dingtalk" { + dingtalk := NewDingtalk(conf) + err := dingtalk.Send(title, msg) + return err + } //邮件发送 - if conf.MustValue("messenger","type","none")=="smtp" { - email:=NewEmail(conf) - err:=email.Send([]string{conf.MustValue("messenger","email","")},title,msg) + if conf.MustValue("messenger", "type", "none") == "smtp" { + email := NewEmail(conf) + err := email.Send([]string{conf.MustValue("messenger", "email", "")}, title, msg) return err } //Server酱推送 - if conf.MustValue("messenger","type","none")=="wechat" { - wechat:=NewWechat(conf) - err:=wechat.Send(title,msg) + if conf.MustValue("messenger", "type", "none") == "wechat" { + wechat := NewWechat(conf) + err := wechat.Send(title, msg) return err } } return nil -} \ No newline at end of file +}