display the link number in the ui, add realms to address selection

This commit is contained in:
olebeck 2023-06-16 20:00:23 +02:00
parent 4d27ac0e47
commit 6d95fdd1b3
9 changed files with 311 additions and 74 deletions

View File

@ -5,6 +5,7 @@ import (
"context"
"flag"
"fmt"
"io"
"os"
"os/signal"
"runtime/debug"
@ -31,9 +32,31 @@ type CLI struct {
func (c *CLI) Init() bool {
utils.SetCurrentUI(c)
utils.Auth.LoginWithMicrosoftCallback = func(r io.Reader) {
io.Copy(os.Stdout, r)
}
return true
}
/*
var m = &worlds.Map{}
func (c *CLI) Message(data interface{}) messages.MessageResponse {
switch me := data.(type) {
case messages.CanShowImages:
return messages.MessageResponse{Ok: true}
case messages.UpdateMap:
m.Update(&me)
}
return messages.MessageResponse{
Ok: false,
Data: nil,
}
}
*/
func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error {
flag.Parse()
subcommands.Execute(ctx)
@ -162,7 +185,7 @@ func (c *TransCMD) Execute(_ context.Context, ui utils.UI) error {
Reset = "\033[0m"
)
if c.auth {
utils.GetTokenSource()
utils.Auth.GetTokenSource()
}
fmt.Println(BlackFg + Bold + Blue + " Trans " + Pink + " Rights " + White + " Are " + Pink + " Human " + Blue + " Rights " + Reset)
return nil

View File

@ -1,10 +1,12 @@
//go:build gui
//go:builda gui
package ui
import (
"bufio"
"context"
"image/color"
"io"
"gioui.org/app"
"gioui.org/font/gofont"
@ -27,11 +29,14 @@ import (
type GUI struct {
utils.BaseUI
router pages.Router
cancel context.CancelFunc
router pages.Router
cancel context.CancelFunc
authPopup bool
authPopupText string
}
func (g *GUI) Init() bool {
utils.Auth.LoginWithMicrosoftCallback = g.LoginWithMicrosoftCallback
return true
}
@ -98,7 +103,20 @@ func (g *GUI) run(w *app.Window) error {
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
g.router.Layout(gtx, g.router.Theme)
layout.Stack{
Alignment: layout.Center,
}.Layout(gtx,
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
return g.router.Layout(gtx, g.router.Theme)
}),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
if g.authPopup {
return g.AuthPopup(gtx)
}
return layout.Dimensions{}
}),
)
e.Frame(gtx.Ops)
}
case <-g.router.Ctx.Done():
@ -129,6 +147,29 @@ func (g *GUI) Message(data interface{}) messages.MessageResponse {
return r
}
func (g *GUI) AuthPopup(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Max = gtx.Constraints.Max.Div(2)
return layout.Center.Layout(gtx, material.Body1(g.router.Theme, g.authPopupText).Layout)
}
func (g *GUI) LoginWithMicrosoftCallback(r io.Reader) {
g.authPopup = true
b := bufio.NewReader(r)
for {
line, _, err := b.ReadLine()
if err != nil {
panic(err)
}
println(string(line))
g.authPopupText += string(line) + "\n"
g.router.Invalidate()
if string(line) == "Authentication successful." {
break
}
}
g.authPopup = false
}
func init() {
utils.MakeGui = func() utils.UI {
return &GUI{}

View File

@ -0,0 +1,128 @@
package settings
import (
"context"
"fmt"
"image"
"image/color"
"sync"
"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft/realms"
"github.com/sirupsen/logrus"
)
type addressInput struct {
Editor widget.Editor
showRealmsList widget.Bool
l sync.Mutex
realmsList widget.List
realms []realms.Realm
realmsButtons map[int]*widget.Clickable
loading bool
}
var AddressInput = &addressInput{
Editor: widget.Editor{
SingleLine: true,
},
realmsList: widget.List{
List: layout.List{
Axis: layout.Vertical,
},
},
}
func (a *addressInput) Value() string {
return a.Editor.Text()
}
func (a *addressInput) getRealms() {
var err error
a.loading = true
a.realms, err = utils.GetRealmsAPI().Realms(context.Background())
a.realmsButtons = make(map[int]*widget.Clickable)
for _, r := range a.realms {
a.realmsButtons[r.ID] = &widget.Clickable{}
}
a.loading = false
if err != nil {
logrus.Error(err)
}
}
func MulAlpha(c color.NRGBA, alpha uint8) color.NRGBA {
c.A = uint8(uint32(c.A) * uint32(alpha) / 0xFF)
return c
}
func (a *addressInput) Layout(th *material.Theme) layout.Widget {
for k, c := range a.realmsButtons {
if c.Clicked() {
for _, r := range a.realms {
if r.ID == k {
a.Editor.SetText(fmt.Sprintf("realm:%s:%d", r.Name, r.ID))
}
}
}
}
return func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
e := material.Editor(th, &a.Editor, "server Address")
return layout.UniformInset(5).Layout(gtx, e.Layout)
}),
layout.Rigid(layout.Spacer{Width: unit.Dp(10)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(material.Label(th, th.TextSize, "list realms").Layout),
layout.Rigid(material.Switch(th, &a.showRealmsList, "realms").Layout),
)
}),
)
}),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
if a.loading {
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Max = image.Pt(20, 20)
return material.Loader(th).Layout(gtx)
})
}
if a.showRealmsList.Value {
if a.showRealmsList.Changed() {
go a.getRealms()
}
a.l.Lock()
defer a.l.Unlock()
if len(a.realms) == 0 {
return material.Label(th, th.TextSize, "you have no realms").Layout(gtx)
}
return material.List(th, &a.realmsList).Layout(gtx, len(a.realms), func(gtx layout.Context, index int) layout.Dimensions {
entry := a.realms[index]
return material.ButtonLayoutStyle{
Background: MulAlpha(th.Palette.Bg, 0x60),
Button: a.realmsButtons[entry.ID],
CornerRadius: 3,
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(15).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
layout.Rigid(material.Label(th, th.TextSize, entry.Name).Layout),
)
})
})
})
}
return layout.Dimensions{}
}),
)
}
}

View File

@ -2,7 +2,6 @@ package settings
import (
"gioui.org/layout"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/bedrock-tool/bedrocktool/subcommands"
"github.com/bedrock-tool/bedrocktool/utils"
@ -11,21 +10,21 @@ import (
type packsSettings struct {
packs *subcommands.ResourcePackCMD
serverAddress widget.Editor
serverAddress *addressInput
}
func (s *packsSettings) Init() {
s.packs = utils.ValidCMDs["packs"].(*subcommands.ResourcePackCMD)
s.serverAddress.SingleLine = true
s.serverAddress = AddressInput
}
func (s *packsSettings) Apply() {
s.packs.ServerAddress = s.serverAddress.Text()
s.packs.ServerAddress = s.serverAddress.Value()
}
func (s *packsSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(material.Editor(th, &s.serverAddress, "Server Address").Layout),
layout.Rigid(s.serverAddress.Layout(th)),
)
}

View File

@ -14,12 +14,12 @@ type skinsSettings struct {
Filter widget.Editor
Proxy widget.Bool
serverAddress widget.Editor
serverAddress *addressInput
}
func (s *skinsSettings) Init() {
s.skins = utils.ValidCMDs["skins"].(*skins.SkinCMD)
s.serverAddress.SingleLine = true
s.serverAddress = AddressInput
s.Filter.SingleLine = true
s.Proxy.Value = true
}
@ -27,7 +27,7 @@ func (s *skinsSettings) Init() {
func (s *skinsSettings) Apply() {
s.skins.Filter = s.Filter.Text()
s.skins.NoProxy = !s.Proxy.Value
s.skins.ServerAddress = s.serverAddress.Text()
s.skins.ServerAddress = s.serverAddress.Value()
}
func (s *skinsSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
@ -35,7 +35,7 @@ func (s *skinsSettings) Layout(gtx layout.Context, th *material.Theme) layout.Di
layout.Rigid(material.CheckBox(th, &s.Proxy, "Enable Proxy").Layout),
layout.Rigid(material.Editor(th, &s.Filter, "Player name filter").Layout),
layout.Rigid(layout.Spacer{Height: unit.Dp(15)}.Layout),
layout.Rigid(material.Editor(th, &s.serverAddress, "server Address").Layout),
layout.Rigid(s.serverAddress.Layout(th)),
)
}

View File

@ -15,12 +15,12 @@ type worldSettings struct {
voidGen widget.Bool
saveImage widget.Bool
PacketCapture widget.Bool
serverAddress widget.Editor
serverAddress *addressInput
}
func (s *worldSettings) Init() {
s.worlds = utils.ValidCMDs["worlds"].(*world.WorldCMD)
s.serverAddress.SingleLine = true
s.serverAddress = AddressInput
s.voidGen.Value = true
s.PacketCapture.Value = false
}
@ -29,7 +29,7 @@ func (s *worldSettings) Apply() {
s.worlds.Packs = s.withPacks.Value
s.worlds.EnableVoid = s.voidGen.Value
s.worlds.SaveImage = s.saveImage.Value
s.worlds.ServerAddress = s.serverAddress.Text()
s.worlds.ServerAddress = s.serverAddress.Value()
s.worlds.SaveEntities = true
s.worlds.SaveInventories = true
utils.Options.Capture = s.PacketCapture.Value
@ -41,7 +41,7 @@ func (s *worldSettings) Layout(gtx layout.Context, th *material.Theme) layout.Di
layout.Rigid(material.CheckBox(th, &s.voidGen, "void Generator").Layout),
layout.Rigid(material.CheckBox(th, &s.saveImage, "save image").Layout),
layout.Rigid(material.CheckBox(th, &s.PacketCapture, "packet capture").Layout),
layout.Rigid(material.Editor(th, &s.serverAddress, "server Address").Layout),
layout.Rigid(s.serverAddress.Layout(th)),
)
}

View File

@ -3,35 +3,102 @@ package utils
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/sandertv/gophertunnel/minecraft/auth"
"github.com/sandertv/gophertunnel/minecraft/realms"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)
const TokenFile = "token.json"
var gTokenSrc oauth2.TokenSource
type authsrv struct {
t *oauth2.Token
src oauth2.TokenSource
func GetTokenSource() oauth2.TokenSource {
if gTokenSrc != nil {
return gTokenSrc
}
token := getToken()
gTokenSrc = auth.RefreshTokenSource(&token)
newToken, err := gTokenSrc.Token()
LoginWithMicrosoftCallback func(io.Reader)
}
var Auth authsrv
func (a *authsrv) HaveToken() bool {
_, err := os.Stat(TokenFile)
return err == nil
}
func (a *authsrv) Refresh() error {
a.src = auth.RefreshTokenSource(a.t)
return nil
}
func (a *authsrv) writeToken() error {
f, err := os.Create(TokenFile)
if err != nil {
panic(err)
}
if !token.Valid() {
logrus.Info(locale.Loc("refreshed_token", nil))
writeToken(newToken)
return err
}
defer f.Close()
e := json.NewEncoder(f)
return e.Encode(a.t)
}
return gTokenSrc
func (a *authsrv) readToken() error {
var token oauth2.Token
f, err := os.Open(TokenFile)
if err != nil {
return err
}
defer f.Close()
e := json.NewDecoder(f)
err = e.Decode(&token)
if err != nil {
return err
}
a.t = &token
return nil
}
func (a *authsrv) GetTokenSource() (src oauth2.TokenSource, err error) {
if a.src != nil {
return a.src, nil
}
if !a.HaveToken() {
// request a new token
r, w := io.Pipe()
go a.LoginWithMicrosoftCallback(r)
a.t, err = auth.RequestLiveTokenWriter(w)
if err != nil {
return nil, err
}
err := a.writeToken()
if err != nil {
return nil, err
}
} else {
// read the existing token
err := a.readToken()
if err != nil {
return nil, err
}
}
// refresh the token if necessary
err = a.Refresh()
if err != nil {
return nil, err
}
// if the old token isnt valid save the new one
if !a.t.Valid() {
newToken, err := a.src.Token()
if err != nil {
return nil, err
}
a.t = newToken
err = a.writeToken()
if err != nil {
return nil, err
}
}
return a.src, nil
}
var RealmsEnv string
@ -43,37 +110,7 @@ func GetRealmsAPI() *realms.Client {
if RealmsEnv != "" {
realms.RealmsAPIBase = fmt.Sprintf("https://pocket-%s.realms.minecraft.net/", RealmsEnv)
}
gRealmsAPI = realms.NewClient(GetTokenSource())
gRealmsAPI = realms.NewClient(Auth.src)
}
return gRealmsAPI
}
func writeToken(token *oauth2.Token) {
buf, err := json.Marshal(token)
if err != nil {
panic(err)
}
os.WriteFile(TokenFile, buf, 0o755)
}
func getToken() oauth2.Token {
var token oauth2.Token
if _, err := os.Stat(TokenFile); err == nil {
f, err := os.Open(TokenFile)
if err != nil {
panic(err)
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&token); err != nil {
panic(err)
}
} else {
_token, err := auth.RequestLiveToken()
if err != nil {
panic(err)
}
writeToken(_token)
token = *_token
}
return token
}

View File

@ -23,6 +23,7 @@ import (
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)
var DisconnectReason = "Connection lost"
@ -310,13 +311,11 @@ func (p *ProxyContext) IsClient(addr net.Addr) bool {
var NewDebugLogger func(bool) *ProxyHandler
var NewPacketCapturer func() *ProxyHandler
func (p *ProxyContext) connectClient(ctx context.Context, serverAddress string, cdpp **login.ClientData) (err error) {
GetTokenSource() // ask for login before listening
func (p *ProxyContext) connectClient(ctx context.Context, serverAddress string, cdpp **login.ClientData, tokenSource oauth2.TokenSource) (err error) {
var packs []*resource.Pack
if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil))
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
serverConn, err := connectServer(ctx, serverAddress, nil, true, nil, tokenSource)
if err != nil {
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
@ -389,10 +388,19 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
isReplay = true
}
var tokenSource oauth2.TokenSource
if !isReplay {
// ask for login before listening
tokenSource, err = Auth.GetTokenSource()
if err != nil {
return err
}
}
var cdp *login.ClientData = nil
if p.WithClient && !isReplay {
CurrentUI.Message(messages.SetUIState(messages.UIStateConnect))
err = p.connectClient(ctx, serverAddress, &cdp)
err = p.connectClient(ctx, serverAddress, &cdp, tokenSource)
if err != nil {
return err
}
@ -436,7 +444,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
return err
}
} else {
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, packetFunc)
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, packetFunc, tokenSource)
}
if err != nil {
for _, handler := range p.handlers {

View File

@ -22,6 +22,7 @@ import (
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
//"github.com/sandertv/gophertunnel/minecraft/gatherings"
@ -57,7 +58,7 @@ func CleanupName(name string) string {
// connections
func connectServer(ctx context.Context, address string, ClientData *login.ClientData, wantPacks bool, packetFunc PacketFunc) (serverConn *minecraft.Conn, err error) {
func connectServer(ctx context.Context, address string, ClientData *login.ClientData, wantPacks bool, packetFunc PacketFunc, tokenSource oauth2.TokenSource) (serverConn *minecraft.Conn, err error) {
cd := login.ClientData{}
if ClientData != nil {
cd = *ClientData
@ -65,7 +66,7 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
logrus.Info(locale.Loc("connecting", locale.Strmap{"Address": address}))
serverConn, err = minecraft.Dialer{
TokenSource: GetTokenSource(),
TokenSource: tokenSource,
ClientData: cd,
PacketFunc: packetFunc,
DownloadResourcePack: func(id uuid.UUID, version string, current int, total int) bool {
@ -188,6 +189,6 @@ func ShowFile(path string) {
return
}
if runtime.GOOS == "linux" {
println(path)
}
}