From 439d979bbbe288805c1fbac4a2cd357c13f181a1 Mon Sep 17 00:00:00 2001 From: johlanse Date: Fri, 27 May 2022 21:37:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A3=80=E6=9F=A5=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 +- go.mod | 3 + go.sum | 4 + lib/respond.go | 4 +- main.go | 18 +++ utils/update/doc.go | 2 + utils/update/update.go | 249 +++++++++++++++++++++++++++++++++ utils/update/update_other.go | 52 +++++++ utils/update/update_test.go | 15 ++ utils/update/update_windows.go | 40 ++++++ 10 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 utils/update/doc.go create mode 100644 utils/update/update.go create mode 100644 utils/update/update_other.go create mode 100644 utils/update/update_test.go create mode 100644 utils/update/update_windows.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43126e4..eb1977c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,8 +43,9 @@ jobs: if [ $GOOS = "windows" ]; then export BINARY_SUFFIX="$BINARY_SUFFIX.exe"; fi if $IS_PR ; then echo $PR_PROMPT; fi export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX" + export LD_FLAGS="-w -s -X main.Version=${COMMIT_ID::7}" go mod tidy - go build -o "output/$BINARY_NAME" ./ + go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" ./ - name: Upload artifact uses: actions/upload-artifact@v2 if: ${{ !github.head_ref }} diff --git a/go.mod b/go.mod index 292d9e0..ed928b7 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,14 @@ replace github.com/willf/bitset v1.2.1 => github.com/bits-and-blooms/bitset v1.2 require ( github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f + github.com/dustin/go-humanize v1.0.0 github.com/gin-gonic/gin v1.7.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.4.0-beta.0 github.com/google/uuid v1.3.0 github.com/guonaihong/gout v0.2.9 github.com/imroc/req/v3 v3.8.2 + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 + github.com/klauspost/compress v1.15.5 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/makiuchi-d/gozxing v0.1.1 github.com/mxschmitt/playwright-go v0.1400.0 diff --git a/go.sum b/go.sum index c42f6a6..384bfa7 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,12 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA= +github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= diff --git a/lib/respond.go b/lib/respond.go index b0260a8..90eec62 100644 --- a/lib/respond.go +++ b/lib/respond.go @@ -60,7 +60,7 @@ func (c *Core) RespondDaily(user *model.User, model string) { log.Errorln("添加cookie信息失败,已退出答题") return } - page.Goto("https://pc.xuexi.cn/points/my-points.html") + _, _ = page.Goto("https://pc.xuexi.cn/points/my-points.html") log.Infoln("已加载答题模块") @@ -70,7 +70,7 @@ func (c *Core) RespondDaily(user *model.User, model string) { WaitUntil: playwright.WaitUntilStateDomcontentloaded, }) if err != nil { - log.Errorln("跳转页面失败") + log.Errorln("跳转页面失败" + err.Error()) return } switch model { diff --git a/main.go b/main.go index c36eca2..6a5dcb2 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "io" "os" @@ -16,12 +17,21 @@ import ( "github.com/huoxue1/study_xxqg/lib" "github.com/huoxue1/study_xxqg/model" "github.com/huoxue1/study_xxqg/push" + "github.com/huoxue1/study_xxqg/utils/update" "github.com/huoxue1/study_xxqg/web" ) +var ( + u bool +) + var VERSION = "unknown" func init() { + + flag.BoolVar(&u, "u", false, "update the study_xxqg") + flag.Parse() + config = lib.GetConfig() logFormatter := &easy.Formatter{ TimestampFormat: "2006-01-02 15:04:05", @@ -64,6 +74,14 @@ func init() { } func main() { + go update.CheckUpdate(VERSION) + + if u { + update.SelfUpdate("", VERSION) + log.Infoln("请重启应用") + os.Exit(1) + } + if config.Web.Enable { engine := web.RouterInit() go func() { diff --git a/utils/update/doc.go b/utils/update/doc.go new file mode 100644 index 0000000..84e13a1 --- /dev/null +++ b/utils/update/doc.go @@ -0,0 +1,2 @@ +// Package update 该包为程序自我更新,代码源于https://github.com/Mrs4s/go-cqhttp/blob/master/internal/selfupdate/update.go +package update diff --git a/utils/update/update.go b/utils/update/update.go new file mode 100644 index 0000000..2d25f95 --- /dev/null +++ b/utils/update/update.go @@ -0,0 +1,249 @@ +package update + +import ( + "bufio" + "encoding/hex" + "fmt" + "hash" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/dustin/go-humanize" + "github.com/kardianos/osext" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" +) + +// CheckUpdate 检查更新 +func CheckUpdate(version string) { + log.Infof("正在检查更新.") + if version == "(devel)" { + log.Warnf("检查更新失败: 使用的 Actions 测试版或自编译版本.") + return + } + if version == "unknown" { + log.Warnf("检查更新失败: 使用的未知版本.") + return + } + + if !strings.HasPrefix(version, "v") { + log.Warnf("版本格式错误") + return + } + latest, err := lastVersion() + if err != nil { + log.Warnf("检查更新失败: %v", err) + return + } + if versionCompare(version, latest) { + log.Infof("当前有更新的 study_xxqg 可供更新, 请前往 https://github.com/johlanse/study_xxqg/releases 下载.") + log.Infof("当前版本: %v 最新版本: %v", version, latest) + return + } + log.Infof("检查更新完成. 当前已运行最新版本.") +} + +func readLine() (str string) { + console := bufio.NewReader(os.Stdin) + str, _ = console.ReadString('\n') + str = strings.TrimSpace(str) + return +} + +func lastVersion() (string, error) { + response, err := http.Get("https://api.github.com/repos/johlanse/study_xxqg/releases/latest") + if err != nil { + return "", err + } + data, _ := io.ReadAll(response.Body) + defer response.Body.Close() + return gjson.GetBytes(data, "tag_name").Str, nil +} + +// +// versionCompare +// @Description: 检测是否有更新,有返回true +// @param nowVersion +// @param lastVersion +// @return bool +// +func versionCompare(nowVersion, lastVersion string) bool { + NowBeta := strings.Contains(nowVersion, "beta") + LastBeta := strings.Contains(lastVersion, "beta") + + // 获取主要版本号 + nowMainVersion := strings.Split(nowVersion, "-") + lastMainVersion := strings.Split(lastVersion, "-") + + nowMainIntVersion, _ := strconv.Atoi(strings.ReplaceAll(strings.TrimLeft(nowMainVersion[0], "v"), ".", "")) + lastMainIntVersion, _ := strconv.Atoi(strings.ReplaceAll(strings.TrimLeft(lastMainVersion[0], "v"), ".", "")) + + if nowMainIntVersion < lastMainIntVersion { + return true + } + // 如果最新版本是beta + if LastBeta { + // 如果当前版本也是beta + if NowBeta { + // 对beta后面的数字进行比较 + nowBetaVersion, _ := strconv.Atoi(strings.TrimLeft(nowMainVersion[1], "beta")) + lastBetaVersion, _ := strconv.Atoi(strings.TrimLeft(lastMainVersion[1], "beta")) + if nowBetaVersion < lastBetaVersion { + return true + } + return false + // 如果当前版本部署beta,需要更新 + } else { + return true + } + // 最新版本不是beta,需要更新 + } else { + return false + } +} + +func binaryName() string { + goarch := runtime.GOARCH + if goarch == "arm" { + goarch += "v7" + } + ext := "tar.gz" + if runtime.GOOS == "windows" { + ext = "zip" + } + return fmt.Sprintf("study_xxqg_%v_%v.%v", runtime.GOOS, goarch, ext) +} + +func checksum(github, version string) []byte { + sumURL := fmt.Sprintf("%v/johlanse/study_xxqg/releases/download/%v/study_xxqg_checksums.txt", github, version) + response, err := http.Get(sumURL) + if err != nil { + return nil + } + rd := bufio.NewReader(response.Body) + for { + str, err := rd.ReadString('\n') + if err != nil { + break + } + str = strings.TrimSpace(str) + if strings.HasSuffix(str, binaryName()) { + sum, _ := hex.DecodeString(strings.TrimSuffix(str, " "+binaryName())) + return sum + } + } + return nil +} + +func wait() { + log.Info("按 Enter 继续....") + readLine() + os.Exit(0) +} + +// SelfUpdate 自更新 +func SelfUpdate(github string, version string) { + if github == "" { + github = "https://github.com" + } + + log.Infof("正在检查更新.") + latest, err := lastVersion() + if err != nil { + log.Warnf("获取最新版本失败: %v", err) + wait() + } + url := fmt.Sprintf("%v/johlanse/study_xxqg/releases/download/%v/%v", github, latest, binaryName()) + if version == latest { + log.Info("当前版本已经是最新版本!") + wait() + } + log.Info("当前最新版本为 ", latest) + log.Info("正在更新,请稍等...") + sum := checksum(github, latest) + if sum != nil { + err = update(url, sum) + if err != nil { + log.Error("更新失败: ", err) + } else { + log.Info("更新成功!") + } + } else { + log.Error("checksum 失败!") + } +} + +// writeSumCounter 写入量计算实例 +type writeSumCounter struct { + total uint64 + hash hash.Hash +} + +// Write 方法将写入的byte长度追加至写入的总长度Total中 +func (wc *writeSumCounter) Write(p []byte) (int, error) { + n := len(p) + wc.total += uint64(n) + wc.hash.Write(p) + fmt.Printf("\r ") + fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.total)) + return n, nil +} + +// FromStream copy form getlantern/go-update +func fromStream(updateWith io.Reader) (err error, errRecover error) { + updatePath, err := osext.Executable() + if err != nil { + return + } + + // get the directory the executable exists in + updateDir := filepath.Dir(updatePath) + filename := filepath.Base(updatePath) + // Copy the contents of of newbinary to a the new executable file + newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) + fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return + } + // We won't log this error, because it's always going to happen. + defer func() { _ = fp.Close() }() + if _, err = io.Copy(fp, bufio.NewReader(updateWith)); err != nil { + log.Errorf("Unable to copy data: %v\n", err) + } + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + if err := fp.Close(); err != nil { + log.Errorf("Unable to close file: %v\n", err) + } + // this is where we'll move the executable to so that we can swap in the updated replacement + oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) + + // delete any existing old exec file - this is necessary on Windows for two reasons: + // 1. after a successful update, Windows can't remove the .old file because the process is still running + // 2. windows rename operations fail if the destination file already exists + _ = os.Remove(oldPath) + + // move the existing executable to a new file in the same directory + err = os.Rename(updatePath, oldPath) + if err != nil { + return + } + + // move the new executable in to become the new program + err = os.Rename(newPath, updatePath) + + if err != nil { + // copy unsuccessful + errRecover = os.Rename(oldPath, updatePath) + } else { + // copy successful, remove the old binary + _ = os.Remove(oldPath) + } + return +} diff --git a/utils/update/update_other.go b/utils/update/update_other.go new file mode 100644 index 0000000..11e36e8 --- /dev/null +++ b/utils/update/update_other.go @@ -0,0 +1,52 @@ +//go:build !windows +// +build !windows + +package update + +import ( + "archive/tar" + "bytes" + "crypto/sha256" + "errors" + "io" + "net/http" + + "github.com/klauspost/compress/gzip" +) + +// update study_xxqg自我更新 +func update(url string, sum []byte) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + wc := writeSumCounter{ + hash: sha256.New(), + } + rsp, err := io.ReadAll(io.TeeReader(resp.Body, &wc)) + if err != nil { + return err + } + if !bytes.Equal(wc.hash.Sum(nil), sum) { + return errors.New("文件已损坏") + } + gr, err := gzip.NewReader(bytes.NewReader(rsp)) + if err != nil { + return err + } + tr := tar.NewReader(gr) + for { + header, err := tr.Next() + if err != nil { + return err + } + if header.Name == "study_xxqg" { + err, _ := fromStream(tr) + if err != nil { + return err + } + return nil + } + } +} diff --git a/utils/update/update_test.go b/utils/update/update_test.go new file mode 100644 index 0000000..38c0719 --- /dev/null +++ b/utils/update/update_test.go @@ -0,0 +1,15 @@ +package update + +import "testing" + +func Test_versionCompare(t *testing.T) { + println(versionCompare("v2.0.1", "v2.0.3")) + println(versionCompare("v2.0.2", "v2.0.2")) + println(versionCompare("v2.0.2", "v2.0.2-beta1")) + println(versionCompare("v2.0.2-beta1", "v2.0.2-beta3")) + println(versionCompare("v2.0.2-beta1", "v2.0.2-beta1")) +} + +func Test_CheckUpdate(t *testing.T) { + CheckUpdate("v1.0.22") +} diff --git a/utils/update/update_windows.go b/utils/update/update_windows.go new file mode 100644 index 0000000..8207316 --- /dev/null +++ b/utils/update/update_windows.go @@ -0,0 +1,40 @@ +package update + +import ( + "bytes" + "crypto/sha256" + "errors" + "io" + "net/http" + + "github.com/klauspost/compress/zip" +) + +// update study_xxqg自我更新 +func update(url string, sum []byte) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + wc := writeSumCounter{ + hash: sha256.New(), + } + rsp, err := io.ReadAll(io.TeeReader(resp.Body, &wc)) + if err != nil { + return err + } + if !bytes.Equal(wc.hash.Sum(nil), sum) { + return errors.New("文件已损坏") + } + reader, _ := zip.NewReader(bytes.NewReader(rsp), resp.ContentLength) + file, err := reader.Open("study_xxqg.exe") + if err != nil { + return err + } + err, _ = fromStream(file) + if err != nil { + return err + } + return nil +}