package lib import ( "bytes" "encoding/base64" "errors" "fmt" "image" "image/gif" "image/jpeg" "image/png" "io" "os" "runtime" "time" qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go" "github.com/makiuchi-d/gozxing" "github.com/makiuchi-d/gozxing/qrcode" "github.com/mxschmitt/playwright-go" "github.com/nfnt/resize" log "github.com/sirupsen/logrus" "golang.org/x/image/bmp" ) type Core struct { pw *playwright.Playwright browser playwright.Browser context *playwright.BrowserContext ShowBrowser bool Push func(kind string, message string) } type Cookie struct { Name string `json:"name" yaml:"name"` Value string `json:"value" yaml:"value"` Domain string `json:"domain" yaml:"domain"` Path string `json:"path" yaml:"path"` Expires int `json:"expires" yaml:"expires"` HTTPOnly bool `json:"httpOnly" yaml:"http_only"` Secure bool `json:"secure" yaml:"secure"` SameSite string `json:"same_site" yaml:"same_site"` } func (c *Core) Init() { pwt, err := playwright.Run() if err != nil { log.Errorln("[core]", "初始化playwright失败") log.Errorln("[core] ", err.Error()) return } c.pw = pwt browser, err := pwt.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ Args: []string{ "--disable-extensions", "--disable-gpu", "--start-maximized", "--no-sandbox", "--window-size=500,450", "--mute-audio", "--window-position=0,0", "--ignore-certificate-errors", "--ignore-ssl-errors", "--disable-features=RendererCodeIntegrity", "--disable-blink-features", "--disable-blink-features=AutomationControlled", }, Channel: nil, ChromiumSandbox: nil, Devtools: nil, DownloadsPath: nil, ExecutablePath: nil, HandleSIGHUP: nil, HandleSIGINT: nil, HandleSIGTERM: nil, Headless: playwright.Bool(!c.ShowBrowser), Proxy: nil, SlowMo: nil, Timeout: nil, }) if err != nil { log.Errorln("[core] ", "初始化chrome失败") log.Errorln("[core] ", err.Error()) return } c.browser = browser context, err := c.browser.NewContext() if err != nil { return } c.context = &context } func (c *Core) Quit() { err := (*c.context).Close() if err != nil { return } err = c.browser.Close() if err != nil { return } err = c.pw.Stop() if err != nil { return } } func (c *Core) IsQuit() bool { return !c.browser.IsConnected() } func (c *Core) Login() ([]Cookie, error) { defer func() { i := recover() if i != nil { log.Errorln("登录模块出现无法挽救的错误") log.Errorln(i) } }() page, err := (*c.context).NewPage() if err != nil { return nil, err } _, err = page.Goto("https://pc.xuexi.cn/points/login.html", playwright.PageGotoOptions{ Referer: nil, Timeout: playwright.Float(30000), WaitUntil: playwright.WaitUntilStateDomcontentloaded, }) if err != nil { log.Errorln("[core] ", "打开登录页面失败") log.Errorln("[core] ", err.Error()) return nil, err } log.Infoln("[core] ", "正在等待二维码加载") if runtime.GOOS == "windows" { time.Sleep(3 * time.Second) } else { _, _ = page.WaitForSelector(`#app > div > div.login_content > div > div.login_qrcode `, playwright.PageWaitForSelectorOptions{ State: playwright.WaitForSelectorStateVisible, }) } _, err = page.Evaluate(`let h = document.body.scrollWidth/2;document.documentElement.scrollTop=h;`) if err != nil { fmt.Println(err.Error()) return nil, err } log.Infoln("[core] ", "加载验证码中,请耐心等待") //frame := page.Frame(playwright.PageFrameOptions{ // Name: playwright.String(`ddlogin-iframe`), // URL: nil, //}) //if frame == nil { // log.Errorln("获取frame失败") //} removeNode(page) screen, _ := page.Screenshot() var result []byte buffer := bytes.NewBuffer(result) //os.WriteFile("screen1.png", screen, 0666) //_ = Clip(bytes.NewReader(screen), buffer, 0, 0, 525, 35, 755, 255, 0) _ = Clip(bytes.NewReader(screen), buffer, 0, 0, 529, 70, 748, 284, 0) c.Push("markdown", fmt.Sprintf("![screenshot](%v) \n>点开查看登录二维码\n>请在五分钟内完成扫码", "data:image/png;base64,"+base64.StdEncoding.EncodeToString(buffer.Bytes()))) c.Push("image", base64.StdEncoding.EncodeToString(buffer.Bytes())) os.WriteFile("screen.png", buffer.Bytes(), 0666) matrix := GetPaymentStr(bytes.NewReader(buffer.Bytes())) log.Debugln("已获取到二维码内容:" + matrix.GetText()) c.Push("text", "https://techxuexi.js.org/jump/techxuexi-20211023.html?"+matrix.GetText()) c.Push("text", matrix.GetText()) qrcodeTerminal.New2(qrcodeTerminal.ConsoleColors.BrightBlack, qrcodeTerminal.ConsoleColors.BrightWhite, qrcodeTerminal.QRCodeRecoveryLevels.Low).Get(matrix.GetText()).Print() _, err = page.WaitForNavigation(playwright.PageWaitForNavigationOptions{ Timeout: playwright.Float(30 * 1000 * 5), URL: nil, WaitUntil: nil, }) if err != nil { log.Errorln(err.Error()) return nil, err } cookies, err := (*c.context).Cookies() //nolint:wsl if err != nil { log.Errorln("[core] ", "获取cookie失败") return nil, err } var ( cos []Cookie ) for _, c := range cookies { co := Cookie{} co.Name = c.Name co.Path = c.Path co.Value = c.Value co.Domain = c.Domain co.Expires = c.Expires co.HTTPOnly = c.HttpOnly co.SameSite = c.SameSite co.Secure = c.Secure cos = append(cos, co) } info, nick, err := GetUserInfo(cos) if err != nil { return nil, err } c.Push("text", "登录成功,用户名:"+nick) err = SaveUser(User{ Cookies: cos, Nick: nick, Uid: info, Time: time.Now().Add(time.Hour * 24).Unix(), }) if err != nil { return nil, err } return cos, err } func removeNode(page playwright.Page) { page.Evaluate(`document.getElementsByClassName("layout-header")[0].remove()`) //nolint:errcheck page.Evaluate(`document.getElementsByClassName("layout-footer")[0].remove()`) //nolint:errcheck page.Evaluate(`document.getElementsByClassName("redflag-2")[0].remove()`) //nolint:errcheck page.Evaluate(`document.getElementsByClassName("ddlogintext")[0].remove()`) //nolint:errcheck page.Evaluate(`document.getElementsByClassName("oath")[0].remove()`) } // Clip //* 图片裁剪 //* 入参:图片输入、输出、缩略图宽、缩略图高、Rectangle{Pt(x0, y0), Pt(x1, y1)},精度 //* 规则:如果精度为0则精度保持不变 //* //* 返回:error // */ func Clip(in io.Reader, out io.Writer, wi, hi, x0, y0, x1, y1, quality int) (err error) { err = errors.New("unknow error") defer func() { if r := recover(); r != nil { log.Println(r) } }() var origin image.Image var fm string origin, fm, err = image.Decode(in) if err != nil { log.Println(err) return err } if wi == 0 || hi == 0 { wi = origin.Bounds().Max.X hi = origin.Bounds().Max.Y } var canvas image.Image if wi != origin.Bounds().Max.X { //先缩略 canvas = resize.Thumbnail(uint(wi), uint(hi), origin, resize.Lanczos3) } else { canvas = origin } switch fm { case "jpeg": img := canvas.(*image.YCbCr) subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.YCbCr) return jpeg.Encode(out, subImg, &jpeg.Options{quality}) case "png": switch canvas.(type) { case *image.NRGBA: img := canvas.(*image.NRGBA) subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.NRGBA) return png.Encode(out, subImg) case *image.RGBA: img := canvas.(*image.RGBA) subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.RGBA) return png.Encode(out, subImg) } case "gif": img := canvas.(*image.Paletted) subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.Paletted) return gif.Encode(out, subImg, &gif.Options{}) case "bmp": img := canvas.(*image.RGBA) subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.RGBA) return bmp.Encode(out, subImg) default: return errors.New("ERROR FORMAT") } return nil } func GetPaymentStr(fi io.Reader) (paymentCodeUrl *gozxing.Result) { img, _, err := image.Decode(fi) if err != nil { fmt.Println(err) } // prepare BinaryBitmap bmp, _ := gozxing.NewBinaryBitmapFromImage(img) // decode image qrReader := qrcode.NewQRCodeReader() result, err := qrReader.Decode(bmp, nil) if err != nil { fmt.Println(err) } return result }