Party 2 now has basic functionality

This commit is contained in:
Eccentric 2024-02-12 20:29:02 +00:00
parent 0781a3c83e
commit 0b9da07d2e
11 changed files with 355 additions and 80 deletions

View File

@ -89,7 +89,6 @@ func (c *ExternalDataClient) LoadExternalData() {
for _, setValue := range addNumericStylesToSets {
set, found := c.FortniteSets[setValue]
if !found {
aid.Print("set not found: " + setValue)
continue
}

View File

@ -152,6 +152,10 @@ func NewFortnitePersonWithId(id string, displayName string, everything bool) *p.
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("allowed_to_send_gifts", true)).Save()
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("gift_history", aid.JSON{})).Save()
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("party.recieveIntents", "ALL")).Save()
person.CommonCoreProfile.Attributes.AddAttribute(p.NewAttribute("party.recieveInvites", "ALL")).Save()
loadout := p.NewLoadout("PRESET 1", person.AthenaProfile)
person.AthenaProfile.Loadouts.AddLoadout(loadout).Save()
person.AthenaProfile.Attributes.AddAttribute(p.NewAttribute("loadouts", []string{loadout.ID})).Save()

View File

@ -415,7 +415,7 @@ func GenerateRandomStorefront() {
}
minimumSets := 4
if aid.Config.Fortnite.Season < 11 {
if aid.Config.Fortnite.Season <+ 12 {
minimumSets = 3
}

View File

@ -44,12 +44,31 @@ func PostTokenClientCredentials(c *fiber.Ctx, body *FortniteTokenBody) error {
return c.Status(400).JSON(aid.ErrorBadRequest("Client Credentials is disabled."))
}
clientCredentials, err := aid.JWTSign(aid.JSON{
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
"clsvc": "prod-fn",
"t": "s",
"mver": false,
"clid": aid.Hash([]byte(c.IP())),
"ic": true,
"exp": 1707772234,
"iat": 1707757834,
"jti": "snow-revoke",
"pfpid": "prod-fn",
"am": "client_credentials",
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
}
return c.Status(200).JSON(aid.JSON{
"access_token": "snow",
"access_token": clientCredentials,
"application_id": "fghi4567FNFBKFz3E4TROb0bmPS8h1GW",
"token_type": "bearer",
"client_id": aid.Hash([]byte(c.IP())),
"client_service": "fortnite",
"client_service": "prod-fn",
"internal_client": true,
"product_id": "prod-fn",
"expires_in": 3600,
"expires_at": time.Now().Add(time.Hour).Format("2006-01-02T15:04:05.999Z"),
})

View File

@ -1,8 +1,11 @@
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"
)
@ -23,3 +26,167 @@ func GetUserParties(c *fiber.Ctx) error {
return c.Status(200).JSON(response)
}
func GetUserPartyPrivacy(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
recieveIntents := person.CommonCoreProfile.Attributes.GetAttributeByKey("party.recieveIntents")
if recieveIntents == nil {
return c.Status(400).JSON(aid.ErrorBadRequest("No Privacy Found"))
}
recieveInvites := person.CommonCoreProfile.Attributes.GetAttributeByKey("party.recieveInvites")
if recieveIntents == nil {
return c.Status(400).JSON(aid.ErrorBadRequest("No Privacy Found"))
}
return c.Status(200).JSON(aid.JSON{
"recieveIntents": aid.JSONParse(recieveIntents.ValueJSON),
"recieveInvites": aid.JSONParse(recieveInvites.ValueJSON),
})
}
func GetUserPartyNotifications(c *fiber.Ctx) error {
return c.Status(200).JSON(aid.JSON{
"pings": 0,
"invites": 0,
})
}
func GetPartyForMember(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"))
}
return c.Status(200).JSON(party.GenerateFortniteParty())
}
func PostCreateParty(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
person.Parties.Range(func(key string, party *p.Party) bool {
party.RemoveMember(person)
return true
})
var body struct {
Config map[string]interface{} `json:"config"`
Meta map[string]interface{} `json:"meta"`
JoinInformation struct {
Meta map[string]interface{} `json:"meta"`
Connection aid.JSON `json:"connection"`
} `json:"join_info"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
}
party := p.NewParty()
party.UpdateMeta(body.Meta)
party.UpdateConfig(body.Config)
party.AddMember(person, "CAPTAIN")
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",
})
return c.Status(200).JSON(party.GenerateFortniteParty())
}
func PatchUpdateParty(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
var body struct {
Config map[string]interface{} `json:"config"`
Meta struct {
Update map[string]interface{} `json:"update"`
Delete []string `json:"delete"`
} `json:"meta"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
}
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"))
}
if member.Role != "CAPTAIN" {
return c.Status(400).JSON(aid.ErrorBadRequest("Not Captain"))
}
party.UpdateConfig(body.Config)
party.UpdateMeta(body.Meta.Update)
party.DeleteMeta(body.Meta.Delete)
return c.Status(200).JSON(party.GenerateFortniteParty())
}
func PatchUpdatePartyMemberMeta(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
var body struct {
Update map[string]interface{} `json:"update"`
Delete []string `json:"delete"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request"))
}
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"))
}
if c.Params("accountId") != person.ID {
return c.Status(400).JSON(aid.ErrorBadRequest("Not owner of person"))
}
party.UpdateMemberMeta(person, body.Update)
party.DeleteMemberMeta(person, body.Delete)
return c.Status(200).JSON(party.GenerateFortniteParty())
}

View File

@ -16,7 +16,7 @@ func MiddlewareOnlyDebug(c *fiber.Ctx) error {
return c.SendStatus(403)
}
func GetPreloadedCosmetics(c *fiber.Ctx) error {
func GetSnowPreloadedCosmetics(c *fiber.Ctx) error {
return c.JSON(fortnite.External)
}
@ -42,7 +42,7 @@ func GetPlayer(c *fiber.Ctx) error {
})
}
func GetCachedPlayers(c *fiber.Ctx) error {
func GetSnowCachedPlayers(c *fiber.Ctx) error {
persons := p.AllFromCache()
players := make([]p.PersonSnapshot, len(persons))
@ -59,3 +59,18 @@ func GetSnowConfig(c *fiber.Ctx) error {
"amazon": storage.Repo.Amazon,
})
}
func GetSnowParties(c *fiber.Ctx) error {
parties := []aid.JSON{}
p.Parties.Range(func(key string, value *p.Party) bool {
parties = append(parties, value.GenerateFortniteParty())
return true
})
return c.JSON(parties)
}
func GetSnowShop(c *fiber.Ctx) error {
return c.JSON(fortnite.StaticCatalog)
}

View File

@ -43,7 +43,7 @@ func WebsocketConnection(c *websocket.Conn) {
}
}
func GetConnectedSockets(c *fiber.Ctx) error {
func GetSnowConnectedSockets(c *fiber.Ctx) error {
jabber := aid.JSON{}
socket.JabberSockets.Range(func(key string, value *socket.Socket[socket.JabberData]) bool {
jabber[key] = value

30
main.go
View File

@ -120,12 +120,9 @@ func main() {
storage.Get("/system", handlers.GetCloudStorageFiles)
storage.Get("/system/config", handlers.GetCloudStorageConfig)
storage.Get("/system/:fileName", handlers.GetCloudStorageFile)
user := storage.Group("/user")
user.Use(handlers.MiddlewareFortnite)
user.Get("/:accountId", handlers.GetUserStorageFiles)
user.Get("/:accountId/:fileName", handlers.GetUserStorageFile)
user.Put("/:accountId/:fileName", handlers.PutUserStorageFile)
storage.Get("/user/:accountId", handlers.MiddlewareFortnite, handlers.GetUserStorageFiles)
storage.Get("/user/:accountId/:fileName", handlers.MiddlewareFortnite, handlers.GetUserStorageFile)
storage.Put("/user/:accountId/:fileName", handlers.MiddlewareFortnite, handlers.PutUserStorageFile)
friends := r.Group("/friends/api")
friends.Use(handlers.MiddlewareFortnite)
@ -155,13 +152,28 @@ 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("/parties/:partyId", handlers.GetPartyForMember)
party.Patch("/parties/:partyId", handlers.PatchUpdateParty)
party.Patch("/parties/:partyId/members/:accountId/meta", handlers.PatchUpdatePartyMemberMeta)
// 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)
// delete /user/:accountId/pings/:pinger/friendId (delete pings)
// post /members/:friendId/intentions/:accountId (send an invite and add invite to party)
snow := r.Group("/snow")
snow.Use(handlers.MiddlewareOnlyDebug)
snow.Get("/cache", handlers.GetCachedPlayers)
snow.Get("/cache", handlers.GetSnowCachedPlayers)
snow.Get("/config", handlers.GetSnowConfig)
snow.Get("/sockets", handlers.GetConnectedSockets)
snow.Get("/cosmetics", handlers.GetPreloadedCosmetics)
snow.Get("/sockets", handlers.GetSnowConnectedSockets)
snow.Get("/cosmetics", handlers.GetSnowPreloadedCosmetics)
snow.Get("/parties", handlers.GetSnowParties)
snow.Get("/shop", handlers.GetSnowShop)
discord := snow.Group("/discord")
discord.Get("/", handlers.GetDiscordOAuthURL)

View File

@ -8,20 +8,44 @@ import (
"github.com/google/uuid"
)
type PartyPing struct{
Person *Person
Party *Party
}
type PartyMember struct{
Person *Person
ConnectionID string
Meta map[string]interface{}
Connections map[string]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)
}
return aid.JSON{
"account_id": pm.Person.ID,
"role": pm.Role,
"meta": pm.Meta,
"joined_at": pm.JoinedAt.Format(time.RFC3339),
"connections": connections,
"revision": 0,
}
}
type Party struct{
ID string
Members []*PartyMember
Members map[string]*PartyMember
Config map[string]interface{}
Meta map[string]interface{}
m sync.Mutex
CreatedAt time.Time
}
var (
@ -31,16 +55,29 @@ var (
func NewParty() *Party {
party := &Party{
ID: uuid.New().String(),
Members: []*PartyMember{},
Config: make(map[string]interface{}),
Members: make(map[string]*PartyMember),
Config: map[string]interface{}{
"type": "DEFAULT",
"sub_type": "default",
"intention_ttl:": 60,
"invite_ttl:": 60,
},
Meta: make(map[string]interface{}),
CreatedAt: time.Now(),
}
Parties.Set(party.ID, party)
return party
}
func (p *Party) AddMember(person *Person) {
func (p *Party) GetMember(person *Person) *PartyMember {
p.m.Lock()
defer p.m.Unlock()
return p.Members[person.ID]
}
func (p *Party) AddMember(person *Person, role string) {
p.m.Lock()
defer p.m.Unlock()
@ -48,10 +85,11 @@ func (p *Party) AddMember(person *Person) {
Person: person,
Meta: make(map[string]interface{}),
Connections: make(map[string]aid.JSON),
Role: "MEMBER",
Role: role,
JoinedAt: time.Now(),
}
p.Members = append(p.Members, partyMember)
p.Members[person.ID] = partyMember
person.Parties.Set(p.ID, p)
// xmpp to person and rest of party to say new member!
}
@ -60,77 +98,91 @@ func (p *Party) RemoveMember(person *Person) {
p.m.Lock()
defer p.m.Unlock()
for i, member := range p.Members {
if member.Person == person {
p.Members = append(p.Members[:i], p.Members[i+1:]...)
break
}
}
delete(p.Members, person.ID)
if len(p.Members) == 0 {
Parties.Delete(p.ID)
}
person.Parties.Delete(p.ID)
// xmpp to person and rest of party to say member left!
}
func (p *Party) UpdateMeta(key string, value interface{}) {
func (p *Party) UpdateMeta(m map[string]interface{}) {
p.m.Lock()
defer p.m.Unlock()
p.Meta[key] = value
// xmpp to rest of party to say meta updated!
}
func (p *Party) DeleteMeta(key string) {
p.m.Lock()
defer p.m.Unlock()
delete(p.Meta, key)
// xmpp to rest of party to say meta deleted!
}
func (p *Party) UpdateMemberMeta(person *Person, key string, value interface{}) {
p.m.Lock()
defer p.m.Unlock()
for _, member := range p.Members {
if member.Person == person {
member.Meta[key] = value
// xmpp to person and rest of party to say member meta updated!
break
}
for key, value := range m {
p.Meta[key] = value
}
}
func (p *Party) DeleteMemberMeta(person *Person, key string) {
func (p *Party) DeleteMeta(keys []string) {
p.m.Lock()
defer p.m.Unlock()
for _, member := range p.Members {
if member.Person == person {
delete(member.Meta, key)
// xmpp to person and rest of party to say member meta deleted!
break
}
for _, key := range keys {
delete(p.Meta, key)
}
}
func (p *Party) UpdateConfig(key string, value interface{}) {
func (p *Party) UpdateMemberMeta(person *Person, m map[string]interface{}) {
p.m.Lock()
defer p.m.Unlock()
p.Config[key] = value
// xmpp to rest of party to say config updated!
member, ok := p.Members[person.ID]
if !ok {
return
}
for key, value := range m {
member.Meta[key] = value
}
}
func (p *Party) DeleteConfig(key string) {
func (p *Party) UpdateMemberRevision(person *Person, revision int) {
p.m.Lock()
defer p.m.Unlock()
delete(p.Config, key)
// xmpp to rest of party to say config deleted!
member, ok := p.Members[person.ID]
if !ok {
return
}
member.Meta["revision"] = revision
}
func (p *Party) DeleteMemberMeta(person *Person, keys []string) {
p.m.Lock()
defer p.m.Unlock()
member, ok := p.Members[person.ID]
if !ok {
return
}
for _, key := range keys {
delete(member.Meta, key)
}
}
func (p *Party) UpdateMemberConnections(person *Person, m aid.JSON) {
p.m.Lock()
defer p.m.Unlock()
member, ok := p.Members[person.ID]
if !ok {
return
}
member.Connections[m["id"].(string)] = m
}
func (p *Party) UpdateConfig(m map[string]interface{}) {
p.m.Lock()
defer p.m.Unlock()
for key, value := range m {
p.Config[key] = value
}
}
func (p *Party) GenerateFortniteParty() aid.JSON {
@ -139,21 +191,19 @@ func (p *Party) GenerateFortniteParty() aid.JSON {
party := aid.JSON{
"id": p.ID,
"members": aid.JSON{},
"config": p.Config,
"meta": p.Meta,
"created_at": "0000-00-00T00:00:00Z",
"applicants": []aid.JSON{},
"members": []aid.JSON{},
"invites": []aid.JSON{},
"intentions": []aid.JSON{},
"created_at": p.CreatedAt.Format(time.RFC3339),
"updated_at": time.Now().Format(time.RFC3339),
"revision": 0,
}
for _, member := range p.Members {
party["members"].(aid.JSON)[member.Person.ID] = aid.JSON{
"account_id": member.Person.ID,
"role": member.Role,
"meta": member.Meta,
"connections": member.Connections,
}
party["members"] = append(party["members"].([]aid.JSON), member.GenerateFortnitePartyMember())
}
return party

View File

@ -23,6 +23,7 @@ type Person struct {
BanHistory aid.GenericSyncMap[storage.DB_BanStatus]
Relationships aid.GenericSyncMap[Relationship]
Parties aid.GenericSyncMap[Party]
Pings aid.GenericSyncMap[PartyPing]
}
func NewPerson() *Person {
@ -37,6 +38,10 @@ func NewPerson() *Person {
Profile0Profile: NewProfile("profile0"),
CollectionsProfile: NewProfile("collections"),
CreativeProfile: NewProfile("creative"),
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
Pings: aid.GenericSyncMap[PartyPing]{},
}
}
@ -52,6 +57,10 @@ func NewPersonWithCustomID(id string) *Person {
Profile0Profile: NewProfile("profile0"),
CollectionsProfile: NewProfile("collections"),
CreativeProfile: NewProfile("creative"),
BanHistory: aid.GenericSyncMap[storage.DB_BanStatus]{},
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
Pings: aid.GenericSyncMap[PartyPing]{},
}
}
@ -199,6 +208,7 @@ func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Per
Discord: &databasePerson.Discord,
RefundTickets: databasePerson.RefundTickets,
Relationships: aid.GenericSyncMap[Relationship]{},
Parties: aid.GenericSyncMap[Party]{},
}
for _, ban := range databasePerson.BanHistory {

View File

@ -5,6 +5,11 @@ import (
)
func GetDefaultEngine() []byte {
/*[OnlineSubsystemMcp]
bUsePartySystemV2=true
[OnlineSubsystemMcp.OnlinePartySystemMcpAdapter]
bUsePartySystemV2=true*/
return []byte(`
[OnlineSubsystemMcp.Xmpp]
bUseSSL=false
@ -16,12 +21,6 @@ bUseSSL=false
Protocol=ws
ServerAddr="ws://`+ aid.Config.API.Host + aid.Config.API.Port +`/?"
[OnlineSubsystemMcp]
bUsePartySystemV2=true
[OnlineSubsystemMcp.OnlinePartySystemMcpAdapter]
bUsePartySystemV2=true
[XMPP]
bEnableWebsockets=true