319 lines
8.2 KiB
Go
319 lines
8.2 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"image/color"
|
||
"io"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"fyne.io/fyne/v2"
|
||
fyneapp "fyne.io/fyne/v2/app"
|
||
"fyne.io/fyne/v2/container"
|
||
"fyne.io/fyne/v2/dialog"
|
||
"fyne.io/fyne/v2/theme"
|
||
"fyne.io/fyne/v2/widget"
|
||
|
||
"updata_hfb/internal/hfb"
|
||
)
|
||
|
||
const sampleText = `上号方式:QQ扫码
|
||
哈夫币纯币:156
|
||
段位:铂金
|
||
等级:60
|
||
保险格数:9
|
||
每日消耗:10
|
||
体力:7
|
||
负重:7
|
||
AWM子弹数量:80
|
||
6头数量:13
|
||
6甲数量:12
|
||
皮肤:暗星
|
||
绝密KD:0.2
|
||
押金:350
|
||
号主在线时间:11:00 至 00:00
|
||
封禁记录:无
|
||
常用地区:海南
|
||
回收比例:1:47.0
|
||
回收租金:332
|
||
联系电话:15203616450`
|
||
|
||
func Run() {
|
||
a := fyneapp.NewWithID("com.hfb.updata")
|
||
w := a.NewWindow("哈夫币外部上传")
|
||
w.Resize(fyne.NewSize(980, 880))
|
||
prefs := a.Preferences()
|
||
|
||
serverEntry := widget.NewEntry()
|
||
serverEntry.SetText(prefStringWithFallback(prefs, "server", envDefault("HFB_SERVER", "http://127.0.0.1:8080")))
|
||
serverEntry.SetPlaceHolder("例如:http://127.0.0.1:8080")
|
||
|
||
uploaderEntry := widget.NewEntry()
|
||
uploaderEntry.SetText(prefStringWithFallback(prefs, "uploader", strings.TrimSpace(os.Getenv("HFB_UPLOADER_NAME"))))
|
||
uploaderEntry.SetPlaceHolder("后台上传管理员名称")
|
||
|
||
secretEntry := widget.NewEntry()
|
||
secretEntry.Password = true
|
||
secretEntry.SetText(prefStringWithFallback(prefs, "secret", strings.TrimSpace(os.Getenv("HFB_UPLOAD_SECRET"))))
|
||
secretEntry.SetPlaceHolder("未配置密钥可留空")
|
||
|
||
timeoutEntry := widget.NewEntry()
|
||
timeoutEntry.SetText(prefStringWithFallback(prefs, "timeout", "15s"))
|
||
timeoutEntry.SetPlaceHolder("例如:15s")
|
||
|
||
dataEntry := widget.NewMultiLineEntry()
|
||
dataEntry.Wrapping = fyne.TextWrapWord
|
||
dataEntry.SetMinRowsVisible(14)
|
||
dataEntry.SetPlaceHolder(sampleText)
|
||
|
||
resultView := newResultPanel(420)
|
||
resultView.SetText("```text\n暂无结果\n点击“预览 JSON”或“上传”后,这里会显示多行结果。\n```")
|
||
|
||
statusLabel := widget.NewLabel("准备就绪")
|
||
|
||
setResult := func(text string) {
|
||
resultView.SetText(formatResultText(text))
|
||
}
|
||
setBusy := func(uploadBtn *widget.Button, previewBtn *widget.Button, busy bool) {
|
||
if busy {
|
||
uploadBtn.Disable()
|
||
previewBtn.Disable()
|
||
statusLabel.SetText("正在上传,请稍等...")
|
||
return
|
||
}
|
||
uploadBtn.Enable()
|
||
previewBtn.Enable()
|
||
}
|
||
|
||
var uploadBtn *widget.Button
|
||
var previewBtn *widget.Button
|
||
|
||
loadBtn := widget.NewButton("打开文件", func() {
|
||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||
if err != nil {
|
||
dialog.ShowError(fmt.Errorf("打开文件失败:%w", err), w)
|
||
return
|
||
}
|
||
if reader == nil {
|
||
return
|
||
}
|
||
defer reader.Close()
|
||
content, err := io.ReadAll(reader)
|
||
if err != nil {
|
||
dialog.ShowError(fmt.Errorf("读取文件失败:%w", err), w)
|
||
return
|
||
}
|
||
dataEntry.SetText(string(content))
|
||
statusLabel.SetText("已载入文件")
|
||
}, w)
|
||
})
|
||
|
||
previewBtn = widget.NewButton("预览 JSON", func() {
|
||
req, err := buildRequest(uploaderEntry.Text, dataEntry.Text)
|
||
if err != nil {
|
||
statusLabel.SetText("解析失败")
|
||
dialog.ShowError(err, w)
|
||
return
|
||
}
|
||
out, err := hfb.MarshalPretty(req)
|
||
if err != nil {
|
||
statusLabel.SetText("生成 JSON 失败")
|
||
dialog.ShowError(err, w)
|
||
return
|
||
}
|
||
setResult(string(out))
|
||
statusLabel.SetText("预览已生成")
|
||
})
|
||
|
||
uploadBtn = widget.NewButton("上传", func() {
|
||
timeout, err := time.ParseDuration(strings.TrimSpace(timeoutEntry.Text))
|
||
if err != nil || timeout <= 0 {
|
||
dialog.ShowError(fmt.Errorf("请求超时时间不正确:%s", timeoutEntry.Text), w)
|
||
return
|
||
}
|
||
req, err := buildRequest(uploaderEntry.Text, dataEntry.Text)
|
||
if err != nil {
|
||
statusLabel.SetText("解析失败")
|
||
dialog.ShowError(err, w)
|
||
return
|
||
}
|
||
|
||
server := strings.TrimSpace(serverEntry.Text)
|
||
secret := strings.TrimSpace(secretEntry.Text)
|
||
setBusy(uploadBtn, previewBtn, true)
|
||
setResult("")
|
||
|
||
go func() {
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
defer cancel()
|
||
resp, err := hfb.Client{
|
||
Server: server,
|
||
Secret: secret,
|
||
Timeout: timeout,
|
||
}.Upload(ctx, req)
|
||
|
||
fyne.Do(func() {
|
||
setBusy(uploadBtn, previewBtn, false)
|
||
if err != nil {
|
||
statusLabel.SetText("上传失败")
|
||
setResult(err.Error())
|
||
dialog.ShowError(err, w)
|
||
return
|
||
}
|
||
statusLabel.SetText("上传成功")
|
||
setResult(resp)
|
||
dialog.ShowInformation("上传成功", "数据已经提交到服务器。", w)
|
||
})
|
||
}()
|
||
})
|
||
uploadBtn.Importance = widget.HighImportance
|
||
|
||
serverEntry.OnChanged = func(s string) { prefs.SetString("server", strings.TrimSpace(s)) }
|
||
uploaderEntry.OnChanged = func(s string) { prefs.SetString("uploader", strings.TrimSpace(s)) }
|
||
secretEntry.OnChanged = func(s string) { prefs.SetString("secret", strings.TrimSpace(s)) }
|
||
timeoutEntry.OnChanged = func(s string) { prefs.SetString("timeout", strings.TrimSpace(s)) }
|
||
|
||
configForm := widget.NewForm(
|
||
widget.NewFormItem("服务器", serverEntry),
|
||
widget.NewFormItem("上传人", uploaderEntry),
|
||
widget.NewFormItem("上传密钥", secretEntry),
|
||
widget.NewFormItem("超时时间", timeoutEntry),
|
||
)
|
||
configAccordion := widget.NewAccordion(
|
||
widget.NewAccordionItem("连接配置", configForm),
|
||
)
|
||
configAccordion.Close(0)
|
||
toolbar := container.NewHBox(loadBtn, previewBtn, uploadBtn)
|
||
content := container.NewBorder(
|
||
nil,
|
||
container.NewVBox(statusLabel),
|
||
nil,
|
||
nil,
|
||
container.NewVScroll(container.NewVBox(
|
||
configAccordion,
|
||
toolbar,
|
||
widget.NewLabel("账号数据"),
|
||
dataEntry,
|
||
widget.NewLabel("结果"),
|
||
resultView,
|
||
)),
|
||
)
|
||
|
||
w.SetContent(content)
|
||
w.ShowAndRun()
|
||
}
|
||
|
||
func buildRequest(uploaderName string, rawText string) (hfb.ExternalUploadRequest, error) {
|
||
if strings.TrimSpace(rawText) == "" {
|
||
return hfb.ExternalUploadRequest{}, fmt.Errorf("账号数据不能为空")
|
||
}
|
||
parsed, err := hfb.ParseUploadText(rawText)
|
||
if err != nil {
|
||
return hfb.ExternalUploadRequest{}, err
|
||
}
|
||
name := strings.TrimSpace(uploaderName)
|
||
if name == "" {
|
||
name = parsed.UploaderName
|
||
}
|
||
if name == "" {
|
||
return hfb.ExternalUploadRequest{}, fmt.Errorf("缺少上传人名称:请在“上传人”里填写,或在文本中增加“上传人:姓名”")
|
||
}
|
||
return hfb.NewExternalUploadRequest(name, parsed.Account, time.Now()), nil
|
||
}
|
||
|
||
func envDefault(key string, fallback string) string {
|
||
value := strings.TrimSpace(os.Getenv(key))
|
||
if value == "" {
|
||
return fallback
|
||
}
|
||
return value
|
||
}
|
||
|
||
func prefStringWithFallback(prefs fyne.Preferences, key, fallback string) string {
|
||
if prefs == nil {
|
||
return fallback
|
||
}
|
||
value := strings.TrimSpace(prefs.String(key))
|
||
if value != "" {
|
||
return value
|
||
}
|
||
return fallback
|
||
}
|
||
|
||
func formatResultText(text string) string {
|
||
text = strings.TrimSpace(text)
|
||
if text == "" {
|
||
return "暂无结果"
|
||
}
|
||
var payload any
|
||
if err := json.Unmarshal([]byte(text), &payload); err == nil {
|
||
if pretty, err := json.MarshalIndent(payload, "", " "); err == nil {
|
||
return "```json\n" + string(pretty) + "\n```"
|
||
}
|
||
}
|
||
return "```text\n" + text + "\n```"
|
||
}
|
||
|
||
type resultPanel struct {
|
||
widget.BaseWidget
|
||
scroll *container.Scroll
|
||
content *widget.RichText
|
||
minHeight float32
|
||
}
|
||
|
||
func newResultPanel(minHeight float32) *resultPanel {
|
||
panel := &resultPanel{
|
||
content: widget.NewRichTextFromMarkdown(""),
|
||
minHeight: minHeight,
|
||
}
|
||
panel.scroll = container.NewVScroll(panel.content)
|
||
panel.ExtendBaseWidget(panel)
|
||
return panel
|
||
}
|
||
|
||
func (p *resultPanel) SetText(text string) {
|
||
p.content.ParseMarkdown(text)
|
||
p.Refresh()
|
||
}
|
||
|
||
func (p *resultPanel) CreateRenderer() fyne.WidgetRenderer {
|
||
return &resultPanelRenderer{panel: p}
|
||
}
|
||
|
||
type resultPanelRenderer struct {
|
||
panel *resultPanel
|
||
}
|
||
|
||
func (r *resultPanelRenderer) Layout(size fyne.Size) {
|
||
r.panel.scroll.Resize(size)
|
||
r.panel.scroll.Move(fyne.NewPos(0, 0))
|
||
}
|
||
|
||
func (r *resultPanelRenderer) MinSize() fyne.Size {
|
||
size := r.panel.scroll.MinSize()
|
||
if size.Height < r.panel.minHeight {
|
||
size.Height = r.panel.minHeight
|
||
}
|
||
if size.Width < 1 {
|
||
size.Width = 1
|
||
}
|
||
return size
|
||
}
|
||
|
||
func (r *resultPanelRenderer) Refresh() {
|
||
r.panel.scroll.Refresh()
|
||
}
|
||
|
||
func (r *resultPanelRenderer) BackgroundColor() color.Color {
|
||
return theme.Color(theme.ColorNameBackground)
|
||
}
|
||
|
||
func (r *resultPanelRenderer) Objects() []fyne.CanvasObject {
|
||
return []fyne.CanvasObject{r.panel.scroll}
|
||
}
|
||
|
||
func (r *resultPanelRenderer) Destroy() {}
|