From dc02700dc28ee7e1034eac924c2a0e437447f656 Mon Sep 17 00:00:00 2001 From: Jemma Poffinbarger Date: Tue, 5 May 2026 21:09:33 -0500 Subject: [PATCH] feat(grpc): implement getUserData rpcs --- database/3ds/get_mii.go | 47 +++++++++++ database/3ds/get_user_data.go | 83 +++++++++++++++++++ database/wiiu/get_user_data.go | 98 ++++++++++++++++++++++ grpc/get_user_data.go | 145 +++++++++++++++++++++++++++++++++ 4 files changed, 373 insertions(+) create mode 100644 database/3ds/get_mii.go create mode 100644 database/3ds/get_user_data.go create mode 100644 database/wiiu/get_user_data.go create mode 100644 grpc/get_user_data.go diff --git a/database/3ds/get_mii.go b/database/3ds/get_mii.go new file mode 100644 index 0000000..595e8cc --- /dev/null +++ b/database/3ds/get_mii.go @@ -0,0 +1,47 @@ +package database_3ds + +import ( + "database/sql" + + "github.com/PretendoNetwork/friends/database" + "github.com/PretendoNetwork/nex-go/v2/types" + friends_3ds_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-3ds/types" +) + +// GetMii returns the Mii of a specified user +func GetMii(pid uint32) (friends_3ds_types.FriendMii, error) { + friendMii := friends_3ds_types.NewFriendMii() + + rows, err := database.Manager.QueryRow(` + SELECT mii_name, mii_profanity, mii_character_set, mii_data, mii_changed FROM "3ds".user_data WHERE pid=$1`, pid) + if err != nil { + return friendMii, err + } + + var miiName string + var miiProfanity bool + var miiCharacterSet uint8 + var miiData []byte + var changedTime uint64 + + err = rows.Scan(&pid, &miiName, &miiProfanity, &miiCharacterSet, &miiData, &changedTime) + if err != nil { + if err == sql.ErrNoRows { + return friendMii, database.ErrPIDNotFound + } else { + return friendMii, err + } + } + + mii := friends_3ds_types.NewMii() + mii.Name = types.NewString(miiName) + mii.ProfanityFlag = types.NewBool(miiProfanity) + mii.CharacterSet = types.NewUInt8(miiCharacterSet) + mii.MiiData = types.NewBuffer(miiData) + + friendMii.PID = types.NewPID(uint64(pid)) + friendMii.Mii = mii + friendMii.ModifiedAt = types.NewDateTime(changedTime) + + return friendMii, nil +} diff --git a/database/3ds/get_user_data.go b/database/3ds/get_user_data.go new file mode 100644 index 0000000..a04540b --- /dev/null +++ b/database/3ds/get_user_data.go @@ -0,0 +1,83 @@ +package database_3ds + +import ( + "database/sql" + + "github.com/PretendoNetwork/friends/database" + "github.com/PretendoNetwork/nex-go/v2/types" + friends_3ds_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-3ds/types" +) + +// GetUserData returns a data for a specific user +func GetUserData(pid uint32) (friends_3ds_types.FriendPersistentInfo, error) { + userData := friends_3ds_types.NewFriendPersistentInfo() + + row, err := database.Manager.QueryRow(` + SELECT pid, region, area, + language, country, favorite_title, + favorite_title_version, comment, + comment_changed, last_online, mii_changed + FROM "3ds".user_data WHERE pid=$1 + `, pid) + if err != nil { + if err == sql.ErrNoRows { + return userData, database.ErrPIDNotFound + } else { + return userData, err + } + } + + var relationshipType uint8 + + err = row.Scan(&pid, &relationshipType) + if err != nil { + return userData, err + } + + gameKey := friends_3ds_types.NewGameKey() + + var region uint8 + var area uint8 + var language uint8 + var country uint8 + var titleID uint64 + var titleVersion uint16 + var message string + var lastOnlineTime uint64 + var msgUpdateTime uint64 + var miiModifiedAtTime uint64 + + err = row.Scan( + &pid, + ®ion, + &area, + &language, + &country, + &titleID, + &titleVersion, + &message, + &msgUpdateTime, + &lastOnlineTime, + &miiModifiedAtTime, + ) + if err != nil { + return userData, err + } + + gameKey.TitleID = types.NewUInt64(titleID) + gameKey.TitleVersion = types.NewUInt16(titleVersion) + + userData.PID = types.NewPID(uint64(pid)) + userData.Region = types.NewUInt8(region) + userData.Country = types.NewUInt8(country) + userData.Area = types.NewUInt8(area) + userData.Language = types.NewUInt8(language) + userData.Platform = types.NewUInt8(2) // * Always 3DS + userData.GameKey = gameKey + userData.Message = types.NewString(message) + userData.MessageUpdatedAt = types.NewDateTime(msgUpdateTime) + userData.MiiModifiedAt = types.NewDateTime(miiModifiedAtTime) + userData.LastOnline = types.NewDateTime(lastOnlineTime) + + return userData, nil +} diff --git a/database/wiiu/get_user_data.go b/database/wiiu/get_user_data.go new file mode 100644 index 0000000..f62346b --- /dev/null +++ b/database/wiiu/get_user_data.go @@ -0,0 +1,98 @@ +package database_wiiu + +import ( + "database/sql" + + "github.com/PretendoNetwork/friends/database" + "github.com/PretendoNetwork/friends/globals" + "github.com/PretendoNetwork/nex-go/v2/types" + friends_wiiu_types "github.com/PretendoNetwork/nex-protocols-go/v2/friends-wiiu/types" +) + +// GetUserData returns a data for a specific user +func GetUserData(pid uint32) (friends_wiiu_types.FriendInfo, error) { + friendInfo := friends_wiiu_types.NewFriendInfo() + + row, err := database.Manager.QueryRow(` + SELECT + u.comment, u.comment_changed, + u.last_online, + bi.username, bi.unknown, + ai.unknown1, ai.unknown2, + mii.name, mii.unknown1, mii.unknown2, mii.data, mii.unknown_datetime + FROM wiiu.user_data AS u + INNER JOIN wiiu.principal_basic_info AS bi ON bi.pid = $1 + INNER JOIN wiiu.network_account_info AS ai ON ai.pid = $1 + INNER JOIN wiiu.mii AS mii ON mii.pid = $1 + WHERE u.pid=$1 + LIMIT 1 + `, pid) + + if err != nil { + if err == sql.ErrNoRows { + return friendInfo, database.ErrPIDNotFound + } else { + return friendInfo, err + } + } + var date uint64 + var lastOnlineTime uint64 + var commentContents string + var commentChanged uint64 = 0 + var nnid string + var unknown uint8 + var unknown1 uint8 + var unknown2 uint8 + var miiName string + var miiUnknown1 uint8 + var miiUnknown2 uint8 + var miiData []byte + var miiDatetime uint64 + + err = row.Scan(&commentContents, &commentChanged, &lastOnlineTime, &nnid, &unknown, &unknown1, &unknown2, &miiName, &miiUnknown1, &miiUnknown2, &miiData, &miiDatetime) + if err != nil { + return friendInfo, err + } + + comment := friends_wiiu_types.NewComment() + comment.Unknown = types.NewUInt8(0) + comment.Contents = types.NewString(commentContents) + comment.LastChanged = types.NewDateTime(commentChanged) + + mii := friends_wiiu_types.NewMiiV2() + mii.Name = types.NewString(miiName) + mii.Unknown1 = types.NewUInt8(miiUnknown1) + mii.Unknown2 = types.NewUInt8(miiUnknown2) + mii.MiiData = types.NewBuffer(miiData) + mii.Datetime = types.NewDateTime(miiDatetime) + + principalBasicInfo := friends_wiiu_types.NewPrincipalBasicInfo() + principalBasicInfo.PID = types.NewPID(uint64(pid)) + principalBasicInfo.NNID = types.NewString(nnid) + principalBasicInfo.Unknown = types.NewUInt8(unknown) + principalBasicInfo.Mii = mii + + nnaInfo := friends_wiiu_types.NewNNAInfo() + nnaInfo.Unknown1 = types.NewUInt8(unknown1) + nnaInfo.Unknown2 = types.NewUInt8(unknown2) + nnaInfo.PrincipalBasicInfo = principalBasicInfo + + friendInfo.NNAInfo = nnaInfo + + lastOnline := types.NewDateTime(0).Now() + connectedUser, ok := globals.ConnectedUsers.Get(pid) + if ok && connectedUser != nil { + // * Online + friendInfo.Presence = connectedUser.PresenceV2.Copy().(friends_wiiu_types.NintendoPresenceV2) + } else { + // * Offline + lastOnline = types.NewDateTime(lastOnlineTime) // TODO - Change this + } + + friendInfo.Status = comment + friendInfo.BecameFriend = types.NewDateTime(date) + friendInfo.LastOnline = lastOnline + friendInfo.Unknown = types.NewUInt64(0) + + return friendInfo, nil +} diff --git a/grpc/get_user_data.go b/grpc/get_user_data.go new file mode 100644 index 0000000..b56a64e --- /dev/null +++ b/grpc/get_user_data.go @@ -0,0 +1,145 @@ +package grpc + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/PretendoNetwork/friends/database" + database_3ds "github.com/PretendoNetwork/friends/database/3ds" + database_wiiu "github.com/PretendoNetwork/friends/database/wiiu" + "github.com/PretendoNetwork/friends/globals" + pb "github.com/PretendoNetwork/grpc/go/friends/v2" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func (s *gRPCFriendsV2Server) GetUserDataWiiU(ctx context.Context, in *pb.GetUserDataWiiURequest) (*pb.GetUserDataWiiUResponse, error) { + friend, err := database_wiiu.GetUserData(in.GetPid()) + if err != nil { + globals.Logger.Critical(err.Error()) + if err == database.ErrPIDNotFound { + return nil, status.Errorf(codes.NotFound, "PID was not found") + } else { + return nil, status.Errorf(codes.Internal, "Internal error") + } + } + var comment = &pb.Comment{ + Contents: string(friend.Status.Contents), + LastChanged: timestamppb.New(time.Unix(int64(friend.Status.LastChanged.Second()), 0)), + } + var mii = &pb.MiiV2{ + Name: string(friend.NNAInfo.PrincipalBasicInfo.Mii.Name), + MiiData: friend.NNAInfo.PrincipalBasicInfo.Mii.MiiData, + Datetime: timestamppb.New(time.Unix(int64(friend.NNAInfo.PrincipalBasicInfo.Mii.Datetime.Second()), 0)), + } + var principal = &pb.PrincipalBasicInfo{ + Pid: uint32(friend.NNAInfo.PrincipalBasicInfo.PID), + Nnid: string(friend.NNAInfo.PrincipalBasicInfo.NNID), + Mii: mii, + } + var nnaInfo = &pb.NNAInfo{ + PrincipalBasicInfo: principal, + } + var gameKey = &pb.GameKey{ + TitleId: uint64(friend.Presence.GameKey.TitleID), + TitleVersion: uint32(friend.Presence.GameKey.TitleVersion), + } + var presence = &pb.NintendoPresenceV2{ + ChangedFlags: uint32(friend.Presence.ChangedFlags), + Online: bool(friend.Presence.Online), + GameKey: gameKey, + Message: string(friend.Presence.Message), + GameServerId: uint32(friend.Presence.GameServerID), + Pid: uint32(friend.Presence.PID), + GatheringId: uint32(friend.Presence.GatheringID), + ApplicationData: friend.Presence.ApplicationData, + } + var info = &pb.FriendInfoWiiU{ + NnaInfo: nnaInfo, + Presence: presence, + Status: comment, + BecameFriend: timestamppb.New(time.Unix(int64(friend.BecameFriend.Second()), 0)), + LastOnline: timestamppb.New(time.Unix(int64(friend.LastOnline.Second()), 0)), + } + return &pb.GetUserDataWiiUResponse{ + User: info, + }, nil +} + +func (s *gRPCFriendsV2Server) GetUserData3DS(ctx context.Context, in *pb.GetUserData3DSRequest) (*pb.GetUserData3DSResponse, error) { + friend, err := database_3ds.GetUserData(in.GetPid()) + if err != nil { + globals.Logger.Critical(err.Error()) + if err == database.ErrPIDNotFound { + return nil, status.Errorf(codes.NotFound, "PID was not found") + } else { + return nil, status.Errorf(codes.Internal, "Internal error") + } + } + + miiData, err := database_3ds.GetMii(in.GetPid()) + if err != nil { + globals.Logger.Critical(err.Error()) + if err == database.ErrPIDNotFound { + return nil, status.Errorf(codes.NotFound, "PID was not found") + } else { + return nil, status.Errorf(codes.Internal, "Internal error") + } + } + + var gameKey = &pb.GameKey{ + TitleId: uint64(friend.GameKey.TitleID), + TitleVersion: uint32(friend.GameKey.TitleVersion), + } + + var mii = &pb.Mii{ + Name: string(miiData.Mii.Name), + ProfanityFlag: bool(miiData.Mii.ProfanityFlag), + CharacterSet: uint32(miiData.Mii.CharacterSet), + MiiData: miiData.Mii.MiiData, + } + var friendMii = &pb.FriendMii{ + Pid: uint32(miiData.PID), + Mii: mii, + ModifiedAt: timestamppb.New(time.Unix(int64(miiData.ModifiedAt.Second()), 0)), + } + + var presence = &pb.NintendoPresence{} + connectedUser, ok := globals.ConnectedUsers.Get(uint32(friend.PID)) + if ok && connectedUser != nil { + presence.ChangedFlags = uint32(connectedUser.Presence.ChangedFlags) + presence.GameKey = &pb.GameKey{ + TitleId: uint64(connectedUser.Presence.GameKey.TitleID), + TitleVersion: uint32(connectedUser.Presence.GameKey.TitleVersion), + } + presence.Message = string(connectedUser.Presence.Message) + presence.JoinAvailableFlag = uint32(connectedUser.Presence.JoinAvailableFlag) + presence.MatchmakeType = uint32(connectedUser.Presence.MatchmakeType) + presence.JoinGameId = uint32(connectedUser.Presence.JoinGameID) + presence.JoinGameMode = uint32(connectedUser.Presence.JoinGameMode) + presence.OwnerPid = uint32(connectedUser.Presence.OwnerPID) + presence.JoinGroupId = uint32(connectedUser.Presence.JoinGroupID) + presence.ApplicationArg = connectedUser.Presence.ApplicationArg + } + + var info = &pb.FriendInfo3DS{ + Pid: uint32(friend.PID), + Region: uint32(friend.Region), + Country: uint32(friend.Country), + Area: uint32(friend.Area), + Language: uint32(friend.Language), + Platform: uint32(friend.Platform), + Presence: presence, + GameKey: gameKey, + Message: string(friend.Message), + MessageUpdatedAt: timestamppb.New(time.Unix(int64(friend.MessageUpdatedAt.Second()), 0)), + MiiModifiedAt: timestamppb.New(time.Unix(int64(friend.MiiModifiedAt.Second()), 0)), + LastOnline: timestamppb.New(time.Unix(int64(friend.LastOnline.Second()), 0)), + Mii: friendMii, + } + return &pb.GetUserData3DSResponse{ + User: info, + }, nil +}