Add more xmpp functions
This commit is contained in:
parent
444d6a4838
commit
ba4f3301be
13
aid/aid.go
13
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")
|
||||
|
||||
|
|
|
@ -14,3 +14,14 @@ 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
|
167
handlers/xmpp.go
167
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 {
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user