diff --git a/aid/aid.go b/aid/aid.go
index 549e968..74f3239 100644
--- a/aid/aid.go
+++ b/aid/aid.go
@@ -67,4 +67,11 @@ func Regex(str, regex string) *string {
}
return nil
+}
+
+func Ternary[T any](condition bool, a, b T) T {
+ if condition {
+ return a
+ }
+ return b
}
\ No newline at end of file
diff --git a/aid/fiber.go b/aid/fiber.go
index 7d87194..bea9c2e 100644
--- a/aid/fiber.go
+++ b/aid/fiber.go
@@ -18,9 +18,9 @@ func FiberLogger() fiber.Handler {
})
}
-func FiberLimiter() fiber.Handler {
+func FiberLimiter(n int) fiber.Handler {
return limiter.New(limiter.Config{
- Max: 100,
+ Max: n,
Expiration: 1 * time.Minute,
})
}
diff --git a/aid/token.go b/aid/token.go
index a69d3a7..ba05444 100644
--- a/aid/token.go
+++ b/aid/token.go
@@ -17,6 +17,7 @@ func JWTSign(m JSON) (string, error) {
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ token.Header["kid"] = "g__VKjSSmqJ0xZj1RYkLGKQ7dnHiM9MLhFVwKPySDB4"
return token.SignedString([]byte(Config.JWT.Secret))
}
diff --git a/handlers/parties.go b/handlers/parties.go
index a712af4..7779929 100644
--- a/handlers/parties.go
+++ b/handlers/parties.go
@@ -1,15 +1,13 @@
package handlers
import (
- "time"
-
"github.com/ectrc/snow/aid"
p "github.com/ectrc/snow/person"
"github.com/ectrc/snow/socket"
"github.com/gofiber/fiber/v2"
)
-func GetUserParties(c *fiber.Ctx) error {
+func GetPartiesForUser(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
response := aid.JSON{
@@ -24,10 +22,15 @@ func GetUserParties(c *fiber.Ctx) error {
return true
})
+ person.Invites.Range(func(key string, invite *p.PartyInvite) bool {
+ response["invites"] = append(response["invites"].([]aid.JSON), invite.GenerateFortnitePartyInvite())
+ return true
+ })
+
return c.Status(200).JSON(response)
}
-func GetUserPartyPrivacy(c *fiber.Ctx) error {
+func GetPartyUserPrivacy(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
recieveIntents := person.CommonCoreProfile.Attributes.GetAttributeByKey("party.recieveIntents")
@@ -46,25 +49,47 @@ func GetUserPartyPrivacy(c *fiber.Ctx) error {
})
}
-func GetUserPartyNotifications(c *fiber.Ctx) error {
+func GetPartyNotifications(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
return c.Status(200).JSON(aid.JSON{
"pings": 0,
- "invites": 0,
+ "invites": person.Invites.Len(),
})
}
func GetPartyForMember(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
- party, ok := person.Parties.Get(c.Params("partyId"))
+ party, ok := p.Parties.Get(c.Params("partyId"))
if !ok {
return c.Status(400).JSON(aid.ErrorBadRequest("Party Not Found"))
}
+ aid.Print(person.DisplayName, " is getting party ", party.ID)
+
return c.Status(200).JSON(party.GenerateFortniteParty())
}
-func PostCreateParty(c *fiber.Ctx) error {
+func GetPartyPingsFromFriend(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
+
+ friend := p.Find(c.Params("friendId"))
+ if friend == nil {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Friend Not Found"))
+ }
+
+ pings := []aid.JSON{}
+ person.Invites.Range(func(key string, ping *p.PartyInvite) bool {
+ if ping.Inviter.ID == friend.ID {
+ pings = append(pings, ping.Party.GenerateFortniteParty())
+ }
+ return true
+ })
+
+ return c.Status(200).JSON(pings)
+}
+
+func PostPartyCreate(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
person.Parties.Range(func(key string, party *p.Party) bool {
@@ -89,41 +114,16 @@ func PostCreateParty(c *fiber.Ctx) error {
party.UpdateMeta(body.Meta)
party.UpdateConfig(body.Config)
- party.AddMember(person, "CAPTAIN")
+ party.AddMember(person)
party.UpdateMemberMeta(person, body.JoinInformation.Meta)
-
- body.JoinInformation.Connection["connected_at"] = time.Now().Format(time.RFC3339)
- body.JoinInformation.Connection["updated_at"] = time.Now().Format(time.RFC3339)
- party.UpdateMemberConnections(person, body.JoinInformation.Connection)
-
- member := party.GetMember(person)
- if member == nil {
- return c.Status(400).JSON(aid.ErrorBadRequest("Failed to create party"))
- }
-
- s, ok := socket.JabberSockets.Get(person.ID)
- if !ok {
- return c.Status(400).JSON(aid.ErrorBadRequest("No socket connection found"))
- }
-
- s.JabberSendMessageToPerson(aid.JSON{
- "ns": "Fortnite",
- "party_id": party.ID,
- "account_id": person.ID,
- "account_dn": person.DisplayName,
- "connection": body.JoinInformation.Connection,
- "member_state_updated": member.Meta,
- "updated_at": member.UpdatedAt.Format(time.RFC3339),
- "joined_at": member.JoinedAt.Format(time.RFC3339),
- "sent": time.Now().Format(time.RFC3339),
- "revision": 0,
- "type": "com.epicgames.social.party.notification.v0.MEMBER_JOINED",
- })
+ party.UpdateMemberConnection(person, body.JoinInformation.Connection)
+ party.ChangeNewCaptain()
+ socket.EmitPartyMemberJoined(party, party.GetMember(person))
return c.Status(200).JSON(party.GenerateFortniteParty())
}
-func PatchUpdateParty(c *fiber.Ctx) error {
+func PatchPartyUpdateState(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
var body struct {
@@ -134,9 +134,11 @@ func PatchUpdateParty(c *fiber.Ctx) error {
} `json:"meta"`
}
+
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
}
+ aid.PrintJSON(body)
party, ok := person.Parties.Get(c.Params("partyId"))
if !ok {
@@ -155,11 +157,12 @@ func PatchUpdateParty(c *fiber.Ctx) error {
party.UpdateConfig(body.Config)
party.UpdateMeta(body.Meta.Update)
party.DeleteMeta(body.Meta.Delete)
+ socket.EmitPartyMetaUpdated(party, body.Meta.Update, body.Meta.Delete, body.Meta.Update)
- return c.Status(200).JSON(party.GenerateFortniteParty())
+ return c.SendStatus(204)
}
-func PatchUpdatePartyMemberMeta(c *fiber.Ctx) error {
+func PatchPartyUpdateMemberState(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
var body struct {
@@ -187,6 +190,108 @@ func PatchUpdatePartyMemberMeta(c *fiber.Ctx) error {
party.UpdateMemberMeta(person, body.Update)
party.DeleteMemberMeta(person, body.Delete)
+ socket.EmitPartyMemberMetaUpdated(party, member, body.Update, body.Delete)
- return c.Status(200).JSON(party.GenerateFortniteParty())
+ return c.SendStatus(204)
+}
+
+func DeletePartyMember(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
+
+ party, ok := person.Parties.Get(c.Params("partyId"))
+ if !ok {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Party Not Found"))
+ }
+
+ member := party.GetMember(person)
+ if member == nil {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Not in Party"))
+ }
+
+ socket.EmitPartyMemberLeft(party, member)
+ party.RemoveMember(person)
+
+ if party.Captain.Person.ID == person.ID && len(party.Members) > 0 {
+ party.ChangeNewCaptain()
+ go socket.EmitPartyNewCaptain(party)
+ }
+
+ return c.SendStatus(204)
+}
+
+func PostPartyInvite(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
+
+ var body map[string]interface{}
+ if err := c.BodyParser(&body); err != nil {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
+ }
+
+ towards := p.Find(c.Params("accountId"))
+ if towards == nil {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Person Not Found"))
+ }
+
+ party, ok := person.Parties.Get(c.Params("partyId"))
+ if !ok {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Party Not Found"))
+ }
+
+ invite := p.NewPartyInvite(party, person, towards, body)
+ party.AddInvite(invite)
+ towards.Invites.Set(party.ID, invite)
+
+ socket.EmitPartyInvite(invite)
+
+ return c.SendStatus(204)
+}
+
+func PostPartyJoin(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
+ if person.Parties.Len() != 0 {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Already in a party"))
+ }
+
+ party, ok := p.Parties.Get(c.Params("partyId"))
+ if !ok {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Party Not Found"))
+ }
+
+ var body struct {
+ Meta map[string]interface{} `json:"meta"`
+ Connection aid.JSON `json:"connection"`
+ }
+
+ if err := c.BodyParser(&body); err != nil {
+ return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
+ }
+
+ // joinability, ok := party.Config["joinability"].(string)
+ // if ok && joinability != "OPEN" {
+ // invite, ok := person.Invites.Get(c.Params("partyId"))
+ // if !ok {
+ // return c.Status(400).JSON(aid.ErrorBadRequest("Invite Not Found"))
+ // }
+
+ // if invite.Party.ID != party.ID {
+ // return c.Status(400).JSON(aid.ErrorBadRequest("Invite Not Found"))
+ // }
+
+ // person.Invites.Delete(c.Params("partyId"))
+ // party.RemoveInvite(invite)
+ // }
+
+ party.AddMember(person)
+ party.UpdateMemberMeta(person, body.Meta)
+ party.UpdateMemberConnection(person, body.Connection)
+
+ member := party.GetMember(person)
+ socket.EmitPartyMemberJoined(party, member)
+ socket.EmitPartyMemberMetaUpdated(party, party.GetMember(person), body.Meta, []string{})
+ socket.EmitPartyMetaUpdated(party, party.Meta, []string{}, map[string]interface{}{})
+
+ return c.Status(200).JSON(aid.JSON{
+ "party_id": party.ID,
+ "status": "JOINED",
+ })
}
\ No newline at end of file
diff --git a/main.go b/main.go
index d0cfa14..a69a4f8 100644
--- a/main.go
+++ b/main.go
@@ -77,7 +77,7 @@ func main() {
})
r.Use(aid.FiberLogger())
- r.Use(aid.FiberLimiter())
+ r.Use(aid.FiberLimiter(100))
r.Use(aid.FiberCors())
r.Get("/region", handlers.GetRegion)
@@ -110,6 +110,7 @@ func main() {
storefront := fortnite.Group("/storefront/v2")
storefront.Use(handlers.MiddlewareFortnite)
+ storefront.Use(aid.FiberLimiter(4))
storefront.Get("/catalog", handlers.GetStorefrontCatalog)
storefront.Get("/keychain", handlers.GetStorefrontKeychain)
@@ -151,15 +152,23 @@ func main() {
party := r.Group("/party/api/v1/Fortnite")
party.Use(handlers.MiddlewareFortnite)
- party.Get("/user/:accountId", handlers.GetUserParties)
- party.Get("/user/:accountId/settings/privacy", handlers.GetUserPartyPrivacy)
- party.Get("/user/:accountId/notifications/undelivered/count", handlers.GetUserPartyNotifications)
- party.Post("/parties", handlers.PostCreateParty)
+
+ party.Get("/user/:accountId", handlers.GetPartiesForUser)
+ party.Get("/user/:accountId/settings/privacy", handlers.GetPartyUserPrivacy)
+ party.Get("/user/:accountId/notifications/undelivered/count", handlers.GetPartyNotifications)
+ party.Get("/user/:accountId/pings/:friendId/parties", handlers.GetPartyPingsFromFriend)
+
party.Get("/parties/:partyId", handlers.GetPartyForMember)
- party.Patch("/parties/:partyId", handlers.PatchUpdateParty)
- party.Patch("/parties/:partyId/members/:accountId/meta", handlers.PatchUpdatePartyMemberMeta)
+ party.Post("/parties", handlers.PostPartyCreate)
+ party.Post("/parties/:partyId/invites/:accountId", handlers.PostPartyInvite)
+ party.Post("/parties/:partyId/members/:accountId/join", handlers.PostPartyJoin)
+ party.Patch("/parties/:partyId", handlers.PatchPartyUpdateState)
+ party.Patch("/parties/:partyId/members/:accountId/meta", handlers.PatchPartyUpdateMemberState)
+ party.Delete("/parties/:partyId/members/:accountId", handlers.DeletePartyMember)
+
// post /parties/:partyId/members/:accountId/conferences/connection (join a voip channel)
// delete /parties/:partyId/members/:accountid (remove a person from a party)
+
// get /user/:accountId/pings/:pinger/friendId/parties (get pings from a friend)
// post /user/:accountId/pings/:pinger/join (join a party from a ping)
// post /user/:friendId/pings/:accountId (send a ping)
diff --git a/person/parties.go b/person/parties.go
index 6edef09..0f90485 100644
--- a/person/parties.go
+++ b/person/parties.go
@@ -8,44 +8,78 @@ import (
"github.com/google/uuid"
)
-type PartyPing struct{
- Person *Person
- Party *Party
+type PartyInvite struct{
+ Party *Party `json:"-"`
+ Inviter *Person `json:"-"`
+ Towards *Person `json:"-"`
+ Meta map[string]interface{}
+ Status string
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ ExpiresAt time.Time
+}
+
+func NewPartyInvite(party *Party, inviter *Person, towards *Person, meta map[string]interface{}) *PartyInvite {
+ return &PartyInvite{
+ Party: party,
+ Inviter: inviter,
+ Towards: towards,
+ Meta: meta,
+ Status: "SENT",
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ ExpiresAt: time.Now().Add(time.Minute * 60),
+ }
+}
+
+func (pi *PartyInvite) GenerateFortnitePartyInvite() aid.JSON {
+ return aid.JSON{
+ "party_id": pi.Party.ID,
+ "sent_by": pi.Inviter.ID,
+ "sent_to": pi.Towards.ID,
+ "sent_at": pi.CreatedAt.Format(time.RFC3339),
+ "status": pi.Status,
+ "meta": pi.Meta,
+ "inviter_pl": "win",
+ "inviter_pl_dn": pi.Inviter.DisplayName,
+ "expires_at": pi.ExpiresAt.Format(time.RFC3339),
+ "updated_at": pi.UpdatedAt.Format(time.RFC3339),
+ }
}
type PartyMember struct{
Person *Person
ConnectionID string
Meta map[string]interface{}
- Connections map[string]aid.JSON
+ Connection aid.JSON
Role string
JoinedAt time.Time
UpdatedAt time.Time
}
func (pm *PartyMember) GenerateFortnitePartyMember() aid.JSON {
- connections := []aid.JSON{}
- for _, connection := range pm.Connections {
- connections = append(connections, connection)
- }
+ conn := pm.Connection
+ conn["yield_leadership"] = true
return aid.JSON{
"account_id": pm.Person.ID,
"role": pm.Role,
"meta": pm.Meta,
"joined_at": pm.JoinedAt.Format(time.RFC3339),
- "connections": connections,
+ "connections": []aid.JSON{conn},
"revision": 0,
}
}
type Party struct{
ID string
+ Captain *PartyMember
Members map[string]*PartyMember
+ Invites []*PartyInvite
Config map[string]interface{}
Meta map[string]interface{}
- m sync.Mutex
CreatedAt time.Time
+ m sync.Mutex
}
var (
@@ -63,6 +97,7 @@ func NewParty() *Party {
"invite_ttl:": 60,
},
Meta: make(map[string]interface{}),
+ Invites: []*PartyInvite{},
CreatedAt: time.Now(),
}
@@ -77,33 +112,43 @@ func (p *Party) GetMember(person *Person) *PartyMember {
return p.Members[person.ID]
}
-func (p *Party) AddMember(person *Person, role string) {
+func (p *Party) ChangeNewCaptain() {
+ p.m.Lock()
+ defer p.m.Unlock()
+
+ for _, member := range p.Members {
+ p.Captain = member
+ p.Captain.Role = "CAPTAIN"
+ p.Captain.UpdatedAt = time.Now()
+ break
+ }
+}
+
+func (p *Party) AddMember(person *Person) {
p.m.Lock()
defer p.m.Unlock()
partyMember := &PartyMember{
Person: person,
Meta: make(map[string]interface{}),
- Connections: make(map[string]aid.JSON),
- Role: role,
+ Role: "MEMBER",
JoinedAt: time.Now(),
}
p.Members[person.ID] = partyMember
person.Parties.Set(p.ID, p)
- // xmpp to person and rest of party to say new member!
}
func (p *Party) RemoveMember(person *Person) {
p.m.Lock()
defer p.m.Unlock()
+ person.Parties.Delete(p.ID)
+
delete(p.Members, person.ID)
if len(p.Members) == 0 {
Parties.Delete(p.ID)
}
-
- person.Parties.Delete(p.ID)
}
func (p *Party) UpdateMeta(m map[string]interface{}) {
@@ -164,16 +209,19 @@ func (p *Party) DeleteMemberMeta(person *Person, keys []string) {
}
}
-func (p *Party) UpdateMemberConnections(person *Person, m aid.JSON) {
+func (p *Party) UpdateMemberConnection(person *Person, m aid.JSON) {
p.m.Lock()
defer p.m.Unlock()
+ m["connected_at"] = time.Now().Format(time.RFC3339)
+ m["updated_at"] = time.Now().Format(time.RFC3339)
+
member, ok := p.Members[person.ID]
if !ok {
return
}
- member.Connections[m["id"].(string)] = m
+ member.Connection = m
}
func (p *Party) UpdateConfig(m map[string]interface{}) {
@@ -185,6 +233,25 @@ func (p *Party) UpdateConfig(m map[string]interface{}) {
}
}
+func (p *Party) AddInvite(invite *PartyInvite) {
+ p.m.Lock()
+ defer p.m.Unlock()
+
+ p.Invites = append(p.Invites, invite)
+}
+
+func (p *Party) RemoveInvite(invite *PartyInvite) {
+ p.m.Lock()
+ defer p.m.Unlock()
+
+ for i, v := range p.Invites {
+ if v == invite {
+ p.Invites = append(p.Invites[:i], p.Invites[i+1:]...)
+ break
+ }
+ }
+}
+
func (p *Party) GenerateFortniteParty() aid.JSON {
p.m.Lock()
defer p.m.Unlock()
@@ -206,5 +273,9 @@ func (p *Party) GenerateFortniteParty() aid.JSON {
party["members"] = append(party["members"].([]aid.JSON), member.GenerateFortnitePartyMember())
}
+ for _, invite := range p.Invites {
+ party["invites"] = append(party["invites"].([]aid.JSON), invite.GenerateFortnitePartyInvite())
+ }
+
return party
}
\ No newline at end of file
diff --git a/person/person.go b/person/person.go
index d67ad52..bc694b2 100644
--- a/person/person.go
+++ b/person/person.go
@@ -23,7 +23,7 @@ type Person struct {
BanHistory aid.GenericSyncMap[storage.DB_BanStatus]
Relationships aid.GenericSyncMap[Relationship]
Parties aid.GenericSyncMap[Party]
- Pings aid.GenericSyncMap[PartyPing]
+ Invites aid.GenericSyncMap[PartyInvite]
}
func NewPerson() *Person {
@@ -41,7 +41,7 @@ func NewPerson() *Person {
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
- Pings: aid.GenericSyncMap[PartyPing]{},
+ Invites: aid.GenericSyncMap[PartyInvite]{},
}
}
@@ -60,7 +60,7 @@ func NewPersonWithCustomID(id string) *Person {
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
- Pings: aid.GenericSyncMap[PartyPing]{},
+ Invites: aid.GenericSyncMap[PartyInvite]{},
}
}
@@ -209,6 +209,7 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
RefundTickets: databasePerson.RefundTickets,
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
+ Invites: aid.GenericSyncMap[PartyInvite]{},
}
for _, ban := range databasePerson.BanHistory {
diff --git a/socket/events.go b/socket/events.go
new file mode 100644
index 0000000..b21a27d
--- /dev/null
+++ b/socket/events.go
@@ -0,0 +1,163 @@
+package socket
+
+import (
+ "time"
+
+ "github.com/ectrc/snow/aid"
+ "github.com/ectrc/snow/person"
+)
+
+func EmitPartyMemberJoined(party *person.Party, joiningMember *person.PartyMember) {
+ for _, partyMember := range party.Members {
+ s, ok := JabberSockets.Get(partyMember.Person.ID)
+ if !ok {
+ continue
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "account_id": joiningMember.Person.ID,
+ "account_dn": joiningMember.Person.DisplayName,
+ "connection": joiningMember.Connection,
+ "member_state_updated": joiningMember.Meta,
+ "updated_at": joiningMember.UpdatedAt.Format(time.RFC3339),
+ "joined_at": joiningMember.JoinedAt.Format(time.RFC3339),
+ "ns": "Fortnite",
+ "party_id": party.ID,
+ "sent": time.Now().Format(time.RFC3339),
+ "revision": 0,
+ "type": "com.epicgames.social.party.notification.v0.MEMBER_JOINED",
+ })
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "interactions": []aid.JSON{{
+ "app": "Snow",
+ "namespace": "Fortnite",
+ "fromAccountId": joiningMember.Person.ID,
+ "toAccountId": partyMember.Person.ID,
+ "interactionScoreIncremental": aid.JSON{
+ "count": 1,
+ "total": 1,
+ },
+ "interactionType": "PartyJoined",
+ "isFriend": true,
+ "happenedAt": time.Now().Unix(),
+ }},
+ "type": "com.epicgames.social.interactions.notification.v2",
+ })
+ }
+}
+
+func EmitPartyMemberLeft(party *person.Party, member *person.PartyMember) {
+ for _, m := range party.Members {
+ s, ok := JabberSockets.Get(m.Person.ID)
+ if !ok {
+ continue
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "account_id": member.Person.ID,
+ "member_state_updated": aid.JSON{},
+ "ns": "Fortnite",
+ "party_id": party.ID,
+ "sent": time.Now().Format(time.RFC3339),
+ "revision": 0,
+ "type": "com.epicgames.social.party.notification.v0.MEMBER_LEFT",
+ })
+ }
+}
+
+func EmitPartyMemberMetaUpdated(party *person.Party, member *person.PartyMember, update map[string]interface{}, deleted []string) {
+ for _, m := range party.Members {
+ s, ok := JabberSockets.Get(m.Person.ID)
+ if !ok {
+ continue
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "account_id": member.Person.ID,
+ "account_dn": member.Person.DisplayName,
+ "member_state_updated": update,
+ "member_state_removed": deleted,
+ "member_state_overriden": aid.JSON{},
+ "updated_at": member.UpdatedAt.Format(time.RFC3339),
+ "joined_at": member.JoinedAt.Format(time.RFC3339),
+ "ns": "Fortnite",
+ "party_id": party.ID,
+ "sent": time.Now().Format(time.RFC3339),
+ "revision": 0,
+ "type": "com.epicgames.social.party.notification.v0.MEMBER_STATE_UPDATED",
+ })
+ }
+}
+
+func EmitPartyMetaUpdated(party *person.Party, override map[string]interface{}, deleted []string, update map[string]interface{}) {
+ for _, m := range party.Members {
+ s, ok := JabberSockets.Get(m.Person.ID)
+ if !ok {
+ continue
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "captain_id": party.Captain.Person.ID,
+ "party_state_updated": update,
+ "party_state_removed": deleted,
+ "party_state_overriden": override,
+ "party_privacy_type": party.Config["joinability"],
+ "party_type": party.Config["type"],
+ "party_sub_type": party.Config["sub_type"],
+ "max_number_of_members": party.Config["max_size"],
+ "invite_ttl_seconds": party.Config["invite_ttl"],
+ "intention_ttl_seconds": party.Config["intention_ttl"],
+ "updated_at": time.Now().Format(time.RFC3339),
+ "created_at": party.CreatedAt.Format(time.RFC3339),
+ "ns": "Fortnite",
+ "party_id": party.ID,
+ "sent": time.Now().Format(time.RFC3339),
+ "revision": 0,
+ "type": "com.epicgames.social.party.notification.v0.PARTY_UPDATED",
+ })
+
+ aid.Print("EmitPartyMetaUpdated party", party.ID, "to", m.Person.DisplayName)
+ }
+}
+
+func EmitPartyNewCaptain(party *person.Party) {
+ for _, m := range party.Members {
+ s, ok := JabberSockets.Get(m.Person.ID)
+ if !ok {
+ continue
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "account_id": party.Captain.Person.ID,
+ "account_dn": party.Captain.Person.DisplayName,
+ "ns": "Fortnite",
+ "party_id": party.ID,
+ "sent": time.Now().Format(time.RFC3339),
+ "revision": 0,
+ "type": "com.epicgames.social.party.notification.v0.MEMBER_NEW_CAPTAIN",
+ })
+ }
+}
+
+func EmitPartyInvite(invite *person.PartyInvite) {
+ s, ok := JabberSockets.Get(invite.Towards.ID)
+ if !ok {
+ return
+ }
+
+ s.JabberSendMessageToPerson(aid.JSON{
+ "inviter_id": invite.Inviter.ID,
+ "inviter_dn": invite.Inviter.DisplayName,
+ "invitee_id": invite.Towards.ID,
+ "meta": invite.Meta,
+ "sent_at": invite.CreatedAt.Format(time.RFC3339),
+ "updated_at": invite.UpdatedAt.Format(time.RFC3339),
+ "friends_ids": []string{},
+ "members_count": 0,
+ "party_id": invite.Party.ID,
+ "ns": "Fortnite",
+ "sent": time.Now().Format(time.RFC3339),
+ "type": "com.epicgames.social.party.notification.v0.INITIAL_INVITE",
+ })
+}
\ No newline at end of file
diff --git a/socket/jabber.go b/socket/jabber.go
index 43214cf..600bde5 100644
--- a/socket/jabber.go
+++ b/socket/jabber.go
@@ -13,13 +13,14 @@ import (
type JabberData struct {
JabberID string
+ PartyID string
LastPresence string
}
var jabberHandlers = map[string]func(*Socket[JabberData], *etree.Document) error {
"open": jabberOpenHandler,
"iq": jabberIqRootHandler,
- "presence": jabberPresenceHandler,
+ "presence": jabberPresenceRootHandler,
"message": jabberMessageHandler,
}
@@ -87,6 +88,7 @@ func jabberIqSetHandler(socket *Socket[JabberData], parsed *etree.Document) erro
socket.ID = person.ID
socket.Person = person
socket.Data.JabberID = snowId + "@prod.ol.epicgames.com/" + parsed.FindElement("/iq/query/resource").Text()
+ socket.Data.PartyID = person.DisplayName + ":" + person.ID + parsed.FindElement("/iq/query/resource").Text()
socket.Write([]byte(``))
return nil
@@ -98,13 +100,74 @@ func jabberIqGetHandler(socket *Socket[JabberData], parsed *etree.Document) erro
return nil
}
-func jabberPresenceHandler(socket *Socket[JabberData], parsed *etree.Document) error {
+func jabberPresenceRootHandler(socket *Socket[JabberData], parsed *etree.Document) error {
status := parsed.FindElement("/presence/status")
if status == nil {
- return nil
+ return jabberPresenceJoinGroupchat(socket, parsed)
}
+
socket.Data.LastPresence = status.Text()
socket.JabberNotifyFriends()
+
+ return nil
+}
+
+func jabberPresenceJoinGroupchat(socket *Socket[JabberData], parsed *etree.Document) error {
+ towards := parsed.FindElement("/presence").SelectAttr("to").Value
+
+ partyId := aid.Regex(towards, `Party-(.*?)@`)
+ if partyId == nil {
+ return nil
+ }
+
+ party, ok := person.Parties.Get(*partyId)
+ if !ok {
+ return nil
+ }
+
+ for _, member := range party.Members {
+ if member.Person.ID == socket.ID {
+ return nil
+ }
+
+ memberSocket, ok := JabberSockets.Get(member.Person.ID)
+ if !ok {
+ continue
+ }
+ memberPartyId := "Party-" + party.ID + "@muc.prod.ol.epicgames.com/" + memberSocket.Data.PartyID
+ memberRole := aid.Ternary[string](party.Captain.Person.ID == member.Person.ID, "moderator", "participant")
+ memberAffiliation := aid.Ternary[string](party.Captain.Person.ID == member.Person.ID, "owner", "none")
+
+ socket.Write([]byte(`
+
+
+
+ `))
+ }
+
+ socketPartyId := "Party-" + party.ID + "@muc.prod.ol.epicgames.com/" + socket.Data.PartyID
+ socketRole := aid.Ternary[string](party.Captain.Person.ID == socket.ID, "moderator", "participant")
+ socketAffiliation := aid.Ternary[string](party.Captain.Person.ID == socket.ID, "owner", "none")
+
+ socket.Write([]byte(`
+
+
+
+
+
+
+ `))
+
return nil
}
@@ -175,4 +238,8 @@ func (s *Socket[T]) JabberNotifyFriends() {
return true
})
+
+ jabberSocket.Write([]byte(`
+ `+ jabberSocket.Data.LastPresence +`
+ `))
}
\ No newline at end of file