Initial unworking app, working demo
This commit is contained in:
commit
5fcea16c2c
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
9
.idea/HypixelStuff.iml
generated
Normal file
9
.idea/HypixelStuff.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/HypixelStuff.iml" filepath="$PROJECT_DIR$/.idea/HypixelStuff.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
BIN
HypixelStuff.7z
Normal file
BIN
HypixelStuff.7z
Normal file
Binary file not shown.
BIN
HypixelStuff.exe
Normal file
BIN
HypixelStuff.exe
Normal file
Binary file not shown.
760
app/app.go
Normal file
760
app/app.go
Normal file
@ -0,0 +1,760 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func errorContext(skip int) string {
|
||||
pc, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
fn := runtime.FuncForPC(pc).Name()
|
||||
return fmt.Sprintf("%s:%d %s", file, line, fn)
|
||||
}
|
||||
|
||||
func APITraceError(format string, args ...interface{}) error {
|
||||
traceInfo := errorContext(2)
|
||||
return fmt.Errorf("[%s] "+format, append([]interface{}{traceInfo}, args...)...)
|
||||
}
|
||||
|
||||
type HypixelPlayerAchievements struct {
|
||||
BedwarsStar int `json:"bedwars_level"`
|
||||
}
|
||||
|
||||
type HypixelPlayerBedwarsStats struct {
|
||||
Experience int `json:"Experience"`
|
||||
Winstreak int `json:"winstreak"`
|
||||
Wins int `json:"wins_bedwars"`
|
||||
Losses int `json:"losses_bedwars"`
|
||||
GamesPlayed int `json:"games_played_bedwars"`
|
||||
WinLossRatio float32 // wins / gamesplayed
|
||||
|
||||
Kills int `json:"kills_bedwars"`
|
||||
Deaths int `json:"deaths_bedwars"`
|
||||
KillDeathRatio float32 // kills / deaths
|
||||
|
||||
FinalKills int `json:"final_kills_bedwars"`
|
||||
FinalDeaths int `json:"final_deaths_bedwars"`
|
||||
FinalKillDeathRatio float32 // final kills / final deaths
|
||||
|
||||
BedsBroken int `json:"beds_broken_bedwars"`
|
||||
BedsLost int `json:"beds_lost_bedwars"`
|
||||
BedsBrokenLostRatio float32 // beds broken / beds lost
|
||||
}
|
||||
|
||||
func (stats *HypixelPlayerBedwarsStats) CalculateRatios() {
|
||||
// WLR
|
||||
if stats.GamesPlayed > 0 {
|
||||
stats.WinLossRatio = float32(stats.Wins) / float32(stats.GamesPlayed)
|
||||
} else {
|
||||
stats.WinLossRatio = float32(stats.Wins)
|
||||
}
|
||||
|
||||
// KDR
|
||||
if stats.Deaths > 0 {
|
||||
stats.KillDeathRatio = float32(stats.Kills) / float32(stats.Deaths)
|
||||
} else {
|
||||
stats.KillDeathRatio = float32(stats.Kills)
|
||||
}
|
||||
|
||||
// FKDR
|
||||
if stats.FinalDeaths > 0 {
|
||||
stats.FinalKillDeathRatio = float32(stats.FinalKills) / float32(stats.FinalDeaths)
|
||||
} else {
|
||||
stats.FinalKillDeathRatio = float32(stats.FinalKills)
|
||||
}
|
||||
|
||||
// BBLR
|
||||
if stats.BedsLost > 0 {
|
||||
stats.BedsBrokenLostRatio = float32(stats.BedsBroken) / float32(stats.BedsLost)
|
||||
} else {
|
||||
stats.BedsBrokenLostRatio = float32(stats.BedsBroken)
|
||||
}
|
||||
}
|
||||
|
||||
func (stats *HypixelPlayerBedwarsStats) IsWinstreakDisabled() bool {
|
||||
return stats.Winstreak == -1
|
||||
}
|
||||
|
||||
type HypixelPlayerStats struct {
|
||||
Bedwars HypixelPlayerBedwarsStats `json:"Bedwars"`
|
||||
}
|
||||
|
||||
type HypixelPlayer struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
Achievements HypixelPlayerAchievements `json:"achievements"`
|
||||
Stats HypixelPlayerStats `json:"stats"`
|
||||
MonthlyRankColor string `json:"monthlyRankColor"`
|
||||
RankPlusColor string `json:"rankPlusColor"`
|
||||
NetworkExp json.Number `json:"networkExp"`
|
||||
}
|
||||
|
||||
type HypixelPlayerResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Player HypixelPlayer `json:"player"`
|
||||
}
|
||||
|
||||
type HypixelAPIKey struct {
|
||||
Value string
|
||||
Uses int
|
||||
RateLimit time.Duration
|
||||
LimitUsesLeft int
|
||||
}
|
||||
|
||||
func NewHypixelAPIKey(key string) *HypixelAPIKey {
|
||||
return &HypixelAPIKey{
|
||||
Value: key,
|
||||
Uses: 0,
|
||||
RateLimit: time.Millisecond * 1,
|
||||
LimitUsesLeft: -1,
|
||||
}
|
||||
}
|
||||
|
||||
type HypixelAPI struct {
|
||||
BaseURL string
|
||||
APIKey HypixelAPIKey
|
||||
}
|
||||
|
||||
func NewHypixelAPI(apiKey string) *HypixelAPI {
|
||||
// TODO: Ensure valid key before creating
|
||||
key := NewHypixelAPIKey(apiKey)
|
||||
return &HypixelAPI{
|
||||
BaseURL: "https://api.hypixel.net/v2/",
|
||||
APIKey: *key,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HypixelAPI) FetchPlayer(PlayerUUID string) (HypixelPlayerResponse, error) {
|
||||
// TODO: Handle nicked players
|
||||
if len(PlayerUUID) != 32 && len(PlayerUUID) != 36 {
|
||||
return HypixelPlayerResponse{}, fmt.Errorf("invalid Player UUID! please pass in a valid UUID (Not a name!)")
|
||||
}
|
||||
RequestUrl := h.BaseURL + "player?uuid=" + PlayerUUID
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", RequestUrl, nil)
|
||||
if err != nil {
|
||||
return HypixelPlayerResponse{}, APITraceError("failed to create HTTP request: %v", err)
|
||||
}
|
||||
req.Header.Set("API-Key", h.APIKey.Value)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return HypixelPlayerResponse{}, APITraceError("failed to send HTTP request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return HypixelPlayerResponse{}, APITraceError("received non-200 response: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return HypixelPlayerResponse{}, APITraceError("failed to read response body: %v", err)
|
||||
}
|
||||
|
||||
var playerResponse HypixelPlayerResponse
|
||||
err = json.Unmarshal(body, &playerResponse)
|
||||
if err != nil {
|
||||
return HypixelPlayerResponse{}, APITraceError("failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if !playerResponse.Success {
|
||||
return HypixelPlayerResponse{}, APITraceError("API returned unsuccessful response")
|
||||
}
|
||||
|
||||
playerResponse.Player.Stats.Bedwars.CalculateRatios()
|
||||
|
||||
return playerResponse, nil
|
||||
}
|
||||
|
||||
func (h *HypixelAPI) FetchPlayerAsync(PlayerUUID string, resultChan chan<- HypixelPlayerResponse, errorChan chan<- error) {
|
||||
go func() {
|
||||
result, err := h.FetchPlayer(PlayerUUID)
|
||||
if err != nil {
|
||||
errorChan <- err
|
||||
return
|
||||
}
|
||||
resultChan <- result
|
||||
}()
|
||||
}
|
||||
|
||||
func (h *HypixelAPI) FetchPlayersAsync(PlayerUUIDs []string, resultChan chan<- HypixelPlayerResponse, errorChan chan<- error) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, uuid := range PlayerUUIDs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(uuid string) {
|
||||
defer wg.Done()
|
||||
|
||||
result, err := h.FetchPlayer(uuid)
|
||||
if err != nil {
|
||||
errorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
resultChan <- result
|
||||
}(uuid)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
close(errorChan)
|
||||
}()
|
||||
}
|
||||
|
||||
type CachedUuid struct {
|
||||
CleanUuid string
|
||||
Uuid string
|
||||
PlayerName string
|
||||
TimeFetched time.Time
|
||||
}
|
||||
|
||||
type UUIDCache struct {
|
||||
mu sync.RWMutex
|
||||
lifetimeLimit time.Duration
|
||||
uuidMap map[string]*CachedUuid
|
||||
cleanUuidMap map[string]*CachedUuid
|
||||
playerNameMap map[string]*CachedUuid
|
||||
}
|
||||
|
||||
func NewUUIDCache(lifetime time.Duration) *UUIDCache {
|
||||
return &UUIDCache{
|
||||
lifetimeLimit: lifetime,
|
||||
uuidMap: make(map[string]*CachedUuid),
|
||||
cleanUuidMap: make(map[string]*CachedUuid),
|
||||
playerNameMap: make(map[string]*CachedUuid),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UUIDCache) Add(cachedUuid *CachedUuid) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.uuidMap[cachedUuid.Uuid] = cachedUuid
|
||||
c.cleanUuidMap[cachedUuid.CleanUuid] = cachedUuid
|
||||
c.playerNameMap[cachedUuid.PlayerName] = cachedUuid
|
||||
}
|
||||
|
||||
func (c *UUIDCache) GetByUuid(uuid string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.uuidMap[uuid]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *UUIDCache) GetByCleanUuid(cleanUuid string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.cleanUuidMap[cleanUuid]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *UUIDCache) GetByPlayerName(playerName string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.playerNameMap[playerName]
|
||||
return val, ok
|
||||
|
||||
}
|
||||
|
||||
func (c *UUIDCache) Get(id string) (*CachedUuid, bool) {
|
||||
val, ok := c.GetByPlayerName(id)
|
||||
if ok {
|
||||
return val, ok
|
||||
}
|
||||
|
||||
val, ok = c.GetByUuid(id)
|
||||
if ok {
|
||||
return val, ok
|
||||
}
|
||||
|
||||
val, ok = c.GetByCleanUuid(id)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *UUIDCache) Delete(uuid string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if cachedUuid, ok := c.uuidMap[uuid]; ok {
|
||||
delete(c.uuidMap, cachedUuid.Uuid)
|
||||
delete(c.cleanUuidMap, cachedUuid.CleanUuid)
|
||||
delete(c.playerNameMap, cachedUuid.PlayerName)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UUIDCache) Clean() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for uuid, cachedUuid := range c.uuidMap {
|
||||
if now.Sub(cachedUuid.TimeFetched) > c.lifetimeLimit {
|
||||
delete(c.uuidMap, uuid)
|
||||
delete(c.cleanUuidMap, cachedUuid.CleanUuid)
|
||||
delete(c.playerNameMap, cachedUuid.PlayerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type MCPlayer struct {
|
||||
Username string
|
||||
UUID string
|
||||
AvatarURL string
|
||||
SkinTexture string
|
||||
Properties []Property
|
||||
}
|
||||
|
||||
func isValidUUID(id string) bool {
|
||||
r := regexp.MustCompile(`^[a-fA-F0-9]{32}$|^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$`)
|
||||
return r.MatchString(id)
|
||||
}
|
||||
|
||||
func isValidUsername(name string) bool {
|
||||
r := regexp.MustCompile(`^[a-zA-Z0-9_]{3,16}$`)
|
||||
return r.MatchString(name)
|
||||
}
|
||||
|
||||
func fetchFromPlayerDB(id string) (*MCPlayer, error) {
|
||||
url := fmt.Sprintf("https://playerdb.co/api/player/minecraft/%s", id)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result["success"].(bool) == false {
|
||||
return nil, fmt.Errorf("player not found (playerdb): %s", id)
|
||||
}
|
||||
|
||||
data := result["data"].(map[string]interface{})
|
||||
player := data["player"].(map[string]interface{})
|
||||
properties := player["properties"].([]interface{})
|
||||
|
||||
mcPlayer := &MCPlayer{
|
||||
Username: player["username"].(string),
|
||||
UUID: player["id"].(string),
|
||||
AvatarURL: player["avatar"].(string),
|
||||
SkinTexture: player["skin_texture"].(string),
|
||||
}
|
||||
|
||||
for _, p := range properties {
|
||||
prop := p.(map[string]interface{})
|
||||
mcPlayer.Properties = append(mcPlayer.Properties, Property{
|
||||
Name: prop["name"].(string),
|
||||
Value: prop["value"].(string),
|
||||
Signature: prop["signature"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
return mcPlayer, nil
|
||||
}
|
||||
|
||||
func FetchMCPlayer(id string) (*MCPlayer, error) {
|
||||
// TODO: Implement mojang API as the fallback
|
||||
// TODO: Catch Nicks (propagate nick catcher through to HypixelAPI)
|
||||
if isValidUUID(id) {
|
||||
player, err := fetchFromPlayerDB(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return player, nil
|
||||
} else if isValidUsername(id) {
|
||||
player, err := fetchFromPlayerDB(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return player, nil
|
||||
} else {
|
||||
return nil, errors.New("invalid input: not a valid UUID or username")
|
||||
}
|
||||
}
|
||||
|
||||
type DisplayTable interface {
|
||||
AddPlayer(player HypixelPlayer)
|
||||
AddPlayerN(player string)
|
||||
Draw()
|
||||
GetPlayers() []HypixelPlayer
|
||||
}
|
||||
|
||||
type BedwarsDisplayTable struct {
|
||||
players []HypixelPlayer
|
||||
}
|
||||
|
||||
func (b *BedwarsDisplayTable) GetPlayers() []HypixelPlayer {
|
||||
return b.players
|
||||
}
|
||||
|
||||
func (b *BedwarsDisplayTable) AddPlayer(player HypixelPlayer) {
|
||||
b.players = append(b.players, player)
|
||||
}
|
||||
|
||||
func (b *BedwarsDisplayTable) AddPlayerN(player string) {
|
||||
cachedPlayer, isAlive := app.UUIDCache.Get(UUID)
|
||||
|
||||
if isAlive {
|
||||
fmt.Printf("Cached player: %+v\n", cachedPlayer)
|
||||
} else {
|
||||
playerdat, err := FetchMCPlayer(UUID)
|
||||
if err != nil {
|
||||
fmt.Println("Error fetching player:", err)
|
||||
} else {
|
||||
app.UUIDCache.Add(&CachedUuid{
|
||||
CleanUuid: strings.ReplaceAll(playerdat.UUID, "-", ""),
|
||||
Uuid: playerdat.UUID,
|
||||
PlayerName: playerdat.Username,
|
||||
TimeFetched: time.Now(),
|
||||
})
|
||||
|
||||
fmt.Printf("Fetched and cached player: %+v\n", playerdat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BedwarsDisplayTable) Draw() {
|
||||
fmt.Println("=== Bedwars Player Table ===")
|
||||
for _, player := range b.players {
|
||||
fmt.Printf("Player: %s, Score: %d\n", player.DisplayName, player.Achievements.BedwarsStar)
|
||||
}
|
||||
}
|
||||
|
||||
type App struct {
|
||||
LogPath string
|
||||
API *HypixelAPI
|
||||
UUIDCache *UUIDCache
|
||||
CurrentDisplayTable DisplayTable
|
||||
}
|
||||
|
||||
func NewApp(apiKey string) *App {
|
||||
path := os.Getenv("USERPROFILE")
|
||||
logPath := filepath.Join(path, ".lunarclient", "offline", "multiver", "logs", "latest.log")
|
||||
|
||||
return &App{
|
||||
LogPath: logPath,
|
||||
API: NewHypixelAPI(apiKey),
|
||||
UUIDCache: NewUUIDCache(120 * time.Minute),
|
||||
CurrentDisplayTable: &BedwarsDisplayTable{},
|
||||
}
|
||||
}
|
||||
|
||||
type LogBuffer struct {
|
||||
strings []string
|
||||
size int
|
||||
}
|
||||
|
||||
func NewLogBuffer(size int) *LogBuffer {
|
||||
return &LogBuffer{
|
||||
strings: make([]string, 0, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogBuffer) Add(s string) {
|
||||
if len(l.strings) == l.size {
|
||||
l.strings = l.strings[1:]
|
||||
}
|
||||
l.strings = append(l.strings, s)
|
||||
}
|
||||
|
||||
func (l *LogBuffer) Get() []string {
|
||||
return l.strings
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetLast() (string, error) {
|
||||
if len(l.strings) == 0 {
|
||||
return "", fmt.Errorf("log buffer is empty")
|
||||
}
|
||||
return l.strings[len(l.strings)-1], nil
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetSecondToLast() (string, error) {
|
||||
if len(l.strings) < 2 {
|
||||
return "", fmt.Errorf("log buffer does not have enough lines")
|
||||
}
|
||||
return l.strings[len(l.strings)-2], nil
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetLineStepsBack(x int) (string, error) {
|
||||
if x < 0 || x >= len(l.strings) {
|
||||
return "", fmt.Errorf("log buffer does not have enough lines to step back %d times", x)
|
||||
}
|
||||
return l.strings[len(l.strings)-1-x], nil
|
||||
}
|
||||
|
||||
var LogBuf = NewLogBuffer(10)
|
||||
|
||||
func replaceCorruptedRune(msg string) string {
|
||||
runes := []rune(msg)
|
||||
for i, r := range runes {
|
||||
if r == '<27>' {
|
||||
runes[i] = '§'
|
||||
}
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func (a *App) onFileEmit(line string) {
|
||||
msg := strings.TrimSpace(line)
|
||||
if len(msg) < 34 {
|
||||
return
|
||||
}
|
||||
submsg := msg[33:]
|
||||
if len(submsg) != 0 {
|
||||
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, ",")
|
||||
for _, player := range players {
|
||||
playerName := strings.TrimSpace(player)
|
||||
playerUUID, err := FetchMCPlayer(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.UUID, playerName, playerName, time.Now()}
|
||||
a.UUIDCache.Add(&cachedPlayer)
|
||||
|
||||
//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)
|
||||
}
|
||||
|
||||
} else if strings.HasPrefix(submsg, PartyListSeparatorLinePrefix) { // Party List
|
||||
last, _ := LogBuf.GetSecondToLast()
|
||||
// TODO: Check if moderators
|
||||
if !strings.HasPrefix(last, PartyListMembersPrefix) {
|
||||
return
|
||||
}
|
||||
|
||||
PartyMembersMsg := strings.TrimPrefix(last, PartyListMembersPrefix)
|
||||
for _, player := range strings.Split(PartyMembersMsg, ",") {
|
||||
playerName := strings.TrimSpace(strings.TrimSuffix(player, " ?"))
|
||||
if strings.HasPrefix(playerName, "[") {
|
||||
playerName = strings.Split(playerName, " ")[1]
|
||||
}
|
||||
print("Found Player: '")
|
||||
print(playerName)
|
||||
print("'\n")
|
||||
a.CurrentDisplayTable.AddPlayerN(playerName)
|
||||
|
||||
//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 := 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]
|
||||
}
|
||||
print("Found Leader: '")
|
||||
print(playerName)
|
||||
print("'\n")
|
||||
a.CurrentDisplayTable.AddPlayerN(playerName)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
println(submsg)
|
||||
}
|
||||
|
||||
func (a *App) Start() {
|
||||
|
||||
rl.SetConfigFlags(rl.FlagWindowTransparent | rl.FlagWindowUndecorated)
|
||||
screenWidth := 800
|
||||
screenHeight := 600
|
||||
rl.InitWindow(int32(screenWidth), int32(screenHeight), "Raylib in Go")
|
||||
defer rl.CloseWindow()
|
||||
|
||||
windowPosition := rl.NewVector2(500, 200)
|
||||
rl.SetWindowPosition(int(int32(windowPosition.X)), int(int32(windowPosition.Y)))
|
||||
|
||||
var panOffset rl.Vector2
|
||||
var dragWindow bool = false
|
||||
|
||||
const LowFps = 8
|
||||
rl.SetTargetFPS(LowFps)
|
||||
|
||||
lineCh := make(chan string)
|
||||
go tailFile(a.LogPath, lineCh)
|
||||
|
||||
ch := make(chan HypixelPlayerResponse)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
sx := 10
|
||||
sy := 10
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
mousePosition := rl.GetMousePosition()
|
||||
windowPosition := rl.GetWindowPosition()
|
||||
screenMousePosition := rl.Vector2{
|
||||
X: windowPosition.X + mousePosition.X,
|
||||
Y: windowPosition.Y + mousePosition.Y,
|
||||
}
|
||||
|
||||
if rl.IsMouseButtonPressed(rl.MouseLeftButton) && !dragWindow {
|
||||
rl.SetTargetFPS(int32(rl.GetMonitorRefreshRate(0)))
|
||||
dragWindow = true
|
||||
panOffset.X = screenMousePosition.X - float32(windowPosition.X)
|
||||
panOffset.Y = screenMousePosition.Y - float32(windowPosition.Y)
|
||||
}
|
||||
|
||||
if dragWindow {
|
||||
newWindowPositionX := int(screenMousePosition.X - panOffset.X)
|
||||
newWindowPositionY := int(screenMousePosition.Y - panOffset.Y)
|
||||
|
||||
rl.SetWindowPosition(newWindowPositionX, newWindowPositionY)
|
||||
|
||||
if rl.IsMouseButtonReleased(rl.MouseLeftButton) {
|
||||
dragWindow = false
|
||||
rl.SetTargetFPS(LowFps)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case line := <-lineCh:
|
||||
a.onFileEmit(replaceCorruptedRune(line))
|
||||
default:
|
||||
}
|
||||
|
||||
rl.BeginDrawing()
|
||||
rl.ClearBackground(rl.Color{0, 0, 0, 100})
|
||||
|
||||
yOffset := sy
|
||||
for _, player := range a.CurrentDisplayTable.GetPlayers() {
|
||||
playerName := player.DisplayName
|
||||
bedwarsLevel := player.Achievements.BedwarsStar
|
||||
bedwarsWins := player.Stats.Bedwars.Wins
|
||||
|
||||
rl.DrawText(fmt.Sprintf("Player: %s", playerName), int32(sx), int32(yOffset), 20, rl.Black)
|
||||
yOffset += 25
|
||||
rl.DrawText(fmt.Sprintf("Bedwars Level: %d", bedwarsLevel), int32(sx), int32(yOffset), 18, rl.DarkGray)
|
||||
yOffset += 25
|
||||
rl.DrawText(fmt.Sprintf("Bedwars Wins: %d", bedwarsWins), int32(sx), int32(yOffset), 18, rl.DarkGray)
|
||||
yOffset += 40
|
||||
}
|
||||
rl.EndDrawing()
|
||||
}
|
||||
}
|
||||
|
||||
func 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
|
||||
}
|
||||
}
|
||||
|
||||
const KEY = "5039a811-1b27-4db6-a7f0-c8dd28eeebcd"
|
||||
const UUID = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||
|
||||
func main() {
|
||||
app := NewApp(KEY)
|
||||
app.Start()
|
||||
|
||||
res, err := app.API.FetchPlayer(UUID)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%+v\n", res.Player)
|
||||
app.CurrentDisplayTable.AddPlayer(res.Player)
|
||||
app.CurrentDisplayTable.Draw()
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
||||
module HypixelStuff
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/gen2brain/raylib-go/raylib v0.0.0-20241014163942-bf5ef1835077
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.7.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
)
|
8
go.sum
Normal file
8
go.sum
Normal file
@ -0,0 +1,8 @@
|
||||
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
|
||||
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20241014163942-bf5ef1835077 h1:DEJVMa/7rYR4xLwpuSoMGgcaAKXW+64VASWtBWusQNM=
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20241014163942-bf5ef1835077/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
538
main.go
Normal file
538
main.go
Normal file
@ -0,0 +1,538 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HypixelPlayerAchievements struct {
|
||||
BedwarsStar int `json:"bedwars_level"`
|
||||
}
|
||||
|
||||
type HypixelPlayerBedwarsStats struct {
|
||||
Wins int `json:"wins_bedwars"`
|
||||
}
|
||||
|
||||
type HypixelPlayerStats struct {
|
||||
Bedwars HypixelPlayerBedwarsStats `json:"Bedwars"`
|
||||
}
|
||||
|
||||
type HypixelPlayer struct {
|
||||
Achievements HypixelPlayerAchievements `json:"achievements"`
|
||||
Stats HypixelPlayerStats `json:"stats"`
|
||||
}
|
||||
|
||||
type HypixelPlayerResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Player HypixelPlayer `json:"player"`
|
||||
}
|
||||
|
||||
const URL_ = "https://api.hypixel.net/player?key=5039a811-1b27-4db6-a7f0-c8dd28eeebcd&uuid="
|
||||
|
||||
type CachedUuid struct {
|
||||
CleanUuid string
|
||||
Uuid string
|
||||
PlayerName string
|
||||
TimeFetched time.Time
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
mu sync.RWMutex
|
||||
uuidMap map[string]*CachedUuid
|
||||
cleanUuidMap map[string]*CachedUuid
|
||||
playerNameMap map[string]*CachedUuid
|
||||
}
|
||||
|
||||
func NewCache() *Cache {
|
||||
return &Cache{
|
||||
uuidMap: make(map[string]*CachedUuid),
|
||||
cleanUuidMap: make(map[string]*CachedUuid),
|
||||
playerNameMap: make(map[string]*CachedUuid),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) Add(cachedUuid *CachedUuid) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.uuidMap[cachedUuid.Uuid] = cachedUuid
|
||||
c.cleanUuidMap[cachedUuid.CleanUuid] = cachedUuid
|
||||
c.playerNameMap[cachedUuid.PlayerName] = cachedUuid
|
||||
}
|
||||
|
||||
func (c *Cache) GetByUuid(uuid string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.uuidMap[uuid]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *Cache) GetByCleanUuid(cleanUuid string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.cleanUuidMap[cleanUuid]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *Cache) GetByPlayerName(playerName string) (*CachedUuid, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
val, ok := c.playerNameMap[playerName]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c *Cache) Delete(uuid string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if cachedUuid, ok := c.uuidMap[uuid]; ok {
|
||||
delete(c.uuidMap, cachedUuid.Uuid)
|
||||
delete(c.cleanUuidMap, cachedUuid.CleanUuid)
|
||||
delete(c.playerNameMap, cachedUuid.PlayerName)
|
||||
}
|
||||
}
|
||||
|
||||
var UuidCache = NewCache()
|
||||
|
||||
type UUIDResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func GetUUIDFromName(name string) (string, error) {
|
||||
println(fmt.Sprintf("Player Name to search: '%s'", name))
|
||||
url := fmt.Sprintf("https://api.mojang.com/users/profiles/minecraft/%s", name)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("UUID error: failed to fetch UUID for name %s, status code: %d", name, resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var uuidResponse UUIDResponse
|
||||
err = json.Unmarshal(body, &uuidResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uuidResponse.ID, nil
|
||||
}
|
||||
|
||||
//func GetNameFromUUID(uuid string) ([]string, error) {
|
||||
// uuid = strings.ReplaceAll(uuid, "-", "")
|
||||
//
|
||||
// url := fmt.Sprintf("https://api.mojang.com/user/profiles/%s/names", uuid)
|
||||
//
|
||||
// resp, err := http.Get(url)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
//
|
||||
// if resp.StatusCode != http.StatusOK {
|
||||
// return nil, fmt.Errorf("error: failed to fetch name for UUID %s, status code: %d", uuid, resp.StatusCode)
|
||||
// }
|
||||
//
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// var nameHistory []NameHistory
|
||||
// err = json.Unmarshal(body, &nameHistory)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// var names []string
|
||||
// for _, history := range nameHistory {
|
||||
// names = append(names, history.Name)
|
||||
// }
|
||||
//
|
||||
// return names, nil
|
||||
//}
|
||||
|
||||
func fetchHypixelData(url string, ch chan<- HypixelPlayerResponse, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Println("Error fetching data:", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("Error: failed to fetch data, status code: %d\n", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println("Error reading response body:", err)
|
||||
return
|
||||
}
|
||||
|
||||
var playerResponse HypixelPlayerResponse
|
||||
err = json.Unmarshal(body, &playerResponse)
|
||||
if err != nil {
|
||||
log.Println("Error unmarshalling JSON:", err)
|
||||
return
|
||||
}
|
||||
|
||||
ch <- playerResponse
|
||||
}
|
||||
|
||||
type StatsDisplayPlayer struct {
|
||||
Name string
|
||||
Player HypixelPlayer
|
||||
}
|
||||
|
||||
type StatsDisplayMode int
|
||||
|
||||
const (
|
||||
BEDWARS StatsDisplayMode = iota
|
||||
)
|
||||
|
||||
type StatsDisplay struct {
|
||||
Players []StatsDisplayPlayer
|
||||
Mode StatsDisplayMode
|
||||
}
|
||||
|
||||
func NewStatsDisplay() *StatsDisplay {
|
||||
return &StatsDisplay{
|
||||
Players: make([]StatsDisplayPlayer, 0),
|
||||
Mode: BEDWARS,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *StatsDisplay) AddPlayer(playername string) {
|
||||
playeruuid, err := GetUUIDFromName(playername)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching UUID for player %s: %v\n", playername, err)
|
||||
return
|
||||
}
|
||||
|
||||
hypixelApiUrl := URL_ + playeruuid
|
||||
|
||||
ch := make(chan HypixelPlayerResponse)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go fetchHypixelData(hypixelApiUrl, ch, &wg)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
for playerResponse := range ch {
|
||||
if playerResponse.Success {
|
||||
fmt.Printf("Player Bedwars Level: %d\n", playerResponse.Player.Achievements.BedwarsStar)
|
||||
fmt.Printf("Player Bedwars Wins: %d\n", playerResponse.Player.Stats.Bedwars.Wins)
|
||||
|
||||
d.Players = append(d.Players, StatsDisplayPlayer{Player: playerResponse.Player, Name: playername})
|
||||
} else {
|
||||
fmt.Println("Failed to fetch player data.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var StatsDisplayApp = NewStatsDisplay()
|
||||
|
||||
type LogBuffer struct {
|
||||
strings []string
|
||||
size int
|
||||
}
|
||||
|
||||
func NewLogBuffer(size int) *LogBuffer {
|
||||
return &LogBuffer{
|
||||
strings: make([]string, 0, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogBuffer) Add(s string) {
|
||||
if len(l.strings) == l.size {
|
||||
l.strings = l.strings[1:]
|
||||
}
|
||||
l.strings = append(l.strings, s)
|
||||
}
|
||||
|
||||
func (l *LogBuffer) Get() []string {
|
||||
return l.strings
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetLast() (string, error) {
|
||||
if len(l.strings) == 0 {
|
||||
return "", fmt.Errorf("log buffer is empty")
|
||||
}
|
||||
return l.strings[len(l.strings)-1], nil
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetSecondToLast() (string, error) {
|
||||
if len(l.strings) < 2 {
|
||||
return "", fmt.Errorf("log buffer does not have enough lines")
|
||||
}
|
||||
return l.strings[len(l.strings)-2], nil
|
||||
}
|
||||
|
||||
func (l *LogBuffer) GetLineStepsBack(x int) (string, error) {
|
||||
if x < 0 || x >= len(l.strings) {
|
||||
return "", fmt.Errorf("log buffer does not have enough lines to step back %d times", x)
|
||||
}
|
||||
return l.strings[len(l.strings)-1-x], nil
|
||||
}
|
||||
|
||||
var LogBuf = NewLogBuffer(10)
|
||||
|
||||
func replaceCorruptedRune(msg string) string {
|
||||
runes := []rune(msg)
|
||||
for i, r := range runes {
|
||||
if r == '<27>' {
|
||||
runes[i] = '§'
|
||||
}
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func onFileEmit(line string) {
|
||||
msg := strings.TrimSpace(line)
|
||||
if len(msg) < 34 {
|
||||
return
|
||||
}
|
||||
submsg := msg[33:]
|
||||
if len(submsg) != 0 {
|
||||
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, ",")
|
||||
for _, player := range players {
|
||||
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)
|
||||
|
||||
//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)
|
||||
}
|
||||
|
||||
} else if strings.HasPrefix(submsg, PartyListSeparatorLinePrefix) { // Party List
|
||||
last, _ := LogBuf.GetSecondToLast()
|
||||
// TODO: Check if moderators
|
||||
if !strings.HasPrefix(last, PartyListMembersPrefix) {
|
||||
return
|
||||
}
|
||||
|
||||
PartyMembersMsg := strings.TrimPrefix(last, PartyListMembersPrefix)
|
||||
for _, player := range strings.Split(PartyMembersMsg, ",") {
|
||||
playerName := strings.TrimSpace(strings.TrimSuffix(player, " ?"))
|
||||
if strings.HasPrefix(playerName, "[") {
|
||||
playerName = strings.Split(playerName, " ")[1]
|
||||
}
|
||||
print("Found Player: '")
|
||||
print(playerName)
|
||||
print("'\n")
|
||||
StatsDisplayApp.AddPlayer(playerName)
|
||||
|
||||
//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 := 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]
|
||||
}
|
||||
print("Found Leader: '")
|
||||
print(playerName)
|
||||
print("'\n")
|
||||
StatsDisplayApp.AddPlayer(playerName)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
println(submsg)
|
||||
}
|
||||
|
||||
func 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 start_demo() {
|
||||
path := os.Getenv("USERPROFILE")
|
||||
path = filepath.Join(path, ".lunarclient", "offline", "multiver", "logs", "latest.log")
|
||||
|
||||
rl.SetConfigFlags(rl.FlagWindowTransparent | rl.FlagWindowUndecorated)
|
||||
screenWidth := 800
|
||||
screenHeight := 600
|
||||
rl.InitWindow(int32(screenWidth), int32(screenHeight), "Raylib in Go")
|
||||
defer rl.CloseWindow()
|
||||
|
||||
windowPosition := rl.NewVector2(500, 200)
|
||||
rl.SetWindowPosition(int(int32(windowPosition.X)), int(int32(windowPosition.Y)))
|
||||
|
||||
var panOffset rl.Vector2
|
||||
var dragWindow bool = false
|
||||
|
||||
const LowFps = 8
|
||||
rl.SetTargetFPS(LowFps)
|
||||
|
||||
lineCh := make(chan string)
|
||||
go tailFile(path, lineCh)
|
||||
|
||||
ch := make(chan HypixelPlayerResponse)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
sx := 10
|
||||
sy := 10
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
mousePosition := rl.GetMousePosition()
|
||||
windowPosition := rl.GetWindowPosition()
|
||||
screenMousePosition := rl.Vector2{
|
||||
X: windowPosition.X + mousePosition.X,
|
||||
Y: windowPosition.Y + mousePosition.Y,
|
||||
}
|
||||
|
||||
if rl.IsMouseButtonPressed(rl.MouseLeftButton) && !dragWindow {
|
||||
rl.SetTargetFPS(int32(rl.GetMonitorRefreshRate(0)))
|
||||
dragWindow = true
|
||||
panOffset.X = screenMousePosition.X - float32(windowPosition.X)
|
||||
panOffset.Y = screenMousePosition.Y - float32(windowPosition.Y)
|
||||
}
|
||||
|
||||
if dragWindow {
|
||||
newWindowPositionX := int(screenMousePosition.X - panOffset.X)
|
||||
newWindowPositionY := int(screenMousePosition.Y - panOffset.Y)
|
||||
|
||||
rl.SetWindowPosition(newWindowPositionX, newWindowPositionY)
|
||||
|
||||
if rl.IsMouseButtonReleased(rl.MouseLeftButton) {
|
||||
dragWindow = false
|
||||
rl.SetTargetFPS(LowFps)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case line := <-lineCh:
|
||||
onFileEmit(replaceCorruptedRune(line))
|
||||
default:
|
||||
}
|
||||
|
||||
rl.BeginDrawing()
|
||||
rl.ClearBackground(rl.Color{0, 0, 0, 100})
|
||||
|
||||
yOffset := sy
|
||||
for _, player := range StatsDisplayApp.Players {
|
||||
playerName := player.Name
|
||||
bedwarsLevel := player.Player.Achievements.BedwarsStar
|
||||
bedwarsWins := player.Player.Stats.Bedwars.Wins
|
||||
|
||||
rl.DrawText(fmt.Sprintf("Player: %s", playerName), int32(sx), int32(yOffset), 20, rl.Black)
|
||||
yOffset += 25
|
||||
rl.DrawText(fmt.Sprintf("Bedwars Level: %d", bedwarsLevel), int32(sx), int32(yOffset), 18, rl.DarkGray)
|
||||
yOffset += 25
|
||||
rl.DrawText(fmt.Sprintf("Bedwars Wins: %d", bedwarsWins), int32(sx), int32(yOffset), 18, rl.DarkGray)
|
||||
yOffset += 40
|
||||
}
|
||||
|
||||
rl.EndDrawing()
|
||||
}
|
||||
|
||||
close(lineCh)
|
||||
|
||||
for playerResponse := range ch {
|
||||
if playerResponse.Success {
|
||||
fmt.Printf("Player Bedwars Level: %d\n", playerResponse.Player.Achievements.BedwarsStar)
|
||||
fmt.Printf("Player Bedwars Wins: %d\n", playerResponse.Player.Stats.Bedwars.Wins)
|
||||
} else {
|
||||
fmt.Println("Failed to fetch player data.")
|
||||
}
|
||||
}
|
||||
}
|
36
notes.md
Normal file
36
notes.md
Normal file
@ -0,0 +1,36 @@
|
||||
rl.SetWindowState(rl.FlagWindowUndecorated)
|
||||
|
||||
|
||||
//[07:41:31] [Client thread/INFO]: [CHAT] {"server":"mini208R","gametype":"HOUSING","mode":"dynamic","map":"Base"}
|
||||
|
||||
https://playerdb.co/api/player/minecraft/illyum
|
||||
https://playerdb.co/
|
||||
|
||||
|
||||
[CHAT] ONLINE: angryhacks
|
||||
|
||||
### When to look up stats
|
||||
- on private message / dm
|
||||
- Party Invite
|
||||
- Guild Invite
|
||||
- Friend Invite
|
||||
- When you invite someone to party
|
||||
- When party leader/other member invites party
|
||||
- When party joins (/stream open)
|
||||
- Duel Request (that specific duel's stats)
|
||||
|
||||
### Trackers
|
||||
- Tips?
|
||||
- WDR Reports?
|
||||
|
||||
### Other Stuff To Add
|
||||
If you screenshot a leaderboard, it will check the screenshots folder and try to detect if a screenshot has a leaderboard in it, then it will load the leaderboard in and check for those people's stats and display them off to the side (daily only since other lb are on api)
|
||||
|
||||
|
||||
|
||||
## Building:
|
||||
```bash
|
||||
set CGO_ENABLED=0
|
||||
go build -ldflags="-s -w"
|
||||
upx --best --lzma HypixelStuff.exe
|
||||
```
|
BIN
raylib.dll
Normal file
BIN
raylib.dll
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user