Files
the-cycle/src/Prospect.Server.Api/Services/CloudScript/Functions/GetFriendList.cs
T
neckfireandClaude Opus 4.8 0a68a97291
Build & Deploy / build (push) Successful in 43s
fix(audit léger): stats carrière, présence amis, AddGenericId
- GetPlayerStatisticsRequestsClient: renvoyait vide -> renvoie les stats demandées
  (0 pour l'instant, raid client-hosted non traçable) pour que l'écran carrière s'affiche.
- UpdatePlayerPresenceState: persiste {inMatch,lastSeenUtc}; GetFriendList lit la présence
  et calcule onlineState (online si vu <3min, 2=en raid) au lieu de 0 en dur.
- AddGenericId: persiste l'id de service lié (était un no-op).
(Free-loadout non touché: S3 only, pas de free-loadout en S2.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:02:45 +02:00

91 lines
4.0 KiB
C#

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<FYBaseSocialRequest, object?>
{
private readonly ILogger<GetFriendList> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserDataService _userDataService;
private readonly DbUserService _userService;
public GetFriendList(ILogger<GetFriendList> logger, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, DbUserService userService)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;
_userDataService = userDataService;
_userService = userService;
}
public async Task<object?> 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<string>();
var userData = await _userDataService.FindAsync(userId, userId, new List<string> { "ImportedSteamFriends" });
if (userData.TryGetValue("ImportedSteamFriends", out var rec) && !string.IsNullOrWhiteSpace(rec.Value))
{
try { steamIds = JsonSerializer.Deserialize<List<string>>(rec.Value) ?? new List<string>(); }
catch { /* ignore malformed */ }
}
var friends = new List<object>();
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<string> { "PresenceState" });
if (presenceData.TryGetValue("PresenceState", out var presenceRec) && !string.IsNullOrWhiteSpace(presenceRec.Value))
{
try
{
var presence = JsonSerializer.Deserialize<PlayerPresenceState>(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 };
}
}