Files
mochaisvibecoded 42f7f57b72 :3
2026-08-24 17:00:26 -04:00

1231 lines
47 KiB
C#

using System.Text.Json;
using Mocha2023.Auth;
using Mocha2023.Classes.DBs;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Mocha2023.Classes.DBs.DBClasses.PlayerDBClasses;
using static Mocha2023.Classes.DBs.DBClasses.RoomDBClasses;
namespace Mocha2023.Controllers
{
[ApiController]
[Mocha2023.Classes.ApiProtection]
public sealed class CompatibilityController : ControllerBase
{
[HttpGet("/roomserver/showcase/{accountId:long}")]
public IActionResult GetRoomShowcase(long accountId)
{
if (PlayerDB.Players.FindById(accountId) == null)
return NotFound();
long[] roomIds = RoomDB.Rooms.Find(room =>
room.CreatorAccountId == accountId && room.State != RoomState.MarkedForDelete)
.OrderByDescending(room => room.CreatedAt)
.Take(20)
.Select(room => room.RoomId)
.ToArray();
return Ok(roomIds);
}
[HttpGet("/rooms/{roomId:long}")]
public IActionResult GetLegacyRoom(long roomId)
{
var room = RoomDB.GetRoom(roomId);
return room == null ? NotFound() : Ok(RoomDB.PrepareRoomForClient(room));
}
[HttpGet("/api/avatar/v2/{accountId:long}")]
public IActionResult GetAccountAvatar(long accountId)
{
var player = PlayerDB.Players.FindById(accountId);
return player?.Player == null
? NotFound()
: Ok(player.Player.PlayerExtra?.Avatar ?? new Avatar());
}
[HttpGet("/api/inventions/v2/search")]
[HttpGet("/api/inventions/v1/search")]
public IActionResult SearchInventions(
[FromQuery] string? query = null,
[FromQuery] string? q = null,
[FromQuery] string? tag = null,
[FromQuery] int skip = 0,
[FromQuery] int take = 100)
{
string? search = string.IsNullOrWhiteSpace(query) ? q : query;
var rows = CreatorFeatureDB.SearchInventions(
search,
tag: tag,
skip: skip,
take: take)
.Select(CreatorFeatureDB.ToClientInvention)
.ToArray();
return Ok(rows);
}
[HttpGet("/api/inventions/v1/fromcreators")]
public IActionResult GetInventionsFromCreators(
[FromQuery] long creatorAccountId = 0,
[FromQuery] long accountId = 0,
[FromQuery] long creatorPlayerId = 0,
[FromQuery] int skip = 0,
[FromQuery] int take = 100)
{
long creatorId = creatorAccountId > 0
? creatorAccountId
: accountId > 0
? accountId
: creatorPlayerId;
var rows = CreatorFeatureDB.SearchInventions(
creatorAccountId: creatorId > 0 ? creatorId : null,
skip: skip,
take: take)
.Select(CreatorFeatureDB.ToClientInvention)
.ToArray();
return Ok(rows);
}
[HttpGet("/api/inventions/v1/featureddormskins")]
public IActionResult GetFeaturedDormSkins(
[FromQuery] int skip = 0,
[FromQuery] int take = 100)
{
var rows = CreatorFeatureDB.SearchInventions(
tag: "Dorm Skin",
skip: skip,
take: take)
.Select(CreatorFeatureDB.ToClientInvention)
.ToArray();
return Ok(rows);
}
[HttpGet("/api/inventions/v2/mine")]
[HttpGet("/api/inventions/v1/mine")]
public IActionResult GetMyInventions(
[FromQuery] int skip = 0,
[FromQuery] int take = 100)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
var rows = CreatorFeatureDB.SearchInventions(
creatorAccountId: accountId.Value,
includeUnpublished: true,
skip: skip,
take: take)
.Select(CreatorFeatureDB.ToClientInvention)
.ToArray();
return Ok(rows);
}
[HttpGet("/api/inventions/v1/batch")]
[HttpGet("/api/inventions/v2/batch")]
[HttpGet("/api/inventions/v3/batch")]
[HttpPost("/api/inventions/v1/batch")]
[HttpPost("/api/inventions/v2/batch")]
[HttpPost("/api/inventions/v3/batch")]
[RequestSizeLimit(128 * 1024)]
public async Task<IActionResult> GetInventionBatch()
{
long? callerAccountId = AuthStuff.GetPlayerId(Request);
List<long> requestedIds = ReadInventionIdsFromQuery();
if (Request.HasFormContentType)
{
IFormCollection form = await Request.ReadFormAsync(
HttpContext.RequestAborted);
foreach ((string key, Microsoft.Extensions.Primitives.StringValues values)
in form)
{
string normalizedKey = key.TrimEnd('[', ']').ToLowerInvariant();
if (normalizedKey is not "ids" and not "id" and
not "inventionids" and not "inventionid" and
not "inventions")
{
continue;
}
foreach (string? raw in values)
AddLongValues(requestedIds, raw);
}
}
else if ((Request.ContentLength ?? 0) > 0)
{
try
{
using JsonDocument document = await JsonDocument.ParseAsync(
Request.Body,
cancellationToken: HttpContext.RequestAborted);
AddLongValues(requestedIds, document.RootElement);
}
catch (JsonException)
{
return BadRequest(new
{
Success = false,
Error = "invalid_invention_batch"
});
}
}
if (requestedIds.Count == 0)
return Ok(Array.Empty<object>());
object[] rows = requestedIds
.Distinct()
.Take(100)
.Select(CreatorFeatureDB.GetInvention)
.Where(value => value != null)
.Where(value => value!.IsPublished ||
(callerAccountId.HasValue &&
value.CreatorAccountId == callerAccountId.Value))
.Select(value => CreatorFeatureDB.ToClientInvention(value!))
.ToArray();
return Ok(rows);
}
[HttpGet("/api/inventions/{inventionId:long}")]
[HttpGet("/api/inventions/v1/{inventionId:long}")]
[HttpGet("/api/inventions/v2/{inventionId:long}")]
[HttpGet("/api/inventions/v3/{inventionId:long}")]
public IActionResult GetInvention(long inventionId)
{
var invention = CreatorFeatureDB.GetInvention(inventionId);
return invention == null
? NotFound()
: Ok(CreatorFeatureDB.ToClientInvention(invention));
}
[HttpGet("/api/inventions/{inventionId:long}/versions")]
[HttpGet("/api/inventions/v1/{inventionId:long}/versions")]
[HttpGet("/api/inventions/v2/{inventionId:long}/versions")]
[HttpGet("/api/inventions/v3/{inventionId:long}/versions")]
public IActionResult GetInventionVersions(long inventionId)
{
CreatorFeatureDB.InventionRecord? invention =
CreatorFeatureDB.GetInvention(inventionId);
if (invention == null)
return NotFound();
object? version = CreatorFeatureDB.ToClientInventionVersion(invention);
return version == null
? StatusCode(StatusCodes.Status409Conflict,
new { Success = false, Error = "invention_object_blob_missing" })
: Ok(new[] { version });
}
[HttpGet("/api/inventions/{inventionId:long}/versions/{versionNumber:int}")]
[HttpGet("/api/inventions/{inventionId:long}/version/{versionNumber:int}")]
[HttpGet("/api/inventions/{inventionId:long}/{versionNumber:int}")]
[HttpGet("/api/inventions/v1/{inventionId:long}/versions/{versionNumber:int}")]
[HttpGet("/api/inventions/v2/{inventionId:long}/versions/{versionNumber:int}")]
[HttpGet("/api/inventions/v3/{inventionId:long}/versions/{versionNumber:int}")]
public IActionResult GetSpecificInventionVersion(
long inventionId,
int versionNumber)
{
CreatorFeatureDB.InventionRecord? invention =
CreatorFeatureDB.GetInvention(inventionId);
if (invention == null)
return NotFound();
if (versionNumber > 0 && versionNumber != Math.Max(1, invention.Version))
return NotFound();
object? version = CreatorFeatureDB.ToClientInventionVersion(invention);
return version == null
? StatusCode(StatusCodes.Status409Conflict,
new { Success = false, Error = "invention_object_blob_missing" })
: Ok(version);
}
[HttpGet("/api/inventions/v1/get")]
[HttpGet("/api/inventions/v2/get")]
public IActionResult GetInventionFromQuery(
[FromQuery] long inventionId = 0,
[FromQuery] long id = 0)
{
long resolvedId = inventionId > 0 ? inventionId : id;
var invention = CreatorFeatureDB.GetInvention(resolvedId);
return invention == null
? NotFound()
: Ok(CreatorFeatureDB.ToClientInvention(invention));
}
[HttpGet("/api/inventions/v1")]
[HttpGet("/api/inventions/v2")]
public IActionResult GetInventionDetailsFromRoot(
[FromQuery] long inventionId = 0,
[FromQuery] long id = 0)
{
long resolvedId = inventionId > 0 ? inventionId : id;
var invention = CreatorFeatureDB.GetInvention(resolvedId);
return invention == null
? NotFound(new { Success = false, Error = "invention_not_found" })
: Ok(CreatorFeatureDB.ToClientInvention(invention));
}
[HttpGet("/api/inventions/v1/details")]
[HttpGet("/api/inventions/v2/details")]
public IActionResult GetInventionTagDetails(
[FromQuery] long inventionId = 0,
[FromQuery] long id = 0)
{
long resolvedId = inventionId > 0 ? inventionId : id;
var invention = CreatorFeatureDB.GetInvention(resolvedId);
if (invention == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
object client = CreatorFeatureDB.ToClientInvention(invention);
CreatorFeatureDB.TryGetInventionBlob(
invention,
out _,
out string blobPath,
out string blobName,
out string blobHash,
out long blobSize);
object? currentVersion =
CreatorFeatureDB.ToClientInventionVersion(invention);
bool canSpawn = CreatorFeatureDB.HasValidInventionBlob(invention);
return Ok(new
{
InventionId = invention.InventionId,
Id = invention.InventionId,
Tags = invention.Tags ?? new List<string>(),
TagNames = invention.Tags ?? new List<string>(),
Version = Math.Max(1, invention.Version),
CurrentVersion = currentVersion,
InventionVersion = currentVersion,
CanSpawn = canSpawn,
DataBlob = blobName,
DataBlobPath = blobPath,
DataBlobHash = blobHash,
DataBlobSize = blobSize,
Value = client,
Invention = client
});
}
[HttpGet("/api/inventions/v1/personaldetails/{inventionId:long}")]
[HttpGet("/api/inventions/v2/personaldetails/{inventionId:long}")]
public IActionResult GetPersonalInventionDetails(long inventionId)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
var invention = CreatorFeatureDB.GetInvention(inventionId);
if (invention == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
bool isCreator = invention.CreatorAccountId == accountId.Value;
object client = CreatorFeatureDB.ToClientInvention(invention);
return Ok(new
{
InventionId = invention.InventionId,
Id = invention.InventionId,
IsCreator = isCreator,
IsOwner = isCreator,
IsSaved = isCreator,
CanEdit = isCreator,
CanDelete = isCreator,
CanSpawn = CreatorFeatureDB.HasValidInventionBlob(invention),
Cheered = CreatorFeatureDB.IsInventionCheered(
invention.InventionId,
accountId.Value),
IsCheered = CreatorFeatureDB.IsInventionCheered(
invention.InventionId,
accountId.Value),
CheerCount = CreatorFeatureDB.GetInventionCheerCount(
invention.InventionId),
IsPublished = invention.IsPublished,
Version = Math.Max(1, invention.Version),
Value = client,
Invention = client
});
}
[HttpGet("/api/inventions/v1/personaldetails")]
[HttpGet("/api/inventions/v2/personaldetails")]
public IActionResult GetPersonalInventionDetailsFromQuery(
[FromQuery] long inventionId = 0,
[FromQuery] long id = 0) =>
GetPersonalInventionDetails(inventionId > 0 ? inventionId : id);
[HttpGet("/api/inventions/v1/cheer")]
[HttpGet("/api/inventions/v2/cheer")]
[HttpGet("/api/inventions/v1/{inventionId:long}/cheer")]
[HttpGet("/api/inventions/v2/{inventionId:long}/cheer")]
public IActionResult GetInventionCheer(long inventionId = 0)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
inventionId = ResolveInventionId(inventionId, Request.Query);
if (inventionId <= 0)
return BadRequest(new { Success = false, Error = "invention_id_required" });
if (CreatorFeatureDB.GetInvention(inventionId) == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
bool cheered = CreatorFeatureDB.IsInventionCheered(
inventionId,
accountId.Value);
return Ok(new
{
InventionId = inventionId,
Id = inventionId,
Cheered = cheered,
IsCheered = cheered,
CheerCount = CreatorFeatureDB.GetInventionCheerCount(inventionId)
});
}
[HttpPost("/api/inventions/v1/cheer")]
[HttpPut("/api/inventions/v1/cheer")]
[HttpDelete("/api/inventions/v1/cheer")]
[HttpPost("/api/inventions/v2/cheer")]
[HttpPut("/api/inventions/v2/cheer")]
[HttpDelete("/api/inventions/v2/cheer")]
[HttpPost("/api/inventions/v1/{inventionId:long}/cheer")]
[HttpPut("/api/inventions/v1/{inventionId:long}/cheer")]
[HttpDelete("/api/inventions/v1/{inventionId:long}/cheer")]
[HttpPost("/api/inventions/v2/{inventionId:long}/cheer")]
[HttpPut("/api/inventions/v2/{inventionId:long}/cheer")]
[HttpDelete("/api/inventions/v2/{inventionId:long}/cheer")]
[RequestSizeLimit(16 * 1024)]
public async Task<IActionResult> SetInventionCheer(long inventionId = 0)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
bool? requestedState = ReadBooleanValue(
Request.Query,
"cheer", "cheered", "isCheered", "state", "value");
inventionId = ResolveInventionId(inventionId, Request.Query);
if (Request.HasFormContentType)
{
IFormCollection form = await Request.ReadFormAsync(
HttpContext.RequestAborted);
inventionId = ResolveInventionId(inventionId, form);
requestedState ??= ReadBooleanValue(
form,
"cheer", "cheered", "isCheered", "state", "value");
}
else if ((Request.ContentLength ?? 0) > 0)
{
try
{
using JsonDocument body = await JsonDocument.ParseAsync(
Request.Body,
cancellationToken: HttpContext.RequestAborted);
inventionId = ResolveInventionId(inventionId, body.RootElement);
requestedState ??= ReadBooleanValue(body.RootElement);
}
catch (JsonException)
{
return BadRequest(new
{
Success = false,
Error = "invalid_cheer_payload"
});
}
}
if (inventionId <= 0)
return BadRequest(new { Success = false, Error = "invention_id_required" });
if (CreatorFeatureDB.GetInvention(inventionId) == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
bool cheered = HttpMethods.IsDelete(Request.Method)
? false
: requestedState ?? true;
CreatorFeatureDB.SetInventionCheer(
inventionId,
accountId.Value,
cheered);
return Ok(new
{
Success = true,
InventionId = inventionId,
Id = inventionId,
Cheered = cheered,
IsCheered = cheered,
CheerCount = CreatorFeatureDB.GetInventionCheerCount(inventionId)
});
}
[HttpGet("/api/storefronts/v1/buyInvention")]
[HttpPost("/api/storefronts/v1/buyInvention")]
[HttpGet("/api/storefronts/v2/buyInvention")]
[HttpPost("/api/storefronts/v2/buyInvention")]
[HttpGet("/api/storefronts/v3/buyInvention")]
[HttpPost("/api/storefronts/v3/buyInvention")]
[RequestSizeLimit(64 * 1024)]
public async Task<IActionResult> BuyInvention(
[FromQuery] long inventionId = 0,
[FromQuery] long id = 0)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
long requestedId = inventionId > 0 ? inventionId : id;
requestedId = ResolveInventionId(requestedId, Request.Query);
if (Request.HasFormContentType)
{
IFormCollection form = await Request.ReadFormAsync(
HttpContext.RequestAborted);
requestedId = ResolveInventionId(requestedId, form);
}
else if ((Request.ContentLength ?? 0) > 0)
{
try
{
using JsonDocument body = await JsonDocument.ParseAsync(
Request.Body,
cancellationToken: HttpContext.RequestAborted);
requestedId = ResolveInventionId(requestedId, body.RootElement);
}
catch (JsonException)
{
return BadRequest();
}
}
CreatorFeatureDB.InventionRecord? invention =
CreatorFeatureDB.GetInvention(requestedId);
if (invention == null)
return NotFound();
if (!invention.IsPublished && invention.CreatorAccountId != accountId.Value)
return StatusCode(403);
if (!CreatorFeatureDB.HasValidInventionBlob(invention))
return StatusCode(StatusCodes.Status409Conflict);
if (!CreatorFeatureDB.RecordInventionPurchase(
accountId.Value,
invention.InventionId,
pricePaid: 0))
{
return BadRequest();
}
int balance = PlayerDB.GetCurrencyBalance(
accountId.Value,
CurrencyType.RecCenterTokens);
object inventionValue = CreatorFeatureDB.ToClientInvention(invention);
object? versionValue = CreatorFeatureDB.ToClientInventionVersion(invention);
if (versionValue == null)
{
return StatusCode(
StatusCodes.Status409Conflict,
new
{
Success = false,
Error = "invention_object_blob_missing",
InventionId = invention.InventionId
});
}
var inventionResponse = new
{
Result = 0,
Success = true,
Invention = inventionValue,
InventionDetails = inventionValue,
InventionVersion = versionValue,
CurrentVersion = versionValue,
Version = versionValue
};
int balanceType = (int)BalanceType.NonPlayStationNonPurchasedP2P;
var balanceUpdateResponse = new
{
Balance = (long)balance,
CurrencyType = (int)CurrencyType.RecCenterTokens,
BalanceType = balanceType,
BalanceAddType = balanceType,
BalanceUpdates = PlayerDB.GetAllCurrencyBalances(accountId.Value)
.Select(entry => new
{
Result = 0,
PurchaseResult = 0,
Data = entry.CurrencyType == CurrencyType.RecCenterTokens
? (object)inventionValue
: Array.Empty<object>(),
Invention = entry.CurrencyType == CurrencyType.RecCenterTokens
? inventionValue
: null
})
.ToArray()
};
Response.Headers["X-Mocha-Invention-Id"] = invention.InventionId.ToString();
Response.Headers["X-Mocha-Token-Balance"] = balance.ToString();
Console.WriteLine(
$"[INVENTION PURCHASE] account={accountId.Value} " +
$"invention={invention.InventionId} result=Success balance={balance}");
return Ok(new
{
Success = true,
InventionResponse = inventionResponse,
BalanceUpdateResponse = balanceUpdateResponse
});
}
[HttpPost("/api/inventions/v1/settags")]
[HttpPut("/api/inventions/v1/settags")]
[HttpPatch("/api/inventions/v1/settags")]
[HttpPost("/api/inventions/v2/settags")]
[HttpPut("/api/inventions/v2/settags")]
[HttpPatch("/api/inventions/v2/settags")]
[HttpPost("/api/inventions/v3/settags")]
[HttpPut("/api/inventions/v3/settags")]
[HttpPatch("/api/inventions/v3/settags")]
[HttpPost("/api/inventions/v1/{inventionId:long}/settags")]
[HttpPut("/api/inventions/v1/{inventionId:long}/settags")]
[HttpPatch("/api/inventions/v1/{inventionId:long}/settags")]
[HttpPost("/api/inventions/v2/{inventionId:long}/settags")]
[HttpPut("/api/inventions/v2/{inventionId:long}/settags")]
[HttpPatch("/api/inventions/v2/{inventionId:long}/settags")]
[RequestSizeLimit(64 * 1024)]
public async Task<IActionResult> SetInventionTags(long inventionId = 0)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
var tags = new List<string>();
inventionId = ResolveInventionId(inventionId, Request.Query);
AddTagsFromQuery(tags, Request.Query);
if (Request.HasFormContentType)
{
IFormCollection form = await Request.ReadFormAsync(
HttpContext.RequestAborted);
inventionId = ResolveInventionId(inventionId, form);
AddTagsFromForm(tags, form);
}
else if ((Request.ContentLength ?? 0) > 0)
{
try
{
using JsonDocument document = await JsonDocument.ParseAsync(
Request.Body,
cancellationToken: HttpContext.RequestAborted);
inventionId = ResolveInventionId(
inventionId,
document.RootElement);
AddTagsFromJson(tags, document.RootElement);
}
catch (JsonException)
{
return BadRequest(new
{
Success = false,
Error = "invalid_tag_payload"
});
}
}
if (inventionId <= 0)
{
return BadRequest(new
{
Success = false,
Error = "invention_id_required"
});
}
tags = NormalizeTags(tags);
try
{
CreatorFeatureDB.InventionRecord? updated =
CreatorFeatureDB.SetInventionTags(
inventionId,
accountId.Value,
tags);
return updated == null
? NotFound(new
{
Success = false,
Error = "invention_not_found"
})
: Ok(CreatorFeatureDB.ToClientInvention(updated));
}
catch (UnauthorizedAccessException)
{
return StatusCode(403);
}
}
[HttpGet("/api/inventions/v1/{inventionId:long}/spawn")]
[HttpPost("/api/inventions/v1/{inventionId:long}/spawn")]
[HttpPut("/api/inventions/v1/{inventionId:long}/spawn")]
[HttpGet("/api/inventions/v2/{inventionId:long}/spawn")]
[HttpPost("/api/inventions/v2/{inventionId:long}/spawn")]
[HttpPut("/api/inventions/v2/{inventionId:long}/spawn")]
[HttpGet("/api/inventions/v1/spawn/{inventionId:long}")]
[HttpGet("/api/inventions/v2/spawn/{inventionId:long}")]
[HttpPost("/api/inventions/v1/{inventionId:long}/use")]
[HttpPut("/api/inventions/v1/{inventionId:long}/use")]
public IActionResult RecordInventionSpawn(long inventionId)
{
if (!AuthStuff.GetPlayerId(Request).HasValue)
return Unauthorized();
CreatorFeatureDB.InventionRecord? existing =
CreatorFeatureDB.GetInvention(inventionId);
if (existing == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
if (!CreatorFeatureDB.HasValidInventionBlob(existing))
{
return StatusCode(
StatusCodes.Status409Conflict,
new
{
Success = false,
Error = "invention_object_blob_missing",
InventionId = inventionId
});
}
CreatorFeatureDB.InventionRecord invention =
CreatorFeatureDB.IncrementInventionUse(inventionId)!;
CreatorFeatureDB.TryGetInventionBlob(
invention,
out _,
out string blobPath,
out string blobName,
out string blobHash,
out long blobSize);
object client = CreatorFeatureDB.ToClientInvention(invention);
object? currentVersion =
CreatorFeatureDB.ToClientInventionVersion(invention);
return Ok(new
{
Success = true,
InventionId = invention.InventionId,
Id = invention.InventionId,
Uses = invention.Uses,
DataBlob = blobName,
DataBlobPath = blobPath,
DataBlobHash = blobHash,
DataBlobSize = blobSize,
CurrentVersion = currentVersion,
InventionVersion = currentVersion,
Version = currentVersion,
Value = client,
Invention = client
});
}
[HttpGet("/api/inventions/{inventionId:long}/data")]
[HttpGet("/api/inventions/{inventionId:long}/download")]
[HttpGet("/api/inventions/{inventionId:long}/blob")]
[HttpGet("/api/inventions/v1/{inventionId:long}/data")]
[HttpGet("/api/inventions/v2/{inventionId:long}/data")]
[HttpGet("/api/inventions/v1/{inventionId:long}/download")]
[HttpGet("/api/inventions/v2/{inventionId:long}/download")]
[HttpGet("/api/inventions/v1/{inventionId:long}/blob")]
[HttpGet("/api/inventions/v2/{inventionId:long}/blob")]
[HttpGet("/api/inventions/v1/data/{inventionId:long}")]
[HttpGet("/api/inventions/v2/data/{inventionId:long}")]
public IActionResult DownloadInventionData(long inventionId)
{
CreatorFeatureDB.InventionRecord? invention =
CreatorFeatureDB.GetInvention(inventionId);
if (invention == null)
return NotFound(new { Success = false, Error = "invention_not_found" });
if (!CreatorFeatureDB.TryGetInventionBlob(
invention,
out string fullPath,
out _,
out string filename,
out string hashBase64,
out long size))
{
return NotFound(new
{
Success = false,
Error = "invention_object_blob_missing",
InventionId = inventionId
});
}
Response.Headers["X-Content-SHA256"] = hashBase64;
Response.Headers["X-Content-Length"] = size.ToString();
Response.Headers.CacheControl = "public,max-age=31536000,immutable";
return PhysicalFile(
fullPath,
"application/octet-stream",
filename,
enableRangeProcessing: true);
}
[HttpDelete("/api/inventions/v1/{inventionId:long}")]
[HttpDelete("/api/inventions/v2/{inventionId:long}")]
public IActionResult DeleteInvention(long inventionId)
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
return CreatorFeatureDB.DeleteInvention(inventionId, accountId.Value)
? Ok(new { Success = true, InventionId = inventionId })
: NotFound(new { Success = false, Error = "invention_not_found" });
}
[HttpGet("/api/inventions/v1/tagfilters")]
public IActionResult GetInventionTagFilters() => Ok(new[]
{
"Dorm Skin", "Decoration", "Furniture", "Game", "Gadget",
"Prop", "Costume", "Art", "Music", "Template"
});
[HttpGet("/api/customAvatarItems/v1/minPriceForPublicItem")]
public IActionResult GetMinimumCustomAvatarItemPrice() => Ok(100);
[HttpPost("/api/externalfriendinvite/v1/getplatformreferrers")]
public IActionResult GetPlatformReferrers() => Ok(Array.Empty<object>());
[HttpPost("/api/consumables/v1/updateActive")]
[RequestSizeLimit(16 * 1024)]
public async Task<IActionResult> UpdateActiveConsumable()
{
long? accountId = AuthStuff.GetPlayerId(Request);
if (!accountId.HasValue)
return Unauthorized();
string json;
try
{
using JsonDocument body = await JsonDocument.ParseAsync(Request.Body);
json = body.RootElement.GetRawText();
}
catch (JsonException)
{
return BadRequest(new { success = false, error = "invalid_request" });
}
PlayerDB.SetPlayerSetting("Consumables.Active", json, accountId.Value);
return Ok(new { success = true });
}
private List<long> ReadInventionIdsFromQuery()
{
var output = new List<long>();
foreach ((string key, Microsoft.Extensions.Primitives.StringValues values)
in Request.Query)
{
string normalizedKey = key.TrimEnd('[', ']').ToLowerInvariant();
if (normalizedKey is not "ids" and not "id" and
not "inventionids" and not "inventionid" and
not "inventions")
{
continue;
}
foreach (string? raw in values)
AddLongValues(output, raw);
}
return output.Where(value => value > 0).ToList();
}
private static void AddLongValues(ICollection<long> output, string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
return;
string trimmed = raw.Trim();
if (trimmed.StartsWith("[", StringComparison.Ordinal) ||
trimmed.StartsWith("{", StringComparison.Ordinal))
{
try
{
using JsonDocument document = JsonDocument.Parse(trimmed);
AddLongValues(output, document.RootElement);
return;
}
catch (JsonException)
{
}
}
foreach (string token in trimmed.Split(
new[] { ',', ';', '|', ' ', '\t', '\r', '\n', '[', ']', '"' },
StringSplitOptions.RemoveEmptyEntries |
StringSplitOptions.TrimEntries))
{
if (long.TryParse(token, out long value) && value > 0)
output.Add(value);
}
}
private static void AddLongValues(
ICollection<long> output,
JsonElement element)
{
if (element.ValueKind == JsonValueKind.Number &&
element.TryGetInt64(out long value) && value > 0)
{
output.Add(value);
return;
}
if (element.ValueKind == JsonValueKind.String)
{
AddLongValues(output, element.GetString());
return;
}
if (element.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in element.EnumerateArray())
AddLongValues(output, item);
return;
}
if (element.ValueKind != JsonValueKind.Object)
return;
foreach (JsonProperty property in element.EnumerateObject())
{
string name = property.Name.ToLowerInvariant();
if (name.Contains("invention", StringComparison.Ordinal) ||
name is "id" or "ids")
{
AddLongValues(output, property.Value);
}
}
}
private static long ResolveInventionId(
long current,
Microsoft.AspNetCore.Http.IQueryCollection values)
{
if (current > 0)
return current;
foreach (string key in new[]
{
"inventionId", "InventionId", "id", "Id"
})
{
foreach (string? raw in values[key])
{
if (long.TryParse(raw, out long parsed) && parsed > 0)
return parsed;
}
}
return current;
}
private static long ResolveInventionId(
long current,
IFormCollection values)
{
if (current > 0)
return current;
foreach (string key in new[]
{
"inventionId", "InventionId", "id", "Id"
})
{
foreach (string? raw in values[key])
{
if (long.TryParse(raw, out long parsed) && parsed > 0)
return parsed;
}
}
return current;
}
private static long ResolveInventionId(
long current,
JsonElement element)
{
if (current > 0)
return current;
if (element.ValueKind == JsonValueKind.Object)
{
foreach (JsonProperty property in element.EnumerateObject())
{
if (property.Name.Equals(
"inventionId",
StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("id", StringComparison.OrdinalIgnoreCase))
{
if (property.Value.ValueKind == JsonValueKind.Number &&
property.Value.TryGetInt64(out long number) && number > 0)
{
return number;
}
if (long.TryParse(property.Value.ToString(), out number) &&
number > 0)
{
return number;
}
}
long nested = ResolveInventionId(0, property.Value);
if (nested > 0)
return nested;
}
}
else if (element.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in element.EnumerateArray())
{
long nested = ResolveInventionId(0, item);
if (nested > 0)
return nested;
}
}
return current;
}
private static bool? ReadBooleanValue(
Microsoft.AspNetCore.Http.IQueryCollection values,
params string[] keys)
{
foreach (string key in keys)
{
foreach (string? raw in values[key])
{
bool? parsed = ParseBooleanValue(raw);
if (parsed.HasValue)
return parsed;
}
}
return null;
}
private static bool? ReadBooleanValue(
IFormCollection values,
params string[] keys)
{
foreach (string key in keys)
{
foreach (string? raw in values[key])
{
bool? parsed = ParseBooleanValue(raw);
if (parsed.HasValue)
return parsed;
}
}
return null;
}
private static bool? ReadBooleanValue(JsonElement element)
{
if (element.ValueKind == JsonValueKind.True)
return true;
if (element.ValueKind == JsonValueKind.False)
return false;
if (element.ValueKind is JsonValueKind.String or JsonValueKind.Number)
return ParseBooleanValue(element.ToString());
if (element.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in element.EnumerateArray())
{
bool? value = ReadBooleanValue(item);
if (value.HasValue)
return value;
}
return null;
}
if (element.ValueKind != JsonValueKind.Object)
return null;
foreach (JsonProperty property in element.EnumerateObject())
{
if (property.Name.Equals("cheer", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("cheered", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("isCheered", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("state", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("value", StringComparison.OrdinalIgnoreCase))
{
bool? direct = ReadBooleanValue(property.Value);
if (direct.HasValue)
return direct;
}
}
return null;
}
private static bool? ParseBooleanValue(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
return null;
if (bool.TryParse(raw, out bool boolean))
return boolean;
if (long.TryParse(raw, out long number))
return number != 0;
return raw.Trim().ToLowerInvariant() switch
{
"yes" or "on" or "cheer" or "add" => true,
"no" or "off" or "uncheer" or "remove" => false,
_ => null
};
}
private static void AddTagsFromQuery(
ICollection<string> output,
Microsoft.AspNetCore.Http.IQueryCollection values)
{
foreach ((string key, Microsoft.Extensions.Primitives.StringValues items)
in values)
{
if (!IsTagKey(key))
continue;
foreach (string? raw in items)
AddTags(output, raw);
}
}
private static void AddTagsFromForm(
ICollection<string> output,
IFormCollection values)
{
foreach ((string key, Microsoft.Extensions.Primitives.StringValues items)
in values)
{
if (!IsTagKey(key))
continue;
foreach (string? raw in items)
AddTags(output, raw);
}
}
private static void AddTagsFromJson(
ICollection<string> output,
JsonElement element)
{
if (element.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in element.EnumerateArray())
AddTagsFromJson(output, item);
return;
}
if (element.ValueKind != JsonValueKind.Object)
return;
foreach (JsonProperty property in element.EnumerateObject())
{
if (IsTagKey(property.Name))
{
if (property.Value.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in property.Value.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.String)
AddTags(output, item.GetString());
else if (item.ValueKind == JsonValueKind.Object)
AddTagObject(output, item);
else
AddTags(output, item.ToString());
}
}
else if (property.Value.ValueKind == JsonValueKind.Object)
{
AddTagObject(output, property.Value);
}
else
{
AddTags(output, property.Value.ToString());
}
}
else if (property.Value.ValueKind is
JsonValueKind.Object or JsonValueKind.Array)
{
AddTagsFromJson(output, property.Value);
}
else if (property.Value.ValueKind == JsonValueKind.String)
{
string? raw = property.Value.GetString();
if (!string.IsNullOrWhiteSpace(raw) &&
(raw.TrimStart().StartsWith("[", StringComparison.Ordinal) ||
raw.TrimStart().StartsWith("{", StringComparison.Ordinal)))
{
try
{
using JsonDocument embedded = JsonDocument.Parse(raw);
AddTagsFromJson(output, embedded.RootElement);
}
catch (JsonException)
{
}
}
}
}
}
private static void AddTagObject(
ICollection<string> output,
JsonElement element)
{
foreach (JsonProperty property in element.EnumerateObject())
{
if (property.Name.Equals("name", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("tag", StringComparison.OrdinalIgnoreCase) ||
property.Name.Equals("value", StringComparison.OrdinalIgnoreCase))
{
AddTags(output, property.Value.ToString());
}
}
}
private static bool IsTagKey(string key)
{
string normalized = key.TrimEnd('[', ']').ToLowerInvariant();
return normalized is "tag" or "tags" or "tagname" or "tagnames";
}
private static void AddTags(ICollection<string> output, string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
return;
string trimmed = raw.Trim();
if (trimmed.StartsWith("[", StringComparison.Ordinal) ||
trimmed.StartsWith("{", StringComparison.Ordinal))
{
try
{
using JsonDocument document = JsonDocument.Parse(trimmed);
AddTagsFromJson(output, document.RootElement);
return;
}
catch (JsonException)
{
}
}
foreach (string value in trimmed.Split(
new[] { ',', ';', '|' },
StringSplitOptions.RemoveEmptyEntries |
StringSplitOptions.TrimEntries))
{
output.Add(value);
}
}
private static List<string> NormalizeTags(IEnumerable<string> values) =>
values
.Select(value => value.Trim())
.Where(value => value.Length is > 0 and <= 32)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Take(30)
.ToList();
[HttpGet("/favicon.ico")]
public IActionResult Favicon() => NoContent();
}
}