From fa372ad96dab7eff5d36835b1db30aa90cce6f70 Mon Sep 17 00:00:00 2001 From: yann0917 <386139859@qq.com> Date: Fri, 15 Jul 2022 21:45:50 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20=E6=94=AF=E6=8C=81=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- cmd/app/login.go | 40 +++++ cmd/download.go | 11 +- cmd/login.go | 13 +- config/config.go | 16 +- downloader/m3u8.go | 256 -------------------------------- go.mod | 9 +- go.sum | 305 +++++++++++++++++++++++++++++++++++++-- request/http.go | 164 ++------------------- request/http_test.go | 15 -- services/article.go | 2 +- services/login.go | 57 ++++++++ services/requester.go | 246 +++++++++++++++++++++---------- services/service.go | 61 ++++---- services/service_test.go | 17 ++- services/user.go | 4 +- utils/qrcode.go | 158 ++++++++++++++++++++ 17 files changed, 816 insertions(+), 562 deletions(-) delete mode 100644 downloader/m3u8.go delete mode 100644 request/http_test.go create mode 100644 services/login.go create mode 100644 utils/qrcode.go diff --git a/README.md b/README.md index ee379920..07ef7c8f 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ docker images|grep none|awk '{print $3 }'|xargs docker rmi ## 使用方法 -* 电脑端登录 [得到](https://www.dedao.cn),以便生成 cookie ☆☆☆☆☆ +* 使用 `dedao-dl login -q` **同时支持「得到App」和「微信」扫码扫码登录**,或者电脑端登录 [得到](https://www.dedao.cn) 生成 cookie 使用 `dedao-dl login -c "xxxxxxxx"` 登录 ☆☆☆☆☆ `dedao-dl -h` 可查看帮助说明,每个命令都有 `-h` 参数可查看该命令的用法 @@ -96,7 +96,6 @@ Available Commands: who 查看当前登录的用户 ``` -`dedao-dl login` 登录,不带任何参数的情况下,默认使用 [`go-rod/rod`](https://github.com/go-rod/rod) 从浏览器获取cookie. 如果无法自动获取 cookie,则使用 `dedao-dl login -c "xxxxxxxx"` 登录 `dedao-dl cat` 获取课程分类 @@ -240,7 +239,6 @@ Available Commands: * [geektime-dl](https://github.com/mmzou/geektime-dl) * [annie](https://github.com/iawia002/annie) -* [m3u8](https://github.com/oopsguy/m3u8) ## Stargazers over time diff --git a/cmd/app/login.go b/cmd/app/login.go index 4caf35ca..a5e02503 100644 --- a/cmd/app/login.go +++ b/cmd/app/login.go @@ -1,9 +1,12 @@ package app import ( + "fmt" "strings" + "time" "github.com/go-rod/rod" + "github.com/pkg/errors" "github.com/yann0917/dedao-dl/config" "github.com/yann0917/dedao-dl/services" "github.com/yann0917/dedao-dl/utils" @@ -28,7 +31,44 @@ func GetCookie() (cookie string) { _ = rod.Try(func() { cookie = utils.Get(config.BaseURL) if !strings.Contains(cookie, "ISID=") { + return } }) return } + +func LoginByQr() error { + token, err := getService().LoginAccessToken() + if err != nil { + return err + } + code, err := getService().GetQrcode(token) + if err != nil { + return err + } + + ticker := time.NewTicker(time.Duration(1) * time.Second) + fmt.Println("同时支持「得到App」和「微信」扫码") + for { + select { + case <-ticker.C: + check, cookie, err := getService().CheckLogin(token, code.Data.QrCodeString) + if err != nil { + return err + } + if check.Data.Status == 1 { + LoginByCookie(cookie) + fmt.Println("扫码成功") + return nil + } else if check.Data.Status == 2 { + err = errors.New("登录失败,二维码已过期") + return err + } + case <-ticker.C: + //10分钟后二维码失效 + case <-time.After(600 * time.Second): + err = errors.New("登录失败,二维码已过期") + return err + } + } +} diff --git a/cmd/download.go b/cmd/download.go index 077ff6f9..f9e543ef 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -96,16 +96,7 @@ func download(cType string, id, aid int) error { if err := downloader.Download(datum, stream, path); err != nil { errors = append(errors, err) } - // use m3u8 downloader - // downloader, err := downloader.NewTask(path, datum.M3U8URL) - // if err != nil { - // fmt.Println(err) - // errors = append(errors, err) - // } - // outName := datum.Title + ".mp3" - // if err := downloader.Start(20, outName); err != nil { - // errors = append(errors, err) - // } + } if len(errors) > 0 { return errors[0] diff --git a/cmd/login.go b/cmd/login.go index e51e631f..3351c73a 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -14,6 +14,7 @@ import ( // Cookie cookie from https://www.dedao.cn var Cookie string +var qr bool // Login login var loginCmd = &cobra.Command{ @@ -21,15 +22,22 @@ var loginCmd = &cobra.Command{ Short: "登录得到 pc 端 https://www.dedao.cn", Long: `使用 dedao-dl login to login https://www.dedao.cn`, RunE: func(cmd *cobra.Command, args []string) error { + if qr { + err := app.LoginByQr() + return err + } if Cookie == "" { defaultCookie := app.GetCookie() if defaultCookie == "" { return errors.New("自动获取 cookie 失败,请使用参数设置 cookie") } Cookie = defaultCookie + } else { + err := app.LoginByCookie(Cookie) + return err } - err := app.LoginByCookie(Cookie) - return err + return nil + }, PostRun: func(cmd *cobra.Command, args []string) { @@ -82,6 +90,7 @@ func init() { rootCmd.AddCommand(usersCmd) rootCmd.AddCommand(suCmd) loginCmd.Flags().StringVarP(&Cookie, "cookie", "c", "", "cookie from https://www.dedao.cn") + loginCmd.Flags().BoolVarP(&qr, "qrcode", "q", false, "scan qrcode login from https://www.dedao.cn") } // LoginedCookies cookie sting to map for chromedp print pdf diff --git a/config/config.go b/config/config.go index 70e444b2..4027ab75 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,8 @@ package config import ( + "fmt" + "io" "io/ioutil" "os" "path" @@ -66,6 +68,7 @@ func (c *ConfigsData) Init() error { return nil } + fmt.Printf("xxxxxx %#v\n", c.activeUser) if c.activeUser != nil { c.service = c.activeUser.New() } @@ -93,6 +96,10 @@ func (c *ConfigsData) initActiveUser() error { } } + if c.AcitveUID == "" && len(c.Users) == 0 { + c.activeUser = new(Dedao) + } + if len(c.Users) > 0 { return errors.New("存在登录的用户,可以进行切换登录用户") } @@ -104,6 +111,7 @@ func (c *ConfigsData) initActiveUser() error { func (c *ConfigsData) Save() error { err := c.lazyOpenConfigFile() if err != nil { + fmt.Println(err) return err } @@ -124,16 +132,19 @@ func (c *ConfigsData) Save() error { // 减掉多余的部分 err = c.configFile.Truncate(int64(len(data))) if err != nil { + fmt.Println(err) return err } - _, err = c.configFile.Seek(0, os.SEEK_SET) + _, err = c.configFile.Seek(0, io.SeekStart) if err != nil { + fmt.Println(err) return err } _, err = c.configFile.Write(data) if err != nil { + fmt.Println(err) return err } @@ -142,6 +153,7 @@ func (c *ConfigsData) Save() error { func (c *ConfigsData) initDefaultConfig() { // todo 默认配置 + c.Save() } func (c *ConfigsData) loadConfigFromFile() error { @@ -162,7 +174,7 @@ func (c *ConfigsData) loadConfigFromFile() error { c.fileMu.Lock() defer c.fileMu.Unlock() - _, err = c.configFile.Seek(0, os.SEEK_SET) + _, err = c.configFile.Seek(0, io.SeekStart) if err != nil { return nil } diff --git a/downloader/m3u8.go b/downloader/m3u8.go deleted file mode 100644 index ae6d5b98..00000000 --- a/downloader/m3u8.go +++ /dev/null @@ -1,256 +0,0 @@ -package downloader - -import ( - "bufio" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "sync" - "sync/atomic" - - "github.com/yann0917/dedao-dl/parse" - "github.com/yann0917/dedao-dl/request" - "github.com/yann0917/dedao-dl/utils" -) - -const ( - tsExt = ".ts" - tsFolderName = "ts" - mergeTSFilename = "main.ts" - tsTempFileSuffix = "_tmp" - progressWidth = 40 -) - -// Downloader ts download -type Downloader struct { - lock sync.Mutex - queue []int - folder string - tsFolder string - finish int32 - segLen int - - result *parse.Result -} - -// NewTask returns a Task instance -func NewTask(output string, url string) (*Downloader, error) { - result, err := parse.FromURL(url) - if err != nil { - return nil, err - } - var folder string - // If no output folder specified, use current directory - if output == "" { - current, err := utils.CurrentDir() - if err != nil { - return nil, err - } - folder = filepath.Join(current, output) - } else { - folder = output - } - if err := os.MkdirAll(folder, os.ModePerm); err != nil { - return nil, fmt.Errorf("create storage folder failed: %s", err.Error()) - } - tsFolder := filepath.Join(folder, tsFolderName) - if err := os.MkdirAll(tsFolder, os.ModePerm); err != nil { - return nil, fmt.Errorf("create ts folder '[%s]' failed: %s", tsFolder, err.Error()) - } - d := &Downloader{ - folder: folder, - tsFolder: tsFolder, - result: result, - } - d.segLen = len(result.M3u8.Segments) - d.queue = genSlice(d.segLen) - return d, nil -} - -// Start runs downloader -func (d *Downloader) Start(concurrency int, outName string) error { - var wg sync.WaitGroup - // struct{} zero size - limitChan := make(chan struct{}, concurrency) - for { - tsIdx, end, err := d.next() - if err != nil { - if end { - break - } - continue - } - wg.Add(1) - go func(idx int) { - defer wg.Done() - if err := d.download(idx); err != nil { - // Back into the queue, retry request - fmt.Printf("[failed] %s\n", err.Error()) - if err := d.back(idx); err != nil { - fmt.Println(err.Error()) - } - } - <-limitChan - }(tsIdx) - limitChan <- struct{}{} - } - wg.Wait() - if err := d.merge(outName); err != nil { - return err - } - return nil -} - -func (d *Downloader) download(segIndex int) error { - tsFilename := tsFilename(segIndex) - tsURL := d.tsURL(segIndex) - b, e := request.Get(tsURL) - if e != nil { - return fmt.Errorf("request %s, %s", tsURL, e.Error()) - } - //noinspection GoUnhandledErrorResult - defer b.Close() - fPath := filepath.Join(d.tsFolder, tsFilename) - fTemp := fPath + tsTempFileSuffix - f, err := os.Create(fTemp) - if err != nil { - return fmt.Errorf("create file: %s, %s", tsFilename, err.Error()) - } - bytes, err := ioutil.ReadAll(b) - if err != nil { - return fmt.Errorf("read bytes: %s, %s", tsURL, err.Error()) - } - sf := d.result.M3u8.Segments[segIndex] - if sf == nil { - return fmt.Errorf("invalid segment index: %d", segIndex) - } - key, ok := d.result.Keys[sf.KeyIndex] - if ok && key != "" { - bytes, err = utils.AES128Decrypt(bytes, []byte(key), - []byte(d.result.M3u8.Keys[sf.KeyIndex].IV)) - if err != nil { - return fmt.Errorf("decryt: %s, %s", tsURL, err.Error()) - } - } - // https://en.wikipedia.org/wiki/MPEG_transport_stream - // Some TS files do not start with SyncByte 0x47, they can not be played after merging, - // Need to remove the bytes before the SyncByte 0x47(71). - syncByte := uint8(71) //0x47 - bLen := len(bytes) - for j := 0; j < bLen; j++ { - if bytes[j] == syncByte { - bytes = bytes[j:] - break - } - } - w := bufio.NewWriter(f) - if _, err := w.Write(bytes); err != nil { - return fmt.Errorf("write to %s: %s", fTemp, err.Error()) - } - // Release file resource to rename file - _ = f.Close() - if err = os.Rename(fTemp, fPath); err != nil { - return err - } - // Maybe it will be safer in this way... - atomic.AddInt32(&d.finish, 1) - //tool.DrawProgressBar("Downloading", float32(d.finish)/float32(d.segLen), progressWidth) - fmt.Printf("[download %6.2f%%] %s\n", float32(d.finish)/float32(d.segLen)*100, tsURL) - return nil -} - -func (d *Downloader) next() (segIndex int, end bool, err error) { - d.lock.Lock() - defer d.lock.Unlock() - if len(d.queue) == 0 { - err = fmt.Errorf("queue empty") - if d.finish == int32(d.segLen) { - end = true - return - } - // Some segment indexes are still running. - end = false - return - } - segIndex = d.queue[0] - d.queue = d.queue[1:] - return -} - -func (d *Downloader) back(segIndex int) error { - d.lock.Lock() - defer d.lock.Unlock() - if sf := d.result.M3u8.Segments[segIndex]; sf == nil { - return fmt.Errorf("invalid segment index: %d", segIndex) - } - d.queue = append(d.queue, segIndex) - return nil -} - -func (d *Downloader) merge(mergeFileName string) error { - // In fact, the number of downloaded segments should be equal to number of m3u8 segments - missingCount := 0 - for idx := 0; idx < d.segLen; idx++ { - tsFilename := tsFilename(idx) - f := filepath.Join(d.tsFolder, tsFilename) - if _, err := os.Stat(f); err != nil { - missingCount++ - } - } - if missingCount > 0 { - fmt.Printf("[warning] %d files missing\n", missingCount) - } - - // Create a TS file for merging, all segment files will be written to this file. - mFilePath := filepath.Join(d.folder, mergeFileName) - mFile, err := os.Create(mFilePath) - if err != nil { - return fmt.Errorf("create main TS file failed:%s", err.Error()) - } - //noinspection GoUnhandledErrorResult - defer mFile.Close() - - writer := bufio.NewWriter(mFile) - mergedCount := 0 - for segIndex := 0; segIndex < d.segLen; segIndex++ { - tsFilename := tsFilename(segIndex) - bytes, err := ioutil.ReadFile(filepath.Join(d.tsFolder, tsFilename)) - _, err = writer.Write(bytes) - if err != nil { - continue - } - mergedCount++ - utils.DrawProgressBar("merge", - float32(mergedCount)/float32(d.segLen), progressWidth) - } - _ = writer.Flush() - // Remove `ts` folder - _ = os.RemoveAll(d.tsFolder) - - if mergedCount != d.segLen { - fmt.Printf("[warning] \n%d files merge failed", d.segLen-mergedCount) - } - - fmt.Printf("\n[output] %s\n", mFilePath) - - return nil -} - -func (d *Downloader) tsURL(segIndex int) string { - seg := d.result.M3u8.Segments[segIndex] - return utils.ResolveURL(d.result.URL, seg.URI) -} - -func tsFilename(ts int) string { - return strconv.Itoa(ts) + tsExt -} - -func genSlice(len int) []int { - s := make([]int, 0) - for i := 0; i < len; i++ { - s = append(s, i) - } - return s -} diff --git a/go.mod b/go.mod index 7e8a660c..0c091b4d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/yann0917/dedao-dl -go 1.15 +go 1.16 require ( github.com/VividCortex/ewma v1.2.0 // indirect @@ -9,18 +9,17 @@ require ( github.com/chromedp/chromedp v0.8.2 github.com/fatih/color v1.13.0 // indirect github.com/go-rod/rod v0.108.1 + github.com/imroc/req/v3 v3.14.0 github.com/json-iterator/go v1.1.12 - github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-colorable v0.1.12 github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.5.0 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 // indirect github.com/ysmood/gson v0.7.2 // indirect golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 2d893c03..6edc50dd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,22 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb/v3 v3.1.0 h1:3uouEsl32RL7gTiQsuaXD4Bzbfl5tGztXGUvXbs4O04= github.com/cheggaaa/pb/v3 v3.1.0/go.mod h1:YjrevcBqadFDaGQKRdmZxTY42pXEqda48Ea3lt0K/BE= github.com/chromedp/cdproto v0.0.0-20220515234810-83d799542a04/go.mod h1:5Y4sD/eXpwrChIuxhSr/G20n9CdbCmoerOHnuAf0Zr0= @@ -10,39 +26,108 @@ github.com/chromedp/chromedp v0.8.2 h1:EYSsSqWuKYwyHZEJpU00kOGOMz5DE0qDVckelzauM github.com/chromedp/chromedp v0.8.2/go.mod h1:vpbCNtfYeOUo2q5reuwX6ZmPpbHRf5PZfAqNR2ObB+g= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-rod/rod v0.108.1 h1:2lKs+v/+B/2pbGKZgNIRbURhduTKNDZ3PXIvTRAV2Mg= github.com/go-rod/rod v0.108.1/go.mod h1:yNvL687cwcjgebRuArQN9AStFdm8iS/e/rzImrS9Pzg= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imroc/req/v3 v3.14.0 h1:N1NdLBz61Ib0epIHSpqFS1g58gTjAmRGKfTdFe/8OVc= +github.com/imroc/req/v3 v3.14.0/go.mod h1:Y52Wz8vsqFEFTbXqmDyCFz6brGwoenIVBxTacIaECZs= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucas-clemente/quic-go v0.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM= +github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= +github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ= +github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= +github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -54,39 +139,95 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc= github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/got v0.31.2 h1:+aNBkkXrVqZ0UJfeiOfuGbKq3kiJarjNl371Nxz6zLA= @@ -98,21 +239,163 @@ github.com/ysmood/gson v0.7.2 h1:1iWUvpi5DPvd2j59W7ifRPR9DiAZ3Ga+fmMl1mJrRbM= github.com/ysmood/gson v0.7.2/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= +golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/request/http.go b/request/http.go index 9672c4cf..1df83b31 100644 --- a/request/http.go +++ b/request/http.go @@ -1,184 +1,42 @@ package request import ( - "bytes" "errors" "fmt" "io" "io/ioutil" - "net" "net/http" - "net/http/cookiejar" - "net/url" "strconv" - "strings" - "time" - jsoniter "github.com/json-iterator/go" + "github.com/imroc/req/v3" ) var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary // UserAgent UserAgent UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" ) -// var client *http.Client -const ( - MaxIdleConns = 100 - MaxIdleConnsPerHost = 100 - IdleConnTimeout = 90 - Timeout = 30 -) - // HTTPClient http client type HTTPClient struct { - BaseURL string - Header map[string]string - Data interface{} - Client *http.Client + req.Client } // NewClient new HTTPClient -func NewClient(baseURL string) *HTTPClient { - c := &HTTPClient{ - BaseURL: baseURL, - Header: make(map[string]string), - Data: make(map[string]string), - Client: createHTTPClient(), - } - c.ResetCookieJar() +func NewClient(baseURL string) *req.Client { + c := req.C() + c = c.SetBaseURL(baseURL) return c } -// createHTTPClient for connection re-use -func createHTTPClient() *http.Client { - client := &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: Timeout * time.Second, - KeepAlive: Timeout * time.Second, - }).DialContext, - MaxIdleConns: MaxIdleConns, - MaxIdleConnsPerHost: MaxIdleConnsPerHost, - IdleConnTimeout: IdleConnTimeout * time.Second, - }, - } - return client -} - -//SetCookiejar set Cookie -func (h *HTTPClient) SetCookiejar(jar http.CookieJar) *HTTPClient { - h.Client.Jar = jar - return h -} - -//ResetCookieJar reset cookie jar -func (h *HTTPClient) ResetCookieJar() *HTTPClient { - h.Client.Jar, _ = cookiejar.New(nil) - return h -} - -//SetTimeout set timeout -func (h *HTTPClient) SetTimeout(t time.Duration) *HTTPClient { - h.Client.Timeout = t - return h -} - -// SetBaseURL 设置基础地址 -func (h *HTTPClient) SetBaseURL(baseURL string) *HTTPClient { - h.BaseURL = baseURL - return h -} - -// AddHeader 添加一个 header -func (h *HTTPClient) AddHeader(name string, value string) *HTTPClient { - h.Header[name] = value - return h -} - -// AddHeaders 添加一组 header 数据 -func (h *HTTPClient) AddHeaders(headers map[string]string) *HTTPClient { - for k, header := range headers { - h.Header[k] = header - } - return h -} - -// SetData 设置查询数据 -func (h *HTTPClient) SetData(data interface{}) *HTTPClient { - h.Data = data - return h -} - -// Request 实现 http/https 访问, -// 根据给定的 method (GET, POST, HEAD, PUT 等等), -// URL (路由), -// 返回值分别为 *http.Response, 错误信息 -func (h *HTTPClient) Request(method, URL string) (*http.Response, error) { - var ( - req *http.Request - obody io.Reader - ) - requrl := h.BaseURL + URL - if h.Data != nil { - switch value := h.Data.(type) { - case io.Reader: - obody = value - case map[string]string, map[string]int, map[string]interface{}, []int, []string: - if method == "GET" { - query := url.Values{} - if params, ok := h.Data.(map[string]string); ok { - for k, v := range params { - query.Add(k, v) - } - } - encode := query.Encode() - if encode != "" { - requrl += "?" + encode - } - } else { - postData, err := jsoniter.Marshal(value) - if err != nil { - return nil, err - } - h.Header["Content-Type"] = "application/json" - obody = bytes.NewReader(postData) - } - case string: - obody = strings.NewReader(value) - case []byte: - obody = bytes.NewReader(value) - default: - return nil, fmt.Errorf("request.Req: unknow post type: %s", h.Data) - } - } - // fmt.Println(method, " ", requrl) - req, err := http.NewRequest(method, requrl, obody) - if err != nil { - return nil, err - } - - // 设置浏览器标识 - req.Header.Set("User-Agent", UserAgent) - - if h.Header != nil { - for k, v := range h.Header { - req.Header.Set(k, v) - } - } - return h.Client.Do(req) -} - // HTTPGet http get request func HTTPGet(url string) (body []byte, err error) { - r, err := Get(url) + r, err := req.Get(url) if err != nil { return } - defer r.Close() - body, err = ioutil.ReadAll(r) + + defer r.Body.Close() + body, err = ioutil.ReadAll(r.Body) if err != nil { return } @@ -188,7 +46,7 @@ func HTTPGet(url string) (body []byte, err error) { // Get http get request func Get(url string) (io.ReadCloser, error) { client := NewClient(url) - resp, err := client.Request("GET", "") + resp, err := client.R().Get(url) if err != nil { return nil, err } @@ -201,7 +59,7 @@ func Get(url string) (io.ReadCloser, error) { // Headers return the HTTP Headers of the url func Headers(url string) (http.Header, error) { client := NewClient(url) - resp, err := client.Request("GET", "") + resp, err := client.R().Get("") if err != nil { return nil, err } diff --git a/request/http_test.go b/request/http_test.go deleted file mode 100644 index 21f7d593..00000000 --- a/request/http_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package request - -import ( - "fmt" - "testing" -) - -func TestGet(t *testing.T) { - resp, err := HTTPGet("https://www.baidu.com") - if err != nil { - - } - - fmt.Println(string(resp)) -} diff --git a/services/article.go b/services/article.go index 0091a356..df579c13 100644 --- a/services/article.go +++ b/services/article.go @@ -416,7 +416,7 @@ func (s *Service) ArticleDetail(token, id, appID string) (detail *ArticleDetail, } // ArticlePoint get article point -func (s *Service) ArticlePoint(id string, pType int) (detail *ArticleDetail, err error) { +func (s *Service) ArticlePoint(id, pType string) (detail *ArticleDetail, err error) { body, err := s.reqArticlePoint(id, pType) if err != nil { return diff --git a/services/login.go b/services/login.go new file mode 100644 index 00000000..c4a6dbec --- /dev/null +++ b/services/login.go @@ -0,0 +1,57 @@ +package services + +import ( + "github.com/pkg/errors" + "github.com/yann0917/dedao-dl/utils" +) + +type QrCodeReqp struct { + ErrCode int `json:"errCode"` + ErrMsg string `json:"errMsg"` + Data struct { + QrCode string `json:"qrcode"` + QrCodeString string `json:"qrCodeString"` + } `json:"data"` +} + +type CheckLoginResp struct { + ErrCode int `json:"errCode"` + ErrMsg string `json:"errMsg"` + Data struct { + Status int `json:"status"` // 1-扫码成功,2-过期 + } `json:"data"` +} + +// get login access token +func (s *Service) LoginAccessToken() (token string, err error) { + token, err = s.reqGetLoginAccessToken() + if err != nil { + err = errors.Wrap(err, "request login err") + return + } + + return +} + +func (s *Service) GetQrcode(token string) (resp *QrCodeReqp, err error) { + resp, err = s.reqGetQrcode(token) + if err != nil { + err = errors.Wrap(err, "request login err") + return + } + content := "https://m.igetget.com/oauth/qrcode/v2/authorize?token=" + resp.Data.QrCodeString + obj := utils.New() + obj.Get(content).Print() + + return +} + +func (s *Service) CheckLogin(token, qrcode string) (check *CheckLoginResp, cookie string, err error) { + check, cookie, err = s.reqCheckLogin(token, qrcode) + if err != nil { + err = errors.Wrap(err, "request login err") + return + } + + return +} diff --git a/services/requester.go b/services/requester.go index f815afeb..3038e7c5 100644 --- a/services/requester.go +++ b/services/requester.go @@ -1,64 +1,146 @@ package services import ( + "fmt" "io" + "strings" ) +// reqGetLoginAccessToken 扫码请求token +func (s *Service) reqGetLoginAccessToken() (string, error) { + // request index get csrf-token + index, err := s.client.R().Get("") + if err != nil { + fmt.Printf("%#v\n", err.Error()) + return "", err + } + + setCookie := index.GetHeaderValues("Set-Cookie") + cookies := strings.Split(strings.Join(setCookie, "; "), "; ") + csrfToken := "" + for _, v := range cookies { + item := strings.Split(v, "=") + if len(item) > 1 && item[0] == "csrfToken" { + csrfToken = item[1] + break + } + } + + resp, err := s.client.R(). + SetHeader("Accept", "application/json, text/plain, */*"). + SetHeader("Xi-Csrf-Token", csrfToken). + SetHeader("Xi-DT", "web"). + Post("/loginapi/getAccessToken") + if err != nil { + fmt.Printf("%#v\n", err.Error()) + return "", err + } + accessToken, err := resp.ToString() + if err != nil { + fmt.Printf("%#v\n", err.Error()) + return "", err + } + return accessToken, err +} + +// reqGetQrcode 扫码登录二维码 +// token: X-Oauth-Access-Token from /loginapi/getAccessToken +func (s *Service) reqGetQrcode(token string) (qr *QrCodeReqp, err error) { + _, err = s.client.R(). + SetHeader("X-Oauth-Access-Token", token). + SetResult(&qr). + Get("/oauth/api/embedded/qrcode") + if err != nil { + fmt.Printf("%#v\n", err.Error()) + return + } + return +} + +// reqCheckLogin 轮询扫码登录结果 +// token: X-Oauth-Access-Token from /loginapi/getAccessToken +// qrCode: qrCodeString from /oauth/api/embedded/qrcode +func (s *Service) reqCheckLogin(token, qrCode string) (check *CheckLoginResp, cookie string, err error) { + resp, err := s.client.R(). + SetHeader("X-Oauth-Access-Token", token). + SetBody(map[string]interface{}{ + "keepLogin": true, + "pname": "igetoauthpc", + "qrCode": qrCode, + "scene": "registerlogin", + }). + SetResult(&check). + Post("/oauth/api/embedded/qrcode/check_login") + if err != nil { + fmt.Printf("%#v\n", err.Error()) + return + } + cookies := resp.GetHeaderValues("Set-Cookie") + cookie = strings.Join(cookies, "; ") + return +} + // reqUser 请求token func (s *Service) reqToken() (io.ReadCloser, error) { - resp, err := s.client.Request("GET", "/ddph/v2/token/create") + resp, err := s.client.R(). + Get("/ddph/v2/token/create") return handleHTTPResponse(resp, err) } // reqUser 请求用户信息 func (s *Service) reqUser() (io.ReadCloser, error) { - resp, err := s.client.Request("GET", "/api/pc/user/info") + resp, err := s.client.R(). + Get("/api/pc/user/info") + return handleHTTPResponse(resp, err) } // reqCourseType 请求首页课程分类列表 func (s *Service) reqCourseType() (io.ReadCloser, error) { - resp, err := s.client.Request("POST", "/api/hades/v1/index/detail") + resp, err := s.client.R().Post("/api/hades/v1/index/detail") return handleHTTPResponse(resp, err) } // reqCourseList 请求课程列表 func (s *Service) reqCourseList(category, order string, page, limit int) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ + resp, err := s.client.R().SetBodyJsonMarshal(map[string]interface{}{ "category": category, "order": order, "filter_complete": 0, "page": page, "page_size": limit, - }).Request("POST", "/api/hades/v1/product/list") + }).Post("/api/hades/v1/product/list") return handleHTTPResponse(resp, err) } // reqCourseInfo 请求课程介绍 func (s *Service) reqCourseInfo(ID string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "detail_id": ID, - "is_login": 1, - }).Request("POST", "/pc/bauhinia/pc/class/info") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "detail_id": ID, + "is_login": 1, + }). + Post("/pc/bauhinia/pc/class/info") return handleHTTPResponse(resp, err) } // reqArticleList 请求文章列表 // chapterID = "" 获取所有的文章列表,否则只获取该章节的文章列表 func (s *Service) reqArticleList(ID, chapterID string, maxID int) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "chapter_id": chapterID, - "count": 30, - "detail_id": ID, - "include_edge": false, - "is_unlearn": false, - "max_id": maxID, - "max_order_num": 0, - "reverse": false, - "since_id": 0, - "since_order_num": 0, - "unlearn_switch": false, - }).Request("POST", "/api/pc/bauhinia/pc/class/purchase/article_list") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "chapter_id": chapterID, + "count": 30, + "detail_id": ID, + "include_edge": false, + "is_unlearn": false, + "max_id": maxID, + "max_order_num": 0, + "reverse": false, + "since_id": 0, + "since_order_num": 0, + "unlearn_switch": false, + }).Post("/api/pc/bauhinia/pc/class/purchase/article_list") return handleHTTPResponse(resp, err) } @@ -66,114 +148,126 @@ func (s *Service) reqArticleList(ID, chapterID string, maxID int) (io.ReadCloser // enId 文章 ID // sort like-最热 create-最新 func (s *Service) reqArticleCommentList(enId, sort string, page, limit int) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "detail_enid": enId, - "note_type": 2, - "only_replied": false, - "page": page, - "page_count": limit, - "sort_by": sort, - "source_type": 65, - }).Request("POST", "/pc/ledgers/notes/article_comment_list") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "detail_enid": enId, + "note_type": 2, + "only_replied": false, + "page": page, + "page_count": limit, + "sort_by": sort, + "source_type": 65, + }).Post("/pc/ledgers/notes/article_comment_list") return handleHTTPResponse(resp, err) } // reqArticleInfo 请求文章 token func (s *Service) reqArticleInfo(ID string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]string{ - "detail_id": ID, - }).Request("POST", "/pc/bauhinia/pc/article/info") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]string{ + "detail_id": ID, + }).Post("/pc/bauhinia/pc/article/info") return handleHTTPResponse(resp, err) } // reqArticleDetail 请求文章详情 func (s *Service) reqArticleDetail(token, appID string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]string{ - "token": token, - "appid": appID, - "is_new": "1", - }).Request("GET", "/pc/ddarticle/v1/article/get/v2") + resp, err := s.client.R(). + SetQueryParams(map[string]string{ + "token": token, + "appid": appID, + "is_new": "1", + }). + Get("/pc/ddarticle/v1/article/get/v2") return handleHTTPResponse(resp, err) } // reqArticlePoint 请求文章重点 -func (s *Service) reqArticlePoint(enid string, pType int) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "article_id_hazy": enid, - "product_type": pType, - }).Request("GET", "/pc/ddarticle/v1/article/get/v2") +func (s *Service) reqArticlePoint(enid string, pType string) (io.ReadCloser, error) { + resp, err := s.client.R(). + SetQueryParams(map[string]string{ + "article_id_hazy": enid, + "product_type": pType, + }).Get("/pc/ddarticle/v1/article/get/v2") return handleHTTPResponse(resp, err) } // reqAudioByAlias 请求音频详情 func (s *Service) reqAudioByAlias(ids string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "ids": ids, - "get_extra_data": 1, - }).Request("POST", "/pc/bauhinia/v1/audio/mutiget_by_alias") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "ids": ids, + "get_extra_data": 1, + }). + Post("/pc/bauhinia/v1/audio/mutiget_by_alias") return handleHTTPResponse(resp, err) } // reqEbookDetail 请求电子书详情 func (s *Service) reqEbookDetail(enid string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]string{ - "id": enid, - }).Request("GET", "/pc/ebook2/v1/pc/detail") + resp, err := s.client.R(). + SetQueryParam("id", enid). + Get("/pc/ebook2/v1/pc/detail") + return handleHTTPResponse(resp, err) } // reqEbookReadToken 请求电子书阅读 token func (s *Service) reqEbookReadToken(enid string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]string{ - "id": enid, - }).Request("POST", "/api/pc/ebook2/v1/pc/read/token") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]string{ + "id": enid, + }). + Post("/api/pc/ebook2/v1/pc/read/token") return handleHTTPResponse(resp, err) } // reqEbookInfo 请求电子书 info func (s *Service) reqEbookInfo(token string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]string{ - "token": token, - }).Request("GET", "/ebk_web/v1/get_book_info") + resp, err := s.client.R(). + SetQueryParam("token", token). + Get("/ebk_web/v1/get_book_info") return handleHTTPResponse(resp, err) } // reqEbookInfo 请求电子书vip info func (s *Service) reqEbookVIPInfo() (io.ReadCloser, error) { - resp, err := s.client.Request("POST", "/api/pc/ebook2/v1/vip/info") + resp, err := s.client.R().Post("/api/pc/ebook2/v1/vip/info") return handleHTTPResponse(resp, err) } // reqTopicAll 请求推荐话题列表 func (s *Service) reqTopicAll(page, limit int, all bool) (io.ReadCloser, error) { - if all { - resp, err := s.client.Request("POST", "/pc/ledgers/topic/all") - return handleHTTPResponse(resp, err) + r := s.client.R() + if !all { + r = r.SetBodyJsonMarshal(map[string]int{ + "page_id": page, + "limit": limit, + }) } - resp, err := s.client.SetData(map[string]int{ - "page_id": page, - "limit": limit, - }).Request("POST", "/pc/ledgers/topic/all") + resp, err := r.Post("/pc/ledgers/topic/all") return handleHTTPResponse(resp, err) } // reqTopicAll 请求话题详情 func (s *Service) reqTopicDetail(topicID string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "incr_view_count": true, - "topic_id_hazy": topicID, - }).Request("POST", "/pc/ledgers/topic/detail") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "incr_view_count": true, + "topic_id_hazy": topicID, + }).Post("/pc/ledgers/topic/detail") return handleHTTPResponse(resp, err) } // reqTopicNotesList 请求话题笔记列表 func (s *Service) reqTopicNotesList(topicID string) (io.ReadCloser, error) { - resp, err := s.client.SetData(map[string]interface{}{ - "count": 40, - "is_elected": true, - "page_id": 0, - "version": 2, - "topic_id_hazy": topicID, - }).Request("POST", "/pc/ledgers/topic/notes/list") + resp, err := s.client.R(). + SetBodyJsonMarshal(map[string]interface{}{ + "count": 40, + "is_elected": true, + "page_id": 0, + "version": 2, + "topic_id_hazy": topicID, + }).Post("/pc/ledgers/topic/notes/list") return handleHTTPResponse(resp, err) } diff --git a/services/service.go b/services/service.go index a16d8489..9b5e2a03 100644 --- a/services/service.go +++ b/services/service.go @@ -1,13 +1,16 @@ package services import ( + "bytes" "errors" "fmt" "io" + "io/ioutil" "net/http" "net/url" "strings" + "github.com/imroc/req/v3" "github.com/mitchellh/mapstructure" "github.com/yann0917/dedao-dl/request" "github.com/yann0917/dedao-dl/utils" @@ -18,7 +21,8 @@ var ( Scheme: "https", Host: "dedao.cn", } - baseURL = "https://www.dedao.cn" + baseURL = "https://www.dedao.cn" + UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" ) // Response dedao success response @@ -28,10 +32,11 @@ type Response struct { } type respH struct { - C int `json:"c"` - E string `json:"e"` - S int `json:"s"` - T int `json:"t"` + C int `json:"c"` + E string `json:"e"` + S int `json:"s"` + T int `json:"t"` + Apm string `json:"apm"` } // respC response content @@ -49,7 +54,7 @@ func (r respC) String() string { //Service dedao service type Service struct { - client *request.HTTPClient + client *req.Client } // CookieOptions dedao cookie options @@ -68,7 +73,7 @@ type CookieOptions struct { //NewService new service func NewService(co *CookieOptions) *Service { client := request.NewClient(baseURL) - client.ResetCookieJar() + // client.ResetCookieJar() cookies := []*http.Cookie{} cookies = append(cookies, &http.Cookie{ Name: "GAT", @@ -110,39 +115,35 @@ func NewService(co *CookieOptions) *Service { Value: co.AliyungfTc, Domain: "www." + dedaoCommURL.Host, }) - client.Client.Jar.SetCookies(dedaoCommURL, cookies) + client.SetBaseURL(baseURL) + client.SetCommonCookies(cookies...) + client.SetUserAgent(UserAgent) return &Service{client: client} } //Cookies get cookies string -func (s *Service) Cookies() map[string]string { - cookies := s.client.Client.Jar.Cookies(dedaoCommURL) +// func (s *Service) Cookies() map[string]string { +// cookies := s.client.Client.Jar.Cookies(dedaoCommURL) - cstr := map[string]string{} +// cstr := map[string]string{} - for _, cookie := range cookies { - cstr[cookie.Name] = cookie.Value - } +// for _, cookie := range cookies { +// cstr[cookie.Name] = cookie.Value +// } - return cstr -} +// return cstr +// } func (r *Response) isSuccess() bool { return r.H.C == 0 } -func deferResponseClose(s *http.Response) { - if s != nil { - defer s.Body.Close() - } -} - -func handleHTTPResponse(resp *http.Response, err error) (io.ReadCloser, error) { +func handleHTTPResponse(resp *req.Response, err error) (io.ReadCloser, error) { if err != nil { - deferResponseClose(resp) return nil, err } + if resp.StatusCode == http.StatusNotFound { return nil, errors.New("404 NotFound") } @@ -155,6 +156,14 @@ func handleHTTPResponse(resp *http.Response, err error) (io.ReadCloser, error) { if resp.StatusCode == 496 { return nil, errors.New("496 NoCertificate") } + + data, err1 := resp.ToBytes() + if err1 != nil { + return nil, err1 + } + reader := bytes.NewReader(data) + + resp.Body = ioutil.NopCloser(reader) return resp.Body, nil } @@ -190,7 +199,9 @@ func ParseCookies(cookie string, v interface{}) (err error) { cookieM := make(map[string]string, len(list)) for _, v := range list { item := strings.Split(v, "=") - cookieM[item[0]] = item[1] + if len(item) > 1 { + cookieM[item[0]] = item[1] + } } err = mapstructure.Decode(cookieM, v) return diff --git a/services/service_test.go b/services/service_test.go index d3e4fc56..a426fc98 100644 --- a/services/service_test.go +++ b/services/service_test.go @@ -10,7 +10,8 @@ var service *Service func TestMain(m *testing.M) { // cookie := utils.Get(baseURL) - cookie := "ISID=538c3c9bc97227fb31290d9f54ad87cd; _sid=1ekdk2rsmmivs75orohjpkk6g2o49vpo; _guard_device_id=1epe8s675vg8Gg5c5dGvhQcgDfe9PD5FdKTkZc7; token=iXBjd6WM-ar7lw2242KOjqm4sQPm-lJViRnw; iget=eyJzZWNyZXQiOiJzVWdwSVJwc3c3QzVoY21DYVhRX1RlSnoiLCJfZXhwaXJlIjoxNjEwMTk2MzkwMzI4LCJfbWF4QWdlIjo2MDQ4MDAwMDB9; GAT=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJpZ2V0Z2V0LmNvbSIsImV4cCI6MTYxMDQ1MjkwNiwiaWF0IjoxNjEwMDIwOTA2LCJpc3MiOiJEREdXIEpXVCBNSURETEVXQVJFIiwibmJmIjoxNjEwMDIwOTA2LCJzdWIiOiIxNzk1MDMyNCIsImRldmljZV9pZCI6IjUzOGMzYzliYzk3MjI3ZmIzMTI5MGQ5ZjU0YWQ4N2NkIiwiZGV2aWNlX3R5cGUiOiJpZ2V0d2ViIn0.GH0o6ZYOCt0tSI4GzUnJnRkvJqEBOKpnI4FrFADfyN0GD0oE4-0ZwhxQ7qbXfWYCtSRN_iJ8PiZ3XqJyijhtmA; acw_tc=276082a916100771826244684e8b9b53f794448d7df554acf58703e1319d18" + cookie := "aliyungf_tc=92ca2d34f067361044be841c6c559599f5b3411f030fac6be52bc59e06cfb77c; ISID=b013cac8ffcabe56f77760d8bf6a47e6; csrfToken=2_EoW1CDysblBqri4ZEGtLH_; token=2_EoW1CDysblBqri4ZEGtLH_; _guard_device_id=1g7vrlkr0C2VzYWMB05NqrSDNUvbqqEotsv6YKt; Hm_lvt_be36b12b82a5f4eaa42c23989d277bb0=1657539221,1657716818,1657722320,1657848773; _sid=1g7vrllhlkomvv71g5e193aw5we817wf; acw_tc=2f6fc12616578553056474156e8e6d9076552d85101eff7b108bcc4bdb9595; GAT=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJpZ2V0Z2V0LmNvbSIsImV4cCI6MTY2MDQ0NzMyNCwiaWF0IjoxNjU3ODU1MzI0LCJpc3MiOiJEREdXIEpXVCBNSURETEVXQVJFIiwibmJmIjoxNjU3ODU1MzI0LCJzdWIiOiIxNzk1MDMyNCIsImRldmljZV9pZCI6ImIwMTNjYWM4ZmZjYWJlNTZmNzc3NjBkOGJmNmE0N2U2IiwiZGV2aWNlX3R5cGUiOiJpZ2V0d2ViIn0.GpToOpMNjqRG8dbP8bHlE0BIK4AmW-jmGOwmWu7cORvLuSzS9GA1OpovuIMCIprLFp33xDiWlyMaPqdM2oNmfA; iget=eyJ1c2VyIjp7InVzZXJJZCI6MTc5NTAzMjQsIm5pY2tuYW1lIjoiWWFibyIsImF2YXRhciI6Imh0dHBzOi8vcGljY2RuLnVtaXdpLmNvbS91cGxvYWRlci9pbWFnZS9hdmF0YXIvMjAyMTEyMjAxMS8xNzYwOTA0MzUwMTE0MjUwOTIwIiwiYXZhdGFyU2hvcnQiOiJodHRwczovL3BpY2Nkbi51bWl3aS5jb20vdXBsb2FkZXIvaW1hZ2UvYXZhdGFyLzIwMjExMjIwMTEvMTc2MDkwNDM1MDExNDI1MDkyMD94LW9zcy1wcm9jZXNzPWltYWdlL3Jlc2l6ZSxwXzI1Iiwic3RhdHVzIjoyLCJwaG9uZSI6IjE3NjgyMzMwOTE3IiwibG9naW5UaW1lIjoiMjAyMi0wNy0xNSAxMToyMjowNCJ9LCJfZXhwaXJlIjoxNjU4NDYwMTI1MDQwLCJfbWF4QWdlIjo2MDQ4MDAwMDB9; Hm_lpvt_be36b12b82a5f4eaa42c23989d277bb0=1657855331" + // cookie := "" co := &CookieOptions{} ParseCookies(cookie, &co) service = NewService(co) @@ -18,12 +19,24 @@ func TestMain(m *testing.M) { // 退出 os.Exit(exitCode) } + +func TestGetLoginAccessToken(t *testing.T) { + result, err := service.reqGetLoginAccessToken() + if err != nil { + fmt.Printf("%#v \n", err) + } + fmt.Printf("%#v \n", result) +} + func TestGet(t *testing.T) { fmt.Println(dedaoCommURL.String()) } func TestNewService(t *testing.T) { - resp, _ := service.client.Request("GET", "/api/pc/user/info") + resp, err := service.client.R().Get("/api/pc/user/info") + if err != nil { + fmt.Printf("%#v \n", err) + } defer resp.Body.Close() var user User diff --git a/services/user.go b/services/user.go index 8bf97344..aff150cf 100644 --- a/services/user.go +++ b/services/user.go @@ -1,6 +1,8 @@ package services -import "github.com/pkg/errors" +import ( + "github.com/pkg/errors" +) // User user info type User struct { diff --git a/utils/qrcode.go b/utils/qrcode.go new file mode 100644 index 00000000..be86e44b --- /dev/null +++ b/utils/qrcode.go @@ -0,0 +1,158 @@ +package utils + +import ( + "fmt" + + nbytes "bytes" + "image/png" + + "github.com/mattn/go-colorable" + "github.com/skip2/go-qrcode" +) + +type consoleColor string + +type consoleColors struct { + NormalBlack consoleColor + NormalRed consoleColor + NormalGreen consoleColor + NormalYellow consoleColor + NormalBlue consoleColor + NormalMagenta consoleColor + NormalCyan consoleColor + NormalWhite consoleColor + BrightBlack consoleColor + BrightRed consoleColor + BrightGreen consoleColor + BrightYellow consoleColor + BrightBlue consoleColor + BrightMagenta consoleColor + BrightCyan consoleColor + BrightWhite consoleColor +} + +type qrcodeRecoveryLevel qrcode.RecoveryLevel +type qrcodeRecoveryLevels struct { + Low qrcodeRecoveryLevel + Medium qrcodeRecoveryLevel + High qrcodeRecoveryLevel + Highest qrcodeRecoveryLevel +} + +var ( + ConsoleColors consoleColors = consoleColors{ + NormalBlack: "\033[38;5;0m \033[0m", + NormalRed: "\033[38;5;1m \033[0m", + NormalGreen: "\033[38;5;2m \033[0m", + NormalYellow: "\033[38;5;3m \033[0m", + NormalBlue: "\033[38;5;4m \033[0m", + NormalMagenta: "\033[38;5;5m \033[0m", + NormalCyan: "\033[38;5;6m \033[0m", + NormalWhite: "\033[38;5;7m \033[0m", + BrightBlack: "\033[48;5;0m \033[0m", + BrightRed: "\033[48;5;1m \033[0m", + BrightGreen: "\033[48;5;2m \033[0m", + BrightYellow: "\033[48;5;3m \033[0m", + BrightBlue: "\033[48;5;4m \033[0m", + BrightMagenta: "\033[48;5;5m \033[0m", + BrightCyan: "\033[48;5;6m \033[0m", + BrightWhite: "\033[48;5;7m \033[0m"} + QRCodeRecoveryLevels = qrcodeRecoveryLevels{ + Low: qrcodeRecoveryLevel(qrcode.Low), + Medium: qrcodeRecoveryLevel(qrcode.Medium), + High: qrcodeRecoveryLevel(qrcode.High), + Highest: qrcodeRecoveryLevel(qrcode.Highest)} + + outer = colorable.NewColorableStdout() +) + +type QRCodeString string + +func (v *QRCodeString) Print() { + fmt.Fprint(outer, *v) +} + +type qrcodeTerminal struct { + front consoleColor + back consoleColor + level qrcodeRecoveryLevel +} + +func (v *qrcodeTerminal) Get(content interface{}) (result *QRCodeString) { + var qr *qrcode.QRCode + var err error + if t, ok := content.(string); ok { + qr, err = qrcode.New(t, qrcode.RecoveryLevel(v.level)) + } else if t, ok := content.([]byte); ok { + qr, err = qrcode.New(string(t), qrcode.RecoveryLevel(v.level)) + } + if qr != nil && err == nil { + data := qr.Bitmap() + result = v.getQRCodeString(data) + } + return +} + +func (v *qrcodeTerminal) Get2(bytes []byte) (result *QRCodeString) { + data, err := parseQR(bytes) + if err == nil { + result = v.getQRCodeString(data) + } + return +} + +func New2(front, back consoleColor, level qrcodeRecoveryLevel) *qrcodeTerminal { + obj := qrcodeTerminal{front: front, back: back, level: level} + return &obj +} + +func New() *qrcodeTerminal { + front, back, level := ConsoleColors.BrightBlack, ConsoleColors.BrightWhite, QRCodeRecoveryLevels.Medium + return New2(front, back, level) +} + +func (v *qrcodeTerminal) getQRCodeString(data [][]bool) (result *QRCodeString) { + str := "" + for ir, row := range data { + lr := len(row) + if ir == 0 || ir == 1 || ir == 2 || + ir == lr-1 || ir == lr-2 || ir == lr-3 { + continue + } + for ic, col := range row { + lc := len(data) + if ic == 0 || ic == 1 || ic == 2 || + ic == lc-1 || ic == lc-2 || ic == lc-3 { + continue + } + if col { + str += fmt.Sprint(v.front) + } else { + str += fmt.Sprint(v.back) + } + } + str += fmt.Sprintln() + } + obj := QRCodeString(str) + result = &obj + return +} + +func parseQR(bytes []byte) (data [][]bool, err error) { + r := nbytes.NewReader(bytes) + img, err := png.Decode(r) + if err == nil { + rect := img.Bounds() + mx, my := rect.Max.X, rect.Max.Y + data = make([][]bool, mx) + for x := 0; x < mx; x++ { + data[x] = make([]bool, my) + for y := 0; y < my; y++ { + c := img.At(x, y) + r, _, _, _ := c.RGBA() + data[x][y] = r == 0 + } + } + } + return +}