Compare commits
2 Commits
f489e0c57a
...
65caea328b
Author | SHA1 | Date | |
---|---|---|---|
65caea328b | |||
c24ae92a81 |
33
README.md
Normal file
33
README.md
Normal file
@ -0,0 +1,33 @@
|
||||
//[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
|
||||
```
|
106
app/app.go
106
app/app.go
@ -1 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/haleyrom/skia-go"
|
||||
"image/color"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/go-gl/glfw/v3.3/glfw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// GLFW event handling must run on the main OS thread
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize GLFW
|
||||
if err := glfw.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize GLFW: %v", err)
|
||||
}
|
||||
defer glfw.Terminate()
|
||||
|
||||
// Create GLFW window
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 3)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 3)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(800, 600, "GLFW + Skia Demo", nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create GLFW window: %v", err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
// Initialize OpenGL
|
||||
if err := gl.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize OpenGL: %v", err)
|
||||
}
|
||||
|
||||
// Set up Skia surface
|
||||
glInterface := skia.NewNativeGrGlinterface()
|
||||
if glInterface == nil {
|
||||
log.Fatalf("failed to create Skia OpenGL interface")
|
||||
}
|
||||
grContext := skia.NewGLGrContext(glInterface)
|
||||
if grContext == nil {
|
||||
log.Fatalf("failed to create Skia GrContext")
|
||||
}
|
||||
|
||||
// Get framebuffer info
|
||||
var framebufferInfo skia.GrGlFramebufferinfo
|
||||
var fboID int32
|
||||
gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &fboID)
|
||||
framebufferInfo.FFBOID = uint32(fboID)
|
||||
framebufferInfo.FFormat = gl.RGBA8
|
||||
|
||||
width, height := window.GetSize()
|
||||
renderTarget := skia.NewGlGrBackendrendertarget(int32(width), int32(height), 1, 8, &framebufferInfo)
|
||||
if renderTarget == nil {
|
||||
log.Fatalf("failed to create Skia render target")
|
||||
}
|
||||
|
||||
surface := skia.NewSurfaceBackendRenderTarget(grContext, renderTarget, skia.GR_SURFACE_ORIGIN_BOTTOM_LEFT, skia.SK_COLORTYPE_RGBA_8888, nil, nil)
|
||||
if surface == nil {
|
||||
log.Fatalf("failed to create Skia surface")
|
||||
}
|
||||
defer surface.Unref()
|
||||
|
||||
c := color.RGBA{R: 255, G: 100, B: 50, A: 255}
|
||||
f := skia.NewColor4f(c)
|
||||
colorWhite := f.ToColor()
|
||||
|
||||
for !window.ShouldClose() {
|
||||
gl.ClearColor(0.3, 0.3, 0.3, 1.0)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
|
||||
canvas := surface.GetCanvas()
|
||||
|
||||
paint := skia.NewPaint()
|
||||
paint.SetColor(0xffff0000)
|
||||
|
||||
// Clear the canvas with white color
|
||||
canvas.Clear(colorWhite)
|
||||
|
||||
// Create a rectangle and draw it
|
||||
rect := skia.Rect{
|
||||
Left: 100,
|
||||
Top: 100,
|
||||
Right: 300,
|
||||
Bottom: 300,
|
||||
}
|
||||
canvas.DrawRect(&rect, paint)
|
||||
grContext.Flush()
|
||||
|
||||
// Swap OpenGL buffers
|
||||
window.SwapBuffers()
|
||||
|
||||
// Poll for GLFW events
|
||||
glfw.PollEvents()
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
surface.Unref()
|
||||
grContext.Unref()
|
||||
}
|
||||
|
60
app/logger.go
Normal file
60
app/logger.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppLogger = NewCustomLogger()
|
||||
|
||||
// CustomLogger wraps the standard logger
|
||||
type CustomLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewCustomLogger initializes the custom logger
|
||||
func NewCustomLogger() *CustomLogger {
|
||||
// Create a logger that writes to stdout with no flags
|
||||
return &CustomLogger{
|
||||
logger: log.New(os.Stdout, "", 0), // we will handle formatting manually
|
||||
}
|
||||
}
|
||||
|
||||
// logFormat retrieves the function name, file, and line number
|
||||
func logFormat() string {
|
||||
// Use the runtime.Caller to retrieve caller info
|
||||
pc, file, line, ok := runtime.Caller(2)
|
||||
if !ok {
|
||||
file = "unknown"
|
||||
line = 0
|
||||
}
|
||||
// Get function name
|
||||
funcName := runtime.FuncForPC(pc).Name()
|
||||
|
||||
// Format timestamp
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
// Return formatted log prefix (timestamp, file, line, and function name)
|
||||
return fmt.Sprintf("%s - %s:%d - %s: ", timestamp, file, line, funcName)
|
||||
}
|
||||
|
||||
// Info logs informational messages
|
||||
func (c *CustomLogger) Info(msg string) {
|
||||
c.logger.Println(logFormat() + "INFO: " + msg)
|
||||
}
|
||||
|
||||
// Error logs error messages
|
||||
func (c *CustomLogger) Error(msg string) {
|
||||
c.logger.Println(logFormat() + "ERROR: " + msg)
|
||||
}
|
||||
|
||||
// Example usage of the custom logger
|
||||
func main() {
|
||||
logger := NewCustomLogger()
|
||||
|
||||
logger.Info("This is an info message")
|
||||
logger.Error("This is an error message")
|
||||
}
|
161
app/main.go
161
app/main.go
@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
netclient "hudly/client"
|
||||
"hudly/hypixel"
|
||||
"hudly/mcfetch"
|
||||
"log"
|
||||
@ -9,8 +12,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var key = "9634ea92-80f0-482f-aebd-b082c6ed6f19"
|
||||
var uuid = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||
var key = "ccebff0f-939a-4afe-b5b3-30a7a665ee38"
|
||||
var UUID = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||
|
||||
func demo() {
|
||||
// Ensure a username is provided as a command-line argument
|
||||
@ -73,3 +76,157 @@ func demo() {
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("%+v", res))
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cmd string
|
||||
fmt.Print(" | CREATE\n | JOIN\nEnter Choice:\n>")
|
||||
fmt.Scanln(&cmd)
|
||||
|
||||
if cmd != "CREATE" && cmd != "JOIN" {
|
||||
fmt.Println("Invalid command.")
|
||||
return
|
||||
}
|
||||
|
||||
client, err := netclient.NewClient("0.0.0.0", uuid.New().String())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
return
|
||||
}
|
||||
var code string
|
||||
if cmd == "CREATE" {
|
||||
var err error
|
||||
code, err = client.CreateRoom("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Created room:", code)
|
||||
|
||||
err = client.JoinRoom(code, "password")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
println("Connected to room")
|
||||
} else if cmd == "JOIN" {
|
||||
var password string
|
||||
fmt.Print("Enter Room Code:\n>")
|
||||
fmt.Scanln(&code)
|
||||
|
||||
fmt.Print("Enter Room Password:\n>")
|
||||
fmt.Scanln(&password)
|
||||
|
||||
err := client.JoinRoom(code, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Joined room:", code)
|
||||
}
|
||||
|
||||
client.ListenForData()
|
||||
// Handle received data in a separate goroutine
|
||||
go func() {
|
||||
for data := range client.DataChannel {
|
||||
fmt.Printf("Received data: %s\n", data)
|
||||
}
|
||||
}()
|
||||
|
||||
if cmd == "CREATE" {
|
||||
println("Sleeping...")
|
||||
time.Sleep(time.Second * 20)
|
||||
println("Continuing!")
|
||||
|
||||
thing := hypixel.NewAPIKey(key)
|
||||
api := hypixel.NewAPI(*thing)
|
||||
|
||||
memCache := &mcfetch.MemoryCache{}
|
||||
memCache.Init()
|
||||
|
||||
resultChan := make(chan map[string]interface{})
|
||||
errorChan := make(chan error)
|
||||
|
||||
players := []string{"illyum", "ergopunch"}
|
||||
stuff := []map[string]interface{}{} // List to hold player JSON objects
|
||||
|
||||
println("Getting UUIDs")
|
||||
for _, player := range players {
|
||||
asyncFetcher := mcfetch.NewAsyncPlayerFetcher(
|
||||
player,
|
||||
memCache,
|
||||
2,
|
||||
2*time.Second,
|
||||
5*time.Second,
|
||||
)
|
||||
asyncFetcher.FetchPlayerData(resultChan, errorChan)
|
||||
}
|
||||
|
||||
var userID string
|
||||
for i := 0; i < len(players); i++ {
|
||||
select {
|
||||
case data := <-resultChan:
|
||||
if uuid, ok := data["id"].(string); ok {
|
||||
userID = uuid
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf("%+v", data))
|
||||
log.Fatal("UUID not found or invalid for player")
|
||||
}
|
||||
|
||||
// Fetch player stats
|
||||
res, err := api.GetPlayerResponse(userID)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get player data: %v", err)
|
||||
}
|
||||
|
||||
// Calculate derived stats
|
||||
res.Player.Stats.Bedwars.WLR = calculateRatio(res.Player.Stats.Bedwars.Wins, res.Player.Stats.Bedwars.Losses)
|
||||
res.Player.Stats.Bedwars.KDR = calculateRatio(res.Player.Stats.Bedwars.Kills, res.Player.Stats.Bedwars.Deaths)
|
||||
res.Player.Stats.Bedwars.FKDR = calculateRatio(res.Player.Stats.Bedwars.FinalKills, res.Player.Stats.Bedwars.FinalDeaths)
|
||||
res.Player.Stats.Bedwars.BBLR = calculateRatio(res.Player.Stats.Bedwars.BedsBroken, res.Player.Stats.Bedwars.BedsLost)
|
||||
|
||||
// Convert player struct to JSON
|
||||
playerJSON, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal player data: %v", err)
|
||||
}
|
||||
|
||||
// Append the player JSON to the stuff list
|
||||
var playerMap map[string]interface{}
|
||||
json.Unmarshal(playerJSON, &playerMap)
|
||||
stuff = append(stuff, playerMap)
|
||||
|
||||
case err := <-errorChan:
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
println("UUID Done!")
|
||||
println("Sending data...")
|
||||
|
||||
// Send the list of JSON objects to the client
|
||||
message, err := json.Marshal(stuff)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal stuff: %v", err)
|
||||
}
|
||||
err = client.SendData(string(message))
|
||||
if err != nil {
|
||||
log.Printf("Error sending data: %v", err)
|
||||
}
|
||||
fmt.Println("Sent stuff:", stuff)
|
||||
println("Sending Done!")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-client.DataChannel: // This blocks the main goroutine until data is received
|
||||
fmt.Println("Data received, exiting...")
|
||||
}
|
||||
|
||||
fmt.Println("Closing app")
|
||||
}
|
||||
|
||||
// calculateRatio is a helper function to calculate ratios (e.g., WLR, KDR, etc.)
|
||||
func calculateRatio(numerator, denominator int) float64 {
|
||||
if denominator == 0 {
|
||||
return float64(numerator)
|
||||
}
|
||||
return float64(numerator) / float64(denominator)
|
||||
}
|
||||
|
270
client/client.go
270
client/client.go
@ -1,75 +1,239 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Connect to the chat server
|
||||
func connectToServer(address string) (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", address)
|
||||
const PORT = ":5518"
|
||||
|
||||
type PacketID int
|
||||
|
||||
const (
|
||||
CONNECT_REQUEST PacketID = iota
|
||||
CONNECT_RESPONSE
|
||||
JOIN_REQUEST
|
||||
JOIN_RESPONSE
|
||||
CREATE_REQUEST
|
||||
CREATE_RESPONSE
|
||||
READ_DATA
|
||||
SEND_DATA_REQUEST
|
||||
SEND_DATA_RESPONSE
|
||||
LEAVE_ROOM
|
||||
)
|
||||
|
||||
// Client represents a client connection to the server.
|
||||
type Client struct {
|
||||
Conn net.Conn
|
||||
ClientID string
|
||||
RoomCode string
|
||||
DataChannel chan string
|
||||
}
|
||||
|
||||
// NewClient creates a new client and connects to the server.
|
||||
func NewClient(serverAddr, clientID string) (*Client, error) {
|
||||
conn, err := net.Dial("tcp", serverAddr+PORT)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not connect to server: %v", err)
|
||||
return nil, fmt.Errorf("failed to connect to server: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
return &Client{
|
||||
Conn: conn,
|
||||
ClientID: clientID,
|
||||
DataChannel: make(chan string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read input from the terminal and send it to the server
|
||||
func readInputAndSend(conn net.Conn) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("> ")
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.TrimSpace(text)
|
||||
|
||||
// Send the command to the server
|
||||
_, err := conn.Write([]byte(text + "\n"))
|
||||
if err != nil {
|
||||
fmt.Println("Error sending message:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If the user types 'quit', exit the program
|
||||
if text == "quit" {
|
||||
fmt.Println("Goodbye!")
|
||||
return
|
||||
}
|
||||
// CreateRoom creates a new room on the server.
|
||||
func (c *Client) CreateRoom(password string) (string, error) {
|
||||
request := CreateRoomRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if err := c.sendPacket(CREATE_REQUEST, request); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var response CreateRoomResponsePacket
|
||||
if err := c.receivePacket(CREATE_RESPONSE, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return "", errors.New(response.Reason)
|
||||
}
|
||||
|
||||
c.RoomCode = response.RoomCode
|
||||
return response.RoomCode, nil
|
||||
}
|
||||
|
||||
// Listen for incoming messages from the server
|
||||
func listenForMessages(conn net.Conn) {
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
message, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println("Disconnected from server.")
|
||||
return
|
||||
}
|
||||
fmt.Print(message)
|
||||
// JoinRoom joins an existing room on the server.
|
||||
func (c *Client) JoinRoom(roomCode, password string) error {
|
||||
request := JoinRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
RoomCode: roomCode,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if err := c.sendPacket(JOIN_REQUEST, request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var response JoinRequestResponsePacket
|
||||
if err := c.receivePacket(JOIN_RESPONSE, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return errors.New(response.Reason)
|
||||
}
|
||||
|
||||
c.RoomCode = roomCode
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: go run client.go <server-address>")
|
||||
return
|
||||
func (c *Client) ListenForData() {
|
||||
go func() {
|
||||
for {
|
||||
var dataPacket SendDataRequestPacket
|
||||
err := c.receivePacket(READ_DATA, &dataPacket)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
fmt.Println("Connection closed by the server")
|
||||
close(c.DataChannel)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Error receiving data: %v\n", err)
|
||||
close(c.DataChannel)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the received data to the channel
|
||||
c.DataChannel <- dataPacket.Data
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// SendData sends a message to all other clients in the room.
|
||||
func (c *Client) SendData(data string) error {
|
||||
request := SendDataRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
serverAddress := os.Args[1]
|
||||
conn, err := connectToServer(serverAddress)
|
||||
if err := c.sendPacket(SEND_DATA_REQUEST, request); err != nil {
|
||||
return fmt.Errorf("failed to send data packet: %w", err)
|
||||
}
|
||||
|
||||
var response SendDataResponsePacket
|
||||
if err := c.receivePacket(SEND_DATA_RESPONSE, &response); err != nil {
|
||||
return fmt.Errorf("failed to receive response for sent data: %w", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return errors.New("server failed to process the data request: " + response.Reason)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LeaveRoom disconnects the client from the current room.
|
||||
func (c *Client) LeaveRoom() error {
|
||||
return c.sendPacket(LEAVE_ROOM, nil)
|
||||
}
|
||||
|
||||
// Close closes the connection to the server.
|
||||
func (c *Client) Close() {
|
||||
c.Conn.Close()
|
||||
}
|
||||
|
||||
// sendPacket sends a packet with a given ID and data to the server.
|
||||
func (c *Client) sendPacket(packetID PacketID, data interface{}) error {
|
||||
packetData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
return fmt.Errorf("failed to encode packet: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Start a goroutine to listen for incoming messages from the server
|
||||
go listenForMessages(conn)
|
||||
|
||||
// Read input from the terminal and send it to the server
|
||||
readInputAndSend(conn)
|
||||
return writePacket(c.Conn, byte(packetID), packetData)
|
||||
}
|
||||
|
||||
// receivePacket reads a response from the server for a given packet ID.
|
||||
func (c *Client) receivePacket(expected PacketID, v interface{}) error {
|
||||
packetID, data, err := readPacket(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packetID != expected {
|
||||
return fmt.Errorf("unexpected packet ID: got %v, want %v", packetID, expected)
|
||||
}
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// readPacket reads a packet from the connection.
|
||||
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var messageType byte
|
||||
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
data := make([]byte, length-5)
|
||||
if _, err := io.ReadFull(conn, data); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return PacketID(messageType), data, nil
|
||||
}
|
||||
|
||||
// writePacket writes a packet to the connection.
|
||||
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||
length := uint32(5 + len(data))
|
||||
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
type CreateRoomRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CreateRoomResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestPacket struct {
|
||||
UserID string
|
||||
RoomCode string
|
||||
Password string
|
||||
}
|
||||
|
||||
type JoinRequestResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
CurrentData string
|
||||
}
|
||||
|
||||
type SendDataRequestPacket struct {
|
||||
UserID string
|
||||
Data string
|
||||
}
|
||||
|
||||
type SendDataResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
11
go.mod
11
go.mod
@ -1 +1,12 @@
|
||||
module hudly
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/haleyrom/skia-go v0.0.0-20240328095045-3f321a6a6e4d
|
||||
)
|
||||
|
||||
require github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f // indirect
|
||||
|
573
server/server.go
573
server/server.go
@ -1,201 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const PORT = ":5518"
|
||||
|
||||
type PacketID int
|
||||
|
||||
const (
|
||||
CONNECT_REQUEST PacketID = iota
|
||||
CONNECT_RESPONSE
|
||||
JOIN_REQUEST
|
||||
JOIN_RESPONSE
|
||||
CREATE_REQUEST
|
||||
CREATE_RESPONSE
|
||||
READ_DATA
|
||||
SEND_DATA_REQUEST
|
||||
SEND_DATA_RESPONSE
|
||||
LEAVE_ROOM
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
username string
|
||||
room *Room
|
||||
Conn net.Conn
|
||||
ClientID string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
code string
|
||||
password string
|
||||
clients map[*Client]bool
|
||||
lock sync.Mutex
|
||||
Code string
|
||||
Password string
|
||||
Clients []*Client
|
||||
}
|
||||
|
||||
var (
|
||||
rooms = make(map[string]*Room)
|
||||
mu sync.Mutex
|
||||
)
|
||||
var rooms = map[string]*Room{}
|
||||
var clients = map[string]*Client{}
|
||||
var mu sync.Mutex
|
||||
|
||||
// Helper function to generate a 4-hexadecimal room code
|
||||
func generateRoomCode() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return fmt.Sprintf("%04x", rand.Intn(0xFFFF))
|
||||
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||
// First, read the length of the packet (4 bytes)
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// Then read the packet ID (1 byte)
|
||||
var messageType byte
|
||||
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// Read the remaining data (length - 5 bytes, since 4 bytes for length and 1 byte for messageType)
|
||||
data := make([]byte, length-5)
|
||||
if _, err := io.ReadFull(conn, data); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return PacketID(int(messageType)), data, nil
|
||||
}
|
||||
|
||||
// Handle client connection
|
||||
func handleClient(client *Client) {
|
||||
defer client.conn.Close()
|
||||
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||
// Calculate the total length of the packet
|
||||
// 4 bytes for length, 1 byte for messageType
|
||||
length := uint32(5 + len(data))
|
||||
|
||||
reader := bufio.NewReader(client.conn)
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if client.room != nil {
|
||||
leaveRoom(client)
|
||||
}
|
||||
fmt.Println("Client disconnected:", client.conn.RemoteAddr())
|
||||
return
|
||||
}
|
||||
processCommand(client, strings.TrimSpace(line))
|
||||
// Write the length and the message type
|
||||
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the data
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Process client commands
|
||||
func processCommand(client *Client, input string) {
|
||||
parts := strings.SplitN(input, " ", 2)
|
||||
if len(parts) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := strings.ToUpper(parts[0])
|
||||
args := ""
|
||||
if len(parts) > 1 {
|
||||
args = parts[1]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "CREATE":
|
||||
createRoom(client, args)
|
||||
case "JOIN":
|
||||
joinRoom(client, args)
|
||||
case "LEAVE":
|
||||
leaveRoom(client)
|
||||
case "MESSAGE":
|
||||
sendMessage(client, args)
|
||||
case "SYNC":
|
||||
handleSync(client, args)
|
||||
default:
|
||||
client.conn.Write([]byte("Unknown command\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Create a room with an optional password
|
||||
func createRoom(client *Client, args string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
password := ""
|
||||
if args != "" {
|
||||
password = args // Treat all input after CREATE as a password
|
||||
}
|
||||
|
||||
roomCode := generateRoomCode() // Room code is generated automatically
|
||||
room := &Room{
|
||||
code: roomCode,
|
||||
password: password,
|
||||
clients: make(map[*Client]bool),
|
||||
}
|
||||
|
||||
rooms[roomCode] = room
|
||||
client.conn.Write([]byte(fmt.Sprintf("Room created with code: %s\n", roomCode)))
|
||||
}
|
||||
|
||||
// Join a room using its 4-hexadecimal code and optional password
|
||||
func joinRoom(client *Client, args string) {
|
||||
parts := strings.SplitN(args, " ", 2)
|
||||
roomCode := parts[0]
|
||||
password := ""
|
||||
if len(parts) == 2 {
|
||||
password = parts[1]
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
room, exists := rooms[roomCode]
|
||||
mu.Unlock()
|
||||
|
||||
if !exists {
|
||||
client.conn.Write([]byte("Room not found\n"))
|
||||
return
|
||||
}
|
||||
|
||||
if room.password != "" && room.password != password {
|
||||
client.conn.Write([]byte("Incorrect password\n"))
|
||||
return
|
||||
}
|
||||
|
||||
room.lock.Lock()
|
||||
room.clients[client] = true
|
||||
client.room = room
|
||||
room.lock.Unlock()
|
||||
|
||||
client.conn.Write([]byte(fmt.Sprintf("Joined room: %s\n", roomCode)))
|
||||
broadcastMessage(client.room, fmt.Sprintf("%s has joined the room\n", client.username))
|
||||
}
|
||||
|
||||
// Leave the current room
|
||||
func leaveRoom(client *Client) {
|
||||
if client.room == nil {
|
||||
client.conn.Write([]byte("You are not in any room\n"))
|
||||
return
|
||||
}
|
||||
|
||||
client.room.lock.Lock()
|
||||
delete(client.room.clients, client)
|
||||
client.room.lock.Unlock()
|
||||
|
||||
broadcastMessage(client.room, fmt.Sprintf("%s has left the room\n", client.username))
|
||||
client.conn.Write([]byte("You have left the room\n"))
|
||||
client.room = nil
|
||||
}
|
||||
|
||||
// Send a message to all clients in the current room
|
||||
func sendMessage(client *Client, message string) {
|
||||
if client.room == nil {
|
||||
client.conn.Write([]byte("You are not in any room\n"))
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
formattedMessage := fmt.Sprintf("[%s] %s: %s\n", timestamp, client.username, message)
|
||||
broadcastMessage(client.room, formattedMessage)
|
||||
}
|
||||
|
||||
// Broadcast a message to all clients in the room
|
||||
func broadcastMessage(room *Room, message string) {
|
||||
room.lock.Lock()
|
||||
defer room.lock.Unlock()
|
||||
for client := range room.clients {
|
||||
client.conn.Write([]byte(message))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the SYNC command, expecting a JSON payload
|
||||
func handleSync(client *Client, payload string) {
|
||||
var data map[string]interface{}
|
||||
err := json.Unmarshal([]byte(payload), &data)
|
||||
if err != nil {
|
||||
client.conn.Write([]byte("Invalid JSON\n"))
|
||||
return
|
||||
}
|
||||
|
||||
// You can process the JSON payload here as needed
|
||||
client.conn.Write([]byte("Sync received\n"))
|
||||
}
|
||||
|
||||
// Start the server
|
||||
func startServer() {
|
||||
listener, err := net.Listen("tcp", ":5518")
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", PORT)
|
||||
if err != nil {
|
||||
fmt.Println("Error starting server:", err)
|
||||
return
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
fmt.Println("Server started on port 5518")
|
||||
fmt.Println("Server started on port", PORT)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
@ -203,21 +99,332 @@ func startServer() {
|
||||
fmt.Println("Error accepting connection:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn.Write([]byte("Enter your username: "))
|
||||
username, _ := bufio.NewReader(conn).ReadString('\n')
|
||||
username = strings.TrimSpace(username)
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
username: username,
|
||||
}
|
||||
conn.Write([]byte(fmt.Sprintf("Welcome %s!\n", username)))
|
||||
handleClient(client)
|
||||
}()
|
||||
go handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
startServer()
|
||||
type ConnectionRequestPacket struct {
|
||||
UserID string
|
||||
RoomCode string
|
||||
Password string
|
||||
}
|
||||
|
||||
type ConnectionResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
type CreateRoomRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CreateRoomResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
CurrentData string
|
||||
}
|
||||
|
||||
type SendDataRequestPacket struct {
|
||||
UserID string
|
||||
Data string
|
||||
}
|
||||
|
||||
type ForwardDataPacket struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
type SendDataResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
func handleClient(conn net.Conn) {
|
||||
// defer conn.Close()
|
||||
|
||||
for {
|
||||
|
||||
var packetId PacketID
|
||||
packetId, data, err := readPacket(conn)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading packet:", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch packetId {
|
||||
case CONNECT_REQUEST:
|
||||
var packet ConnectionRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleConnectRequest(conn, packet)
|
||||
case CREATE_REQUEST:
|
||||
var packet CreateRoomRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleCreateRoomRequest(conn, packet)
|
||||
case JOIN_REQUEST:
|
||||
var packet JoinRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleJoinRequest(conn, packet)
|
||||
case SEND_DATA_REQUEST:
|
||||
var packet SendDataRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleSendData(conn, packet)
|
||||
case LEAVE_ROOM:
|
||||
handleLeaveRoom(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnectRequest(conn net.Conn, packet ConnectionRequestPacket) {
|
||||
room := findRoom(packet.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room does not exist",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if room.Password == "" || packet.Password == room.Password {
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
}
|
||||
|
||||
func handleCreateRoomRequest(conn net.Conn, packet CreateRoomRequestPacket) {
|
||||
if !isValidPassword(packet.Password) {
|
||||
writePacket(conn, byte(CREATE_RESPONSE), encodeResponsePacket(CreateRoomResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
RoomCode: "",
|
||||
}))
|
||||
return
|
||||
}
|
||||
code := generateRandomRoomCode()
|
||||
|
||||
room := &Room{
|
||||
Code: code,
|
||||
Password: packet.Password,
|
||||
Clients: []*Client{},
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
rooms[code] = room
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(CREATE_RESPONSE), encodeResponsePacket(CreateRoomResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
RoomCode: code,
|
||||
}))
|
||||
}
|
||||
|
||||
func handleJoinRequest(conn net.Conn, packet JoinRequestPacket) {
|
||||
room := findRoom(packet.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room not found",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidPassword(packet.Password) {
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if room.Password == "" || room.Password == packet.Password {
|
||||
client := &Client{
|
||||
Conn: conn,
|
||||
ClientID: packet.UserID,
|
||||
RoomCode: packet.RoomCode,
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
room.Clients = append(room.Clients, client)
|
||||
clients[packet.UserID] = client
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
CurrentData: "{}",
|
||||
// TODO: Send current data
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
func handleSendData(conn net.Conn, packet SendDataRequestPacket) {
|
||||
mu.Lock()
|
||||
client, exists := clients[packet.UserID]
|
||||
mu.Unlock()
|
||||
|
||||
if !exists || client.RoomCode == "" {
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: false,
|
||||
Reason: "client not in a room",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
room := findRoom(client.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room not found",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
dataPacket := encodeResponsePacket(ForwardDataPacket{
|
||||
Data: packet.Data,
|
||||
})
|
||||
|
||||
mu.Lock()
|
||||
for _, roomClient := range room.Clients {
|
||||
// if roomClient.ClientID != packet.UserID {
|
||||
// // Send data to all other clients except the sender
|
||||
// writePacket(roomClient.Conn, byte(READ_DATA), dataPacket)
|
||||
// }
|
||||
writePacket(roomClient.Conn, byte(READ_DATA), dataPacket)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
}))
|
||||
}
|
||||
|
||||
func handleLeaveRoom(conn net.Conn) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
var leavingClient *Client
|
||||
for _, client := range clients {
|
||||
if client.Conn == conn {
|
||||
leavingClient = client
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if leavingClient == nil {
|
||||
fmt.Println("Client not found")
|
||||
return
|
||||
}
|
||||
|
||||
room := findRoom(leavingClient.RoomCode)
|
||||
if room == nil {
|
||||
fmt.Println("Room not found")
|
||||
return
|
||||
}
|
||||
|
||||
for i, client := range room.Clients {
|
||||
if client == leavingClient {
|
||||
// Remove the client from the list
|
||||
room.Clients = append(room.Clients[:i], room.Clients[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the room is now empty or the leaving client was the creator, delete the room
|
||||
if len(room.Clients) == 0 || room.Clients[0] == leavingClient {
|
||||
for _, client := range room.Clients {
|
||||
client.Conn.Close()
|
||||
}
|
||||
|
||||
delete(rooms, room.Code)
|
||||
fmt.Println("Room", room.Code, "deleted")
|
||||
} else {
|
||||
// // Notify remaining clients that a user has left (optional)
|
||||
// for _, remainingClient := range room.Clients {
|
||||
// writePacket(remainingClient.Conn, byte(LEAVE_ROOM), []byte(fmt.Sprintf("User %s has left the room", leavingClient.ClientID)))
|
||||
// }
|
||||
}
|
||||
|
||||
delete(clients, leavingClient.ClientID)
|
||||
|
||||
leavingClient.Conn.Close()
|
||||
fmt.Println("Client", leavingClient.ClientID, "left the room and connection closed")
|
||||
}
|
||||
|
||||
func findRoom(code string) *Room {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return rooms[code]
|
||||
}
|
||||
|
||||
func encodeResponsePacket(packet interface{}) []byte {
|
||||
data, err := json.Marshal(packet)
|
||||
if err != nil {
|
||||
fmt.Println("Error encoding response packet:", err)
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func generateRandomRoomCode() string {
|
||||
validChars := "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
code := make([]byte, 4)
|
||||
for i := range code {
|
||||
code[i] = validChars[rand.Intn(len(validChars))]
|
||||
}
|
||||
return string(code)
|
||||
}
|
||||
|
||||
func isValidPassword(password string) bool {
|
||||
validChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!()[]{}<>-_"
|
||||
charMap := make(map[rune]bool)
|
||||
for _, char := range validChars {
|
||||
charMap[char] = true
|
||||
}
|
||||
|
||||
for _, char := range password {
|
||||
if !charMap[char] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user