2021-11-12 07:46:33 +00:00
|
|
|
|
package lib
|
|
|
|
|
|
|
|
|
|
import (
|
2021-11-22 12:07:55 +00:00
|
|
|
|
"errors"
|
2021-11-12 07:46:33 +00:00
|
|
|
|
"fmt"
|
2022-02-14 13:45:20 +00:00
|
|
|
|
"net/url"
|
2022-02-24 06:14:30 +00:00
|
|
|
|
"os"
|
2022-02-11 10:19:34 +00:00
|
|
|
|
"runtime"
|
2022-03-06 14:03:14 +00:00
|
|
|
|
"strings"
|
2021-11-17 09:39:48 +00:00
|
|
|
|
"time"
|
2021-11-12 07:46:33 +00:00
|
|
|
|
|
2022-03-06 14:03:14 +00:00
|
|
|
|
"github.com/google/uuid"
|
2022-08-05 09:14:14 +00:00
|
|
|
|
"github.com/playwright-community/playwright-go"
|
2021-11-12 07:46:33 +00:00
|
|
|
|
log "github.com/sirupsen/logrus"
|
2022-03-17 15:11:43 +00:00
|
|
|
|
|
2022-12-14 07:43:39 +00:00
|
|
|
|
"github.com/sjkhsl/study_xxqg/conf"
|
|
|
|
|
"github.com/sjkhsl/study_xxqg/model"
|
|
|
|
|
"github.com/sjkhsl/study_xxqg/utils"
|
2021-11-12 07:46:33 +00:00
|
|
|
|
)
|
|
|
|
|
|
2022-02-24 06:14:30 +00:00
|
|
|
|
// Core
|
|
|
|
|
// @Description:
|
2021-11-12 07:46:33 +00:00
|
|
|
|
type Core struct {
|
2021-11-12 08:44:19 +00:00
|
|
|
|
pw *playwright.Playwright
|
|
|
|
|
browser playwright.Browser
|
|
|
|
|
ShowBrowser bool
|
2022-08-15 07:07:03 +00:00
|
|
|
|
Push func(id string, kind string, message string)
|
2021-11-12 07:46:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 06:14:30 +00:00
|
|
|
|
// Cookie
|
|
|
|
|
// @Description:
|
2021-11-17 09:39:48 +00:00
|
|
|
|
type Cookie struct {
|
2021-11-12 07:46:33 +00:00
|
|
|
|
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"`
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-06 14:03:14 +00:00
|
|
|
|
type signResp struct {
|
|
|
|
|
Data struct {
|
|
|
|
|
Sign string `json:"sign"`
|
|
|
|
|
} `json:"data"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
Code int `json:"code"`
|
|
|
|
|
Error interface{} `json:"error"`
|
|
|
|
|
Ok bool `json:"ok"`
|
|
|
|
|
}
|
|
|
|
|
type gennerateResp struct {
|
|
|
|
|
Success bool `json:"success"`
|
|
|
|
|
ErrorCode interface{} `json:"errorCode"`
|
|
|
|
|
ErrorMsg interface{} `json:"errorMsg"`
|
|
|
|
|
Result string `json:"result"`
|
|
|
|
|
Arguments interface{} `json:"arguments"`
|
|
|
|
|
}
|
|
|
|
|
type checkQrCodeResp struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
Success bool `json:"success"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
Data string `json:"data"`
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 06:14:30 +00:00
|
|
|
|
// Init
|
|
|
|
|
/**
|
|
|
|
|
* @Description:
|
|
|
|
|
* @receiver c
|
|
|
|
|
*/
|
2021-11-12 07:46:33 +00:00
|
|
|
|
func (c *Core) Init() {
|
2022-02-24 06:14:30 +00:00
|
|
|
|
if runtime.GOOS == "windows" {
|
2022-05-03 11:35:14 +00:00
|
|
|
|
c.initWindows()
|
2022-02-24 06:14:30 +00:00
|
|
|
|
} else {
|
|
|
|
|
c.initNotWindows()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-24 07:56:12 +00:00
|
|
|
|
func GetToken(code, sign, pushId string) (bool, error) {
|
2022-08-14 04:14:46 +00:00
|
|
|
|
client := utils.GetClient()
|
2022-04-20 13:31:46 +00:00
|
|
|
|
response, err := client.R().SetQueryParams(map[string]string{
|
|
|
|
|
"code": code,
|
|
|
|
|
"state": sign + uuid.New().String(),
|
|
|
|
|
}).Get("https://pc-api.xuexi.cn/login/secure_check")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln(err.Error())
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
uid, nick, err := GetUserInfo(response.Cookies())
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln(err.Error())
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
var token string
|
|
|
|
|
|
|
|
|
|
for _, cookie := range response.Cookies() {
|
|
|
|
|
if cookie.Name == "token" {
|
|
|
|
|
token = cookie.Value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
user := &model.User{
|
|
|
|
|
Nick: nick,
|
2022-11-08 07:44:47 +00:00
|
|
|
|
Uid: uid,
|
2022-04-20 13:31:46 +00:00
|
|
|
|
Token: token,
|
2022-05-03 11:35:14 +00:00
|
|
|
|
LoginTime: time.Now().Unix(),
|
2022-07-24 07:56:12 +00:00
|
|
|
|
PushId: pushId,
|
2022-04-20 13:31:46 +00:00
|
|
|
|
}
|
|
|
|
|
err = model.AddUser(user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln(err.Error())
|
|
|
|
|
}
|
|
|
|
|
log.Infoln("添加数据库成功")
|
|
|
|
|
return true, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 03:27:57 +00:00
|
|
|
|
// GenerateCode
|
|
|
|
|
/* @Description: 生成二维码
|
2022-04-11 03:19:26 +00:00
|
|
|
|
* @receiver c
|
2022-07-04 03:27:57 +00:00
|
|
|
|
* @return string 二维码连接
|
|
|
|
|
* @return string 二维码回调查询的code
|
2022-04-11 03:19:26 +00:00
|
|
|
|
* @return error
|
|
|
|
|
*/
|
2022-08-15 07:07:03 +00:00
|
|
|
|
func (c *Core) GenerateCode(pushID string) (string, string, error) {
|
2022-08-14 04:14:46 +00:00
|
|
|
|
client := utils.GetClient()
|
2022-03-06 14:03:14 +00:00
|
|
|
|
g := new(gennerateResp)
|
2022-07-04 03:27:57 +00:00
|
|
|
|
_, err := client.R().SetResult(g).Get("https://login.xuexi.cn/user/qrcode/generate")
|
2022-03-06 14:03:14 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln(err.Error())
|
2022-07-04 03:27:57 +00:00
|
|
|
|
return "", "", err
|
2022-03-06 14:03:14 +00:00
|
|
|
|
}
|
|
|
|
|
log.Infoln(g.Result)
|
|
|
|
|
codeURL := fmt.Sprintf("https://login.xuexi.cn/login/qrcommit?showmenu=false&code=%v&appId=dingoankubyrfkttorhpou", g.Result)
|
2022-10-31 07:10:29 +00:00
|
|
|
|
log.Infoln("登录链接: " + conf.GetConfig().Scheme + url.QueryEscape(codeURL))
|
2022-08-20 03:04:13 +00:00
|
|
|
|
c.Push(pushID, "flush", conf.GetConfig().Scheme+url.QueryEscape(codeURL))
|
2022-10-18 04:20:02 +00:00
|
|
|
|
c.Push(pushID, "flush", "请在一分钟内点击链接登录")
|
2022-07-04 03:27:57 +00:00
|
|
|
|
return codeURL, g.Result, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-31 12:33:39 +00:00
|
|
|
|
func (c *Core) CheckQrCode(code, pushID string) (*model.User, bool, error) {
|
2022-08-14 04:14:46 +00:00
|
|
|
|
client := utils.GetClient()
|
2022-03-06 14:03:14 +00:00
|
|
|
|
checkQrCode := func() (bool, string) {
|
|
|
|
|
res := new(checkQrCodeResp)
|
|
|
|
|
_, err := client.R().SetResult(res).SetFormData(map[string]string{
|
2022-07-04 03:27:57 +00:00
|
|
|
|
"qrCode": code,
|
2022-03-06 14:03:14 +00:00
|
|
|
|
"goto": "https://oa.xuexi.cn",
|
|
|
|
|
"pdmToken": ""}).SetHeader("content-type", "application/x-www-form-urlencoded;charset=UTF-8").Post("https://login.xuexi.cn/login/login_with_qr")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, ""
|
|
|
|
|
}
|
|
|
|
|
if res.Success {
|
|
|
|
|
return true, res.Data
|
|
|
|
|
}
|
2022-05-03 11:35:14 +00:00
|
|
|
|
return false, ""
|
2022-03-06 14:03:14 +00:00
|
|
|
|
}
|
2022-07-04 03:27:57 +00:00
|
|
|
|
qrCode, s := checkQrCode()
|
|
|
|
|
if !qrCode {
|
|
|
|
|
return nil, false, nil
|
|
|
|
|
} else {
|
|
|
|
|
sign := new(signResp)
|
|
|
|
|
_, err := client.R().SetResult(s).Get("https://pc-api.xuexi.cn/open/api/sns/sign")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln(err.Error())
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
s2 := strings.Split(s, "=")[1]
|
|
|
|
|
response, err := client.R().SetQueryParams(map[string]string{
|
|
|
|
|
"code": s2,
|
|
|
|
|
"state": sign.Data.Sign + uuid.New().String(),
|
|
|
|
|
}).Get("https://pc-api.xuexi.cn/login/secure_check")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uid, nick, err := GetUserInfo(response.Cookies())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
user := &model.User{
|
|
|
|
|
Nick: nick,
|
2022-11-08 07:44:47 +00:00
|
|
|
|
Uid: uid,
|
2022-07-04 03:27:57 +00:00
|
|
|
|
Token: response.Cookies()[0].Value,
|
|
|
|
|
LoginTime: time.Now().Unix(),
|
2022-07-31 12:33:39 +00:00
|
|
|
|
PushId: pushID,
|
2022-07-04 03:27:57 +00:00
|
|
|
|
}
|
|
|
|
|
err = model.AddUser(user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
2022-08-15 07:07:03 +00:00
|
|
|
|
c.Push(pushID, "text", "登录成功,用户名:"+nick)
|
2022-07-04 03:27:57 +00:00
|
|
|
|
return user, true, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// L
|
|
|
|
|
/**
|
|
|
|
|
* @Description:
|
|
|
|
|
* @receiver c
|
|
|
|
|
* @return *model.User
|
|
|
|
|
* @return error
|
|
|
|
|
*/
|
2022-07-31 12:33:39 +00:00
|
|
|
|
func (c *Core) L(retryTimes int, pushID string) (*model.User, error) {
|
2022-08-15 07:07:03 +00:00
|
|
|
|
_, codeData, err := c.GenerateCode(pushID)
|
2022-07-04 03:27:57 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-10-18 04:20:02 +00:00
|
|
|
|
for i := 0; i < 30; i++ {
|
2022-07-31 12:33:39 +00:00
|
|
|
|
user, b, err := c.CheckQrCode(codeData, pushID)
|
2022-07-04 03:27:57 +00:00
|
|
|
|
if b && err == nil {
|
2022-03-17 15:11:43 +00:00
|
|
|
|
return user, err
|
2022-03-06 14:03:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-03 11:35:14 +00:00
|
|
|
|
if retryTimes == 0 {
|
|
|
|
|
return nil, errors.New("time out")
|
|
|
|
|
}
|
2022-07-30 10:55:00 +00:00
|
|
|
|
// 等待几分钟后重新执行
|
|
|
|
|
time.Sleep(time.Duration(conf.GetConfig().Retry.Intervals) * time.Minute)
|
2022-08-26 00:25:07 +00:00
|
|
|
|
c.Push(pushID, "flush", fmt.Sprintf("登录超时,将进行第%d重新次登录", retryTimes))
|
2022-07-31 12:33:39 +00:00
|
|
|
|
return c.L(retryTimes-1, pushID)
|
2022-03-06 14:03:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 11:35:14 +00:00
|
|
|
|
func (c *Core) initWindows() {
|
2022-07-31 12:33:39 +00:00
|
|
|
|
_, err := os.Stat("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe")
|
|
|
|
|
if err != nil {
|
|
|
|
|
if os.IsNotExist(err) && conf.GetConfig().EdgePath == "" {
|
|
|
|
|
log.Warningln("检测到edge浏览器不存在并且未配置edge_path,将再次运行时自动下载chrome浏览器")
|
|
|
|
|
c.initNotWindows()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 06:14:30 +00:00
|
|
|
|
dir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-08-05 09:14:14 +00:00
|
|
|
|
|
|
|
|
|
pwo := &playwright.RunOptions{
|
2022-02-24 06:14:30 +00:00
|
|
|
|
DriverDirectory: dir + "/tools/driver/",
|
|
|
|
|
SkipInstallBrowsers: true,
|
|
|
|
|
Browsers: []string{"msedge"},
|
2022-08-05 09:14:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = playwright.Install(pwo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("[core]", "安装playwright失败")
|
|
|
|
|
log.Errorln("[core] ", err.Error())
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pwt, err := playwright.Run(pwo)
|
2022-02-24 06:14:30 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("[core]", "初始化playwright失败")
|
|
|
|
|
log.Errorln("[core] ", err.Error())
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
c.pw = pwt
|
2022-07-27 10:21:49 +00:00
|
|
|
|
path := conf.GetConfig().EdgePath
|
2022-02-24 06:14:30 +00:00
|
|
|
|
if path == "" {
|
|
|
|
|
path = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
|
|
|
|
|
}
|
|
|
|
|
browser, err := pwt.Chromium.Launch(playwright.BrowserTypeLaunchOptions{
|
|
|
|
|
Args: []string{
|
|
|
|
|
"--disable-extensions",
|
|
|
|
|
"--disable-gpu",
|
|
|
|
|
"--start-maximized",
|
|
|
|
|
"--no-sandbox",
|
|
|
|
|
"--window-size=500,450",
|
2022-04-11 03:19:26 +00:00
|
|
|
|
"--mute-audio",
|
2022-02-24 06:14:30 +00:00
|
|
|
|
"--window-position=0,0",
|
|
|
|
|
"--ignore-certificate-errors",
|
2022-04-11 03:19:26 +00:00
|
|
|
|
"--ignore-ssl-errors",
|
|
|
|
|
"--disable-features=RendererCodeIntegrity",
|
|
|
|
|
"--disable-blink-features",
|
|
|
|
|
"--disable-blink-features=AutomationControlled",
|
2022-02-24 06:14:30 +00:00
|
|
|
|
},
|
|
|
|
|
Channel: nil,
|
|
|
|
|
ChromiumSandbox: nil,
|
|
|
|
|
Devtools: nil,
|
2022-04-14 13:18:36 +00:00
|
|
|
|
DownloadsPath: playwright.String("./tools/temp/"),
|
2022-02-24 06:14:30 +00:00
|
|
|
|
ExecutablePath: playwright.String(path),
|
|
|
|
|
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
|
|
|
|
|
}
|
2022-04-11 03:19:26 +00:00
|
|
|
|
|
2022-02-24 06:14:30 +00:00
|
|
|
|
c.browser = browser
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Core) initNotWindows() {
|
|
|
|
|
dir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-06-04 08:33:56 +00:00
|
|
|
|
_, b := os.LookupEnv("PLAYWRIGHT_BROWSERS_PATH")
|
|
|
|
|
if !b {
|
|
|
|
|
err = os.Setenv("PLAYWRIGHT_BROWSERS_PATH", dir+"/tools/browser/")
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("设置环境变量PLAYWRIGHT_BROWSERS_PATH失败" + err.Error())
|
|
|
|
|
err = nil
|
|
|
|
|
}
|
2022-02-24 06:14:30 +00:00
|
|
|
|
}
|
2022-06-04 08:33:56 +00:00
|
|
|
|
|
2022-08-05 09:14:14 +00:00
|
|
|
|
pwo := &playwright.RunOptions{
|
2022-02-24 06:14:30 +00:00
|
|
|
|
DriverDirectory: dir + "/tools/driver/",
|
|
|
|
|
SkipInstallBrowsers: false,
|
2022-06-04 09:05:07 +00:00
|
|
|
|
Browsers: []string{"chromium"},
|
2022-08-05 09:14:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = playwright.Install(pwo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("[core]", "安装playwright失败")
|
|
|
|
|
log.Errorln("[core] ", err.Error())
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pwt, err := playwright.Run(pwo)
|
2021-11-12 07:46:33 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("[core]", "初始化playwright失败")
|
|
|
|
|
log.Errorln("[core] ", err.Error())
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
c.pw = pwt
|
2022-06-04 08:33:56 +00:00
|
|
|
|
browser, err := pwt.Chromium.Launch(playwright.BrowserTypeLaunchOptions{
|
2021-11-12 07:46:33 +00:00
|
|
|
|
Args: []string{
|
|
|
|
|
"--disable-extensions",
|
2022-02-24 06:14:30 +00:00
|
|
|
|
"--disable-gpu",
|
2021-12-08 09:51:03 +00:00
|
|
|
|
"--start-maximized",
|
2021-11-12 07:46:33 +00:00
|
|
|
|
"--no-sandbox",
|
2021-11-22 12:07:55 +00:00
|
|
|
|
"--window-size=500,450",
|
2022-06-04 08:33:56 +00:00
|
|
|
|
"--mute-audio",
|
2021-11-12 07:46:33 +00:00
|
|
|
|
"--window-position=0,0",
|
|
|
|
|
"--ignore-certificate-errors",
|
2022-06-04 08:33:56 +00:00
|
|
|
|
"--ignore-ssl-errors",
|
|
|
|
|
"--disable-features=RendererCodeIntegrity",
|
|
|
|
|
"--disable-blink-features",
|
|
|
|
|
"--disable-blink-features=AutomationControlled",
|
2021-11-12 07:46:33 +00:00
|
|
|
|
},
|
|
|
|
|
Channel: nil,
|
|
|
|
|
ChromiumSandbox: nil,
|
|
|
|
|
Devtools: nil,
|
|
|
|
|
DownloadsPath: nil,
|
|
|
|
|
ExecutablePath: nil,
|
|
|
|
|
HandleSIGHUP: nil,
|
|
|
|
|
HandleSIGINT: nil,
|
|
|
|
|
HandleSIGTERM: nil,
|
2021-11-15 13:52:59 +00:00
|
|
|
|
Headless: playwright.Bool(!c.ShowBrowser),
|
2021-11-12 07:46:33 +00:00
|
|
|
|
Proxy: nil,
|
|
|
|
|
SlowMo: nil,
|
|
|
|
|
Timeout: nil,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("[core] ", "初始化chrome失败")
|
|
|
|
|
log.Errorln("[core] ", err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
c.browser = browser
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Core) Quit() {
|
2022-07-30 10:55:00 +00:00
|
|
|
|
err := c.browser.Close()
|
2021-11-12 07:46:33 +00:00
|
|
|
|
if err != nil {
|
2022-10-03 03:05:57 +00:00
|
|
|
|
log.Errorln("关闭浏览器失败" + err.Error())
|
2021-11-12 07:46:33 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
err = c.pw.Stop()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 14:44:28 +00:00
|
|
|
|
func (c *Core) IsQuit() bool {
|
|
|
|
|
return !c.browser.IsConnected()
|
|
|
|
|
}
|