diff --git a/RecRoomArchive.Models/API/Activities/CharadesWord.cs b/RecRoomArchive.Models/API/Activities/CharadesWord.cs index 58f507f..9934166 100644 --- a/RecRoomArchive.Models/API/Activities/CharadesWord.cs +++ b/RecRoomArchive.Models/API/Activities/CharadesWord.cs @@ -2,9 +2,12 @@ namespace RecRoomArchive.Models.API.Activities { - public class CharadesWord(string word, CharadesWordsDifficulty difficulty = CharadesWordsDifficulty.Easy) + public class CharadesWord { - [JsonPropertyName(name: "Difficulty")] public CharadesWordsDifficulty Difficulty { get; set; } = difficulty; - [JsonPropertyName(name: "EN_US")] public string EN_US { get; set; } = word; + [JsonPropertyName("Difficulty")] + public CharadesWordsDifficulty Difficulty { get; set; } = CharadesWordsDifficulty.Easy; + + [JsonPropertyName("EN_US")] + public string EN_US { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/RecRoomArchive.Models/API/Config/PhotonConfig.cs b/RecRoomArchive.Models/API/Config/PhotonConfig.cs index 8c68fff..18e9ee5 100644 --- a/RecRoomArchive.Models/API/Config/PhotonConfig.cs +++ b/RecRoomArchive.Models/API/Config/PhotonConfig.cs @@ -6,5 +6,6 @@ namespace RecRoomArchive.Models.API.Config { [JsonPropertyName(name: "CloudRegion")] public string CloudRegion { get; set; } = "us"; [JsonPropertyName(name: "CrcCheckEnabled")] public bool CrcCheckEnabled { get; set; } = true; + [JsonPropertyName(name: "EnableServerTracingAfterDisconnect")] public bool EnableServerTracingAfterDisconnect { get; set; } = true; } } \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/ActivityLevels.cs b/RecRoomArchive.Models/API/GameSessions/ActivityLevels.cs new file mode 100644 index 0000000..f80a890 --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/ActivityLevels.cs @@ -0,0 +1,21 @@ +namespace RecRoomArchive.Models.API.GameSessions +{ + public enum ActivityLevels + { + INVALID = -1, + DORM_ROOM = 1000, + REC_CENTER = 2000, + CHARADES = 3000, + DISC_GOLF = 4000, + DODGEBALL = 5000, + THE_LOUNGE = 6000, + PADDLEBALL = 7000, + PAINTBALL = 8000, + QUEST = 9000, + SOCCER = 10000, + ART_TESTING = 11000, + PERFORMANCE_HALL = 12000, + ROOM_CALIBRATION = 13000, + PARK = 14000 + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/GameSession.cs b/RecRoomArchive.Models/API/GameSessions/GameSession.cs new file mode 100644 index 0000000..a214ac4 --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/GameSession.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace RecRoomArchive.Models.API.GameSessions +{ + public class GameSession + { + [JsonPropertyName(name: "GameSessionId")] + public long GameSessionId { get; set; } = 1; + + [JsonPropertyName(name: "RegionId")] + public string RegionId { get; set; } = "us"; + + [JsonPropertyName(name: "RoomId")] + public string RoomId { get; set; } = "fba33a23-b4a5-4f55-a631-37028b1db7f9"; + + [JsonPropertyName(name: "EventId")] + public ulong? EventId { get; set; } + + [JsonPropertyName(name: "ActivityLevelId")] + public string ActivityLevelId { get; set; } = "76d98498-60a1-430c-ab76-b54a29b7a163"; + + [JsonPropertyName(name: "Private")] + public bool Private { get; set; } = false; + + [JsonPropertyName(name: "GameInProgress")] + public bool GameInProgress { get; set; } = false; + + [JsonPropertyName(name: "MaxCapacity")] + public int MaxCapacity { get; set; } = 8; + + [JsonPropertyName(name: "IsFull")] + public bool IsFull { get; set; } = false; + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/JoinGameErrorCode.cs b/RecRoomArchive.Models/API/GameSessions/JoinGameErrorCode.cs new file mode 100644 index 0000000..7cbce40 --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/JoinGameErrorCode.cs @@ -0,0 +1,14 @@ +namespace RecRoomArchive.Models.API.GameSessions +{ + public enum JoinGameErrorCode + { + Success, + NoSuchGame, + PlayerNotOnline, + InsufficientSpace, + EventNotStarted, + EventAlreadyFinished, + EventCreatorNotReady, + Blocked + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/JoinGameSessionResponse.cs b/RecRoomArchive.Models/API/GameSessions/JoinGameSessionResponse.cs new file mode 100644 index 0000000..e4e1078 --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/JoinGameSessionResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace RecRoomArchive.Models.API.GameSessions +{ + public class JoinGameSessionResponse + { + [JsonPropertyName(name: "Result")] + public JoinGameErrorCode Result { get; set; } = JoinGameErrorCode.Success; + + [JsonPropertyName(name: "GameSession")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public GameSession? GameSession { get; set; } + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/JoinRandomGameSessionRequest.cs b/RecRoomArchive.Models/API/GameSessions/JoinRandomGameSessionRequest.cs new file mode 100644 index 0000000..f6efecb --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/JoinRandomGameSessionRequest.cs @@ -0,0 +1,8 @@ +namespace RecRoomArchive.Models.API.GameSessions +{ + public class JoinRandomGameSessionRequest + { + public required string[] ActivityLevelIds { get; set; } = []; + public ulong[] ExpectedPlayerIds { get; set; } = []; + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/GameSessions/PresenceResponseFuckYou.cs b/RecRoomArchive.Models/API/GameSessions/PresenceResponseFuckYou.cs new file mode 100644 index 0000000..ba1292d --- /dev/null +++ b/RecRoomArchive.Models/API/GameSessions/PresenceResponseFuckYou.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace RecRoomArchive.Models.API.GameSessions +{ + public class PresenceResponseFuckYou + { + [JsonPropertyName(name: "PlayerId")] + public ulong PlayerId { get; set; } + + [JsonPropertyName(name: "IsOnline")] + public bool IsOnline { get; set; } + + [JsonPropertyName(name: "GameSession")] + public GameSession? GameSession { get; set; } + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/Notification/PushNotificationId.cs b/RecRoomArchive.Models/API/Notification/PushNotificationId.cs new file mode 100644 index 0000000..68ffc15 --- /dev/null +++ b/RecRoomArchive.Models/API/Notification/PushNotificationId.cs @@ -0,0 +1,46 @@ +namespace RecRoomArchive.Models.API.Notification +{ + public enum PushNotificationId + { + RelationshipChanged = 1, + MessageReceived, + MessageDeleted, + PresenceHeartbeatResponse, + RefreshLogin, + Logout, + SubscriptionUpdateProfile = 11, + SubscriptionUpdatePresence, + SubscriptionUpdateGameSession, + SubscriptionUpdateRoom = 15, + SubscriptionUpdateRoomPlaylist, + ModerationQuitGame = 20, + ModerationUpdateRequired, + ModerationKick, + ModerationKickAttemptFailed, + ModerationRoomBan, + ServerMaintenance, + GiftPackageReceived = 30, + GiftPackageReceivedImmediate, + GiftPackageRewardSelectionReceived, + ProfileJuniorStatusUpdate = 40, + RelationshipsInvalid = 50, + StorefrontBalanceAdd = 60, + StorefrontBalanceUpdate, + StorefrontBalancePurchase, + ConsumableMappingAdded = 70, + ConsumableMappingRemoved, + PlayerEventCreated = 80, + PlayerEventUpdated, + PlayerEventDeleted, + PlayerEventResponseChanged, + PlayerEventResponseDeleted, + PlayerEventStateChanged, + ChatMessageReceived = 90, + CommunityBoardUpdate = 95, + CommunityBoardAnnouncementUpdate, + InventionModerationStateChanged = 100, + FreeGiftButtonItemsAdded = 110, + LocalRoomKeyCreated = 120, + LocalRoomKeyDeleted + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/PlatformLogin/Requests/BaseLoginRequest.cs b/RecRoomArchive.Models/API/PlatformLogin/Requests/BaseLoginRequest.cs index b8d6d35..7203c0f 100644 --- a/RecRoomArchive.Models/API/PlatformLogin/Requests/BaseLoginRequest.cs +++ b/RecRoomArchive.Models/API/PlatformLogin/Requests/BaseLoginRequest.cs @@ -12,5 +12,6 @@ namespace RecRoomArchive.Models.API.PlatformLogin.Requests public required long BuildTimestamp { get; set; } public required string AuthParams { get; set; } public required string Verify { get; set; } + public string? PlayerId { get; set; } } } \ No newline at end of file diff --git a/RecRoomArchive.Models/API/PlatformLogin/Responses/BaseLoginResponse.cs b/RecRoomArchive.Models/API/PlatformLogin/Responses/BaseLoginResponse.cs index b0d4ea9..5f906ac 100644 --- a/RecRoomArchive.Models/API/PlatformLogin/Responses/BaseLoginResponse.cs +++ b/RecRoomArchive.Models/API/PlatformLogin/Responses/BaseLoginResponse.cs @@ -4,10 +4,13 @@ namespace RecRoomArchive.Models.API.PlatformLogin.Responses { public class BaseLoginResponse { + [JsonPropertyName(name: "PlayerId")] + public ulong PlayerId { get; set; } + [JsonPropertyName(name: "Token")] public string Token { get; set; } = string.Empty; - [JsonPropertyName(name: "PlayerId")] - public ulong PlayerId { get; set; } + [JsonPropertyName(name: "Error")] + public string Error { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/RecRoomArchive.Models/API/VersionCheck/VersionCheckResponse.cs b/RecRoomArchive.Models/API/VersionCheck/VersionCheckResponse.cs new file mode 100644 index 0000000..3e8fbdb --- /dev/null +++ b/RecRoomArchive.Models/API/VersionCheck/VersionCheckResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace RecRoomArchive.Models.API.VersionCheck +{ + public class VersionCheckResponse(VersionStatus versionStatus = VersionStatus.ValidForPlay) + { + [JsonPropertyName(name: "VersionStatus")] public VersionStatus VersionStatus { get; set; } = versionStatus; + + [JsonPropertyName(name: "ValidVersion")] public bool ValidVersion => VersionStatus == VersionStatus.ValidForPlay; + [JsonPropertyName(name: "IsValid")] public bool IsValid => VersionStatus == VersionStatus.ValidForPlay; + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/API/VersionCheck/VersionStatus.cs b/RecRoomArchive.Models/API/VersionCheck/VersionStatus.cs new file mode 100644 index 0000000..ff9c0bf --- /dev/null +++ b/RecRoomArchive.Models/API/VersionCheck/VersionStatus.cs @@ -0,0 +1,8 @@ +namespace RecRoomArchive.Models.API.VersionCheck +{ + public enum VersionStatus + { + ValidForPlay, + UpdateRequired + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/Common/Constants.cs b/RecRoomArchive.Models/Common/Constants.cs new file mode 100644 index 0000000..c904cf3 --- /dev/null +++ b/RecRoomArchive.Models/Common/Constants.cs @@ -0,0 +1,7 @@ +namespace RecRoomArchive.Models.Common +{ + public class Constants + { + public static readonly string Version = "0.0.5"; + } +} \ No newline at end of file diff --git a/RecRoomArchive.Models/RRA/ServerPreferences.cs b/RecRoomArchive.Models/RRA/ServerPreferences.cs new file mode 100644 index 0000000..ebfa64d --- /dev/null +++ b/RecRoomArchive.Models/RRA/ServerPreferences.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace RecRoomArchive.Models.RRA +{ + public class ServerPreferences + { + [JsonPropertyName(name: "CompletedSetup")] public bool CompletedSetup { get; set; } = false; + [JsonPropertyName(name: "OverrideNamedImages")] public bool OverrideNamedImages { get; set; } = false; + [JsonPropertyName(name: "EnableWebRequests")] public bool EnableWebRequests { get; set; } = true; + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/Equipment/V1/EquipmentController.cs b/RecRoomArchive/Controllers/API/Equipment/V1/EquipmentController.cs new file mode 100644 index 0000000..d195180 --- /dev/null +++ b/RecRoomArchive/Controllers/API/Equipment/V1/EquipmentController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace RecRoomArchive.Controllers.API.Equipment.V1 +{ + [Route(template: "api/[controller]/v1")] + [ApiController] + public class EquipmentController : ControllerBase + { + [HttpGet(template: "getUnlocked")] + public async Task>> GetEquipment() + { + return new List(); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/Events/V3/EventsController.cs b/RecRoomArchive/Controllers/API/Events/V3/EventsController.cs new file mode 100644 index 0000000..525eccd --- /dev/null +++ b/RecRoomArchive/Controllers/API/Events/V3/EventsController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace RecRoomArchive.Controllers.API.Events.V3 +{ + [Route(template: "api/[controller]/v3")] + [ApiController] + public class EventsController : ControllerBase + { + [HttpGet(template: "list")] + public async Task>> GetActiveEventsList() + { + return new List(); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/GameSessions/V2/GameSessionsController.cs b/RecRoomArchive/Controllers/API/GameSessions/V2/GameSessionsController.cs new file mode 100644 index 0000000..81dd697 --- /dev/null +++ b/RecRoomArchive/Controllers/API/GameSessions/V2/GameSessionsController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Models.API.GameSessions; +using System.Text.Json; + +namespace RecRoomArchive.Controllers.API.GameSessions.V2 +{ + [Route("api/[controller]/v2")] + [ApiController] + public class GameSessionsController : ControllerBase + { + [HttpPost(template: "joinRandom")] + public async Task> JoinRandomGameSession([FromBody] JoinRandomGameSessionRequest request) + { + Console.WriteLine(JsonSerializer.Serialize(request)); + + var session = new JoinGameSessionResponse() + { + GameSession = new GameSession() + }; + + Console.WriteLine(JsonSerializer.Serialize(session)); + + return Ok(session); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/Images/V2/ImagesController.cs b/RecRoomArchive/Controllers/API/Images/V2/ImagesController.cs index 1e6d3f3..e4d07e4 100644 --- a/RecRoomArchive/Controllers/API/Images/V2/ImagesController.cs +++ b/RecRoomArchive/Controllers/API/Images/V2/ImagesController.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Controllers.API.Notification; +using RecRoomArchive.Models.API.Notification; using RecRoomArchive.Models.API.Players; using RecRoomArchive.Services; using System.Text.Json; @@ -25,6 +27,8 @@ namespace RecRoomArchive.Controllers.API.Images.V2 fileService.SetData("profile.json", JsonSerializer.Serialize(profile)); + await NotificationController.Notify(profile.Id, PushNotificationId.SubscriptionUpdateProfile, profile); + return Ok(); } } diff --git a/RecRoomArchive/Controllers/API/Notification/NotificationController.cs b/RecRoomArchive/Controllers/API/Notification/NotificationController.cs new file mode 100644 index 0000000..7516836 --- /dev/null +++ b/RecRoomArchive/Controllers/API/Notification/NotificationController.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Models.API.Notification; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; + +namespace RecRoomArchive.Controllers.API.Notification +{ + [Route("api/[controller]")] + [ApiController] + public class NotificationController : ControllerBase + { + private static readonly Dictionary Clients = []; + + [HttpGet("v2")] + public async Task Get() + { + if (!HttpContext.WebSockets.IsWebSocketRequest) + { + HttpContext.Response.StatusCode = 400; + return; + } + var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + var buffer = new byte[1024 * 4]; + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + var handshakeJson = Encoding.UTF8.GetString(buffer, 0, result.Count); + var handshakeData = JsonSerializer.Deserialize>(handshakeJson)!; + var playerId = ulong.Parse(handshakeData["PlayerId"]); + Clients[playerId] = webSocket; + var sessionResponse = JsonSerializer.Serialize(new { SessionId = playerId }); + await webSocket.SendAsync(Encoding.UTF8.GetBytes(sessionResponse), WebSocketMessageType.Text, true, CancellationToken.None); + await Echo(playerId, webSocket); + Clients.Remove(playerId); + } + + private static async Task Echo(ulong playerId, WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + var receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), CancellationToken.None); + + while (!receiveResult.CloseStatus.HasValue) + { + var response = JsonSerializer.Serialize(new { SessionId = playerId }); // i think?? only for analytics tho lolololool + + await webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(response)), + WebSocketMessageType.Text, true, CancellationToken.None); + + /*await webSocket.SendAsync( + new ArraySegment(buffer, 0, receiveResult.Count), + receiveResult.MessageType, + receiveResult.EndOfMessage, + CancellationToken.None);*/ + + receiveResult = await webSocket.ReceiveAsync( + new ArraySegment(buffer), CancellationToken.None); + } + + await webSocket.CloseAsync( + receiveResult.CloseStatus.Value, + receiveResult.CloseStatusDescription, + CancellationToken.None); + + Clients.Remove(playerId); + } + + public static async Task Notify(ulong playerId, PushNotificationId id, object msg) + { + if (!Clients.TryGetValue(playerId, out var socket)) + return; + + if (socket.State != WebSocketState.Open) + return; + + var notification = new + { + Id = (int)id, + Msg = msg + }; + + var json = JsonSerializer.Serialize(notification); + var bytes = Encoding.UTF8.GetBytes(json); + + await socket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/PlatformLogin/V1/PlatformLoginController.cs b/RecRoomArchive/Controllers/API/PlatformLogin/V1/PlatformLoginController.cs new file mode 100644 index 0000000..4f241c8 --- /dev/null +++ b/RecRoomArchive/Controllers/API/PlatformLogin/V1/PlatformLoginController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Models.API.Platform; +using RecRoomArchive.Models.API.Players; +using RecRoomArchive.Services; + +namespace RecRoomArchive.Controllers.API.PlatformLogin.V1 +{ + [Route("api/[controller]/v1")] + [ApiController] + public class PlatformLoginController(AccountService accountService) : ControllerBase + { + [HttpPost(template: "profiles")] + public async Task>> GetProfiles([FromForm(Name = "Platform")] PlatformType platform, [FromForm(Name = "PlatformId")] ulong platformId) + { + if (!accountService.AccountExists()) + { + accountService.CreateAccount(); + } + + var profile = accountService.GetSelfAccount(); + if (profile == null) + return BadRequest("Please relaunch RRAC and re-run setup"); + + List profiles = [profile]; + + return Ok(profiles); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs b/RecRoomArchive/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs index ce192f8..f68d8d4 100644 --- a/RecRoomArchive/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs +++ b/RecRoomArchive/Controllers/API/PlatformLogin/V2/PlatformLoginController.cs @@ -34,7 +34,7 @@ namespace RecRoomArchive.Controllers.API.PlatformLogin.V2 accountService.CreateAccount(username); } - var accountId = accountService.GetSelfAccount()!.Id; + var accountId = accountService.GetSelfAccountId()!.Value; return Ok(new BaseLoginResponse { diff --git a/RecRoomArchive/Controllers/API/PlatformLogin/V5/PlatformLoginController.cs b/RecRoomArchive/Controllers/API/PlatformLogin/V5/PlatformLoginController.cs new file mode 100644 index 0000000..af94809 --- /dev/null +++ b/RecRoomArchive/Controllers/API/PlatformLogin/V5/PlatformLoginController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Models.API.PlatformLogin.Requests; +using RecRoomArchive.Models.API.PlatformLogin.Responses; +using RecRoomArchive.Services; + +namespace RecRoomArchive.Controllers.API.PlatformLogin.V5 +{ + /// + /// Used to login to accounts on Rec Room + /// + [Route(template: "api/[controller]/v5")] + [ApiController] + public class PlatformLoginController(AppVersionService appVersionService, AccountService accountService, AuthorizationService authorizationService) : ControllerBase + { + /// + /// Checks if the appVersion provided is allowed to play (which it most certainly will be unless its a weird build or someone messed with it) + /// + /// An Ok response if the version is valid, Forbid if it is not. Forbid will yield the client displaying "Rec Room Update Required" soo maybe don't + [HttpPost] + public async Task Login([FromForm] BaseLoginRequest loginRequest) + { + var username = loginRequest.Name ?? string.Empty; + + var buildTimestamp = loginRequest.BuildTimestamp; + + await appVersionService.StoreBuildTimestamp(buildTimestamp); + + // See if a profile exists yet... + if (!accountService.AccountExists()) + { + // ...if not, create it! + accountService.CreateAccount(username); + } + + var accountId = accountService.GetSelfAccountId()!.Value; + + return Ok(new BaseLoginResponse + { + Token = authorizationService.GenerateToken(accountId), + PlayerId = accountId + }); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/Players/PlayersController.cs b/RecRoomArchive/Controllers/API/Players/PlayersController.cs index 1042ef6..bb03406 100644 --- a/RecRoomArchive/Controllers/API/Players/PlayersController.cs +++ b/RecRoomArchive/Controllers/API/Players/PlayersController.cs @@ -69,7 +69,13 @@ namespace RecRoomArchive.Controllers.API.Players [HttpPut(template: "{profileId:long}")] public async Task> UpdateProfile([Required] ulong profileId, [FromBody] AugustProfile model) { - return (AugustProfile)accountService.GetSelfAccount()!; + var baseProfile = accountService.GetSelfAccount(); + if (baseProfile == null) + return NotFound(); + + var profile = new AugustProfile(baseProfile); + + return profile; } } } \ No newline at end of file diff --git a/RecRoomArchive/Controllers/API/Players/V1/PlayersController.cs b/RecRoomArchive/Controllers/API/Players/V1/PlayersController.cs index fb898cc..ee3a260 100644 --- a/RecRoomArchive/Controllers/API/Players/V1/PlayersController.cs +++ b/RecRoomArchive/Controllers/API/Players/V1/PlayersController.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using RecRoomArchive.Models.API.Config; using RecRoomArchive.Models.API.Platform; using RecRoomArchive.Models.API.Players; using RecRoomArchive.Services; diff --git a/RecRoomArchive/Controllers/API/Players/V2/PlayersController.cs b/RecRoomArchive/Controllers/API/Players/V2/PlayersController.cs index 4ce7e18..f20cd63 100644 --- a/RecRoomArchive/Controllers/API/Players/V2/PlayersController.cs +++ b/RecRoomArchive/Controllers/API/Players/V2/PlayersController.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Controllers.API.Notification; +using RecRoomArchive.Models.API.Notification; using RecRoomArchive.Models.API.Players; using RecRoomArchive.Models.Common; using RecRoomArchive.Services; @@ -31,6 +33,8 @@ namespace RecRoomArchive.Controllers.API.Players.V2 fileService.SetData("profile.json", JsonSerializer.Serialize(profile)); + await NotificationController.Notify(profile.Id, PushNotificationId.SubscriptionUpdateProfile, profile); + return Ok(OkResponse.Ok()); } } diff --git a/RecRoomArchive/Controllers/API/VersionCheck/V3/VersionCheckController.cs b/RecRoomArchive/Controllers/API/VersionCheck/V3/VersionCheckController.cs new file mode 100644 index 0000000..57cf68b --- /dev/null +++ b/RecRoomArchive/Controllers/API/VersionCheck/V3/VersionCheckController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; +using RecRoomArchive.Models.API.VersionCheck; +using RecRoomArchive.Services; + +namespace RecRoomArchive.Controllers.API.VersionCheck.V3 +{ + /// + /// Endpoints used to check if the version the player is playing on is up to date enough to play Rec Room, due to this being a custom server, it doesn't really matter + /// + [Route(template: "api/[controller]/v3")] + [ApiController] + public class VersionCheckController(AppVersionService appVersionService) : ControllerBase + { + /// + /// Checks if the appVersion provided is allowed to play (which it most certainly will be unless its a weird build or someone messed with it) + /// + /// A valid VersionCheckResponse if the version is valid, an invalid VersionCheckResponse if it is not. Forbid will yield the client displaying "Rec Room Update Required" soo maybe don't + [HttpGet] + public async Task CheckVersion([FromQuery(Name = "v")] string appVersion) + { + if (appVersion == null) + return BadRequest(new VersionCheckResponse(VersionStatus.UpdateRequired)); + + await appVersionService.StoreAppVersion(appVersion); + + return Ok(new VersionCheckResponse()); + } + } +} \ No newline at end of file diff --git a/RecRoomArchive/Program.cs b/RecRoomArchive/Program.cs index c3f36c9..e0cfadd 100644 --- a/RecRoomArchive/Program.cs +++ b/RecRoomArchive/Program.cs @@ -1,5 +1,6 @@ - +using RecRoomArchive.Models.Common; using RecRoomArchive.Services; +using Serilog; namespace RecRoomArchive { @@ -8,11 +9,15 @@ namespace RecRoomArchive public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); + builder.WebHost.UseKestrel(); + + Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); #region Services builder.Services.AddControllers(); builder.Services.AddOpenApi(); builder.Services.AddHttpContextAccessor(); + builder.Services.AddSerilog(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -21,17 +26,31 @@ namespace RecRoomArchive builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); #endregion + Console.Title = $"RRAC {Constants.Version}"; + var app = builder.Build(); - if (app.Environment.IsDevelopment()) - { - app.MapOpenApi(); - } + app.MapOpenApi(); + + app.UseSwagger(); + app.UseSwaggerUI(); + + app.UseSerilogRequestLogging(); app.UseAuthorization(); + var webSocketOptions = new WebSocketOptions + { + KeepAliveInterval = TimeSpan.FromMinutes(2) + }; + + app.UseWebSockets(webSocketOptions); + app.MapControllers(); app.Run(); diff --git a/RecRoomArchive/Properties/launchSettings.json b/RecRoomArchive/Properties/launchSettings.json index 8506764..e1f0e3e 100644 --- a/RecRoomArchive/Properties/launchSettings.json +++ b/RecRoomArchive/Properties/launchSettings.json @@ -20,4 +20,4 @@ } } } -} +} \ No newline at end of file diff --git a/RecRoomArchive/RecRoomArchive.csproj b/RecRoomArchive/RecRoomArchive.csproj index c162a1c..81196e2 100644 --- a/RecRoomArchive/RecRoomArchive.csproj +++ b/RecRoomArchive/RecRoomArchive.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -8,6 +8,9 @@ + + + diff --git a/RecRoomArchive/Services/AccountService.cs b/RecRoomArchive/Services/AccountService.cs index 7298e32..3736979 100644 --- a/RecRoomArchive/Services/AccountService.cs +++ b/RecRoomArchive/Services/AccountService.cs @@ -6,6 +6,8 @@ namespace RecRoomArchive.Services { public class AccountService { + public static ulong? AccountId { get; private set; } + public bool AccountExists() { return File.Exists("data/profile.json"); @@ -13,8 +15,6 @@ namespace RecRoomArchive.Services public bool CreateAccount(string? username = null) { - PopulateServerData(); - if (string.IsNullOrEmpty(username)) { username = GetRandomUsername(); @@ -38,35 +38,25 @@ namespace RecRoomArchive.Services return JsonSerializer.Deserialize(File.ReadAllText("data/profile.json")); } + public ulong? GetSelfAccountId() + { + if (AccountId.HasValue) + return AccountId; + + var profile = JsonSerializer.Deserialize(File.ReadAllText("data/profile.json")); + if (profile == null) + return null; + + AccountId = profile.Id; + + return AccountId; + + } + private static string GetRandomUsername() { int randomFourDigits = RandomNumberGenerator.GetInt32(1000, 9999); return $"RRA-User_{randomFourDigits}"; } - - private static void PopulateServerData() - { - string basePath = "data"; - - string[] directories = ["rooms", "images", "blobs"]; - string[] files = []; - - Directory.CreateDirectory(basePath); - - foreach (var directory in directories) - { - Directory.CreateDirectory(Path.Combine(basePath, directory)); - } - - foreach (var file in files) - { - string fullPath = Path.Combine(basePath, file); - - if (!File.Exists(fullPath)) - { - File.WriteAllText(fullPath, string.Empty); - } - } - } } } \ No newline at end of file diff --git a/RecRoomArchive/Services/AppVersionService.cs b/RecRoomArchive/Services/AppVersionService.cs index 4d63345..14e7d1a 100644 --- a/RecRoomArchive/Services/AppVersionService.cs +++ b/RecRoomArchive/Services/AppVersionService.cs @@ -1,4 +1,6 @@ -namespace RecRoomArchive.Services +using RecRoomArchive.Models.Common; + +namespace RecRoomArchive.Services { /// /// Used to get the appVersion from the client to determine how to run the server @@ -63,6 +65,8 @@ FullBuildTimestamp = buildTimestamp; BuildTimestamp = buildTimestampDateTime; + Console.Title = $"RRAC {Constants.Version} - ({BuildTimestamp.Value.Year}) {BuildTimestamp.Value}"; + Console.WriteLine ($"RRAC {Constants.Version} - ({BuildTimestamp.Value.Year}) {BuildTimestamp.Value}"); Console.WriteLine($"buildTimestamp: {FullBuildTimestamp}, buildTimestampDateTime: {BuildTimestamp}"); return true; diff --git a/RecRoomArchive/Services/AuthorizationService.cs b/RecRoomArchive/Services/AuthorizationService.cs index a1f8880..a31f646 100644 --- a/RecRoomArchive/Services/AuthorizationService.cs +++ b/RecRoomArchive/Services/AuthorizationService.cs @@ -19,7 +19,7 @@ namespace RecRoomArchive.Services new(ClaimTypes.Role, "gameClient") }; - SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor() + SecurityTokenDescriptor tokenDescriptor = new() { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.Add(TimeSpan.FromHours(12)), diff --git a/RecRoomArchive/Services/ConfigService.cs b/RecRoomArchive/Services/ConfigService.cs index 2445c41..35e9d2e 100644 --- a/RecRoomArchive/Services/ConfigService.cs +++ b/RecRoomArchive/Services/ConfigService.cs @@ -17,12 +17,13 @@ namespace RecRoomArchive.Services public async Task GetRecRoomConfig() { var request = _httpContextAccessor.HttpContext?.Request; - var host = $"{request!.Scheme}://{request.Host}"; + var cdnHost = $"{request!.Scheme}://{request.Host}"; + var motd = await _motdService.GetMessageOfTheDay(); return new RecRoomConfig() { - MessageOfTheDay = await _motdService.GetMessageOfTheDay(), - CdnBaseUri = host, + MessageOfTheDay = motd, + CdnBaseUri = cdnHost, MatchmakingParams = new MatchmakingParams(), LevelProgressionMaps = [ diff --git a/RecRoomArchive/Services/FileService.cs b/RecRoomArchive/Services/FileService.cs index d8ff88e..80b3778 100644 --- a/RecRoomArchive/Services/FileService.cs +++ b/RecRoomArchive/Services/FileService.cs @@ -1,4 +1,5 @@ -using System.Xml.Linq; +using RecRoomArchive.Models.RRA; +using System.Text.Json; namespace RecRoomArchive.Services { @@ -9,7 +10,7 @@ namespace RecRoomArchive.Services string basePath = "data"; string[] directories = ["rooms", "images", "blobs"]; - string[] files = ["rooms.json", "avatar.json", "settings.json", "profile.json"]; + string[] files = ["rooms.json", "avatar.json", "settings.json", "profile.json", "avatarItems.json", "serverPreferences.json"]; Directory.CreateDirectory(basePath); @@ -28,6 +29,17 @@ namespace RecRoomArchive.Services } } } + + public void WriteServerPrefs(bool isComplete) + { + var preferences = new ServerPreferences + { + CompletedSetup = isComplete + }; + + SetData("serverPreferences.json", JsonSerializer.Serialize(preferences)); + } + public string? GetData(string name) { var path = $"data/{name}"; diff --git a/RecRoomArchive/Services/MessageOfTheDayService.cs b/RecRoomArchive/Services/MessageOfTheDayService.cs index c29ccb6..f2ad4e0 100644 --- a/RecRoomArchive/Services/MessageOfTheDayService.cs +++ b/RecRoomArchive/Services/MessageOfTheDayService.cs @@ -11,7 +11,7 @@ namespace RecRoomArchive.Services /// /// HttpClient for making requests to Gitea /// - private static readonly HttpClient httpClient = new HttpClient(); + private static readonly HttpClient httpClient = new(); /// /// MessageOfTheDay reference @@ -22,16 +22,18 @@ namespace RecRoomArchive.Services /// Gets the message of the day from Gitea. If the URL cannot be resolved, it will fall back to "Welcome to RecRoomArchive!" /// /// String related to the Message of the Day - public async Task GetMessageOfTheDay(string? version = null) + public async Task GetMessageOfTheDay() { // I wouldn't want to re-request the MOTD from the server a bunch of times... if (string.IsNullOrEmpty(MessageOfTheDay)) { - var motd = await httpClient.GetAsync($"https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/MOTD"); - if (!motd.IsSuccessStatusCode) - return "Welcome to RecRoomArchive!"; + //var motd = await httpClient.GetAsync($"https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/MOTD"); + //if (!motd.IsSuccessStatusCode) + // return "Welcome to RecRoomArchive!"; - MessageOfTheDay = await motd.Content.ReadAsStringAsync(); + //MessageOfTheDay = await motd.Content.ReadAsStringAsync(); + + MessageOfTheDay = "Welcome to RecRoomArchive!"; } return MessageOfTheDay; @@ -46,17 +48,19 @@ namespace RecRoomArchive.Services if (CharadesWordsLastFetchedAt - DateTime.UtcNow > TimeSpan.FromMinutes(30)) return CachedCharadesWords; - var request = await httpClient.GetAsync("https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/CharadesWords"); - if (!request.IsSuccessStatusCode) - return []; + //var request = await httpClient.GetAsync("https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/CharadesWords"); + //if (!request.IsSuccessStatusCode) + // return []; - var words = await request.Content.ReadAsStringAsync(); - if (words == null) - return []; + //var words = await request.Content.ReadAsStringAsync(); + //if (words == null) + // return []; - CachedCharadesWords = JsonSerializer.Deserialize>(words)!; + return []; - return CachedCharadesWords; + //CachedCharadesWords = JsonSerializer.Deserialize>(words)!; + + //return CachedCharadesWords; } } } \ No newline at end of file