Add remaining project files

This commit is contained in:
Marco Baldwin
2026-07-23 18:21:43 -07:00
parent c12d8ac35d
commit 6e15e89a9d
453 changed files with 64265 additions and 0 deletions
@@ -0,0 +1,48 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Accounts
{
[Route("Accounts/accountprivacysettings")]
[ApiController]
public class AccountPrivacySettingsController(ILiteDbService db, IJwtService jwt) : ControllerBase
{
[HttpGet("{accId}")]
public async Task<IActionResult> AccountPrivacySettings(long accId)
{
Account? account = db.Accounts.FindById(accId);
if (account == null)
{
return NotFound();
}
return Ok(new
{
accountId = accId,
isRecentHistoryVisible = account.IsRecentHistoryVisible
});
}
[HttpGet("recenthistoryvisibility")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> RecentHistoryVisibility([FromForm] bool isRecentHistoryVisible)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
account.IsRecentHistoryVisible = isRecentHistoryVisible;
db.Accounts.Update(account);
return Ok(new
{
Success = true
});
}
}
}
+43
View File
@@ -0,0 +1,43 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Accounts
{
[Route("Accounts/account")]
[ApiController]
public class BruhController : ControllerBase
{
private readonly IJwtService jwt;
private readonly ILiteDbService db;
public BruhController(IJwtService _jwt, ILiteDbService _db)
{
jwt = _jwt;
db = _db;
}
[HttpGet("{accId}/bio")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Btio(long accId)
{
Account? account = db.Accounts.FindById(accId);
if (account == null)
{
return NotFound();
}
return Ok(new
{
accountId = accId,
bio = account.Bio
});
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using DeluxeBackend.Models;
namespace DeluxeBackend.Controllers.Accounts
{
[Route("Accounts/account/bulk")]
[ApiController]
public class BulkController : ControllerBase
{
private readonly ILiteDbService db;
private readonly bool setup = false;
public BulkController(ILiteDbService _db, IJwtService _jwtService)
{
db = _db;
if (!setup)
{
if (db.Accounts.FindById(1) == null)
{
Account account = new()
{
Username = "Coach",
DisplayName = "Coach",
Birthday = DateOnly.MinValue,
ImageName= "DefaultProfileImage"
};
db.Accounts.Insert(account);
}
}
}
[HttpGet]
public async Task<IActionResult> Bulk([FromQuery(Name ="id")] List<long> accountIds)
{
var accounts = db.Accounts.Find(x => accountIds.Contains(x.Id));
var accountDictionaries = accounts.Select(a => a.ToDictionary()).ToList();
return Ok(accountDictionaries);
}
[HttpGet("all_db")]
public async Task<IActionResult> all()
{
var accounts = db.Accounts.FindAll();
return Ok(accounts.ToList());
}
}
}
+244
View File
@@ -0,0 +1,244 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.IO;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Accounts
{
[Route("Accounts/account/me")]
[ApiController]
public partial class MeController : ControllerBase
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly IPasswordService passwordService;
private readonly INotificationService ws;
public MeController(ILiteDbService _db, IJwtService _jwtService, IPasswordService _passwordService, INotificationService _ws)
{
db = _db;
jwt = _jwtService;
passwordService = _passwordService;
ws = _ws;
}
[HttpGet]
[Authorize]
public async Task<IActionResult> Me()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
return Ok(account.ToDictionaryMe());
}
[HttpPut("birthday")]
[Authorize]
public async Task<IActionResult> Birthday([FromForm] string birthday)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
if (account.Birthday.HasValue)
{
return Conflict(new { Success = false, Error = "Account birthday has already been set." });
}
if (DateTime.TryParse(birthday, out DateTime parsedDateTime))
{
if (parsedDateTime > DateTime.UtcNow || parsedDateTime < DateTime.UtcNow.AddYears(-125))
{
return BadRequest(new { Success = false, Error = "Please provide a valid birth date." });
}
account.Birthday = DateOnly.FromDateTime(parsedDateTime);
db.Accounts.Update(account);
return Ok(new { Success = true });
}
return BadRequest(new { Success = false, Error = "Invalid date format received." });
}
[HttpPut("username")]
[Authorize]
public async Task<IActionResult> Username([FromForm] string username)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
if (string.IsNullOrWhiteSpace(username))
{
return BadRequest(new { Success = false, Error = "Username cannot be empty." });
}
username = username.Trim();
if (username.Length < 3 || username.Length > 20)
{
return BadRequest(new { Success = false, Error = "Username must be between 3 and 20 characters." });
}
if (!UsernameRegex().IsMatch(username))
{
return BadRequest(new { Success = false, Error = "Username can only contain letters, numbers, and underscores." });
}
string[] reservedNames = { "admin", "administrator", "moderator", "system", "support", "staff", "deluxe" };
if (reservedNames.Contains(username.ToLowerInvariant()))
{
return BadRequest(new { Success = false, Error = "This username is reserved." });
}
bool isTaken = db.Accounts.Exists(a => a.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
if (isTaken)
{
return Conflict(new { Success = false, Error = $"The username '{username}' is already taken." });
}
account.Username = username;
account.DisplayName = username;
db.Accounts.Update(account);
await ws.SendToPlayer(account.Id, "SelfAccountUpdate", account.ToDictionaryMe());
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", account.ToDictionary());
return Ok(new { Success = true });
}
[HttpPost("/Auth/account/me/changepassword")]
[Authorize]
public async Task<IActionResult> Changepassword([FromForm] string newPassword, [FromForm] string oldPassword = "")
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
if (string.IsNullOrWhiteSpace(newPassword) || newPassword.Length < 6)
{
return BadRequest(new { Success = false, Error = "New password must be at least 6 characters long." });
}
if (!string.IsNullOrEmpty(account.PasswordHash))
{
if (string.IsNullOrEmpty(oldPassword))
{
return BadRequest(new { Success = false, Error = "Old password is required." });
}
var verificationResult = passwordService.VerifyPassword(account, oldPassword);
if (verificationResult == Microsoft.AspNetCore.Identity.PasswordVerificationResult.Failed)
{
return BadRequest(new { Success = false, Error = "Old password is incorrect." });
}
}
passwordService.setPassword(account, newPassword);
db.Accounts.Update(account);
return Ok(new { Success = true });
}
[HttpPost("/Auth/account/me/haspassword")]
[Authorize]
public async Task<IActionResult> HasPassword([FromForm] string newPassword, [FromForm] string oldPassword = "")
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
return Ok(!string.IsNullOrEmpty(account.PasswordHash));
}
[HttpGet("/Accounts/parentalcontrol/me")]
[Authorize]
public async Task<IActionResult> ParentalControl()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
return Ok(new
{
accountId = account.Id,
disallowInAppPurchases = false
});
}
[HttpPut("personalpronouns")]
[Authorize]
public async Task<IActionResult> PersonalPronouns([FromForm] PronounsType pronounFlags)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
account.Pronouns = pronounFlags;
db.Accounts.Update(account);
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", account.ToDictionary());
return Ok(new { Success = true });
}
[HttpPut("identityflags")]
[Authorize]
public async Task<IActionResult> IdentityFlags([FromForm] IdentityFlagsType identityFlags)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
account.IdentityFlags = identityFlags;
db.Accounts.Update(account);
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", account.ToDictionary());
return Ok(new { Success = true });
}
[HttpPut("bio")]
[Authorize]
public async Task<IActionResult> Bio([FromForm] string bio)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
if (bio != null && bio.Length > 250)
{
return BadRequest(new { Success = false, Error = "Bio cannot exceed 250 characters." });
}
account.Bio = bio ?? string.Empty;
db.Accounts.Update(account);
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", account.ToDictionary());
return Ok(new { Success = true });
}
[HttpPut("profileimage")]
[Authorize]
public async Task<IActionResult> ProfileImage([FromForm(Name = "imageName"), Required] string ImageName)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
string sanitizedPath = Path.GetFileName(ImageName);
if (string.IsNullOrWhiteSpace(sanitizedPath) || sanitizedPath != ImageName)
{
return BadRequest(new { Success = false, Error = "Invalid image file name format." });
}
account.ImageName = ImageName;
db.Accounts.Update(account);
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", account.ToDictionary());
return Ok(new { Success = true });
}
[GeneratedRegex("^[a-zA-Z0-9_]+$")]
private static partial Regex UsernameRegex();
}
}
+19
View File
@@ -0,0 +1,19 @@
using DeluxeBackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/announcement")]
[ApiController]
public class AnnouncementController : ControllerBase
{
[HttpGet("v1/get")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> None()
{
return Ok(new List<object>());
}
}
}
+217
View File
@@ -0,0 +1,217 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/avatar")]
[ApiController]
public class AvatarController : ControllerBase
{
private readonly IJwtService jwt;
private readonly ILiteDbService db;
public AvatarController(IJwtService _jwt, ILiteDbService _db)
{
jwt = _jwt;
db = _db;
}
[HttpGet("v1/defaultunlocked")]
public async Task<IActionResult> Defaultunlocked()
{
return Ok(ServerConfig.avatarItems);
}
[HttpGet("v1/defaultbaseavataritems")]
public async Task<IActionResult> Defaultbaseavataritems()
{
return Ok(new List<object>());
}
[HttpGet("v4/items")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Items()
{
return Ok(ServerConfig.avatarItems);
}
[HttpGet("v2")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> MyAv()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountAvatar? accountAvatar = db.Avatar.FindOne(x => x.Account!.Id == account.Id);
return Ok(new
{
OutfitSelections = accountAvatar?.OutfitSelections ?? "",
OutfitSelectionsV2 = accountAvatar?.OutfitSelectionsV2 ?? "",
FaceFeatures = accountAvatar?.FaceFeatures ?? "",
SkinColor = accountAvatar?.SkinColor ?? "",
HairColor = accountAvatar?.HairColor ?? "",
CustomAvatarItems = new List<object>()
});
}
[HttpGet("v2/{accId}")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Av(long accId)
{
Account? account = db.Accounts.FindById(accId);
if (account == null)
{
return NotFound();
}
AccountAvatar? accountAvatar = db.Avatar.FindOne(x => x.Account!.Id == account.Id);
return Ok(new
{
OutfitSelections = accountAvatar?.OutfitSelections ?? "",
OutfitSelectionsV2 = accountAvatar?.OutfitSelectionsV2 ?? "",
FaceFeatures = accountAvatar?.FaceFeatures ?? "",
SkinColor = accountAvatar?.SkinColor ?? "",
HairColor = accountAvatar?.HairColor ?? "",
CustomAvatarItems = new List<object>()
});
}
public class AvatarSetRequest
{
public required string OutfitSelections { get; set; }
public required string OutfitSelectionsV2 { get; set; }
public required string FaceFeatures { get; set; }
public required string SkinColor { get; set; }
public required string HairColor { get; set; }
}
[HttpPost("v2/set")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Set([FromBody] AvatarSetRequest request)
{
if (request == null) return BadRequest();
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountAvatar? accountAvatar = db.Avatar.FindOne(x => x.Account!.Id == account.Id);
if (accountAvatar == null)
{
accountAvatar = new AccountAvatar() { Account = account };
}
accountAvatar.OutfitSelections = request.OutfitSelections;
accountAvatar.OutfitSelectionsV2 = request.OutfitSelectionsV2;
accountAvatar.FaceFeatures = request.FaceFeatures;
accountAvatar.HairColor = request.HairColor;
accountAvatar.SkinColor = request.SkinColor;
db.Avatar.Upsert(accountAvatar);
return Ok(new { Success = true });
}
[HttpGet("v2/gifts")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Gifts()
{
return Ok(new List<object>());
}
private Dictionary<string, object> SavedAvToDict(AvatarSaved av)
{
return new Dictionary<string, object> {
["CustomAvatarItems"] = new List<object>(),
["FaceFeatures"] = av.FaceFeatures,
["HairColor"] = av.HairColor,
["Name"] = av.Name,
["OutfitSelections"] = av.OutfitSelections,
["OutfitSelectionsV2"] = av.OutfitSelectionsV2,
["PreviewImageName"] = av.PreviewImageName,
["SkinColor"] = av.SkinColor,
["Slot"] = av.Slot
};
}
[HttpGet("v3/saved")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Saved()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
return Ok(db.AvatarSaveds.Find(x => x.Account.Id == account.Id).Select(d => SavedAvToDict(d)).ToList());
}
public class AvatarSavedSetRequest
{
public required string OutfitSelections { get; set; }
public required string OutfitSelectionsV2 { get; set; }
public required string FaceFeatures { get; set; }
public required string SkinColor { get; set; }
public required string HairColor { get; set; }
public string? Name { get; set; } = null;
public int Slot { get; set; }
public required string PreviewImageName { get; set; }
}
[HttpPost("v4/saved/set")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> SavedSet([FromBody] AvatarSavedSetRequest request)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
SavedImage? image = db.SavedImages.Include(x => x.Player).FindOne(x => x.ImageName == request.PreviewImageName);
if (image == null) { return NotFound("image not found"); }
if (image.SavedImageType != SavedImageType.OutfitThumbnail)
{
return BadRequest("image not for outfut");
}
AvatarSaved avatarSaved = new()
{
Account = account,
Slot = request.Slot,
PreviewImageName = request.PreviewImageName,
Name = request.Name ?? "",
OutfitSelections = request.OutfitSelections,
OutfitSelectionsV2 = request.OutfitSelectionsV2,
SkinColor = request.SkinColor,
HairColor = request.HairColor,
FaceFeatures = request.FaceFeatures,
};
db.AvatarSaveds.Insert(avatarSaved);
return Ok(new { Success = true, Value = SavedAvToDict(avatarSaved)});
}
}
}
+78
View File
@@ -0,0 +1,78 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
public class CampusCardController : ControllerBase
{
private readonly IJwtService jwt;
private readonly ILiteDbService db;
private readonly INotificationService ws;
private readonly DiscordBotService bot;
public CampusCardController(IJwtService _jwt, ILiteDbService _db, INotificationService _ws, DiscordBotService _bot)
{
jwt = _jwt;
db = _db;
ws = _ws;
bot = _bot;
}
[HttpPost("v1/UpdateAndGetSubscription")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> UpdateAndGetSubscription()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
bool isActive = false;
if (account.DiscordId.HasValue)
{
isActive = await bot.HasUserBoostedGuild(account.DiscordId.Value);
}
if (isActive)
{
return Ok(new
{
CanBuySubscription = false,
PlatformAccountSubscribedPlayerId = 0,
Subscription = new
{
CreatedAt = DateTime.MinValue.ToString("O"),
ExpirationDate = DateTime.MaxValue.ToString("O"),
IsActive = true,
IsAutoRenewing = true,
Level = 0,
ModifiedAt = DateTime.MinValue.ToString("O"),
Period = 0,
PlatformId = "1",
PlatformPurchaseId = "0",
PlatformType = (int)PlatformType.RecNet,
RecNetPlayerId = account.Id,
SubscriptionId = 0
}
});
}
else
{
return Ok(new
{
CanBuySubscription = false,
PlatformAccountSubscribedPlayerId = 0,
Subscription = (string?)null
});
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/checklist")]
[ApiController]
public class ChecklistController : ControllerBase
{
[HttpGet("v1/current")]
[Authorize]
public async Task<IActionResult> Current()
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/communityboard")]
[ApiController]
public class CommunityBoardController : ControllerBase
{
[HttpGet("v2/current")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Current()
{
return Ok(new
{
CurrentAnnouncement = new Dictionary<string, object>()
{
["Message"] = ":3",
["MoreInfoUrl"] = ""
},
InstagramImages = new List<object>(),
Videos = new List<object>(),
});
}
}
}
+139
View File
@@ -0,0 +1,139 @@
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/config")]
[ApiController]
public class ConfigController : ControllerBase
{
[HttpGet("v1/amplitude")]
public IActionResult Amplitude()
{
return Ok(new
{
AmplitudeKey = "e1693a1003671058b6abc356c8ba8d59",
UseRudderStack = false,
RudderStackKey = "23NiJHIgu3koaGNCZIiuYvIQNCu",
UseStatSig = false,
StatSigKey = "client-SBZkOrjD3r1Cat3f3W8K6sBd11WKlXZXIlCWj6l4Aje",
StatSigEnvironment = 0
});
}
[HttpGet("v1/backtrace")]
public IActionResult Backtrace()
{
return Ok(new
{
ReportBudget = 0,
FilterType = 0,
SampleRate = 0.025,
LogLineCount = 50,
CaptureNativeCrashes = 1,
ANRThresholdMs = 0,
MessageCount = 1000,
MessageRegex = "^Cannot set the parent of the GameObject .* while its new parent|^\\\\u003E\\\\x2010x\\\\:\\\\x20|\\'LabelTheme\\' contains missing PaletteTheme reference on",
VersionRegex = ".*"
});
}
[HttpGet("v2")]
public IActionResult GetConfig()
{
return Ok(new
{
ShareBaseUrl = "https://127.0.0.1:5000{0}",
LevelProgressionMaps = new[]
{
new { Level = 1, RequiredXp = 2, GiftDropId = 1 },
new { Level = 2, RequiredXp = 3, GiftDropId = 881 },
new { Level = 3, RequiredXp = 4, GiftDropId = 19 },
new { Level = 4, RequiredXp = 6, GiftDropId = 2134 },
new { Level = 5, RequiredXp = 9, GiftDropId = 1063 },
new { Level = 6, RequiredXp = 13, GiftDropId = 1043 },
new { Level = 7, RequiredXp = 19, GiftDropId = 1287 },
new { Level = 8, RequiredXp = 28, GiftDropId = 1765 },
new { Level = 9, RequiredXp = 42, GiftDropId = 1328 },
new { Level = 10, RequiredXp = 63, GiftDropId = 1857 },
new { Level = 11, RequiredXp = 94, GiftDropId = 1359 },
new { Level = 12, RequiredXp = 141, GiftDropId = 1433 },
new { Level = 13, RequiredXp = 211, GiftDropId = 320 },
new { Level = 14, RequiredXp = 316, GiftDropId = 1373 },
new { Level = 15, RequiredXp = 474, GiftDropId = 1949 },
new { Level = 16, RequiredXp = 711, GiftDropId = 961 },
new { Level = 17, RequiredXp = 1066, GiftDropId = 934 },
new { Level = 18, RequiredXp = 1599, GiftDropId = 633 },
new { Level = 19, RequiredXp = 2398, GiftDropId = 1766 },
new { Level = 20, RequiredXp = 3597, GiftDropId = 523 },
new { Level = 21, RequiredXp = 5395, GiftDropId = 106 },
new { Level = 22, RequiredXp = 8092, GiftDropId = 1075 },
new { Level = 23, RequiredXp = 12138, GiftDropId = 352 },
new { Level = 24, RequiredXp = 18207, GiftDropId = 49 },
new { Level = 25, RequiredXp = 27310, GiftDropId = 879 },
new { Level = 26, RequiredXp = 40965, GiftDropId = 2115 },
new { Level = 27, RequiredXp = 61447, GiftDropId = 2167 },
new { Level = 28, RequiredXp = 92170, GiftDropId = 2246 },
new { Level = 29, RequiredXp = 138255, GiftDropId = 1895 },
new { Level = 30, RequiredXp = 207382, GiftDropId = 1584 },
new { Level = 31, RequiredXp = 311073, GiftDropId = 385 },
new { Level = 32, RequiredXp = 466609, GiftDropId = 2308 },
new { Level = 33, RequiredXp = 699913, GiftDropId = 1499 },
new { Level = 34, RequiredXp = 1049869, GiftDropId = 1410 },
new { Level = 35, RequiredXp = 1574803, GiftDropId = 373 },
new { Level = 36, RequiredXp = 2362204, GiftDropId = 254 },
new { Level = 37, RequiredXp = 3543306, GiftDropId = 1069 },
new { Level = 38, RequiredXp = 5314959, GiftDropId = 993 },
new { Level = 39, RequiredXp = 7972438, GiftDropId = 1887 },
new { Level = 40, RequiredXp = 11958657, GiftDropId = 985 },
new { Level = 41, RequiredXp = 17937986, GiftDropId = 2079 },
new { Level = 42, RequiredXp = 26906980, GiftDropId = 105 },
new { Level = 43, RequiredXp = 40360472, GiftDropId = 1363 },
new { Level = 44, RequiredXp = 60540708, GiftDropId = 1526 },
new { Level = 45, RequiredXp = 90811064, GiftDropId = 131 },
new { Level = 46, RequiredXp = 136216592, GiftDropId = 1376 },
new { Level = 47, RequiredXp = 204324896, GiftDropId = 834 },
new { Level = 48, RequiredXp = 306487360, GiftDropId = 816 },
new { Level = 49, RequiredXp = 459731040, GiftDropId = 138 },
new { Level = 50, RequiredXp = 689596544, GiftDropId = 10 }
},
DailyObjectives = new[]
{
new[] { new { type = 101, score = 1, xp = 10 }, new { type = 1000, score = 1, xp = 10 }, new { type = 802, score = 2, xp = 10 } },
new[] { new { type = 26, score = 1, xp = 10 }, new { type = 1021, score = 2, xp = 10 }, new { type = 2004, score = 1, xp = 10 } },
new[] { new { type = 3001, score = 1, xp = 10 }, new { type = 102, score = 1, xp = 10 }, new { type = 601, score = 2, xp = 10 } },
new[] { new { type = 14, score = 2, xp = 10 }, new { type = 1000, score = 1, xp = 10 }, new { type = 14, score = 2, xp = 10 } },
new[] { new { type = 801, score = 1, xp = 10 }, new { type = 2001, score = 1, xp = 10 }, new { type = 21, score = 1, xp = 10 } },
new[] { new { type = 603, score = 1, xp = 10 }, new { type = 1002, score = 1, xp = 10 }, new { type = 1041, score = 1, xp = 10 } },
new[] { new { type = 700, score = 1, xp = 10 }, new { type = 4001, score = 1, xp = 10 }, new { type = 602, score = 2, xp = 10 } }
},
ServerMaintenance = new { StartsInMinutes = 0 },
AutoMicMutingConfig = new
{
MicSpamVolumeThreshold = 1.125,
MicVolumeSampleInterval = 0.25,
MicVolumeSampleRollingWindowLength = 7,
MicSpamSamplePercentageForWarning = 0.8,
MicSpamSamplePercentageForWarningToEnd = 0.2,
MicSpamSamplePercentageForForceMute = 0.8,
MicSpamSamplePercentageForForceMuteToEnd = 0.2,
MicSpamWarningStateVolumeMultiplier = 0.25
},
StorefrontConfig = new { MinPlayerLevelForGifting = 15 },
RoomKeyConfig = new { MaxKeysPerRoom = 10 },
RoomCurrencyConfig = new { AwardCurrencyCooldownSeconds = 10 }
});
}
[HttpGet("v1/azurespeech")]
public IActionResult Azurespeech()
{
return Ok(new
{
Key = "dce8de5b297747d9b5bddcc7f19e8c5b",
Region= "eastus",
Enabled=true
});
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/consumables")]
[ApiController]
public class ConsumablesController : ControllerBase
{
[HttpGet("v2/getUnlocked")]
public async Task<IActionResult> Saved()
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,291 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Linq.Expressions;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/customAvatarItems")]
[ApiController]
[Authorize]
public class CustomAvatarItemsController(
ILiteDbService db,
IJwtService jwt,
DiscordBotService discord,
ICdnService cdn,
HttpClient httpClient) : ControllerBase
{
private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
[HttpGet("v1/isCreationEnabled")]
[HttpGet("v1/isRenderingEnabled")]
public IActionResult IsCreationEnabled() => Ok(true);
[HttpGet("v1/minPriceForPublicItem")]
public IActionResult MinPriceForPublicItem() => Ok(0);
[HttpGet("v1/isCreationAllowedForAccount")]
public async Task<IActionResult> IsCreationAllowedForAccount()
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
if (!account.DiscordId.HasValue)
return Ok(new { Success = false });
bool hasBoosted = await discord.HasUserBoostedGuild(account.DiscordId.Value);
return Ok(new { Success = hasBoosted });
}
[HttpGet("/econ/customAvatarItems/v1/owned")]
public async Task<IActionResult> Owned([FromQuery] int skip = 0, [FromQuery] int take = 100)
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
const bool isCreator = true;
Expression<Func<CustomAvatarItem, bool>> filter = x =>
x.Creator.Id == account.Id && (isCreator || x.Accessibility == RoomAccessibility.Public);
return GetPagedItems(filter, skip, take);
}
[HttpGet("v2/fromCreator/{accId}")]
public async Task<IActionResult> FromCreator(long accId, [FromQuery] int skip = 0, [FromQuery] int take = 100)
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
bool isCreator = account.Id == accId;
Expression<Func<CustomAvatarItem, bool>> filter = x =>
x.Creator.Id == accId && (isCreator || x.Accessibility == RoomAccessibility.Public);
return GetPagedItems(filter, skip, take);
}
[HttpPost("v1")]
public async Task<IActionResult> Create(
[FromForm(Name = "thumbnailImage"), Required] IFormFile thumbnailImage,
[FromForm(Name = "design"), Required] IFormFile designImage,
[FromForm(Name = "metadata"), Required] string metaJson)
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
if (!account.DiscordId.HasValue || !await discord.HasUserBoostedGuild(account.DiscordId.Value))
return StatusCode(StatusCodes.Status403Forbidden);
CustomAvatarItemMetaDTO? meta;
try
{
meta = JsonSerializer.Deserialize<CustomAvatarItemMetaDTO>(metaJson);
if (meta == null) return BadRequest("Invalid metadata format.");
}
catch
{
return BadRequest("Metadata is not valid JSON.");
}
using var thumbStream = thumbnailImage.OpenReadStream();
string? thumbRemotePath = await cdn.UploadFile(thumbStream, "img");
if (thumbRemotePath == null)
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to upload ThumbnailImage to the CDN.");
using var designStream = designImage.OpenReadStream();
string? designRemotePath = await cdn.UploadFile(designStream, "img");
if (designRemotePath == null)
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to upload DesignImage to the CDN.");
var customAvatar = new CustomAvatarItem
{
Creator = account,
Name = meta.Name,
Price = meta.Price,
BaseAvatarItemColor = ColorTranslator.FromHtml(meta.BaseAvatarItemColor),
ThumbnailImageFilename = thumbRemotePath,
DesignFilename = designRemotePath,
PreviewOrientation = meta.PreviewOrientation
};
db.CustomAvatarItems.Insert(customAvatar);
return Ok(new { Success = true, Value = customAvatar.ToDictionary() });
}
[HttpPost("v1/bulk")]
public async Task<IActionResult> Bulk([FromForm(Name = "customAvatarItemIds")] List<Guid> customAvatarItemIds)
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
var items = db.CustomAvatarItems.Find(x => customAvatarItemIds.Contains(x.Id)).ToList();
var foundIds = items.Select(x => x.Id).ToHashSet();
var missingIds = customAvatarItemIds.Where(id => !foundIds.Contains(id)).ToList();
if (missingIds.Any())
{
var importAccount = GetOrCreateImportAccount();
foreach (var missingId in missingIds)
{
var fallbackItem = await FetchItemFromRecNetAsync(missingId, importAccount);
if (fallbackItem != null)
{
items.Add(fallbackItem);
}
}
}
return Ok(items.Select(x => x.ToDictionary()).ToList());
}
[HttpGet("v1/{customAvatarItemId}")]
public async Task<IActionResult> Get(Guid customAvatarItemId)
{
var account = await GetAuthenticatedAccountAsync();
if (account == null) return Unauthorized();
var customAvatarItem = db.CustomAvatarItems.FindById(customAvatarItemId);
if (customAvatarItem == null)
{
var importAccount = GetOrCreateImportAccount();
customAvatarItem = await FetchItemFromRecNetAsync(customAvatarItemId, importAccount);
}
if (customAvatarItem == null)
return NotFound();
return Ok(customAvatarItem.ToDictionary());
}
#region Helper Methods
private async Task<Account?> GetAuthenticatedAccountAsync()
{
var login = await jwt.GetLoginWithInfo(User);
return login?.Account;
}
private IActionResult GetPagedItems(Expression<Func<CustomAvatarItem, bool>> filter, int skip, int take)
{
int totalCount = db.CustomAvatarItems.Count(filter);
var results = db.CustomAvatarItems
.Include(x => x.Creator)
.Find(filter)
.Skip(skip)
.Take(take)
.Select(x => x.ToDictionary());
return Ok(new
{
Results = results,
TotalResults = totalCount
});
}
private Account GetOrCreateImportAccount()
{
var importAccount = db.Accounts.FindOne(x => x.Username == "Import");
if (importAccount == null)
{
importAccount = new Account
{
Username = "Import",
DisplayName = "Import",
Birthday = DateOnly.MinValue,
ImageName = "DefaultProfileImage",
IsRecentHistoryVisible = false,
};
importAccount.Roles.AddRange(["developer", "keepsake", "livekeepsakeeventroomsaveoverride", "betaroomcurrencycreator", "multiinstanceevent"]);
db.Accounts.Insert(importAccount);
}
return importAccount;
}
private async Task<CustomAvatarItem?> FetchItemFromRecNetAsync(Guid itemId, Account importAccount)
{
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.rec.net/api/customAvatarItems/v1/{itemId}");
request.Headers.Add("Authorization", $"Bearer {ServerConfig.RRToken}");
using var response = await httpClient.SendAsync(request);
if (response.StatusCode == HttpStatusCode.OK)
{
string content = await response.Content.ReadAsStringAsync();
var recNetResult = JsonSerializer.Deserialize<CAvatarItemDto>(content, _jsonOptions);
if (recNetResult != null && !string.IsNullOrEmpty(recNetResult.DesignFilename))
{
var customAvatarItem = new CustomAvatarItem
{
Id = recNetResult.CustomAvatarItemId,
Accessibility=recNetResult.Accessibility,
Creator = importAccount,
Name = recNetResult.Name,
Description = recNetResult.Description,
Price = recNetResult.Price,
BaseAvatarItemId = recNetResult.BaseAvatarItemId,
BaseAvatarItemColor = ColorTranslator.FromHtml(recNetResult.BaseAvatarItemColor),
DesignFilename = recNetResult.DesignFilename,
ThumbnailImageFilename = recNetResult.ThumbnailImageFilename,
PreviewOrientation = recNetResult.PreviewOrientation
};
db.CustomAvatarItems.Insert(customAvatarItem);
return customAvatarItem;
}
}
}
catch
{
}
return null;
}
#endregion
#region DTOs
public class CustomAvatarItemMetaDTO
{
[Required] public required string Name { get; set; }
[Required] public required string Description { get; set; }
[Required] public required int Price { get; set; }
[Required] public required long BaseAvatarItemId { get; set; }
[Required] public required string BaseAvatarItemColor { get; set; }
[Required] public required RoomAccessibility Accessibility { get; set; }
[Required] public required PreviewOrientationType PreviewOrientation { get; set; }
}
public class CAvatarItemDto
{
public Guid CustomAvatarItemId { get; set; }
public long CreatorAccountId { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Price { get; set; }
public RoomAccessibility Accessibility { get; set; }
public bool IsFeatured { get; set; }
public string BaseAvatarItemColor { get; set; } = string.Empty;
public string DesignFilename { get; set; } = string.Empty;
public string ThumbnailImageFilename { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
public int? BaseAvatarItemId { get; set; }
public PreviewOrientationType PreviewOrientation { get; set; } = PreviewOrientationType.Front;
}
#endregion
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/equipment")]
[ApiController]
public class EquipmentController : ControllerBase
{
[HttpGet("v2/getUnlocked")]
public async Task<IActionResult> Saved()
{
return Ok(new List<object>());
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Enums;
using System.Text.Json;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/gameconfigs")]
[ApiController]
public class GameConfigsController : ControllerBase
{
private readonly List<GameConfigDto> gameConfigDtos = [];
public GameConfigsController()
{
if (gameConfigDtos.Count == 0)
{
var json = System.IO.File.ReadAllText(Path.Combine("data", "GameConfigs.json"));
var gameConfigs = JsonSerializer.Deserialize<List<GameConfigDto>>(json);
gameConfigDtos.AddRange(gameConfigs);
}
}
public class GameConfigDto
{
public required string Key { get; set; }
public required string Value { get; set; }
public string? ActiveExperiments { get; set; } = null;
}
[HttpGet("v1/all")]
public async Task<IActionResult> List()
{
return Ok(gameConfigDtos);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/gamerewards")]
[ApiController]
public class GameRewardsController : ControllerBase
{
[HttpGet("v1/pending")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Pending()
{
return Ok(new List<object>());
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/gamesight")]//we are not going to fucking pay for gamesight
[ApiController]
public class GamesightController : ControllerBase
{
[HttpPost("event")]
public async Task<IActionResult> Event()
{
return Ok(new
{
Success=true
});
}
}
}
+115
View File
@@ -0,0 +1,115 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/images")]
[ApiController]
public class ImagesController(IJwtService jwt, ILiteDbService db, ICdnService cdn, DiscordBotService discord) : ControllerBase
{
[HttpGet("v2/named")]
public async Task<IActionResult> Named()
{
return Ok(new List<object>());
}
[HttpGet("v5/cheered/bulk")]
public async Task<IActionResult> cheeredBulk()
{
return Ok(new List<object>());
}
[HttpGet("v5/player/{accId}")]
public async Task<IActionResult> FromPlayer(long accId)
{
var images = db.SavedImages.Include(x => x.Player).Find(x => x.Player.Id == accId && x.Accessibility == SavedImageAccessibility.Public).OrderByDescending(x => x.Id).Select(image => image.ToDictionary()).ToList();
return Ok(images);
}
public class SavedImageMetaDTO
{
[JsonPropertyName("playerIds")]
[Required]
public required List<ulong> PlayerIds { get; set; }
[JsonPropertyName("savedImageType")]
[Required]
public required SavedImageType SavedImageType { get; set; }
[JsonPropertyName("roomId")]
[Required]
public required long RoomId { get; set; }
[JsonPropertyName("accessibility")]
[Required]
public required SavedImageAccessibility Accessibility { get; set; }
}
[HttpPost("v4/uploadsaved")]
[Authorize(Roles = "gameClient")]
//[Consumes("image/jpeg")]
public async Task<IActionResult> UploadSaved([FromForm(Name = "image"), Required] IFormFile Image, [FromForm(Name = "imgMeta"), Required] string metaJson)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
SavedImageMetaDTO? Meta;
try
{
Meta = JsonSerializer.Deserialize<SavedImageMetaDTO>(metaJson);
if (Meta == null) return BadRequest("Invalid metadata format.");
}
catch { return BadRequest("Metadata is not valid JSON."); }
using var stream = Image.OpenReadStream();
string? remotePath = await cdn.UploadFile(
stream,
"img"
);
if (remotePath == null)
{
return StatusCode(500, "Failed to upload image to the CDN.");
}
SavedImage image = new()
{
Player = account,
PlayerIds = Meta.PlayerIds,
ImageName = remotePath,
SavedImageType = Meta.SavedImageType,
Accessibility = Meta.Accessibility,
RoomId = Meta.RoomId == -1 ? null : Meta.RoomId
};
db.SavedImages.Insert(image);
await discord.SendSavedImg(account, image);
return Ok(new { ImageName = remotePath });
}
[HttpGet("v6")]
public async Task<IActionResult> GetByName([FromQuery] string name)
{
SavedImage? image = db.SavedImages.Include(x => x.Player).FindOne(x => x.ImageName == name);
if (image == null) { return NotFound(); }
return Ok(image.ToDictionary());
}
[HttpGet("v4/room/{roomId}")]
public async Task<IActionResult> GetByRoom([FromQuery] long roomId)
{
var images = db.SavedImages.Include(x => x.Player).Find(x => x.RoomId == roomId && x.Accessibility == SavedImageAccessibility.Public).OrderByDescending(x => x.Id).Select(image => image.ToDictionary()).ToList();
return Ok(images);
}
}
}
+333
View File
@@ -0,0 +1,333 @@
using DeluxeBackend.Controllers.Matchmaking;
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Net.NetworkInformation;
using static DeluxeBackend.Controllers.Api.PlayerEventsController;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/inventions")]
[ApiController]
[Authorize(Roles = "gameClient")]
public class InventionsController(ILiteDbService db, IJwtService jwt, DiscordBotService discord) : ControllerBase
{
public class NewInventionRequestDTO
{
public required string name { get; set; }
public required string description { get; set; }
public required string imageName { get; set; }
public int instantiationCost { get; set; }
public int lightsCost { get; set; }
public int chipsCost { get; set; }
public int cloudVariablesCost { get; set; }
public int aiCost { get; set; }
public long creationRoomId { get; set; } = -1;
public required string inventionDataFilename { get; set; }
public required List<long> referencedInventions { get; set; }
public RoomRoleType creatorAccountRole { get; set; }
}
public class AddVersionInventionRequestDTO
{
public long inventionId { get; set; }
public int instantiationCost { get; set; }
public int lightsCost { get; set; }
public int chipsCost { get; set; }
public int cloudVariablesCost { get; set; }
public int aiCost { get; set; }
public long creationRoomId { get; set; } = -1;
public string? inventionDataFilename { get; set; }
public List<long>? referencedInventions { get; set; }
}
[Token(Token = "0x200056D")]
public enum FDOIOPFMNJL
{
[Token(Token = "0x4001262")]
Success,
[Token(Token = "0x4001263")]
InvalidParameters,
[Token(Token = "0x4001264")]
PlayerCannotUpload,
[Token(Token = "0x4001265")]
DuplicateName,
[Token(Token = "0x4001266")]
NameTooShort,
[Token(Token = "0x4001267")]
NameTooLong,
[Token(Token = "0x4001268")]
NotCreator,
[Token(Token = "0x4001269")]
DoesNotExist,
[Token(Token = "0x400126A")]
ImageDoesNotExist,
[Token(Token = "0x400126B")]
InventionLimitReached,
[Token(Token = "0x400126C")]
DescriptionTooLong,
[Token(Token = "0x400126D")]
InnapropriateName,
[Token(Token = "0x400126E")]
InnapropriateDescription,
[Token(Token = "0x400126F")]
CannotBeModified,
[Token(Token = "0x4001270")]
PlayerCannotPublish,
[Token(Token = "0x4001271")]
AlreadyPublished,
[Token(Token = "0x4001272")]
AlreadyUnpublished,
[Token(Token = "0x4001273")]
InventionUnderModerationReview,
[Token(Token = "0x4001274")]
PlayerCannotDownload,
[Token(Token = "0x4001275")]
PlayerAlreadyOwns,
[Token(Token = "0x4001276")]
DescriptionTooShort,
[Token(Token = "0x4001277")]
DoesNotHavePermission,
[Token(Token = "0x4001278")]
PermissionLevelCannotBeChanged,
[Token(Token = "0x4001279")]
AlreadyCheered,
[Token(Token = "0x400127A")]
AlreadyRemovedCheer,
[Token(Token = "0x400127B")]
ModeratorRestrictedPublishing,
[Token(Token = "0x400127C")]
PlayerCannotSell,
[Token(Token = "0x400127D")]
InvalidPrice,
[Token(Token = "0x400127E")]
PriceCannotBeChanged,
[Token(Token = "0x400127F")]
InvalidPermissionForPaidInvention,
[Token(Token = "0x4001280")]
PurchaseFailed,
[Token(Token = "0x4001281")]
CannotDownloadPaidInvention,
[Token(Token = "0x4001282")]
CannotSellUnownedLineage,
[Token(Token = "0x4001283")]
DoesNotAllowTrial,
[Token(Token = "0x4001284")]
StillOnTrialCooldown,
[Token(Token = "0x4001285")]
PlayerCannotTrial,
[Token(Token = "0x4001286")]
PaidInventionPublishingDisabled,
[Token(Token = "0x4001287")]
PaidInventionPurchasingDisabled,
[Token(Token = "0x4001288")]
OperationIsDisabled,
[Token(Token = "0x4001289")]
PlayerRestrictedFromP2PSelling,
[Token(Token = "0x400128A")]
PlayerNotRecRoomPlusMember,
[Token(Token = "0x400128B")]
InvalidInstantiationCost,
[Token(Token = "0x400128C")]
FeaturedInventionNotPublished,
[Token(Token = "0x400128D")]
FeaturedInventionNotActive,
[Token(Token = "0x400128E")]
InventionContainsBlockedFiles,
[Token(Token = "0x400128F")]
PlayerRestrictedFromP2PBuying
}
[HttpGet("v2/mine")]
public async Task<IActionResult> Mine()
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
var downloadedInventions = db.Inventions
.Include(x => x.Creator)
.Include(x => x.Room)
.Find(x => x.DownloadIds.Contains(account.Id))
.Select(x => x.ToDictionary())
.ToList();
return Ok(downloadedInventions);
}
[HttpPost("v6/save")]
public async Task<IActionResult> Save([FromBody] NewInventionRequestDTO request)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
if (!account.DiscordId.HasValue)
{
return Ok(new { Status = (int)FDOIOPFMNJL.PlayerNotRecRoomPlusMember });
}
if (!await discord.HasUserBoostedGuild(account.DiscordId.Value))
{
return Ok(new { Status = (int)FDOIOPFMNJL.PlayerNotRecRoomPlusMember });
}
Room? room = db.Rooms.FindById(request.creationRoomId);
if (room == null) return NotFound();
InventionVersion inventionVersion = new()
{
Id = 1,
ReplicationId = Guid.NewGuid(),
InstantiationCost = request.instantiationCost,
LightsCost = request.lightsCost,
ChipsCost = request.chipsCost,
CloudVariablesCost = request.cloudVariablesCost,
BlobName = request.inventionDataFilename
};
Invention invention = new()
{
ReplicationId = Guid.NewGuid(),
Creator = account,
Name = request.name,
Description = request.description,
ImageName = request.imageName,
CurrentVersionNumber = 1,
Accessibility = RoomAccessibility.Private,
Room = room,
Versions=[inventionVersion],
DownloadIds = [account.Id]
};
db.Inventions.Insert(invention);
return Ok(new { Status= (int)FDOIOPFMNJL.Success, Invention=invention.ToDictionary(), InventionVersion=inventionVersion.ToDictionary(invention.Id) });
}
[HttpGet("v1/versions")]
public async Task<IActionResult> Versions([FromQuery] long inventionId)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
Invention? invention = db.Inventions.FindById(inventionId);
if (invention == null)
{
return NotFound();
}
var versionList = invention.Versions.Select(v => v.ToDictionary(invention.Id)).ToList();
return Ok(versionList);
}
[HttpPatch("v4/addversion")]
public async Task<IActionResult> AddVersion([FromBody] AddVersionInventionRequestDTO request)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
Invention? invention = db.Inventions.Include(x => x.Creator).Include(x => x.Room).FindById(request.inventionId);
if (invention == null)
{
return Ok(new { Status = (int)FDOIOPFMNJL.DoesNotExist });
}
if (invention.Creator.Id != account.Id)
{
return Ok(new { Status = (int)FDOIOPFMNJL.NotCreator });
}
if (string.IsNullOrEmpty(request.inventionDataFilename))
{
return Ok(new { Status = (int)FDOIOPFMNJL.InvalidParameters });
}
if (!account.DiscordId.HasValue)
{
return Ok(new { Status = (int)FDOIOPFMNJL.PlayerNotRecRoomPlusMember });
}
if (!await discord.HasUserBoostedGuild(account.DiscordId.Value))
{
return Ok(new { Status = (int)FDOIOPFMNJL.PlayerNotRecRoomPlusMember });
}
int nextVersionNumber = invention.CurrentVersionNumber + 1;
InventionVersion inventionVersion = new()
{
Id = nextVersionNumber,
ReplicationId = Guid.NewGuid(),
InstantiationCost = request.instantiationCost,
LightsCost = request.lightsCost,
ChipsCost = request.chipsCost,
CloudVariablesCost = request.cloudVariablesCost,
BlobName = request.inventionDataFilename
};
invention.Versions.Add(inventionVersion);
invention.CurrentVersionNumber = nextVersionNumber;
invention.ModifiedAt = DateTime.UtcNow;
db.Inventions.Update(invention);
return Ok(new
{
Status = (int)FDOIOPFMNJL.Success,
Invention = invention.ToDictionary(),
InventionVersion = inventionVersion.ToDictionary(invention.Id)
});
}
[HttpGet("v2/batch")]
[HttpPost("v2/batch")]
public async Task<IActionResult> Versions([FromQuery(Name = "id")] List<long>? queryIds,[FromForm(Name = "id")] List<long>? formIds)
{
List<long> id = (queryIds != null && queryIds.Count > 0)
? queryIds
: (formIds ?? new List<long>());
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
if (id.Count == 0)
{
return Ok(new List<object>());
}
var inventionsBatch = db.Inventions
.Find(x => id.Contains(x.Id))
.Select(x => x.ToDictionary())
.ToList();
return Ok(inventionsBatch);
}
}
}
+186
View File
@@ -0,0 +1,186 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Numerics;
using System.Security.Claims;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/messages")]
[ApiController]
public class MessagesController : ControllerBase
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly INotificationService ws;
private readonly IMessageService messageService;
public List<MessageType> allowedMsgs =
[
MessageType.TextMessage,
MessageType.GameInvite,
MessageType.GameInviteDeclined,
MessageType.GameJoinFailed,
MessageType.FriendStatusOnline,
MessageType.RequestGameInvite,
MessageType.RequestGameInviteDeclined,
MessageType.PartyUpRequest
];
public MessagesController(ILiteDbService _db, IJwtService _jwt, INotificationService _ws, IMessageService _messageService)
{
db = _db;
jwt = _jwt;
ws = _ws;
messageService = _messageService;
}
[HttpGet("v2/get")]
[Authorize]
public async Task<IActionResult> get()
{
string? playerId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(playerId))
{
return Unauthorized();
}
Account player = db.Accounts.FindById((long)Convert.ToDouble(playerId));
if (player == null)
{
return Unauthorized();
}
var msgs = db.Messages
.Query()
.Include(x => x.Player)
.Include(x => x.FromPlayer)
.Where(x => x.Player.Id == player.Id)
.OrderByDescending(x => x.Id)
.ToList()
.Select(r => r.ToDictionary())
.ToList();
return Ok(msgs);
}
[HttpPost("v2/delete")]
[Authorize]
public async Task<IActionResult> delete([FromForm(Name = "Id"), Required] long msgId)
{
string? rawPlayerId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? User.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(rawPlayerId))
return Unauthorized();
if (!long.TryParse(rawPlayerId, out long playerId))
return Unauthorized();
Account? player = db.Accounts.FindById(playerId);
if (player == null)
return Unauthorized();
Message? message = db.Messages.Include(x => x.Player).FindById(msgId);
if (message == null)
{
return NotFound();
}
if (message.Player.Id != player.Id)
{
return StatusCode(403);
}
db.Messages.Delete(msgId);
return Ok();
}
public class V3DeleteRequestDto
{
public required List<long> MessageIds { get; set; }
}
[HttpPost("v3/delete")]
[Authorize]
public async Task<IActionResult> V3Delete([FromBody] V3DeleteRequestDto request)
{
string? rawPlayerId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? User.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(rawPlayerId))
return Unauthorized();
if (!long.TryParse(rawPlayerId, out long playerId))
return Unauthorized();
Account? player = db.Accounts.FindById(playerId);
if (player == null)
return Unauthorized();
if (request.MessageIds == null || request.MessageIds.Count == 0)
return BadRequest("No message IDs provided.");
foreach (ulong messageId in request.MessageIds)
{
Message? message = db.Messages
.Include(x => x.Player)
.FindById((long)messageId);
if (message == null)
continue;
if (message.Player.Id != player.Id)
continue;
db.Messages.Delete(messageId);
}
return Ok();
}
[HttpPost("v2/send")]
[Authorize]
public async Task<IActionResult> send([FromForm(Name = "ToPlayerId"), Required] long toPlayerId, [FromForm(Name = "Type"), Required] MessageType type, [FromForm(Name = "Data")] string data = "", [FromForm(Name = "RoomId")] long? roomId = null)
{
string? rawPlayerId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? User.FindFirst("sub")?.Value;
if (string.IsNullOrEmpty(rawPlayerId))
return Unauthorized();
if (!long.TryParse(rawPlayerId, out long playerId))
return Unauthorized();
Account? player = db.Accounts.FindById(playerId);
if (player == null)
return Unauthorized();
Account? player2 = db.Accounts.FindById(toPlayerId);
if (player2 == null)
return NotFound();
if (!allowedMsgs.Contains(type) && false)
{
return BadRequest();
}
await messageService.SendMessage(player, player2, type, data);
return Ok();
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/objectives")]
[ApiController]
public class ObjectivesController : ControllerBase
{
[HttpGet("v1/myprogress")]
public async Task<IActionResult> MyProgress()
{
return Ok(new { Objectives = new List<object>(), ObjectiveGroups= new List<object>() });
}
}
}
+280
View File
@@ -0,0 +1,280 @@
using DeluxeBackend.Controllers.Matchmaking;
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static DeluxeBackend.Controllers.Api.AvatarController;
using static DeluxeBackend.Controllers.RoomsController;
using static DeluxeBackend.Enums;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/playerevents")]
[ApiController]
public class PlayerEventsController(IJwtService jwt, ILiteDbService db, ICdnService cdn, INotificationService ws, DiscordBotService discord) : ControllerBase
{
[HttpGet("v1/all")]
public async Task<IActionResult> All()
{
return Ok(new { Created = new List<object>(), Responses = new List<object>() });
}
[Token(Token = "0x2000F45")]
public enum DNPDKMHJGAO
{
[Token(Token = "0x4003C04")]
Success,
[Token(Token = "0x4003C05")]
HasModeratorClosedEvent,
[Token(Token = "0x4003C06")]
DoesNotExist,
[Token(Token = "0x4003C07")]
PlayerDoesNotExist,
[Token(Token = "0x4003C08")]
RoomDoesNotExist,
[Token(Token = "0x4003C09")]
StatusUnchanged,
[Token(Token = "0x4003C0A")]
PrivateEvent,
[Token(Token = "0x4003C0B")]
SomethingWentWrong,
[Token(Token = "0x4003C0C")]
DoesNotOwnRoom,
[Token(Token = "0x4003C0D")]
ResponseDoesNotExist,
[Token(Token = "0x4003C0E")]
PlayerAlreadyInvited,
[Token(Token = "0x4003C0F")]
EventDatesInvalid,
[Token(Token = "0x4003C10")]
EventTooLong,
[Token(Token = "0x4003C11")]
EventTooShort,
[Token(Token = "0x4003C12")]
InappropriateName,
[Token(Token = "0x4003C13")]
InappropriateDescription,
[Token(Token = "0x4003C14")]
SomeInvitesFailed,
[Token(Token = "0x4003C15")]
CannotInviteJunior,
[Token(Token = "0x4003C16")]
EventCountLimitReached,
[Token(Token = "0x4003C17")]
DoesNotOwnEvent,
[Token(Token = "0x4003C18")]
UnregisteredOrJuniorNotAllowed,
[Token(Token = "0x4003C19")]
InvalidClubPermissions,
[Token(Token = "0x4003C1A")]
ImageDoesNotExist,
[Token(Token = "0x4003C1B")]
SubRoomDoesNotExist,
[Token(Token = "0x4003C1C")]
DoesNotOwnSubRoom,
[Token(Token = "0x4003C1D")]
ModifyTagsFailed,
[Token(Token = "0x4003C1E")]
RoomCapacityTooLow,
[Token(Token = "0x4003C1F")]
BroadcastEventNotMultiInstance,
[Token(Token = "0x4003C20")]
PlayerNotAllowedToCreateMultiInstanceEvents,
[Token(Token = "0x4003C21")]
PlayerBannedFromEventCreation,
[Token(Token = "0x4003C22")]
EventIsModerationClosed,
[Token(Token = "0x4003C23")]
EventIsModerationPendingReview
}
[HttpGet("v1/tagfilters")]
public async Task<IActionResult> Tagfilters()
{
return Ok(new
{
PinnedFilters = new[]
{
"workshops",
"celebration",
"game",
"meetup",
"performance",
"coop",
"grandopening",
"class",
"competition"
},
PopularFilters = new[]
{
"workshops",
"celebration",
"class",
"coop",
"competition",
"game",
"grandopening",
"meetup",
"performance"
},
TrendingFilters = (string[]?)null
});
}
public class CreateEventRequest
{
public RoomAccessibility Accessibility { get; set; }
public BroadcastPerms CanRequestBroadcastPermissions { get; set; } = BroadcastPerms.None;
public int? ClubId { get; set; }
public BroadcastPerms DefaultBroadcastPermissions { get; set; } = BroadcastPerms.None;
public string Description { get; set; } = string.Empty;
public DateTime EndTime { get; set; }
public string? ImageName { get; set; }
public bool IsMultiInstance { get; set; } = false;
public string Name { get; set; } = string.Empty;
public int RoomId { get; set; }
public DateTime StartTime { get; set; }
public int? SubRoomId { get; set; }
public bool SupportMultiInstanceRoomChat { get; set; } = false;
public List<string> Tags { get; set; } = new();
}
[HttpPost("v2")]
public async Task<IActionResult> V2([FromBody] CreateEventRequest request)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
/*if (request.IsMultiInstance && !account.HasRoleAsync(discord, "").Result)
{
//return Ok(new { Result = DNPDKMHJGAO.PlayerNotAllowedToCreateMultiInstanceEvents });
request.IsMultiInstance = false;
}*/
Room? room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Id == request.RoomId);
if (room == null)
{
return Ok(new { Result = DNPDKMHJGAO.RoomDoesNotExist });
}
if (room.Accessibility == RoomAccessibility.Private)
{
if (!room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Ok(new { Result = DNPDKMHJGAO.DoesNotOwnRoom });
}
}
List<SubRoom> subRooms = db.SubRooms.Include(x => x.Room).Find(x => x.Room.Id == room.Id && x.CanMatchmakeInto).ToList();
if (subRooms.Count == 0)
{
return Ok(new { Result = DNPDKMHJGAO.SubRoomDoesNotExist });
}
SubRoom subRoom = subRooms[Random.Shared.Next(subRooms.Count)];
PlayerEvent playerEvent = new()
{
Creator = account,
Room = room,
SubRoom = subRoom,
Name = request.Name,
Description=request.Description,
ImageName=request.ImageName ?? "",
StartTime=request.StartTime,
EndTime=request.EndTime,
Accessibility=request.Accessibility,
IsMultiInstance=request.IsMultiInstance,
SupportMultiInstanceRoomChat=request.SupportMultiInstanceRoomChat,
DefaultBroadcastPermissions=request.DefaultBroadcastPermissions,
CanRequestBroadcastPermissions=request.CanRequestBroadcastPermissions
};
db.PlayerEvents.Insert(playerEvent);
/*AccountPresence presence = GetOrCreatePresence(account);
RoomInstance roomInstance = CreateNewInstance(subRoom, Enums.RoomInstanceType.MultiInstanceEvent, true, playerEvent.Id);
playerEvent.BroadcastingRoomInstanceId = roomInstance.Id;
db.PlayerEvents.Update(playerEvent);
presence.Instance = roomInstance;
await SaveAndNotifyPresenceAsync(presence);*/
return Ok(new { Result = DNPDKMHJGAO.Success, PlayerEvent=playerEvent.ToDictionary()});
}
[HttpGet("v1/{eventId}")]
public async Task<IActionResult> GetEvent(long eventId)
{
PlayerEvent? playerEvent = db.PlayerEvents.Include(x => x.Room).Include(x => x.SubRoom).Include(x => x.Creator).FindById(eventId);
if (playerEvent == null)
{
return NotFound();
}
return Ok(playerEvent.ToDictionary());
}
[HttpGet("v1/{eventId}/responses")]
public async Task<IActionResult> GetEventResponses(long eventId)
{
PlayerEvent? playerEvent = db.PlayerEvents.Include(x => x.Room).Include(x => x.SubRoom).Include(x => x.Creator).FindById(eventId);
if (playerEvent == null)
{
return NotFound();
}
return Ok(new List<object>());
}
public class BroadcastRequest
{
public required long PlayerEventId { get; set; }
public required long BroadcastRoomInstanceId { get; set; }
}
[HttpPost("v1/broadcast")]
public async Task<IActionResult> Broadcast([FromBody] BroadcastRequest request)
{
PlayerEvent? playerEvent = db.PlayerEvents.Include(x => x.Room).Include(x => x.SubRoom).Include(x => x.Creator).FindById(request.PlayerEventId);
if (playerEvent == null)
{
return NotFound();
}
RoomInstance? instance = db.RoomInstances.Include(x => x.SubRoom).FindById(request.BroadcastRoomInstanceId);
if (instance == null)
{
return NotFound();
}
playerEvent.BroadcastingRoomInstanceId = request.BroadcastRoomInstanceId;
db.PlayerEvents.Update(playerEvent);
/*instance.InstanceType = RoomInstanceType.MultiInstanceEvent;
instance.Private = true;
db.RoomInstances.Update(instance);
await ws.SendToAllPlayer("RoomInstanceUpdate", instance.ToDictionary());*/
return Ok(new { Result = DNPDKMHJGAO.Success, PlayerEvent = playerEvent.ToDictionary() });
}
private AccountPresence GetOrCreatePresence(Account account)
{
var presence = db.Presences
.Include(x => x.Account)
.Include(x => x.Instance)
.Include(x => x.Instance!.SubRoom)
.Include(x => x.Instance!.SubRoom!.Room)
.Include(x => x.Instance!.SubRoom!.Room!.Creator)
.FindOne(x => x.Account.Id == account.Id);
return presence ?? new AccountPresence { Account = account };
}
private async Task SaveAndNotifyPresenceAsync(AccountPresence presence)
{
presence.LastOnline = DateTime.UtcNow;
presence.IsOnline = true;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
}
}
}
@@ -0,0 +1,110 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
public class PlayerReportingController(DiscordBotService discord, IJwtService jwt, INotificationService ws) : ControllerBase
{
[HttpGet("v1/moderationBlockDetails")]
[HttpPost("v1/moderationBlockDetails")]
public async Task<IActionResult> ModerationBlockDetails()
{
return Ok(new { ReportCategory =0, Duration =0, GameSessionId = 0, Message = "" });
}
[Token(Token = "0x2000C9B")]
public enum PGGOOHJPFPC
{
[Token(Token = "0x400315A")]
Obscured,
[Token(Token = "0x400315B")]
Time,
[Token(Token = "0x400315C")]
Inject,
[Token(Token = "0x400315D")]
GiftCount,
[Token(Token = "0x400315E")]
Engine,
[Token(Token = "0x400315F")]
UnknownDll,
[Token(Token = "0x4003160")]
ImageSignature,
[Token(Token = "0x4003161")]
AvatarHack,
[Token(Token = "0x4003162")]
NetworkCertificatePublicKey = 100,
[Token(Token = "0x4003163")]
NetworkCertificateIssuer,
[Token(Token = "0x4003164")]
NetworkCertificateMissing,
[Token(Token = "0x4003165")]
NetworkCertificateMismatch,
[Token(Token = "0x4003166")]
AutosaveChecksumMismatch = 150,
[Token(Token = "0x4003167")]
AutosaveSubRoomIdMismatch,
[Token(Token = "0x4003168")]
AutosaveChecksumException,
[Token(Token = "0x4003169")]
Photon_MissingHash = 200,
[Token(Token = "0x400316A")]
Photon_CorruptHash,
[Token(Token = "0x400316B")]
Photon_DifferentHash = 203,
[Token(Token = "0x400316C")]
AppData_Runtime_LengthMismatch = 300,
[Token(Token = "0x400316D")]
AppData_Runtime_LastWriteTimeMismatch,
[Token(Token = "0x400316E")]
AppData_Runtime_FileModified,
[Token(Token = "0x400316F")]
AppData_Boot_InvalidSignature = 310,
[Token(Token = "0x4003170")]
AppData_Boot_UnableToVerifySignatures,
[Token(Token = "0x4003171")]
Config_MissingHash = 320,
[Token(Token = "0x4003172")]
Config_DifferentHash,
[Token(Token = "0x4003173")]
Photon_InstantiateTool = 400,
[Token(Token = "0x4003174")]
Memory_Hash_Mismatch = 500,
[Token(Token = "0x4003175")]
Driver_Invalid_Signature = 600,
[Token(Token = "0x4003176")]
Native_Memory_Hash_Mismatch = 700
}
[HttpPost("v1/hile")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Hile([FromForm(Name = "Type")] PGGOOHJPFPC hileType, [FromForm(Name = "Message")] string message)
{
Account? account = await jwt.GetLogin(User);
if (hileType == PGGOOHJPFPC.UnknownDll)
{
return Ok(false);
}
if (message.Contains("main.2208069.com.AgainstGravity.RecRoom.obb"))
{
return Ok(false);
}
await discord.SendSelfReport(account, hileType, message);
//await ws.SendToPlayer(account.Id, PushNotification.ModerationQuitGame, new Dictionary<string, object>());
return Ok(false);
}
[HttpGet("v1/voteToKickReasons")]
public async Task<IActionResult> VoteToKickReasons()
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Controllers.Api.AvatarController;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/playerReputation")]
[ApiController]
public class PlayerReputationController : ControllerBase
{
[HttpGet("v2/bulk")]
public async Task<IActionResult> bulk([FromQuery(Name = "id")] List<long> accountIds)
{
List<Dictionary<string, object>> reputations = new List<Dictionary<string, object>>();
foreach (long item in accountIds)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
reputations.Add(new Dictionary<string, object>
{
["AccountId"] = item,
["IsCheerful"] = true,
["Noteriety"] = 0.0,
["CheerCredit"] = 99,
["CheerGeneral"] = 0,
["CheerHelpful"] = 0,
["CheerCreative"] = 0,
["CheerGreatHost"] = 0,
["CheerSportsman"] = 0
});
#pragma warning restore CS8625
}
return Ok(reputations);
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Controllers.Api.AvatarController;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/players")]
[ApiController]
public class PlayersController : ControllerBase
{
[HttpGet("v2/progression/bulk")]
public async Task<IActionResult> ProgressionBulk([FromQuery(Name = "id")] List<long> accountIds)
{
List<Dictionary<string, object>> reputations = [];
foreach (long item in accountIds)
{
reputations.Add(new Dictionary<string, object>
{
["PlayerId"] = item,
["Level"] = 1,
["XP"] = 0
});
}
return Ok(reputations);
}
}
}
@@ -0,0 +1,189 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/progressionEvents")]
[ApiController]
public class ProgressionEventsController : ControllerBase
{
[HttpGet("active")]
public async Task<IActionResult> Active()
{
return NotFound();
}
[HttpGet("event/{eventId}")]
public async Task<IActionResult> Event(long eventId)
{
return NotFound();
/*
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
return Ok(new
{
ProgressionEventId = 2,
Name = "Coming in Hot! Progression Event Alpha",
IsEnabled = true,
Rewards = new[]
{
new {
ProgressionEventRewardId = 1,
ProgressionEventId = 2,
GiftDropId = 3798,
ImageName = "7w7xumgr5rtaouze5e5lwspea.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 30,
RewardIndex = 0,
IsBonus = false,
IsRRPlusExclusive = false
},
new {
ProgressionEventRewardId = 2,
ProgressionEventId = 2,
GiftDropId = 3779,
ImageName = "eynegc0nils797wgjqz96a2ds.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 60,
RewardIndex = 1,
IsBonus = false,
IsRRPlusExclusive = false
},
new {
ProgressionEventRewardId = 3,
ProgressionEventId = 2,
GiftDropId = 3025,
ImageName = "2ikt2bx6xwiyyr6s8qducxjr1.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 90,
RewardIndex = 2,
IsBonus = false,
IsRRPlusExclusive = false
},
new {
ProgressionEventRewardId = 4,
ProgressionEventId = 2,
GiftDropId = 4079,
ImageName = "dziv7z4fwbejheoy7yvrjlby1.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 130,
RewardIndex = 3,
IsBonus = false,
IsRRPlusExclusive = false
},
new {
ProgressionEventRewardId = 6,
ProgressionEventId = 2,
GiftDropId = 4078,
ImageName = "6fepaaug17ai3guqpazy3v86o.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 180,
RewardIndex = 4,
IsBonus = false,
IsRRPlusExclusive = false
},
new {
ProgressionEventRewardId = 7,
ProgressionEventId = 2,
GiftDropId = 3974,
ImageName = "7958ooqtj2o04rkcdqtyb3rko.png",
ImageStream = (object)null,
ImageContentType = (object)null,
Xp = 250,
RewardIndex = 5,
IsBonus = false,
IsRRPlusExclusive = false
}
},
KeepsakeRoomLists = new[]
{
new {
KeepsakeRoomListId = 1,
ProgressionEventId = 3,
UnlockItemAvatarItemId = (object)null,
UnlockItemGiftDropId = (object)null,
UnlockItemLockDurationTicks = (object)null,
KeepsakeRooms = new[] {
new { KeepsakeRoomId = 1, RoomId = 1, KeepsakeRoomListId = 1, Type = 0, Order = 7 },
new { KeepsakeRoomId = 2, RoomId = 2, KeepsakeRoomListId = 1, Type = 0, Order = 8 },
new { KeepsakeRoomId = 3, RoomId = 3, KeepsakeRoomListId = 1, Type = 0, Order = 9 },
new { KeepsakeRoomId = 4, RoomId = 4, KeepsakeRoomListId = 1, Type = 0, Order = 10 },
new { KeepsakeRoomId = 5, RoomId = 5, KeepsakeRoomListId = 1, Type = 0, Order = 11 },
new { KeepsakeRoomId = 6, RoomId = 6, KeepsakeRoomListId = 1, Type = 0, Order = 12 },
new { KeepsakeRoomId = 7, RoomId = 7, KeepsakeRoomListId = 1, Type = 0, Order = 13 },
new { KeepsakeRoomId = 8, RoomId = 8, KeepsakeRoomListId = 1, Type = 0, Order = 14 },
new { KeepsakeRoomId = 11, RoomId = 9, KeepsakeRoomListId = 1, Type = 0, Order = 15 },
new { KeepsakeRoomId = 12, RoomId = 10, KeepsakeRoomListId = 1, Type = 0, Order = 16 },
new { KeepsakeRoomId = 13, RoomId = 11, KeepsakeRoomListId = 1, Type = 0, Order = 17 },
new { KeepsakeRoomId = 14, RoomId = 12, KeepsakeRoomListId = 1, Type = 0, Order = 18 },
new { KeepsakeRoomId = 15, RoomId = 13, KeepsakeRoomListId = 1, Type = 0, Order = 19 },
new { KeepsakeRoomId = 22, RoomId = 14, KeepsakeRoomListId = 1, Type = 0, Order = 6 },
new { KeepsakeRoomId = 23, RoomId = 15, KeepsakeRoomListId = 1, Type = 0, Order = 5 },
new { KeepsakeRoomId = 24, RoomId = 16, KeepsakeRoomListId = 1, Type = 0, Order = 4 },
new { KeepsakeRoomId = 25, RoomId = 17, KeepsakeRoomListId = 1, Type = 0, Order = 3 },
new { KeepsakeRoomId = 26, RoomId = 18, KeepsakeRoomListId = 1, Type = 0, Order = 2 },
new { KeepsakeRoomId = 27, RoomId = 29, KeepsakeRoomListId = 1, Type = 0, Order = 1 },
new { KeepsakeRoomId = 28, RoomId = 20, KeepsakeRoomListId = 1, Type = 0, Order = 0 },
new { KeepsakeRoomId = 37, RoomId = 21, KeepsakeRoomListId = 1, Type = 0, Order = 20 }
},
RoomUnlockStartOffsetTicks = 0,
RoomUnlockIntervalTicks = 0,
RoomUnlockBatchSize = 0,
RoomType = 0,
UnlockItemLockDuration = (object)null,
RoomUnlockStartOffset = "00:00:00",
RoomUnlockInterval = "00:00:00"
},
new {
KeepsakeRoomListId = 2,
ProgressionEventId = 3,
UnlockItemAvatarItemId = (object)null,
UnlockItemGiftDropId = (object)null,
UnlockItemLockDurationTicks = (object)null,
KeepsakeRooms = new[] {
new { KeepsakeRoomId = 16, RoomId = 1, KeepsakeRoomListId = 2, Type = 1, Order = 5 },
new { KeepsakeRoomId = 17, RoomId = 2, KeepsakeRoomListId = 2, Type = 1, Order = 6 },
new { KeepsakeRoomId = 18, RoomId = 3, KeepsakeRoomListId = 2, Type = 1, Order = 7 },
new { KeepsakeRoomId = 20, RoomId = 4, KeepsakeRoomListId = 2, Type = 1, Order = 8 },
new { KeepsakeRoomId = 29, RoomId = 5, KeepsakeRoomListId = 2, Type = 1, Order = 4 },
new { KeepsakeRoomId = 30, RoomId = 6, KeepsakeRoomListId = 2, Type = 1, Order = 3 },
new { KeepsakeRoomId = 31, RoomId = 7, KeepsakeRoomListId = 2, Type = 1, Order = 2 },
new { KeepsakeRoomId = 32, RoomId = 8, KeepsakeRoomListId = 2, Type = 1, Order = 1 },
new { KeepsakeRoomId = 33, RoomId = 9, KeepsakeRoomListId = 2, Type = 1, Order = 0 },
new { KeepsakeRoomId = 34, RoomId = 10, KeepsakeRoomListId = 2, Type = 1, Order = 9 },
new { KeepsakeRoomId = 36, RoomId = 11, KeepsakeRoomListId = 2, Type = 1, Order = 10 }
},
RoomUnlockStartOffsetTicks = 0,
RoomUnlockIntervalTicks = 0,
RoomUnlockBatchSize = 0,
RoomType = 1,
UnlockItemLockDuration = (object)null,
RoomUnlockStartOffset = "00:00:00",
RoomUnlockInterval = "00:00:00"
}
},
StartTime = "2022-09-13T23:00:00Z",
EndTime = "9999-09-16T23:00:00Z",
CollectionEndTime = "9999-09-17T23:00:00Z",
UsesBoost = true,
BoostDailyGameplayMinutesLimit = 20,
BoostXpMultiplier = 3.0,
PurchasableXpBoostId = (object)null,
ActiveExperiment = (object)null,
ChallengesIconImageName = (object)null,
RewardsPipImageName = (object)null,
EventInfoImageName = (object)null
});*/
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
}
[HttpGet("record/{eventId}")]
public async Task<IActionResult> Record(long eventId)
{
return NotFound();
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/quickPlay")]
[ApiController]
public class QuickPlayController : ControllerBase
{
[HttpGet("v1/getandclear")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> GetAndClear()
{
return Ok(null);
}
}
}
+364
View File
@@ -0,0 +1,364 @@
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Numerics;
using DeluxeBackend.Models;
using System.Security.Claims;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/relationships")]
[ApiController]
public class RelationshipsController : ControllerBase//this is from KittyRec https://git.tabbycluster.net/KittyRec/KittyRec-Api/src/branch/main/Controllers/relationshipsController.cs
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly INotificationService ws;
private readonly IMessageService messageService;
public RelationshipsController(ILiteDbService _db, IJwtService _jwt, INotificationService _ws, IMessageService _messageService)
{
db = _db;
jwt = _jwt;
ws = _ws;
messageService = _messageService;
}
private long? GetCurrentPlayerId()
{
string? raw = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value;
if (long.TryParse(raw, out long id)) return id;
return null;
}
private Relationship? GetRelationship(long playerId, long targetId) =>
db.Relationships
.Include(x => x.Player)
.Include(x => x.TargetPlayer)
.FindOne(x => x.Player.Id == playerId && x.TargetPlayer.Id == targetId);
private Relationship GetOrCreate(long playerId, long targetId)
{
var rel = GetRelationship(playerId, targetId);
if (rel != null) return rel;
rel = new Relationship
{
Player = db.Accounts.FindById(playerId),
TargetPlayer = db.Accounts.FindById(targetId)
};
db.Relationships.Insert(rel);
return rel;
}
private async Task PushRelToPlayer(long toPlayerId, long aboutPlayerId)
{
var rel = GetRelationship(toPlayerId, aboutPlayerId);
await ws.SendToPlayer(toPlayerId, PushNotification.RelationshipChanged, new Dictionary<string, object>
{
["PlayerID"] = aboutPlayerId,
["RelationshipType"] = (int)(rel?.RelationshipType ?? RelationshipType.None),
["Muted"] = (int)(rel?.Muted ?? MuteState.None),
["Ignored"] = (int)(rel?.Ignored ?? IgnoreState.None),
["Favorited"] = rel?.Favorited ?? 0
});
}
private void DeleteFriendInvites(long fromPlayerId, long toPlayerId)
{
var messageToDelete = db.Messages.FindOne(m =>
m.FromPlayer.Id == fromPlayerId &&
m.Player.Id == toPlayerId &&
m.Type == MessageType.FriendInvite);
if (messageToDelete != null)
{
db.Messages.Delete(messageToDelete.Id);
ws.SendToPlayer(toPlayerId, PushNotification.MessageDeleted, new Dictionary<string, object>
{
["Id"] = messageToDelete.Id
});
}
}
[HttpGet("v2/get")]
[Authorize]
public async Task<IActionResult> get()
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
var rels = db.Relationships
.Query()
.Include(x => x.Player)
.Include(x => x.TargetPlayer)
.Where(x => x.Player.Id == pid.Value)
.ToList()
.Select(r => r.ToDictionary())
.ToList();
return Ok(rels);
}
[HttpGet("v2/sendfriendrequest")]
[Authorize]
public async Task<IActionResult> SendFriendRequest([FromQuery] long id = 0)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (id == 0 || id == pid || db.Accounts.FindById(id) == null)
return Ok();
var myRel = GetOrCreate(pid.Value, id);
var theirRel = GetOrCreate(id, pid.Value);
if (myRel.RelationshipType == RelationshipType.Friend)
return Ok();
// they already sent us one — auto-accept both sides
if (theirRel.RelationshipType == RelationshipType.Sent || myRel.RelationshipType == RelationshipType.Received)
{
myRel.RelationshipType = RelationshipType.Friend;
theirRel.RelationshipType = RelationshipType.Friend;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
DeleteFriendInvites(pid.Value, id);
Account me = db.Accounts.FindById(pid.Value);
Account them = db.Accounts.FindById(id);
await messageService.SendMessage(me, them, MessageType.FriendRequestAccepted, pid.Value.ToString());
await PushRelToPlayer(id, pid.Value);
await PushRelToPlayer(pid.Value, id);
return Ok();
}
// new outgoing request
myRel.RelationshipType = RelationshipType.Sent;
theirRel.RelationshipType = RelationshipType.Received;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
DeleteFriendInvites(id, pid.Value);
Account sender = db.Accounts.FindById(pid.Value);
Account recipient = db.Accounts.FindById(id);
await messageService.SendMessage(sender, recipient, MessageType.FriendInvite, pid.Value.ToString());
await PushRelToPlayer(id, pid.Value);
await PushRelToPlayer(pid.Value, id);
return Ok(myRel.ToDictionary());
}
[HttpGet("v2/addfriend")]
[Authorize]
public async Task<IActionResult> AddFriend([FromQuery] long id = 0) =>
await SendFriendRequest(id);
[HttpGet("v2/acceptfriendrequest")]
[Authorize]
public async Task<IActionResult> AcceptFriendRequest([FromQuery] long id = 0)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (id == 0 || id == pid)
return Ok();
var myRel = GetOrCreate(pid.Value, id);
var theirRel = GetOrCreate(id, pid.Value);
if (theirRel.RelationshipType != RelationshipType.Sent && myRel.RelationshipType != RelationshipType.Received)
return Ok();
myRel.RelationshipType = RelationshipType.Friend;
theirRel.RelationshipType = RelationshipType.Friend;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
DeleteFriendInvites(pid.Value, id);
Account me = db.Accounts.FindById(pid.Value);
Account them = db.Accounts.FindById(id);
await messageService.SendMessage(me, them, MessageType.FriendRequestAccepted, pid.Value.ToString());
await PushRelToPlayer(id, pid.Value);
await PushRelToPlayer(pid.Value, id);
return Ok(myRel.ToDictionary());
}
[HttpGet("v2/removefriend")]
[Authorize]
public async Task<IActionResult> RemoveFriend([FromQuery] long id = 0)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (id != 0 && id != pid)
{
var myRel = GetRelationship(pid.Value, id);
var theirRel = GetRelationship(id, pid.Value);
if (myRel != null) { myRel.RelationshipType = RelationshipType.None; db.Relationships.Update(myRel); }
if (theirRel != null) { theirRel.RelationshipType = RelationshipType.None; db.Relationships.Update(theirRel); }
DeleteFriendInvites(pid.Value, id);
DeleteFriendInvites(id, pid.Value);
await PushRelToPlayer(id, pid.Value);
await PushRelToPlayer(pid.Value, id);
Dictionary<string, object> gg = new()
{
["PlayerID"] = pid,
["RelationshipType"] = (int)(myRel?.RelationshipType ?? RelationshipType.None),
["Muted"] = (int)(myRel?.Muted ?? MuteState.None),
["Ignored"] = (int)(myRel?.Ignored ?? IgnoreState.None),
["Favorited"] = myRel?.Favorited ?? 0
};
return Ok(gg);
}
return Ok();
}
[HttpGet("v1/favorite")]
[Authorize]
public async Task<IActionResult> Favorite([FromQuery] long id = 0)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (id != 0 && id != pid)
{
var rel = GetOrCreate(pid.Value, id);
rel.Favorited = 1;
db.Relationships.Update(rel);
return Ok(rel.ToDictionary());
}
return Ok();
}
[HttpGet("v1/unfavorite")]
[Authorize]
public async Task<IActionResult> Unfavorite([FromQuery] long id = 0)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (id != 0 && id != pid)
{
var rel = GetOrCreate(pid.Value, id);
rel.Favorited = 0;
db.Relationships.Update(rel);
return Ok(rel.ToDictionary());
}
return Ok();
}
[HttpPost("v1/mute")]
[Authorize]
public async Task<IActionResult> Mute([FromForm(Name = "PlayerId"), Required] long playerId)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (playerId == 0 || playerId == pid)
return Ok();
var myRel = GetOrCreate(pid.Value, playerId);
var theirRel = GetOrCreate(playerId, pid.Value);
myRel.Muted = MuteState.Local;
theirRel.Muted = MuteState.Remote;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
await PushRelToPlayer(pid.Value, playerId);
await PushRelToPlayer(playerId, pid.Value);
return Ok(myRel.ToDictionary());
}
[HttpPost("v1/unmute")]
[Authorize]
public async Task<IActionResult> Unmute([FromForm(Name = "PlayerId"), Required] long playerId)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (playerId == 0 || playerId == pid)
return Ok();
var myRel = GetOrCreate(pid.Value, playerId);
var theirRel = GetOrCreate(playerId, pid.Value);
myRel.Muted = MuteState.None;
if (theirRel.Muted == MuteState.Remote) theirRel.Muted = MuteState.None;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
await PushRelToPlayer(pid.Value, playerId);
await PushRelToPlayer(playerId, pid.Value);
return Ok(myRel.ToDictionary());
}
[HttpPost("v1/ignore")]
[Authorize]
public async Task<IActionResult> Ignore([FromForm(Name = "PlayerId"), Required] long playerId)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (playerId == 0 || playerId == pid)
return Ok();
var myRel = GetOrCreate(pid.Value, playerId);
var theirRel = GetOrCreate(playerId, pid.Value);
myRel.Ignored = IgnoreState.Local;
theirRel.Ignored = IgnoreState.Remote;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
await PushRelToPlayer(pid.Value, playerId);
await PushRelToPlayer(playerId, pid.Value);
return Ok(myRel.ToDictionary());
}
[HttpPost("v1/unignore")]
[Authorize]
public async Task<IActionResult> Unignore([FromForm(Name = "PlayerId"), Required] long playerId)
{
long? pid = GetCurrentPlayerId();
if (pid == null) return Unauthorized();
if (playerId == 0 || playerId == pid)
return Ok();
var myRel = GetOrCreate(pid.Value, playerId);
var theirRel = GetOrCreate(playerId, pid.Value);
myRel.Ignored = IgnoreState.None;
if (theirRel.Ignored == IgnoreState.Remote) theirRel.Ignored = IgnoreState.None;
db.Relationships.Update(myRel);
db.Relationships.Update(theirRel);
await PushRelToPlayer(pid.Value, playerId);
await PushRelToPlayer(playerId, pid.Value);
return Ok(myRel.ToDictionary());
}
[HttpPost("v1/bulkignoreplatformusers")]
public IActionResult BulkIgnorePlatformUsers() =>
Ok();
}
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
public class RoomConsumablesController : ControllerBase
{
[HttpGet("v1/roomConsumable/room/{roomId}")]
public async Task<IActionResult> Saved([FromRoute] long roomId)
{
return Ok(new List<object>());
}
[HttpGet("v1/roomConsumable/room/{roomId}/me")]
public async Task<IActionResult> myRoomConsumables([FromRoute] long roomId)
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
public class RoomCurrenciesController : ControllerBase
{
[HttpGet("v1/currencies")]
public async Task<IActionResult> Saved([FromQuery] long roomId)
{
return Ok(new List<object>());
}
[HttpGet("v1/getAllBalances")]
public async Task<IActionResult> GetAllBalances([FromQuery] long roomId)
{
return Ok(new List<object>());
}
}
}
+25
View File
@@ -0,0 +1,25 @@
using DeluxeBackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/roomkeys")]
[ApiController]
public class RoomKeysController : ControllerBase
{
[HttpGet("v1/mine")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> None()
{
return Ok(new List<object>());
}
[HttpGet("v1/room")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Room()
{
return Ok(new List<object>());
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using DeluxeBackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/sanitize")]
[ApiController]
public class SanitizeController : ControllerBase
{
[HttpPost("v1/isPure")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> IsPure()
{
return Ok(new { IsPure = true});
}
public class SanitizeRequest
{
public string Value { get; set; } = string.Empty;
public int ReplacementChar { get; set; } = 42;
}
[HttpPost("v1")]
public async Task<IActionResult> SanitizeMessage([FromBody] SanitizeRequest request)
{
var sanitized = JsonSerializer.Serialize(request.Value);
return Ok(sanitized);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/storefronts")]
[ApiController]
public class StorefrontsController : ControllerBase
{
[HttpGet("v3/giftdropstore/{type}")]
[Authorize]
public async Task<IActionResult> Thread([FromRoute] StorefrontType type)
{
return Ok(new
{
StorefrontType = (int)type,
NextUpdate = DateTime.MaxValue.ToString("O"),
StoreItems = new List<object>(),
SubscriberDiscountPercent = 0
});
}
[HttpGet("v4/balance/{type}")]
[Authorize]
public async Task<IActionResult> Thread([FromRoute] PMKKDOJNMGM type)
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/ugcPurchasables")]
[ApiController]
public class UgcPurchasablesController() : ControllerBase
{
}
}
+51
View File
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
namespace DeluxeBackend.Controllers.Api
{
[Route("api/versioncheck")]
[ApiController]
public class VersioncheckController : ControllerBase
{
[HttpGet("v1")]//this is for 2016 builds
public IActionResult V1([FromHeader(Name = "X-Rec-Room-Version")] string appVersion = "")
{
if (appVersion != ServerConfig.AppVersion)
{
return StatusCode(403);
}
return Ok();
}
[HttpGet("v2")]//this is for 2017/2018 builds
[HttpGet("v3")]
public IActionResult V3([FromQuery(Name = "v")] string appVersion = "")
{
return Ok(new
{
ValidVersion = appVersion == ServerConfig.AppVersion
});
}
[HttpGet("v4")]//this is for 2019 and later builds
public IActionResult V4([FromQuery(Name = "v"), Required] string appVersion, [FromQuery(Name = "p")] int? platform = null, [FromQuery(Name = "pid")] string? platformId = null)
{
int versionStatus = 0;
if (appVersion != ServerConfig.AppVersion)
{
versionStatus = 1;
}
return Ok(new
{
VersionStatus = versionStatus,
UpdateNotificationStage = 0,
IsVersionIslanded = false,
IsCrossPlayDisabled = false
});
}
}
}
+68
View File
@@ -0,0 +1,68 @@
using DeluxeBackend.Controllers.Auth; // Added to access ConnectController explicitly
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Threading.Tasks;
namespace DeluxeBackend.Controllers.Auth
{
[Route("Auth/account")]
[ApiController]
[Authorize]
public class AccountController(IJwtService jwt, ILiteDbService db, DiscordBotService discord) : ControllerBase
{
[HttpPost("me/remoteauth")]
public async Task<IActionResult> RemoteAuth([FromForm(Name = "code"), Required] string code)
{
if (string.IsNullOrWhiteSpace(code))
{
return BadRequest(new { error = "invalid_request", description = "Authorization code cannot be empty." });
}
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return Unauthorized(new { error = "unauthorized", description = "Session expired or invalid user profile." });
}
string? matchingServerCode = null;
string upperUserCode = code.Trim().ToUpperInvariant();
DateTime cachedCreatedAt = DateTime.UtcNow;
foreach (var kvp in ConnectController.DeviceAuthorizationKeys)
{
if (kvp.Value.UserCode == upperUserCode)
{
matchingServerCode = kvp.Key;
cachedCreatedAt = kvp.Value.CreatedAt;
break;
}
}
if (matchingServerCode == null)
{
return BadRequest(new { error = "invalid_code", description = "The authorization code is invalid or has expired." });
}
if (DateTime.UtcNow - cachedCreatedAt > TimeSpan.FromMinutes(15))
{
ConnectController.DeviceAuthorizationKeys.TryRemove(matchingServerCode, out _);
return BadRequest(new { error = "code_expired", description = "The authorization code has expired." });
}
var updatedTuple = (UserCode: upperUserCode, AccId: account.Id, CreatedAt: cachedCreatedAt);
if (!ConnectController.DeviceAuthorizationKeys.TryUpdate(matchingServerCode, updatedTuple, (upperUserCode, -1, cachedCreatedAt)))
{
return BadRequest(new { error = "transaction_conflict", description = "Authorization signature was modified or consumed." });
}
return Ok(new { Success = true, message = "Device authorized successfully. Your studio instance will resume shortly." });
}
}
}
+40
View File
@@ -0,0 +1,40 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Numerics;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Auth
{
[Route("Auth/cachedlogin")]
[ApiController]
public class CachedLoginController : ControllerBase
{
private readonly ILiteDbService db;
public CachedLoginController(ILiteDbService _db)
{
db = _db;
}
[HttpGet("forplatformid/{platform}/{platformId}")]
public async Task<IActionResult> ForPlatformId([FromRoute] PlatformType platform, [FromRoute] string platformId)
{
List<Dictionary<string, object>> logins = new List<Dictionary<string, object>>();
foreach (Cachedlogin item in db.Cachedlogins.Include(x => x.Account).Find(x => x.Platform == platform && x.PlatformId == platformId).OrderByDescending(x => x.LastLoginAt))
{
logins.Add(new Dictionary<string, object>
{
["platform"] = (int)item.Platform,
["platformId"] = item.PlatformId,
["accountId"] = item.Account.Id,
["lastLoginTime"] = item.LastLoginAt.ToString("O"),
["requirePassword"] = item.RequirePassword
});
}
return Ok(logins);
}
}
}
+364
View File
@@ -0,0 +1,364 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Auth
{
public class GameTokenRequestDto
{
[FromForm(Name = "grant_type"), Required]
public required string GrantType { get; set; }
[FromForm(Name = "client_id"), Required]
public required string ClientId { get; set; }
[FromForm(Name = "client_secret"), Required]
public required string ClientSecret { get; set; }
[FromForm(Name = "platform"), Required]
public required PlatformType Platform { get; set; }
[FromForm(Name = "platform_id"), Required]
public required string PlatformId { get; set; }
[FromForm(Name = "device_id"), Required]
public required string DeviceId { get; set; }
[FromForm(Name = "device_class"), Required]
public required DeviceClassType DeviceClass { get; set; }
[FromForm(Name = "time"), Required]
public required DateTimeOffset Time { get; set; }
[FromForm(Name = "ver"), Required]
public required string Version { get; set; }
[FromForm(Name = "cid"), Required]
public required int Cid { get; set; }
[FromForm(Name = "build_key"), Required]
public required string BuildKey { get; set; }
[FromForm(Name = "asid"), Required]
public required long Asid { get; set; }
[FromForm(Name = "locale"), Required]
public required string Locale { get; set; }
[FromForm(Name = "isInitialLogin"), Required]
public required bool IsInitialLogin { get; set; }
[FromForm(Name = "dinfo")]
[MaxLength(2000)]
public string? DeviceInfo { get; set; }
[FromForm(Name = "eac_challenge")]
public string? EacChallenge { get; set; }
[FromForm(Name = "eac_response")]
public string? EacResponse { get; set; }
[FromForm(Name = "platform_auth"), Required]
public required string PlatformAuthRaw { get; set; }
[FromForm(Name = "account_id")]
public long? AccountId { get; set; } = null;
[FromForm(Name = "refresh_token")]
public string? RefreshToken { get; set; } = null;
[FromForm(Name = "username")]
[MaxLength(256)]
public string? Username { get; set; } = null;
[FromForm(Name = "password")]
[MaxLength(256)]
public string? Password { get; set; } = null;
}
public class OculusPlatformAuth
{
public required string Nonce { get; set; }
public required string AppId { get; set; }
public required string Source { get; set; }
}
[Route("Auth/connect")]
[ApiController]
public class ConnectController : ControllerBase
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly IPasswordService password;
private readonly HttpClient httpClient;
public static readonly ConcurrentDictionary<string, (string UserCode, long AccId, DateTime CreatedAt)> DeviceAuthorizationKeys = new();
public ConnectController(ILiteDbService _db, IJwtService _jwtService, IPasswordService _password, HttpClient _httpClient)
{
db = _db;
jwt = _jwtService;
password = _password;
httpClient = _httpClient;
}
[HttpPost("gametoken")]
[HttpPost("token")]
public async Task<IActionResult> GameToken([FromForm, Required] GameTokenRequestDto request)
{
if (request.ClientId != "recroom")
{
return BadRequest(new { error = "invalid_client" });
}
switch (request.Platform)
{
case PlatformType.Oculus:
try
{
var auth = JsonConvert.DeserializeObject<OculusPlatformAuth>(request.PlatformAuthRaw);
if (auth == null || string.IsNullOrWhiteSpace(auth.Nonce) || string.IsNullOrWhiteSpace(auth.AppId))
{
return BadRequest(new { error = "invalid_grant", error_description = "Meta authentication failed. Missing required nonces." });
}
if (!ServerConfig.OvrAppSecret.TryGetValue(auth.AppId, out var ovrAppSecret))
{
return BadRequest(new { error = "invalid_grant", error_description = "Oculus integration context not configured." });
}
var postData = new Dictionary<string, string>
{
{ "access_token", $"OC|{auth.AppId}|{ovrAppSecret}" },
{ "user_id", request.PlatformId },
{ "nonce", auth.Nonce }
};
var formContent = new FormUrlEncodedContent(postData);
var metaResponse = await httpClient.PostAsync("https://graph.oculus.com/user_nonce_validate", formContent);
if (!metaResponse.IsSuccessStatusCode)
{
return StatusCode(502, new { error = "invalid_grant", error_description = "Upstream identity tracking challenge rejected." });
}
var metaContent = await metaResponse.Content.ReadAsStringAsync();
var metaJson = JObject.Parse(metaContent);
if (metaJson["is_valid"]?.Value<bool>() != true)
{
return BadRequest(new { error = "invalid_grant", error_description = "Meta identity tracking validation signature verification failed." });
}
}
catch (JsonException)
{
return BadRequest(new { error = "invalid_grant", error_description = "JSONDecodeError" });
}
catch (Exception)
{
return StatusCode(500, new { error = "invalid_grant", error_description = "Internal Meta handling error." });
}
break;
default:
return BadRequest(new { error = "unsupported_platform_type" });
}
Account? account = null;
switch (request.GrantType)
{
case "create_account":
Account newacc = new() { Username = Guid.NewGuid().ToString("N") };
db.Accounts.Insert(newacc);
account = newacc;
Cachedlogin cachedlogin = new()
{
Platform = request.Platform,
PlatformId = request.PlatformId,
LastLoginAt = DateTime.UtcNow,
Account = newacc
};
db.Cachedlogins.Insert(cachedlogin);
break;
case "cached_login":
if (request.AccountId == null || request.AccountId <= 0)
{
return BadRequest(new { error = "invalid_request" });
}
var cached = db.Cachedlogins.Include(x => x.Account)
.FindOne(x => x.Platform == request.Platform && x.PlatformId == request.PlatformId && x.Account.Id == request.AccountId);
if (cached != null)
{
cached.LastLoginAt = DateTime.UtcNow;
db.Cachedlogins.Update(cached);
account = cached.Account;
}
break;
case "refresh_token":
if (string.IsNullOrWhiteSpace(request.RefreshToken))
{
return BadRequest(new { error = "invalid_request" });
}
RefreshToken? refreshToken = db.RefreshTokens.Include(x => x.Account).FindOne(x => x.Token == request.RefreshToken && !x.IsRevoked);
if (refreshToken == null || refreshToken.Expires < DateTime.UtcNow)
{
return BadRequest(new { error = "invalid_grant", error_description = "Token expired or blacklisted." });
}
refreshToken.IsRevoked = true;
db.RefreshTokens.Update(refreshToken);
account = refreshToken.Account;
break;
case "password":
if (string.IsNullOrWhiteSpace(request.Password) || string.IsNullOrWhiteSpace(request.Username))
{
return BadRequest(new { error = "invalid_request" });
}
Account? account1 = db.Accounts.FindOne(x => x.Username == request.Username);
if (account1 == null)
{
return BadRequest(new { error = "invalid_grant", error_description = "invalid_username_or_password" });
}
if (password.VerifyPassword(account1, request.Password) == Microsoft.AspNetCore.Identity.PasswordVerificationResult.Failed)
{
return BadRequest(new { error = "invalid_grant", error_description = "invalid_username_or_password" });
}
account = account1;
Cachedlogin cachedlogin1 = new()
{
Platform = request.Platform,
PlatformId = request.PlatformId,
LastLoginAt = DateTime.UtcNow,
Account = account1
};
db.Cachedlogins.Insert(cachedlogin1);
break;
default:
return BadRequest(new { error = "unsupported_grant_type" });
}
if (account == null)
{
return BadRequest(new { error = "invalid_grant", error_description = "Authentication reference lookups resolved to null." });
}
return Ok(new
{
access_token = await jwt.GenAccessToken(account, ["gameClient"], new Dictionary<string, object>
{
["db.platform"] = (int)request.Platform,
["db.platform.id"] = request.PlatformId,
["db.deviceclass"] = (int)request.DeviceClass,
["db.locale"] = request.Locale,
["db.appver"] = request.Version
}),
refresh_token = await jwt.GenRefreshToken(account),
key = ServerConfig.SigKey
});
}
[HttpPost("deviceauthorization")]
public IActionResult DeviceAuthorization()
{
CleanExpiredDeviceCodes();
string userCode = GenerateSecureCode(6).ToUpperInvariant();
string serverCode = GenerateSecureCode(30).ToUpperInvariant();
DeviceAuthorizationKeys[serverCode] = (userCode, -1, DateTime.UtcNow);
return Ok(new
{
device_code = serverCode,
user_code = userCode,
verification_uri = string.Empty,
verification_uri_complete = "calc://60+7",
expires_in = int.MaxValue,
interval = 5
});
}
[HttpPost("studiotoken")]
public async Task<IActionResult> StudioToken([FromForm(Name = "device_code")] string deviceCode)
{
if (string.IsNullOrWhiteSpace(deviceCode) || !DeviceAuthorizationKeys.TryGetValue(deviceCode, out var authData))
{
return NotFound(new { error = "invalid_grant", error_description = "The provided authorization tracker code was not found." });
}
if (DateTime.UtcNow - authData.CreatedAt > TimeSpan.FromMinutes(15))
{
DeviceAuthorizationKeys.TryRemove(deviceCode, out _);
return BadRequest(new { error = "code_expired" });
}
if (authData.AccId == -1)
{
return BadRequest(new { error = "authorization_pending" });
}
Account account = db.Accounts.FindById(authData.AccId);
if (account == null)
{
return BadRequest(new { error = "no_account" });
}
DeviceAuthorizationKeys.TryRemove(deviceCode, out _);
return Ok(new
{
access_token = await jwt.GenAccessToken(account, ["studioClient"], new Dictionary<string, object>()),
refresh_token = await jwt.GenRefreshToken(account),
scope = string.Empty,
expires_in = 3600
});
}
private static void CleanExpiredDeviceCodes()
{
var cutoff = DateTime.UtcNow.AddMinutes(-15);
foreach (var kvp in DeviceAuthorizationKeys)
{
if (kvp.Value.CreatedAt < cutoff)
{
DeviceAuthorizationKeys.TryRemove(kvp.Key, out _);
}
}
}
private static string GenerateSecureCode(int length)
{
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return string.Create(length, chars, (buffer, symbols) =>
{
for (int i = 0; i < buffer.Length; i++)
{
buffer[buffer.Length - 1 - i] = symbols[RandomNumberGenerator.GetInt32(symbols.Length)];
}
});
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Auth
{
[Route("Auth/eac")]
[ApiController]
public class EacController : ControllerBase
{
[HttpGet("challenge")]
public async Task<IActionResult> C_hallenge()
{
return Ok("\"AQAAAHsg7mW5FQEE9HVl9EKMWXrqDzQxUCdgV/IPuQfbRgTx+cGnQqhhAgv1RvpihEC77gQ29JdoGFn2806Q+QPEj7nYg9C8pynbaiSVO8rKLJPvROsHuSXVJpQMv3TD8KyK3Y+n5bb86vAb5kRdZGD//uC8HY+D9jJLlEfTUlU=\"");
}
}
}
+62
View File
@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace DeluxeBackend.Controllers.Auth
{
[Route("Auth/photon")]
[ApiController]
public class PhotonController : ControllerBase
{
private readonly ILogger<PhotonController> _logger;
public PhotonController(ILogger<PhotonController> logger)
{
_logger = logger;
}
public class PhotonAuthResponse
{
[JsonPropertyName("ResultCode")]
public int ResultCode { get; set; } // 1 = Success, 2 = Fail, 3 = Invalid Params
[JsonPropertyName("Message")]
public string? Message { get; set; }
[JsonPropertyName("UserId")]
public string? UserId { get; set; }
[JsonPropertyName("Nickname")]
public string? Nickname { get; set; }
[JsonPropertyName("Data")]
public object? Data { get; set; }
}
/*[HttpPost()]
public async Task<IActionResult> Authenticate()
{
_logger.LogInformation("Photon Auth Request Query: {Query}", Request.QueryString.Value);
string requestBody = string.Empty;
using (var reader = new StreamReader(Request.Body))
{
requestBody = await reader.ReadToEndAsync();
}
_logger.LogInformation("Photon Auth Request Body: {Body}", requestBody);
return Ok(new PhotonAuthResponse
{
ResultCode = 1,
UserId = "2",
Nickname = "player",
Message = "Authentication verified successfully.",
Data = new { }
});
}*/
}
}
+36
View File
@@ -0,0 +1,36 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Auth
{
[Route("Auth/role")]
[ApiController]
public class RoleController : ControllerBase
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly DiscordBotService discord;
public RoleController(ILiteDbService _db, IJwtService _jwtService, DiscordBotService discordBotService)
{
db = _db;
jwt = _jwtService;
discord = discordBotService;
}
[HttpGet("{roleName}/{accId}")]
public async Task<IActionResult> HasRole(string roleName, long accId)
{
Account? account = db.Accounts.FindById(accId);
if (account == null)
{
return NotFound();
}
return Ok(await account.HasRoleAsync(discord, roleName));
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers
{
[Route("Chat")]
[ApiController]
public class ChatController : ControllerBase
{
[HttpGet("thread")]
[Authorize]
public async Task<IActionResult> Thread()
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Clubs
{
[Route("Clubs/announcements")]
[ApiController]
public class AnnouncementsController : ControllerBase
{
[HttpGet("v2/subscription/mine/unread")]
[HttpGet("v2/mine/unread")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> IdkWhatToCallThis()
{
return Ok(new List<object>());
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Clubs
{
[Route("Clubs/club")]
[ApiController]
public class ClubController : ControllerBase
{
[HttpGet("mine/member")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> IdkWhatToCallThis()
{
return Ok(new List<object>());
}
}
}
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Clubs
{
[Route("Clubs/subscription")]
[ApiController]
public class SubscriptionController : ControllerBase
{
[HttpGet("mine/member")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> IdkWhatToCallThis()
{
return Ok(new List<object>());
}
[HttpGet("subscriberCount/{accId}")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> SubscriberCount(long accId)
{
return Ok(0);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers
{
[Route("DataCollection")]
[ApiController]
public class DataCollectionController : ControllerBase
{
[HttpPost("data/{Event}")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> GetData([FromRoute] string Event)
{
return Ok(new { Success = true });
}
}
}
+160
View File
@@ -0,0 +1,160 @@
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SkiaSharp;
using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Text;
namespace DeluxeBackend.Controllers
{
[Route("Images")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly HttpClient httpClient;
private readonly string _cachePath;
public ImagesController(HttpClient httpClient, IWebHostEnvironment env)
{
this.httpClient = httpClient;
_cachePath = Path.Combine(env.ContentRootPath, "image_cache");
}
[HttpGet("{*filePath}")]
public async Task<IActionResult> GetImage(
string filePath,
[FromQuery] int? width = null,
[FromQuery] int? height = null,
[FromQuery] string? cropSquare = null,
[FromQuery] string? sig = null)
{
if (string.IsNullOrEmpty(filePath)) return BadRequest();
if (sig != null && sig != "p1") return BadRequest();
bool crop = cropSquare?.ToLower() switch
{
"1" => true,
"true" => true,
_ => false
};
string cacheKey = GenerateCacheKey(filePath, width, height, crop);
string cachedFilePath = Path.Combine(_cachePath, cacheKey + ".png");
byte[] imageBytes;
if (System.IO.File.Exists(cachedFilePath))
{
imageBytes = await System.IO.File.ReadAllBytesAsync(cachedFilePath);
}
else
{
string cdnUrl = $"{CdnService.Url.TrimEnd('/')}/img/{filePath}";
try
{
var response = await httpClient.GetAsync(cdnUrl);
if (!response.IsSuccessStatusCode) return NotFound();
using var stream = await response.Content.ReadAsStreamAsync();
using var original = SKBitmap.Decode(stream);
if (original == null) return await ServeRawFallback(cdnUrl, sig, filePath);
SKBitmap currentBitmap = original;
if (crop)
{
int size = Math.Min(original.Width, original.Height);
int x = (original.Width - size) / 2;
int y = (original.Height - size) / 2;
var subset = new SKBitmap(size, size);
original.ExtractSubset(subset, new SKRectI(x, y, x + size, y + size));
currentBitmap = subset;
}
if (width.HasValue || height.HasValue)
{
int w = width ?? (int)(currentBitmap.Width * ((float)height! / currentBitmap.Height));
int h = height ?? (int)(currentBitmap.Height * ((float)width! / currentBitmap.Width));
var info = new SKImageInfo(w, h);
var resized = new SKBitmap(info);
currentBitmap.ScalePixels(resized, SKSamplingOptions.Default);
if (currentBitmap != original) currentBitmap.Dispose();
currentBitmap = resized;
}
using (var image = SKImage.FromBitmap(currentBitmap))
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
{
imageBytes = data.ToArray();
}
if (currentBitmap != original) currentBitmap.Dispose();
if (!Directory.Exists(_cachePath)) Directory.CreateDirectory(_cachePath);
await System.IO.File.WriteAllBytesAsync(cachedFilePath, imageBytes);
}
catch
{
return await ServeRawFallback(cdnUrl, sig, filePath);
}
}
if (!string.IsNullOrEmpty(sig))
{
SignPayloadAndAppendHeader(imageBytes, sig);
}
return File(imageBytes, "image/png");
}
private async Task<IActionResult> ServeRawFallback(string url, string? sig, string? filename)
{
try
{
var response = await httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode) return NotFound();
byte[] rawBytes = await response.Content.ReadAsByteArrayAsync();
if (!string.IsNullOrEmpty(sig))
{
SignPayloadAndAppendHeader(Encoding.UTF8.GetBytes(filename), sig);
}
string contentType = response.Content.Headers.ContentType?.ToString() ?? "application/octet-stream";
return File(rawBytes, contentType);
}
catch
{
return StatusCode(500, "Error pipeline processing fallback asset safely.");
}
}
private void SignPayloadAndAppendHeader(byte[] payload, string sig)
{
using var rsa = RSA.Create();
rsa.ImportParameters(ServerConfig.RsaParams);
byte[] signatureBytes = rsa.SignData(payload, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
string base64Signature = Convert.ToBase64String(signatureBytes);
Response.Headers.Append("Content-Signature", $"key-id=KEY:RSA:{sig}.rec.net; data={base64Signature};");
}
private static string GenerateCacheKey(string path, int? w, int? h, bool crop)
{
string rawKey = $"{path}_{w}_{h}_{crop}";
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(rawKey));
return Convert.ToHexString(hashBytes);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Link
{
[Route("Link/actionlink")]
[ApiController]
public class ActionLinkController : ControllerBase
{
private readonly ILiteDbService db;
private readonly INotificationService ws;
private readonly IJwtService jwt;
public ActionLinkController(ILiteDbService _db, INotificationService _ws, IJwtService _jwt)
{
db = _db;
ws = _ws;
jwt= _jwt;
}
[HttpGet("{code}")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> LinkFromCode(string code)
{
ActionLink? actionLink = db.ActionLinks.FindOne(x => x.Code == code);
if (actionLink == null)
{
return NotFound();
}
return Ok(actionLink.ToDictionary());
}
[HttpPost("{code}/consume")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> ConsumeLink(string code)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
ActionLink? actionLink = db.ActionLinks.FindOne(x => x.Code == code);
if (actionLink == null)
{
return NotFound();
}
if (!actionLink.IsValid)
{
return NotFound();
}
actionLink.Uses += 1;
db.ActionLinks.Update(actionLink);
switch (actionLink.Type)
{
case ActionLinkType.DiscordLink:
account.DiscordId = actionLink.ExtraData!.DiscordUserId;
db.Accounts.Update(account);
await ws.SendToPlayer(account.Id, PushNotification.ModerationQuitGame, new Dictionary<string, object>());
break;
}
return Ok(null);
}
}
}
@@ -0,0 +1,56 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Data;
using static DeluxeBackend.Enums;
using System.Text.Json;
namespace DeluxeBackend.Controllers.Matchmaking
{
[Route("Matchmaking/invite")]
[ApiController]
[Authorize(Roles = "gameClient")]
public class InviteController(IJwtService jwt, ILiteDbService db, IMessageService message) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Invite([FromForm(Name = "roomInstanceId")] long RoomInstanceId, [FromForm(Name = "playerId")] long PlayerId)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
Account? account1 = db.Accounts.FindById(PlayerId);
if (account1 == null)
{
return Ok(new { Success = false });
}
RoomInstance roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).Include(x => x.SubRoom!.Room!.Creator).FindById(RoomInstanceId);
if (roomInstance == null)
{
return Ok(new { Success = false });
}
PlayerInvite playerInvite = new()
{
Account = account1,
InstanceId = roomInstance.Id,
InvitedBy = account,
ExpiresAt = DateTime.UtcNow.AddMinutes(10)
};
db.PlayerInvites.Insert(playerInvite);
await message.SendMessage(account, account1, MessageType.GameInviteV2, JsonSerializer.Serialize(new Dictionary<string, object>()
{
["inviteId"] = playerInvite.Id,
["name"] = "meow",
["roomInstanceId"] = roomInstance.Id
}));
return Ok(new { Success = true });
}
}
}
@@ -0,0 +1,458 @@
using DeluxeBackend.Controllers.Auth;
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Discord;
using Discord.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Matchmaking
{
[Token(Token = "0x2000BB7")]
public enum PLBMBHKLCHD
{
[Token(Token = "0x4002DF5")] UnknownError = -1,
[Token(Token = "0x4002DF6")] Success,
[Token(Token = "0x4002DF7")] NoSuchGame,
[Token(Token = "0x4002DF8")] PlayerNotOnline,
[Token(Token = "0x4002DF9")] InsufficientSpace,
[Token(Token = "0x4002DFA")] EventNotStarted,
[Token(Token = "0x4002DFB")] EventAlreadyFinished,
[Token(Token = "0x4002DFC")] BlockedFromRoom = 7,
[Token(Token = "0x4002DFD")] JuniorNotAllowed = 11,
[Token(Token = "0x4002DFE")] Banned,
[Token(Token = "0x4002DFF")] AlreadyInBestInstance,
[Token(Token = "0x4002E00")] InsufficientRelationship,
[Token(Token = "0x4002E01")] UpdateRequired = 16,
[Token(Token = "0x4002E02")] AlreadyInTargetInstance,
[Token(Token = "0x4002E03")] UGCNotAllowed = 19,
[Token(Token = "0x4002E04")] NoSuchRoom,
[Token(Token = "0x4002E05")] RoomIsNotActive = 22,
[Token(Token = "0x4002E06")] RoomBlockedByCreator,
[Token(Token = "0x4002E07")] RoomIsPrivate = 25,
[Token(Token = "0x4002E08")] RoomInstanceIsPrivate,
[Token(Token = "0x4002E09")] DeviceClassNotSupported = 30,
[Token(Token = "0x4002E0A")] DeviceClassNotSupportedByRoomOwner,
[Token(Token = "0x4002E0B")] MovementModeNotSupportedByRoomOwner,
[Token(Token = "0x4002E0C")] EventIsPrivate = 35,
[Token(Token = "0x4002E0D")] EventIsFull,
[Token(Token = "0x4002E0E")] RoomInviteExpired = 40,
[Token(Token = "0x4002E0F")] NoAvailableRegion = 45,
[Token(Token = "0x4002E10")] NotorietyTooPoor = 50,
[Token(Token = "0x4002E11")] BannedFromRoom = 55,
[Token(Token = "0x4002E12")] NoSuchClub = 70,
[Token(Token = "0x4002E13")] ClubHasNoClubhouse,
[Token(Token = "0x4002E14")] ClubIsNotActive = 73,
[Token(Token = "0x4002E15")] NotAMemberOfClub,
[Token(Token = "0x4002E16")] BannedFromClub,
[Token(Token = "0x4002E17")] InstanceJoinNotPermitted,
[Token(Token = "0x4002E18")] LevelTooLow,
[Token(Token = "0x4002E19")] ChatPartyInviteNotFound,
[Token(Token = "0x4002E1A")] ChatPartyInviteModerated,
[Token(Token = "0x4002E1B")] ChatMessageNotAnInvite,
[Token(Token = "0x4002E1C")] DeveloperOnly,
[Token(Token = "0x4002E1D")] RRPlusRequired,
[Token(Token = "0x4002E1F")] MetaJuniorAccountRestriction,
[Token(Token = "0x4002E1F")] NotExclusivelyLoggedIn,
[Token(Token = "0x4002E20")] AccountDoesNotExist
}
public enum JoinType
{
[Token(Token = "0x4002DF1")] PublicMatchmaking,
[Token(Token = "0x4002DF2")] PublicNewInstance,
[Token(Token = "0x4002DF3")] PrivateNewInstance
}
public class JoinRoomRequestDto
{
[Required] public bool BypassMovementModeRestriction { get; set; }
[Required] public int MaxPersistenceVersion { get; set; }
[Required] public JoinType JoinMode { get; set; }
public string? ClientJoinData { get; set; }
}
[Route("Matchmaking/matchmake")]
[ApiController]
[Authorize(Roles = "gameClient")]
public class MatchmakeController(IJwtService jwt, ILiteDbService db, INotificationService ws) : ControllerBase
{
private static readonly object _statsLock = new();
[HttpPost("none")]
public async Task<IActionResult> None()
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var presence = GetOrCreatePresence(login.Account);
presence.Instance = null;
presence.AppVersion = login.AppVersion;
await SaveAndNotifyPresenceAsync(presence);
return Ok(presence.ToDictionary());
}
[HttpPost("dorm")]
public async Task<IActionResult> Dorm([FromForm, Required] JoinRoomRequestDto request)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var account = login.Account;
var presence = GetOrCreatePresence(account);
var room = db.Rooms.Include(x => x.Creator).FindOne(x => x.IsDorm == true && x.Creator.Id == account.Id);
if (room == null)
{
var baseDorm = db.Rooms.Include(x => x.Creator).FindOne(x => x.IsDorm == true && x.Creator.Id == 1);
if (baseDorm == null)
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomIsNotActive });
}
room = InitializeNewDormRoom(account, baseDorm);
db.Rooms.Insert(room);
CloneSubRooms(room, baseDorm, account, login);
}
if (room.Accessibility == RoomAccessibility.Private && !room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomIsPrivate });
}
var subRooms = db.SubRooms.Include(x => x.Room).Find(x => x.Room.Id == room.Id && x.CanMatchmakeInto).ToList();
if (subRooms.Count == 0)
{
return Ok(new { errorCode = PLBMBHKLCHD.DeveloperOnly });
}
var subRoom = subRooms[Random.Shared.Next(subRooms.Count)];
var privateInstances = db.RoomInstances
.Include(x => x.SubRoom)
.Include(x => x.SubRoom!.Room)
.Include(x => x.SubRoom!.Room!.Creator)
.Find(x => x.SubRoom.Id == subRoom.Id && x.InstanceType == Enums.RoomInstanceType.Dormroom && x.EventId == -1)
.ToList();
var roomInstance = privateInstances.Count == 0
? CreateNewInstance(subRoom, Enums.RoomInstanceType.Dormroom, true)
: privateInstances[0];
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, room, login.AppVersion);
}
[HttpPost("room/{RoomId}")]
public async Task<IActionResult> GoToRoom([FromRoute] long RoomId, [FromForm, Required] JoinRoomRequestDto request)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Id == RoomId);
if (RoomId == 3 && DateTime.UtcNow.Month == 6)
{
room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Name == "PrideCenter");
}
if (room == null) return Ok(new { errorCode = PLBMBHKLCHD.NoSuchRoom });
var account = login.Account;
if (room.Accessibility == RoomAccessibility.Private && !room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomIsPrivate });
}
var subRooms = db.SubRooms.Include(x => x.Room).Find(x => x.Room.Id == room.Id && x.CanMatchmakeInto).ToList();
if (subRooms.Count == 0) return Ok(new { errorCode = PLBMBHKLCHD.DeveloperOnly });
var subRoom = subRooms[Random.Shared.Next(subRooms.Count)];
var presence = GetOrCreatePresence(account);
var roomInstance = ResolveRoomInstance(subRoom, room.Accessibility, request.JoinMode);
if (roomInstance == null) return Ok(new { errorCode = PLBMBHKLCHD.NoAvailableRegion });
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, room, login.AppVersion);
}
[HttpPost("room/{RoomId}/{SubRoomId}")]
public async Task<IActionResult> GoToSubRoom([FromRoute] long RoomId, [FromRoute] long SubRoomId, [FromForm, Required] JoinRoomRequestDto request)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Id == RoomId);
if (RoomId == 3 && DateTime.UtcNow.Month == 6)
{
room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Name == "PrideCenter");
}
if (room == null) return Ok(new { errorCode = PLBMBHKLCHD.NoSuchRoom });
var account = login.Account;
if (room.Accessibility == RoomAccessibility.Private && !room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomIsPrivate });
}
var subRoom = db.SubRooms.Include(x => x.Room).FindOne(x => x.Room.Id == room.Id && x.Id == SubRoomId);
if (subRoom == null) return Ok(new { errorCode = PLBMBHKLCHD.NoSuchGame });
var presence = GetOrCreatePresence(account);
var roomInstance = ResolveRoomInstance(subRoom, room.Accessibility, request.JoinMode);
if (roomInstance == null) return Ok(new { errorCode = PLBMBHKLCHD.NoAvailableRegion });
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, room, login.AppVersion);
}
[HttpPost("event/{EventId}")]
public async Task<IActionResult> GoToEvent([FromRoute] long EventId, [FromForm, Required] JoinRoomRequestDto request)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var playerEvent = db.PlayerEvents.Include(x => x.Room).Include(x => x.SubRoom).Include(x => x.Creator).FindById(EventId);
if (playerEvent == null) return NotFound();
var room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Id == playerEvent.Room.Id);
if (room == null) return Ok(new { errorCode = PLBMBHKLCHD.NoSuchRoom });
var account = login.Account;
if (room.Accessibility == RoomAccessibility.Private && !room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomIsPrivate });
}
var subRoom = db.SubRooms.Include(x => x.Room).FindOne(x => x.Room.Id == room.Id && x.Id == playerEvent.SubRoom.Id);
if (subRoom == null) return Ok(new { errorCode = PLBMBHKLCHD.NoSuchGame });
RoomInstance? roomInstance = null;
if (playerEvent.IsMultiInstance)
{
var roomInstances = db.RoomInstances.Include(x => x.SubRoom)
.Find(x => x.SubRoom.Id == playerEvent.SubRoom.Id && x.Id != playerEvent.BroadcastingRoomInstanceId && x.EventId == playerEvent.Id && !x.Private).ToList();
roomInstance = roomInstances.Count == 0
? CreateNewInstance(subRoom, Enums.RoomInstanceType.MultiInstanceEvent, false, EventId)
: roomInstances[Random.Shared.Next(roomInstances.Count)];
}
if (roomInstance == null) return Ok(new { errorCode = PLBMBHKLCHD.NoAvailableRegion });
var presence = GetOrCreatePresence(account);
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, room, login.AppVersion);
}
[HttpPost("invite/{InviteId}")]
public async Task<IActionResult> GoToInvite([FromRoute] long InviteId)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var account = login.Account;
var playerInvite = db.PlayerInvites.FindById(InviteId);
if (playerInvite == null || playerInvite.Account.Id != account.Id || !playerInvite.IsValid)
{
return Ok(new { errorCode = PLBMBHKLCHD.RoomInviteExpired });
}
var roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).Include(x => x.SubRoom!.Room!.Creator).FindById(playerInvite.InstanceId);
if (roomInstance == null) return Ok(new { errorCode = PLBMBHKLCHD.NoAvailableRegion });
var presence = GetOrCreatePresence(account);
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, roomInstance.SubRoom.Room, login.AppVersion);
}
[HttpPost("instance/{instanceId}")]
public async Task<IActionResult> GoToInstance([FromRoute] long instanceId)
{
var login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
var roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).Include(x => x.SubRoom!.Room!.Creator).FindById(instanceId);
if (roomInstance == null) return NotFound();
if (roomInstance.Private) return Ok(new { errorCode = PLBMBHKLCHD.RoomInstanceIsPrivate });
var account = login.Account;
var presence = GetOrCreatePresence(account);
return await ProcessPresenceTransitionAsync(presence, roomInstance, account, roomInstance.SubRoom.Room, login.AppVersion);
}
#region Helpers
private AccountPresence GetOrCreatePresence(Account account)
{
var presence = db.Presences
.Include(x => x.Account)
.Include(x => x.Instance)
.Include(x => x.Instance!.SubRoom)
.Include(x => x.Instance!.SubRoom!.Room)
.Include(x => x.Instance!.SubRoom!.Room!.Creator)
.FindOne(x => x.Account.Id == account.Id);
return presence ?? new AccountPresence { Account = account };
}
private async Task SaveAndNotifyPresenceAsync(AccountPresence presence)
{
presence.LastOnline = DateTime.UtcNow;
presence.IsOnline = true;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
}
private RoomInstance CreateNewInstance(SubRoom subRoom, RoomInstanceType type, bool isPrivate, long eventId = -1)
{
var instance = new RoomInstance
{
SubRoom = subRoom,
InstanceType = type,
Private = isPrivate,
PhotonRegion = "eu",
PhotonRoom = Guid.NewGuid().ToString(),
EventId = eventId,
CreatedAt = DateTime.UtcNow
};
db.RoomInstances.Insert(instance);
return instance;
}
private RoomInstance? ResolveRoomInstance(SubRoom subRoom, RoomAccessibility accessibility, JoinType joinMode)
{
if (accessibility == RoomAccessibility.Private)
{
var privateInstances = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room)
.Find(x => x.SubRoom.Id == subRoom.Id && x.InstanceType == Enums.RoomInstanceType.Private && x.EventId == -1).ToList();
return privateInstances.Count == 0
? CreateNewInstance(subRoom, Enums.RoomInstanceType.Private, true)
: privateInstances[0];
}
return joinMode switch
{
JoinType.PublicMatchmaking => GetOrCreatePublicInstance(subRoom),
JoinType.PublicNewInstance => CreateNewInstance(subRoom, Enums.RoomInstanceType.Public, false),
JoinType.PrivateNewInstance => CreateNewInstance(subRoom, Enums.RoomInstanceType.Private, true),
_ => null
};
}
private RoomInstance GetOrCreatePublicInstance(SubRoom subRoom)
{
var roomInstances = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room)
.Find(x => x.SubRoom.Id == subRoom.Id && x.InstanceType == Enums.RoomInstanceType.Public && x.EventId == -1).ToList();
return roomInstances.Count == 0
? CreateNewInstance(subRoom, Enums.RoomInstanceType.Public, false)
: roomInstances[Random.Shared.Next(roomInstances.Count)];
}
private async Task<IActionResult> ProcessPresenceTransitionAsync(AccountPresence presence, RoomInstance targetInstance, Account account, Room room, string appVersion)
{
if (presence.Instance?.Id == targetInstance.Id)
{
return Ok(new { errorCode = PLBMBHKLCHD.AlreadyInTargetInstance });
}
UpdateRoomStats(account, room);
presence.Instance = targetInstance;
presence.AppVersion = appVersion;
await SaveAndNotifyPresenceAsync(presence);
return Ok(new
{
errorCode = PLBMBHKLCHD.Success,
roomInstance = targetInstance.ToDictionary()
});
}
private static Room InitializeNewDormRoom(Account account, Room baseDorm)
{
return new Room
{
Name = Guid.NewGuid().ToString(),
IsDorm = true,
SupportedPlayerTypes = baseDorm.SupportedPlayerTypes,
Accessibility = RoomAccessibility.Private,
ImageName = baseDorm.ImageName,
Description = baseDorm.Description,
Creator = account,
CustomWarning = baseDorm.CustomWarning,
WarningMask = baseDorm.WarningMask,
DataBlob = baseDorm.DataBlob,
DisableMicAutoMute = baseDorm.DisableMicAutoMute,
DisableRoomComments = baseDorm.DisableRoomComments,
EncryptVoiceChat = baseDorm.EncryptVoiceChat,
MinLevel = baseDorm.MinLevel,
PersistenceVersion = baseDorm.PersistenceVersion
};
}
private void CloneSubRooms(Room targetRoom, Room sourceRoom, Account account, LoginWithInfoResult login)
{
var sourceSubRooms = db.SubRooms.Include(x => x.Room).Include(x => x.CurrentSave).Find(x => x.Room.Id == sourceRoom.Id);
foreach (var subRoom in sourceSubRooms)
{
var newSubRoom = new SubRoom
{
Room = targetRoom,
LocationId = subRoom.LocationId,
Name = subRoom.Name,
IsSandbox = subRoom.IsSandbox,
MaxPlayers = subRoom.MaxPlayers,
CanMatchmakeInto = subRoom.CanMatchmakeInto,
SupportsJoinInProgress = subRoom.SupportsJoinInProgress,
UseLevelBasedMatchmaking = subRoom.UseLevelBasedMatchmaking,
UseAgeBasedMatchmaking = subRoom.UseAgeBasedMatchmaking,
UseRecRoyaleMatchmaking = subRoom.UseRecRoyaleMatchmaking
};
db.SubRooms.Insert(newSubRoom);
if (subRoom.CurrentSave != null)
{
var newSave = new SubRoomSave
{
SubRoomId = newSubRoom.Id,
DataBlob = subRoom.CurrentSave.DataBlob,
SavedByAccountId = account.Id,
SavedOnPlatform = login.Platform,
SavedOnDeviceClass = login.DeviceClass,
Description = $"Cloned From ^{targetRoom.Name}",
UnityAssetId = subRoom.CurrentSave.UnityAssetId,
PersistenceVersion = subRoom.CurrentSave.PersistenceVersion
};
db.SubRoomSave.Insert(newSave);
newSubRoom.CurrentSave = newSave;
db.SubRooms.Update(newSubRoom);
}
}
}
private void UpdateRoomStats(Account account, Room room)
{
lock (_statsLock)
{
if (!room.Stats.VisitorIds.Contains(account.Id))
{
room.Stats.VisitorIds.Add(account.Id);
}
room.Stats.VisitCount += 1;
db.Rooms.Update(room);
account.VisitedRooms[room.Id] = DateTime.UtcNow;
db.Accounts.Update(account);
}
}
#endregion
}
}
+221
View File
@@ -0,0 +1,221 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Matchmaking
{
[Route("Matchmaking/player")]
[ApiController]
public class PlayerController : ControllerBase
{
private readonly IJwtService jwt;
private readonly ILiteDbService db;
private readonly INotificationService ws;
public PlayerController(IJwtService _jwt, ILiteDbService _db, INotificationService _ws) {
jwt = _jwt;
db = _db;
ws = _ws;
}
[HttpPost("login")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Login()
{
return Ok("");
}
[HttpPost("exclusivelogin")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> ExclusiveLogin()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
if (presence == null)
{
presence = new AccountPresence
{
Account = account,
};
}
presence.Instance = null;
presence.LastOnline = DateTime.UtcNow;
presence.IsOnline = true;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
return Ok("");
}
[HttpGet("connection-info")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> ConnectioniInfo([FromQuery(Name ="roomInstanceId")] long? roomInstanceId = null)
{
//return Ok(new { success = false });
string? photonRegion = null;
string? photonRoomId = null;
if (roomInstanceId.HasValue)
{
RoomInstance? roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).FindById(roomInstanceId.Value);
if (roomInstance != null)
{
photonRegion = roomInstance.PhotonRegion;
photonRoomId = roomInstance.PhotonRoom;
}
}
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8601 // Possible null reference assignment.
return Ok(new { success =true, value = new Dictionary<string, object>() {
["photonAuthToken"] = "Meow",
["photonRealtimeAppId"] = "1b822489-5923-4d99-a0c9-7ca8b39e3481",
["photonVoiceAppId"] = "5e54ba56-1c6d-4d7d-95b3-000c1b4f1d82",
["photonChatAppId"] = "df1cd903-1d6a-4d74-ab85-19cb5ba38760",
["photonRegion"] = photonRegion,
["photonRoomId"] = photonRoomId,
//["VoiceServerId"] = "test-voice-5",
//["VoiceConnectionInfo"] = "192.168.1.171:6791"
} });
#pragma warning restore CS8601
#pragma warning restore CS8625
}
[HttpPost("heartbeat")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Heartbeat()
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
if (presence == null)
{
presence = new AccountPresence
{
Account = account,
};
}
presence.LastOnline = DateTime.UtcNow;
presence.IsOnline = true;
presence.AppVersion = login.AppVersion;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
return Ok(presence.ToDictionary());
}
[HttpGet]
[Authorize]
public async Task<IActionResult> Player([FromQuery(Name = "id")] List<long> accountIds)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
List<Dictionary<string, object>> presences = new List<Dictionary<string, object>>();
foreach (var accountId in accountIds)
{
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == accountId);
if (presence == null)
{
presence = new AccountPresence { Account = db.Accounts.FindById(accountId) };
}
presences.Add(presence.ToDictionary());
}
return Ok(presences);
}
[HttpPut("gameserverregionpings")]
[Authorize]
public async Task<IActionResult> gameserverregionpings()
{
return Ok(new { Success = true });
}
[HttpPut("statusvisibility")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> StatusVisibility([FromForm] StatusVisibilityType statusVisibility)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
if (presence == null)
{
presence = new AccountPresence
{
Account = account,
};
}
presence.StatusVisibility = statusVisibility;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
return Ok(presence.ToDictionary());
}
[HttpPut("vrmovementmode")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> VrMovementMode([FromForm] VrMovementModeType vrMovementMode)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
if (presence == null)
{
presence = new AccountPresence
{
Account = account,
};
}
presence.VrMovementMode = vrMovementMode;
db.Presences.Upsert(presence);
await ws.SendToPlayerSubs(account.Id, "PresenceUpdate", presence.ToDictionary());
return Ok(presence.ToDictionary());
}
[HttpPost("logout")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Logout()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
if (presence == null)
{
presence = new AccountPresence
{
Account = account,
};
}
presence.Instance = null;
presence.LastOnline = DateTime.UtcNow;
presence.IsOnline = false;
db.Presences.Upsert(presence);
await ws.SendToAllPlayer("PresenceUpdate", presence.ToDictionary());
return Ok("");
}
}
}
+103
View File
@@ -0,0 +1,103 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Matchmaking
{
[Route("Matchmaking/room")]
[ApiController]
[Authorize(Roles = "gameClient")]
public class RoomController(IJwtService jwt, ILiteDbService db, DiscordBotService discord) : ControllerBase
{
[HttpGet("{roomId}/instances")]
public async Task<IActionResult> Instances(long roomId)
{
var account = await jwt.GetLogin(User);
if (account == null)
{
return Unauthorized();
}
var room = db.Rooms.FindById(roomId);
if (room == null)
{
return NotFound();
}
bool isMod = await account.HasRoleAsync(discord, "moderator") || await account.HasRoleAsync(discord, "developer");
if (!isMod && !room.HasRole(account.Id, RoomRoleType.Moderator))
{
return StatusCode(StatusCodes.Status403Forbidden, "Permission Denied");
}
var query = db.RoomInstances.Query()
.Include(x => x.SubRoom)
.Include(x => x.SubRoom!.Room)
.Where(x => x.SubRoom.Room.Id == roomId);
if (!isMod)
{
query = query.Where(x => x.InstanceType == RoomInstanceType.Public);
}
var instances = query.ToList();
if (instances.Count == 0)
{
return Ok(Array.Empty<object>());
}
var instanceIds = instances.Select(x => x.Id).ToList();
var presences = db.Presences.Query()
.Include(x => x.Account)
.Include(x => x.Instance)
.Where(x => x.Instance != null && instanceIds.Contains(x.Instance.Id))
.ToList();
var playersByInstance = presences
.Where(x => x.Instance != null && x.Account != null)
.GroupBy(x => x.Instance!.Id)
.ToDictionary(
g => g.Key,
g => g.Select(p => p.Account.Id).ToList()
);
var result = instances
.OrderByDescending(x =>
playersByInstance.TryGetValue(x.Id, out var ids) ? ids.Count : 0)
.Select(x =>
{
var playerIds = playersByInstance.TryGetValue(x.Id, out var ids) ? ids : [];
return MapToDictionary(x, playerIds);
});
return Ok(result);
}
#region Helpers
private static Dictionary<string, object> MapToDictionary(RoomInstance roomInstance, List<long> playerIds)
{
return new Dictionary<string, object>
{
["RoomInstanceId"] = roomInstance.Id,
["RoomId"] = roomInstance.SubRoom?.Room?.Id ?? 0,
["SubRoomId"] = roomInstance.SubRoom?.Id ?? 0,
["IsFull"] = false,
["CreatedAt"] = roomInstance.CreatedAt.ToString("O"),
["PlayerIds"] = playerIds
};
}
#endregion
}
}
@@ -0,0 +1,98 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers.Matchmaking
{
[Route("Matchmaking/roominstance")]
[ApiController]
[Authorize(Roles = "gameClient")]
public class RoomInstanceController(IJwtService jwt, ILiteDbService db, INotificationService ws) : ControllerBase
{
[HttpPut("{roominstanceId}/markprivate")]
public async Task<IActionResult> MakePrivate(long roominstanceId)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
AccountPresence presence = GetOrCreatePresence(account);
if (presence.Instance == null)
{
return BadRequest("Not in a Instance");
}
RoomInstance? roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).Include(x => x.SubRoom!.Room!.Creator).FindById(roominstanceId);
if (roomInstance == null) return NotFound();
if (roomInstance.Id != presence.Instance!.Id)
{
return StatusCode(403);
}
if (roomInstance.InstanceType == RoomInstanceType.Public)
{
roomInstance.InstanceType = RoomInstanceType.Private;
}
roomInstance.Private= true;
db.RoomInstances.Update(roomInstance);
await ws.SendToPlayerSubs(account.Id, "RoomInstanceUpdate", roomInstance.ToDictionary());
return Ok(new { Success = true });
}
[HttpPut("{roominstanceId}/inprogress")]
public async Task<IActionResult> SetInProgress(long roominstanceId, [FromForm] bool inprogress)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
AccountPresence presence = GetOrCreatePresence(account);
if (presence.Instance == null)
{
return BadRequest("Not in a Instance");
}
RoomInstance? roomInstance = db.RoomInstances.Include(x => x.SubRoom).Include(x => x.SubRoom!.Room).Include(x => x.SubRoom!.Room!.Creator).FindById(roominstanceId);
if (roomInstance == null) return NotFound();
if (roomInstance.Id != presence.Instance!.Id)
{
return StatusCode(403);
}
if (roomInstance.GameInProgress == inprogress)
{
return Ok(new { Success = true });
}
roomInstance.GameInProgress= inprogress;
db.RoomInstances.Update(roomInstance);
await ws.SendToPlayerSubs(account.Id, "RoomInstanceUpdate", roomInstance.ToDictionary());
return Ok(new { Success = true });
}
[HttpPut("{roominstanceId}/roomCode")]
public async Task<IActionResult> SetRoomCode(long roominstanceId, [FromForm, Required] string roomCode, [FromForm, Required] bool forceChange)
{
return StatusCode(501);
}
[HttpPost("{roominstanceId}/reportjoinresult")]
public async Task<IActionResult> ReportJoinResult(long roominstanceId)
{
return StatusCode(501);
}
private AccountPresence GetOrCreatePresence(Account account)
{
var presence = db.Presences
.Include(x => x.Account)
.Include(x => x.Instance)
.Include(x => x.Instance!.SubRoom)
.Include(x => x.Instance!.SubRoom!.Room)
.Include(x => x.Instance!.SubRoom!.Room!.Creator)
.FindOne(x => x.Account.Id == account.Id);
return presence ?? new AccountPresence { Account = account };
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using DeluxeBackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Moderation
{
[Route("Moderation/voice")]
[ApiController]
public class VoiceController : ControllerBase
{
[HttpGet("config")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Config()
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
Dictionary<string, object> config = new Dictionary<string, object>()
{
["accountId"] = null,
["apiKey"] = null,
["submitAppealUrl"] = null,
["submitExternalModerationUrl"] = null
};
#pragma warning restore CS8625
return Ok(config);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Enums;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace DeluxeBackend.Controllers
{
[Route("nameserver")]
[ApiController]
public class NameServerController : ControllerBase
{
private readonly Dictionary<string, string> nameserData = [];
[HttpGet]
public async Task<IActionResult> Ns()
{
if (nameserData.Count == 0)
{
foreach (Service service in Enum.GetValues<Service>())
{
if (service == Service.WWW || service == Service.API || service == Service.Econ)
{
nameserData.Add(service.ToString(), $"https://{Request.Host}");
continue;
}
if (service == Service.CDN)
{
#if DEBUG
nameserData.Add(service.ToString(), "https://YOUR-URL");
#else
nameserData.Add(service.ToString(), "https://YOUR-URL");
#endif
continue;
}
nameserData.Add(service.ToString(), $"https://{Request.Host}/{service}/");
}
}
return Ok(nameserData);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers
{
[Route("Notifications/hub/v1/negotiate")]
[ApiController]
public class NotificationsNegotiate: ControllerBase
{
[HttpGet]
[HttpPost]
[Authorize]
public async Task<IActionResult> Ns()
{
return Ok(new
{
negotiateVersion = 2,
//connectionId = "meow",
//accessToken = "Meow",
availableTransports = new[]
{
new
{
transport = "WebSockets",
transferFormats = new[] { "Text", "Binary" }
}
}
});
}
}
}
@@ -0,0 +1,21 @@
using DeluxeBackend.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using static DeluxeBackend.Controllers.Api.MessagesController;
namespace DeluxeBackend.Controllers.PlatformNotifications
{
[Route("PlatformNotifications/accounts")]
[ApiController]
public class AccountsController : ControllerBase
{
[HttpGet("{accId}/receives/GameplayInvites")]
[Authorize]
public async Task<IActionResult> ReceivesGameplayInvites(long accId)
{
return Ok(false);
}
}
}
+88
View File
@@ -0,0 +1,88 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System.ComponentModel.DataAnnotations;
namespace DeluxeBackend.Controllers
{
[Route("PlayerSettings")]
[ApiController]
public class PlayerSettingsController : ControllerBase
{
private readonly IJwtService jwt;
private readonly ILiteDbService db;
public PlayerSettingsController(IJwtService _jwt, ILiteDbService _db)
{
jwt = _jwt;
db = _db;
}
private Dictionary<string, string> SettingRoomToDictionary(AccountSetting setting)
{
return new Dictionary<string, string>
{
["Key"] = setting.Key,
["Value"] = setting.Value
};
}
[HttpGet("playersettings")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Get()
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
return Ok(db.Settings.Find(x => x.Account.Id == account.Id).Select(p => SettingRoomToDictionary(p)).ToList());
}
[HttpPut("playersettings")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Put([FromForm] string key, [FromForm] string value)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountSetting setting = db.Settings.FindOne(x => x.Account.Id == account.Id && x.Key == key);
if (setting == null)
{
setting = new AccountSetting() { Account=account, Key=key, Value=value};
}
else
{
setting.Value = value;
}
db.Settings.Upsert(setting);
return Ok(new { Success = true });
}
[HttpDelete("playersettings")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Delete([FromForm] string key)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return NotFound("User not found in database");
}
AccountSetting setting = db.Settings.FindOne(x => x.Account.Id == account.Id && x.Key == key);
if (setting == null)
{
return NotFound("Key not found");
}
return Ok(new { Success = db.Settings.Delete(setting.Id) });
}
}
}
+141
View File
@@ -0,0 +1,141 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static DeluxeBackend.Enums;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static DeluxeBackend.Extensions.RoomExtensions;
namespace DeluxeBackend.Controllers.Rooms
{
[Route("Rooms")]
[ApiController]
public class GetController(ILiteDbService db, IJwtService jwt, DiscordBotService discord) : ControllerBase
{
[HttpGet("rooms")]
public async Task<IActionResult> Room([FromQuery(Name = "name"), Required] string roomName, [FromQuery(Name = "include")] RoomDetailsMask include = RoomDetailsMask.None)
{
Room? room = db.Rooms.Include(x => x.Creator).FindOne(x => x.Name == roomName);
if (room == null) return NotFound();
Account? account = await jwt.GetLogin(User);
if (room.Accessibility == RoomAccessibility.Private)
{
if (account == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner))
{
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
presence ??= new AccountPresence
{
Account = account,
};
bool blockData = true;
if (presence.Instance != null)
{
if (presence.Instance!.SubRoom!.Room!.Id == room.Id)
{
blockData = false;
}
}
if (blockData) { include &= ~RoomDetailsMask.DataBlob; }
}
}
if (room.Accessibility == RoomAccessibility.Unlisted)
{
if (account == null) return NotFound();
}
if (account == null)
{
include &= ~RoomDetailsMask.DataBlob;
}
return Ok(await room.ToDictionary(discord, db, include));
}
[HttpGet("rooms/{roomId}")]
public async Task<IActionResult> RoomById([FromRoute(Name = "roomId"), Required] int roomId, [FromQuery(Name = "include")] RoomDetailsMask include = RoomDetailsMask.None)
{
Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId);
if (room == null) return NotFound();
Account? account = await jwt.GetLogin(User);
if (room.Accessibility == RoomAccessibility.Private)
{
if (account == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner))
{
AccountPresence presence = db.Presences.Include(x => x.Account).Include(x => x.Instance).Include(x => x.Instance!.SubRoom).Include(x => x.Instance!.SubRoom!.Room).Include(x => x.Instance!.SubRoom!.Room!.Creator).FindOne(x => x.Account.Id == account.Id);
presence ??= new AccountPresence
{
Account = account,
};
bool blockData = true;
if (presence.Instance != null)
{
if (presence.Instance!.SubRoom!.Room!.Id == room.Id)
{
blockData = false;
}
}
if (blockData) { include &= ~RoomDetailsMask.DataBlob; }
}
}
if (room.Accessibility == RoomAccessibility.Unlisted)
{
if (account == null) return NotFound();
}
if (account == null)
{
include &= ~RoomDetailsMask.DataBlob;
}
return Ok(await room.ToDictionary(discord, db, include));
}
[HttpGet("rooms/bulk")]
public async Task<IActionResult> Bulk([FromQuery(Name = "name")] List<string> roomNames, [FromQuery(Name = "id")] List<long> roomIds)
{
roomNames ??= [];
roomIds ??= [];
Account? account = await jwt.GetLogin(User);
var rooms = db.Rooms
.Include(x => x.Creator)
.FindAll()
.Where(x =>
roomIds.Contains(x.Id) ||
roomNames.Contains(x.Name))
.ToList();
List<Dictionary<string, object>> response = [];
foreach (var room in rooms)
{
if (room.Accessibility == RoomAccessibility.Private)
{
if (account == null)
{
continue;
}
}
if (room.Accessibility == RoomAccessibility.Unlisted)
{
if (account == null)
{
continue;
}
}
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
}
}
@@ -0,0 +1,108 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Rooms
{
[Route("Rooms/rooms/{roomId:long}/interactionby")]
[ApiController]
[Authorize]
public class InteractionByController(ILiteDbService db, IJwtService jwt) : ControllerBase
{
[HttpGet("me")]
public async Task<IActionResult> Me([FromRoute] long roomId)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
string? lastVisitedAt = null;
if (account.VisitedRooms != null && account.VisitedRooms.TryGetValue(roomId, out var visitDate))
{
lastVisitedAt = visitDate.ToString("O");
}
return Ok(new
{
Cheered = room.Stats.CheeredIds.Contains(account.Id),
Favorited = room.Stats.FavoritedIds.Contains(account.Id),
LastVisitedAt = lastVisitedAt
});
}
[HttpPut("me/cheer")]
public async Task<IActionResult> Cheer([FromRoute] long roomId)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.Stats.CheeredIds.Contains(account.Id))
{
room.Stats.CheeredIds.Add(account.Id);
db.Rooms.Update(room);
}
return Ok(new { Success = true });
}
[HttpDelete("me/cheer")]
public async Task<IActionResult> RemoveCheer([FromRoute] long roomId)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (room.Stats.CheeredIds.Contains(account.Id))
{
room.Stats.CheeredIds.Remove(account.Id);
db.Rooms.Update(room);
}
return Ok(new { Success = true });
}
[HttpPut("me/favorite")]
public async Task<IActionResult> Favorite([FromRoute] long roomId)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.Stats.FavoritedIds.Contains(account.Id))
{
room.Stats.FavoritedIds.Add(account.Id);
db.Rooms.Update(room);
}
return Ok(new { Success = true });
}
[HttpDelete("me/favorite")]
public async Task<IActionResult> RemoveFavorite([FromRoute] long roomId)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (room.Stats.FavoritedIds.Contains(account.Id))
{
room.Stats.FavoritedIds.Remove(account.Id);
db.Rooms.Update(room);
}
return Ok(new { Success = true });
}
}
}
+182
View File
@@ -0,0 +1,182 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.ComponentModel.DataAnnotations;
using static DeluxeBackend.Enums;
using static DeluxeBackend.Extensions.RoomExtensions;
namespace DeluxeBackend.Controllers.Rooms
{
[Route("Rooms/rooms/{roomId:long}")]
[ApiController]
[Authorize]
public class RoomEditController(ILiteDbService db,IJwtService jwt,DiscordBotService discord,INotificationService ws) : ControllerBase
{
[HttpPut("automute")]
public async Task<IActionResult> Automute([FromRoute] long roomId, [FromForm] bool disable)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
room.DisableMicAutoMute = disable;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("cloning")]
public async Task<IActionResult> Cloning([FromRoute] long roomId, [FromForm] bool cloningAllowed)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
room.CloningAllowed = cloningAllowed;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("creator")]
public async Task<IActionResult> Creator([FromRoute] long roomId, [FromForm] ulong accountId)
{
return Ok(new { Success = false, Error = "Room ownership transfers are currently disabled." });
}
[HttpPut("comments")]
public async Task<IActionResult> Comments([FromRoute] long roomId, [FromForm] bool disable)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
room.DisableRoomComments = disable;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("voice_chat_encryption")]
public async Task<IActionResult> VoiceChatEncryption([FromRoute] long roomId, [FromForm] bool encryptVoiceChat)
{
return Ok(new { Success = false, Error = "Voice chat encryption settings cannot be modified manually." });
}
[HttpPut("restrictions")]
public async Task<IActionResult> Restrictions(
[FromRoute] long roomId,
[FromForm] bool supportsScreens,
[FromForm] bool supportsWalkVR,
[FromForm] bool supportsTeleportVR,
[FromForm] bool supportsJuniors)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
RoomSupports flags = RoomSupports.None;
if (supportsScreens) flags |= RoomSupports.Screens;
if (supportsWalkVR) flags |= RoomSupports.WalkVR;
if (supportsTeleportVR) flags |= RoomSupports.TeleportVR;
if (supportsJuniors) flags |= RoomSupports.Juniors;
room.SupportedPlayerTypes = flags;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("warning")]
public async Task<IActionResult> Warning([FromRoute] long roomId, [FromForm] RoomWarningMask warningMask, [FromForm] string customWarning = "")
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
// Mitigation: Guard against severe memory buffer bloat / database inflation DoS vectors
if (customWarning != null && customWarning.Length > 500)
{
return BadRequest(new { Success = false, Error = "Custom warning string length cannot exceed 500 characters." });
}
room.WarningMask = warningMask;
room.CustomWarning = string.IsNullOrWhiteSpace(customWarning) ? string.Empty : customWarning.Trim();
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("image")]
public async Task<IActionResult> Image([FromRoute] long roomId, [FromForm] string imageName = "")
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
// Mitigation: Defend against Path Traversal escape sequences (e.g., "../../../etc")
string sanitizedImage = Path.GetFileName(imageName);
if (imageName != sanitizedImage && !string.IsNullOrWhiteSpace(imageName))
{
return BadRequest(new { Success = false, Error = "Invalid room image asset format." });
}
room.ImageName = imageName;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpPut("accessibility")]
public async Task<IActionResult> Accessibility([FromRoute] long roomId, [FromForm] RoomAccessibility accessibility)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
Room? room = db.Rooms.FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner)) return Forbid();
room.Accessibility = accessibility;
db.Rooms.Update(room);
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
return Ok(new { Success = true, Value = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
}
}
+569
View File
@@ -0,0 +1,569 @@
using DeluxeBackend.Extensions;
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Numerics;
using System.Security.Principal;
using System.Text.Json;
using System.Text.Json.Serialization;
using static DeluxeBackend.Enums;
using static DeluxeBackend.Extensions.RoomExtensions;
namespace DeluxeBackend.Controllers
{
[Route("Rooms")]
[ApiController]
public class RoomsController : ControllerBase
{
private readonly ILiteDbService db;
private readonly IJwtService jwt;
private readonly ICdnService cdn;
private readonly DiscordBotService discord;
private readonly bool setup = false;
public RoomsController(ILiteDbService _db, IJwtService _jwtService, DiscordBotService _discord, ICdnService _cdn)
{
db = _db;
jwt = _jwtService;
discord = _discord;
cdn = _cdn;
if (!setup)
{
setup = true;
var json = System.IO.File.ReadAllText(Path.Combine("data", "AGRoomRuntimeConfig.json"));
AGRoomRuntimeConfig activityRuntimeConfig = JsonSerializer.Deserialize<AGRoomRuntimeConfig>(json) ?? throw new Exception("Failed to deserialize");
Account mainPlayer = db.Accounts.FindById(1) ?? throw new Exception("player not found");
foreach (AGRoomRuntimeConfig.Room room in activityRuntimeConfig.Rooms)
{
if (db.Rooms.FindOne(x => x.Name == room.Name) != null)
{
continue;
}
Room newRoom = new Room
{
Accessibility = room.Accessibility,
Name = room.Name,
Description = room.Description,
CloningAllowed = room.CloningAllowed,
Creator = mainPlayer,
CustomWarning = room.CustomRoomWarning,
WarningMask = room.RoomWarningMask,
DisableMicAutoMute = room.DisableMicAutoMute,
DisableRoomComments = room.DisableRoomComments,
EncryptVoiceChat = false,
IsDorm = room.Name == "DormRoom",
IsRRO = true,
};
RoomSupports RoomSup = RoomSupports.None;
if (room.SupportsScreens) RoomSup |= RoomSupports.Screens;
if (room.SupportsWalkVR) RoomSup |= RoomSupports.WalkVR;
if (room.SupportsTeleportVR) RoomSup |= RoomSupports.TeleportVR;
RoomSup |= RoomSupports.Juniors;
newRoom.SupportedPlayerTypes = RoomSup;
db.Rooms.Insert(newRoom);
foreach (var scene in room.Scenes)
{
SubRoom newScene = new()
{
Room = newRoom,
LocationId = scene.RoomSceneLocationId,
Name = scene.Name,
IsSandbox = scene.IsSandbox,
MaxPlayers = scene.MaxPlayers,
CanMatchmakeInto = scene.CanMatchmakeInto,
SupportsJoinInProgress = scene.SupportsJoinInProgress,
UseLevelBasedMatchmaking = scene.UseLevelBasedMatchmaking,
UseAgeBasedMatchmaking = scene.UseAgeBasedMatchmaking,
UseRecRoyaleMatchmaking = scene.UseRecRoyaleMatchmaking
};
db.SubRooms.Insert(newScene);
}
}
}
}
[HttpGet("rooms/hot")]
public async Task<IActionResult> Hot([FromQuery] string? tag, [FromQuery] int skip = 0, [FromQuery] int take = 32)
{
var query = db.Rooms.Query().Where(x => x.Accessibility == RoomAccessibility.Public);
if (!string.IsNullOrWhiteSpace(tag))
{
if (tag.ToLower() == "rro")
{
query = query.Where(x => x.IsRRO == true);
}
}
var roomEntities = query.OrderByDescending(x => x.Stats.VisitCount).Skip(skip).Limit(take).ToList();
var tasks = roomEntities.Select(async p => await p.ToDictionary(discord, db));
var rooms = (await Task.WhenAll(tasks)).ToList();
return Ok(new
{
Results = rooms,
TotalResults = rooms.Count
});
}
[HttpGet("rooms/{roomId}/subrooms/{subroomId}/saves")]
[Authorize]
public async Task<IActionResult> RoomSaves([FromRoute] int roomId, [FromRoute] int subroomId, [FromQuery] int skip, [FromQuery] int take)
{
Account? account = await jwt.GetLogin(User);
if (account == null) return NotFound("User not found in database");
Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return StatusCode(403);
}
SubRoom? subRoom = db.SubRooms.Include(x => x.Room).FindById(subroomId);
if (subRoom == null) return NotFound();
if (subRoom.Room.Id != roomId)
{
return NotFound();
}
var saves = db.SubRoomSave.Find(x => x.SubRoomId == subroomId).OrderByDescending(x => x.Id);
List<SubRoomSave> subRoomSaves = saves.Skip(skip).Take(take).ToList();
return Ok(new
{
Results = subRoomSaves.Select(x => x.ToDictionary()).ToList(),
TotalResults = saves.Count()
});
}
[HttpPut("rooms/{roomId}/subrooms/{subroomId}/permissions")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Permissions([FromRoute] int roomId, [FromRoute] int subroomId)
{
return Ok(new
{
Success = true
});
}
/*[HttpGet("subroom/{subroomId}/MakeSAVE")]
public async Task<IActionResult> MakeSave([FromRoute] int subroomId, [FromQuery] string DataBlob, [FromQuery] string? UnityAssetId = null)
{
SubRoom? room = db.SubRooms.FindById(subroomId);
if (room == null) return NotFound();
SubRoomSave Save = new SubRoomSave()
{
DataBlob = DataBlob,
SubRoomId = subroomId,
UnityAssetId = UnityAssetId
};
db.SubRoomSave.Insert(Save);
room.CurrentSave = Save;
db.SubRooms.Update(room);
return Ok(new { hiii = "meow :3" });
}*/
[HttpGet("rooms/createdby/me")]
[Authorize]
public async Task<IActionResult> CreatedbyMe()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return NotFound("User not found in database");
List<Dictionary<string, object>> response = [];
var foundRooms = db.Rooms.Include(x => x.Creator).Find(x => x.Creator != null && x.Creator.Id == account.Id);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
[HttpGet("rooms/ownedby/me")]
[Authorize]
public async Task<IActionResult> OwnedbybyMe()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return NotFound("User not found in database");
List<Dictionary<string, object>> response = [];
var foundRooms = db.Rooms.Include(x => x.Creator)
.FindAll()
.Where(x =>
(x.Creator != null && x.Creator.Id == account.Id) ||
(x.Roles != null && x.Roles.Any(r => r.AccountId == account.Id && r.Role >= RoomRoleType.CoOwner))
);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
[HttpGet("photon_access_token")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> PhotonAccessToken()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return NotFound("User not found in database");
AccountPresence presence = db.Presences.Include(x => x.Account)
.Include(x => x.Instance)
.Include(x => x.Instance!.SubRoom)
.Include(x => x.Instance!.SubRoom!.Room)
.Include(x => x.Instance!.SubRoom!.Room!.Creator)
.FindOne(x => x.Account.Id == account.Id);
if (presence == null) return NotFound("");
if (presence.Instance == null) return BadRequest("Not in a Instance");
return Ok(new
{
RoomInstanceId = presence.Instance.Id,
PhotonAccessToken = "Meow",
Permissions = new List<object>()
});
}
[HttpGet("unity_assets/{UnityAssetId}/{Target}/{Version}")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> UnityAssetss([FromRoute] Guid UnityAssetId, [FromRoute] AssetBundleType Target, AssetBundleVersion Version)
{
if (Target == AssetBundleType.OculusQuest)
{
Target = AssetBundleType.MobileAndroid;
}
UnityAssets unityAssets = db.UnityAssets.FindById(UnityAssetId);
if (unityAssets == null) return NotFound();
UnityAsset? unityAsset = unityAssets.Assets.FirstOrDefault(u => u.Target == Target && u.Version == Version);
if (unityAsset == null) return NotFound();
return Ok(new
{
UnityAssetId,
Target,
Version,
unityAsset.Filename
});
}
[HttpGet("rooms/ownedby/{accId}")]
public async Task<IActionResult> RoomsOwnedby(long accId)
{
List<Dictionary<string, object>> response = [];
var foundRooms = db.Rooms.Include(x => x.Creator)
.FindAll()
.Where(x =>
x.Accessibility == RoomAccessibility.Public &&
(
(x.Creator != null && x.Creator.Id == accId) ||
(x.Roles != null && x.Roles.Any(r => r.AccountId == accId && r.Role >= RoomRoleType.CoOwner))
)
);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
[HttpGet("rooms/base")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Base()
{
List<Dictionary<string, object>> response = [];
var foundRooms = db.Rooms.Include(x => x.Creator).Find(x => x.Accessibility == RoomAccessibility.Unlisted && x.CloningAllowed == true);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
[HttpPost("rooms/{roomId}/clone")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Clone(long roomId, [FromForm, Required] string name)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId);
if (room == null) return NotFound();
if (string.IsNullOrWhiteSpace(name) || name.Length < 3 || name.Length > 32)
{
return BadRequest(new { Success = false, Error = "Room name must be between 3 and 32 characters." });
}
if (!room.CloningAllowed && !room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return Forbid();
}
if (db.Rooms.FindOne(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null)
{
return Conflict(new { Success = false, Error = $"The room name '{name}' is already taken." });
}
Room newRoom = new()
{
Name = name.Trim(),
SupportedPlayerTypes = room.SupportedPlayerTypes,
Accessibility = RoomAccessibility.Private,
ImageName = room.ImageName,
Description = room.Description,
Creator = account,
CustomWarning = room.CustomWarning,
WarningMask = room.WarningMask,
DataBlob = room.DataBlob,
DisableMicAutoMute = room.DisableMicAutoMute,
DisableRoomComments = room.DisableRoomComments,
EncryptVoiceChat = room.EncryptVoiceChat,
MinLevel = room.MinLevel,
PersistenceVersion = room.PersistenceVersion
};
db.Rooms.Insert(newRoom);
foreach (SubRoom subRoom in db.SubRooms.Include(x => x.Room).Include(x => x.CurrentSave).Find(x => x.Room.Id == roomId))
{
SubRoom newSubroom = new()
{
Room = newRoom,
LocationId = subRoom.LocationId,
Name = subRoom.Name,
IsSandbox = subRoom.IsSandbox,
MaxPlayers = subRoom.MaxPlayers,
CanMatchmakeInto = subRoom.CanMatchmakeInto,
SupportsJoinInProgress = subRoom.SupportsJoinInProgress,
UseLevelBasedMatchmaking = subRoom.UseLevelBasedMatchmaking,
UseAgeBasedMatchmaking = subRoom.UseAgeBasedMatchmaking,
UseRecRoyaleMatchmaking = subRoom.UseRecRoyaleMatchmaking
};
db.SubRooms.Insert(newSubroom);
if (subRoom.CurrentSave != null)
{
SubRoomSave newSave = new()
{
SubRoomId = newSubroom.Id,
DataBlob = subRoom.CurrentSave.DataBlob,
SavedByAccountId = account.Id,
SavedOnPlatform = login.Platform,
SavedOnDeviceClass = login.DeviceClass,
Description = $"Cloned From ^{room.Name}",
UnityAssetId = subRoom.CurrentSave.UnityAssetId,
PersistenceVersion = subRoom.CurrentSave.PersistenceVersion
};
db.SubRoomSave.Insert(newSave);
newSubroom.CurrentSave = newSave;
db.SubRooms.Update(newSubroom);
}
}
return Ok(new { Success = true, Value = await newRoom.ToDictionary(discord, db, RoomDetailsMask.StandardDetails) });
}
[HttpGet("rooms/moderatedby/me")]
public async Task<IActionResult> ModerateByNe()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return NotFound("User not found in database");
List<Dictionary<string, object>> response = [];
var foundRooms = db.Rooms.Include(x => x.Creator)
.FindAll()
.Where(x =>
(x.Creator != null && x.Creator.Id == account.Id) ||
(x.Roles != null && x.Roles.Any(r => r.AccountId == account.Id && r.Role >= RoomRoleType.Moderator))
);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
return Ok(response);
}
public class UploadDTO
{
[JsonPropertyName("Filename")]
public required string filename { get; set; }
[JsonPropertyName("Hash")]
public required string hash { get; set; }
[JsonPropertyName("OwnershipProof")]
public required string ownershipProof { get; set; }
}
public class SaveSubroomDTO
{
[JsonPropertyName("AutoPublish")]
public bool autoPublish { get; set; } = false;
[JsonPropertyName("Description")]
public string? description { get; set; } = null;
[JsonPropertyName("RoomData")]
public required UploadDTO roomData { get; set; }
[JsonPropertyName("SubRoomData")]
public required UploadDTO SubRoomData { get; set; }
[JsonPropertyName("UnityAssetId")]
public string? unityAssetId { get; set; } = null;
[JsonPropertyName("PersistenceVersion")]
public int persistenceVersion { get; set; }
}
[HttpPost("rooms/{roomId}/subrooms/{subroomId}/data")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> SubRoonData(long roomId, long subroomId, [FromBody, Required] SaveSubroomDTO request)
{
LoginWithInfoResult? login = await jwt.GetLoginWithInfo(User);
if (login == null) return Unauthorized();
Account account = login.Account;
Room? room = db.Rooms.Include(x => x.Creator).FindById(roomId);
if (room == null) return NotFound();
if (!room.HasRole(account.Id, RoomRoleType.CoOwner))
{
return StatusCode(403);
}
SubRoom? subRoom = db.SubRooms.Include(x => x.Room).FindById(subroomId);
if (subRoom == null) return NotFound();
if (subRoom.Room.Id != roomId)
{
return NotFound();
}
if (request.roomData.ownershipProof != CdnService.GenerateOwnershipProof(account, request.roomData.filename))
{
return Ok(new { Success = false, Error = "Room Ownership Proof Mismatch!" });
}
if (request.SubRoomData.ownershipProof != CdnService.GenerateOwnershipProof(account, request.SubRoomData.filename))
{
return Ok(new { Success = false, Error = "Subroom Ownership Proof Mismatch!" });
}
if (room.Accessibility == RoomAccessibility.Private)
{
request.autoPublish = true;
}
SubRoomSave save = new()
{
SubRoomId = subroomId,
DataBlob = request.SubRoomData.filename,
SavedByAccountId = account.Id,
Description = request.description,
UnityAssetId = request.unityAssetId,
SavedOnPlatform=login.Platform,
SavedOnDeviceClass=login.DeviceClass,
PersistenceVersion = request.persistenceVersion,
};
db.SubRoomSave.Insert(save);
if (request.autoPublish)
{
subRoom.CurrentSave = save;
db.SubRooms.Update(subRoom);
}
room.DataBlob = request.roomData.filename;
room.PersistenceVersion = request.persistenceVersion;
db.Rooms.Update(room);
return Ok(new { Success = true, Value = new Dictionary<string, object> { ["Room"] = await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetails), ["SubRoomDataSave"] = save.ToDictionary()} });
}
[HttpGet("rooms/visitedby/{accId}")]
[Authorize]
public async Task<IActionResult> VisitedBy(long accId)
{
Account? account = db.Accounts.FindById(accId);
if (account == null) return NotFound();
if (!account.IsRecentHistoryVisible)
{
return StatusCode(403);
}
List<Dictionary<string, object>> response = [];
var visitedRoomIds = account.VisitedRooms.Keys.ToList();
if (visitedRoomIds.Count > 0)
{
var foundRooms = db.Rooms.Include(x => x.Creator)
.FindAll()
.Where(x => visitedRoomIds.Contains(x.Id))
.ToList().Where(room => room.Accessibility == RoomAccessibility.Public).OrderByDescending(room => account.VisitedRooms.TryGetValue(room.Id, out var date) ? date : DateTime.MinValue);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
}
return Ok(response);
}
[HttpGet("rooms/visitedby/me")]
[Authorize]
public async Task<IActionResult> VisitedByMe()
{
Account? account = await jwt.GetLogin(User);
if (account == null) return Unauthorized();
List<Dictionary<string, object>> response = [];
var visitedRoomIds = account.VisitedRooms.Keys.ToList();
if (visitedRoomIds.Count > 0)
{
var foundRooms = db.Rooms.Include(x => x.Creator)
.FindAll()
.Where(x => visitedRoomIds.Contains(x.Id))
.ToList().Where(room => room.Accessibility == RoomAccessibility.Public || room.HasRole(account.Id, RoomRoleType.CoOwner)).OrderByDescending(room => account.VisitedRooms.TryGetValue(room.Id, out var date) ? date : DateTime.MinValue);
foreach (var room in foundRooms)
{
response.Add(await room.ToDictionary(discord, db, RoomDetailsMask.None));
}
}
return Ok(response);
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using DeluxeBackend.Models;
using DeluxeBackend.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualBasic.FileIO;
using System.ComponentModel.DataAnnotations;
using static DeluxeBackend.Enums;
namespace DeluxeBackend.Controllers
{
[Route("Storage")]
[ApiController]
public class StorageController(IJwtService jwt, ILiteDbService db, ICdnService cdn) : ControllerBase
{
[HttpPost("upload")]
[Authorize(Roles = "gameClient")]
public async Task<IActionResult> Upload([FromForm(Name = "FileType"), Required] FileType fileType, [FromForm(Name = "File"), Required] IFormFile file)
{
Account? account = await jwt.GetLogin(User);
if (account == null)
{
return Unauthorized();
}
string path = "fuck";
switch (fileType)
{
case FileType.Image:
path = "img";
break;
case FileType.Holotar:
path = "data";
break;
case FileType.SubRoomSave:
path = "room";
break;
case FileType.RoomMetadata:
path = "room";
break;
case FileType.Invention:
path = "invention";
break;
default:
return BadRequest();
}
using var stream = file.OpenReadStream();
var (remotePath, proof) = await cdn.UploadFile(
stream,
path,
account
);
if (remotePath == null)
{
return StatusCode(500, $"Failed to upload {fileType} to the CDN.");
}
return Ok(new
{
Filename = remotePath,
Hash = "meow",
OwnershipProof = proof
});
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Studio
{
[Route("Studio/analytics")]
[ApiController]
public class AnalyticsController : ControllerBase
{
[HttpGet("rudderstack-api-key")]
[Authorize(Roles = "studioClient")]
public async Task<IActionResult> ForRoom()
{
return Ok("Meow");
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace DeluxeBackend.Controllers.Studio
{
[Route("Studio/cloud-builds")]
[ApiController]
public class CloudBuildsController : ControllerBase
{
[HttpGet("for-room")]
[Authorize(Roles = "studioClient")]
public async Task<IActionResult> ForRoom()
{
return Ok(new
{
Results = new List<object>(),
TotalResults = 0
});
}
}
}