mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace RadicadeServer.API.Compatibility;
|
||||
|
||||
[ApiController]
|
||||
public sealed class CommonQueryAPI : ControllerBase
|
||||
{
|
||||
[HttpGet("/data/commonQuery/userSegment")]
|
||||
public IActionResult GetUserSegment() => Ok(new Dictionary<string, object?>
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
["User_segment_algo_name"] = "none",
|
||||
["User_segment_algo_version"] = "1",
|
||||
["User_segment_value"] = "0",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.API.Storefront;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Avatar;
|
||||
using RadicadeServer.RecNet.Config;
|
||||
using RadicadeServer.RecNet.Keepsakes;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
using RadicadeServer.Utils;
|
||||
|
||||
namespace RadicadeServer.API.Compatibility;
|
||||
|
||||
[ApiController]
|
||||
public sealed class CompatibilityAPI(RecDBContext db): ControllerBase
|
||||
{
|
||||
[HttpGet("/api/progressionEvents/active")]
|
||||
public IActionResult ActiveProgressionEvents()
|
||||
{
|
||||
if (!KeepsakeFeature.Enabled) return NoActiveEvent();
|
||||
|
||||
var keepsakeRooms = KeepsakeRoomConfig.Current;
|
||||
if (!keepsakeRooms.Any || !keepsakeRooms.EventEnabled) return NoActiveEvent();
|
||||
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("RADICADE_PROGRESSION_ACTIVE"), "record", StringComparison.OrdinalIgnoreCase))
|
||||
return Ok(EventRecord(keepsakeRooms));
|
||||
return Ok(keepsakeRooms.EventId);
|
||||
}
|
||||
|
||||
private ContentResult NoActiveEvent()
|
||||
{
|
||||
return Content("null", "application/json");
|
||||
}
|
||||
|
||||
[HttpGet("/api/progressionEvents/event/{eventId:long}")]
|
||||
public IActionResult ProgressionEventDefinition(long eventId) => EventById(eventId, "event");
|
||||
|
||||
[HttpGet("/api/progressionEvents/record/{eventId:long}")]
|
||||
public IActionResult ProgressionEventRecord(long eventId) => EventById(eventId, "record");
|
||||
|
||||
[HttpGet("/api/progressionEvents/{eventId:long}")]
|
||||
public IActionResult ProgressionEvent(long eventId) => EventById(eventId, "event");
|
||||
|
||||
private IActionResult EventById(long eventId, string route)
|
||||
{
|
||||
if (!KeepsakeFeature.Enabled) return NotFound();
|
||||
|
||||
var keepsakeRooms = KeepsakeRoomConfig.Current;
|
||||
if (!keepsakeRooms.Any || !keepsakeRooms.EventEnabled || eventId != keepsakeRooms.EventId) return NotFound();
|
||||
|
||||
return route == "record"
|
||||
? Ok(EventRecord(keepsakeRooms, includeDefinitionCopy: true))
|
||||
: Ok(EventDefinition(keepsakeRooms));
|
||||
}
|
||||
|
||||
private object EventRecord(KeepsakeRoomConfig keepsakeRooms)
|
||||
=> EventRecord(keepsakeRooms, includeDefinitionCopy: false);
|
||||
|
||||
private object EventRecord(KeepsakeRoomConfig keepsakeRooms, bool includeDefinitionCopy)
|
||||
{
|
||||
if (!includeDefinitionCopy) return EventDefinition(keepsakeRooms);
|
||||
|
||||
var accountId = HttpContext?.User?.GetUserId() ?? 0;
|
||||
var categories = db == null ? new List<int>() : db.KeepsakeCollectionRecords
|
||||
.Where(r => r.AccountId == accountId).Select(r => r.KeepsakeCategoryId).ToList();
|
||||
|
||||
return new
|
||||
{
|
||||
_route = "record",
|
||||
AccountId = accountId,
|
||||
Xp = categories.Sum(id => KeepsakeCatalog.Find(id)?.XpValue ?? 0),
|
||||
RewardsCollected = 0,
|
||||
BonusRewardsCollected = 0,
|
||||
GameMinutesToday = 0,
|
||||
XpBoostLastPurchasedAt = (DateTime?)null,
|
||||
};
|
||||
}
|
||||
|
||||
private object EventDefinition(KeepsakeRoomConfig keepsakeRooms)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var end = now.AddYears(1);
|
||||
|
||||
return new
|
||||
{
|
||||
_route = "event",
|
||||
ProgressionEventId = keepsakeRooms.EventId,
|
||||
Name = keepsakeRooms.EventName,
|
||||
IsEnabled = true,
|
||||
StartTime = now.AddDays(-30),
|
||||
EndTime = end,
|
||||
CollectionEndTime = end,
|
||||
Rewards = Array.Empty<object>(),
|
||||
KeepsakeRoomLists = EventKeepsakeRoomLists(keepsakeRooms),
|
||||
};
|
||||
}
|
||||
|
||||
private static string KeepsakeListMode()
|
||||
=> string.Equals(Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_EVENT_LISTS"), "empty", StringComparison.OrdinalIgnoreCase)
|
||||
? "no"
|
||||
: "all";
|
||||
|
||||
private object EventKeepsakeRoomLists(KeepsakeRoomConfig keepsakeRooms)
|
||||
=> string.Equals(Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_EVENT_LISTS"), "empty", StringComparison.OrdinalIgnoreCase)
|
||||
? Array.Empty<object>()
|
||||
: keepsakeRooms.RoomListEntries(keepsakeRooms.AllRooms && db != null
|
||||
? db.Rooms.Select(r => r.RoomId).OrderBy(id => id).ToArray()
|
||||
: keepsakeRooms.ConfiguredRooms);
|
||||
|
||||
[HttpGet("/commerce/purchasecampaign/allcurrent/v2")]
|
||||
public IActionResult CurrentPurchaseCampaigns() => Ok(Array.Empty<object>());
|
||||
|
||||
[HttpGet("/api/apple/musicpromotion/active")]
|
||||
public IActionResult AppleMusicPromotionActive() => Ok(false);
|
||||
|
||||
[HttpGet("/accounts/emojiConfig/whitelistedEmojis")]
|
||||
public IActionResult WhitelistedEmojis() => Ok(ProfileEmojis);
|
||||
|
||||
private static readonly string[] ProfileEmojis =
|
||||
[
|
||||
"😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "🙂",
|
||||
"🙃", "😉", "😍", "🥰", "😘", "😎", "🤓", "🤩", "🥳", "🤔",
|
||||
"😴", "😭", "😱", "😇", "🤠", "🤖", "👻", "👽", "👾", "💀",
|
||||
"😺", "😸", "😹", "😻", "🙈", "🙉", "🙊", "👋", "👍", "👏",
|
||||
"🙌", "💪", "🤝", "🤘", "🤙", "✌️", "❤️", "🧡", "💛", "💚",
|
||||
"💙", "💜", "🖤", "🤍", "🤎", "💕", "💖", "💯", "✨", "⭐",
|
||||
"🌟", "🔥", "🌈", "☀️", "🌙", "⚡", "❄️", "🌊", "🍀", "🌻",
|
||||
"🌸", "🌹", "🌵", "🌲", "🐶", "🐱", "🐭", "🐰", "🦊", "🐻",
|
||||
"🐼", "🐸", "🐵", "🦁", "🐯", "🐨", "🐷", "🐧", "🐢", "🐙",
|
||||
"🦋", "🐝", "🦄", "🐲", "🍎", "🍉", "🍓", "🍒", "🍍", "🥑",
|
||||
"🍕", "🍔", "🍟", "🌮", "🍿", "🍩", "🍪", "🎂", "🍦", "☕",
|
||||
"⚽", "🏀", "🏈", "⚾", "🎾", "🎯", "🎮", "🎲", "🏆", "🎨",
|
||||
"🎭", "🎵", "🎸", "🥁", "🎤", "🎧", "🚀", "🛸", "✈️", "🚗",
|
||||
"🚲", "⛵", "🏠", "🏰", "🏝️", "🎡", "💎", "👑", "🎁", "🎈",
|
||||
"🎉", "🎊", "💡", "📚", "📷", "🔑", "🧩", "🧸", "🏁", "🏳️🌈"
|
||||
];
|
||||
|
||||
[HttpGet("/commerce/reminder/currentTokenBundles/v2")]
|
||||
public IActionResult CurrentTokenBundles() => Ok(Array.Empty<object>());
|
||||
|
||||
[HttpPost("/econ/api/freegifts/v1/sendmultiple")]
|
||||
[HttpPost("/api/freegifts/v1/sendmultiple")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> SendFreeGifts()
|
||||
{
|
||||
var senderId = User.GetUserId();
|
||||
if (senderId is not > 0) return Unauthorized();
|
||||
|
||||
var request = await ReadFreeGiftRequestAsync();
|
||||
var recipients = request.Recipients.Where(id => id > 0).Distinct().ToList();
|
||||
|
||||
if (recipients.Count == 0)
|
||||
return FreeGiftResult(false, "No recipients were given.");
|
||||
if (recipients.Count > FriendotronService.MaxRecipients)
|
||||
return FreeGiftResult(false, $"A friend box can be sent to {FriendotronService.MaxRecipients} players at a time.");
|
||||
if (recipients.Contains(senderId.Value) && !await SelfGiftingAllowedAsync())
|
||||
return FreeGiftResult(false, "You cannot send a friend box to yourself.");
|
||||
|
||||
var known = await db.Players.AsNoTracking()
|
||||
.Where(player => recipients.Contains(player.AccountId))
|
||||
.Select(player => player.AccountId)
|
||||
.ToListAsync();
|
||||
var missing = recipients.FirstOrDefault(id => !known.Contains(id));
|
||||
if (missing != 0)
|
||||
return FreeGiftResult(false, $"Player {missing} does not exist.");
|
||||
|
||||
var lastSent = await LastFriendBoxAsync(senderId.Value);
|
||||
if (lastSent != null && DateTime.UtcNow - lastSent.Value < FriendotronService.Cooldown)
|
||||
return FreeGiftResult(false,
|
||||
$"You can send another friend box in {FriendotronService.FormatRemaining(DateTime.UtcNow, lastSent.Value)}.");
|
||||
|
||||
var boxes = new List<(long Recipient, Gift Box)>();
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
var item = FriendotronService.RollConsumable(DBConfig.DefaultConsumableItems, out var starTier);
|
||||
if (item == null)
|
||||
return FreeGiftResult(false, "No consumable is configured for the friend box.");
|
||||
|
||||
boxes.Add((recipient, FriendotronService.BuildBox(senderId.Value, request.Message, item, starTier)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
await db.Database.CreateExecutionStrategy().ExecuteAsync(async () =>
|
||||
{
|
||||
await using var transaction = db.Database.IsRelational() ? await db.Database.BeginTransactionAsync() : null;
|
||||
foreach (var (recipient, box) in boxes)
|
||||
await StorefrontShared.PersistGiftBoxAsync(db, recipient, box, notify: false);
|
||||
await StampFriendBoxAsync(senderId.Value);
|
||||
if (transaction != null) await transaction.CommitAsync();
|
||||
});
|
||||
|
||||
foreach (var (recipient, box) in boxes)
|
||||
await StorefrontShared.SendGiftPackageNotificationAsync(recipient, box);
|
||||
|
||||
return FreeGiftResult(true, string.Empty);
|
||||
}
|
||||
|
||||
private static OkObjectResult FreeGiftResult(bool success, string error) => new(new Dictionary<string, object?>
|
||||
{
|
||||
["ALOCDGMHFKA"] = success,
|
||||
["FBILGKFKFJE"] = error
|
||||
});
|
||||
|
||||
private sealed class FreeGiftRequest
|
||||
{
|
||||
public List<long> Recipients { get; } = new();
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
private async Task<FreeGiftRequest> ReadFreeGiftRequestAsync()
|
||||
{
|
||||
var request = new FreeGiftRequest();
|
||||
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
var form = await Request.ReadFormAsync();
|
||||
foreach (var key in new[] { "ToPlayerIds", "toPlayerIds", "PlayerIds", "playerIds", "players" })
|
||||
foreach (var value in form[key]) AddRecipients(request, value);
|
||||
request.Message = FirstFilled(form["Message"], form["message"]);
|
||||
return request;
|
||||
}
|
||||
|
||||
string raw;
|
||||
using (var reader = new StreamReader(Request.Body))
|
||||
raw = await reader.ReadToEndAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (JsonNode.Parse(raw) is JsonObject body)
|
||||
{
|
||||
foreach (var key in new[] { "ToPlayerIds", "toPlayerIds", "PlayerIds", "playerIds", "players" })
|
||||
{
|
||||
var node = body[key];
|
||||
if (node is JsonArray array)
|
||||
foreach (var entry in array) AddRecipients(request, entry?.ToString());
|
||||
else if (node != null)
|
||||
AddRecipients(request, node.ToString());
|
||||
}
|
||||
|
||||
request.Message = FirstFilled(Str(body, "Message"), Str(body, "message"));
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in new[] { "ToPlayerIds", "toPlayerIds", "PlayerIds", "playerIds" })
|
||||
foreach (var value in Request.Query[key]) AddRecipients(request, value);
|
||||
request.Message ??= FirstFilled(Request.Query["Message"], Request.Query["message"]);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void AddRecipients(FreeGiftRequest request, string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return;
|
||||
|
||||
foreach (var part in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
if (long.TryParse(part, out var id))
|
||||
request.Recipients.Add(id);
|
||||
}
|
||||
|
||||
private static string? FirstFilled(params string?[] values) =>
|
||||
values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));
|
||||
|
||||
private async Task<bool> SelfGiftingAllowedAsync()
|
||||
{
|
||||
var row = await db.GameConfig.AsNoTracking().FirstOrDefaultAsync(x => x.Key == "Friendotron_AllowSelfGifting");
|
||||
if (row == null || string.IsNullOrWhiteSpace(row.Value)) return true;
|
||||
return !bool.TryParse(row.Value.Trim(), out var allowed) || allowed;
|
||||
}
|
||||
|
||||
private async Task<DateTime?> LastFriendBoxAsync(long accountId)
|
||||
{
|
||||
var key = FriendotronService.CooldownKey(accountId);
|
||||
var row = await db.ServerConfig.AsNoTracking().FirstOrDefaultAsync(x => x.Key == key);
|
||||
if (row == null) return null;
|
||||
|
||||
return DateTime.TryParse(row.Value, CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var stamp)
|
||||
? stamp
|
||||
: null;
|
||||
}
|
||||
|
||||
private async Task StampFriendBoxAsync(long accountId)
|
||||
{
|
||||
var key = FriendotronService.CooldownKey(accountId);
|
||||
var value = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture);
|
||||
|
||||
var row = await db.ServerConfig.FirstOrDefaultAsync(x => x.Key == key);
|
||||
if (row == null) db.ServerConfig.Add(new ServerConfigVal(key, value));
|
||||
else row.Value = value;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[HttpPut("/gamelogs/reports/{reportId}/userinfo")]
|
||||
public IActionResult ReportUserInfo(string reportId) => Ok();
|
||||
|
||||
[HttpDelete("/auth/cachedlogin/current"), Authorize]
|
||||
public async Task<IActionResult> DeleteCurrentCachedLogin()
|
||||
{
|
||||
var accountId = User.GetUserId();
|
||||
var platformId = User.GetPlatId();
|
||||
var rawPlatform = User.FindFirst("rn.plat")?.Value;
|
||||
if (accountId is not > 0 || string.IsNullOrWhiteSpace(platformId) ||
|
||||
!int.TryParse(rawPlatform, out var platformValue) ||
|
||||
!Enum.IsDefined(typeof(RadicadeServer.RecNet.Enums.Players.Platforms), platformValue) ||
|
||||
platformValue == (int)RadicadeServer.RecNet.Enums.Players.Platforms.All)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
var cachedLogins = await db.CachedLogins
|
||||
.Where(login => login.accountId == accountId.Value &&
|
||||
login.platform == (RadicadeServer.RecNet.Enums.Players.Platforms)platformValue &&
|
||||
login.platformId == platformId)
|
||||
.ToListAsync();
|
||||
if (cachedLogins.Count > 0)
|
||||
{
|
||||
db.CachedLogins.RemoveRange(cachedLogins);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("/roomcomments/comments/create/{roomId:long}"), Authorize]
|
||||
public async Task<IActionResult> CreateRoomComment(long roomId)
|
||||
{
|
||||
var authorId = User.GetUserId();
|
||||
if (authorId == null) return Unauthorized(new { success = false, error = "Unauthorized" });
|
||||
|
||||
var body = await ReadCommentBodyAsync();
|
||||
var text = body.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return BadRequest(new { success = false, error = "Comment is required" });
|
||||
}
|
||||
|
||||
var comment = new RoomComment
|
||||
{
|
||||
RoomId = roomId,
|
||||
CreatorAccountId = authorId.Value,
|
||||
Comment = text,
|
||||
CommentType = string.IsNullOrWhiteSpace(body.Type) ? "Feedback" : body.Type!.Trim(),
|
||||
PositionX = body.X,
|
||||
PositionY = body.Y,
|
||||
PositionZ = body.Z,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await db.RoomComments.AddAsync(comment);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var roomExists = await db.Rooms.AnyAsync(x => x.RoomId == roomId);
|
||||
|
||||
return Ok(new { success = true, message = string.Empty, comment_id = comment.Id });
|
||||
}
|
||||
|
||||
private sealed class CommentBody
|
||||
{
|
||||
public string? Text { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public float? X { get; set; }
|
||||
public float? Y { get; set; }
|
||||
public float? Z { get; set; }
|
||||
}
|
||||
|
||||
private async Task<CommentBody> ReadCommentBodyAsync()
|
||||
{
|
||||
var result = new CommentBody();
|
||||
|
||||
var query = Request.Query;
|
||||
result.Text = FirstNonEmpty(query["comment"], query["Comment"], query["commentText"], query["CommentText"],
|
||||
query["text"], query["body"]);
|
||||
result.Type = FirstNonEmpty(query["commentType"], query["CommentType"], query["type"]);
|
||||
result.X = TryFloat(query["x"], query["positionX"], query["PositionX"]);
|
||||
result.Y = TryFloat(query["y"], query["positionY"], query["PositionY"]);
|
||||
result.Z = TryFloat(query["z"], query["positionZ"], query["PositionZ"]);
|
||||
|
||||
var formKeys = string.Empty;
|
||||
if (Request.HasFormContentType)
|
||||
{
|
||||
var form = await Request.ReadFormAsync();
|
||||
formKeys = string.Join(",", form.Keys);
|
||||
result.Text = FirstNonEmpty(result.Text,
|
||||
FirstNonEmpty(form["comment"], form["Comment"], form["commentText"], form["text"], form["body"],
|
||||
form["message"], form["Message"], form["content"], form["Content"]));
|
||||
result.Type = FirstNonEmpty(result.Type,
|
||||
FirstNonEmpty(form["commentType"], form["CommentType"], form["type"]));
|
||||
result.X ??= TryFloat(FirstNonEmpty(form["x"], form["positionX"], form["PositionX"]));
|
||||
result.Y ??= TryFloat(FirstNonEmpty(form["y"], form["positionY"], form["PositionY"]));
|
||||
result.Z ??= TryFloat(FirstNonEmpty(form["z"], form["positionZ"], form["PositionZ"]));
|
||||
}
|
||||
|
||||
var raw = string.Empty;
|
||||
if (!Request.HasFormContentType && string.IsNullOrWhiteSpace(result.Text))
|
||||
{
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
raw = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(raw)) return result;
|
||||
|
||||
try
|
||||
{
|
||||
var node = JsonNode.Parse(raw);
|
||||
if (node is JsonValue value && value.TryGetValue<string>(out var bare))
|
||||
{
|
||||
result.Text = FirstNonEmpty(result.Text, bare);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (node is not JsonObject obj) return result;
|
||||
|
||||
result.Text = FirstNonEmpty(result.Text, FirstNonEmpty(
|
||||
Str(obj, "Comment"), Str(obj, "comment"),
|
||||
Str(obj, "CommentText"), Str(obj, "commentText"),
|
||||
Str(obj, "Text"), Str(obj, "text"),
|
||||
Str(obj, "Message"), Str(obj, "message"),
|
||||
Str(obj, "Content"), Str(obj, "content")));
|
||||
|
||||
result.Type = FirstNonEmpty(result.Type,
|
||||
FirstNonEmpty(Str(obj, "CommentType"), Str(obj, "commentType"), Str(obj, "Type"), Str(obj, "type")));
|
||||
|
||||
var position = obj["Position"] ?? obj["position"];
|
||||
if (position is JsonObject pos)
|
||||
{
|
||||
result.X ??= TryFloat(Str(pos, "x"), Str(pos, "X"));
|
||||
result.Y ??= TryFloat(Str(pos, "y"), Str(pos, "Y"));
|
||||
result.Z ??= TryFloat(Str(pos, "z"), Str(pos, "Z"));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.X ??= TryFloat(Str(obj, "x"), Str(obj, "positionX"));
|
||||
result.Y ??= TryFloat(Str(obj, "y"), Str(obj, "positionY"));
|
||||
result.Z ??= TryFloat(Str(obj, "z"), Str(obj, "positionZ"));
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
result.Text = raw.Trim();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string? Str(JsonObject obj, string key)
|
||||
{
|
||||
var node = obj[key];
|
||||
if (node == null) return null;
|
||||
try
|
||||
{
|
||||
if (node is JsonValue value && value.TryGetValue<string>(out var text)) return text;
|
||||
return node.ToJsonString().Trim('"');
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? FirstNonEmpty(params string?[] values)
|
||||
=> values.FirstOrDefault(v => !string.IsNullOrWhiteSpace(v));
|
||||
|
||||
private static float? TryFloat(params string?[] values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
if (float.TryParse(value, System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var parsed))
|
||||
return parsed;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user