Add more xmpp functions

This commit is contained in:
Eccentric 2024-01-21 17:49:14 +00:00
parent 444d6a4838
commit ba4f3301be
5 changed files with 197 additions and 28 deletions

View File

@ -6,8 +6,6 @@ import (
"os/signal" "os/signal"
"strconv" "strconv"
"syscall" "syscall"
"github.com/goccy/go-json"
) )
func WaitForExit() { func WaitForExit() {
@ -16,17 +14,6 @@ func WaitForExit() {
<-sc <-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 { func RandomString(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")

View File

@ -13,4 +13,15 @@ func JSONFromBytes(input []byte) JSON {
func (j *JSON) ToBytes() []byte { func (j *JSON) ToBytes() []byte {
json, _ := json.Marshal(j) json, _ := json.Marshal(j)
return json 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
} }

View File

@ -10,17 +10,16 @@ import (
) )
type SocketType string type SocketType string
var ( const SocketTypeXmpp SocketType = "xmpp"
SocketTypeXmpp SocketType = "xmpp" const SocketTypeUnknown SocketType = "unknown"
SocketTypeUnknown SocketType = "unknown"
)
type Socket struct { type Socket struct {
ID string ID string
JID string
Type SocketType
Connection *websocket.Conn Connection *websocket.Conn
Person *person.Person Person *person.Person
Type SocketType
PresenceState *PresenceState
} }
type MessageToWrite struct { type MessageToWrite struct {
@ -50,7 +49,7 @@ func (s *Socket) WriteTree(message *etree.Document) {
var ( var (
socketWriteQueue = make(chan MessageToWrite, 1000) socketWriteQueue = make(chan MessageToWrite, 1000)
socketHandlers = map[SocketType]func(string) { socketHandlers = map[SocketType]func(string) {
SocketTypeXmpp: handlePresenceSocket, SocketTypeXmpp: presenceSocketHandle,
} }
sockets = aid.GenericSyncMap[Socket]{} 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() { func init() {
go func() { go func() {
for { for {
@ -110,7 +127,7 @@ func init() {
for { for {
message := <-socketWriteQueue 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) message.Socket.Connection.WriteMessage(websocket.TextMessage, message.Message)
} }
}() }()

View File

@ -8,23 +8,58 @@ import (
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
p "github.com/ectrc/snow/person" 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 ( var (
socketXmppMessageHandlers = map[string]func(*Socket, *etree.Document) error { socketXmppMessageHandlers = map[string]func(*Socket, *etree.Document) error {
"open": presenceSocketOpenEvent, "open": presenceSocketOpenEvent,
"iq": presenceSocketIqRootEvent, "iq": presenceSocketIqRootEvent,
"message": presenceSocketMessageEvent,
"presence": presenceSocketPresenceEvent,
"close": presenceSocketCloseEvent, "close": presenceSocketCloseEvent,
} }
) )
func handlePresenceSocket(id string) { func presenceSocketHandle(id string) {
aid.Print("(xmpp) connection opened", id)
socket, ok := sockets.Get(id) socket, ok := sockets.Get(id)
if !ok { if !ok {
return return
} }
socket.Type = SocketTypeXmpp
socket.PresenceState = &PresenceState{}
for { for {
_, message, err := socket.Connection.ReadMessage() _, message, err := socket.Connection.ReadMessage()
if err != nil { if err != nil {
@ -36,15 +71,66 @@ func handlePresenceSocket(id string) {
return return
} }
aid.Print("(xmpp) message received", string(message))
if handler, ok := socketXmppMessageHandlers[parsed.Root().Tag]; ok { if handler, ok := socketXmppMessageHandlers[parsed.Root().Tag]; ok {
if err := handler(socket, parsed); err != nil { if err := handler(socket, parsed); err != nil {
aid.Print("(xmpp) connection closed", id, err.Error())
return 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 { func presenceSocketOpenEvent(socket *Socket, tree *etree.Document) error {
@ -105,7 +191,7 @@ func presenceSocketIqSetEvent(socket *Socket, tree *etree.Document) error {
} }
socket.Person = person 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() document := etree.NewDocument()
iq := document.CreateElement("iq") 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"}) iq.Attr = append(iq.Attr, etree.Attr{Key: "from", Value: "prod.ol.epicgames.com"})
socket.WriteTree(document) socket.WriteTree(document)
SendPresenceSocketStatusToFriends(socket)
return nil return nil
} }
@ -131,6 +218,74 @@ func presenceSocketIqGetEvent(socket *Socket, tree *etree.Document) error {
return nil 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() { func init() {
go func() { go func() {
for { for {

View File

@ -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. - **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. - **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`. - **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? ## What's next?