ソースを参照

Merge branch 'main' of https://github.com/ztino/jd_seckill into main

 Conflicts:
	conf.ini
main
燕鹏 4年前
コミット
367e5e9fd8
  1. 28
      .github/workflows/build-release.yml
  2. 43
      .goreleaser.yml
  3. 22
      README.md
  4. 41
      cmd/jdTdudfp.go
  5. 14
      cmd/reserve.go
  6. 83
      cmd/seckill.go
  7. 2
      cmd/version.go
  8. 31
      common/lib.go
  9. 12
      conf.ini
  10. 128
      jd_seckill/seckill.go

28
.github/workflows/build-release.yml

@ -0,0 +1,28 @@
name: goreleaser
on:
push:
tag:
- "*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

43
.goreleaser.yml

@ -0,0 +1,43 @@
# This is an example .goreleaser.yml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod download
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
- GOPROXY=https://goproxy.io,direct
binary: jd_seckill
ldflags:
- -s -w
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
archives:
-
replacements:
darwin: Darwin
linux: Linux
windows: Windows
amd64: amd64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

22
README.md

@ -1,6 +1,9 @@
helloworld
=======
> ⚠ 此项目是[python jd_seckill](https://github.com/huanghyw/jd_seckill) 的go版本实现,旨在降低使用门栏和相互学习而创建。
### 不定时开放群链接:https://t.me/joinchat/GsDnhtkdKJ4nbwJh
> ⚠ 此项目是[python jd_seckill](https://github.com/huanghyw/jd_seckill) 的go版本实现,旨在降低使用门槛和相互学习而创建。
**go版本的jd_seckill,京东抢茅台神器,支持跨平台,使用者请在发布页下载可执行文件,欢迎pr。**
@ -25,7 +28,6 @@ go get github.com/ztino/jd_seckill
```
## 待办
- 自动化预约抢购支持,程序自动去茅台页面获取下一次抢购时间
- 跨平台桌面端支持,打算使用:https://github.com/therecipe/qt
## 使用
@ -118,6 +120,22 @@ jd_seckill version
(8)通知配置
> 目前支持email,wechat,dingtalk,具体可查看配置文件
## Linux 无图形界面获取 eid 与 fp 方法参考
(1) 安装无头 chrome
```shell
sudo apt install ./google-chrome-stable_current_amd64.deb
sudo apt-get -y install xorg xvfb gtk2-engines-pixbuf
sudo apt-get -y install dbus-x11 xfonts-base xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable
sudo apt-get install -y xvfb
Xvfb -ac :99 -screen 0 1280x1024x16 & export DISPLAY=:99
```
(2) 执行获取 eid 与 fp
```shell
#参数--good_url商品链接必须设置,链接地址是一个可以加入购物车的商品
jd_seckill jdTdudfp --good_url https://item.jd.com/100007959916.html
```
## 感谢
##### 非常感谢原作者 https://github.com/zhou-xiaojun/jd_mask 提供的代码
##### 也非常感谢 https://github.com/wlwwu/jd_maotai 进行的优化

41
cmd/jdTdudfp.go

@ -60,6 +60,14 @@ func startJdTdudfp(cmd *cobra.Command, args []string) {
//商品链接
good_url,_:=cmd.Flags().GetString("good_url")
//返回的eid和fp
returnEid:=""
returnFp:=""
//获取到的eid和fp
eid := ""
fp := ""
var res []byte
err = chromedp.Run(ctx,
chromedp.Tasks{
@ -88,6 +96,8 @@ func startJdTdudfp(cmd *cobra.Command, args []string) {
chromedp.Click(".common-submit-btn"),
chromedp.Sleep(3 * time.Second),
chromedp.Evaluate("_JdTdudfp", &res),
chromedp.Evaluate("_JdEid", &eid),
chromedp.Evaluate("_JdJrTdRiskFpInfo", &fp),
)
if err != nil {
log.Println("chromedp 出错了")
@ -95,13 +105,23 @@ func startJdTdudfp(cmd *cobra.Command, args []string) {
}
value := string(res)
if !gjson.Valid(value) || gjson.Get(value, "eid").String() == "" || gjson.Get(value, "fp").String() == "" {
//判断_JdTdudfp是否能获取到eid和fp,如果不能去获取_JdEid和_JdJrTdRiskFpInfo获取到的值
if gjson.Valid(value) && gjson.Get(value, "eid").String() != "" && gjson.Get(value, "fp").String() != "" {
returnEid = gjson.Get(value, "eid").String()
returnFp = gjson.Get(value, "fp").String()
}else{
if eid!="" && fp!=""{
returnEid=eid
returnFp=fp
}
}
//eid,fp合法性判断
if returnEid=="" || returnFp=="" {
log.Println("获取失败,请重新尝试,返回信息:" + value)
} else {
eid := gjson.Get(value, "eid").String()
fp := gjson.Get(value, "fp").String()
log.Println("eid:" + eid)
log.Println("fp:" + fp)
}else{
log.Println("eid:" + returnEid)
log.Println("fp:" + returnFp)
//修改配置文件
confFile := "./conf.ini"
@ -111,14 +131,13 @@ func startJdTdudfp(cmd *cobra.Command, args []string) {
os.Exit(0)
}
cfg.SetValue("config", "eid", eid)
cfg.SetValue("config", "fp", fp)
cfg.SetValue("config", "eid", returnEid)
cfg.SetValue("config", "fp", returnFp)
if err := goconfig.SaveConfigFile(cfg, confFile); err != nil {
log.Println("保存配置文件失败,请手动填入配置文件")
}else{
log.Println("eid, fp参数已经自动填入配置文件")
}
log.Println("eid, fp参数已经自动填入配置文件")
}
}
}

14
cmd/reserve.go

@ -14,17 +14,17 @@ func init() {
var reserveCmd = &cobra.Command{
Use: "reserve",
Short: "Open JD Moutai buying appointment",
Run: startReserve,
Run: startReserve,
}
func startReserve(cmd *cobra.Command, args []string) {
session:=jd_seckill.NewSession(common.CookieJar)
err:=session.CheckLoginStatus()
if err!=nil {
func startReserve(cmd *cobra.Command, args []string) {
session := jd_seckill.NewSession(common.CookieJar)
err := session.CheckLoginStatus()
if err != nil {
log.Println("预约失败,请重新登录")
}else{
} else {
//开始预约,预约过的就重复预约
seckill:=jd_seckill.NewSeckill(common.Client,common.Config)
seckill := jd_seckill.NewSeckill(common.Client, common.Config)
seckill.MakeReserve()
}
}

83
cmd/seckill.go

@ -2,7 +2,6 @@ package cmd
import (
"errors"
"fmt"
"github.com/Albert-Zhan/httpc"
"github.com/spf13/cobra"
"github.com/tidwall/gjson"
@ -11,62 +10,88 @@ import (
"github.com/ztino/jd_seckill/log"
"net/http"
"os"
"regexp"
"strconv"
"time"
)
func init() {
rootCmd.AddCommand(seckillCmd)
seckillCmd.Flags().BoolP("run","r",false,"Run directly without waiting for the time to buy")
seckillCmd.Flags().BoolP("run", "r", false, "Run directly without waiting for the time to buy")
}
var seckillCmd = &cobra.Command{
Use: "seckill",
Short: "Start panic buying procedure",
Run: startSeckill,
Run: startSeckill,
}
func startSeckill(cmd *cobra.Command, args []string) {
func startSeckill(cmd *cobra.Command, args []string) {
//获取是否直接运行抢购
isRun,_:=cmd.Flags().GetBool("run")
session:=jd_seckill.NewSession(common.CookieJar)
err:=session.CheckLoginStatus()
if err!=nil {
isRun, _ := cmd.Flags().GetBool("run")
session := jd_seckill.NewSession(common.CookieJar)
err := session.CheckLoginStatus()
if err != nil {
log.Println("抢购失败,请重新登录")
}else{
} else {
//活跃用户会话,当会话失效自动退出程序
user:=jd_seckill.NewUser(common.Client,common.Config)
user := jd_seckill.NewUser(common.Client, common.Config)
go KeepSession(user)
seckill := jd_seckill.NewSeckill(common.Client, common.Config)
//直接运行抢购跳过等待抢购时间
if !isRun {
//获取本地时间与京东云端时间差
diffTime := seckill.GetDiffTime()
//获取抢购时间
buyDate := common.Config.MustValue("config", "buy_time", "")
buyTimeReg := regexp.MustCompile(`(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})`)
buyTimeArr := buyTimeReg.FindAllString(buyDate, 1)
if len(buyTimeArr) == 1 {
buyDate = buyTimeArr[0]
} else {
_, buyTimeArr, err := seckill.GetWareBusiness()
if err != nil || len(buyTimeArr) != 2 {
log.Println("请设置conf.ini中的抢购时间(buy_time)")
os.Exit(0)
}
buyDate = buyTimeArr[0] + ":00"
}
//计算抢购时间
nowLocalTime:=time.Now().UnixNano()/1e6
jdTime,_:=GetJdTime()
buyDate:=common.Config.MustValue("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("请设置抢购时间")
t, _ := time.ParseInLocation("2006-01-02 15:04:05", buyDate, loc)
buyTime := t.UnixNano()/1e6 + diffTime
//抢购总时间读取配置文件
str := common.Config.MustValue("config", "seckill_time", "2")
seckillTime, err := strconv.Atoi(str)
if err != nil {
seckillTime = 2
}
timerTime := buyTime - time.Now().UnixNano()/1e6
if timerTime >= 0 { //等待抢购
log.Println("还没到达抢购时间:", buyDate, ",等待中...")
time.Sleep(time.Duration(timerTime) * time.Millisecond)
log.Println("时间到达,开始抢购……")
} else if timerTime <= int64(-seckillTime*6e4) {
log.Println("已经超过抢购时间(", buyDate, ")不止", seckillTime, "分钟,败局已定,下次请早!")
os.Exit(0)
} else {
log.Println("您已经错过抢购时间,但还在抢购总时间(", seckillTime, "分钟)内,直接执行抢购,祝您好运!")
}
//等待抢购
time.Sleep(time.Duration(timerTime)*time.Millisecond)
//开始抢购
log.Println("时间到达,开始执行……")
}else{
} else {
log.Println("开始执行……")
}
seckill:=jd_seckill.NewSeckill(common.Client,common.Config)
//开启抢购任务,第二个参数为开启几个协程
//怕封号的可以减少协程数量,相反抢到的成功率也减低了
//抢购任务数读取配置文件
str:=common.Config.MustValue("config","task_num","5")
taskNum,_:=strconv.Atoi(str)
Start(seckill,taskNum)
str := common.Config.MustValue("config", "task_num", "5")
taskNum, _ := strconv.Atoi(str)
Start(seckill, taskNum)
}
}

2
cmd/version.go

@ -15,6 +15,6 @@ var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of jd_seckill",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(fmt.Sprintf("%s version %s %s %s/%s",common.SoftName,common.SoftName,common.Version,runtime.GOOS,runtime.GOARCH))
fmt.Println(fmt.Sprintf("%s version %s %s %s/%s", common.SoftName, common.SoftName, common.Version, runtime.GOOS, runtime.GOARCH))
},
}

31
common/lib.go

@ -101,13 +101,23 @@ func Exists(path string) bool {
}
func OpenImage(qrPath string) {
if runtime.GOOS == "windows" {//windows
cmd := exec.Command("cmd", "/k", "start", qrPath)
if runtime.GOOS == "windows" { //windows
cmd := exec.Command("cmd", "/c", "rundll32.exe", "C:\\Windows\\System32\\shimgvw.dll,ImageView_FullscreenA", qrPath)
_ = cmd.Start()
}else if runtime.GOOS == "darwin" {//Macos
//扫码后二维码自动删除,自动关闭照片查看器
go func() {
for {
time.Sleep(time.Duration(1) * time.Second)
if !Exists(qrPath) {
_ = exec.Command("taskkill", "/F", "/T", "/PID", fmt.Sprint(cmd.Process.Pid)).Run()
break
}
}
}()
} else if runtime.GOOS == "darwin" { //Macos
cmd := exec.Command("open", qrPath)
_ = cmd.Start()
}else{
} else {
//linux或者其他系统
file, _ := os.Open(qrPath)
img, _, _ := image.Decode(file)
@ -119,3 +129,16 @@ func OpenImage(qrPath string) {
fmt.Println(qr.ToSmallString(false))
}
}
//指定位数随机数
func RandomNumber(len int) string {
var container string
var str = "0123456789"
b := bytes.NewBufferString(str)
length := b.Len()
rand.Seed(time.Now().UnixNano())
for i := 0; i < len; i++ {
container += string(str[rand.Intn(length)])
}
return container
}

12
conf.ini

@ -9,16 +9,16 @@ fp = 6daf4b091e8dc5d67632c682ac086a81
sku_id = 100012043978
# 抢购数量
seckill_num = 2
# 抢购开始时间设定 2021-01-01 09:59:59
buy_time = 2021-01-08 09:59:55
# 抢购开始时间设定 2021-01-01 09:59:59 (PS.预约成功后会自动更新)
buy_time = 2021-01-01 09:59:59
# 抢购总时间,单位:分钟,默认两分钟
seckill_time =
# 抢购任务数量,默认6
# 抢购任务数量,默认5
task_num =
# 每次抢购间隔时间,单位:毫秒,默认1500毫秒,每1000毫秒等于1秒
ticker_time = 1000
ticker_time =
# 默认UA
default_user_agent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
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]
@ -36,7 +36,7 @@ enable = false
# 目前支持smtp邮箱推送和Server酱推送服务,选值smtp,wechat,dingtalk
type = none
# 邮箱推送消息接收人
email =
email =
#Server酱推送key,当type为wechat有效
server_chan_sckey =

128
jd_seckill/seckill.go

@ -11,6 +11,9 @@ import (
"github.com/ztino/jd_seckill/log"
"github.com/ztino/jd_seckill/service"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
@ -42,7 +45,103 @@ func (this *Seckill) SkuTitle() (string, error) {
return strings.TrimSpace(doc.Find(".sku-name").Text()), nil
}
func (this *Seckill) GetDiffTime() int64 {
log.Println("获取本地时间与京东云端时间差")
localTime := time.Now().UnixNano() / 1e6
log.Println("本地系统时间:", time.Unix(0, localTime*1e6))
jdTime := localTime
req := httpc.NewRequest(common.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("获取京东服务器时间失败,以本地时间为准")
} else {
jdTime = gjson.Get(body, "serverTime").Int()
}
log.Println("京东云端时间:", time.Unix(0, jdTime*1e6))
delayTime := time.Now().UnixNano()/1e6 - localTime
log.Println("网络请求延时:", delayTime, "ms")
diffTime := localTime - jdTime + delayTime/2
log.Println("实际时间误差:", diffTime, "ms (本地时间-京东云端时间+网络请求延时/2)")
return diffTime
}
func (this *Seckill) GetWareBusiness() ([]string, []string, error) {
log.Println("获取商品的预约时间、抢购时间")
skuId := this.conf.MustValue("config", "sku_id", "") //商品ID
cat := "12259,12260,9435" //分类路径,TODO:适配其他商品时要调整
area := "16_1303_3484_0" //配送至,TODO:适配其他商品时要调整成购买者实际地区
shopId := "1000085463" //卖家ID
venderId := "1000085463" //供应商ID
paramJson := "{\"platform2\":\"1\",\"specialAttrStr\":\"p0pp1pppppppppppppppp\",\"skuMarkStr\":\"00\"}" //TODO:不知道干嘛用的?
num := this.conf.MustValue("config", "seckill_num", "1") //购买数量
req := httpc.NewRequest(this.client)
req.SetHeader("User-Agent", this.getUserAgent())
req.SetHeader("Referer", fmt.Sprintf("https://item.jd.com/%s.html", skuId))
resp, body, err := req.SetUrl(fmt.Sprintf("https://item-soa.jd.com/getWareBusiness?callback=jQuery%s&skuId=%s&cat=%s&area=%s&shopId=%s&venderId=%s&paramJson=%s&num=%s&_=%s",
common.RandomNumber(7),
skuId,
cat,
area,
shopId,
venderId,
url.QueryEscape(paramJson),
num,
strconv.Itoa(int(time.Now().Unix()*1000)),
)).SetMethod("get").Send().End()
var yuyueTimeArr []string
var buyTimeArr []string
if err != nil || resp.StatusCode != http.StatusOK {
log.Println("获取商品详情失败", resp, body, err)
return yuyueTimeArr, buyTimeArr, errors.New("访问商品详情失败")
}
if !gjson.Get(body, "yuyueInfo").Exists() || !gjson.Get(body, "yuyueInfo.yuyueTime").Exists() || !gjson.Get(body, "yuyueInfo.buyTime").Exists() {
log.Println("获取商品预约信息失败", body)
return yuyueTimeArr, buyTimeArr, errors.New("获取商品预约信息失败")
}
yuyueTime := gjson.Get(body, "yuyueInfo.yuyueTime").String()
buyTime := gjson.Get(body, "yuyueInfo.buyTime").String()
log.Println("预约起止时间:", yuyueTime)
log.Println("购买起止时间:", buyTime)
reg := regexp.MustCompile(`(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2})`)
yuyueTimeArr = reg.FindAllString(yuyueTime, 2)
buyTimeArr = reg.FindAllString(buyTime, 2)
return yuyueTimeArr, buyTimeArr, nil
}
func (this *Seckill) MakeReserve() {
yuyueTimeArr, buyTimeArr, err := this.GetWareBusiness()
if err == nil && len(yuyueTimeArr) == 2 {
diffTime := this.GetDiffTime()
loc, _ := time.LoadLocation("Local")
yuyueTimeBegin, _ := time.ParseInLocation(common.DateTimeFormatStr, yuyueTimeArr[0]+":00", loc)
yuyueTimeEnd, _ := time.ParseInLocation(common.DateTimeFormatStr, yuyueTimeArr[1]+":59", loc)
beginTime := yuyueTimeBegin.UnixNano()/1e6 + diffTime
endTime := yuyueTimeEnd.UnixNano()/1e6 + diffTime
diffTime = beginTime - time.Now().UnixNano()/1e6
if diffTime > 0 {
log.Println("还没到预约时间,等待", diffTime, "ms 后开始预约")
time.Sleep(time.Duration(diffTime) * time.Millisecond)
}
diffTime = time.Now().UnixNano()/1e6 - endTime
if diffTime > 0 {
log.Println("您已经错过预约时间,下次请早!")
os.Exit(0)
}
} else {
log.Println("预约起始时间获取失败,立即尝试预约:", err, yuyueTimeArr)
}
user := NewUser(this.client, this.conf)
userInfo, _ := user.GetUserInfo()
log.Println("用户:" + userInfo)
@ -63,9 +162,26 @@ func (this *Seckill) MakeReserve() {
reserveUrl := gjson.Get(body, "url").String()
req = httpc.NewRequest(this.client)
_, _, _ = req.SetUrl("https:" + reserveUrl).SetMethod("get").Send().End()
msg := "预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约![我的预约](https://yushou.jd.com/member/qualificationList.action)"
_ = service.SendMessage(this.conf, "茅台抢购通知", msg)
msg := "商品名称《" + shopTitle + "》预约成功,已获得抢购资格 / 您已成功预约过了,无需重复预约!\n\n[我的预约](https://yushou.jd.com/member/qualificationList.action)"
_ = service.SendMessage(this.conf, "京东秒杀通知", msg)
log.Println(msg)
//更新购买时间
if len(buyTimeArr) == 2 {
confFile := "./conf.ini"
cfg, err := goconfig.LoadConfigFile(confFile)
if err != nil {
log.Println("配置文件不存在,程序退出")
os.Exit(0)
}
buyTime := buyTimeArr[0] + ":00"
cfg.SetValue("config", "buy_time", buyTime)
if err := goconfig.SaveConfigFile(cfg, confFile); err != nil {
log.Println("保存配置文件失败,请手动修改conf.ini,buy_time =", buyTime)
}
log.Println("下一次抢购开始时间设定已经更新:", buyTime)
}
}
}
@ -237,13 +353,13 @@ func (this *Seckill) SubmitSeckillOrder() bool {
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("抢购失败,网络错误")
_ = service.SendMessage(this.conf, "茅台抢购通知", "抢购失败,网络错误")
_ = service.SendMessage(this.conf, "京东秒杀通知", "抢购失败,网络错误")
return false
}
if !gjson.Valid(body) {
log.Println("抢购失败,返回信息:" + body)
_ = service.SendMessage(this.conf, "茅台抢购通知", "抢购失败,返回信息:"+body)
_ = service.SendMessage(this.conf, "京东秒杀通知", "抢购失败,返回信息:"+body)
return false
}
if gjson.Get(body, "success").Bool() {
@ -251,11 +367,11 @@ func (this *Seckill) SubmitSeckillOrder() bool {
totalMoney := gjson.Get(body, "totalMoney").String()
payUrl := "https:" + gjson.Get(body, "pcUrl").String()
log.Println(fmt.Sprintf("抢购成功,订单号:%s, 总价:%s, 电脑端付款链接:%s", orderId, totalMoney, payUrl))
_ = service.SendMessage(this.conf, "茅台抢购通知", fmt.Sprintf("抢购成功,订单号:%s, 总价:%s, 电脑端付款链接:%s", orderId, totalMoney, payUrl))
_ = service.SendMessage(this.conf, "京东秒杀通知", fmt.Sprintf("抢购成功,订单号:%s, 总价:%s, 电脑端付款链接:%s", orderId, totalMoney, payUrl))
return true
} else {
log.Println("抢购失败,返回信息:" + body)
_ = service.SendMessage(this.conf, "茅台抢购通知", "抢购失败,返回信息:"+body)
_ = service.SendMessage(this.conf, "京东秒杀通知", "抢购失败,返回信息:"+body)
return false
}
}

読み込み中…
キャンセル
保存