diff --git a/handlers/parties.go b/handlers/parties.go index 7779929..99ee5b2 100644 --- a/handlers/parties.go +++ b/handlers/parties.go @@ -117,8 +117,10 @@ func PostPartyCreate(c *fiber.Ctx) error { party.AddMember(person) party.UpdateMemberMeta(person, body.JoinInformation.Meta) party.UpdateMemberConnection(person, body.JoinInformation.Connection) - party.ChangeNewCaptain() - socket.EmitPartyMemberJoined(party, party.GetMember(person)) + + member := party.GetMember(person) + party.PromoteMember(member) + socket.EmitPartyMemberJoined(party, member) return c.Status(200).JSON(party.GenerateFortniteParty()) } @@ -134,11 +136,9 @@ func PatchPartyUpdateState(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 { @@ -212,8 +212,8 @@ func DeletePartyMember(c *fiber.Ctx) error { party.RemoveMember(person) if party.Captain.Person.ID == person.ID && len(party.Members) > 0 { - party.ChangeNewCaptain() - go socket.EmitPartyNewCaptain(party) + party.PromoteMember(party.GetFirstMember()) + socket.EmitPartyNewCaptain(party) } return c.SendStatus(204) @@ -266,21 +266,6 @@ func PostPartyJoin(c *fiber.Ctx) error { 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) @@ -294,4 +279,26 @@ func PostPartyJoin(c *fiber.Ctx) error { "party_id": party.ID, "status": "JOINED", }) +} + +func PostPartyPromoteMember(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(p.Find(c.Params("accountId"))) + if member == nil { + return c.Status(400).JSON(aid.ErrorBadRequest("Member Not Found")) + } + + if party.Captain.Person.ID != person.ID { + return c.Status(400).JSON(aid.ErrorBadRequest("Not Captain")) + } + + party.PromoteMember(member) + socket.EmitPartyNewCaptain(party) + + return c.SendStatus(204) } \ No newline at end of file diff --git a/main.go b/main.go index a69a4f8..ed1fc94 100644 --- a/main.go +++ b/main.go @@ -162,12 +162,12 @@ func main() { party.Post("/parties", handlers.PostPartyCreate) party.Post("/parties/:partyId/invites/:accountId", handlers.PostPartyInvite) party.Post("/parties/:partyId/members/:accountId/join", handlers.PostPartyJoin) + party.Post("/parties/:partyId/members/:accountId/promote", handlers.PostPartyPromoteMember) 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) diff --git a/person/parties.go b/person/parties.go index 0f90485..a4af35a 100644 --- a/person/parties.go +++ b/person/parties.go @@ -55,6 +55,11 @@ type PartyMember struct{ Role string JoinedAt time.Time UpdatedAt time.Time + Revision int +} + +func (pm *PartyMember) IncreaseRevision() { + pm.Revision++ } func (pm *PartyMember) GenerateFortnitePartyMember() aid.JSON { @@ -67,7 +72,7 @@ func (pm *PartyMember) GenerateFortnitePartyMember() aid.JSON { "meta": pm.Meta, "joined_at": pm.JoinedAt.Format(time.RFC3339), "connections": []aid.JSON{conn}, - "revision": 0, + "revision": pm.Revision, } } @@ -79,6 +84,7 @@ type Party struct{ Config map[string]interface{} Meta map[string]interface{} CreatedAt time.Time + Revision int m sync.Mutex } @@ -105,6 +111,13 @@ func NewParty() *Party { return party } +func (p *Party) IncreaseRevision() { + p.m.Lock() + defer p.m.Unlock() + + p.Revision++ +} + func (p *Party) GetMember(person *Person) *PartyMember { p.m.Lock() defer p.m.Unlock() @@ -112,16 +125,24 @@ func (p *Party) GetMember(person *Person) *PartyMember { return p.Members[person.ID] } -func (p *Party) ChangeNewCaptain() { +func (p *Party) PromoteMember(member *PartyMember) { + p.m.Lock() + defer p.m.Unlock() + + member.Role = "CAPTAIN" + member.UpdatedAt = time.Now() + p.Captain = member +} + +func (p *Party) GetFirstMember() *PartyMember { 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 + return member } + + return nil } func (p *Party) AddMember(person *Person) { diff --git a/readme.md b/readme.md index 025a499..47f1e83 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,6 @@ ## What's up next? -- **Party System V2** Currently it relies on the automatic XMPP solution which is very hard to keep track of. - Seeded randomization for the **Item Shop** instead of a random number generator. This will ensure that even if the backend is restarted, the same random items will be in the shop during that day. - Purchasing the **Battle Pass**. This will require the Battle Pass Storefront ID for every build. I am yet to think of a solution for this. - Interaction with a Game Server to handle **Event Tracking** for player statistics and challenges. This will be a very large task as a new specialised game server will need to be created. @@ -33,4 +32,4 @@ And once battle royale is completed ... ## Contributing -Contributions are welcome! Please open an issue or pull request if you would like to contribute. Make sure to follow the same format (2 space indents) and style! Make sure to keep commits concise and readable e.g. do not change formating to mess up code review! +Contributions are welcome! Please open an issue or pull request if you would like to contribute. Make sure to follow the same formatting and to keep commits concise and readable e.g. do not change line indents to mess up code review! diff --git a/socket/events.go b/socket/events.go index b21a27d..e1cf092 100644 --- a/socket/events.go +++ b/socket/events.go @@ -14,6 +14,7 @@ func EmitPartyMemberJoined(party *person.Party, joiningMember *person.PartyMembe continue } + joiningMember.IncreaseRevision() s.JabberSendMessageToPerson(aid.JSON{ "account_id": joiningMember.Person.ID, "account_dn": joiningMember.Person.DisplayName, @@ -24,7 +25,7 @@ func EmitPartyMemberJoined(party *person.Party, joiningMember *person.PartyMembe "ns": "Fortnite", "party_id": party.ID, "sent": time.Now().Format(time.RFC3339), - "revision": 0, + "revision": joiningMember.Revision, "type": "com.epicgames.social.party.notification.v0.MEMBER_JOINED", }) @@ -47,20 +48,21 @@ func EmitPartyMemberJoined(party *person.Party, joiningMember *person.PartyMembe } } -func EmitPartyMemberLeft(party *person.Party, member *person.PartyMember) { +func EmitPartyMemberLeft(party *person.Party, leavingMember *person.PartyMember) { for _, m := range party.Members { s, ok := JabberSockets.Get(m.Person.ID) if !ok { continue } + leavingMember.IncreaseRevision() s.JabberSendMessageToPerson(aid.JSON{ - "account_id": member.Person.ID, + "account_id": leavingMember.Person.ID, "member_state_updated": aid.JSON{}, "ns": "Fortnite", "party_id": party.ID, "sent": time.Now().Format(time.RFC3339), - "revision": 0, + "revision": leavingMember.Revision, "type": "com.epicgames.social.party.notification.v0.MEMBER_LEFT", }) } @@ -73,6 +75,7 @@ func EmitPartyMemberMetaUpdated(party *person.Party, member *person.PartyMember, continue } + member.IncreaseRevision() s.JabberSendMessageToPerson(aid.JSON{ "account_id": member.Person.ID, "account_dn": member.Person.DisplayName, @@ -84,7 +87,7 @@ func EmitPartyMemberMetaUpdated(party *person.Party, member *person.PartyMember, "ns": "Fortnite", "party_id": party.ID, "sent": time.Now().Format(time.RFC3339), - "revision": 0, + "revision": member.Revision, "type": "com.epicgames.social.party.notification.v0.MEMBER_STATE_UPDATED", }) } @@ -97,6 +100,7 @@ func EmitPartyMetaUpdated(party *person.Party, override map[string]interface{}, continue } + party.IncreaseRevision() s.JabberSendMessageToPerson(aid.JSON{ "captain_id": party.Captain.Person.ID, "party_state_updated": update, @@ -113,11 +117,9 @@ func EmitPartyMetaUpdated(party *person.Party, override map[string]interface{}, "ns": "Fortnite", "party_id": party.ID, "sent": time.Now().Format(time.RFC3339), - "revision": 0, + "revision": party.Revision, "type": "com.epicgames.social.party.notification.v0.PARTY_UPDATED", }) - - aid.Print("EmitPartyMetaUpdated party", party.ID, "to", m.Person.DisplayName) } } @@ -128,13 +130,14 @@ func EmitPartyNewCaptain(party *person.Party) { continue } + party.IncreaseRevision() 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, + "revision": party.Revision, "type": "com.epicgames.social.party.notification.v0.MEMBER_NEW_CAPTAIN", }) } @@ -154,7 +157,7 @@ func EmitPartyInvite(invite *person.PartyInvite) { "sent_at": invite.CreatedAt.Format(time.RFC3339), "updated_at": invite.UpdatedAt.Format(time.RFC3339), "friends_ids": []string{}, - "members_count": 0, + "members_count": len(invite.Party.Members), "party_id": invite.Party.ID, "ns": "Fortnite", "sent": time.Now().Format(time.RFC3339), diff --git a/socket/jabber.go b/socket/jabber.go index 600bde5..6ee0ba7 100644 --- a/socket/jabber.go +++ b/socket/jabber.go @@ -3,7 +3,6 @@ package socket import ( "fmt" "reflect" - "strings" "github.com/beevik/etree" "github.com/ectrc/snow/aid" @@ -172,23 +171,37 @@ func jabberPresenceJoinGroupchat(socket *Socket[JabberData], parsed *etree.Docum } func jabberMessageHandler(socket *Socket[JabberData], parsed *etree.Document) error { - if parsed.FindElement("/message/body") == nil { - return nil - } - message := parsed.FindElement("/message") - target := message.SelectAttr("to").Value - parts := strings.Split(target, "@") - - if len(parts) != 2 { + if message.FindElement("/body") == nil { return nil } - if reciever, ok := JabberSockets.Get(parts[0]); ok { - reciever.Write([]byte(` - `+ message.FindElement("/message/body").Text() +` - `)) + towards := message.SelectAttr("to") + if towards == nil { + return nil } + + redirect := map[string]func(*Socket[JabberData], *etree.Document) error { + "chat": jabberMessageChatHandler, + "groupchat": jabberMessageGroupchatHandler, + } + + if handler, ok := redirect[towards.Value]; ok { + if err := handler(socket, parsed); err != nil { + return err + } + } + + return nil +} + +// TODO: IMPLEMENT WHISPERING + +func jabberMessageChatHandler(socket *Socket[JabberData], parsed *etree.Document) error { + return nil +} + +func jabberMessageGroupchatHandler(socket *Socket[JabberData], parsed *etree.Document) error { return nil }