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
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
netclient "hudly/client"
|
||||||
"hudly/hypixel"
|
"hudly/hypixel"
|
||||||
"hudly/mcfetch"
|
"hudly/mcfetch"
|
||||||
"log"
|
"log"
|
||||||
@ -9,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var key = "9634ea92-80f0-482f-aebd-b082c6ed6f19"
|
var key = "ccebff0f-939a-4afe-b5b3-30a7a665ee38"
|
||||||
var uuid = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
var UUID = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||||
|
|
||||||
func demo() {
|
func demo() {
|
||||||
// Ensure a username is provided as a command-line argument
|
// Ensure a username is provided as a command-line argument
|
||||||
@ -73,3 +76,157 @@ func demo() {
|
|||||||
}
|
}
|
||||||
fmt.Println(fmt.Sprintf("%+v", res))
|
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)
|
||||||
|
}
|
||||||
|
266
client/client.go
266
client/client.go
@ -1,75 +1,239 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect to the chat server
|
const PORT = ":5518"
|
||||||
func connectToServer(address string) (net.Conn, error) {
|
|
||||||
conn, err := net.Dial("tcp", address)
|
type PacketID int
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not connect to server: %v", err)
|
const (
|
||||||
}
|
CONNECT_REQUEST PacketID = iota
|
||||||
return conn, nil
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read input from the terminal and send it to the server
|
// NewClient creates a new client and connects to the server.
|
||||||
func readInputAndSend(conn net.Conn) {
|
func NewClient(serverAddr, clientID string) (*Client, error) {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
conn, err := net.Dial("tcp", serverAddr+PORT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to server: %w", err)
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
Conn: conn,
|
||||||
|
ClientID: clientID,
|
||||||
|
DataChannel: make(chan string),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (c *Client) ListenForData() {
|
||||||
|
go func() {
|
||||||
for {
|
for {
|
||||||
fmt.Print("> ")
|
var dataPacket SendDataRequestPacket
|
||||||
text, _ := reader.ReadString('\n')
|
err := c.receivePacket(READ_DATA, &dataPacket)
|
||||||
text = strings.TrimSpace(text)
|
|
||||||
|
|
||||||
// Send the command to the server
|
|
||||||
_, err := conn.Write([]byte(text + "\n"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error sending message:", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user types 'quit', exit the program
|
// Send the received data to the channel
|
||||||
if text == "quit" {
|
c.DataChannel <- dataPacket.Data
|
||||||
fmt.Println("Goodbye!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for incoming messages from the server
|
// SendData sends a message to all other clients in the room.
|
||||||
func listenForMessages(conn net.Conn) {
|
func (c *Client) SendData(data string) error {
|
||||||
reader := bufio.NewReader(conn)
|
request := SendDataRequestPacket{
|
||||||
for {
|
UserID: c.ClientID,
|
||||||
message, err := reader.ReadString('\n')
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
fmt.Println("Disconnected from server.")
|
return fmt.Errorf("failed to encode packet: %w", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Print(message)
|
|
||||||
}
|
}
|
||||||
|
return writePacket(c.Conn, byte(packetID), packetData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
// receivePacket reads a response from the server for a given packet ID.
|
||||||
if len(os.Args) < 2 {
|
func (c *Client) receivePacket(expected PacketID, v interface{}) error {
|
||||||
fmt.Println("Usage: go run client.go <server-address>")
|
packetID, data, err := readPacket(c.Conn)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverAddress := os.Args[1]
|
|
||||||
conn, err := connectToServer(serverAddress)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
if packetID != expected {
|
||||||
|
return fmt.Errorf("unexpected packet ID: got %v, want %v", packetID, expected)
|
||||||
// Start a goroutine to listen for incoming messages from the server
|
}
|
||||||
go listenForMessages(conn)
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
// Read input from the terminal and send it to the server
|
|
||||||
readInputAndSend(conn)
|
// 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
|
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
|
||||||
|
553
server/server.go
553
server/server.go
@ -1,201 +1,97 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"sync"
|
"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 {
|
type Client struct {
|
||||||
conn net.Conn
|
Conn net.Conn
|
||||||
username string
|
ClientID string
|
||||||
room *Room
|
RoomCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
code string
|
Code string
|
||||||
password string
|
Password string
|
||||||
clients map[*Client]bool
|
Clients []*Client
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var rooms = map[string]*Room{}
|
||||||
rooms = make(map[string]*Room)
|
var clients = map[string]*Client{}
|
||||||
mu sync.Mutex
|
var mu sync.Mutex
|
||||||
)
|
|
||||||
|
|
||||||
// Helper function to generate a 4-hexadecimal room code
|
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||||
func generateRoomCode() string {
|
// First, read the length of the packet (4 bytes)
|
||||||
rand.Seed(time.Now().UnixNano())
|
var length uint32
|
||||||
return fmt.Sprintf("%04x", rand.Intn(0xFFFF))
|
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle client connection
|
// Then read the packet ID (1 byte)
|
||||||
func handleClient(client *Client) {
|
var messageType byte
|
||||||
defer client.conn.Close()
|
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process client commands
|
// Read the remaining data (length - 5 bytes, since 4 bytes for length and 1 byte for messageType)
|
||||||
func processCommand(client *Client, input string) {
|
data := make([]byte, length-5)
|
||||||
parts := strings.SplitN(input, " ", 2)
|
if _, err := io.ReadFull(conn, data); err != nil {
|
||||||
if len(parts) < 1 {
|
return 0, nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := strings.ToUpper(parts[0])
|
return PacketID(int(messageType)), data, nil
|
||||||
args := ""
|
|
||||||
if len(parts) > 1 {
|
|
||||||
args = parts[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cmd {
|
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||||
case "CREATE":
|
// Calculate the total length of the packet
|
||||||
createRoom(client, args)
|
// 4 bytes for length, 1 byte for messageType
|
||||||
case "JOIN":
|
length := uint32(5 + len(data))
|
||||||
joinRoom(client, args)
|
|
||||||
case "LEAVE":
|
// Write the length and the message type
|
||||||
leaveRoom(client)
|
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||||
case "MESSAGE":
|
return err
|
||||||
sendMessage(client, args)
|
|
||||||
case "SYNC":
|
|
||||||
handleSync(client, args)
|
|
||||||
default:
|
|
||||||
client.conn.Write([]byte("Unknown command\n"))
|
|
||||||
}
|
}
|
||||||
|
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a room with an optional password
|
// Write the data
|
||||||
func createRoom(client *Client, args string) {
|
_, err := conn.Write(data)
|
||||||
mu.Lock()
|
return err
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
password := ""
|
|
||||||
if args != "" {
|
|
||||||
password = args // Treat all input after CREATE as a password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
roomCode := generateRoomCode() // Room code is generated automatically
|
func main() {
|
||||||
room := &Room{
|
listener, err := net.Listen("tcp", PORT)
|
||||||
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")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error starting server:", err)
|
fmt.Println("Error starting server:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
fmt.Println("Server started on port 5518")
|
fmt.Println("Server started on port", PORT)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
@ -203,21 +99,332 @@ func startServer() {
|
|||||||
fmt.Println("Error accepting connection:", err)
|
fmt.Println("Error accepting connection:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
go handleClient(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
type ConnectionRequestPacket struct {
|
||||||
conn.Write([]byte("Enter your username: "))
|
UserID string
|
||||||
username, _ := bufio.NewReader(conn).ReadString('\n')
|
RoomCode string
|
||||||
username = strings.TrimSpace(username)
|
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{
|
client := &Client{
|
||||||
conn: conn,
|
Conn: conn,
|
||||||
username: username,
|
ClientID: packet.UserID,
|
||||||
|
RoomCode: packet.RoomCode,
|
||||||
}
|
}
|
||||||
conn.Write([]byte(fmt.Sprintf("Welcome %s!\n", username)))
|
|
||||||
handleClient(client)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
if leavingClient == nil {
|
||||||
startServer()
|
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