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