mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
355 lines
11 KiB
C#
355 lines
11 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Text.Json;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Accounts.Local;
|
|
|
|
namespace RadicadeServer.API.Config
|
|
{
|
|
[Route("api/settings")]
|
|
[Route("playersettings")]
|
|
public class PlayerSettingsAPI : Controller
|
|
{
|
|
private readonly RecDBContext db;
|
|
|
|
public PlayerSettingsAPI(RecDBContext context)
|
|
{
|
|
db = context;
|
|
}
|
|
|
|
[HttpGet(""), Authorize]
|
|
[HttpGet("v2"), Authorize]
|
|
[HttpGet("playersettings"), Authorize]
|
|
public List<Setting> GetSettings()
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null)
|
|
return new List<Setting>();
|
|
|
|
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
|
|
var list = settings?.DeserializeSettings() ?? GetDefaultSettings();
|
|
ApplyDefaultSettings(list);
|
|
var player = db.Players.AsNoTracking().FirstOrDefault(x => x.AccountId == userId.Value);
|
|
if (player != null && string.IsNullOrWhiteSpace(player.Hashword))
|
|
{
|
|
SetOrReplace(list, "Recroom.AccountCreation.HasCreatedPassword", "False");
|
|
SetOrReplace(list, "Recroom.AccountCreation.HasFinished", "False");
|
|
SetOrReplace(list, "Recroom.OOBE", "0");
|
|
}
|
|
ApplyResponseMetadata(list, userId.Value);
|
|
if (settings == null)
|
|
{
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = userId.Value,
|
|
Settings = list.ToJson()
|
|
});
|
|
db.SaveChanges();
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static void SetOrReplace(List<Setting> list, string key, string value)
|
|
{
|
|
var existing = list.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
|
|
if (existing != null)
|
|
existing.Value = value;
|
|
else
|
|
list.Add(new Setting { Key = key, Value = value });
|
|
}
|
|
|
|
private static void ApplyDefaultSettings(List<Setting> list)
|
|
{
|
|
bool needsOrientation = list.Any(setting =>
|
|
string.Equals(setting.Key, "Radicade.NeedsOrientation", StringComparison.OrdinalIgnoreCase) &&
|
|
(setting.Value == "1" || string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase)));
|
|
foreach (var setting in GetDefaultSettings())
|
|
{
|
|
if (needsOrientation &&
|
|
(string.Equals(setting.Key, "HAS_COMPLETED_ORIENTATION", StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(setting.Key, "OrientationCompletionTime", StringComparison.OrdinalIgnoreCase)))
|
|
continue;
|
|
if (list.All(existing => !string.Equals(existing.Key, setting.Key, StringComparison.OrdinalIgnoreCase)))
|
|
list.Add(setting);
|
|
}
|
|
|
|
SetOrReplace(list, "SplitTestAssignedSegments", "1|{}");
|
|
}
|
|
|
|
private static void ApplyResponseMetadata(List<Setting> list, long accountId)
|
|
{
|
|
for (var i = 0; i < list.Count; i++)
|
|
{
|
|
list[i].Id = i + 1;
|
|
list[i].AccountId = accountId;
|
|
}
|
|
}
|
|
|
|
public const int InviteLinkLifetimeInMinutes = 60;
|
|
|
|
[HttpGet("partyinvite")]
|
|
[HttpGet("~/chat/settings/partyinvite")]
|
|
public IActionResult GetPartyInviteSettings() => Ok(new { InviteLinkLifetimeInMinutes });
|
|
|
|
[HttpPost("v2/set"), Authorize]
|
|
[HttpPost(""), Authorize]
|
|
[HttpPost("playersettings/set"), Authorize]
|
|
public async Task<IActionResult> SetSettingRequest()
|
|
{
|
|
if (User.GetUserId() == null) return Unauthorized();
|
|
var body = await ReadJsonBodyAsync();
|
|
var updates = body.HasValue ? ReadSettings(body.Value) : new List<Setting>();
|
|
if (updates.Count != 1) return BadRequest("Expected one setting with Key and Value.");
|
|
return await SetSetting(updates[0]);
|
|
}
|
|
|
|
[NonAction]
|
|
public async Task<IActionResult> SetSetting(Setting val)
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null)
|
|
return Unauthorized();
|
|
if (val == null || string.IsNullOrWhiteSpace(val.Key) || val.Value == null)
|
|
return BadRequest("Expected a setting with Key and Value.");
|
|
|
|
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
|
|
if (settings == null)
|
|
{
|
|
settings = new PlayerSettings
|
|
{
|
|
AccountId = userId.Value,
|
|
Settings = DBConfig.DefaultSettings2023
|
|
};
|
|
await db.PlayerSettings.AddAsync(settings);
|
|
}
|
|
|
|
var actualSettings = settings.DeserializeSettings();
|
|
if (!await CanSaveOnboardingMarkerAsync(userId.Value, val, actualSettings, new[] { val }))
|
|
return Ok(Array.Empty<object>());
|
|
|
|
SetOrReplace(actualSettings, val.Key, val.Value);
|
|
if (IsOnboardingFinishedMarker(val))
|
|
SetOrReplace(actualSettings, "Radicade.NeedsOrientation", "1");
|
|
|
|
settings.Settings = actualSettings.ToJson();
|
|
await db.SaveChangesAsync();
|
|
|
|
await RecordCodeOfConductAsync(userId.Value, new[] { val }, actualSettings);
|
|
|
|
return Ok(Array.Empty<object>());
|
|
}
|
|
|
|
[HttpPut(""), Authorize]
|
|
[HttpPut("v2"), Authorize]
|
|
[HttpPut("playersettings"), Authorize]
|
|
public async Task<IActionResult> PutSettings()
|
|
{
|
|
var userId = User.GetUserId();
|
|
if (userId == null)
|
|
return Unauthorized();
|
|
|
|
var body = await ReadJsonBodyAsync();
|
|
var updates = body.HasValue ? ReadSettings(body.Value) : new List<Setting>();
|
|
if (updates.Count == 0)
|
|
return BadRequest("No valid settings were supplied.");
|
|
|
|
var settings = db.PlayerSettings.FirstOrDefault(x => x.AccountId == userId.Value);
|
|
if (settings == null)
|
|
{
|
|
settings = new PlayerSettings
|
|
{
|
|
AccountId = userId.Value,
|
|
Settings = DBConfig.DefaultSettings2023
|
|
};
|
|
await db.PlayerSettings.AddAsync(settings);
|
|
}
|
|
|
|
var actualSettings = settings.DeserializeSettings();
|
|
foreach (var update in updates)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(update.Key) || update.Value == null ||
|
|
!await CanSaveOnboardingMarkerAsync(userId.Value, update, actualSettings, updates))
|
|
continue;
|
|
|
|
SetOrReplace(actualSettings, update.Key, update.Value);
|
|
if (IsOnboardingFinishedMarker(update))
|
|
SetOrReplace(actualSettings, "Radicade.NeedsOrientation", "1");
|
|
}
|
|
|
|
settings.Settings = actualSettings.ToJson();
|
|
await db.SaveChangesAsync();
|
|
await RecordCodeOfConductAsync(userId.Value, updates, actualSettings);
|
|
ApplyResponseMetadata(actualSettings, userId.Value);
|
|
|
|
return Ok(actualSettings);
|
|
}
|
|
|
|
private async Task RecordCodeOfConductAsync(
|
|
long accountId,
|
|
IEnumerable<Setting> incomingSettings,
|
|
IEnumerable<Setting> savedSettings)
|
|
{
|
|
bool accepted = incomingSettings.Concat(savedSettings).Any(setting =>
|
|
string.Equals(setting.Key, "Recroom.AccountCreation.AcceptedCodeOfConduct", StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
|
|
if (!accepted) return;
|
|
|
|
var player = await db.Players.FirstOrDefaultAsync(x => x.AccountId == accountId);
|
|
if (player == null || player.CodeOfConductAccepted) return;
|
|
|
|
player.CodeOfConductAccepted = true;
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
private static bool IsOnboardingFinishedMarker(Setting setting) =>
|
|
(string.Equals(setting.Key, "Recroom.OOBE", StringComparison.OrdinalIgnoreCase) && setting.Value == "100") ||
|
|
(string.Equals(setting.Key, "Recroom.AccountCreation.HasFinished", StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
|
|
|
|
private async Task<bool> CanSaveOnboardingMarkerAsync(
|
|
long accountId,
|
|
Setting update,
|
|
IReadOnlyCollection<Setting> savedSettings,
|
|
IReadOnlyCollection<Setting> incomingSettings)
|
|
{
|
|
bool marksPasswordCreated =
|
|
string.Equals(update.Key, "Recroom.AccountCreation.HasCreatedPassword", StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(update.Value, "true", StringComparison.OrdinalIgnoreCase);
|
|
if (!marksPasswordCreated && !IsOnboardingFinishedMarker(update))
|
|
return true;
|
|
|
|
var player = await db.Players.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == accountId);
|
|
if (player == null || string.IsNullOrWhiteSpace(player.Hashword))
|
|
{
|
|
return false;
|
|
}
|
|
if (marksPasswordCreated)
|
|
return true;
|
|
|
|
var allSettings = incomingSettings.Concat(savedSettings).ToList();
|
|
var profile = await db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == accountId);
|
|
bool hasBirthday = player.Birthday.HasValue && player.Birthday.Value.Year > 1900;
|
|
bool hasChosenUsername = allSettings.Any(setting =>
|
|
string.Equals(setting.Key, "Recroom.AccountCreation.HasChosenUsername", StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
|
|
bool hasAcceptedCodeOfConduct = player.CodeOfConductAccepted || allSettings.Any(setting =>
|
|
string.Equals(setting.Key, "Recroom.AccountCreation.AcceptedCodeOfConduct", StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(setting.Value, "true", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (profile == null || string.IsNullOrWhiteSpace(profile.username))
|
|
{
|
|
return false;
|
|
}
|
|
if (!hasChosenUsername)
|
|
{
|
|
return false;
|
|
}
|
|
if (!hasAcceptedCodeOfConduct)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static List<Setting> GetDefaultSettings()
|
|
{
|
|
try
|
|
{
|
|
return DBConfig.DefaultSettings2023.FromJson<List<Setting>>() ?? new List<Setting>();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return new List<Setting>();
|
|
}
|
|
}
|
|
|
|
private async Task<JsonElement?> ReadJsonBodyAsync()
|
|
{
|
|
if (Request.Body == null)
|
|
return null;
|
|
|
|
if (Request.ContentLength == 0)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
if (Request.HasFormContentType)
|
|
{
|
|
var form = await Request.ReadFormAsync();
|
|
return JsonSerializer.SerializeToElement(form.ToDictionary(pair => pair.Key, pair => pair.Value.ToString()));
|
|
}
|
|
using var doc = await JsonDocument.ParseAsync(Request.Body);
|
|
return doc.RootElement.Clone();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static List<Setting> ReadSettings(JsonElement body)
|
|
{
|
|
if (body.ValueKind == JsonValueKind.Array)
|
|
return ReadSettingsArray(body);
|
|
|
|
if (body.ValueKind != JsonValueKind.Object)
|
|
return new List<Setting>();
|
|
|
|
foreach (var key in new[] { "settings", "Settings", "playerSettings", "PlayerSettings" })
|
|
{
|
|
if (body.TryGetProperty(key, out var nested) && nested.ValueKind == JsonValueKind.Array)
|
|
return ReadSettingsArray(nested);
|
|
}
|
|
|
|
var setting = ReadSettingObject(body);
|
|
return setting == null ? new List<Setting>() : new List<Setting> { setting };
|
|
}
|
|
|
|
private static List<Setting> ReadSettingsArray(JsonElement array)
|
|
{
|
|
var settings = new List<Setting>();
|
|
foreach (var item in array.EnumerateArray())
|
|
{
|
|
var setting = ReadSettingObject(item);
|
|
if (setting != null)
|
|
settings.Add(setting);
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
private static Setting? ReadSettingObject(JsonElement item)
|
|
{
|
|
if (item.ValueKind != JsonValueKind.Object)
|
|
return null;
|
|
|
|
var key = ReadString(item, "Key", "key", "Name", "name");
|
|
var value = ReadString(item, "Value", "value");
|
|
if (string.IsNullOrWhiteSpace(key) || value == null)
|
|
return null;
|
|
|
|
return new Setting { Key = key, Value = value };
|
|
}
|
|
|
|
private static string? ReadString(JsonElement item, params string[] names)
|
|
{
|
|
foreach (var name in names)
|
|
{
|
|
if (!item.TryGetProperty(name, out var value))
|
|
continue;
|
|
|
|
return value.ValueKind switch
|
|
{
|
|
JsonValueKind.String => value.GetString(),
|
|
JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False => value.ToString(),
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|