Start work on reimplementing friends

This commit is contained in:
Eccentric 2024-01-29 23:46:22 +00:00
parent f803490c41
commit d16d9dd3ec
10 changed files with 385 additions and 11 deletions

View File

@ -21,6 +21,15 @@ func (s *GenericSyncMap[T]) Get(key string) (*T, bool) {
return v.(*T), true return v.(*T), true
} }
func (s *GenericSyncMap[T]) MustGet(key string) *T {
v, ok := s.m.Load(key)
if !ok {
return nil
}
return v.(*T)
}
func (s *GenericSyncMap[T]) Delete(key string) { func (s *GenericSyncMap[T]) Delete(key string) {
s.m.Delete(key) s.m.Delete(key)
} }
@ -30,3 +39,8 @@ func (s *GenericSyncMap[T]) Range(f func(key string, value *T) bool) {
return f(key.(string), value.(*T)) return f(key.(string), value.(*T))
}) })
} }
func (s *GenericSyncMap[T]) Has(key string) bool {
_, ok := s.Get(key)
return ok
}

View File

@ -1,15 +1,83 @@
package handlers package handlers
import ( import (
"fmt"
"time"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
p "github.com/ectrc/snow/person"
"github.com/ectrc/snow/socket"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func GetFriendList(c *fiber.Ctx) error { func GetFriendList(c *fiber.Ctx) error {
return c.Status(200).JSON([]aid.JSON{}) person := c.Locals("person").(*p.Person)
result := []aid.JSON{}
person.IncomingRelationships.Range(func(key string, value *p.Relationship[p.RelationshipInboundDirection]) bool {
result = append(result, value.GenerateFortniteFriendEntry())
return true
})
person.OutgoingRelationships.Range(func(key string, value *p.Relationship[p.RelationshipOutboundDirection]) bool {
result = append(result, value.GenerateFortniteFriendEntry())
return true
})
return c.Status(200).JSON(result)
} }
func PostCreateFriend(c *fiber.Ctx) error { func PostCreateFriend(c *fiber.Ctx) error {
person := c.Locals("person").(*p.Person)
wanted := c.Params("wanted")
direction, err := person.CreateRelationship(wanted)
if err != nil {
aid.Print(fmt.Sprintf("Error creating relationship: %s", err.Error()))
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
}
socket.JabberSockets.Range(func(key string, value *socket.Socket[socket.JabberData]) bool {
aid.Print(fmt.Sprintf("Checking socket: %s", key, value))
return true
})
personSocket, found := socket.GetJabberSocketByPersonID(wanted)
aid.Print(fmt.Sprintf("Found socket: %t", found), fmt.Sprintf("Direction: %s", direction))
if found {
payload := aid.JSON{}
switch direction {
case "INBOUND":
payload = personSocket.Person.IncomingRelationships.MustGet(wanted).GenerateFortniteFriendEntry()
case "OUTBOUND":
payload = personSocket.Person.OutgoingRelationships.MustGet(wanted).GenerateFortniteFriendEntry()
}
personSocket.JabberSendMessageToPerson(aid.JSON{
"type": "com.epicgames.friends.core.apiobjects.Friend",
"timestamp": time.Now().Format(time.RFC3339),
"payload": payload,
})
}
friendSocket, found := socket.GetJabberSocketByPersonID(wanted)
aid.Print(fmt.Sprintf("Found friend socket: %t", found), fmt.Sprintf("Direction: %s", direction))
if found {
payload := aid.JSON{}
switch direction {
case "INBOUND":
payload = friendSocket.Person.OutgoingRelationships.MustGet(person.ID).GenerateFortniteFriendEntry()
case "OUTBOUND":
payload = friendSocket.Person.IncomingRelationships.MustGet(person.ID).GenerateFortniteFriendEntry()
}
friendSocket.JabberSendMessageToPerson(aid.JSON{
"type": "com.epicgames.friends.core.apiobjects.Friend",
"timestamp": time.Now().Format(time.RFC3339),
"payload": payload,
})
}
return c.SendStatus(204) return c.SendStatus(204)
} }

View File

@ -13,19 +13,16 @@ func MiddlewareWebsocket(c *fiber.Ctx) error {
return fiber.ErrUpgradeRequired return fiber.ErrUpgradeRequired
} }
var identifier string
var protocol string var protocol string
switch c.Get("Sec-WebSocket-Protocol") { switch c.Get("Sec-WebSocket-Protocol") {
case "xmpp": case "xmpp":
identifier = c.Get("Sec-WebSocket-Key")
protocol = "jabber" protocol = "jabber"
default: default:
protocol = "matchmaking" protocol = "matchmaking"
identifier = uuid.New().String()
} }
c.Locals("identifier", identifier) c.Locals("identifier", uuid.New().String())
c.Locals("protocol", protocol) c.Locals("protocol", protocol)
return c.Next() return c.Next()
@ -45,3 +42,22 @@ func WebsocketConnection(c *websocket.Conn) {
aid.Print("Invalid protocol: " + protocol) aid.Print("Invalid protocol: " + protocol)
} }
} }
func GetConnectedSockets(c *fiber.Ctx) error {
jabber := []socket.Socket[socket.JabberData]{}
socket.JabberSockets.Range(func(key string, value *socket.Socket[socket.JabberData]) bool {
jabber = append(jabber, *value)
return true
})
matchmaking := []socket.Socket[socket.MatchmakerData]{}
socket.MatchmakerSockets.Range(func(key string, value *socket.Socket[socket.MatchmakerData]) bool {
matchmaking = append(matchmaking, *value)
return true
})
return c.Status(200).JSON(aid.JSON{
"jabber": jabber,
"matchmaking": matchmaking,
})
}

View File

@ -129,6 +129,7 @@ func main() {
lightswitch.Get("/service/bulk/status", handlers.GetLightswitchBulkStatus) lightswitch.Get("/service/bulk/status", handlers.GetLightswitchBulkStatus)
snow := r.Group("/snow") snow := r.Group("/snow")
snow.Get("/sockets", handlers.GetConnectedSockets)
snow.Get("/cosmetics", handlers.GetPreloadedCosmetics) snow.Get("/cosmetics", handlers.GetPreloadedCosmetics)
snow.Get("/image/:playlist", handlers.GetPlaylistImage) snow.Get("/image/:playlist", handlers.GetPlaylistImage)

View File

@ -1,6 +1,7 @@
package person package person
import ( import (
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage" "github.com/ectrc/snow/storage"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -17,6 +18,8 @@ type Person struct {
CollectionsProfile *Profile CollectionsProfile *Profile
CreativeProfile *Profile CreativeProfile *Profile
Discord *storage.DB_DiscordPerson Discord *storage.DB_DiscordPerson
OutgoingRelationships aid.GenericSyncMap[Relationship[RelationshipOutboundDirection]]
IncomingRelationships aid.GenericSyncMap[Relationship[RelationshipInboundDirection]]
} }
func NewPerson() *Person { func NewPerson() *Person {
@ -64,7 +67,25 @@ func Find(personId string) *Person {
return nil return nil
} }
return findHelper(person) return findHelper(person, false, true)
}
func FindShallow(personId string) *Person {
if cache == nil {
cache = NewPersonsCacheMutex()
}
cachedPerson := cache.GetPerson(personId)
if cachedPerson != nil {
return cachedPerson
}
person := storage.Repo.GetPersonFromDB(personId)
if person == nil {
return nil
}
return findHelper(person, true, true)
} }
func FindByDisplay(displayName string) *Person { func FindByDisplay(displayName string) *Person {
@ -82,7 +103,25 @@ func FindByDisplay(displayName string) *Person {
return nil return nil
} }
return findHelper(person) return findHelper(person, false, true)
}
func FindByDisplayShallow(displayName string) *Person {
if cache == nil {
cache = NewPersonsCacheMutex()
}
cachedPerson := cache.GetPersonByDisplay(displayName)
if cachedPerson != nil {
return cachedPerson
}
person := storage.Repo.GetPersonByDisplayFromDB(displayName)
if person == nil {
return nil
}
return findHelper(person, true, true)
} }
func FindByDiscord(discordId string) *Person { func FindByDiscord(discordId string) *Person {
@ -100,10 +139,10 @@ func FindByDiscord(discordId string) *Person {
return nil return nil
} }
return findHelper(person) return findHelper(person, false, true)
} }
func findHelper(databasePerson *storage.DB_Person) *Person { func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Person {
athenaProfile := NewProfile("athena") athenaProfile := NewProfile("athena")
commonCoreProfile := NewProfile("common_core") commonCoreProfile := NewProfile("common_core")
commonPublicProfile := NewProfile("common_public") commonPublicProfile := NewProfile("common_public")
@ -155,9 +194,17 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
CollectionsProfile: collectionsProfile, CollectionsProfile: collectionsProfile,
CreativeProfile: creativeProfile, CreativeProfile: creativeProfile,
Discord: &databasePerson.Discord, Discord: &databasePerson.Discord,
OutgoingRelationships: aid.GenericSyncMap[Relationship[RelationshipOutboundDirection]]{},
IncomingRelationships: aid.GenericSyncMap[Relationship[RelationshipInboundDirection]]{},
} }
if !shallow {
person.LoadRelationships()
}
if save {
cache.SavePerson(person) cache.SavePerson(person)
}
return person return person
} }

141
person/relationships.go Normal file
View File

@ -0,0 +1,141 @@
package person
import (
"fmt"
"github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
)
type RelationshipDirection string
type RelationshipInboundDirection RelationshipDirection
const RelationshipInboundDirectionValue RelationshipInboundDirection = "INBOUND"
type RelationshipOutboundDirection RelationshipDirection
const RelationshipOutboundDirectionValue RelationshipOutboundDirection = "OUTBOUND"
type Relationship[T RelationshipInboundDirection | RelationshipOutboundDirection] struct {
Me *Person
Towards *Person
Status string
Direction T
}
func (r *Relationship[T]) ToDatabase() *storage.DB_Relationship {
return &storage.DB_Relationship{
IncomingPersonID: r.Me.ID,
OutgoingPersonID: r.Towards.ID,
Status: r.Status,
}
}
func (r *Relationship[T]) GenerateFortniteFriendEntry() aid.JSON {
return aid.JSON{
"accountId": r.Towards.ID,
"status": r.Status,
"direction": string(r.Direction),
"created": "0000-00-00T00:00:00.000Z",
"favorite": false,
}
}
func (r *Relationship[T]) Save() {
storage.Repo.Storage.SaveRelationship(r.ToDatabase())
}
func (r *Relationship[T]) Delete() {
storage.Repo.Storage.DeleteRelationship(r.ToDatabase())
}
func (p *Person) LoadRelationships() {
incoming := storage.Repo.Storage.GetIncomingRelationships(p.ID)
for _, entry := range incoming {
relationship := &Relationship[RelationshipInboundDirection]{
Status: entry.Status,
Me: p,
Towards: FindShallow(entry.OutgoingPersonID),
Direction: RelationshipInboundDirectionValue,
}
p.IncomingRelationships.Set(entry.OutgoingPersonID, relationship)
}
outgoing := storage.Repo.Storage.GetOutgoingRelationships(p.ID)
for _, entry := range outgoing {
relationship := &Relationship[RelationshipOutboundDirection]{
Status: entry.Status,
Me: p,
Towards: FindShallow(entry.IncomingPersonID),
Direction: RelationshipOutboundDirectionValue,
}
p.OutgoingRelationships.Set(entry.IncomingPersonID, relationship)
}
}
func (p *Person) CreateRelationship(personId string) (string, error) {
if p.ID == personId {
return "", fmt.Errorf("cannot create relationship with yourself")
}
if p.IncomingRelationships.Has(personId) {
return "INBOUND", p.createAcceptInboundRelationship(personId)
}
return "OUTBOUND", p.createOutboundRelationship(personId)
}
func (p *Person) createOutboundRelationship(towards string) error {
towardsPerson := Find(towards)
if towardsPerson == nil {
return fmt.Errorf("person not found")
}
relationship := &Relationship[RelationshipOutboundDirection]{
Me: p,
Towards: towardsPerson,
Status: "PENDING",
Direction: RelationshipOutboundDirectionValue,
}
relationship.Save()
p.OutgoingRelationships.Set(towards, relationship)
tempRelationship := &Relationship[RelationshipInboundDirection]{
Me: towardsPerson,
Towards: p,
Status: "PENDING",
Direction: RelationshipInboundDirectionValue,
}
tempRelationship.Save()
towardsPerson.IncomingRelationships.Set(p.ID, tempRelationship)
return nil
}
func (p *Person) createAcceptInboundRelationship(towards string) error {
towardsPerson := Find(towards)
if towardsPerson == nil {
return fmt.Errorf("person not found")
}
relationship := &Relationship[RelationshipInboundDirection]{
Me: p,
Towards: towardsPerson,
Status: "ACCEPTED",
Direction: RelationshipInboundDirectionValue,
}
relationship.Save()
p.IncomingRelationships.Set(towards, relationship)
tempRelationship := &Relationship[RelationshipOutboundDirection]{
Me: towardsPerson,
Towards: p,
Status: "ACCEPTED",
Direction: RelationshipOutboundDirectionValue,
}
tempRelationship.Save()
towardsPerson.OutgoingRelationships.Set(p.ID, tempRelationship)
return nil
}

View File

@ -2,6 +2,7 @@ package socket
import ( import (
"fmt" "fmt"
"reflect"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
@ -92,3 +93,38 @@ func jabberIqGetHandler(socket *Socket[JabberData], parsed *etree.Document) erro
socket.Connection.WriteMessage(websocket.TextMessage, []byte(`<iq xmlns="jabber:client" type="result" id="`+ parsed.Root().SelectAttr("id").Value +`" from="prod.ol.epicgames.com" to="`+ socket.Data.JabberID +`" />`)) socket.Connection.WriteMessage(websocket.TextMessage, []byte(`<iq xmlns="jabber:client" type="result" id="`+ parsed.Root().SelectAttr("id").Value +`" from="prod.ol.epicgames.com" to="`+ socket.Data.JabberID +`" />`))
return nil return nil
} }
func GetJabberSocketByPersonID(id string) (*Socket[JabberData], bool) {
var found *Socket[JabberData]
JabberSockets.Range(func(key string, socket *Socket[JabberData]) bool {
if socket.Person.ID == id {
found = socket
return false
}
return true
})
return found, found != nil
}
func (s *Socket[T]) JabberSendMessageToPerson(data aid.JSON) {
if reflect.TypeOf(s.Data) != reflect.TypeOf(&JabberData{}) {
return
}
jabberSocket, ok := JabberSockets.Get(s.ID)
if !ok {
aid.Print("jabber socket not found even though it should be")
return
}
aid.Print(`<message xmlns="jabber:client" from="xmpp-admin@prod.ol.epicgames.com" to="`+ jabberSocket.Data.JabberID +`">
<body>`+ string(data.ToBytes()) +`</body>
</message>`)
s.Connection.WriteMessage(1, []byte(`<message xmlns="jabber:client" from="xmpp-admin@prod.ol.epicgames.com" to="`+ jabberSocket.Data.JabberID +`">
<body>`+ string(data.ToBytes()) +`</body>
</message>`))
}

View File

@ -45,6 +45,7 @@ func (s *PostgresStorage) MigrateAll() {
s.Migrate(&DB_PAttribute{}, "Attributes") s.Migrate(&DB_PAttribute{}, "Attributes")
s.Migrate(&DB_DiscordPerson{}, "Discords") s.Migrate(&DB_DiscordPerson{}, "Discords")
s.Migrate(&DB_SeasonStat{}, "Stats") s.Migrate(&DB_SeasonStat{}, "Stats")
s.Migrate(&DB_Relationship{}, "Relationships")
} }
func (s *PostgresStorage) DropTables() { func (s *PostgresStorage) DropTables() {
@ -181,6 +182,26 @@ func (s *PostgresStorage) DeletePerson(personId string) {
Delete(&DB_Person{}, "id = ?", personId) Delete(&DB_Person{}, "id = ?", personId)
} }
func (s *PostgresStorage) GetIncomingRelationships(personId string) []*DB_Relationship {
var dbRelationships []*DB_Relationship
s.Postgres.Model(&DB_Relationship{}).Where("incoming_person_id = ?", personId).Find(&dbRelationships)
return dbRelationships
}
func (s *PostgresStorage) GetOutgoingRelationships(personId string) []*DB_Relationship {
var dbRelationships []*DB_Relationship
s.Postgres.Model(&DB_Relationship{}).Where("outgoing_person_id = ?", personId).Find(&dbRelationships)
return dbRelationships
}
func (s *PostgresStorage) SaveRelationship(relationship *DB_Relationship) {
s.Postgres.Save(relationship)
}
func (s *PostgresStorage) DeleteRelationship(relationship *DB_Relationship) {
s.Postgres.Delete(&DB_Relationship{}, "incoming_person_id = ? AND outgoing_person_id = ?", relationship.IncomingPersonID, relationship.OutgoingPersonID)
}
func (s *PostgresStorage) SaveProfile(profile *DB_Profile) { func (s *PostgresStorage) SaveProfile(profile *DB_Profile) {
s.Postgres.Save(profile) s.Postgres.Save(profile)
} }

View File

@ -9,7 +9,6 @@ type Storage interface {
GetAllPersons() []*DB_Person GetAllPersons() []*DB_Person
GetPersonsCount() int GetPersonsCount() int
TotalVBucks() int TotalVBucks() int
GetPerson(personId string) *DB_Person GetPerson(personId string) *DB_Person
@ -19,6 +18,11 @@ type Storage interface {
SavePerson(person *DB_Person) SavePerson(person *DB_Person)
DeletePerson(personId string) DeletePerson(personId string)
GetIncomingRelationships(personId string) []*DB_Relationship
GetOutgoingRelationships(personId string) []*DB_Relationship
SaveRelationship(relationship *DB_Relationship)
DeleteRelationship(relationship *DB_Relationship)
SaveProfile(profile *DB_Profile) SaveProfile(profile *DB_Profile)
DeleteProfile(profileId string) DeleteProfile(profileId string)
@ -122,6 +126,22 @@ func (r *Repository) DeleteProfile(profileId string) {
r.Storage.DeleteProfile(profileId) r.Storage.DeleteProfile(profileId)
} }
func (r *Repository) GetIncomingRelationships(personId string) []*DB_Relationship {
return r.Storage.GetIncomingRelationships(personId)
}
func (r *Repository) GetOutgoingRelationships(personId string) []*DB_Relationship {
return r.Storage.GetOutgoingRelationships(personId)
}
func (r *Repository) SaveRelationship(relationship *DB_Relationship) {
r.Storage.SaveRelationship(relationship)
}
func (r *Repository) DeleteRelationship(relationship *DB_Relationship) {
r.Storage.DeleteRelationship(relationship)
}
func (r *Repository) SaveItem(item *DB_Item) { func (r *Repository) SaveItem(item *DB_Item) {
r.Storage.SaveItem(item) r.Storage.SaveItem(item)
} }

View File

@ -20,6 +20,16 @@ func (DB_Person) TableName() string {
return "Persons" return "Persons"
} }
type DB_Relationship struct {
OutgoingPersonID string `gorm:"primary_key"`
IncomingPersonID string `gorm:"primary_key"`
Status string
}
func (DB_Relationship) TableName() string {
return "Relationships"
}
type DB_Profile struct { type DB_Profile struct {
ID string `gorm:"primary_key"` ID string `gorm:"primary_key"`
PersonID string PersonID string