diff --git a/aid/aid.go b/aid/aid.go index 03ce41e..3297234 100644 --- a/aid/aid.go +++ b/aid/aid.go @@ -6,8 +6,6 @@ import ( "os/signal" "strconv" "syscall" - - "github.com/goccy/go-json" ) func WaitForExit() { @@ -16,17 +14,6 @@ func WaitForExit() { <-sc } -func JSONStringify(input interface{}) string { - json, _ := json.Marshal(input) - return string(json) -} - -func JSONParse(input string) interface{} { - var output interface{} - json.Unmarshal([]byte(input), &output) - return output -} - func RandomString(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") diff --git a/aid/type.go b/aid/json.go similarity index 53% rename from aid/type.go rename to aid/json.go index 60f24b3..89c9138 100644 --- a/aid/type.go +++ b/aid/json.go @@ -13,4 +13,15 @@ func JSONFromBytes(input []byte) JSON { func (j *JSON) ToBytes() []byte { json, _ := json.Marshal(j) return json +} + +func JSONStringify(input interface{}) string { + json, _ := json.Marshal(input) + return string(json) +} + +func JSONParse(input string) interface{} { + var output interface{} + json.Unmarshal([]byte(input), &output) + return output } \ No newline at end of file diff --git a/handlers/socket.go b/handlers/socket.go index 384d21c..1f80ff9 100644 --- a/handlers/socket.go +++ b/handlers/socket.go @@ -10,17 +10,16 @@ import ( ) type SocketType string -var ( - SocketTypeXmpp SocketType = "xmpp" - SocketTypeUnknown SocketType = "unknown" -) +const SocketTypeXmpp SocketType = "xmpp" +const SocketTypeUnknown SocketType = "unknown" type Socket struct { ID string - JID string - Type SocketType Connection *websocket.Conn Person *person.Person + + Type SocketType + PresenceState *PresenceState } type MessageToWrite struct { @@ -50,7 +49,7 @@ func (s *Socket) WriteTree(message *etree.Document) { var ( socketWriteQueue = make(chan MessageToWrite, 1000) socketHandlers = map[SocketType]func(string) { - SocketTypeXmpp: handlePresenceSocket, + SocketTypeXmpp: presenceSocketHandle, } sockets = aid.GenericSyncMap[Socket]{} @@ -98,6 +97,24 @@ func WebsocketConnection(c *websocket.Conn) { } } +func GetSocketByPerson(person *person.Person) *Socket { + var recieverSocket *Socket + sockets.Range(func(key string, value *Socket) bool { + if value.Person == nil { + return true + } + + if value.Person.ID == person.ID { + recieverSocket = value + return false + } + + return true + }) + + return recieverSocket +} + func init() { go func() { for { @@ -110,7 +127,7 @@ func init() { for { message := <-socketWriteQueue - aid.Print("(socket) message sent", message.Socket.ID, string(message.Message)) + aid.Print("(socket) writing message to", message.Socket.ID, string(message.Message)) message.Socket.Connection.WriteMessage(websocket.TextMessage, message.Message) } }() diff --git a/handlers/xmpp.go b/handlers/xmpp.go index 6bf51dd..0209c94 100644 --- a/handlers/xmpp.go +++ b/handlers/xmpp.go @@ -8,23 +8,58 @@ import ( "github.com/beevik/etree" "github.com/ectrc/snow/aid" p "github.com/ectrc/snow/person" + "github.com/ectrc/snow/storage" + "github.com/goccy/go-json" + "github.com/google/uuid" ) +type PresenceStatus struct { + Status string `json:"Status"` + IsPlaying bool `json:"bIsPlaying"` + IsJoinable bool `json:"bIsJoinable"` + HasVoiceSupport bool `json:"bHasVoiceSupport"` + SessionId string `json:"SessionId"` + Properties map[string]struct { + SourceId string `json:"sourceId"` + SourceDisplayName string `json:"sourceDisplayName"` + SourcePlatform string `json:"sourcePlatform"` + PartyId string `json:"partyId"` + PartyTypeId int `json:"partyTypeId"` + Key string `json:"key"` + AppId string `json:"appId"` + BuildId string `json:"buildId"` + PartyFlags int `json:"partyFlags"` + NotAcceptingReason int `json:"notAcceptingReason"` + Pc int `json:"pc"` + } `json:"Properties"` +} + +type PresenceState struct { + JID string + Open bool + RawStatus string + ParsedStatus PresenceStatus +} + var ( socketXmppMessageHandlers = map[string]func(*Socket, *etree.Document) error { "open": presenceSocketOpenEvent, "iq": presenceSocketIqRootEvent, + "message": presenceSocketMessageEvent, + "presence": presenceSocketPresenceEvent, "close": presenceSocketCloseEvent, } ) -func handlePresenceSocket(id string) { - aid.Print("(xmpp) connection opened", id) +func presenceSocketHandle(id string) { socket, ok := sockets.Get(id) if !ok { return } + socket.Type = SocketTypeXmpp + socket.PresenceState = &PresenceState{} + for { _, message, err := socket.Connection.ReadMessage() if err != nil { @@ -36,15 +71,66 @@ func handlePresenceSocket(id string) { return } - aid.Print("(xmpp) message received", string(message)) - if handler, ok := socketXmppMessageHandlers[parsed.Root().Tag]; ok { if err := handler(socket, parsed); err != nil { - aid.Print("(xmpp) connection closed", id, err.Error()) return } } } + + for _, partial := range storage.Repo.GetFriendsForPerson(socket.Person.ID) { + friend := socket.Person.GetFriend(partial.ID) + if friend == nil { + continue + } + + friendSocket := GetSocketByPerson(friend.Person) + if friendSocket == nil { + continue + } + + friendDocument := etree.NewDocument() + friendPresence := friendDocument.CreateElement("presence") + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "from", Value: socket.PresenceState.JID}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "to", Value: friendSocket.PresenceState.JID}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "type", Value: "available"}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "xmlns", Value: "jabber:client"}) + friendPresence.CreateElement("status").SetText(aid.JSONStringify(aid.JSON{})) + friendPresence.CreateElement("show").SetText("away") + + friendSocket.WriteTree(friendDocument) + } + + for _, party := range socket.PresenceState.ParsedStatus.Properties { + if party.PartyId == "" { + continue + } + + sockets.Range(func(_ string, recieverSocket *Socket) bool { + if recieverSocket.Type != SocketTypeXmpp || recieverSocket.Person == nil { + return true + } + + document := etree.NewDocument() + message := document.CreateElement("message") + message.Attr = append(message.Attr, etree.Attr{Key: "id", Value: uuid.New().String()}) + message.Attr = append(message.Attr, etree.Attr{Key: "from", Value: socket.PresenceState.JID}) + message.Attr = append(message.Attr, etree.Attr{Key: "to", Value: recieverSocket.PresenceState.JID}) + message.Attr = append(message.Attr, etree.Attr{Key: "xmlns", Value: "jabber:client"}) + message.CreateElement("body").SetText(aid.JSONStringify(aid.JSON{ + "type": "com.epicgames.party.memberexited", + "timestamp": time.Now().Format(time.RFC3339), + "payload": aid.JSON{ + "memberId": socket.Person.ID, + "partyId": party.PartyId, + "wasKicked": false, + }, + })) + + recieverSocket.WriteTree(document) + return true + }) + } } func presenceSocketOpenEvent(socket *Socket, tree *etree.Document) error { @@ -105,7 +191,7 @@ func presenceSocketIqSetEvent(socket *Socket, tree *etree.Document) error { } socket.Person = person - socket.JID = person.ID + "@prod.ol.epicgames.com/" + tree.Root().SelectElement("query").SelectElement("resource").Text() + socket.PresenceState.JID = person.ID + "@prod.ol.epicgames.com/" + tree.Root().SelectElement("query").SelectElement("resource").Text() document := etree.NewDocument() iq := document.CreateElement("iq") @@ -114,6 +200,7 @@ func presenceSocketIqSetEvent(socket *Socket, tree *etree.Document) error { iq.Attr = append(iq.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"}) socket.WriteTree(document) + SendPresenceSocketStatusToFriends(socket) return nil } @@ -131,6 +218,74 @@ func presenceSocketIqGetEvent(socket *Socket, tree *etree.Document) error { return nil } +func presenceSocketMessageEvent(socket *Socket, tree *etree.Document) error { + reciever := p.Find(strings.Split(tree.Root().SelectAttr("to").Value, "@")[0]) + if reciever == nil { + return nil + } + + recieverSocket := GetSocketByPerson(reciever) + if recieverSocket == nil { + return nil + } + + document := etree.NewDocument() + message := document.CreateElement("message") + message.Attr = append(message.Attr, etree.Attr{Key: "id", Value: tree.Root().SelectAttr("id").Value}) + message.Attr = append(message.Attr, etree.Attr{Key: "from", Value: socket.PresenceState.JID}) + message.Attr = append(message.Attr, etree.Attr{Key: "to", Value: recieverSocket.PresenceState.JID}) + message.Attr = append(message.Attr, etree.Attr{Key: "xmlns", Value: "jabber:client"}) + message.CreateElement("body").SetText(tree.Root().SelectElement("body").Text()) + + recieverSocket.WriteTree(document) + return nil +} + +func presenceSocketPresenceEvent(socket *Socket, tree *etree.Document) error { + status := tree.Root().SelectElement("status") + if status == nil { + return nil + } + + socket.PresenceState.RawStatus = status.Text() + json.NewDecoder(strings.NewReader(status.Text())).Decode(&socket.PresenceState.ParsedStatus) + + SendPresenceSocketStatusToFriends(socket) + return nil +} + +func SendPresenceSocketStatusToFriends(socket *Socket) { + for _, partial := range storage.Repo.GetFriendsForPerson(socket.Person.ID) { + friend := socket.Person.GetFriend(partial.ID) + if friend == nil { + continue + } + + friendSocket := GetSocketByPerson(friend.Person) + if friendSocket == nil { + continue + } + + friendDocument := etree.NewDocument() + friendPresence := friendDocument.CreateElement("presence") + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "from", Value: socket.PresenceState.JID}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "to", Value: friendSocket.PresenceState.JID}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "type", Value: "available"}) + friendPresence.Attr = append(friendPresence.Attr, etree.Attr{Key: "xmlns", Value: "jabber:client"}) + friendPresence.CreateElement("status").SetText(socket.PresenceState.RawStatus) + friendSocket.WriteTree(friendDocument) + + document := etree.NewDocument() + presence := document.CreateElement("presence") + presence.Attr = append(presence.Attr, etree.Attr{Key: "from", Value: friendSocket.PresenceState.JID}) + presence.Attr = append(presence.Attr, etree.Attr{Key: "to", Value: socket.PresenceState.JID}) + presence.Attr = append(presence.Attr, etree.Attr{Key: "type", Value: "available"}) + presence.Attr = append(presence.Attr, etree.Attr{Key: "xmlns", Value: "jabber:client"}) + presence.CreateElement("status").SetText(friendSocket.PresenceState.RawStatus) + socket.WriteTree(document) + } +} + func init() { go func() { for { diff --git a/readme.md b/readme.md index a18295e..d3f8783 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,6 @@ Performance first, universal Fortnite private server backend written in Go. - **Blazingly Fast** Written in Go and built upon Fast HTTP, it is extremely fast and can handle any profile action in milliseconds with its caching system. - **Profile Changes** Automatically keeps track of profile changes exactly so any external changes are displayed in-game on the next action. - **Universal Database** It is possible to add new database types to satisfy your needs. Currently, it only supports `postgresql`. -- **Production Ready** Optimised and tested to be ran in production environments. It is also possible to run multiple instances of the backend to scale it horizontally. ## What's next?