From 4614b135a1f250f3c6e6bbdad1219b7652afe653 Mon Sep 17 00:00:00 2001 From: neckfire Date: Wed, 15 Jul 2026 10:25:52 +0200 Subject: [PATCH] feat: resolve player display names from Steam persona (Steam Web API) Accounts were created as 'Unknown' and the client never sends a name, so the admin panel / friends / squad showed no names. Add SteamWebApiService (GetPlayerSummaries), wired into LoginWithSteam to backfill the DisplayName from the Steam persona when it's still 'Unknown'. Admin gets a LAN-only /admin/backfill-names action (+ button) to backfill existing accounts in one shot. Key read from config SteamWebApiKey (Vault -> env), inert when unset. Co-Authored-By: Claude Opus 4.8 --- .../Controllers/AdminController.cs | 41 +++++++++++++- .../Controllers/ClientController.cs | 18 +++++- .../Services/Steam/SteamWebApiService.cs | 55 +++++++++++++++++++ src/Prospect.Server.Api/Startup.cs | 2 + 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/Prospect.Server.Api/Services/Steam/SteamWebApiService.cs diff --git a/src/Prospect.Server.Api/Controllers/AdminController.cs b/src/Prospect.Server.Api/Controllers/AdminController.cs index 0cd6041..74f1515 100644 --- a/src/Prospect.Server.Api/Controllers/AdminController.cs +++ b/src/Prospect.Server.Api/Controllers/AdminController.cs @@ -4,6 +4,8 @@ using System.Text; using System.Text.Json; using Microsoft.AspNetCore.Mvc; using Prospect.Server.Api.Services.Database; +using Prospect.Server.Api.Services.Database.Models; +using Prospect.Server.Api.Services.Steam; using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Controllers; @@ -18,14 +20,16 @@ public class AdminController : ControllerBase private readonly DbEntityService _entityService; private readonly DbUserDataService _userDataService; private readonly TitleDataService _titleDataService; + private readonly SteamWebApiService _steamWebApi; public AdminController(DbUserService userService, DbEntityService entityService, - DbUserDataService userDataService, TitleDataService titleDataService) + DbUserDataService userDataService, TitleDataService titleDataService, SteamWebApiService steamWebApi) { _userService = userService; _entityService = entityService; _userDataService = userDataService; _titleDataService = titleDataService; + _steamWebApi = steamWebApi; } // ---------- pages ---------- @@ -82,7 +86,8 @@ public class AdminController : ControllerBase } var sb = new StringBuilder(); - sb.Append($""); + sb.Append($""); sb.Append($"

{users.Count} joueur(s)

"); sb.Append(""); foreach (var u in users.OrderBy(u => u.DisplayName)) @@ -179,6 +184,38 @@ public class AdminController : ControllerBase return Html(Layout("TitleData · " + key, $"

{Esc(key)}

" + body)); } + [HttpGet("backfill-names")] + public async Task BackfillNames() + { + if (!IsLan()) return Denied(); + if (!_steamWebApi.Enabled) + return Html(Layout("Backfill", "

Steam Web API non configurée (clé absente).

← Joueurs

")); + + var users = await _userService.GetAllAsync(); + int updated = 0, skipped = 0, failed = 0; + var rows = new StringBuilder(); + foreach (var u in users) + { + if (!string.IsNullOrEmpty(u.DisplayName) && u.DisplayName != "Unknown") { skipped++; continue; } + var steam = u.Auth?.FirstOrDefault(a => a.Type == PlayFabUserAuthType.Steam)?.Key; + if (string.IsNullOrEmpty(steam)) { skipped++; continue; } + + var persona = await _steamWebApi.GetPersonaNameAsync(steam); + if (!string.IsNullOrWhiteSpace(persona)) + { + await _userService.UpdateDisplayNameAsync(u.Id, persona); + updated++; + rows.Append($"
"); + } + else { failed++; } + } + + var body = $"

Backfill des pseudos Steam

{updated} mis à jour · {skipped} ignorés · {failed} échec(s)

" + + (updated > 0 ? $"
DisplayNamePlayFab IdEntity IdAuth
{Esc(u.Id)}{Esc(persona)}
{rows}
PlayFabIdNouveau nom
" : "") + + "

← Joueurs

"; + return Html(Layout("Backfill", body)); + } + // ---------- helpers ---------- private bool IsLan() diff --git a/src/Prospect.Server.Api/Controllers/ClientController.cs b/src/Prospect.Server.Api/Controllers/ClientController.cs index e8c6b00..b31e35a 100644 --- a/src/Prospect.Server.Api/Controllers/ClientController.cs +++ b/src/Prospect.Server.Api/Controllers/ClientController.cs @@ -32,6 +32,7 @@ public class ClientController : Controller private readonly DbEntityService _entityService; private readonly UserDataService _userDataService; private readonly TitleDataService _titleDataService; + private readonly Prospect.Server.Api.Services.Steam.SteamWebApiService _steamWebApi; public ClientController(ILogger logger, IOptions settings, @@ -39,7 +40,8 @@ public class ClientController : Controller DbUserService userService, DbEntityService entityService, UserDataService userDataService, - TitleDataService titleDataService) + TitleDataService titleDataService, + Prospect.Server.Api.Services.Steam.SteamWebApiService steamWebApi) { _settings = settings.Value; _logger = logger; @@ -48,6 +50,7 @@ public class ClientController : Controller _entityService = entityService; _userDataService = userDataService; _titleDataService = titleDataService; + _steamWebApi = steamWebApi; } [AllowAnonymous] @@ -63,6 +66,19 @@ public class ClientController : Controller var userSteamId = ticket.SteamId.ToString(); var user = await _userService.FindOrCreateAsync(PlayFabUserAuthType.Steam, userSteamId); + + // Backfill the display name from the Steam persona when we don't have a real + // one yet (accounts are created as "Unknown"; the client never sends a name). + if (string.IsNullOrEmpty(user.DisplayName) || user.DisplayName == "Unknown") + { + var persona = await _steamWebApi.GetPersonaNameAsync(userSteamId); + if (!string.IsNullOrWhiteSpace(persona)) + { + await _userService.UpdateDisplayNameAsync(user.Id, persona); + user.DisplayName = persona; + } + } + var entity = await _entityService.FindOrCreateAsync(user.Id); var userTicket = _authTokenService.GenerateUser(entity); diff --git a/src/Prospect.Server.Api/Services/Steam/SteamWebApiService.cs b/src/Prospect.Server.Api/Services/Steam/SteamWebApiService.cs new file mode 100644 index 0000000..f6f480e --- /dev/null +++ b/src/Prospect.Server.Api/Services/Steam/SteamWebApiService.cs @@ -0,0 +1,55 @@ +using System.Text.Json; + +namespace Prospect.Server.Api.Services.Steam; + +// Resolves a Steam persona (display) name from a SteamID64 via the Steam Web API. +// PlayFab does this server-side too; our emulator only gets the SteamID from the auth +// ticket, so the real name needs an API key. Inert (no-op) when no key is configured. +public class SteamWebApiService +{ + private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(5) }; + + private readonly string? _apiKey; + private readonly ILogger _logger; + + public SteamWebApiService(IConfiguration configuration, ILogger logger) + { + _apiKey = configuration["SteamWebApiKey"]; + _logger = logger; + } + + public bool Enabled => !string.IsNullOrWhiteSpace(_apiKey); + + public async Task GetPersonaNameAsync(string steamId64) + { + if (!Enabled) + { + return null; + } + + try + { + var url = $"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key={_apiKey}&steamids={steamId64}"; + var json = await Http.GetStringAsync(url); + using var doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("response", out var response) && + response.TryGetProperty("players", out var players) && + players.ValueKind == JsonValueKind.Array) + { + foreach (var player in players.EnumerateArray()) + { + if (player.TryGetProperty("personaname", out var name)) + { + return name.GetString(); + } + } + } + } + catch (Exception e) + { + _logger.LogWarning(e, "Steam GetPlayerSummaries failed for {SteamId}", steamId64); + } + + return null; + } +} diff --git a/src/Prospect.Server.Api/Startup.cs b/src/Prospect.Server.Api/Startup.cs index 96e42a1..fea82b1 100644 --- a/src/Prospect.Server.Api/Startup.cs +++ b/src/Prospect.Server.Api/Startup.cs @@ -30,6 +30,8 @@ public class Startup services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); -- 2.54.0