using System.Text.Json; using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript.Models; using Prospect.Server.Api.Services.Database; using Prospect.Server.Api.Services.Database.Models; using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Services.CloudScript.Functions; [CloudScriptFunction("GetFriendList")] public class GetFriendList : ICloudScriptFunction { private readonly ILogger _logger; private readonly IHttpContextAccessor _httpContextAccessor; private readonly UserDataService _userDataService; private readonly DbUserService _userService; public GetFriendList(ILogger logger, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, DbUserService userService) { _logger = logger; _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; _userService = userService; } public async Task ExecuteAsync(FYBaseSocialRequest request) { var context = _httpContextAccessor.HttpContext; if (context == null) { throw new CloudScriptException("CloudScript was not called within a http request"); } var userId = context.User.FindAuthUserId(); // Resolve the imported Steam friends (persisted by ClientsideFriendsImport) to the // players that actually exist on this server. It's a private server, so only friends // who have logged in here will show up. var steamIds = new List(); var userData = await _userDataService.FindAsync(userId, userId, new List { "ImportedSteamFriends" }); if (userData.TryGetValue("ImportedSteamFriends", out var rec) && !string.IsNullOrWhiteSpace(rec.Value)) { try { steamIds = JsonSerializer.Deserialize>(rec.Value) ?? new List(); } catch { /* ignore malformed */ } } var friends = new List(); if (steamIds.Count > 0) { var players = await _userService.FindManyAsync(PlayFabUserAuthType.Steam, steamIds); var nowUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); foreach (var player in players) { if (player.Id == userId) continue; // never list yourself // Presence persisted by UpdatePlayerPresenceState: online if seen in the last // 3 minutes. EYUserState best-effort: 0 = offline, 1 = online, 2 = in match. var onlineState = 0; var presenceData = await _userDataService.FindAsync(player.Id, player.Id, new List { "PresenceState" }); if (presenceData.TryGetValue("PresenceState", out var presenceRec) && !string.IsNullOrWhiteSpace(presenceRec.Value)) { try { var presence = JsonSerializer.Deserialize(presenceRec.Value); if (presence != null && nowUtc - presence.LastSeenUtc <= 180) { onlineState = presence.InMatch ? 2 : 1; } } catch { /* ignore malformed presence */ } } friends.Add(new { profile = new { playerId = player.Id, displayName = player.DisplayName, avatarUrl = "", }, onlineState, }); } } // NOTE: the exact response shape the client expects is not yet confirmed; this is a // best-effort structure. The log lets us verify resolution while we validate in game. _logger.LogInformation("GetFriendList for {User}: {Count} friend(s) resolved on server", userId, friends.Count); return new { friends }; } }