bedrocktool/utils/input.go

183 lines
3.6 KiB
Go

package utils
import (
"context"
"fmt"
"net"
"os"
"regexp"
"strings"
"sync/atomic"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/sirupsen/logrus"
"golang.org/x/term"
)
func UserInput(ctx context.Context, q string, validator func(string) bool) (string, bool) {
c := make(chan string)
oldState, _ := term.MakeRaw(int(os.Stdin.Fd()))
defer term.Restore(int(os.Stdin.Fd()), oldState)
go func() {
fmt.Print(q)
var answerb []byte
var b [1]byte
var valid bool
var validatorRunning atomic.Bool
var validatorQueued atomic.Bool
for {
os.Stdin.Read(b[:])
done := false
switch b[0] {
case 0x3:
c <- ""
return
case 0xA:
fallthrough
case 0xD:
done = true
case 0x8:
fallthrough
case 0x7F:
if len(answerb) > 0 {
answerb = answerb[:len(answerb)-1]
}
default:
if b[0] >= 0x20 {
answerb = append(answerb, b[0])
}
}
if done {
break
}
fmt.Printf("\r%s%s\033[K", q, string(answerb))
if validator != nil {
validatorQueued.Store(true)
if validatorRunning.CompareAndSwap(false, true) {
go func() {
for validatorQueued.Load() {
validatorQueued.Store(false)
valid = validator(string(answerb))
validatorRunning.Store(false)
var st = "❌"
if valid {
st = "✅"
}
fmt.Printf("\r%s%s %s\033[K\033[%dD", q, string(answerb), st, 4)
}
}()
}
}
}
print("\n\r")
answer := string(answerb)
c <- answer
}()
select {
case <-ctx.Done():
return "", true
case a := <-c:
if a == "" {
return a, true
}
return a, false
}
}
func serverGetHostname(server string) string {
host, _, err := net.SplitHostPort(server)
if err != nil {
logrus.Fatalf(locale.Loc("invalid_server", locale.Strmap{"Err": err.Error()}))
}
return host
}
var (
realmRegex = regexp.MustCompile("realm:(?P<Name>.*)(?::(?P<ID>.*))?")
pcapRegex = regexp.MustCompile(`(?P<Filename>(?P<Name>.*)\.pcap2)(?:\?(?P<Args>.*))?`)
)
func regexGetParams(r *regexp.Regexp, s string) (params map[string]string) {
match := r.FindStringSubmatch(s)
params = make(map[string]string)
for i, name := range r.SubexpNames() {
if i > 0 && i <= len(match) {
params[name] = match[i]
}
}
return params
}
func ServerInput(ctx context.Context, server string) (string, string, error) {
// no arg provided, interactive input
if server == "" {
var cancelled bool
server, cancelled = UserInput(ctx, locale.Loc("enter_server", nil), ValidateServerInput)
if cancelled {
return "", "", context.Canceled
}
}
// realm
if realmRegex.MatchString(server) {
p := regexGetParams(realmRegex, server)
name, address, err := getRealm(ctx, p["Name"], p["ID"])
if err != nil {
return "", "", err
}
return address, CleanupName(name), nil
}
// old pcap format
if match, _ := regexp.MatchString(`.*\.pcap$`, server); match {
return "", "", fmt.Errorf(locale.Loc("not_supported_anymore", nil))
}
// new pcap format
if pcapRegex.MatchString(server) {
p := regexGetParams(pcapRegex, server)
return "PCAP!" + p["Filename"], p["Name"], nil
}
// normal server dns or ip
if len(strings.Split(server, ":")) == 1 {
server += ":19132"
}
return server, serverGetHostname(server), nil
}
func ValidateServerInput(server string) bool {
if pcapRegex.MatchString(server) {
return true
}
if realmRegex.MatchString(server) {
return true // todo
}
host, _, err := net.SplitHostPort(server)
if err != nil {
if strings.Contains(err.Error(), "missing port in address") {
host = server
}
}
ip := net.ParseIP(host)
if ip != nil {
return true
}
ips, _ := net.LookupIP(host)
return len(ips) > 0
}