Steam persona display names + admin backfill #9

Merged
neckfire merged 1 commits from preprod into main 2026-07-15 08:25:53 +00:00
4 changed files with 113 additions and 3 deletions
Showing only changes of commit 4614b135a1 - Show all commits
@@ -4,6 +4,8 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Prospect.Server.Api.Services.Database; 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; using Prospect.Server.Api.Services.UserData;
namespace Prospect.Server.Api.Controllers; namespace Prospect.Server.Api.Controllers;
@@ -18,14 +20,16 @@ public class AdminController : ControllerBase
private readonly DbEntityService _entityService; private readonly DbEntityService _entityService;
private readonly DbUserDataService _userDataService; private readonly DbUserDataService _userDataService;
private readonly TitleDataService _titleDataService; private readonly TitleDataService _titleDataService;
private readonly SteamWebApiService _steamWebApi;
public AdminController(DbUserService userService, DbEntityService entityService, public AdminController(DbUserService userService, DbEntityService entityService,
DbUserDataService userDataService, TitleDataService titleDataService) DbUserDataService userDataService, TitleDataService titleDataService, SteamWebApiService steamWebApi)
{ {
_userService = userService; _userService = userService;
_entityService = entityService; _entityService = entityService;
_userDataService = userDataService; _userDataService = userDataService;
_titleDataService = titleDataService; _titleDataService = titleDataService;
_steamWebApi = steamWebApi;
} }
// ---------- pages ---------- // ---------- pages ----------
@@ -82,7 +86,8 @@ public class AdminController : ControllerBase
} }
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"<form method='get' class='search'><input name='q' placeholder='Rechercher nom / id / steam…' value='{Esc(q ?? "")}'/><button class='btn'>Rechercher</button></form>"); sb.Append($"<form method='get' class='search'><input name='q' placeholder='Rechercher nom / id / steam…' value='{Esc(q ?? "")}'/><button class='btn'>Rechercher</button>" +
"<a class='btn' href='/admin/backfill-names' title='Récupère les pseudos Steam des comptes sans nom'>Backfill pseudos Steam</a></form>");
sb.Append($"<p class='muted'>{users.Count} joueur(s)</p>"); sb.Append($"<p class='muted'>{users.Count} joueur(s)</p>");
sb.Append("<table><thead><tr><th>DisplayName</th><th>PlayFab Id</th><th>Entity Id</th><th>Auth</th><th></th></tr></thead><tbody>"); sb.Append("<table><thead><tr><th>DisplayName</th><th>PlayFab Id</th><th>Entity Id</th><th>Auth</th><th></th></tr></thead><tbody>");
foreach (var u in users.OrderBy(u => u.DisplayName)) foreach (var u in users.OrderBy(u => u.DisplayName))
@@ -179,6 +184,38 @@ public class AdminController : ControllerBase
return Html(Layout("TitleData · " + key, $"<h2 class='mono'>{Esc(key)}</h2>" + body)); return Html(Layout("TitleData · " + key, $"<h2 class='mono'>{Esc(key)}</h2>" + body));
} }
[HttpGet("backfill-names")]
public async Task<IActionResult> BackfillNames()
{
if (!IsLan()) return Denied();
if (!_steamWebApi.Enabled)
return Html(Layout("Backfill", "<p class='muted'>Steam Web API non configurée (clé absente).</p><p><a class='btn' href='/admin/players'>← Joueurs</a></p>"));
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($"<tr><td class='mono small'>{Esc(u.Id)}</td><td>{Esc(persona)}</td></tr>");
}
else { failed++; }
}
var body = $"<h2>Backfill des pseudos Steam</h2><p class='muted'>{updated} mis à jour · {skipped} ignorés · {failed} échec(s)</p>" +
(updated > 0 ? $"<table><thead><tr><th>PlayFabId</th><th>Nouveau nom</th></tr></thead><tbody>{rows}</tbody></table>" : "") +
"<p><a class='btn' href='/admin/players'>← Joueurs</a></p>";
return Html(Layout("Backfill", body));
}
// ---------- helpers ---------- // ---------- helpers ----------
private bool IsLan() private bool IsLan()
@@ -32,6 +32,7 @@ public class ClientController : Controller
private readonly DbEntityService _entityService; private readonly DbEntityService _entityService;
private readonly UserDataService _userDataService; private readonly UserDataService _userDataService;
private readonly TitleDataService _titleDataService; private readonly TitleDataService _titleDataService;
private readonly Prospect.Server.Api.Services.Steam.SteamWebApiService _steamWebApi;
public ClientController(ILogger<ClientController> logger, public ClientController(ILogger<ClientController> logger,
IOptions<PlayFabSettings> settings, IOptions<PlayFabSettings> settings,
@@ -39,7 +40,8 @@ public class ClientController : Controller
DbUserService userService, DbUserService userService,
DbEntityService entityService, DbEntityService entityService,
UserDataService userDataService, UserDataService userDataService,
TitleDataService titleDataService) TitleDataService titleDataService,
Prospect.Server.Api.Services.Steam.SteamWebApiService steamWebApi)
{ {
_settings = settings.Value; _settings = settings.Value;
_logger = logger; _logger = logger;
@@ -48,6 +50,7 @@ public class ClientController : Controller
_entityService = entityService; _entityService = entityService;
_userDataService = userDataService; _userDataService = userDataService;
_titleDataService = titleDataService; _titleDataService = titleDataService;
_steamWebApi = steamWebApi;
} }
[AllowAnonymous] [AllowAnonymous]
@@ -63,6 +66,19 @@ public class ClientController : Controller
var userSteamId = ticket.SteamId.ToString(); var userSteamId = ticket.SteamId.ToString();
var user = await _userService.FindOrCreateAsync(PlayFabUserAuthType.Steam, userSteamId); 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 entity = await _entityService.FindOrCreateAsync(user.Id);
var userTicket = _authTokenService.GenerateUser(entity); var userTicket = _authTokenService.GenerateUser(entity);
@@ -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<SteamWebApiService> _logger;
public SteamWebApiService(IConfiguration configuration, ILogger<SteamWebApiService> logger)
{
_apiKey = configuration["SteamWebApiKey"];
_logger = logger;
}
public bool Enabled => !string.IsNullOrWhiteSpace(_apiKey);
public async Task<string?> 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;
}
}
+2
View File
@@ -30,6 +30,8 @@ public class Startup
services.AddSingleton<UserDataService>(); services.AddSingleton<UserDataService>();
services.AddSingleton<TitleDataService>(); services.AddSingleton<TitleDataService>();
services.AddSingleton<Prospect.Server.Api.Services.Steam.SteamWebApiService>();
services.AddSingleton<DbUserService>(); services.AddSingleton<DbUserService>();
services.AddSingleton<DbEntityService>(); services.AddSingleton<DbEntityService>();