залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
package gui
import (
"os/exec"
"runtime"
"stream-bot/internal/logger"
"time"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
)
const appVersion = "11.0.43"
func Run(webURL string,
getChatStatus func() bool,
getEventSubStatus func() (connected bool, subscriptions []string)) {
if runtime.GOOS != "windows" {
logger.Warn("GUI only supported on Windows, running without window")
return
}
var urlLE *walk.LineEdit
var openBtn *walk.PushButton
var exitBtn *walk.PushButton
var chatStatusLabel, eventSubLabel *walk.Label
var mw *walk.MainWindow
exitWithoutMinimize := false // флаг для полного закрытия
err := MainWindow{
AssignTo: &mw,
Title: "TTW_Bot v" + appVersion,
MinSize: Size{Width: 450, Height: 280},
Size: Size{Width: 500, Height: 300},
Layout: VBox{},
Children: []Widget{
Label{Text: "Веб-интерфейс бота доступен по адресу:"},
LineEdit{
AssignTo: &urlLE,
Text: webURL,
ReadOnly: true,
},
PushButton{
AssignTo: &openBtn,
Text: "🌐 Открыть в браузере",
OnClicked: func() {
openBrowser(webURL)
},
},
Label{Text: "─────────────────────────────────"},
Label{Text: "Статус Twitch чата:"},
Label{AssignTo: &chatStatusLabel, Text: "загрузка..."},
Label{Text: "Статус EventSub:"},
Label{AssignTo: &eventSubLabel, Text: "загрузка..."},
Label{Text: "─────────────────────────────────"},
PushButton{
AssignTo: &exitBtn,
Text: "❌ Завершить работу бота",
OnClicked: func() {
exitWithoutMinimize = true // запоминаем, что хотим выйти
_ = mw.Close()
},
},
},
}.Create()
if err != nil {
msg := "Failed to create GUI window: " + err.Error()
logger.Error(msg)
walk.MsgBox(nil, "Ошибка", msg, walk.MsgBoxIconError)
return
}
// При закрытии окна – либо сворачиваем в трей, либо завершаем программу
mw.Closing().Attach(func(cancel *bool, reason walk.CloseReason) {
if exitWithoutMinimize {
// полное завершение – не отменяем закрытие
return
}
// иначе – сворачиваем в трей
*cancel = true
mw.Hide()
})
// Создаём иконку в трее
ni, err := walk.NewNotifyIcon(mw)
if err != nil {
logger.Error("Failed to create notify icon: %v", err)
} else {
_ = ni.SetToolTip("TTW_Bot")
// Устанавливаем иконку (системная иконка информации)
if err := ni.SetIcon(walk.IconInformation()); err != nil {
logger.Warn("Failed to set icon: %v", err)
}
showAction := walk.NewAction()
_ = showAction.SetText("Показать окно")
showAction.Triggered().Attach(func() {
mw.Show()
mw.SetVisible(true)
})
exitAction := walk.NewAction()
_ = exitAction.SetText("Выход")
exitAction.Triggered().Attach(func() {
exitWithoutMinimize = true
_ = mw.Close()
})
menu := ni.ContextMenu()
_ = menu.Actions().Add(showAction)
_ = menu.Actions().Add(exitAction)
ni.MouseDown().Attach(func(x, y int, button walk.MouseButton) {
if button == walk.LeftButton {
mw.Show()
mw.SetVisible(true)
}
})
if err := ni.SetVisible(true); err != nil {
logger.Error("Failed to set tray icon visible: %v", err)
}
}
// Запускаем периодическое обновление статусов
ticker := time.NewTicker(5 * time.Second)
go func() {
for range ticker.C {
mw.Synchronize(func() {
chatConnected := getChatStatus()
if chatConnected {
_ = chatStatusLabel.SetText("✅ Подключен")
} else {
_ = chatStatusLabel.SetText("❌ Отключен")
}
esConnected, subs := getEventSubStatus()
if esConnected {
_ = eventSubLabel.SetText("✅ Подключен (" + itoa(len(subs)) + " подписок)")
} else {
_ = eventSubLabel.SetText("❌ Отключен")
}
})
}
}()
mw.Run()
ticker.Stop()
}
func openBrowser(url string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
case "darwin":
cmd = exec.Command("open", url)
default:
cmd = exec.Command("xdg-open", url)
}
if err := cmd.Start(); err != nil {
logger.Error("Failed to open browser: %v", err)
walk.MsgBox(nil, "Ошибка", "Не удалось открыть браузер: "+err.Error(), walk.MsgBoxIconError)
}
}
func itoa(i int) string {
if i == 0 {
return "0"
}
digits := ""
for i > 0 {
digits = string(rune('0'+i%10)) + digits
i /= 10
}
return digits
}