This commit is contained in:
skidfire
2026-09-06 19:59:27 -04:00
parent 55d8e11a5d
commit fe3468c65d
247 changed files with 338183 additions and 0 deletions
+736
View File
@@ -0,0 +1,736 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using Vanadium.Classes.WebSocket;
using Vanadium.Auth;
using Vanadium.Hubs;
using Vanadium.Utils.NotiController;
using System.Text.RegularExpressions;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/")]
public class AccountController : ControllerBase
{
public class VerifyDiscordConfirmRequest { public string? pin { get; set; } }
[HttpPost("beta/verifydiscord/confirm")] // lowk
public async Task<IActionResult> BetaVerifyDiscordConfirm([FromBody] VerifyDiscordConfirmRequest req)
{
if (string.IsNullOrWhiteSpace(req.pin) || !Program.PendingInGameCodes.TryRemove(req.pin.Trim(), out var data))
return BadRequest(new { success = false, error = "Invalid or expired PIN." });
var player = PlayerDB.Players.FindById(data.AccountId);
if (player == null)
return BadRequest(new { success = false, error = "Account not found." });
player.Player.DiscordUserId = data.DiscordUserId.ToString();
player.Player.DiscordLinked = true;
PlayerDB.Players.Update(player);
return Ok(new { success = true });
}
/* Fixed reputation + cheering due to progression overhaul in a way */
[HttpPost("api/PlayerCheer/v1/create")]
public async Task<IActionResult> CreatePlayerCheer([FromForm] long PlayerIdTo, [FromForm] int CheerCategory, [FromForm] long RoomId, [FromForm] bool Anonymous)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (id.Value == PlayerIdTo)
return Ok(new { Message = "You can not cheer yourself silly!", success = false });
var target = PlayerDB.Players.FindById(PlayerIdTo);
if (target == null)
return NotFound("");
if (Anonymous)
await NotificationsController.SendPlayerCheerAnonymous(PlayerIdTo, id.Value, CheerCategory);
else
await NotificationsController.SendPlayerCheer(PlayerIdTo, id.Value, CheerCategory);
return Ok(new { success = true });
}
// disabled SetSelectedCheer due to it breaking rr account birthdays!?!? idk but changing it doesnt let you spawn inventions or request to join someone
/*[HttpPost("api/PlayerCheer/v1/SetSelectedCheer")]
public async Task<IActionResult> SetSelectedCheer([FromForm(Name = "CheerCategory")] long cheerCategoryValue)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null || player.Player == null)
return NotFound("");
var requestedCheer = (CheerCategory)cheerCategoryValue;
bool isDeveloper = player.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
if (requestedCheer == CheerCategory.RecRoomDeveloper && !isDeveloper && player.PlayerId != 216 && player.PlayerId != 936)
return StatusCode(403);
player.Player.Reputation ??= new Reputation();
player.Player.Reputation.SelectedCheer = requestedCheer;
player.Player.Reputation.IsCheerful = true;
PlayerDB.Players.Update(player);
var rep = player.Player.Reputation;
var reupation = new // game doesnt wanna respect IsCheerful true so fuck it
{
AccountId = player.PlayerId,
Notoriety = rep.Noteriety,
IsCheerful = true,
CheerGeneral = rep.CheerGeneral,
CheerHelpful = rep.CheerHelpful,
CheerGreatHost = rep.CheerGreatHost,
CheerSportsman = rep.CheerSportsman,
CheerCreative = rep.CheerCreative,
CheerCredit = rep.CheerCredit,
SelectedCheer = (int)rep.SelectedCheer
};
var json = JsonConvert.SerializeObject(new WebsocketEvents.Response { Id = NotiEventTypes.ReputationUpdate, Msg = reupation });
await Task.WhenAll(NotificationsController.SendToAll(json), NotificationsController.SendToPlayer(id.Value, json));
await NotificationsController.RefreshAccount(id.Value);
return Ok(new { success = true, selectedCheer = (int)rep.SelectedCheer });
}*/
/* End of rep/cheer */
[HttpGet("accounts/{playerId}/receives/GameplayInvites")]
public IActionResult PlayerRecievesGameplayInvites(long playerId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
return Ok(true);
}
[HttpGet("acc/account/bulk")]
public async Task<IActionResult> GetAccountsBulk([FromQuery] List<long> id)
{
return BuildAccountsBulkResponse(id);
}
[Route("acc/account/bulk/email")]
[Route("acc/account/bulk/phone")]
public IActionResult GetiOSBulk() => Ok(ServerConfig.Bracket);
[HttpPost("acc/account/bulk")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> PostAccountsBulk([FromForm] List<long> id)
{
return BuildAccountsBulkResponse(id);
}
private IActionResult BuildAccountsBulkResponse(List<long> id)
{
if (id == null || id.Count == 0)
return Ok(new List<PlayerDTO>());
if (id[0] == 0)
return Ok(new List<PlayerDTO>
{
new PlayerDTO
{
accountId = 0,
createdAt = DateTime.UtcNow,
displayName = "@",
displayEmoji = null,
isJunior = false,
platforms = 0,
profileImage = "DefaultPFP.png",
username = "@",
personalPronouns = 0,
identityFlags = 0
}
});
return Ok(PlayerDB.GetAccountsBulk(id));
}
[HttpPut("acc/account/me/profileimage")]
public async Task<IActionResult> PostAccountMeProfileImage([FromForm] string imageName)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (string.IsNullOrWhiteSpace(imageName))
return BadRequest("come on bro");
if (PlayerDB.IsBanned(id.Value))
{
APIController.SendNonHileWebhook(id.Value, "not a hile, but a banned user tried to change their profile image");
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: false);
return StatusCode(403, "recapi activate");
}
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
player.Player.ProfileImage = imageName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
var updated = PlayerDB.GetAccountMe(id.Value);
return Ok(new
{
success = true,
});
}
[HttpGet("api/banappeal/generateCode")]
public IActionResult MakeBanAppealCode()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
return Ok(new { value = "appeal at discord.gg/rec-room" });
}
[HttpPut("acc/account/me/bannerimage")]
public async Task<IActionResult> PostAccountMeBannerImage([FromForm] string imageName)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (string.IsNullOrWhiteSpace(imageName))
return BadRequest("hahaha you are bad at this");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("Player not found.");
player.Player.BannerImage = imageName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
var updated = PlayerDB.GetAccountMe(id.Value);
return Ok(new
{
success = true
});
}
[HttpPut("acc/account/me/bio")]
public async Task<IActionResult> UpdateAccountMeBio([FromForm] string bio)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
bio = bio ?? "";
if (!ServerConfig.AllowSwears && !string.IsNullOrWhiteSpace(bio))
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
if (Regex.IsMatch(bio, $@"(?<![a-zA-Z]){Regex.Escape(word)}(?![a-zA-Z])", RegexOptions.IgnoreCase))
return Ok(new { error = "Bio contains prohibited text.", success = false, value = (object?)null });
}
}
}
var player = PlayerDB.Players.FindById(id.Value);
if (player == null || player.Player == null)
return NotFound("");
player.Player.Bio = bio;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
return Ok(new { success = true });
}
[HttpGet("acc/account/{playerId}/bio")]
public async Task<IActionResult> GetAccountBio(long playerId)
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null || player.Player == null)
return NotFound("");
return Ok(new
{
accountId = playerId,
bio = player.Player.Bio ?? null
});
}
[HttpPut("/acc/account/me/username")]
public async Task<IActionResult> UpdateUsername([FromForm] string username)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
if (player.Player.NameLocked)
{
return Ok(new
{
error = "Your name is locked and is unable to be changed.",
success = false,
value = (object?)null
});
}
if (string.IsNullOrWhiteSpace(username))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
username = username.Trim();
username = Regex.Replace(username, @"[\u200B-\u200D\uFEFF]", "");
if (!Regex.IsMatch(username, @"^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$"))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
if (!ServerConfig.AllowSwears)
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
string pattern = $@"\b{Regex.Escape(word)}\b";
if (Regex.IsMatch(username, pattern, RegexOptions.IgnoreCase))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
}
}
}
var taken = PlayerDB.Players.FindAll()
.Any(p => p.PlayerId != account.Value &&
p.Player != null &&
string.Equals(p.Player.Username, username, StringComparison.OrdinalIgnoreCase));
if (taken)
{
return Ok(new
{
errorCode = "Accounts.UsernameAlreadyInUse",
error = "This username is already in use.",
success = false,
value = (object?)null
});
}
if (player.Player.LastUsernameChangeAt.HasValue &&
(DateTime.UtcNow - player.Player.LastUsernameChangeAt.Value).TotalHours >= 2)
{
player.Player.AvailableUsernameChanges = 3;
player.Player.LastUsernameChangeAt = null;
}
if (player.Player.AvailableUsernameChanges <= 0)
{
APIController.SendNonHileWebhook(account.Value, "User tried to change their username but has no available changes left. current username: " + player.Player.Username + ", attempted new username: " + username);
return Ok(new
{
error = "Please wait a while before changing your username!",
success = false,
value = (object?)null
});
}
player.Player.Username = username;
player.Player.DisplayName = username;
player.Player.AvailableUsernameChanges -= 1;
player.Player.LastUsernameChangeAt ??= DateTime.UtcNow;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new
{
success = true,
});
}
[HttpPut("/acc/account/me/displayname")]
public async Task<IActionResult> UpdateDisplayName([FromForm] string displayName)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
if (player.Player.NameLocked)
return Ok(new { error = "Your name is locked and is unable to be changed.", success = false, value = (object?)null });
if (string.IsNullOrWhiteSpace(displayName))
return Ok(new { error = "Name contains prohibited text.", success = false, value = (object?)null });
displayName = displayName.Trim();
displayName = Regex.Replace(displayName, @"[\u200B-\u200D\uFEFF]", "");
if (!ServerConfig.AllowSwears)
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
if (Regex.IsMatch(displayName, $@"\b{Regex.Escape(word)}\b", RegexOptions.IgnoreCase))
return Ok(new { error = "Name contains prohibited text.", success = false, value = (object?)null });
}
}
}
player.Player.DisplayName = displayName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new { error = (object?)null, success = true });
}
[HttpPut("/acc/account/me/personalpronouns")]
public async Task<IActionResult> UpdatePersonalPronouns([FromForm] int pronounFlags)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.PronounFlags = pronounFlags;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
var updated = PlayerDB.GetAccountMe(account.Value);
return Ok(new
{
success = true
});
}
[HttpPut("/acc/account/me/identityflags")]
public async Task<IActionResult> UpdateIdentityFlags([FromForm] int identityFlags)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.IdentityFlags = identityFlags;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
var updated = PlayerDB.GetAccountMe(account.Value);
return Ok(new
{
success = true
});
}
[HttpPut("/acc/account/me/emoji")]
public async Task<IActionResult> UpdateDisplayEmoji([FromForm] string? displayEmoji)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.DisplayEmoji = string.IsNullOrWhiteSpace(displayEmoji) ? null : displayEmoji;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new
{
success = true
});
}
[HttpGet("acc/account/me")]
public async Task<IActionResult> GetAccountMe()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
var account = PlayerDB.GetAccountMe(id.Value);
return account != null ? Ok(account) : NotFound();
}
[HttpPost("acc/account/me/email")]
public async Task<IActionResult> AccAccountMeEmail()
{
return Ok(new
{
email = "vanadium@van.net",
success = true
});
}
private static long EnsurePlayerClub(FullPlayer target)
{
long clubId = target.Player.PlayerExtra.PlayerClubId;
if (clubId > 0 && ClubsDB.GetClub(clubId) != null)
return clubId;
var club = ClubsDB.CreatePlayerClub(target.PlayerId);
target.Player.PlayerExtra.PlayerClubId = club.ClubId;
PlayerDB.Players.Update(target);
return club.ClubId;
}
[HttpPost("subscription/{PlayerId}")]
public async Task<IActionResult> Subscribe(ulong? PlayerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(PlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
var club = ClubsDB.JoinClub(clubId, user.Value);
var repUpdate = WebsocketEvents.CreateReputationUpdateResponse(target.PlayerId);
await NotificationsController.SendToAll(JsonConvert.SerializeObject(repUpdate));
if (club != null)
{
var joined = club.Members.FirstOrDefault(m => m.AccountId == user.Value);
if (joined != null)
{
await NotificationsController.BroadcastClubMembershipUpdate(joined.MemberId, user.Value, clubId, joined.MemberType, joined.JoinedAt);
await NotificationsController.BroadcastCreatorClubSubscriptionUpdate(target.PlayerId, clubId, joined.MemberType);
}
}
return Ok(new
{
error = (object?)null,
success = true,
value = 0
});
}
[HttpGet("acc/parentalcontrols/me")]
public IActionResult ParentalControlShit()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
return Ok(new { accountId = player, disallowInAppPurchases = false });
}
[HttpGet("acc/accountprivacysettings/{playerId}")]
public async Task<IActionResult> PrivacySettingsAcc(long playerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
return Ok(new { accountId = playerId, isRecentHistoryVisible = true });
}
[HttpGet("/config/categories")]
public async Task<IActionResult> ConfigCategories(long PlayerId)
{
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "categories.json");
if (!System.IO.File.Exists(path)) return Ok(ServerConfig.Bracket);
return PhysicalFile(path, "application/json");
}
[HttpGet("/preferences")]
public async Task<IActionResult> Preferences(long PlayerId)
{
return Ok(new { mutedCategories = Array.Empty<string>() });
}
[HttpDelete("subscription/{PlayerId}")]
public async Task<IActionResult> Unsubscribe(long PlayerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(PlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound();
long clubId = EnsurePlayerClub(target);
var clubBefore = ClubsDB.GetClub(clubId);
var leaving = clubBefore?.Members.FirstOrDefault(m => m.AccountId == user.Value);
ClubsDB.LeaveClub(clubId, user.Value);
var repUpdate = WebsocketEvents.CreateReputationUpdateResponse(target.PlayerId);
await NotificationsController.SendToAll(JsonConvert.SerializeObject(repUpdate));
if (leaving != null)
{
await NotificationsController.BroadcastClubMembershipUpdate(leaving.MemberId, user.Value, clubId, 0, DateTime.UtcNow);
await NotificationsController.BroadcastCreatorClubSubscriptionUpdate(target.PlayerId, clubId, 0);
}
return Ok(new
{
error = (object?)null,
success = true,
value = 1
});
}
[HttpGet("subscription/subscriberCount/{targetPlayerId}")]
public async Task<IActionResult> GetSubscriberCount(long targetPlayerId)
{
var target = PlayerDB.Players.FindById(targetPlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
int count = ClubsDB.GetSubscriberCount(clubId);
return Content(count.ToString(), "text/plain");
}
[Route("/acc/account/me/haspassword")]
public async Task<IActionResult> HasPassword()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
return Ok(!string.IsNullOrEmpty(player.Password));
}
[HttpPost("auth/account/me/changepassword")]
public async Task<IActionResult> ChangePassword([FromForm] string newPassword)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return Ok(new { success = false, error = "Something went wrong trying to change your password." });
if (string.IsNullOrWhiteSpace(newPassword))
return Ok(new { success = false, error = "Password cannot be empty." });
var (valid, theerror) = PlayerDB.PasswordManager.ValidatePassword(newPassword);
if (!valid)
return Ok(new { success = false, error = theerror });
var result = PlayerDB.PasswordManager.ChangePassword(id.Value, newPassword);
if (!result)
return Ok(new { success = false, error = "Something went wrong trying to change your password." });
return Ok(new { success = true });
}
[HttpGet("subscription/details/{targetPlayerId}")]
public async Task<IActionResult> GetSubscriptionDetails(long targetPlayerId)
{
var target = PlayerDB.Players.FindById(targetPlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
int count = ClubsDB.GetSubscriberCount(clubId);
return Ok(new
{
accountId = targetPlayerId,
clubId = clubId,
subscriberCount = count
});
}
}
}