home to your local SPACEGIRL 馃挮 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 201 lines 5.5 kB view raw
1package cursor 2 3import ( 4 "arimelody-web/model" 5 "fmt" 6 "math/rand" 7 "net/http" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/gorilla/websocket" 14) 15 16type CursorClient struct { 17 ID int32 18 Conn *websocket.Conn 19 Route string 20 X float32 21 Y float32 22 Click bool 23 Disconnected bool 24} 25 26type CursorMessage struct { 27 Data []byte 28 Route string 29 Exclude []*CursorClient 30} 31 32func (client *CursorClient) Send(data []byte) { 33 err := client.Conn.WriteMessage(websocket.TextMessage, data) 34 if err != nil { 35 client.Disconnect() 36 } 37} 38 39func (client *CursorClient) Disconnect() { 40 client.Disconnected = true 41 broadcast <- CursorMessage{ 42 []byte(fmt.Sprintf("leave:%d", client.ID)), 43 client.Route, 44 []*CursorClient{}, 45 } 46} 47 48var clients = make(map[int32]*CursorClient) 49var broadcast = make(chan CursorMessage) 50var mutex = &sync.Mutex{} 51 52func StartCursor(app *model.AppState) { 53 var includes = func (clients []*CursorClient, client *CursorClient) bool { 54 for _, c := range clients { 55 if c.ID == client.ID { return true } 56 } 57 return false 58 } 59 60 log("Cursor message handler ready!") 61 62 for { 63 message := <-broadcast 64 mutex.Lock() 65 for _, client := range clients { 66 if client.Route != message.Route { continue } 67 if includes(message.Exclude, client) { continue } 68 client.Send(message.Data) 69 } 70 mutex.Unlock() 71 } 72} 73 74func handleClient(client *CursorClient) { 75 msgType, message, err := client.Conn.ReadMessage() 76 if err != nil { 77 client.Disconnect() 78 return 79 } 80 if msgType != websocket.TextMessage { return } 81 82 args := strings.Split(string(message), ":") 83 if len(args) == 0 { return } 84 switch args[0] { 85 case "loc": 86 if len(args) < 2 { return } 87 88 client.Route = args[1] 89 90 mutex.Lock() 91 for otherClientID, otherClient := range clients { 92 if otherClientID == client.ID || otherClient.Route != client.Route { continue } 93 client.Send([]byte(fmt.Sprintf("join:%d", otherClientID))) 94 client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClientID, otherClient.X, otherClient.Y))) 95 } 96 mutex.Unlock() 97 98 broadcast <- CursorMessage{ 99 []byte(fmt.Sprintf("join:%d", client.ID)), 100 client.Route, 101 []*CursorClient{ client }, 102 } 103 case "char": 104 if len(args) < 2 { return } 105 // haha, turns out using ':' as a separator means you can't type ':'s 106 // i should really be writing byte packets, not this nonsense 107 msg := byte(':') 108 if len(args[1]) > 0 { 109 msg = args[1][0] 110 } 111 broadcast <- CursorMessage{ 112 []byte(fmt.Sprintf("char:%d:%c", client.ID, msg)), 113 client.Route, 114 []*CursorClient{ client }, 115 } 116 case "nochar": 117 broadcast <- CursorMessage{ 118 []byte(fmt.Sprintf("nochar:%d", client.ID)), 119 client.Route, 120 []*CursorClient{ client }, 121 } 122 case "click": 123 if len(args) < 2 { return } 124 click := 0 125 if args[1][0] == '1' { 126 click = 1 127 } 128 broadcast <- CursorMessage{ 129 []byte(fmt.Sprintf("click:%d:%d", client.ID, click)), 130 client.Route, 131 []*CursorClient{ client }, 132 } 133 case "pos": 134 if len(args) < 3 { return } 135 x, err := strconv.ParseFloat(args[1], 32) 136 y, err := strconv.ParseFloat(args[2], 32) 137 if err != nil { return } 138 client.X = float32(x) 139 client.Y = float32(y) 140 broadcast <- CursorMessage{ 141 []byte(fmt.Sprintf("pos:%d:%f:%f", client.ID, client.X, client.Y)), 142 client.Route, 143 []*CursorClient{ client }, 144 } 145 } 146} 147 148func Handler(app *model.AppState) http.HandlerFunc { 149 var upgrader = websocket.Upgrader{ 150 CheckOrigin: func (r *http.Request) bool { 151 origin := r.Header.Get("Origin") 152 return origin == app.Config.BaseUrl 153 }, 154 } 155 156 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 157 conn, err := upgrader.Upgrade(w, r, nil) 158 if err != nil { 159 log("Failed to upgrade to WebSocket connection: %v\n", err) 160 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 161 return 162 } 163 defer conn.Close() 164 165 client := CursorClient{ 166 ID: rand.Int31(), 167 Conn: conn, 168 X: 0.0, 169 Y: 0.0, 170 Disconnected: false, 171 } 172 173 err = client.Conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("id:%d", client.ID))) 174 if err != nil { 175 client.Conn.Close() 176 return 177 } 178 179 mutex.Lock() 180 clients[client.ID] = &client 181 mutex.Unlock() 182 183 // log("Client connected: %s (%s)", fmt.Sprintf("0x%08x", client.ID), client.Conn.RemoteAddr().String()) 184 185 for { 186 if client.Disconnected { 187 mutex.Lock() 188 delete(clients, client.ID) 189 client.Conn.Close() 190 mutex.Unlock() 191 return 192 } 193 handleClient(&client) 194 } 195 }) 196} 197 198func log(format string, args ...any) { 199 logString := fmt.Sprintf(format, args...) 200 fmt.Printf("[%s] [CURSOR] %s\n", time.Now().Format(time.UnixDate), logString) 201}