package main import ( "bufio" "encoding/json" "fmt" "github.com/google/uuid" netclient "hudly/client" "hudly/hypixel" "hudly/mcfetch" "log" "os" "os/exec" "path/filepath" "runtime" "strings" "time" ) type PlayerWrapper struct { Player hypixel.Player `json:"player"` } func replaceCorruptedRune(msg string) string { runes := []rune(msg) for i, r := range runes { if r == '�' { runes[i] = '§' } } return string(runes) } func clearTerminal() { var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("cmd", "/c", "cls") } else { cmd = exec.Command("clear") } cmd.Stdout = os.Stdout cmd.Run() } func calcRatio(numerator, denominator int) float64 { if denominator == 0 { return float64(numerator) } return float64(numerator) / float64(denominator) } type DemoApp struct { Client *netclient.Client API *hypixel.HypixelApi MemCache *mcfetch.MemoryCache LogBuf *LogBuffer PartyBuilder []map[string]interface{} } func NewDemoApp(key string) *DemoApp { var api_key = hypixel.NewAPIKey(key) app := &DemoApp{ API: hypixel.NewAPI(*api_key), MemCache: &mcfetch.MemoryCache{}, LogBuf: NewLogBuffer(10), PartyBuilder: []map[string]interface{}{}, } app.MemCache.Init() return app } func (app *DemoApp) FetchMCPlayer(name string) (*mcfetch.FetchedPlayerResult, error) { asyncFetcher := mcfetch.NewPlayerFetcher( name, app.MemCache, 2, 2*time.Second, 5*time.Second, ) data, err := asyncFetcher.FetchPlayerData() if err != nil { return nil, err } return data, nil } func (app *DemoApp) onFileEmit(line string) { msg := strings.TrimSpace(line) if len(msg) < 34 { return } submsg := msg[33:] if len(submsg) != 0 { app.LogBuf.Add(submsg) } OnlinePrefix := "[CHAT] ONLINE: " PartyListSeparatorLinePrefix := "[CHAT] -----------------------------------------------------" //PartyMemberCountPrefix := "[CHAT] Party Members (" PartyLeaderPrefix := "[CHAT] Party Leader: " PartyListMembersPrefix := "[CHAT] Party Members: " if strings.HasPrefix(submsg, OnlinePrefix) { // Online Message newsubmsg := strings.TrimPrefix(submsg, OnlinePrefix) players := strings.Split(newsubmsg, ",") var online []mcfetch.CacheResult for _, player := range players { playerName := strings.TrimSpace(player) plr, err := app.FetchMCPlayer(playerName) res_name := plr.Name res_uuid := plr.UUID if err != nil { fmt.Println(fmt.Sprintf("Error fetching UUID: %v", err)) continue } fmt.Printf("UUID of player %s: %s\n", res_name, res_uuid) res_player := mcfetch.CacheResult{ UUID: plr.UUID, Name: plr.Name, } online = append(online, res_player) //names, err := GetNameFromUUID(playerUUID) //if err != nil { // log.Fatalf("Error fetching names from UUID: %v", err) //} //fmt.Printf("Name history for UUID %s: %v\n", playerUUID, names) } app.sendPartyList(online) } else if strings.HasPrefix(submsg, PartyListSeparatorLinePrefix) { // Party List last, _ := app.LogBuf.GetSecondToLast() // TODO: Check if moderators if !strings.HasPrefix(last, PartyListMembersPrefix) { return } PartyMembersMsg := strings.TrimPrefix(last, PartyListMembersPrefix) var ppl []mcfetch.CacheResult for _, player := range strings.Split(PartyMembersMsg, ",") { playerName := strings.TrimSpace(strings.TrimSuffix(player, " ?")) if strings.HasPrefix(playerName, "[") { playerName = strings.Split(playerName, " ")[1] } plr, err := app.FetchMCPlayer(playerName) if err != nil { log.Fatalf("Error fetching Player: %v", err) continue } res_name := plr.Name res_uuid := plr.UUID res_player := mcfetch.CacheResult{ UUID: plr.UUID, Name: plr.Name, } fmt.Printf("UUID of player %s: %s\n", res_name, res_uuid) ppl = append(ppl, res_player) //playerName := strings.TrimSpace(player) //playerUUID, err := GetUUIDFromName(playerName) //if err != nil { // log.Fatalf("Error fetching UUID: %v", err) // return //} //fmt.Printf("UUID of player %s: %s\n", playerName, playerUUID) //cachedPlayer := CachedUuid{playerUUID, playerName, playerName, time.Now()} //UuidCache.Add(&cachedPlayer) } // Parse Party Leader leaders_msg, err := app.LogBuf.GetLineStepsBack(2) if err != nil { println("Unable to find party leader message") return } PartyLeaderMsg := strings.TrimPrefix(leaders_msg, PartyLeaderPrefix) playerName := strings.TrimSpace(strings.TrimSuffix(PartyLeaderMsg, " ?")) if strings.HasPrefix(playerName, "[") { playerName = strings.Split(playerName, " ")[1] } plr, err := app.FetchMCPlayer(playerName) if err != nil { log.Fatalf("Error fetching Player: %v", err) } res_name := plr.Name res_uuid := plr.UUID res_player := mcfetch.CacheResult{ UUID: plr.UUID, Name: plr.Name, } fmt.Printf("UUID of player %s: %s\n", res_name, res_uuid) ppl = append(ppl, res_player) // Parse Party Count //party_count_msg, err := LogBuf.GetLineStepsBack(4) //if err != nil { // println("Unable to find party count message") // return //} //PartyCountMsg := strings.TrimPrefix(party_count_msg, PartyMemberCountPrefix) //count_str := strings.TrimSuffix(PartyCountMsg, ")") //count, err := strconv.Atoi(count_str) //if err != nil { // println("Unable to parse party count message - Invalid number used") // return //} //print("Expected ") //print(count) //print(" party members\n") app.sendPartyList(ppl) return } println(submsg) } func (app *DemoApp) tailFile(path string, lineCh chan<- string) { file, err := os.Open(path) if err != nil { log.Fatalf("Failed to open file: %v", err) } defer file.Close() file.Seek(0, 2) reader := bufio.NewReader(file) for { line, err := reader.ReadString('\n') if err != nil { time.Sleep(100 * time.Millisecond) continue } lineCh <- line } } func (app *DemoApp) sendPartyList(ppl []mcfetch.CacheResult) { for _, user := range ppl { res, err := app.API.GetPlayerResponse(user.UUID) if err != nil { log.Fatalf("Failed to get player data: %v", err) } res.Player.Stats.Bedwars.WLR = calcRatio(res.Player.Stats.Bedwars.Wins, res.Player.Stats.Bedwars.Losses) res.Player.Stats.Bedwars.KDR = calcRatio(res.Player.Stats.Bedwars.Kills, res.Player.Stats.Bedwars.Deaths) res.Player.Stats.Bedwars.FKDR = calcRatio(res.Player.Stats.Bedwars.FinalKills, res.Player.Stats.Bedwars.FinalDeaths) res.Player.Stats.Bedwars.BBLR = calcRatio(res.Player.Stats.Bedwars.BedsBroken, res.Player.Stats.Bedwars.BedsLost) playerJSON, err := json.Marshal(res) if err != nil { log.Fatalf("Failed to marshal player data: %v", err) } var playerMap map[string]interface{} json.Unmarshal(playerJSON, &playerMap) app.PartyBuilder = append(app.PartyBuilder, playerMap) message, err := json.Marshal(app.PartyBuilder) if err != nil { log.Fatalf("Failed to marshal stuff: %v", err) } err = app.Client.SendData(string(message)) if err != nil { log.Printf("Error sending data: %v", err) } fmt.Println("Sent stuff:", app.PartyBuilder) println("Sending Done!") } // Clear buffer so it only sends the current party list, not previous party lists app.PartyBuilder = []map[string]interface{}{} } func (app *DemoApp) Start() { var cmd string fmt.Print(" | CREATE\n | JOIN\nEnter Choice:\n>") fmt.Scanln(&cmd) if cmd != "CREATE" && cmd != "JOIN" { fmt.Println("Invalid command.") return } var err error app.Client, err = netclient.NewClient("chat.itzilly.com", uuid.New().String()) if err != nil { log.Fatalf("Failed to create client: %v", err) return } if cmd == "CREATE" { app.CreateRoom() } else if cmd == "JOIN" { err := app.JoinRoom() if err != nil { return } } fmt.Printf("[DEV] Joined Branches\n") app.Client.ListenForData() for { select { case data, ok := <-app.Client.DataChannel: if !ok { fmt.Println("Data channel closed, exiting...") return } app.HandleData(data) } } fmt.Println("Closing app") } func (app *DemoApp) CreateRoom() { var err error code, err := app.Client.CreateRoom("") if err != nil { log.Fatal(err) return } fmt.Println("Created room:", code) err = app.Client.JoinRoom(code, "password") if err != nil { log.Fatal(err) return } println("Connected to room") path := os.Getenv("USERPROFILE") logPath := filepath.Join(path, ".lunarclient", "offline", "multiver", "logs", "latest.log") fmt.Println("Reading log file from:", logPath) lineCh := make(chan string) go app.tailFile(logPath, lineCh) // TODO: Do this in a different goroutine so that you can still listen to data from your own client for { select { case line := <-lineCh: app.onFileEmit(replaceCorruptedRune(line)) } } } func (app *DemoApp) JoinRoom() error { var code string var password string fmt.Print("Enter Room Code:\n>") fmt.Scanln(&code) fmt.Print("Enter Room Password:\n>") fmt.Scanln(&password) err := app.Client.JoinRoom(code, password) if err != nil { log.Fatal(err) return err } fmt.Println("Joined room:", code) return nil } func (app *DemoApp) HandleData(data string) { var playerWrappers []PlayerWrapper err := json.Unmarshal([]byte(data), &playerWrappers) if err != nil { fmt.Println("Error unmarshalling data:", err) return } var players []*hypixel.Player for _, wrapper := range playerWrappers { players = append(players, &wrapper.Player) } app.DisplayPlayers(players) } func (app *DemoApp) DisplayPlayers(players []*hypixel.Player) { clearTerminal() fmt.Printf("| %-20s | %-10s | %-10s |\n", "Player Name", "Bedwars Level", "FKDR") fmt.Println("|----------------------|------------|------------|") for _, player := range players { fmt.Printf("| %-20s | %-10d | %10.3f |\n", player.DisplayName, player.Achievements.BedwarsLevel, player.Stats.Bedwars.FKDR) } fmt.Println("|----------------------|------------|------------|") }