mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(app): 兼容darwin版本 #30
This commit is contained in:
parent
8b94e14ec9
commit
71bfed3744
255
app.go
255
app.go
@ -1,11 +1,8 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"go-stock/backend/data"
|
"go-stock/backend/data"
|
||||||
"go-stock/backend/db"
|
"go-stock/backend/db"
|
||||||
@ -21,13 +18,9 @@ import (
|
|||||||
"github.com/duke-git/lancet/v2/mathutil"
|
"github.com/duke-git/lancet/v2/mathutil"
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
"github.com/duke-git/lancet/v2/strutil"
|
"github.com/duke-git/lancet/v2/strutil"
|
||||||
"github.com/energye/systray"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/go-toast/toast"
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
"golang.org/x/sys/windows/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// App struct
|
// App struct
|
||||||
@ -107,60 +100,6 @@ func AddTools(tools []data.Tool) []data.Tool {
|
|||||||
return tools
|
return tools
|
||||||
}
|
}
|
||||||
|
|
||||||
// startup is called at application startup
|
|
||||||
func (a *App) startup(ctx context.Context) {
|
|
||||||
defer PanicHandler()
|
|
||||||
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
|
||||||
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
|
||||||
})
|
|
||||||
logger.SugaredLogger.Infof("Version:%s", Version)
|
|
||||||
// Perform your setup here
|
|
||||||
a.ctx = ctx
|
|
||||||
|
|
||||||
// 创建系统托盘
|
|
||||||
//systray.RunWithExternalLoop(func() {
|
|
||||||
// onReady(a)
|
|
||||||
//}, func() {
|
|
||||||
// onExit(a)
|
|
||||||
//})
|
|
||||||
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
|
||||||
logger.SugaredLogger.Infof("updateSettings : %v\n", optionalData)
|
|
||||||
config := &data.Settings{}
|
|
||||||
setMap := optionalData[0].(map[string]interface{})
|
|
||||||
|
|
||||||
// 将 map 转换为 JSON 字节切片
|
|
||||||
jsonData, err := json.Marshal(setMap)
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 将 JSON 字节切片解析到结构体中
|
|
||||||
err = json.Unmarshal(jsonData, config)
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
|
||||||
if config.DarkTheme {
|
|
||||||
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
|
||||||
runtime.WindowSetDarkTheme(ctx)
|
|
||||||
} else {
|
|
||||||
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
|
||||||
runtime.WindowSetLightTheme(ctx)
|
|
||||||
}
|
|
||||||
runtime.WindowReloadApp(ctx)
|
|
||||||
|
|
||||||
})
|
|
||||||
go systray.Run(func() {
|
|
||||||
onReady(a)
|
|
||||||
}, func() {
|
|
||||||
onExit(a)
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) CheckUpdate() {
|
func (a *App) CheckUpdate() {
|
||||||
releaseVersion := &models.GitHubReleaseVersion{}
|
releaseVersion := &models.GitHubReleaseVersion{}
|
||||||
_, err := resty.New().R().
|
_, err := resty.New().R().
|
||||||
@ -525,49 +464,6 @@ func MonitorFundPrices(a *App) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MonitorStockPrices(a *App) {
|
|
||||||
dest := &[]data.FollowedStock{}
|
|
||||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
|
||||||
total := float64(0)
|
|
||||||
//for _, follow := range *dest {
|
|
||||||
// stockData := getStockInfo(follow)
|
|
||||||
// total += stockData.ProfitAmountToday
|
|
||||||
// price, _ := convertor.ToFloat(stockData.Price)
|
|
||||||
// if stockData.PrePrice != price {
|
|
||||||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
stockInfos := GetStockInfos(*dest...)
|
|
||||||
for _, stockInfo := range *stockInfos {
|
|
||||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
total += stockInfo.ProfitAmountToday
|
|
||||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
|
||||||
|
|
||||||
if stockInfo.PrePrice != price {
|
|
||||||
//logger.SugaredLogger.Infof("-----------sz------------股票代码: %s, 股票名称: %s, 股票价格: %s,盘前盘后:%s", stockInfo.Code, stockInfo.Name, stockInfo.Price, stockInfo.BA)
|
|
||||||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if total != 0 {
|
|
||||||
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
|
||||||
systray.SetTooltip(title)
|
|
||||||
}
|
|
||||||
|
|
||||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
|
||||||
//runtime.WindowSetTitle(a.ctx, title)
|
|
||||||
|
|
||||||
}
|
|
||||||
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
||||||
stockInfos := make([]data.StockInfo, 0)
|
stockInfos := make([]data.StockInfo, 0)
|
||||||
stockCodes := make([]string, 0)
|
stockCodes := make([]string, 0)
|
||||||
@ -685,35 +581,6 @@ func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// beforeClose is called when the application is about to quit,
|
|
||||||
// either by clicking the window close button or calling runtime.Quit.
|
|
||||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
|
||||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
|
||||||
defer PanicHandler()
|
|
||||||
|
|
||||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
|
||||||
Type: runtime.QuestionDialog,
|
|
||||||
Title: "go-stock",
|
|
||||||
Message: "确定关闭吗?",
|
|
||||||
Buttons: []string{"确定"},
|
|
||||||
Icon: icon,
|
|
||||||
CancelButton: "取消",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
|
||||||
if dialog == "No" {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
systray.Quit()
|
|
||||||
a.cron.Stop()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// shutdown is called at application termination
|
// shutdown is called at application termination
|
||||||
func (a *App) shutdown(ctx context.Context) {
|
func (a *App) shutdown(ctx context.Context) {
|
||||||
defer PanicHandler()
|
defer PanicHandler()
|
||||||
@ -834,40 +701,40 @@ func (a *App) GetVersionInfo() *models.VersionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkChromeOnWindows 在 Windows 系统上检查谷歌浏览器是否安装
|
//// checkChromeOnWindows 在 Windows 系统上检查谷歌浏览器是否安装
|
||||||
func checkChromeOnWindows() bool {
|
//func checkChromeOnWindows() bool {
|
||||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
// key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
// // 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
// key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
defer key.Close()
|
// defer key.Close()
|
||||||
}
|
// }
|
||||||
defer key.Close()
|
// defer key.Close()
|
||||||
_, _, err = key.GetValue("Path", nil)
|
// _, _, err = key.GetValue("Path", nil)
|
||||||
return err == nil
|
// return err == nil
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// checkEdgeOnWindows 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
//// checkEdgeOnWindows 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
||||||
func checkEdgeOnWindows() (string, bool) {
|
//func checkEdgeOnWindows() (string, bool) {
|
||||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
// key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
// // 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
// key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", false
|
// return "", false
|
||||||
}
|
// }
|
||||||
defer key.Close()
|
// defer key.Close()
|
||||||
}
|
// }
|
||||||
defer key.Close()
|
// defer key.Close()
|
||||||
path, _, err := key.GetStringValue("Path")
|
// path, _, err := key.GetStringValue("Path")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", false
|
// return "", false
|
||||||
}
|
// }
|
||||||
return path, true
|
// return path, true
|
||||||
}
|
//}
|
||||||
|
|
||||||
func GetImageBase(bytes []byte) string {
|
func GetImageBase(bytes []byte) string {
|
||||||
return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(bytes)
|
return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(bytes)
|
||||||
@ -924,44 +791,6 @@ func onExit(a *App) {
|
|||||||
//runtime.Quit(a.ctx)
|
//runtime.Quit(a.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func onReady(a *App) {
|
|
||||||
|
|
||||||
// 初始化操作
|
|
||||||
logger.SugaredLogger.Infof("systray onReady")
|
|
||||||
systray.SetIcon(icon2)
|
|
||||||
systray.SetTitle("go-stock")
|
|
||||||
systray.SetTooltip("go-stock 股票行情实时获取")
|
|
||||||
// 创建菜单项
|
|
||||||
show := systray.AddMenuItem("显示", "显示应用程序")
|
|
||||||
show.Click(func() {
|
|
||||||
//logger.SugaredLogger.Infof("显示应用程序")
|
|
||||||
runtime.WindowShow(a.ctx)
|
|
||||||
})
|
|
||||||
hide := systray.AddMenuItem("隐藏", "隐藏应用程序")
|
|
||||||
hide.Click(func() {
|
|
||||||
//logger.SugaredLogger.Infof("隐藏应用程序")
|
|
||||||
runtime.WindowHide(a.ctx)
|
|
||||||
})
|
|
||||||
systray.AddSeparator()
|
|
||||||
mQuitOrig := systray.AddMenuItem("退出", "退出应用程序")
|
|
||||||
mQuitOrig.Click(func() {
|
|
||||||
//logger.SugaredLogger.Infof("退出应用程序")
|
|
||||||
runtime.Quit(a.ctx)
|
|
||||||
})
|
|
||||||
systray.SetOnRClick(func(menu systray.IMenu) {
|
|
||||||
menu.ShowMenu()
|
|
||||||
//logger.SugaredLogger.Infof("SetOnRClick")
|
|
||||||
})
|
|
||||||
systray.SetOnClick(func(menu systray.IMenu) {
|
|
||||||
//logger.SugaredLogger.Infof("SetOnClick")
|
|
||||||
menu.ShowMenu()
|
|
||||||
})
|
|
||||||
systray.SetOnDClick(func(menu systray.IMenu) {
|
|
||||||
menu.ShowMenu()
|
|
||||||
//logger.SugaredLogger.Infof("SetOnDClick")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
func (a *App) UpdateConfig(settings *data.Settings) string {
|
||||||
//logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
//logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
||||||
if settings.RefreshInterval > 0 {
|
if settings.RefreshInterval > 0 {
|
||||||
@ -1096,22 +925,6 @@ func (a *App) SetStockAICron(cronText, stockCode string) {
|
|||||||
a.cronEntrys[stockCode] = id
|
a.cronEntrys[stockCode] = id
|
||||||
|
|
||||||
}
|
}
|
||||||
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
|
||||||
notification := toast.Notification{
|
|
||||||
AppID: "go-stock",
|
|
||||||
Title: "go-stock",
|
|
||||||
Message: "程序已经在运行了",
|
|
||||||
Icon: "",
|
|
||||||
Duration: "short",
|
|
||||||
Audio: toast.Default,
|
|
||||||
}
|
|
||||||
err := notification.Push()
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Error(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) AddGroup(group data.Group) string {
|
func (a *App) AddGroup(group data.Group) string {
|
||||||
ok := data.NewStockGroupApi(db.Dao).AddGroup(group)
|
ok := data.NewStockGroupApi(db.Dao).AddGroup(group)
|
||||||
if ok {
|
if ok {
|
||||||
|
492
app_darwin.go
492
app_darwin.go
@ -5,304 +5,149 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
|
"github.com/duke-git/lancet/v2/strutil"
|
||||||
|
"github.com/gen2brain/beeep"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
"go-stock/backend/data"
|
"go-stock/backend/data"
|
||||||
"go-stock/backend/db"
|
"go-stock/backend/db"
|
||||||
"go-stock/backend/logger"
|
"go-stock/backend/logger"
|
||||||
"go-stock/backend/models"
|
"log"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/coocood/freecache"
|
|
||||||
"github.com/duke-git/lancet/v2/convertor"
|
|
||||||
"github.com/duke-git/lancet/v2/mathutil"
|
|
||||||
"github.com/duke-git/lancet/v2/slice"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// App struct
|
// startup 在应用程序启动时调用
|
||||||
type App struct {
|
|
||||||
ctx context.Context
|
|
||||||
cache *freecache.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates a new App application struct
|
|
||||||
func NewApp() *App {
|
|
||||||
cacheSize := 512 * 1024
|
|
||||||
cache := freecache.NewCache(cacheSize)
|
|
||||||
return &App{
|
|
||||||
cache: cache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// startup is called at application startup
|
|
||||||
func (a *App) startup(ctx context.Context) {
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
defer PanicHandler()
|
||||||
|
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
||||||
|
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
||||||
|
})
|
||||||
logger.SugaredLogger.Infof("Version:%s", Version)
|
logger.SugaredLogger.Infof("Version:%s", Version)
|
||||||
// Perform your setup here
|
// Perform your setup here
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
|
|
||||||
// TODO 创建系统托盘
|
// 监听设置更新事件
|
||||||
|
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
||||||
|
logger.SugaredLogger.Infof("updateSettings : %v\n", optionalData)
|
||||||
|
config := &data.Settings{}
|
||||||
|
setMap := optionalData[0].(map[string]interface{})
|
||||||
|
|
||||||
}
|
// 将 map 转换为 JSON 字节切片
|
||||||
|
jsonData, err := json.Marshal(setMap)
|
||||||
func checkUpdate(a *App) {
|
if err != nil {
|
||||||
releaseVersion := &models.GitHubReleaseVersion{}
|
logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
||||||
_, err := resty.New().R().
|
return
|
||||||
SetResult(releaseVersion).
|
|
||||||
Get("https://api.github.com/repos/ArvinLovegood/go-stock/releases/latest")
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("get github release version error:%s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.SugaredLogger.Infof("releaseVersion:%+v", releaseVersion.TagName)
|
|
||||||
if releaseVersion.TagName != Version {
|
|
||||||
go runtime.EventsEmit(a.ctx, "updateVersion", releaseVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// domReady is called after front-end resources have been loaded
|
|
||||||
func (a *App) domReady(ctx context.Context) {
|
|
||||||
// Add your action here
|
|
||||||
//定时更新数据
|
|
||||||
go func() {
|
|
||||||
config := data.NewSettingsApi(&data.Settings{}).GetConfig()
|
|
||||||
interval := config.RefreshInterval
|
|
||||||
if interval <= 0 {
|
|
||||||
interval = 1
|
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(time.Second * time.Duration(interval))
|
// 将 JSON 字节切片解析到结构体中
|
||||||
defer ticker.Stop()
|
err = json.Unmarshal(jsonData, config)
|
||||||
for range ticker.C {
|
if err != nil {
|
||||||
if isTradingTime(time.Now()) {
|
logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
||||||
MonitorStockPrices(a)
|
return
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(time.Second * time.Duration(60))
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
telegraph := refreshTelegraphList()
|
|
||||||
if telegraph != nil {
|
|
||||||
go runtime.EventsEmit(a.ctx, "telegraph", telegraph)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
||||||
go runtime.EventsEmit(a.ctx, "telegraph", refreshTelegraphList())
|
if config.DarkTheme {
|
||||||
go MonitorStockPrices(a)
|
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
||||||
|
runtime.WindowSetDarkTheme(ctx)
|
||||||
//检查新版本
|
} else {
|
||||||
go func() {
|
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
||||||
checkUpdate(a)
|
runtime.WindowSetLightTheme(ctx)
|
||||||
}()
|
}
|
||||||
}
|
runtime.WindowReloadApp(ctx)
|
||||||
|
|
||||||
func refreshTelegraphList() *[]string {
|
|
||||||
url := "https://www.cls.cn/telegraph"
|
|
||||||
response, err := resty.New().R().
|
|
||||||
SetHeader("Referer", "https://www.cls.cn/").
|
|
||||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60").
|
|
||||||
Get(fmt.Sprintf(url))
|
|
||||||
if err != nil {
|
|
||||||
return &[]string{}
|
|
||||||
}
|
|
||||||
//logger.SugaredLogger.Info(string(response.Body()))
|
|
||||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(string(response.Body())))
|
|
||||||
if err != nil {
|
|
||||||
return &[]string{}
|
|
||||||
}
|
|
||||||
var telegraph []string
|
|
||||||
document.Find("div.telegraph-content-box").Each(func(i int, selection *goquery.Selection) {
|
|
||||||
//logger.SugaredLogger.Info(selection.Text())
|
|
||||||
telegraph = append(telegraph, selection.Text())
|
|
||||||
})
|
})
|
||||||
return &telegraph
|
|
||||||
|
// 创建 macOS 托盘
|
||||||
|
go func() {
|
||||||
|
// 使用 Beeep 库替代 Windows 的托盘库
|
||||||
|
err := beeep.Notify("go-stock", "应用程序已启动", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("系统通知失败: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTradingDay 判断是否是交易日
|
// OnSecondInstanceLaunch 处理第二实例启动时的通知
|
||||||
func isTradingDay(date time.Time) bool {
|
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
||||||
weekday := date.Weekday()
|
err := beeep.Notify("go-stock", "程序已经在运行了", "")
|
||||||
// 判断是否是周末
|
if err != nil {
|
||||||
if weekday == time.Saturday || weekday == time.Sunday {
|
logger.SugaredLogger.Error(err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
// 这里可以添加具体的节假日判断逻辑
|
time.Sleep(time.Second * 3)
|
||||||
// 例如:判断是否是春节、国庆节等
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// isTradingTime 判断是否是交易时间
|
|
||||||
func isTradingTime(date time.Time) bool {
|
|
||||||
if !isTradingDay(date) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
hour, minute, _ := date.Clock()
|
|
||||||
|
|
||||||
// 判断是否在9:15到11:30之间
|
|
||||||
if (hour == 9 && minute >= 15) || (hour == 10) || (hour == 11 && minute <= 30) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否在13:00到15:00之间
|
|
||||||
if (hour == 13) || (hour == 14) || (hour == 15 && minute <= 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MonitorStockPrices(a *App) {
|
func MonitorStockPrices(a *App) {
|
||||||
dest := &[]data.FollowedStock{}
|
dest := &[]data.FollowedStock{}
|
||||||
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||||
total := float64(0)
|
total := float64(0)
|
||||||
//for _, follow := range *dest {
|
|
||||||
// stockData := getStockInfo(follow)
|
|
||||||
// total += stockData.ProfitAmountToday
|
|
||||||
// price, _ := convertor.ToFloat(stockData.Price)
|
|
||||||
// if stockData.PrePrice != price {
|
|
||||||
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
// 股票信息处理逻辑
|
||||||
stockInfos := GetStockInfos(*dest...)
|
stockInfos := GetStockInfos(*dest...)
|
||||||
for _, stockInfo := range *stockInfos {
|
for _, stockInfo := range *stockInfos {
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
total += stockInfo.ProfitAmountToday
|
total += stockInfo.ProfitAmountToday
|
||||||
price, _ := convertor.ToFloat(stockInfo.Price)
|
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||||
|
|
||||||
if stockInfo.PrePrice != price {
|
if stockInfo.PrePrice != price {
|
||||||
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算总收益并更新状态
|
||||||
if total != 0 {
|
if total != 0 {
|
||||||
// title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
// 使用通知替代 systray 更新 Tooltip
|
||||||
// systray.SetTooltip(title)
|
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||||
|
|
||||||
|
// 发送通知显示实时数据
|
||||||
|
err := beeep.Notify("go-stock", title, "")
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("发送通知失败: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 触发实时利润事件
|
||||||
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||||
//runtime.WindowSetTitle(a.ctx, title)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
func GetStockInfos(follows ...data.FollowedStock) *[]data.StockInfo {
|
|
||||||
stockCodes := make([]string, 0)
|
// onReady 在应用程序准备好时调用
|
||||||
for _, follow := range follows {
|
func onReady(a *App) {
|
||||||
stockCodes = append(stockCodes, follow.StockCode)
|
// 初始化操作
|
||||||
}
|
logger.SugaredLogger.Infof("onReady")
|
||||||
stockData, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCodes...)
|
|
||||||
|
// 使用 Beeep 发送通知
|
||||||
|
err := beeep.Notify("go-stock", "应用程序已准备就绪", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SugaredLogger.Errorf("get stock code real time data error:%s", err.Error())
|
log.Fatalf("系统通知失败: %v", err)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
stockInfos := make([]data.StockInfo, 0)
|
|
||||||
for _, info := range *stockData {
|
// 显示应用窗口
|
||||||
v, ok := slice.FindBy(follows, func(idx int, follow data.FollowedStock) bool {
|
runtime.WindowShow(a.ctx)
|
||||||
return follow.StockCode == info.Code
|
|
||||||
})
|
// 在 macOS 上没有系统托盘图标菜单,通常我们通过通知或其他方式提供与用户交互的界面
|
||||||
if ok {
|
|
||||||
addStockFollowData(v, &info)
|
|
||||||
stockInfos = append(stockInfos, info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &stockInfos
|
|
||||||
}
|
|
||||||
func getStockInfo(follow data.FollowedStock) *data.StockInfo {
|
|
||||||
stockCode := follow.StockCode
|
|
||||||
stockDatas, err := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
|
||||||
if err != nil || len(*stockDatas) == 0 {
|
|
||||||
return &data.StockInfo{}
|
|
||||||
}
|
|
||||||
stockData := (*stockDatas)[0]
|
|
||||||
addStockFollowData(follow, &stockData)
|
|
||||||
return &stockData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) {
|
// beforeClose 在应用程序关闭前调用,显示确认对话框
|
||||||
stockData.PrePrice = follow.Price //上次当前价格
|
|
||||||
stockData.Sort = follow.Sort
|
|
||||||
stockData.CostPrice = follow.CostPrice //成本价
|
|
||||||
stockData.CostVolume = follow.Volume //成本量
|
|
||||||
stockData.AlarmChangePercent = follow.AlarmChangePercent
|
|
||||||
stockData.AlarmPrice = follow.AlarmPrice
|
|
||||||
|
|
||||||
//当前价格
|
|
||||||
price, _ := convertor.ToFloat(stockData.Price)
|
|
||||||
//当前价格为0 时 使用卖一价格作为当前价格
|
|
||||||
if price == 0 {
|
|
||||||
price, _ = convertor.ToFloat(stockData.A1P)
|
|
||||||
}
|
|
||||||
//当前价格依然为0 时 使用买一报价作为当前价格
|
|
||||||
if price == 0 {
|
|
||||||
price, _ = convertor.ToFloat(stockData.B1P)
|
|
||||||
}
|
|
||||||
|
|
||||||
//昨日收盘价
|
|
||||||
preClosePrice, _ := convertor.ToFloat(stockData.PreClose)
|
|
||||||
|
|
||||||
//当前价格依然为0 时 使用昨日收盘价为当前价格
|
|
||||||
if price == 0 {
|
|
||||||
price = preClosePrice
|
|
||||||
}
|
|
||||||
|
|
||||||
//今日最高价
|
|
||||||
highPrice, _ := convertor.ToFloat(stockData.High)
|
|
||||||
if highPrice == 0 {
|
|
||||||
highPrice, _ = convertor.ToFloat(stockData.Open)
|
|
||||||
}
|
|
||||||
|
|
||||||
//今日最低价
|
|
||||||
lowPrice, _ := convertor.ToFloat(stockData.Low)
|
|
||||||
if lowPrice == 0 {
|
|
||||||
lowPrice, _ = convertor.ToFloat(stockData.Open)
|
|
||||||
}
|
|
||||||
//开盘价
|
|
||||||
//openPrice, _ := convertor.ToFloat(stockData.Open)
|
|
||||||
|
|
||||||
if price > 0 {
|
|
||||||
stockData.ChangePrice = mathutil.RoundToFloat(price-preClosePrice, 2)
|
|
||||||
stockData.ChangePercent = mathutil.RoundToFloat(mathutil.Div(price-preClosePrice, preClosePrice)*100, 3)
|
|
||||||
}
|
|
||||||
if highPrice > 0 {
|
|
||||||
stockData.HighRate = mathutil.RoundToFloat(mathutil.Div(highPrice-preClosePrice, preClosePrice)*100, 3)
|
|
||||||
}
|
|
||||||
if lowPrice > 0 {
|
|
||||||
stockData.LowRate = mathutil.RoundToFloat(mathutil.Div(lowPrice-preClosePrice, preClosePrice)*100, 3)
|
|
||||||
}
|
|
||||||
if follow.CostPrice > 0 && follow.Volume > 0 {
|
|
||||||
if price > 0 {
|
|
||||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(price-follow.CostPrice, follow.CostPrice)*100, 3)
|
|
||||||
stockData.ProfitAmount = mathutil.RoundToFloat((price-follow.CostPrice)*float64(follow.Volume), 2)
|
|
||||||
stockData.ProfitAmountToday = mathutil.RoundToFloat((price-preClosePrice)*float64(follow.Volume), 2)
|
|
||||||
} else {
|
|
||||||
//未开盘时当前价格为昨日收盘价
|
|
||||||
stockData.Profit = mathutil.RoundToFloat(mathutil.Div(preClosePrice-follow.CostPrice, follow.CostPrice)*100, 3)
|
|
||||||
stockData.ProfitAmount = mathutil.RoundToFloat((preClosePrice-follow.CostPrice)*float64(follow.Volume), 2)
|
|
||||||
// 未开盘时,今日盈亏为 0
|
|
||||||
stockData.ProfitAmountToday = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//logger.SugaredLogger.Debugf("stockData:%+v", stockData)
|
|
||||||
if follow.Price != price && price > 0 {
|
|
||||||
go db.Dao.Model(follow).Where("stock_code = ?", follow.StockCode).Updates(map[string]interface{}{
|
|
||||||
"price": price,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// beforeClose is called when the application is about to quit,
|
|
||||||
// either by clicking the window close button or calling runtime.Quit.
|
|
||||||
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
|
||||||
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||||
|
defer PanicHandler()
|
||||||
|
|
||||||
|
// 在 macOS 上使用 MessageDialog 显示确认窗口
|
||||||
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||||
Type: runtime.QuestionDialog,
|
Type: runtime.QuestionDialog,
|
||||||
Title: "go-stock",
|
Title: "go-stock",
|
||||||
Message: "确定关闭吗?",
|
Message: "确定关闭吗?",
|
||||||
Buttons: []string{"确定"},
|
Buttons: []string{"确定", "取消"},
|
||||||
Icon: icon,
|
Icon: icon,
|
||||||
CancelButton: "取消",
|
CancelButton: "取消",
|
||||||
})
|
})
|
||||||
@ -311,150 +156,13 @@ func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
|||||||
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||||
if dialog == "No" {
|
if dialog == "取消" {
|
||||||
return true
|
return true // 如果选择了取消,不关闭应用
|
||||||
}
|
} else {
|
||||||
return false
|
// 在 macOS 上应用退出时执行清理工作
|
||||||
}
|
a.cron.Stop() // 停止定时任务
|
||||||
|
return false // 如果选择了确定,继续关闭应用
|
||||||
// shutdown is called at application termination
|
|
||||||
func (a *App) shutdown(ctx context.Context) {
|
|
||||||
// Perform your teardown here
|
|
||||||
// systray.Quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greet returns a greeting for the given name
|
|
||||||
func (a *App) Greet(stockCode string) *data.StockInfo {
|
|
||||||
//stockInfo, _ := data.NewStockDataApi().GetStockCodeRealTimeData(stockCode)
|
|
||||||
|
|
||||||
follow := &data.FollowedStock{
|
|
||||||
StockCode: stockCode,
|
|
||||||
}
|
|
||||||
db.Dao.Model(follow).Where("stock_code = ?", stockCode).First(follow)
|
|
||||||
stockInfo := getStockInfo(*follow)
|
|
||||||
return stockInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) Follow(stockCode string) string {
|
|
||||||
return data.NewStockDataApi().Follow(stockCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UnFollow(stockCode string) string {
|
|
||||||
return data.NewStockDataApi().UnFollow(stockCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetFollowList() []data.FollowedStock {
|
|
||||||
return data.NewStockDataApi().GetFollowList()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetStockList(key string) []data.StockBasic {
|
|
||||||
return data.NewStockDataApi().GetStockList(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) SetCostPriceAndVolume(stockCode string, price float64, volume int64) string {
|
|
||||||
return data.NewStockDataApi().SetCostPriceAndVolume(price, volume, stockCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) SetAlarmChangePercent(val, alarmPrice float64, stockCode string) string {
|
|
||||||
return data.NewStockDataApi().SetAlarmChangePercent(val, alarmPrice, stockCode)
|
|
||||||
}
|
|
||||||
func (a *App) SetStockSort(sort int64, stockCode string) {
|
|
||||||
data.NewStockDataApi().SetStockSort(sort, stockCode)
|
|
||||||
}
|
|
||||||
func (a *App) SendDingDingMessage(message string, stockCode string) string {
|
|
||||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
|
||||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
|
||||||
if ttl > 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
err := a.cache.Set([]byte(stockCode), []byte("1"), 60*5)
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendDingDingMessageByType msgType 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
|
|
||||||
func (a *App) SendDingDingMessageByType(message string, stockCode string, msgType int) string {
|
|
||||||
ttl, _ := a.cache.TTL([]byte(stockCode))
|
|
||||||
logger.SugaredLogger.Infof("stockCode %s ttl:%d", stockCode, ttl)
|
|
||||||
if ttl > 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
err := a.cache.Set([]byte(stockCode), []byte("1"), getMsgTypeTTL(msgType))
|
|
||||||
if err != nil {
|
|
||||||
logger.SugaredLogger.Errorf("set cache error:%s", err.Error())
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
stockInfo := &data.StockInfo{}
|
|
||||||
db.Dao.Model(stockInfo).Where("code = ?", stockCode).First(stockInfo)
|
|
||||||
go data.NewAlertWindowsApi("go-stock消息通知", getMsgTypeName(msgType), GenNotificationMsg(stockInfo), "").SendNotification()
|
|
||||||
return data.NewDingDingAPI().SendDingDingMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) NewChat(stock string) string {
|
|
||||||
return data.NewDeepSeekOpenAi().NewChat(stock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) NewChatStream(stock, stockCode string) {
|
|
||||||
msgs := data.NewDeepSeekOpenAi().NewChatStream(stock, stockCode)
|
|
||||||
for msg := range msgs {
|
|
||||||
runtime.EventsEmit(a.ctx, "newChatStream", msg)
|
|
||||||
}
|
|
||||||
runtime.EventsEmit(a.ctx, "newChatStream", "DONE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenNotificationMsg(stockInfo *data.StockInfo) string {
|
|
||||||
Price, err := convertor.ToFloat(stockInfo.Price)
|
|
||||||
if err != nil {
|
|
||||||
Price = 0
|
|
||||||
}
|
|
||||||
PreClose, err := convertor.ToFloat(stockInfo.PreClose)
|
|
||||||
if err != nil {
|
|
||||||
PreClose = 0
|
|
||||||
}
|
|
||||||
var RF float64
|
|
||||||
if PreClose > 0 {
|
|
||||||
RF = mathutil.RoundToFloat(((Price-PreClose)/PreClose)*100, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "[" + stockInfo.Name + "] " + stockInfo.Price + " " + convertor.ToString(RF) + "% " + stockInfo.Date + " " + stockInfo.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// msgType : 1 涨跌报警(5分钟);2 股价报警(30分钟) 3 成本价报警(30分钟)
|
|
||||||
func getMsgTypeTTL(msgType int) int {
|
|
||||||
switch msgType {
|
|
||||||
case 1:
|
|
||||||
return 60 * 5
|
|
||||||
case 2:
|
|
||||||
return 60 * 30
|
|
||||||
case 3:
|
|
||||||
return 60 * 30
|
|
||||||
default:
|
|
||||||
return 60 * 5
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMsgTypeName(msgType int) string {
|
|
||||||
switch msgType {
|
|
||||||
case 1:
|
|
||||||
return "涨跌报警"
|
|
||||||
case 2:
|
|
||||||
return "股价报警"
|
|
||||||
case 3:
|
|
||||||
return "成本价报警"
|
|
||||||
default:
|
|
||||||
return "未知类型"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) UpdateConfig(settings *data.Settings) string {
|
|
||||||
logger.SugaredLogger.Infof("UpdateConfig:%+v", settings)
|
|
||||||
return data.NewSettingsApi(settings).UpdateConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) GetConfig() *data.Settings {
|
|
||||||
return data.NewSettingsApi(&data.Settings{}).GetConfig()
|
|
||||||
}
|
|
||||||
|
185
app_windows.go
Normal file
185
app_windows.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// startup is called at application startup
|
||||||
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
defer PanicHandler()
|
||||||
|
runtime.EventsOn(ctx, "frontendError", func(optionalData ...interface{}) {
|
||||||
|
logger.SugaredLogger.Errorf("Frontend error: %v\n", optionalData)
|
||||||
|
})
|
||||||
|
logger.SugaredLogger.Infof("Version:%s", Version)
|
||||||
|
// Perform your setup here
|
||||||
|
a.ctx = ctx
|
||||||
|
|
||||||
|
// 创建系统托盘
|
||||||
|
//systray.RunWithExternalLoop(func() {
|
||||||
|
// onReady(a)
|
||||||
|
//}, func() {
|
||||||
|
// onExit(a)
|
||||||
|
//})
|
||||||
|
runtime.EventsOn(ctx, "updateSettings", func(optionalData ...interface{}) {
|
||||||
|
logger.SugaredLogger.Infof("updateSettings : %v\n", optionalData)
|
||||||
|
config := &data.Settings{}
|
||||||
|
setMap := optionalData[0].(map[string]interface{})
|
||||||
|
|
||||||
|
// 将 map 转换为 JSON 字节切片
|
||||||
|
jsonData, err := json.Marshal(setMap)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("Marshal error:%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 将 JSON 字节切片解析到结构体中
|
||||||
|
err = json.Unmarshal(jsonData, config)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("Unmarshal error:%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.SugaredLogger.Infof("updateSettings config:%+v", config)
|
||||||
|
if config.DarkTheme {
|
||||||
|
runtime.WindowSetBackgroundColour(ctx, 27, 38, 54, 1)
|
||||||
|
runtime.WindowSetDarkTheme(ctx)
|
||||||
|
} else {
|
||||||
|
runtime.WindowSetBackgroundColour(ctx, 255, 255, 255, 1)
|
||||||
|
runtime.WindowSetLightTheme(ctx)
|
||||||
|
}
|
||||||
|
runtime.WindowReloadApp(ctx)
|
||||||
|
|
||||||
|
})
|
||||||
|
go systray.Run(func() {
|
||||||
|
onReady(a)
|
||||||
|
}, func() {
|
||||||
|
onExit(a)
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.SugaredLogger.Infof(" application startup Version:%s", Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
|
||||||
|
notification := toast.Notification{
|
||||||
|
AppID: "go-stock",
|
||||||
|
Title: "go-stock",
|
||||||
|
Message: "程序已经在运行了",
|
||||||
|
Icon: "",
|
||||||
|
Duration: "short",
|
||||||
|
Audio: toast.Default,
|
||||||
|
}
|
||||||
|
err := notification.Push()
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonitorStockPrices(a *App) {
|
||||||
|
dest := &[]data.FollowedStock{}
|
||||||
|
db.Dao.Model(&data.FollowedStock{}).Find(dest)
|
||||||
|
total := float64(0)
|
||||||
|
//for _, follow := range *dest {
|
||||||
|
// stockData := getStockInfo(follow)
|
||||||
|
// total += stockData.ProfitAmountToday
|
||||||
|
// price, _ := convertor.ToFloat(stockData.Price)
|
||||||
|
// if stockData.PrePrice != price {
|
||||||
|
// go runtime.EventsEmit(a.ctx, "stock_price", stockData)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
stockInfos := GetStockInfos(*dest...)
|
||||||
|
for _, stockInfo := range *stockInfos {
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"SZ", "SH", "sh", "sz"}) && (!isTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"hk", "HK"}) && (!IsHKTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strutil.HasPrefixAny(stockInfo.Code, []string{"us", "US", "gb_"}) && (!IsUSTradingTime(time.Now())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
total += stockInfo.ProfitAmountToday
|
||||||
|
price, _ := convertor.ToFloat(stockInfo.Price)
|
||||||
|
|
||||||
|
if stockInfo.PrePrice != price {
|
||||||
|
//logger.SugaredLogger.Infof("-----------sz------------股票代码: %s, 股票名称: %s, 股票价格: %s,盘前盘后:%s", stockInfo.Code, stockInfo.Name, stockInfo.Price, stockInfo.BA)
|
||||||
|
go runtime.EventsEmit(a.ctx, "stock_price", stockInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if total != 0 {
|
||||||
|
title := "go-stock " + time.Now().Format(time.DateTime) + fmt.Sprintf(" %.2f¥", total)
|
||||||
|
systray.SetTooltip(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
go runtime.EventsEmit(a.ctx, "realtime_profit", fmt.Sprintf(" %.2f", total))
|
||||||
|
//runtime.WindowSetTitle(a.ctx, title)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func onReady(a *App) {
|
||||||
|
|
||||||
|
// 初始化操作
|
||||||
|
logger.SugaredLogger.Infof("systray onReady")
|
||||||
|
systray.SetIcon(icon2)
|
||||||
|
systray.SetTitle("go-stock")
|
||||||
|
systray.SetTooltip("go-stock 股票行情实时获取")
|
||||||
|
// 创建菜单项
|
||||||
|
show := systray.AddMenuItem("显示", "显示应用程序")
|
||||||
|
show.Click(func() {
|
||||||
|
//logger.SugaredLogger.Infof("显示应用程序")
|
||||||
|
runtime.WindowShow(a.ctx)
|
||||||
|
})
|
||||||
|
hide := systray.AddMenuItem("隐藏", "隐藏应用程序")
|
||||||
|
hide.Click(func() {
|
||||||
|
//logger.SugaredLogger.Infof("隐藏应用程序")
|
||||||
|
runtime.WindowHide(a.ctx)
|
||||||
|
})
|
||||||
|
systray.AddSeparator()
|
||||||
|
mQuitOrig := systray.AddMenuItem("退出", "退出应用程序")
|
||||||
|
mQuitOrig.Click(func() {
|
||||||
|
//logger.SugaredLogger.Infof("退出应用程序")
|
||||||
|
runtime.Quit(a.ctx)
|
||||||
|
})
|
||||||
|
systray.SetOnRClick(func(menu systray.IMenu) {
|
||||||
|
menu.ShowMenu()
|
||||||
|
//logger.SugaredLogger.Infof("SetOnRClick")
|
||||||
|
})
|
||||||
|
systray.SetOnClick(func(menu systray.IMenu) {
|
||||||
|
//logger.SugaredLogger.Infof("SetOnClick")
|
||||||
|
menu.ShowMenu()
|
||||||
|
})
|
||||||
|
systray.SetOnDClick(func(menu systray.IMenu) {
|
||||||
|
menu.ShowMenu()
|
||||||
|
//logger.SugaredLogger.Infof("SetOnDClick")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// beforeClose is called when the application is about to quit,
|
||||||
|
// either by clicking the window close button or calling runtime.Quit.
|
||||||
|
// Returning true will cause the application to continue, false will continue shutdown as normal.
|
||||||
|
func (a *App) beforeClose(ctx context.Context) (prevent bool) {
|
||||||
|
defer PanicHandler()
|
||||||
|
|
||||||
|
dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.QuestionDialog,
|
||||||
|
Title: "go-stock",
|
||||||
|
Message: "确定关闭吗?",
|
||||||
|
Buttons: []string{"确定"},
|
||||||
|
Icon: icon,
|
||||||
|
CancelButton: "取消",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("dialog error:%s", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
logger.SugaredLogger.Debugf("dialog:%s", dialog)
|
||||||
|
if dialog == "No" {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
systray.Quit()
|
||||||
|
a.cron.Stop()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -128,7 +128,7 @@ func (s SettingsApi) GetConfig() *Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if settings.BrowserPath == "" {
|
if settings.BrowserPath == "" {
|
||||||
settings.BrowserPath, _ = CheckBrowserOnWindows()
|
settings.BrowserPath, _ = CheckBrowser()
|
||||||
}
|
}
|
||||||
if settings.BrowserPoolSize <= 0 {
|
if settings.BrowserPoolSize <= 0 {
|
||||||
settings.BrowserPoolSize = 1
|
settings.BrowserPoolSize = 1
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"go-stock/backend/db"
|
"go-stock/backend/db"
|
||||||
"go-stock/backend/logger"
|
"go-stock/backend/logger"
|
||||||
"go-stock/backend/models"
|
"go-stock/backend/models"
|
||||||
"golang.org/x/sys/windows/registry"
|
|
||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -1298,50 +1297,6 @@ func SearchStockInfoByCode(stock string) *[]string {
|
|||||||
return &messages
|
return &messages
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkChromeOnWindows 在 Windows 系统上检查谷歌浏览器是否安装
|
|
||||||
func checkChromeOnWindows() (string, bool) {
|
|
||||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
|
||||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
defer key.Close()
|
|
||||||
}
|
|
||||||
defer key.Close()
|
|
||||||
path, _, err := key.GetStringValue("Path")
|
|
||||||
//logger.SugaredLogger.Infof("Chrome安装路径:%s", path)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return path + "\\chrome.exe", true
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckBrowserOnWindows 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
|
||||||
func CheckBrowserOnWindows() (string, bool) {
|
|
||||||
if path, ok := checkChromeOnWindows(); ok {
|
|
||||||
return path, true
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
|
||||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
defer key.Close()
|
|
||||||
}
|
|
||||||
defer key.Close()
|
|
||||||
path, _, err := key.GetStringValue("Path")
|
|
||||||
//logger.SugaredLogger.Infof("Edge安装路径:%s", path)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return path + "\\msedge.exe", true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分时数据
|
// 分时数据
|
||||||
func (receiver StockDataApi) GetStockMinutePriceData(stockCode string) (*[]MinuteData, string) {
|
func (receiver StockDataApi) GetStockMinutePriceData(stockCode string) (*[]MinuteData, string) {
|
||||||
url := fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/minute/query?code=%s", stockCode)
|
url := fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/minute/query?code=%s", stockCode)
|
||||||
|
37
backend/data/stock_data_api_darwin.go
Normal file
37
backend/data/stock_data_api_darwin.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package data
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// CheckChrome 检查 macOS 是否安装了 Chrome 浏览器
|
||||||
|
func CheckChrome() (string, bool) {
|
||||||
|
// 检查 /Applications 目录下是否存在 Chrome
|
||||||
|
locations := []string{
|
||||||
|
// Mac
|
||||||
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||||
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
}
|
||||||
|
path := ""
|
||||||
|
for _, location := range locations {
|
||||||
|
_, err := os.Stat(location)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path = location
|
||||||
|
}
|
||||||
|
if path == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckBrowser 检查 macOS 是否安装了浏览器,并返回安装路径
|
||||||
|
func CheckBrowser() (string, bool) {
|
||||||
|
if path, ok := CheckChrome(); ok {
|
||||||
|
return path, ok
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
50
backend/data/stock_data_api_windows.go
Normal file
50
backend/data/stock_data_api_windows.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package data
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows/registry"
|
||||||
|
|
||||||
|
// CheckChrome 在 Windows 系统上检查谷歌浏览器是否安装
|
||||||
|
func CheckChrome() (string, bool) {
|
||||||
|
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||||
|
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
path, _, err := key.GetStringValue("Path")
|
||||||
|
//logger.SugaredLogger.Infof("Chrome安装路径:%s", path)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return path + "\\chrome.exe", true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckBrowser 在 Windows 系统上检查Edge浏览器是否安装,并返回安装路径
|
||||||
|
func CheckBrowser() (string, bool) {
|
||||||
|
if path, ok := checkChromeOnWindows(); ok {
|
||||||
|
return path, true
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
// 尝试在 WOW6432Node 中查找(适用于 64 位系统上的 32 位程序)
|
||||||
|
key, err = registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
path, _, err := key.GetStringValue("Path")
|
||||||
|
//logger.SugaredLogger.Infof("Edge安装路径:%s", path)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return path + "\\msedge.exe", true
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
2d63c3a999d797889c01d6c96451b197
|
8d3264f90073dfceb29c3619775d830d
|
@ -130,16 +130,20 @@ EventsOn("changeMarketTab", async (msg) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
EventsOn("newTelegraph", (data) => {
|
EventsOn("newTelegraph", (data) => {
|
||||||
for (let i = 0; i < data.length; i++) {
|
if (data!=null) {
|
||||||
telegraphList.value.pop()
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
telegraphList.value.pop()
|
||||||
|
}
|
||||||
|
telegraphList.value.unshift(...data)
|
||||||
}
|
}
|
||||||
telegraphList.value.unshift(...data)
|
|
||||||
})
|
})
|
||||||
EventsOn("newSinaNews", (data) => {
|
EventsOn("newSinaNews", (data) => {
|
||||||
|
if (data!=null) {
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
sinaNewsList.value.pop()
|
sinaNewsList.value.pop()
|
||||||
}
|
}
|
||||||
sinaNewsList.value.unshift(...data)
|
sinaNewsList.value.unshift(...data)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
//获取页面高度
|
//获取页面高度
|
||||||
|
0
frontend/wailsjs/go/main/App.d.ts
vendored
Normal file → Executable file
0
frontend/wailsjs/go/main/App.d.ts
vendored
Normal file → Executable file
0
frontend/wailsjs/go/main/App.js
Normal file → Executable file
0
frontend/wailsjs/go/main/App.js
Normal file → Executable file
0
frontend/wailsjs/go/models.ts
Normal file → Executable file
0
frontend/wailsjs/go/models.ts
Normal file → Executable file
10
go.mod
10
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/chromedp/chromedp v0.11.2
|
github.com/chromedp/chromedp v0.11.2
|
||||||
github.com/coocood/freecache v1.2.4
|
github.com/coocood/freecache v1.2.4
|
||||||
github.com/duke-git/lancet/v2 v2.3.4
|
github.com/duke-git/lancet/v2 v2.3.4
|
||||||
github.com/energye/systray v1.0.2
|
github.com/gen2brain/beeep v0.11.1
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-ego/gse v0.80.3
|
github.com/go-ego/gse v0.80.3
|
||||||
github.com/go-resty/resty/v2 v2.16.2
|
github.com/go-resty/resty/v2 v2.16.2
|
||||||
@ -28,6 +28,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
@ -35,6 +36,7 @@ require (
|
|||||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/esiqveland/notify v0.13.3 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
@ -42,6 +44,7 @@ require (
|
|||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
@ -55,13 +58,16 @@ require (
|
|||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
|
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
||||||
|
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
|
||||||
|
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||||
|
30
go.sum
30
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo=
|
||||||
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
|
||||||
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
@ -14,14 +16,17 @@ github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipw
|
|||||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||||
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
|
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
|
||||||
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
||||||
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/duke-git/lancet/v2 v2.3.4 h1:8XGI7P9w+/GqmEBEXYaH/XuNiM0f4/90Ioti0IvYJls=
|
github.com/duke-git/lancet/v2 v2.3.4 h1:8XGI7P9w+/GqmEBEXYaH/XuNiM0f4/90Ioti0IvYJls=
|
||||||
github.com/duke-git/lancet/v2 v2.3.4/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
github.com/duke-git/lancet/v2 v2.3.4/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/energye/systray v1.0.2 h1:63R4prQkANtpM2CIA4UrDCuwZFt+FiygG77JYCsNmXc=
|
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
|
||||||
github.com/energye/systray v1.0.2/go.mod h1:sp7Q/q/I4/w5ebvpSuJVep71s9Bg7L9ZVp69gBASehM=
|
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
|
||||||
|
github.com/gen2brain/beeep v0.11.1 h1:EbSIhrQZFDj1K2fzlMpAYlFOzV8YuNe721A58XcCTYI=
|
||||||
|
github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
|
||||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
@ -42,7 +47,6 @@ 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/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -50,6 +54,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu
|
|||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
|
||||||
|
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
@ -91,6 +97,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
@ -115,10 +123,20 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
|
github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M=
|
||||||
|
github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY=
|
||||||
|
github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ=
|
||||||
|
github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
|
||||||
|
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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
|
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
|
||||||
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
|
||||||
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
|
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
@ -183,7 +201,6 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -238,6 +255,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST
|
|||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||||
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user