Add remaining project files
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CS8604: Possible null reference argument.
|
||||||
|
dotnet_diagnostic.CS8604.severity = none
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
||||||
+364
@@ -0,0 +1,364 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
/image_cache
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class AGRoomRuntimeConfig
|
||||||
|
{
|
||||||
|
public required Location[] Locations { get; set; }
|
||||||
|
public required Room[] Rooms { get; set; }
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class Location
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string ReplicationId { get; set; }
|
||||||
|
public required string SceneName { get; set; }
|
||||||
|
public required string[] RequiredSubSceneNames { get; set; }
|
||||||
|
public required string[] LevelRoomSubSceneNames { get; set; }
|
||||||
|
public required bool SupportsVRLow { get; set; }
|
||||||
|
public required bool SupportsQuest2 { get; set; }
|
||||||
|
public required bool SupportsMobile { get; set; }
|
||||||
|
public required int MaxPlayers { get; set; }
|
||||||
|
public required ReleaseType ReleaseStatus { get; set; }
|
||||||
|
public required bool EmptyOnSandboxClone { get; set; }
|
||||||
|
public required bool CustomizationAllowed { get; set; }
|
||||||
|
//public required KJKEMKAOLJC SupportedGameMode { get; set; }
|
||||||
|
//public required GameTeamColorSettings GameTeamColorSettings { get; set; }
|
||||||
|
public required GiftContextType RewardGiftContext { get; set; }
|
||||||
|
//public required KPFMPDBFDJB LocationEnum { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class Room
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string ReplicationId { get; set; }
|
||||||
|
public required string Description { get; set; }
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
public required bool SupportsLevelVoting { get; set; }
|
||||||
|
public required bool CloningAllowed { get; set; }
|
||||||
|
public required bool SupportsScreens { get; set; }
|
||||||
|
public required bool SupportsWalkVR { get; set; }
|
||||||
|
public required bool SupportsTeleportVR { get; set; }
|
||||||
|
public required bool AllowsJuniors { get; set; }
|
||||||
|
public required RoomWarningMask RoomWarningMask { get; set; }
|
||||||
|
public required string CustomRoomWarning { get; set; }
|
||||||
|
public required bool DisableMicAutoMute { get; set; }
|
||||||
|
public required bool DisableRoomComments { get; set; }
|
||||||
|
public required ReleaseType ReleaseStatus { get; set; }
|
||||||
|
public required RoomScene[] Scenes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class RoomScene
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string ReplicationId { get; set; }
|
||||||
|
public required string RoomSceneLocationId { get; set; }
|
||||||
|
public required bool IsSandbox { get; set; }
|
||||||
|
public required bool CanMatchmakeInto { get; set; }
|
||||||
|
public required bool SupportsJoinInProgress { get; set; }
|
||||||
|
public required bool UseLevelBasedMatchmaking { get; set; }
|
||||||
|
public required bool UseAgeBasedMatchmaking { get; set; }
|
||||||
|
public required bool UseRecRoyaleMatchmaking { get; set; }
|
||||||
|
public required int MaxPlayers { get; set; }
|
||||||
|
public required ReleaseType ReleaseStatus { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace DeluxeBackend
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.All)]
|
||||||
|
public class TokenAttribute : Attribute
|
||||||
|
{
|
||||||
|
public required string Token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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=\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { }
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="image_cache\**" />
|
||||||
|
<Content Remove="image_cache\**" />
|
||||||
|
<EmbeddedResource Remove="image_cache\**" />
|
||||||
|
<None Remove="image_cache\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Discord.Net" Version="3.19.1" />
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.35.0" />
|
||||||
|
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="10.0.7" />
|
||||||
|
<PackageReference Include="SkiaSharp" Version="3.119.2" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="3.119.2" />
|
||||||
|
<PackageReference Include="Scalar.AspNetCore" Version="2.0.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="RR.Protobuf">
|
||||||
|
<HintPath>..\..\..\..\Downloads\auent_rr_toolkit\RR.Protobuf\bin\Debug\net9.0\RR.Protobuf.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@DeluxeBackend_HostAddress = http://localhost:5132
|
||||||
|
|
||||||
|
GET {{DeluxeBackend_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="DeluxeBackend.csproj" />
|
||||||
|
</Solution>
|
||||||
@@ -0,0 +1,687 @@
|
|||||||
|
using DeluxeBackend.Controllers;
|
||||||
|
using DeluxeBackend.Models;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Interactions;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
using static DeluxeBackend.Extensions.RoomExtensions;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Discord.Commands
|
||||||
|
{
|
||||||
|
public class Debug : InteractionModuleBase<SocketInteractionContext>
|
||||||
|
{
|
||||||
|
private readonly ILiteDbService db;
|
||||||
|
private readonly INotificationService ws;
|
||||||
|
private readonly IMessageService message;
|
||||||
|
private readonly DiscordBotService discord;
|
||||||
|
private readonly IHostApplicationLifetime lifetime;
|
||||||
|
public readonly static ulong AdminRole = 1509152644365811752;
|
||||||
|
|
||||||
|
public Debug(ILiteDbService _db, INotificationService _ws, IMessageService _message, DiscordBotService _discord, IHostApplicationLifetime _lifetime)
|
||||||
|
{
|
||||||
|
db = _db;
|
||||||
|
ws = _ws;
|
||||||
|
message = _message;
|
||||||
|
discord = _discord;
|
||||||
|
lifetime = _lifetime;
|
||||||
|
}
|
||||||
|
private bool IsAdmin()
|
||||||
|
{
|
||||||
|
return Context.User is SocketGuildUser guildUser && guildUser.Roles.Any(r => r.Id == AdminRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("send_msg", ".")]
|
||||||
|
public async Task send_msg(long playerId, Enums.MessageType type, string data = "")
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
data = string.Empty;
|
||||||
|
}
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync($"No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.SendMessage(db.Accounts.FindById(1), db.Accounts.FindById(playerId), type, data);
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("upload_ua", ".")]
|
||||||
|
public async Task UploadUnityAsset(string id, AssetBundleType target, AssetBundleVersion version, string fileName)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync($"No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Guid.TryParse(id, out var guidId))
|
||||||
|
{
|
||||||
|
await FollowupAsync("Invalid GUID format.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == guidId);
|
||||||
|
if (unityAssets == null)
|
||||||
|
{
|
||||||
|
unityAssets = new();
|
||||||
|
}
|
||||||
|
unityAssets.Id = guidId;
|
||||||
|
UnityAsset unityAsset = new()
|
||||||
|
{
|
||||||
|
Filename = fileName,
|
||||||
|
Target = target,
|
||||||
|
Version = version
|
||||||
|
};
|
||||||
|
unityAssets.Assets.Add(unityAsset);
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Version
|
||||||
|
{
|
||||||
|
_2022AndOlder,
|
||||||
|
_2023AndNewer,
|
||||||
|
};
|
||||||
|
|
||||||
|
public class RoomData2023AndNewer
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required long RoomId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Description { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string ImageName { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required RoomWarningMask WarningMask { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
public string? CustomWarning { get; set; } = null;
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsScreens { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsWalkVR { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsTeleportVR { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsJuniors { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required List<SubRoomData2023AndNewer> SubRooms { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
public class SubRoomData2023AndNewer
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required long SubRoomId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required long RoomId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string UnitySceneId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required CurrentSave2023AndNewer? CurrentSave { get; set; } = null;
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool IsSandbox { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required int MaxPlayers { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
}
|
||||||
|
public class CurrentSave2023AndNewer
|
||||||
|
{
|
||||||
|
public Guid? UnityAssetId { get; set; } = null;
|
||||||
|
[JsonRequired]
|
||||||
|
public required string DataBlob { get; set; }
|
||||||
|
public string? Description { get; set; } = null;
|
||||||
|
[JsonRequired]
|
||||||
|
public required DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
public class Unity2023AndNewer
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public AssetBundleType Target { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public AssetBundleVersion Version { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Filename { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RoomData2022AndNewer
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required long RoomId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Description { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string ImageName { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required RoomWarningMask WarningMask { get; set; }
|
||||||
|
public string? CustomWarning { get; set; } = null;
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsScreens { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsWalkVR { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsTeleportVR { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool SupportsJuniors { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required List<SubRoomData2022AndNewer> SubRooms { get; set; }
|
||||||
|
}
|
||||||
|
public class SubRoomData2022AndNewer
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required long SubRoomId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string UnitySceneId { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required bool IsSandbox { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required int MaxPlayers { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
public Guid? UnityAssetId { get; set; } = null;
|
||||||
|
public string? DataBlob { get; set; } = null;
|
||||||
|
public string? Description { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[SlashCommand("reupload_room", ".")]
|
||||||
|
public async Task ReuploadRoom(IAttachment file, Version version)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.Filename.EndsWith(".json"))
|
||||||
|
{
|
||||||
|
await FollowupAsync("Please upload a file ending in .json!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Account? account = db.Accounts.FindOne(x => x.DiscordId == Context.User.Id);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Could not find your account in the database.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
var jsonString = await httpClient.GetStringAsync(file.Url);
|
||||||
|
using var httpAuthClient = new HttpClient();
|
||||||
|
httpAuthClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {ServerConfig.RRToken}");
|
||||||
|
HttpResponseMessage testreq = await httpAuthClient.GetAsync("https://accounts.rec.net/account/me");
|
||||||
|
|
||||||
|
if (testreq.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to authenticate with rec.net. (Status: {testreq.StatusCode})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Room? newRoom = null;
|
||||||
|
if (version == Version._2023AndNewer)
|
||||||
|
{
|
||||||
|
RoomData2023AndNewer? room = JsonSerializer.Deserialize<RoomData2023AndNewer>(jsonString);
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Failed to parse the uploaded JSON into a valid room format.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoom = new Room
|
||||||
|
{
|
||||||
|
Accessibility = RoomAccessibility.Private,
|
||||||
|
Name = $"{room.Name}_{DateTime.UtcNow.Ticks}",
|
||||||
|
Description = room.Description,
|
||||||
|
ImageName = room.ImageName,
|
||||||
|
CloningAllowed = false,
|
||||||
|
Creator = account,
|
||||||
|
CustomWarning = room.CustomWarning,
|
||||||
|
WarningMask = room.WarningMask,
|
||||||
|
IsDorm = room.Name == "DormRoom",
|
||||||
|
IsRRO = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (SubRoomData2023AndNewer scene in room.SubRooms)
|
||||||
|
{
|
||||||
|
SubRoom newScene = new()
|
||||||
|
{
|
||||||
|
Room = newRoom,
|
||||||
|
LocationId = scene.UnitySceneId,
|
||||||
|
Name = scene.Name,
|
||||||
|
IsSandbox = scene.IsSandbox,
|
||||||
|
MaxPlayers = scene.MaxPlayers,
|
||||||
|
CanMatchmakeInto = scene.Accessibility == RoomAccessibility.Public
|
||||||
|
};
|
||||||
|
db.SubRooms.Insert(newScene);
|
||||||
|
|
||||||
|
if (scene.CurrentSave != null)
|
||||||
|
{
|
||||||
|
SubRoomSave newSave = new()
|
||||||
|
{
|
||||||
|
SubRoomId = newScene.Id,
|
||||||
|
DataBlob = scene.CurrentSave.DataBlob,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (scene.CurrentSave.UnityAssetId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString2 = await httpAuthClient.GetStringAsync($"https://rooms.rec.net/unity_assets/{scene.CurrentSave.UnityAssetId.Value}/{(int)AssetBundleType.WindowsDesktop}/{(int)AssetBundleVersion.Unity_2020_3_33f1}");
|
||||||
|
|
||||||
|
Unity2023AndNewer? u = JsonSerializer.Deserialize<Unity2023AndNewer>(jsonString2);
|
||||||
|
|
||||||
|
if (u == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to parse Unity Asset JSON from rec.net for subroom '{scene.Name}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == scene.CurrentSave.UnityAssetId.Value);
|
||||||
|
if (unityAssets == null)
|
||||||
|
{
|
||||||
|
unityAssets = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
unityAssets.Id = scene.CurrentSave.UnityAssetId.Value;
|
||||||
|
UnityAsset unityAsset = new()
|
||||||
|
{
|
||||||
|
Filename = u.Filename,
|
||||||
|
Target = u.Target,
|
||||||
|
Version = u.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
unityAssets.Assets.Add(unityAsset);
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
newSave.UnityAssetId = scene.CurrentSave.UnityAssetId.Value.ToString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString2 = await httpAuthClient.GetStringAsync($"https://rooms.rec.net/unity_assets/{scene.CurrentSave.UnityAssetId.Value}/{(int)AssetBundleType.MobileAndroid}/{(int)AssetBundleVersion.Unity_2020_3_33f1}");
|
||||||
|
|
||||||
|
Unity2023AndNewer? u = JsonSerializer.Deserialize<Unity2023AndNewer>(jsonString2);
|
||||||
|
|
||||||
|
if (u == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to parse Unity Asset JSON from rec.net for subroom '{scene.Name}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == scene.CurrentSave.UnityAssetId.Value);
|
||||||
|
if (unityAssets == null)
|
||||||
|
{
|
||||||
|
unityAssets = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
unityAssets.Id = scene.CurrentSave.UnityAssetId.Value;
|
||||||
|
UnityAsset unityAsset = new()
|
||||||
|
{
|
||||||
|
Filename = u.Filename,
|
||||||
|
Target = u.Target,
|
||||||
|
Version = u.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
unityAssets.Assets.Add(unityAsset);
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
newSave.UnityAssetId = scene.CurrentSave.UnityAssetId.Value.ToString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SubRoomSave.Insert(newSave);
|
||||||
|
newScene.CurrentSave = newSave;
|
||||||
|
db.SubRooms.Update(newScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (version == Version._2022AndOlder)
|
||||||
|
{
|
||||||
|
RoomData2022AndNewer? room = JsonSerializer.Deserialize<RoomData2022AndNewer>(jsonString);
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Failed to parse the uploaded JSON into a valid room format.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoom = new Room
|
||||||
|
{
|
||||||
|
Accessibility = RoomAccessibility.Private,
|
||||||
|
Name = $"{room.Name}_{DateTime.UtcNow.Ticks}",
|
||||||
|
Description = room.Description,
|
||||||
|
CloningAllowed = false,
|
||||||
|
Creator = account,
|
||||||
|
CustomWarning = room.CustomWarning,
|
||||||
|
WarningMask = room.WarningMask,
|
||||||
|
IsDorm = room.Name == "DormRoom",
|
||||||
|
IsRRO = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (SubRoomData2022AndNewer scene in room.SubRooms)
|
||||||
|
{
|
||||||
|
SubRoom newScene = new()
|
||||||
|
{
|
||||||
|
Room = newRoom,
|
||||||
|
LocationId = scene.UnitySceneId,
|
||||||
|
Name = scene.Name,
|
||||||
|
IsSandbox = scene.IsSandbox,
|
||||||
|
MaxPlayers = scene.MaxPlayers,
|
||||||
|
CanMatchmakeInto = scene.Accessibility == RoomAccessibility.Public
|
||||||
|
};
|
||||||
|
db.SubRooms.Insert(newScene);
|
||||||
|
|
||||||
|
if (scene.DataBlob != null)
|
||||||
|
{
|
||||||
|
SubRoomSave newSave = new()
|
||||||
|
{
|
||||||
|
SubRoomId = newScene.Id,
|
||||||
|
DataBlob = scene.DataBlob,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (scene.UnityAssetId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString2 = await httpAuthClient.GetStringAsync($"https://rooms.rec.net/unity_assets/{scene.UnityAssetId.Value}/{(int)AssetBundleType.WindowsDesktop}/{(int)AssetBundleVersion.Unity_2020_3_33f1}");
|
||||||
|
|
||||||
|
Unity2023AndNewer? u = JsonSerializer.Deserialize<Unity2023AndNewer>(jsonString2);
|
||||||
|
|
||||||
|
if (u == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to parse Unity Asset JSON from rec.net for subroom '{scene.Name}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == scene.UnityAssetId.Value);
|
||||||
|
if (unityAssets == null)
|
||||||
|
{
|
||||||
|
unityAssets = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
unityAssets.Id = scene.UnityAssetId.Value;
|
||||||
|
UnityAsset unityAsset = new()
|
||||||
|
{
|
||||||
|
Filename = u.Filename,
|
||||||
|
Target = u.Target,
|
||||||
|
Version = u.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
unityAssets.Assets.Add(unityAsset);
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
newSave.UnityAssetId = scene.UnityAssetId.Value.ToString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonString2 = await httpAuthClient.GetStringAsync($"https://rooms.rec.net/unity_assets/{scene.UnityAssetId.Value}/{(int)AssetBundleType.MobileAndroid}/{(int)AssetBundleVersion.Unity_2020_3_33f1}");
|
||||||
|
|
||||||
|
Unity2023AndNewer? u = JsonSerializer.Deserialize<Unity2023AndNewer>(jsonString2);
|
||||||
|
|
||||||
|
if (u == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to parse Unity Asset JSON from rec.net for subroom '{scene.Name}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == scene.UnityAssetId.Value);
|
||||||
|
if (unityAssets == null)
|
||||||
|
{
|
||||||
|
unityAssets = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
unityAssets.Id = scene.UnityAssetId.Value;
|
||||||
|
UnityAsset unityAsset = new()
|
||||||
|
{
|
||||||
|
Filename = u.Filename,
|
||||||
|
Target = u.Target,
|
||||||
|
Version = u.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
unityAssets.Assets.Add(unityAsset);
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
newSave.UnityAssetId = scene.UnityAssetId.Value.ToString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SubRoomSave.Insert(newSave);
|
||||||
|
newScene.CurrentSave = newSave;
|
||||||
|
db.SubRooms.Update(newScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await FollowupAsync("Tf");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newRoom != null)
|
||||||
|
{
|
||||||
|
await ws.SendToPlayer(account.Id, "RoomUpdate", await newRoom.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await FollowupAsync("Room reuploaded successfully!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error (Exception): {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("give_role_in_room", ".")]
|
||||||
|
public async Task give_role_in_room(ulong roomId, RoomRoleType role)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account? account = db.Accounts.FindOne(x => x.DiscordId == Context.User.Id);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Could not find your account in the database.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room? room = db.Rooms.FindById(roomId);
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"room not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.Roles.Add(new RoomRole() { AccountId = account.Id, Role = role });
|
||||||
|
db.Rooms.Update(room);
|
||||||
|
|
||||||
|
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("edit_room", ".")]
|
||||||
|
public async Task edit_room(ulong roomId, string rooName, RoomAccessibility accessibility, long creatorId)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account? account = db.Accounts.FindOne(x => x.DiscordId == Context.User.Id);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Could not find your account in the database.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room? room = db.Rooms.FindById(roomId);
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"room not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.Name = rooName;
|
||||||
|
room.Accessibility = accessibility;
|
||||||
|
room.Creator = db.Accounts.FindById(creatorId);
|
||||||
|
|
||||||
|
|
||||||
|
db.Rooms.Update(room);
|
||||||
|
|
||||||
|
await ws.SendToPlayerSubs(account.Id, "RoomUpdate", await room.ToDictionary(discord, db, RoomDetailsMask.StandardDetailsNoDataBlob));
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("edit_account", ".")]
|
||||||
|
public async Task edit_account(ulong accountId, string Username, string DisplayName)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account? account = db.Accounts.FindOne(x => x.DiscordId == Context.User.Id);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync("Error: Could not find your account in the database.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Account? room = db.Accounts.FindById(accountId);
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"account not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.Username = Username;
|
||||||
|
room.DisplayName = DisplayName;
|
||||||
|
|
||||||
|
|
||||||
|
db.Accounts.Update(room);
|
||||||
|
|
||||||
|
await ws.SendToPlayerSubs(account.Id, "AccountUpdate", room.ToDictionary());
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("shutdown", ".")]
|
||||||
|
public async Task Shutdown()
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ws.SendToAllPlayer(PushNotification.ServerMaintenance, new { StartsInMinutes = 1 });
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
lifetime.StopApplication();
|
||||||
|
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("close_all", ".")]
|
||||||
|
public async Task CloseAll()
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ws.SendToAllPlayer(PushNotification.ModerationQuitGame, new { });
|
||||||
|
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("give_account_all_roles", ".")]
|
||||||
|
public async Task GivePlayerAllRoles(long accountId)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account account = db.Accounts.FindById(accountId);
|
||||||
|
|
||||||
|
account.Roles.Add("influencer");
|
||||||
|
account.Roles.Add("developer");
|
||||||
|
account.Roles.Add("moderator");
|
||||||
|
account.Roles.Add("screenshare");
|
||||||
|
account.Roles.Add("keepsake");
|
||||||
|
account.Roles.Add("livekeepsakeeventroomsaveoverride");
|
||||||
|
account.Roles.Add("betaroomcurrencycreator");
|
||||||
|
account.Roles.Add("ps5recroomplus");
|
||||||
|
account.Roles.Add("multiinstanceevent");
|
||||||
|
db.Accounts.Update(account);
|
||||||
|
await ws.SendToPlayer(account.Id, PushNotification.RefreshLogin, new Dictionary<string, object>());
|
||||||
|
|
||||||
|
await FollowupAsync($"Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("upload_file", ".")]
|
||||||
|
public async Task UploadFile(IAttachment file, FileType fileType)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
using DeluxeBackend.Controllers;
|
||||||
|
using DeluxeBackend.Models;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Interactions;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static DeluxeBackend.Controllers.Api.CustomAvatarItemsController;
|
||||||
|
using static DeluxeBackend.Discord.Commands.Debug;
|
||||||
|
using static DeluxeBackend.Discord.Commands.ImportStuff;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
using static DeluxeBackend.Extensions.RoomExtensions;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Discord.Commands
|
||||||
|
{
|
||||||
|
public class ImportStuff(ILiteDbService db, ICdnService cdn) : InteractionModuleBase<SocketInteractionContext>
|
||||||
|
{
|
||||||
|
private readonly DateTime _saveCutoffDate = new(2023, 6, 21);
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
|
private bool IsAdmin()
|
||||||
|
{
|
||||||
|
return Context.User is SocketGuildUser guildUser && guildUser.Roles.Any(r => r.Id == Debug.AdminRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecNetResult<T>
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required List<T> Results { get; set; }
|
||||||
|
[JsonRequired]
|
||||||
|
public int TotalResults { get; set; } = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("import_room", ".")]
|
||||||
|
public async Task importRoom(IAttachment file)
|
||||||
|
{
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Account account = db.Accounts.FindOne(x => x.Username == "Import");
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
account = new()
|
||||||
|
{
|
||||||
|
Username = "Import",
|
||||||
|
DisplayName = "Import",
|
||||||
|
Birthday = DateOnly.MinValue,
|
||||||
|
ImageName = "DefaultProfileImage",
|
||||||
|
IsRecentHistoryVisible = false,
|
||||||
|
};
|
||||||
|
account.Roles.AddRange(["developer", "keepsake", "livekeepsakeeventroomsaveoverride", "betaroomcurrencycreator", "multiinstanceevent"]);
|
||||||
|
db.Accounts.Insert(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {ServerConfig.RRToken}");
|
||||||
|
|
||||||
|
HttpResponseMessage testreq = await httpClient.GetAsync("https://accounts.rec.net/account/me");
|
||||||
|
if (testreq.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to authenticate with rec.net. (Status: {testreq.StatusCode})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var textData = await httpClient.GetStringAsync(file.Url);
|
||||||
|
List<string> logs = [];
|
||||||
|
int successCount = 0;
|
||||||
|
|
||||||
|
async Task ImportSave(SubRoom subroom, SubRoomData2023AndNewer subRoom2)
|
||||||
|
{
|
||||||
|
if (subRoom2.CurrentSave == null) return;
|
||||||
|
|
||||||
|
CurrentSave2023AndNewer? save = null;
|
||||||
|
|
||||||
|
if (subRoom2.CurrentSave.CreatedAt >= _saveCutoffDate)
|
||||||
|
{
|
||||||
|
HttpResponseMessage savesReq = await httpClient.GetAsync($"https://rooms.rec.net/rooms/{subRoom2.RoomId}/subrooms/{subRoom2.SubRoomId}/saves?skip=0&take=1000");
|
||||||
|
if (savesReq.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
var rawSavesJson = await savesReq.Content.ReadAsStringAsync();
|
||||||
|
var savesResult = JsonSerializer.Deserialize<RecNetResult<CurrentSave2023AndNewer>>(rawSavesJson, _jsonOptions);
|
||||||
|
|
||||||
|
save = savesResult?.Results?
|
||||||
|
.Where(s => s != null && s.CreatedAt < _saveCutoffDate && !string.IsNullOrEmpty(s.DataBlob))
|
||||||
|
.OrderByDescending(s => s.CreatedAt)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save = subRoom2.CurrentSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save == null)
|
||||||
|
{
|
||||||
|
logs.Add($"`{subroom.Name}`: Room has no compatible classic save file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubRoomSave newSave = new()
|
||||||
|
{
|
||||||
|
SubRoomId = subroom.Id,
|
||||||
|
DataBlob = save.DataBlob,
|
||||||
|
SavedByAccountId = account.Id,
|
||||||
|
Description = $"Imported by @{Context.User.Username}"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (save.UnityAssetId.HasValue)
|
||||||
|
{
|
||||||
|
var assetTypes = new[] { AssetBundleType.WindowsDesktop, AssetBundleType.MobileAndroid };
|
||||||
|
|
||||||
|
var distinctVersions = Enum.GetValues<AssetBundleVersion>().Cast<int>().Distinct();
|
||||||
|
|
||||||
|
foreach (var assetType in assetTypes)
|
||||||
|
{
|
||||||
|
foreach (var versionId in distinctVersions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assetUrl = $"https://rooms.rec.net/unity_assets/{save.UnityAssetId.Value}/{(int)assetType}/{versionId}";
|
||||||
|
var jsonString = await httpClient.GetStringAsync(assetUrl);
|
||||||
|
Unity2023AndNewer? u = JsonSerializer.Deserialize<Unity2023AndNewer>(jsonString, _jsonOptions);
|
||||||
|
|
||||||
|
if (u == null) continue;
|
||||||
|
|
||||||
|
UnityAssets unityAssets = db.UnityAssets.FindOne(x => x.Id == save.UnityAssetId.Value) ?? new UnityAssets { Id = save.UnityAssetId.Value };
|
||||||
|
|
||||||
|
if (!unityAssets.Assets.Any(a => a.Filename == u.Filename && a.Target == u.Target && a.Version == u.Version))
|
||||||
|
{
|
||||||
|
unityAssets.Assets.Add(new UnityAsset
|
||||||
|
{
|
||||||
|
Filename = u.Filename,
|
||||||
|
Target = u.Target,
|
||||||
|
Version = u.Version
|
||||||
|
});
|
||||||
|
db.UnityAssets.Upsert(unityAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
newSave.UnityAssetId = save.UnityAssetId.Value.ToString();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SubRoomSave.Insert(newSave);
|
||||||
|
subroom.CurrentSave = newSave;
|
||||||
|
db.SubRooms.Update(subroom);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task import(string inputLine)
|
||||||
|
{
|
||||||
|
string roomName = inputLine;
|
||||||
|
|
||||||
|
if (roomName.Contains("rec.net/room/"))
|
||||||
|
{
|
||||||
|
int markerIndex = roomName.IndexOf("rec.net/room/") + "rec.net/room/".Length;
|
||||||
|
roomName = roomName.Substring(markerIndex).Split('/')[0].Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(roomName)) return;
|
||||||
|
|
||||||
|
HttpResponseMessage roomData = await httpClient.GetAsync($"https://rooms.rec.net/rooms/?name={roomName}&include=1325");
|
||||||
|
if (roomData.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
logs.Add($"`{roomName}`: Not found on rec.net");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
roomData.EnsureSuccessStatusCode();
|
||||||
|
string rawJson = await roomData.Content.ReadAsStringAsync();
|
||||||
|
RoomData2023AndNewer? room = JsonSerializer.Deserialize<RoomData2023AndNewer>(rawJson, _jsonOptions);
|
||||||
|
|
||||||
|
if (room == null)
|
||||||
|
{
|
||||||
|
logs.Add($"`{roomName}`: Failed to parse room metadata");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room.Accessibility == RoomAccessibility.Private)
|
||||||
|
{
|
||||||
|
logs.Add($"`{roomName}`: Room is private.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room.CreatedAt >= _saveCutoffDate)
|
||||||
|
{
|
||||||
|
logs.Add($"`{roomName}`: Room metadata is too new (Created: {room.CreatedAt:yyyy-MM-dd}).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.Rooms.FindOne(x => x.Name.Equals(roomName, StringComparison.InvariantCultureIgnoreCase)) != null)
|
||||||
|
{
|
||||||
|
logs.Add($"`{roomName}`: Already exists in the database.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room newRoom = new()
|
||||||
|
{
|
||||||
|
Accessibility = RoomAccessibility.Public,
|
||||||
|
Name = $"{room.Name}",
|
||||||
|
Description = room.Description,
|
||||||
|
ImageName = room.ImageName,
|
||||||
|
CloningAllowed = false,
|
||||||
|
Creator = account,
|
||||||
|
CustomWarning = room.CustomWarning,
|
||||||
|
WarningMask = room.WarningMask,
|
||||||
|
IsDorm = room.Name == "DormRoom",
|
||||||
|
IsRRO = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (SubRoomData2023AndNewer scene in room.SubRooms)
|
||||||
|
{
|
||||||
|
SubRoom newScene = new()
|
||||||
|
{
|
||||||
|
Room = newRoom,
|
||||||
|
LocationId = scene.UnitySceneId,
|
||||||
|
Name = scene.Name,
|
||||||
|
IsSandbox = scene.IsSandbox,
|
||||||
|
MaxPlayers = scene.MaxPlayers,
|
||||||
|
CanMatchmakeInto = scene.Accessibility == RoomAccessibility.Public
|
||||||
|
};
|
||||||
|
db.SubRooms.Insert(newScene);
|
||||||
|
await ImportSave(newScene, scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.Add($"`{roomName}`: Successfully imported.");
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = textData.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
string cleanLine = line.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(cleanLine))
|
||||||
|
{
|
||||||
|
await import(cleanLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string summary = $"**Import Summary:** {successCount} room(s) imported successfully.\n\n" + string.Join("\n", logs);
|
||||||
|
if (summary.Length > 2000)
|
||||||
|
{
|
||||||
|
summary = summary.Substring(0, 1950) + "\n*...Truncated due to Discord length limits.*";
|
||||||
|
}
|
||||||
|
|
||||||
|
await FollowupAsync(summary);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error (Exception): {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[SlashCommand("import_custom_avatar_item", ".")]
|
||||||
|
public async Task importCustomAvatarItem(ulong RecNetPlayerId, ulong DeluxeId)
|
||||||
|
{
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
using var noAuthHttpClient = new HttpClient();
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {ServerConfig.RRToken}");
|
||||||
|
|
||||||
|
HttpResponseMessage testreq = await httpClient.GetAsync("https://accounts.rec.net/account/me");
|
||||||
|
if (testreq.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: Failed to authenticate with rec.net. (Status: {testreq.StatusCode})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await DeferAsync();
|
||||||
|
if (!IsAdmin())
|
||||||
|
{
|
||||||
|
await FollowupAsync("No");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Account? importAccount = db.Accounts.FindById(DeluxeId);
|
||||||
|
if (importAccount == null)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error: No account found with Deluxe ID {DeluxeId}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponseMessage savesReq = await httpClient.GetAsync($"https://api.rec.net/api/customAvatarItems/v2/fromCreator/{RecNetPlayerId}?take=1000");
|
||||||
|
savesReq.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var Result = JsonSerializer.Deserialize<RecNetResult<CAvatarItemDto>>(await savesReq.Content.ReadAsStringAsync(), _jsonOptions);
|
||||||
|
|
||||||
|
if (Result == null || Result.Results.Count == 0)
|
||||||
|
{
|
||||||
|
await FollowupAsync("No custom avatar items found for that user.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (var item in Result.Results)
|
||||||
|
{
|
||||||
|
if (item != null && !string.IsNullOrEmpty(item.DesignFilename))
|
||||||
|
{
|
||||||
|
if (item.BaseAvatarItemId != null && item.BaseAvatarItemId != 2184)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
using var response = await noAuthHttpClient.GetAsync($"https://cdn.rec.net/img/{item.DesignFilename}");
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using var stream = await response.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
string? remotePath = await cdn.UploadFile(stream, "img");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(remotePath))
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Failed to upload design image for item `{item.Name}`. Skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var response2 = await noAuthHttpClient.GetAsync($"https://cdn.rec.net/img/{item.ThumbnailImageFilename}");
|
||||||
|
|
||||||
|
response2.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
using var stream2 = await response2.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
string? remotePath2 = await cdn.UploadFile(stream2, "img");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(remotePath2))
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Failed to upload thumbnail image for item `{item.Name}`. Skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var customAvatarItem = new CustomAvatarItem
|
||||||
|
{
|
||||||
|
Id = item.CustomAvatarItemId,
|
||||||
|
Accessibility = item.Accessibility,
|
||||||
|
Creator = importAccount,
|
||||||
|
Name = item.Name,
|
||||||
|
Description = item.Description,
|
||||||
|
Price = item.Price,
|
||||||
|
BaseAvatarItemId = item.BaseAvatarItemId,
|
||||||
|
BaseAvatarItemColor = ColorTranslator.FromHtml(item.BaseAvatarItemColor),
|
||||||
|
DesignFilename = remotePath,
|
||||||
|
ThumbnailImageFilename = remotePath2,
|
||||||
|
PreviewOrientation = item.PreviewOrientation
|
||||||
|
};
|
||||||
|
db.CustomAvatarItems.Insert(customAvatarItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
await FollowupAsync($"Successfully imported {Result.Results.Count} custom avatar item(s).");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await FollowupAsync($"Error (Exception): {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using DeluxeBackend.Models;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Interactions;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Discord.Commands
|
||||||
|
{
|
||||||
|
public class LinkCode : InteractionModuleBase<SocketInteractionContext>
|
||||||
|
{
|
||||||
|
private readonly ILiteDbService _db;
|
||||||
|
private readonly INotificationService _ws;
|
||||||
|
|
||||||
|
private static readonly HashSet<ulong> GameRoles = new()
|
||||||
|
{
|
||||||
|
1502718783700078767, 1495587327593021490, 1495580320525844581,
|
||||||
|
1495580213529280563, 1495909230643908729, 1500323661251477635,
|
||||||
|
1495580027453046804, 1501711512996282369, 1495580006712213674,
|
||||||
|
1495580717134905457
|
||||||
|
};
|
||||||
|
|
||||||
|
public LinkCode(ILiteDbService db, INotificationService ws)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_ws = ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("ping", "Check bot latency")]
|
||||||
|
public async Task Ping() => await RespondAsync($"{Context.Client.Latency}ms");
|
||||||
|
|
||||||
|
[SlashCommand("link", "Generate a unique code to link your Deluxe account")]
|
||||||
|
public async Task LinkAccount()
|
||||||
|
{
|
||||||
|
await DeferAsync(ephemeral: true);
|
||||||
|
|
||||||
|
if (Context.User is not SocketGuildUser guildUser)
|
||||||
|
{
|
||||||
|
await FollowupAsync("This command must be used within a server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!guildUser.Roles.Any(r => GameRoles.Contains(r.Id)))
|
||||||
|
{
|
||||||
|
await FollowupAsync("You do not have the required permissions.", ephemeral: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account? account = _db.Accounts.FindOne(x => x.DiscordId == guildUser.Id);
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
var linkedEmbed = new EmbedBuilder()
|
||||||
|
.WithDescription("Your Discord profile is **already linked** to a Deluxe account.")
|
||||||
|
.WithColor(Color.LighterGrey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await FollowupAsync(embed: linkedEmbed, ephemeral: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pendingLink = _db.ActionLinks.FindOne(x =>
|
||||||
|
x.ExtraData!.DiscordUserId == guildUser.Id &&
|
||||||
|
x.ExpiresAt > DateTime.UtcNow &&
|
||||||
|
x.Uses < x.MaxUses);
|
||||||
|
|
||||||
|
if (pendingLink != null)
|
||||||
|
{
|
||||||
|
long unixTime = ((DateTimeOffset)pendingLink.ExpiresAt).ToUnixTimeSeconds();
|
||||||
|
var existingEmbed = new EmbedBuilder()
|
||||||
|
.WithDescription($"You already have an active code:\n\n**`{pendingLink.Code}`**\n\nIt expires **<t:{unixTime}:R>** (<t:{unixTime}:f>).")
|
||||||
|
.WithColor(Color.LighterGrey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await FollowupAsync(embed: existingEmbed, ephemeral: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string code;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
code = GenerateSecureCode(8);
|
||||||
|
var existing = _db.ActionLinks.FindOne(x => x.Code == code);
|
||||||
|
|
||||||
|
if (existing == null) break;
|
||||||
|
|
||||||
|
if (!existing.IsValid)
|
||||||
|
{
|
||||||
|
_db.ActionLinks.Delete(existing.Id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime expiry = DateTime.UtcNow.AddMinutes(10);
|
||||||
|
long expiryUnix = ((DateTimeOffset)expiry).ToUnixTimeSeconds();
|
||||||
|
|
||||||
|
var actionLink = new ActionLink
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
CreatorPlayerId = 1,
|
||||||
|
Description = $"Discord Link: {guildUser.Username}",
|
||||||
|
ExpiresAt = expiry,
|
||||||
|
Type = ActionLinkType.DiscordLink,
|
||||||
|
ExtraData = new ActionLinkExtraData { DiscordUserId = guildUser.Id },
|
||||||
|
MaxUses = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.ActionLinks.Insert(actionLink);
|
||||||
|
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithDescription($"Use the code below to link your account:\n\n**`{code}`**\n\nExpires **<t:{expiryUnix}:R>**.")
|
||||||
|
.WithColor(Color.LighterGrey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await FollowupAsync(embed: embed, ephemeral: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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[i] = symbols[RandomNumberGenerator.GetInt32(symbols.Length)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,891 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace DeluxeBackend
|
||||||
|
{
|
||||||
|
public static class Enums
|
||||||
|
{
|
||||||
|
public enum Service
|
||||||
|
{
|
||||||
|
Auth,
|
||||||
|
API,
|
||||||
|
Commerce,
|
||||||
|
Matchmaking,
|
||||||
|
Notifications,
|
||||||
|
Images,
|
||||||
|
CDN,
|
||||||
|
Storage,
|
||||||
|
Chat,
|
||||||
|
Leaderboard,
|
||||||
|
Accounts,
|
||||||
|
Link,
|
||||||
|
Lists,
|
||||||
|
RoomComments,
|
||||||
|
Clubs,
|
||||||
|
Rooms,
|
||||||
|
PlatformNotifications,
|
||||||
|
Moderation,
|
||||||
|
DataCollection,
|
||||||
|
BugReporting,
|
||||||
|
Discovery,
|
||||||
|
PlayerSettings,
|
||||||
|
Studio,
|
||||||
|
GameLogs,
|
||||||
|
Strings,
|
||||||
|
StringsCDN,
|
||||||
|
WWW,
|
||||||
|
Econ,
|
||||||
|
StudioCDN
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PlatformType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003D1A")]
|
||||||
|
All = -1,
|
||||||
|
[Token(Token = "0x4003D1B")]
|
||||||
|
Steam,
|
||||||
|
[Token(Token = "0x4003D1C")]
|
||||||
|
Oculus,
|
||||||
|
[Token(Token = "0x4003D1D")]
|
||||||
|
PlayStation,
|
||||||
|
[Token(Token = "0x4003D1E")]
|
||||||
|
Xbox,
|
||||||
|
[Token(Token = "0x4003D1F")]
|
||||||
|
RecNet,
|
||||||
|
[Token(Token = "0x4003D20")]
|
||||||
|
IOS,
|
||||||
|
[Token(Token = "0x4003D21")]
|
||||||
|
GooglePlay,
|
||||||
|
[Token(Token = "0x4003D22")]
|
||||||
|
Standalone,
|
||||||
|
[Token(Token = "0x4003D23")]
|
||||||
|
Pico
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x20009F3")]
|
||||||
|
public enum AvatarItemType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002661")]
|
||||||
|
Outfit,
|
||||||
|
[Token(Token = "0x4002662")]
|
||||||
|
HairDye,
|
||||||
|
[Token(Token = "0x4002663")]
|
||||||
|
CustomOutfit
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PlatformMask
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003D25")]
|
||||||
|
None = 0,
|
||||||
|
[Token(Token = "0x4003D26")]
|
||||||
|
Steam = 1,
|
||||||
|
[Token(Token = "0x4003D27")]
|
||||||
|
Oculus = 2,
|
||||||
|
[Token(Token = "0x4003D28")]
|
||||||
|
PlayStation = 4,
|
||||||
|
[Token(Token = "0x4003D29")]
|
||||||
|
Xbox = 8,
|
||||||
|
[Token(Token = "0x4003D2A")]
|
||||||
|
RecNet = 16,
|
||||||
|
[Token(Token = "0x4003D2B")]
|
||||||
|
IOS = 32,
|
||||||
|
[Token(Token = "0x4003D2C")]
|
||||||
|
GooglePlay = 64,
|
||||||
|
[Token(Token = "0x4003D2D")]
|
||||||
|
Standalone = 128,
|
||||||
|
[Token(Token = "0x4003D2E")]
|
||||||
|
Pico = 256,
|
||||||
|
[Token(Token = "0x4003D2F")]
|
||||||
|
All = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RarityType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x400339A")]
|
||||||
|
None = -1,
|
||||||
|
[Token(Token = "0x400339B")]
|
||||||
|
Common,
|
||||||
|
[Token(Token = "0x400339C")]
|
||||||
|
Uncommon = 10,
|
||||||
|
[Token(Token = "0x400339D")]
|
||||||
|
Rare = 20,
|
||||||
|
[Token(Token = "0x400339E")]
|
||||||
|
Epic = 30,
|
||||||
|
[Token(Token = "0x400339F")]
|
||||||
|
Legendary = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum RoomDetailsMask
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
SubRooms = 1,
|
||||||
|
SubRoomsWithMatchmakingSettings = 2,
|
||||||
|
AccountRoles = 4,
|
||||||
|
Tags = 8,
|
||||||
|
PromoContent = 32,
|
||||||
|
Scores = 64,
|
||||||
|
ReplicationIds = 128,
|
||||||
|
LoadScreens = 256,
|
||||||
|
DataBlob = 1024,
|
||||||
|
UnityAssets = 2048,
|
||||||
|
|
||||||
|
StandardDetails = SubRooms | AccountRoles | Tags | PromoContent | LoadScreens | DataBlob,
|
||||||
|
StandardDetailsNoDataBlob = SubRooms | AccountRoles | Tags | PromoContent | LoadScreens,
|
||||||
|
MatchmakingDetails = SubRoomsWithMatchmakingSettings,
|
||||||
|
ReplicationDetails = SubRoomsWithMatchmakingSettings | Tags | ReplicationIds | DataBlob | UnityAssets
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000E94")]
|
||||||
|
public enum RoomAccessibility : byte
|
||||||
|
{
|
||||||
|
[Token(Token = "0x400391F")]
|
||||||
|
Private,
|
||||||
|
[Token(Token = "0x4003920")]
|
||||||
|
Public,
|
||||||
|
[Token(Token = "0x4003921")]
|
||||||
|
Unlisted
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000E96")]
|
||||||
|
public enum RoomRoleType : byte
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003927")]
|
||||||
|
None,
|
||||||
|
[Token(Token = "0x4003928")]
|
||||||
|
Banned,
|
||||||
|
[Token(Token = "0x4003929")]
|
||||||
|
Host = 10,
|
||||||
|
[Token(Token = "0x400392A")]
|
||||||
|
Moderator = 20,
|
||||||
|
[Token(Token = "0x400392B")]
|
||||||
|
CoOwner = 30,
|
||||||
|
[Token(Token = "0x400392C")]
|
||||||
|
TemporaryCoOwner,
|
||||||
|
[Token(Token = "0x400392D")]
|
||||||
|
Creator = 255
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x200000D")]
|
||||||
|
public enum ReleaseType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4000016")]
|
||||||
|
EditorOnly,
|
||||||
|
[Token(Token = "0x4000017")]
|
||||||
|
TestServersOnly,
|
||||||
|
[Token(Token = "0x4000018")]
|
||||||
|
ReleasedToProduction
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000D30")]
|
||||||
|
public enum GiftContextType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x40033A1")]
|
||||||
|
None = -1,
|
||||||
|
[Token(Token = "0x40033A2")]
|
||||||
|
Default,
|
||||||
|
[Token(Token = "0x40033A3")]
|
||||||
|
First_Activity,
|
||||||
|
[Token(Token = "0x40033A4")]
|
||||||
|
Game_Drop,
|
||||||
|
[Token(Token = "0x40033A5")]
|
||||||
|
All_Daily_Challenges_Complete,
|
||||||
|
[Token(Token = "0x40033A6")]
|
||||||
|
All_Weekly_Challenge_Complete,
|
||||||
|
[Token(Token = "0x40033A7")]
|
||||||
|
Daily_Challenge_Complete,
|
||||||
|
[Token(Token = "0x40033A8")]
|
||||||
|
Weekly_Challenge_Complete,
|
||||||
|
[Token(Token = "0x40033A9")]
|
||||||
|
Unassigned_Equipment = 10,
|
||||||
|
[Token(Token = "0x40033AA")]
|
||||||
|
Unassigned_Avatar,
|
||||||
|
[Token(Token = "0x40033AB")]
|
||||||
|
Unassigned_Consumable,
|
||||||
|
[Token(Token = "0x40033AC")]
|
||||||
|
Reacquisition = 20,
|
||||||
|
[Token(Token = "0x40033AD")]
|
||||||
|
Membership,
|
||||||
|
[Token(Token = "0x40033AE")]
|
||||||
|
NUX_TokensAndDressUp = 30,
|
||||||
|
[Token(Token = "0x40033AF")]
|
||||||
|
NUX_Experiment1,
|
||||||
|
[Token(Token = "0x40033B0")]
|
||||||
|
NUX_Experiment2,
|
||||||
|
[Token(Token = "0x40033B1")]
|
||||||
|
NUX_Experiment3,
|
||||||
|
[Token(Token = "0x40033B2")]
|
||||||
|
NUX_Experiment4,
|
||||||
|
[Token(Token = "0x40033B3")]
|
||||||
|
NUX_Experiment5,
|
||||||
|
[Token(Token = "0x40033B4")]
|
||||||
|
GameRewards = 50,
|
||||||
|
[Token(Token = "0x40033B5")]
|
||||||
|
GameRewards_Tokens,
|
||||||
|
[Token(Token = "0x40033B6")]
|
||||||
|
GameRewards_Tokens_Experiment_Override,
|
||||||
|
[Token(Token = "0x40033B7")]
|
||||||
|
LevelUp = 100,
|
||||||
|
[Token(Token = "0x40033B8")]
|
||||||
|
Purchased_Gift_A = 500,
|
||||||
|
[Token(Token = "0x40033B9")]
|
||||||
|
Purchased_Gift_B,
|
||||||
|
[Token(Token = "0x40033BA")]
|
||||||
|
Purchased_Gift_C,
|
||||||
|
[Token(Token = "0x40033BB")]
|
||||||
|
Purchased_Gift_D,
|
||||||
|
[Token(Token = "0x40033BC")]
|
||||||
|
Holiday = 1000,
|
||||||
|
[Token(Token = "0x40033BD")]
|
||||||
|
Contest,
|
||||||
|
[Token(Token = "0x40033BE")]
|
||||||
|
Promotion,
|
||||||
|
[Token(Token = "0x40033BF")]
|
||||||
|
LimitedTime,
|
||||||
|
[Token(Token = "0x40033C0")]
|
||||||
|
LimitedTimeEvent,
|
||||||
|
[Token(Token = "0x40033C1")]
|
||||||
|
Deprecated = 1100,
|
||||||
|
[Token(Token = "0x40033C2")]
|
||||||
|
RecRoyale = 1200,
|
||||||
|
[Token(Token = "0x40033C3")]
|
||||||
|
Friendotron_Gift = 1300,
|
||||||
|
[Token(Token = "0x40033C4")]
|
||||||
|
DEPRECATED_Paintball_ClearCut = 2000,
|
||||||
|
[Token(Token = "0x40033C5")]
|
||||||
|
DEPRECATED_Paintball_Homestead,
|
||||||
|
[Token(Token = "0x40033C6")]
|
||||||
|
DEPRECATED_Paintball_Quarry,
|
||||||
|
[Token(Token = "0x40033C7")]
|
||||||
|
DEPRECATED_Paintball_River,
|
||||||
|
[Token(Token = "0x40033C8")]
|
||||||
|
DEPRECATED_Paintball_Dam,
|
||||||
|
[Token(Token = "0x40033C9")]
|
||||||
|
DEPRECATED_Paintball_DriveIn,
|
||||||
|
[Token(Token = "0x40033CA")]
|
||||||
|
Paintball_ClearCut = 2010,
|
||||||
|
[Token(Token = "0x40033CB")]
|
||||||
|
Paintball_Homestead,
|
||||||
|
[Token(Token = "0x40033CC")]
|
||||||
|
Paintball_Quarry,
|
||||||
|
[Token(Token = "0x40033CD")]
|
||||||
|
Paintball_River,
|
||||||
|
[Token(Token = "0x40033CE")]
|
||||||
|
Paintball_Dam,
|
||||||
|
[Token(Token = "0x40033CF")]
|
||||||
|
Paintball_DriveIn,
|
||||||
|
[Token(Token = "0x40033D0")]
|
||||||
|
DEPRECATED_Discgolf_Propulsion = 3000,
|
||||||
|
[Token(Token = "0x40033D1")]
|
||||||
|
DEPRECATED_Discgolf_Lake,
|
||||||
|
[Token(Token = "0x40033D2")]
|
||||||
|
Discgolf_Propulsion = 3010,
|
||||||
|
[Token(Token = "0x40033D3")]
|
||||||
|
Discgolf_Lake,
|
||||||
|
[Token(Token = "0x40033D4")]
|
||||||
|
Discgolf_Mode_CoopCatch = 3500,
|
||||||
|
[Token(Token = "0x40033D5")]
|
||||||
|
Quest_Goblin_A = 4000,
|
||||||
|
[Token(Token = "0x40033D6")]
|
||||||
|
Quest_Goblin_B,
|
||||||
|
[Token(Token = "0x40033D7")]
|
||||||
|
Quest_Goblin_C,
|
||||||
|
[Token(Token = "0x40033D8")]
|
||||||
|
Quest_Goblin_S,
|
||||||
|
[Token(Token = "0x40033D9")]
|
||||||
|
Quest_Goblin_Consumable,
|
||||||
|
[Token(Token = "0x40033DA")]
|
||||||
|
Quest_Cauldron_A = 4010,
|
||||||
|
[Token(Token = "0x40033DB")]
|
||||||
|
Quest_Cauldron_B,
|
||||||
|
[Token(Token = "0x40033DC")]
|
||||||
|
Quest_Cauldron_C,
|
||||||
|
[Token(Token = "0x40033DD")]
|
||||||
|
Quest_Cauldron_S,
|
||||||
|
[Token(Token = "0x40033DE")]
|
||||||
|
Quest_Cauldron_Consumable,
|
||||||
|
[Token(Token = "0x40033DF")]
|
||||||
|
Quest_Pirate1_A = 4100,
|
||||||
|
[Token(Token = "0x40033E0")]
|
||||||
|
Quest_Pirate1_B,
|
||||||
|
[Token(Token = "0x40033E1")]
|
||||||
|
Quest_Pirate1_C,
|
||||||
|
[Token(Token = "0x40033E2")]
|
||||||
|
Quest_Pirate1_S,
|
||||||
|
[Token(Token = "0x40033E3")]
|
||||||
|
Quest_Pirate1_X,
|
||||||
|
[Token(Token = "0x40033E4")]
|
||||||
|
Quest_Pirate1_Consumable,
|
||||||
|
[Token(Token = "0x40033E5")]
|
||||||
|
Quest_Dracula1_A = 4200,
|
||||||
|
[Token(Token = "0x40033E6")]
|
||||||
|
Quest_Dracula1_B,
|
||||||
|
[Token(Token = "0x40033E7")]
|
||||||
|
Quest_Dracula1_C,
|
||||||
|
[Token(Token = "0x40033E8")]
|
||||||
|
Quest_Dracula1_S,
|
||||||
|
[Token(Token = "0x40033E9")]
|
||||||
|
Quest_Dracula1_X,
|
||||||
|
[Token(Token = "0x40033EA")]
|
||||||
|
Quest_Dracula1_Consumable,
|
||||||
|
[Token(Token = "0x40033EB")]
|
||||||
|
Quest_Dracula1_SS,
|
||||||
|
[Token(Token = "0x40033EC")]
|
||||||
|
Quest_SciFi_A = 4500,
|
||||||
|
[Token(Token = "0x40033ED")]
|
||||||
|
Quest_SciFi_B,
|
||||||
|
[Token(Token = "0x40033EE")]
|
||||||
|
Quest_SciFi_C,
|
||||||
|
[Token(Token = "0x40033EF")]
|
||||||
|
Quest_SciFi_S,
|
||||||
|
[Token(Token = "0x40033F0")]
|
||||||
|
Quest_Scifi_Consumable,
|
||||||
|
[Token(Token = "0x40033F1")]
|
||||||
|
DEPRECATED_Charades = 5000,
|
||||||
|
[Token(Token = "0x40033F2")]
|
||||||
|
Charades,
|
||||||
|
[Token(Token = "0x40033F3")]
|
||||||
|
DEPRECATED_Soccer = 6000,
|
||||||
|
[Token(Token = "0x40033F4")]
|
||||||
|
Soccer,
|
||||||
|
[Token(Token = "0x40033F5")]
|
||||||
|
DEPRECATED_Paddleball = 7000,
|
||||||
|
[Token(Token = "0x40033F6")]
|
||||||
|
Paddleball,
|
||||||
|
[Token(Token = "0x40033F7")]
|
||||||
|
DEPRECATED_Dodgeball = 8000,
|
||||||
|
[Token(Token = "0x40033F8")]
|
||||||
|
Dodgeball,
|
||||||
|
[Token(Token = "0x40033F9")]
|
||||||
|
DEPRECATED_Lasertag = 9000,
|
||||||
|
[Token(Token = "0x40033FA")]
|
||||||
|
Lasertag,
|
||||||
|
[Token(Token = "0x40033FB")]
|
||||||
|
DEPRECATED_Bowling = 10000,
|
||||||
|
[Token(Token = "0x40033FC")]
|
||||||
|
Bowling,
|
||||||
|
[Token(Token = "0x40033FD")]
|
||||||
|
StuntRunner_TheMainEvent_A = 11000,
|
||||||
|
[Token(Token = "0x40033FE")]
|
||||||
|
StuntRunner_TheMainEvent_B,
|
||||||
|
[Token(Token = "0x40033FF")]
|
||||||
|
StuntRunner_TheMainEvent_C,
|
||||||
|
[Token(Token = "0x4003400")]
|
||||||
|
StuntRunner_TheMainEvent_D,
|
||||||
|
[Token(Token = "0x4003401")]
|
||||||
|
StuntRunner_TheMainEvent_S,
|
||||||
|
[Token(Token = "0x4003402")]
|
||||||
|
StuntRunner_TheMainEvent_X,
|
||||||
|
[Token(Token = "0x4003403")]
|
||||||
|
StuntRunner_TheMainEvent_Consumable,
|
||||||
|
[Token(Token = "0x4003404")]
|
||||||
|
StuntRunner_TheMainEvent_SS,
|
||||||
|
[Token(Token = "0x4003405")]
|
||||||
|
RecRally = 12000,
|
||||||
|
[Token(Token = "0x4003406")]
|
||||||
|
Showdown_A = 12100,
|
||||||
|
[Token(Token = "0x4003407")]
|
||||||
|
Showdown_B,
|
||||||
|
[Token(Token = "0x4003408")]
|
||||||
|
Showdown_C,
|
||||||
|
[Token(Token = "0x4003409")]
|
||||||
|
ProgressionEvent = 13000,
|
||||||
|
[Token(Token = "0x400340A")]
|
||||||
|
Store_LaserTag = 100000,
|
||||||
|
[Token(Token = "0x400340B")]
|
||||||
|
Store_RecCenter = 100010,
|
||||||
|
[Token(Token = "0x400340C")]
|
||||||
|
Consumable = 110000,
|
||||||
|
[Token(Token = "0x400340D")]
|
||||||
|
Token = 110100,
|
||||||
|
[Token(Token = "0x400340E")]
|
||||||
|
Punchcard_Challenge_Complete = 110200,
|
||||||
|
[Token(Token = "0x400340F")]
|
||||||
|
All_Punchcard_Challenges_Complete,
|
||||||
|
[Token(Token = "0x4003410")]
|
||||||
|
New_Player_Checklist_Item_Complete = 110300,
|
||||||
|
[Token(Token = "0x4003411")]
|
||||||
|
Commerce_Purchase = 200000,
|
||||||
|
[Token(Token = "0x4003412")]
|
||||||
|
Gift_Card_Redemption = 210000
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000ECF")]
|
||||||
|
[Flags]
|
||||||
|
public enum RoomWarningMask
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003A39")]
|
||||||
|
None = 0,
|
||||||
|
[Token(Token = "0x4003A3A")]
|
||||||
|
Scary = 1,
|
||||||
|
[Token(Token = "0x4003A3B")]
|
||||||
|
Mature = 2,
|
||||||
|
[Token(Token = "0x4003A3C")]
|
||||||
|
FlashingLights = 4,
|
||||||
|
[Token(Token = "0x4003A3D")]
|
||||||
|
IntenseMotion = 8,
|
||||||
|
[Token(Token = "0x4003A3E")]
|
||||||
|
Violence = 16,
|
||||||
|
[Token(Token = "0x4003A3F")]
|
||||||
|
Custom = 32,
|
||||||
|
[Token(Token = "0x4003A40")]
|
||||||
|
Reports = 64
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PlayerType
|
||||||
|
{
|
||||||
|
UNINITIALIZED,
|
||||||
|
VR_WALK,
|
||||||
|
VR_TELEPORT,
|
||||||
|
SCREEN
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000009")]
|
||||||
|
public enum StatusVisibilityType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4000051")]
|
||||||
|
Public,
|
||||||
|
[Token(Token = "0x4000052")]
|
||||||
|
FriendsOnly,
|
||||||
|
[Token(Token = "0x4000053")]
|
||||||
|
FavoriteFriendsOnly,
|
||||||
|
[Token(Token = "0x4000054")]
|
||||||
|
Offline
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x200000A")]
|
||||||
|
public enum VrMovementModeType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4000056")]
|
||||||
|
TELEPORT,
|
||||||
|
[Token(Token = "0x4000057")]
|
||||||
|
WALK
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StorefrontType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003333")]
|
||||||
|
None,
|
||||||
|
[Token(Token = "0x4003334")]
|
||||||
|
LaserTag,
|
||||||
|
[Token(Token = "0x4003335")]
|
||||||
|
RecCenter,
|
||||||
|
[Token(Token = "0x4003336")]
|
||||||
|
Watch,
|
||||||
|
[Token(Token = "0x4003337")]
|
||||||
|
Quest_LostSkulls = 100,
|
||||||
|
[Token(Token = "0x4003338")]
|
||||||
|
Quest_Dracula,
|
||||||
|
[Token(Token = "0x4003339")]
|
||||||
|
Quest_GoldenTrophy,
|
||||||
|
[Token(Token = "0x400333A")]
|
||||||
|
Quest_CrimsonCauldron,
|
||||||
|
[Token(Token = "0x400333B")]
|
||||||
|
RecRoyale = 200,
|
||||||
|
[Token(Token = "0x400333C")]
|
||||||
|
Cafe = 300,
|
||||||
|
[Token(Token = "0x400333D")]
|
||||||
|
Paintball = 400,
|
||||||
|
[Token(Token = "0x400333E")]
|
||||||
|
Paintball_River,
|
||||||
|
[Token(Token = "0x400333F")]
|
||||||
|
Paintball_Homestead,
|
||||||
|
[Token(Token = "0x4003340")]
|
||||||
|
Paintball_Quarry,
|
||||||
|
[Token(Token = "0x4003341")]
|
||||||
|
Paintball_ClearCut,
|
||||||
|
[Token(Token = "0x4003342")]
|
||||||
|
Paintball_Spillway,
|
||||||
|
[Token(Token = "0x4003343")]
|
||||||
|
Paintball_SunsetDriveIn,
|
||||||
|
[Token(Token = "0x4003344")]
|
||||||
|
Bowling = 500,
|
||||||
|
[Token(Token = "0x4003345")]
|
||||||
|
StuntRunner = 600,
|
||||||
|
[Token(Token = "0x4003346")]
|
||||||
|
DormMirror = 700,
|
||||||
|
[Token(Token = "0x4003347")]
|
||||||
|
InventionStore = 800,
|
||||||
|
[Token(Token = "0x4003348")]
|
||||||
|
RoomKeys = 900,
|
||||||
|
[Token(Token = "0x4003349")]
|
||||||
|
Player_Profile = 1000,
|
||||||
|
[Token(Token = "0x400334A")]
|
||||||
|
Room_Save = 1100,
|
||||||
|
[Token(Token = "0x400334B")]
|
||||||
|
RoomCurrency = 1200,
|
||||||
|
[Token(Token = "0x400334C")]
|
||||||
|
Wishlist = 1300,
|
||||||
|
[Token(Token = "0x400334D")]
|
||||||
|
RoomConsumable = 1400,
|
||||||
|
[Token(Token = "0x400334E")]
|
||||||
|
RecRally = 1600,
|
||||||
|
[Token(Token = "0x400334F")]
|
||||||
|
PopUpShop_1 = 1700,
|
||||||
|
[Token(Token = "0x4003350")]
|
||||||
|
PopUpShop_2,
|
||||||
|
[Token(Token = "0x4003351")]
|
||||||
|
PopUpShop_3,
|
||||||
|
[Token(Token = "0x4003352")]
|
||||||
|
PopUpShop_4,
|
||||||
|
[Token(Token = "0x4003353")]
|
||||||
|
PopUpShop_5,
|
||||||
|
[Token(Token = "0x4003354")]
|
||||||
|
CustomAvatarItems = 1800,
|
||||||
|
[Token(Token = "0x4003355")]
|
||||||
|
ProgressionEventPurchasableXpBoosts = 1900,
|
||||||
|
[Token(Token = "0x4003356")]
|
||||||
|
PopUpShop_ProgressionEvent_1 = 2000,
|
||||||
|
[Token(Token = "0x4003357")]
|
||||||
|
PopUpShop_ProgressionEvent_2,
|
||||||
|
[Token(Token = "0x4003358")]
|
||||||
|
PopUpShop_ProgressionEvent_3,
|
||||||
|
[Token(Token = "0x4003359")]
|
||||||
|
PopUpShop_ProgressionEvent_4,
|
||||||
|
[Token(Token = "0x400335A")]
|
||||||
|
PopUpShop_ProgressionEvent_5
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000D29")]
|
||||||
|
public enum PMKKDOJNMGM
|
||||||
|
{
|
||||||
|
[Token(Token = "0x400335C")]
|
||||||
|
Invalid,
|
||||||
|
[Token(Token = "0x400335D")]
|
||||||
|
LaserTagTickets,
|
||||||
|
[Token(Token = "0x400335E")]
|
||||||
|
RecCenterTokens,
|
||||||
|
[Token(Token = "0x400335F")]
|
||||||
|
LostSkullsGold = 100,
|
||||||
|
[Token(Token = "0x4003360")]
|
||||||
|
DraculaSilver,
|
||||||
|
[Token(Token = "0x4003361")]
|
||||||
|
RecRoyale_Season1 = 200,
|
||||||
|
[Token(Token = "0x4003362")]
|
||||||
|
RoomCurrency = 300,
|
||||||
|
[Token(Token = "0x4003363")]
|
||||||
|
ProgressionEvent = 400
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000BD1")]
|
||||||
|
public enum RoomInstanceType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002E6A")]
|
||||||
|
Public,
|
||||||
|
[Token(Token = "0x4002E6B")]
|
||||||
|
Private,
|
||||||
|
[Token(Token = "0x4002E6C")]
|
||||||
|
Dormroom,
|
||||||
|
[Token(Token = "0x4002E6D")]
|
||||||
|
Event,
|
||||||
|
[Token(Token = "0x4002E6E")]
|
||||||
|
Meetup,
|
||||||
|
[Token(Token = "0x4002E6F")]
|
||||||
|
Clubhouse,
|
||||||
|
[Token(Token = "0x4002E70")]
|
||||||
|
MultiInstanceEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000BD2")]
|
||||||
|
public enum MatchmakingPolicyType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002E72")]
|
||||||
|
Default,
|
||||||
|
[Token(Token = "0x4002E73")]
|
||||||
|
Avoid,
|
||||||
|
[Token(Token = "0x4002E74")]
|
||||||
|
Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AssetBundleVersion
|
||||||
|
{
|
||||||
|
InitialRelease_Unity_2019_4_26f1,
|
||||||
|
Unity_2020_3_33f1,
|
||||||
|
Current = Unity_2020_3_33f1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AssetBundleType : byte
|
||||||
|
{
|
||||||
|
WindowsDesktop,
|
||||||
|
MobileIOS,
|
||||||
|
MobileAndroid,
|
||||||
|
//[Obsolete("Quest bundles are Obsolete. Use MobileAndroid instead", true)]
|
||||||
|
OculusQuest,
|
||||||
|
XboxOne,
|
||||||
|
Playstation4,
|
||||||
|
Playstation5
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActionLinkType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002CFE")]
|
||||||
|
Unknown = -1,
|
||||||
|
[Token(Token = "0x4002CFF")]
|
||||||
|
Friend,
|
||||||
|
[Token(Token = "0x4002D00")]
|
||||||
|
Referral,
|
||||||
|
[Token(Token = "0x4002D01")]
|
||||||
|
Meetup,
|
||||||
|
[Token(Token = "0x4002D02")]
|
||||||
|
Club,
|
||||||
|
[Token(Token = "0x4002D03")]
|
||||||
|
PlayerEvent,
|
||||||
|
[Token(Token = "0x4002D04")]
|
||||||
|
Room,
|
||||||
|
[Token(Token = "0x4002D05")]
|
||||||
|
Influencer,
|
||||||
|
[Token(Token = "0x4002D06")]
|
||||||
|
Photo,
|
||||||
|
DiscordLink//fake enum value
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PronounsType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002564")]
|
||||||
|
None = 0,
|
||||||
|
[Token(Token = "0x4002565")]
|
||||||
|
SheHer = 1,
|
||||||
|
[Token(Token = "0x4002566")]
|
||||||
|
HeHim = 2,
|
||||||
|
[Token(Token = "0x4002567")]
|
||||||
|
TheyThem = 4,
|
||||||
|
[Token(Token = "0x4002568")]
|
||||||
|
ZeHir = 8,
|
||||||
|
[Token(Token = "0x4002569")]
|
||||||
|
ZeZir = 16,
|
||||||
|
[Token(Token = "0x400256A")]
|
||||||
|
XeXem = 32
|
||||||
|
}
|
||||||
|
[Flags]
|
||||||
|
public enum IdentityFlagsType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x400256C")]
|
||||||
|
None = 0,
|
||||||
|
[Token(Token = "0x400256D")]
|
||||||
|
LGBTQIA = 1,
|
||||||
|
[Token(Token = "0x400256E")]
|
||||||
|
Transgender = 2,
|
||||||
|
[Token(Token = "0x400256F")]
|
||||||
|
Bisexual = 4,
|
||||||
|
[Token(Token = "0x4002570")]
|
||||||
|
Lesbian = 8,
|
||||||
|
[Token(Token = "0x4002571")]
|
||||||
|
Pansexual = 16,
|
||||||
|
[Token(Token = "0x4002572")]
|
||||||
|
Asexual = 32,
|
||||||
|
[Token(Token = "0x4002573")]
|
||||||
|
Intersex = 64,
|
||||||
|
[Token(Token = "0x4002574")]
|
||||||
|
Genderqueer = 128,
|
||||||
|
[Token(Token = "0x4002575")]
|
||||||
|
Nonbinary = 256,
|
||||||
|
[Token(Token = "0x4002576")]
|
||||||
|
Aromantic = 512
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RelationshipType { None = 0, Sent = 1, Received = 2, Friend = 3 }
|
||||||
|
public enum MuteState { None = 0, Local = 1, Remote = 2 }
|
||||||
|
public enum IgnoreState { None = 0, Local = 1, Remote = 2 }
|
||||||
|
|
||||||
|
public enum MessageType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002F0B")]
|
||||||
|
GameInvite,
|
||||||
|
[Token(Token = "0x4002F0C")]
|
||||||
|
GameInviteDeclined,
|
||||||
|
[Token(Token = "0x4002F0D")]
|
||||||
|
GameJoinFailed,
|
||||||
|
[Token(Token = "0x4002F0E")]
|
||||||
|
PartyActivitySwitch,
|
||||||
|
[Token(Token = "0x4002F0F")]
|
||||||
|
FriendInvite,
|
||||||
|
[Token(Token = "0x4002F10")]
|
||||||
|
VoteToKick,
|
||||||
|
[Token(Token = "0x4002F11")]
|
||||||
|
GameInviteV2,
|
||||||
|
[Token(Token = "0x4002F12")]
|
||||||
|
PartyActivitySwitchV2,
|
||||||
|
[Token(Token = "0x4002F13")]
|
||||||
|
RequestGameInvite = 10,
|
||||||
|
[Token(Token = "0x4002F14")]
|
||||||
|
RequestGameInviteDeclined,
|
||||||
|
[Token(Token = "0x4002F15")]
|
||||||
|
FriendStatusOnline = 20,
|
||||||
|
[Token(Token = "0x4002F16")]
|
||||||
|
TextMessage = 30,
|
||||||
|
[Token(Token = "0x4002F17")]
|
||||||
|
FriendRequestAccepted = 40,
|
||||||
|
[Token(Token = "0x4002F18")]
|
||||||
|
FriendCodeUsed,
|
||||||
|
[Token(Token = "0x4002F19")]
|
||||||
|
PlayerCheer = 50,
|
||||||
|
[Token(Token = "0x4002F1A")]
|
||||||
|
PlayerCheerAnonymous,
|
||||||
|
[Token(Token = "0x4002F1B")]
|
||||||
|
RoomCoOwnerAdded = 60,
|
||||||
|
[Token(Token = "0x4002F1C")]
|
||||||
|
RoomCoOwnerRemoved,
|
||||||
|
[Token(Token = "0x4002F1D")]
|
||||||
|
RoomCoOwnerInvited,
|
||||||
|
[Token(Token = "0x4002F1E")]
|
||||||
|
RoomTranslationCompleted,
|
||||||
|
[Token(Token = "0x4002F1F")]
|
||||||
|
CreatorPublishedNewRoom = 70,
|
||||||
|
[Token(Token = "0x4002F20")]
|
||||||
|
PlayerAttendingEvent = 80,
|
||||||
|
[Token(Token = "0x4002F21")]
|
||||||
|
PlayerEventInvitation,
|
||||||
|
[Token(Token = "0x4002F22")]
|
||||||
|
DEPRECATED_GroupInvitation = 90,
|
||||||
|
[Token(Token = "0x4002F23")]
|
||||||
|
DEPRECATED_PlayerJoinedGroup,
|
||||||
|
[Token(Token = "0x4002F24")]
|
||||||
|
CoachMessage = 100,
|
||||||
|
[Token(Token = "0x4002F25")]
|
||||||
|
NewRoomComments = 110,
|
||||||
|
[Token(Token = "0x4002F26")]
|
||||||
|
PartyUpRequest = 120,
|
||||||
|
[Token(Token = "0x4002F27")]
|
||||||
|
FriendIntroduction = 130,
|
||||||
|
[Token(Token = "0x4002F28")]
|
||||||
|
ClubMemberInvited = 200,
|
||||||
|
[Token(Token = "0x4002F29")]
|
||||||
|
ClubModeratorInvited,
|
||||||
|
[Token(Token = "0x4002F2A")]
|
||||||
|
ClubCoownerInvited,
|
||||||
|
[Token(Token = "0x4002F2B")]
|
||||||
|
RoomEarningsDistributionCoOwnerAdded = 210,
|
||||||
|
[Token(Token = "0x4002F2C")]
|
||||||
|
RoomEarningsDistributionCoOwnerRemoved,
|
||||||
|
[Token(Token = "0x4002F2D")]
|
||||||
|
RoomEarningsDistributionPercentageUpdated,
|
||||||
|
[Token(Token = "0x4002F2E")]
|
||||||
|
VirtualClubAnnouncementRoomPublished = 100000,
|
||||||
|
[Token(Token = "0x4002F2F")]
|
||||||
|
VirtualClubAnnouncementInventionPublished,
|
||||||
|
[Token(Token = "0x4002F30")]
|
||||||
|
VirtualClubAnnouncementGeneric,
|
||||||
|
[Token(Token = "0x4002F31")]
|
||||||
|
VirtualClubAnnouncementPlayerEventPublished,
|
||||||
|
[Token(Token = "0x4002F32")]
|
||||||
|
VirtualClubAnnouncementClub,
|
||||||
|
[Token(Token = "0x4002F33")]
|
||||||
|
VirtualClubAnnouncementPlayer,
|
||||||
|
[Token(Token = "0x4002F34")]
|
||||||
|
VirtualClubAnnouncementCode,
|
||||||
|
[Token(Token = "0x4002F35")]
|
||||||
|
VirtualClubAnnouncementPhoto,
|
||||||
|
[Token(Token = "0x4002F36")]
|
||||||
|
VirtualRoomNotification,
|
||||||
|
[Token(Token = "0x4002F37")]
|
||||||
|
VirtualBroadcastingRequest,
|
||||||
|
[Token(Token = "0x4002F38")]
|
||||||
|
VirtualLeagueAssignment
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SavedImageType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
[Token(Token = "0x4002AF1")]
|
||||||
|
ShareCamera,
|
||||||
|
[Token(Token = "0x4002AF2")]
|
||||||
|
OutfitThumbnail,
|
||||||
|
[Token(Token = "0x4002AF3")]
|
||||||
|
RoomThumbnail,
|
||||||
|
[Token(Token = "0x4002AF4")]
|
||||||
|
ProfileThumbnail,
|
||||||
|
[Token(Token = "0x4002AF5")]
|
||||||
|
InventionThumbnail,
|
||||||
|
[Token(Token = "0x4002AF6")]
|
||||||
|
PlayerEventThumbnail,
|
||||||
|
[Token(Token = "0x4002AF7")]
|
||||||
|
RoomLoadScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SavedImageAccessibility
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002AEC")]
|
||||||
|
Private,
|
||||||
|
[Token(Token = "0x4002AED")]
|
||||||
|
Public,
|
||||||
|
[Token(Token = "0x4002AEE")]
|
||||||
|
FriendsOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000D26")]
|
||||||
|
public enum FileType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4003328")]
|
||||||
|
Unknown,
|
||||||
|
[Token(Token = "0x4003329")]
|
||||||
|
SubRoomSave,
|
||||||
|
[Token(Token = "0x400332A")]
|
||||||
|
Holotar,
|
||||||
|
[Token(Token = "0x400332B")]
|
||||||
|
Image,
|
||||||
|
[Token(Token = "0x400332C")]
|
||||||
|
Video,
|
||||||
|
[Token(Token = "0x400332D")]
|
||||||
|
Invention,
|
||||||
|
[Token(Token = "0x400332E")]
|
||||||
|
RoomMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DeviceClassType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002A4B")]
|
||||||
|
Unknown,
|
||||||
|
[Token(Token = "0x4002A4C")]
|
||||||
|
VR,
|
||||||
|
[Token(Token = "0x4002A4D")]
|
||||||
|
Screen,
|
||||||
|
[Token(Token = "0x4002A4E")]
|
||||||
|
Mobile,
|
||||||
|
[Token(Token = "0x4002A4F")]
|
||||||
|
VRLow,
|
||||||
|
[Token(Token = "0x4002A50")]
|
||||||
|
Quest2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BroadcastPerms
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4002DDA")]
|
||||||
|
None,
|
||||||
|
[Token(Token = "0x4002DDB")]
|
||||||
|
RoomOwners = 256,
|
||||||
|
[Token(Token = "0x4002DDC")]
|
||||||
|
All = 2147483647
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x2000963")]
|
||||||
|
public enum PreviewOrientationType
|
||||||
|
{
|
||||||
|
[Token(Token = "0x40024A5")]
|
||||||
|
Front,
|
||||||
|
[Token(Token = "0x40024A6")]
|
||||||
|
Back
|
||||||
|
}
|
||||||
|
|
||||||
|
[Token(Token = "0x200056E")]
|
||||||
|
public enum InventionPermission
|
||||||
|
{
|
||||||
|
[Token(Token = "0x4001291")]
|
||||||
|
Unassigned,
|
||||||
|
[Token(Token = "0x4001292")]
|
||||||
|
LimitedOneUseOnly = 10,
|
||||||
|
[Token(Token = "0x4001293")]
|
||||||
|
DisallowKeyLock = 15,
|
||||||
|
[Token(Token = "0x4001294")]
|
||||||
|
UseOnly = 20,
|
||||||
|
[Token(Token = "0x4001295")]
|
||||||
|
EditAndSave = 40,
|
||||||
|
[Token(Token = "0x4001296")]
|
||||||
|
Publish = 60,
|
||||||
|
[Token(Token = "0x4001297")]
|
||||||
|
Charge = 80,
|
||||||
|
[Token(Token = "0x4001298")]
|
||||||
|
Unlimited = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class EquipmentWardrobeRuntimeConfig
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class EquipmentSkinMappingData
|
||||||
|
{
|
||||||
|
}
|
||||||
|
[Serializable]
|
||||||
|
public class SkinData
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using DeluxeBackend.Models;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Extensions
|
||||||
|
{
|
||||||
|
public static class RoomExtensions
|
||||||
|
{
|
||||||
|
public static async Task<Dictionary<string, object>> ToDictionary(this Room room, DiscordBotService discord, ILiteDbService db, RoomDetailsMask include = RoomDetailsMask.None)
|
||||||
|
{
|
||||||
|
List<SubRoom> subRooms = db.SubRooms.Include(x => x.CurrentSave).Include(x => x.Room).Find(x => x.Room.Id == room.Id).ToList();
|
||||||
|
Dictionary<string, object> response = new()
|
||||||
|
{
|
||||||
|
["RoomId"] = room.Id,
|
||||||
|
["Accessibility"] = (int)room.Accessibility,
|
||||||
|
["CloningAllowed"] = room.CloningAllowed,
|
||||||
|
["CreatedAt"] = room.CreatedAt.ToString("O"),
|
||||||
|
["CreatorAccountId"] = room.Creator?.Id ?? -1,
|
||||||
|
["CustomWarning"] = room.CustomWarning ?? "",
|
||||||
|
["Description"] = room.Description ?? "",
|
||||||
|
["DisableMicAutoMute"] = room.DisableMicAutoMute,
|
||||||
|
["DisableRoomComments"] = room.DisableRoomComments,
|
||||||
|
["EncryptVoiceChat"] = room.EncryptVoiceChat,
|
||||||
|
["ImageName"] = room.ImageName ?? "DefaultRoomImage.jpg",
|
||||||
|
["IsDeveloperOwned"] = room.Creator != null && (room.Creator.Id == 1 || await room.Creator.HasRoleAsync(discord, "Developer")),
|
||||||
|
["IsDorm"] = room.IsDorm,
|
||||||
|
["IsRRO"] = room.IsRRO,
|
||||||
|
["MinLevel"] = room.MinLevel,
|
||||||
|
["Name"] = room.IsDorm ? "DormRoom" : (room.Name ?? "NO NAME PLS UPDATE"),
|
||||||
|
["MaxPlayerCalculationMode"] = 1,
|
||||||
|
["MaxPlayers"] = subRooms.Count != 0 ? subRooms.Max(x => x.MaxPlayers) : -1,
|
||||||
|
["State"] = 0,
|
||||||
|
["Stats"] = new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
["CheerCount"] = room.Stats.CheeredIds.Count,
|
||||||
|
["FavoriteCount"] = room.Stats.FavoritedIds.Count,
|
||||||
|
["VisitCount"] = room.Stats.VisitCount,
|
||||||
|
["VisitorCount"] = room.Stats.VisitorIds.Count
|
||||||
|
},
|
||||||
|
["SupportsJuniors"] = room.SupportedPlayerTypes.HasFlag(RoomSupports.Juniors),
|
||||||
|
["SupportsMobile"] = true,
|
||||||
|
["SupportsQuest2"] = true,
|
||||||
|
["SupportsVRLow"] = true,
|
||||||
|
["SupportsWalkVR"] = room.SupportedPlayerTypes.HasFlag(RoomSupports.WalkVR),
|
||||||
|
["SupportsTeleportVR"] = room.SupportedPlayerTypes.HasFlag(RoomSupports.TeleportVR),
|
||||||
|
["SupportsScreens"] = room.SupportedPlayerTypes.HasFlag(RoomSupports.Screens),
|
||||||
|
["SupportsLevelVoting"] = false,
|
||||||
|
["Version"] = 0,
|
||||||
|
["ToxmodEnabled"] = false,
|
||||||
|
["RankedEntityId"] = room.Id.ToString(),
|
||||||
|
["WarningMask"] = (int)room.WarningMask,
|
||||||
|
["AutoLocalizeRoom"] = false,
|
||||||
|
["LocalizationContext"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["LocalizedFields"] = new List<string> { }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (room.PersistenceVersion.HasValue)
|
||||||
|
{
|
||||||
|
response["PersistenceVersion"] = room.PersistenceVersion.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.PromoContent))
|
||||||
|
{
|
||||||
|
response["PromoExternalContent"] = new List<Dictionary<string, object>>();
|
||||||
|
response["PromoImages"] = new List<Dictionary<string, object>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.AccountRoles))
|
||||||
|
{
|
||||||
|
List<Dictionary<string, object>> roles = [];
|
||||||
|
if (room.Creator != null)
|
||||||
|
{
|
||||||
|
roles.Add(new()
|
||||||
|
{
|
||||||
|
["AccountId"] = room.Creator.Id,
|
||||||
|
["InvitedRole"] = (int)RoomRoleType.None,
|
||||||
|
["Role"] = (int)RoomRoleType.Creator
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
foreach (RoomRole role in room.Roles)
|
||||||
|
{
|
||||||
|
roles.Add(new()
|
||||||
|
{
|
||||||
|
["AccountId"] = role.AccountId,
|
||||||
|
["InvitedRole"] = (int)role.InvitedRole,
|
||||||
|
["Role"] = (int)role.Role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
response["Roles"] = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.SubRooms) || include.HasFlag(RoomDetailsMask.SubRoomsWithMatchmakingSettings))
|
||||||
|
{
|
||||||
|
response["SubRooms"] = subRooms.Select(x => x.ToDictionary(include.HasFlag(RoomDetailsMask.DataBlob), include.HasFlag(RoomDetailsMask.MatchmakingDetails))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.Tags))
|
||||||
|
{
|
||||||
|
response["Tags"] = new List<Dictionary<string, object>>()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
["Tag"] = "beta",
|
||||||
|
["Type"] = 1
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
["Tag"] = "limitsv2",
|
||||||
|
["Type"] = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.DataBlob))
|
||||||
|
{
|
||||||
|
response["DataBlob"] = room.DataBlob ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include.HasFlag(RoomDetailsMask.LoadScreens))
|
||||||
|
{
|
||||||
|
response["LoadScreens"] = new List<Dictionary<string, object>>();
|
||||||
|
response["LoadScreenLocked"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using DeluxeBackend.Models;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using Discord;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Hubs
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
public class NotificationHub : Hub
|
||||||
|
{
|
||||||
|
private readonly ILiteDbService db;
|
||||||
|
private readonly IJwtService jwt;
|
||||||
|
private readonly INotificationService ws;
|
||||||
|
//private readonly INotificationService notificationService;
|
||||||
|
|
||||||
|
public NotificationHub(ILiteDbService _db, IJwtService _jet, INotificationService _ws)
|
||||||
|
{
|
||||||
|
db = _db;
|
||||||
|
jwt = _jet;
|
||||||
|
ws= _ws;
|
||||||
|
}
|
||||||
|
public class SubscriptionParam
|
||||||
|
{
|
||||||
|
public List<long> PlayerIds { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task SubscribeToPlayers(SubscriptionParam parms)
|
||||||
|
{
|
||||||
|
/*var subscriberId = Context.User?.FindFirst("sub")?.Value;
|
||||||
|
if (subscriberId == null) return;
|
||||||
|
|
||||||
|
var currentSubs = Context.Items["subs"] as HashSet<long> ?? [];
|
||||||
|
var newSet = new HashSet<long>(parms.PlayerIds);
|
||||||
|
|
||||||
|
foreach (var oldTarget in currentSubs.Where(id => !newSet.Contains(id)))
|
||||||
|
{
|
||||||
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"subs_{oldTarget}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var newTarget in newSet.Where(id => !currentSubs.Contains(id)))
|
||||||
|
{
|
||||||
|
await Groups.AddToGroupAsync(Context.ConnectionId, $"subs_{newTarget}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Context.Items["subs"] = newSet;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||||
|
{
|
||||||
|
await base.OnDisconnectedAsync(exception);
|
||||||
|
Account? account = await jwt.GetLogin(Context.User);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class Account
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public required string Username { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string PasswordHash { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<string> Roles { get; set; } = [];
|
||||||
|
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
public string ImageName { get; set; } = "DefaultProfileImage";
|
||||||
|
public DateOnly? Birthday { get; set; } = null;
|
||||||
|
public string Bio { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public ulong? DiscordId { get; set; } = null;
|
||||||
|
|
||||||
|
public PronounsType Pronouns { get; set; } = PronounsType.None;
|
||||||
|
public IdentityFlagsType IdentityFlags { get; set; } = IdentityFlagsType.None;
|
||||||
|
|
||||||
|
public Dictionary<long, DateTime> VisitedRooms { get; set; } = [];
|
||||||
|
public bool IsRecentHistoryVisible { get; set; } = true;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public bool? IsJunior
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Birthday.HasValue) return null;
|
||||||
|
|
||||||
|
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||||
|
return Birthday.Value.AddYears(13) > today;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
private readonly Dictionary<string, bool> _roleCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
private DateTime _cacheExpiration = DateTime.MinValue;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public async Task<bool> HasRoleAsync(DiscordBotService discord, string role)
|
||||||
|
{
|
||||||
|
if (Roles.Any(r => r.Equals(role, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!DiscordId.HasValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (DateTime.UtcNow > _cacheExpiration)
|
||||||
|
{
|
||||||
|
_roleCache.Clear();
|
||||||
|
_cacheExpiration = DateTime.UtcNow.Add(CacheDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_roleCache.TryGetValue(role, out bool hasRole))
|
||||||
|
{
|
||||||
|
return hasRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool externalHasRole = await discord.HasRole(DiscordId.Value, role);
|
||||||
|
_roleCache[role] = externalHasRole;
|
||||||
|
|
||||||
|
return externalHasRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionaryMe()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["accountId"] = Id,
|
||||||
|
["availableUsernameChanges"] = 0,
|
||||||
|
["birthday"] = Birthday?.ToString("o"),
|
||||||
|
["username"] = Username,
|
||||||
|
["displayName"] = DisplayName ?? Username,
|
||||||
|
["profileImage"] = ImageName,
|
||||||
|
["isJunior"] = IsJunior,
|
||||||
|
["platforms"] = 1,//steam,
|
||||||
|
["personalPronouns"] = (int)Pronouns,
|
||||||
|
["identityFlags"] = (int)IdentityFlags,
|
||||||
|
["createdAt"] = CreatedAt.ToString("o")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["accountId"] = Id,
|
||||||
|
["username"] = Username,
|
||||||
|
["displayName"] = DisplayName ?? Username,
|
||||||
|
["profileImage"] = ImageName,
|
||||||
|
["isJunior"] = IsJunior,
|
||||||
|
["platforms"] = 1,//steam,
|
||||||
|
["personalPronouns"] = (int)Pronouns,
|
||||||
|
["identityFlags"] = (int)IdentityFlags,
|
||||||
|
["createdAt"] = CreatedAt.ToString("o")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class AccountAvatar
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
|
||||||
|
public string OutfitSelections { get; set; } = "";
|
||||||
|
public string OutfitSelectionsV2 { get; set; } = "";
|
||||||
|
public string FaceFeatures { get; set; } = "";
|
||||||
|
public string SkinColor { get; set; } = "";
|
||||||
|
public string HairColor { get; set; } = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class AccountPresence
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
public PlayerType PlayerType { get; set; } = PlayerType.UNINITIALIZED;
|
||||||
|
public StatusVisibilityType StatusVisibility { get; set; } = StatusVisibilityType.Public;
|
||||||
|
public PlatformType LastPlatform { get; set; } = PlatformType.All;
|
||||||
|
//public something LastDeviceClass { get; set; }
|
||||||
|
public VrMovementModeType VrMovementMode { get; set; } = VrMovementModeType.TELEPORT;
|
||||||
|
public DateTime LastOnline { get; set; } = DateTime.UtcNow;
|
||||||
|
public bool IsOnline { get; set; } = false;
|
||||||
|
public string? AppVersion { get; set; } = null;
|
||||||
|
|
||||||
|
[BsonRef("roominstances")]
|
||||||
|
public RoomInstance? Instance { get; set; } = null;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<string, object?>? RoomInstance = null;
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
RoomInstance = Instance.ToDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
return new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
["serverTime"] = DateTime.UtcNow.Ticks,
|
||||||
|
["playerId"] = Account!.Id,
|
||||||
|
["statusVisibility"] = (int)StatusVisibility,
|
||||||
|
["platform"] = (int)PlatformType.Steam,//(int)LastPlatform,
|
||||||
|
["deviceClass"] = 0,
|
||||||
|
["roomInstance"] = RoomInstance,
|
||||||
|
["vrMovementMode"] = (int)VrMovementMode,
|
||||||
|
["lastOnline"] = LastOnline,
|
||||||
|
["isOnline"] = IsOnline,
|
||||||
|
["appVersion"] = AppVersion
|
||||||
|
};
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class AccountSetting
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
|
||||||
|
public required string Key { get; set; }
|
||||||
|
public required string Value { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class ActionLinkExtraData
|
||||||
|
{
|
||||||
|
public ulong? DiscordUserId { get; set; } = null;
|
||||||
|
public long? RoomId { get; set; } = null;
|
||||||
|
}
|
||||||
|
public class ActionLink
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
public PlatformType? Platform { get; set; } = null;
|
||||||
|
public required string Code { get; set; }
|
||||||
|
public required long CreatorPlayerId { get; set; }
|
||||||
|
public required string Description { get; set; }
|
||||||
|
public required ActionLinkType Type { get; set; }
|
||||||
|
public ActionLinkExtraData? ExtraData { get; set; } = null;
|
||||||
|
public required DateTime ExpiresAt { get; set; }
|
||||||
|
public int MaxUses { get; set; } = 100;
|
||||||
|
public int Uses { get; set; } = 0;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
bool timeValid = ExpiresAt.Kind == DateTimeKind.Utc
|
||||||
|
? ExpiresAt > now
|
||||||
|
: ExpiresAt.ToUniversalTime() > now;
|
||||||
|
|
||||||
|
return timeValid && Uses < MaxUses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<string, object> dataMeow = new()
|
||||||
|
{
|
||||||
|
["Type"] = (int)Type
|
||||||
|
};
|
||||||
|
if (Type == ActionLinkType.DiscordLink)
|
||||||
|
{
|
||||||
|
dataMeow["Type"] = (int)ActionLinkType.Room;
|
||||||
|
ExtraData!.RoomId = 2;
|
||||||
|
}
|
||||||
|
if (Platform != null)
|
||||||
|
{
|
||||||
|
dataMeow["Platform"] = (int)Platform;
|
||||||
|
}
|
||||||
|
if (ExtraData != null)
|
||||||
|
{
|
||||||
|
dataMeow["ExtraJson"] = System.Text.Json.JsonSerializer.Serialize(ExtraData);
|
||||||
|
}
|
||||||
|
Dictionary<string, object> data = new()
|
||||||
|
{
|
||||||
|
["creatorPlayerId"] = CreatorPlayerId,
|
||||||
|
["data"] = System.Text.Json.JsonSerializer.Serialize(dataMeow),
|
||||||
|
["isValid"] = IsValid
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class AvatarSaved
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
public required int Slot { get; set; }
|
||||||
|
public required string PreviewImageName { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string OutfitSelections { get; set; } = "";
|
||||||
|
public string OutfitSelectionsV2 { get; set; } = "";
|
||||||
|
public string FaceFeatures { get; set; } = "";
|
||||||
|
public string SkinColor { get; set; } = "";
|
||||||
|
public string HairColor { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Numerics;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class Cachedlogin
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
public required PlatformType Platform { get; set; }
|
||||||
|
public required string PlatformId { get; set; }
|
||||||
|
public required DateTime LastLoginAt { get; set; }
|
||||||
|
public bool RequirePassword { get; set; } = false;
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Drawing;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class CustomAvatarItem
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Creator { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public required int Price { get; set; }
|
||||||
|
public bool IsFeatured { get; set; } = false;
|
||||||
|
public int? BaseAvatarItemId { get; set; }
|
||||||
|
public required Color BaseAvatarItemColor { get; set; }
|
||||||
|
public required string DesignFilename { get; set; }
|
||||||
|
public required string ThumbnailImageFilename { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime ModifiedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public RoomAccessibility Accessibility { get; set; } = RoomAccessibility.Public;//i love how i just use RoomAccessibility for any accessibility
|
||||||
|
public required PreviewOrientationType PreviewOrientation { get; set; }
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<string, object> data= new()
|
||||||
|
{
|
||||||
|
["CustomAvatarItemId"] = Id.ToString(),
|
||||||
|
["CreatorAccountId"] = Creator.Id,
|
||||||
|
["Name"] = Name,
|
||||||
|
["Description"] = Description ?? "",
|
||||||
|
["Price"] = Price,
|
||||||
|
["Accessibility"] = (int)Accessibility,
|
||||||
|
["IsFeatured"] = IsFeatured,
|
||||||
|
["BaseAvatarItemColor"] = $"{ColorTranslator.ToHtml(BaseAvatarItemColor)}",
|
||||||
|
["DesignFilename"] = DesignFilename,
|
||||||
|
["ThumbnailImageFilename"] = ThumbnailImageFilename,
|
||||||
|
["CreatedAt"] = CreatedAt.ToString("O"),
|
||||||
|
["ModifiedAt"] = ModifiedAt.ToString("O"),
|
||||||
|
};
|
||||||
|
if (BaseAvatarItemId.HasValue)
|
||||||
|
{
|
||||||
|
data["BaseAvatarItemId"] = BaseAvatarItemId.Value;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class Invention
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
public required Guid ReplicationId { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Creator { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Description { get; set; }
|
||||||
|
public required string ImageName { get; set; }
|
||||||
|
public List<InventionVersion> Versions { get; set; } = [];
|
||||||
|
public required int CurrentVersionNumber { get; set; }
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
public DateTime ModifiedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime? FirstPublishedAt { get; set; }
|
||||||
|
[BsonRef("rooms")]
|
||||||
|
public required Room Room { get; set; }
|
||||||
|
public List<long> DownloadIds { get; set; } = [];
|
||||||
|
public List<long> CheeredIds { get; set; } = [];
|
||||||
|
|
||||||
|
public InventionPermission CreatorPermission { get; set; } = InventionPermission.Unassigned;
|
||||||
|
public InventionPermission GeneralPermission { get; set; } = InventionPermission.Unassigned;
|
||||||
|
public bool AllowTrial { get; set; } = false;
|
||||||
|
public int? Price { get; set; }
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object?> ToDictionary()
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
Dictionary<string, object?> data = new()
|
||||||
|
{
|
||||||
|
["InventionId"] = Id,
|
||||||
|
["ReplicationId"] = ReplicationId.ToString(),
|
||||||
|
["CreatorPlayerId"] = Creator.Id,
|
||||||
|
["Name"] = Name,
|
||||||
|
["Description"] = Description,
|
||||||
|
["ImageName"] = ImageName,
|
||||||
|
["CurrentVersionNumber"] = CurrentVersionNumber,
|
||||||
|
["Accessibility"] = Accessibility,
|
||||||
|
["ModifiedAt"] = ModifiedAt.ToString("O"),
|
||||||
|
["CreatedAt"] = CreatedAt.ToString("O"),
|
||||||
|
["CreationRoomId"] = Room.Id,
|
||||||
|
["NumPlayersHaveUsedInRoom"] = -1,
|
||||||
|
["NumDownloads"] = DownloadIds.Count,
|
||||||
|
["CheerCount"] = CheeredIds.Count,
|
||||||
|
["CreatorPermission"] = CreatorPermission,
|
||||||
|
["GeneralPermission"] = GeneralPermission,
|
||||||
|
["IsAGInvention"] = false,
|
||||||
|
["IsCertifiedInvention"] = false,
|
||||||
|
["AllowTrial"] = AllowTrial
|
||||||
|
|
||||||
|
};
|
||||||
|
if (FirstPublishedAt.HasValue)
|
||||||
|
{
|
||||||
|
data["FirstPublishedAt"] = FirstPublishedAt.Value.ToString("O");
|
||||||
|
}
|
||||||
|
if (Price.HasValue)
|
||||||
|
{
|
||||||
|
data["Price"] = Price.Value;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InventionVersion
|
||||||
|
{
|
||||||
|
public required int Id { get; set; }
|
||||||
|
public required Guid ReplicationId { get; set; }
|
||||||
|
public required int InstantiationCost { get; set; }
|
||||||
|
public required int LightsCost { get; set; }
|
||||||
|
public required int ChipsCost { get; set; }
|
||||||
|
public required int CloudVariablesCost { get; set; }
|
||||||
|
public required string BlobName { get; set; }
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object?> ToDictionary(long inventionId)
|
||||||
|
{
|
||||||
|
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
Dictionary<string, object?> data = new()
|
||||||
|
{
|
||||||
|
["InventionId"] = inventionId,
|
||||||
|
["ReplicationId"] = ReplicationId.ToString(),
|
||||||
|
["VersionNumber"] = Id,
|
||||||
|
["InstantiationCost"] = InstantiationCost,
|
||||||
|
["LightsCost"] = LightsCost,
|
||||||
|
["ChipsCost"] = ChipsCost,
|
||||||
|
["CloudVariablesCost"] = CloudVariablesCost,
|
||||||
|
["BlobName"] = BlobName
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Numerics;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Player { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account FromPlayer { get; set; }
|
||||||
|
public DateTime SentTime { get; set; } = DateTime.UtcNow;
|
||||||
|
public required MessageType Type { get; set; }
|
||||||
|
public string Data { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
string daya = string.Empty;
|
||||||
|
if (Data != null)
|
||||||
|
{
|
||||||
|
daya = Data;
|
||||||
|
}
|
||||||
|
var data = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["Id"] = Id,
|
||||||
|
["FromPlayerId"] = FromPlayer.Id,
|
||||||
|
["SentTime"] = SentTime.ToString("o", CultureInfo.InvariantCulture),
|
||||||
|
["Type"] = (int)Type,
|
||||||
|
["Data"] = daya
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class PlayerEvent
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Creator { get; set; }
|
||||||
|
[BsonRef("rooms")]
|
||||||
|
public required Room Room { get; set; }
|
||||||
|
[BsonRef("subrooms")]
|
||||||
|
public required SubRoom SubRoom { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public string? ImageName { get; set; } = string.Empty;
|
||||||
|
public required DateTime StartTime { get; set; }
|
||||||
|
public required DateTime EndTime { get; set; }
|
||||||
|
public required RoomAccessibility Accessibility { get; set; }
|
||||||
|
public bool IsMultiInstance { get; set; } = false;
|
||||||
|
public bool SupportMultiInstanceRoomChat { get; set; } = false;
|
||||||
|
public BroadcastPerms DefaultBroadcastPermissions { get; set; } = BroadcastPerms.None;
|
||||||
|
public BroadcastPerms CanRequestBroadcastPermissions { get; set; } = BroadcastPerms.None;
|
||||||
|
public long? BroadcastingRoomInstanceId { get; set; } = null;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object?> ToDictionary()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object?>()
|
||||||
|
{
|
||||||
|
["PlayerEventId"] = Id,
|
||||||
|
["CreatorPlayerId"] = Creator.Id,
|
||||||
|
["RoomId"] = Room.Id,
|
||||||
|
["SubRoomId"] = SubRoom.Id,
|
||||||
|
["Name"] = Name,
|
||||||
|
["Description"] = Description ?? "",
|
||||||
|
["ImageName"] = ImageName ?? "",
|
||||||
|
["StartTime"] = StartTime.ToUniversalTime().ToString("O"),
|
||||||
|
["EndTime"] = EndTime.ToUniversalTime().ToString("O"),
|
||||||
|
["AttendeeCount"] = -1,
|
||||||
|
["Accessibility"] = (int)Accessibility,
|
||||||
|
["IsMultiInstance"] = IsMultiInstance,
|
||||||
|
["SupportMultiInstanceRoomChat"] = SupportMultiInstanceRoomChat,
|
||||||
|
["DefaultBroadcastPermissions"] = (int)DefaultBroadcastPermissions,
|
||||||
|
["CanRequestBroadcastPermissions"] = (int)CanRequestBroadcastPermissions,
|
||||||
|
["BroadcastingRoomInstanceId"] = BroadcastingRoomInstanceId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class PlayerInvite
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account InvitedBy { get; set; }
|
||||||
|
public required long InstanceId { get; set; }
|
||||||
|
public required DateTime ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
bool timeValid = ExpiresAt.Kind == DateTimeKind.Utc
|
||||||
|
? ExpiresAt > now
|
||||||
|
: ExpiresAt.ToUniversalTime() > now;
|
||||||
|
|
||||||
|
return timeValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class RefreshToken
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public required string Token { get; set; }
|
||||||
|
public required DateTime Expires { get; set; }
|
||||||
|
public bool IsRevoked { get; set; } = false;
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Account { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Numerics;
|
||||||
|
using DeluxeBackend.Models;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class Relationship
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Player { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account TargetPlayer { get; set; }
|
||||||
|
|
||||||
|
public RelationshipType RelationshipType { get; set; } = RelationshipType.None;
|
||||||
|
public MuteState Muted { get; set; } = MuteState.None;
|
||||||
|
public IgnoreState Ignored { get; set; } = IgnoreState.None;
|
||||||
|
public int Favorited { get; set; } = 0;
|
||||||
|
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["PlayerID"] = TargetPlayer.Id,
|
||||||
|
["RelationshipType"] = (int)RelationshipType,
|
||||||
|
["Muted"] = (int)Muted,
|
||||||
|
["Ignored"] = (int)Ignored,
|
||||||
|
["Favorited"] = Favorited
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum RoomSupports
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Screens = 1,
|
||||||
|
WalkVR = 2,
|
||||||
|
TeleportVR = 4,
|
||||||
|
Juniors = 8,
|
||||||
|
All = Screens | WalkVR | TeleportVR | Juniors
|
||||||
|
}
|
||||||
|
public class RoomRole
|
||||||
|
{
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
public RoomRoleType InvitedRole { get; set; } = RoomRoleType.None;
|
||||||
|
public RoomRoleType Role { get; set; } = RoomRoleType.None;
|
||||||
|
}
|
||||||
|
public class RoomStats
|
||||||
|
{
|
||||||
|
public List<long> VisitorIds { get; set; } = [];
|
||||||
|
public List<long> CheeredIds { get; set; } = [];
|
||||||
|
public List<long> FavoritedIds { get; set; } = [];
|
||||||
|
|
||||||
|
public long VisitCount { get; set; } = 0;
|
||||||
|
}
|
||||||
|
public class Room
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public RoomAccessibility Accessibility { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public bool CloningAllowed { get; set; } = false;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public Account Creator { get; set; } = null!;
|
||||||
|
public string? CustomWarning { get; set; } = string.Empty;
|
||||||
|
public RoomWarningMask WarningMask { get; set; } = RoomWarningMask.None;
|
||||||
|
public string DataBlob { get; set; } = string.Empty;
|
||||||
|
public bool DisableMicAutoMute { get; set; } = false;
|
||||||
|
public bool DisableRoomComments { get; set; } = false;
|
||||||
|
public bool EncryptVoiceChat { get; set; } = false;
|
||||||
|
public string ImageName { get; set; } = "DefaultRoomImage.jpg";
|
||||||
|
public bool IsDorm { get; set; } = false;
|
||||||
|
public bool IsRRO { get; set; } = false;
|
||||||
|
public int MinLevel { get; set; } = 0;
|
||||||
|
public RoomSupports SupportedPlayerTypes { get; set; } = RoomSupports.All;
|
||||||
|
public List<RoomRole> Roles { get; set; } = [];
|
||||||
|
public int? PersistenceVersion { get; set; }
|
||||||
|
public RoomStats Stats { get; set; } = new RoomStats();
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public bool HasRole(long id, RoomRoleType requiredRole)
|
||||||
|
{
|
||||||
|
if (Creator != null && Creator.Id == id)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerRoleEntry = Roles.FirstOrDefault(r => r.AccountId == id);
|
||||||
|
|
||||||
|
RoomRoleType playerRole = playerRoleEntry?.Role ?? RoomRoleType.None;
|
||||||
|
|
||||||
|
return (int)playerRole >= (int)requiredRole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class RoomInstance
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public required string PhotonRegion { get; set; } = "eu";
|
||||||
|
public required string PhotonRoom { get; set; }
|
||||||
|
|
||||||
|
public RoomInstanceType InstanceType { get; set; } = RoomInstanceType.Private;
|
||||||
|
|
||||||
|
[BsonRef("subrooms")]
|
||||||
|
public required SubRoom SubRoom { get; set; }
|
||||||
|
public long EventId { get; set; } = -1;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool Private { get; set; } = true;
|
||||||
|
public bool GameInProgress { get; set; } = false;
|
||||||
|
public MatchmakingPolicyType MatchmakingPolicy { get; set; } = MatchmakingPolicyType.Default;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.MinValue;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object?> ToDictionary()
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
Dictionary<string, object?> data = new()
|
||||||
|
{
|
||||||
|
["roomInstanceId"] = Id,
|
||||||
|
["roomId"] = SubRoom.Room.Id,
|
||||||
|
["subRoomId"] = SubRoom.Id,
|
||||||
|
["location"] = SubRoom.LocationId,
|
||||||
|
["roomInstanceType"] = (int)InstanceType,
|
||||||
|
["maxCapacity"] = SubRoom.MaxPlayers,
|
||||||
|
["isFull"] = false,
|
||||||
|
["isPrivate"] = Private,
|
||||||
|
["isInProgress"] = GameInProgress,
|
||||||
|
//["voiceServerId"] = "test-voice-5",
|
||||||
|
//["voiceAuthId"] = "",
|
||||||
|
["matchmakingPolicy"] = (int)MatchmakingPolicy
|
||||||
|
};
|
||||||
|
if (EventId != -1)
|
||||||
|
{
|
||||||
|
data["eventId"] = EventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
string roomName = string.IsNullOrWhiteSpace(SubRoom.Room.Name)
|
||||||
|
? $"UnknownRoom-{SubRoom.Room.Id}"
|
||||||
|
: SubRoom.Room.Name;
|
||||||
|
string name = $"^{roomName}";
|
||||||
|
if (InstanceType == RoomInstanceType.Dormroom)
|
||||||
|
{
|
||||||
|
if (SubRoom.Room.Creator != null) {
|
||||||
|
if (string.IsNullOrEmpty(SubRoom.Room.Creator.Username))
|
||||||
|
{
|
||||||
|
name = $"Can't find room creator username :(";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = $"@{SubRoom.Room.Creator.Username}'s Dorm";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = $"Can't find room creator :(";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data["name"] = name;
|
||||||
|
return data;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Numerics;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class SavedImage
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[BsonRef("accounts")]
|
||||||
|
public required Account Player { get; set; }
|
||||||
|
public List<ulong> PlayerIds { get; set; } = new List<ulong>();
|
||||||
|
|
||||||
|
public string ImageName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public long? RoomId { get; set; } = null;
|
||||||
|
|
||||||
|
public SavedImageType SavedImageType { get; set; } = SavedImageType.None;
|
||||||
|
|
||||||
|
public SavedImageAccessibility Accessibility { get; set; } = SavedImageAccessibility.Private;
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<string, object> keyValuePairs = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["SavedImageId"] = Id,
|
||||||
|
["SavedImageType"] = (int)SavedImageType,
|
||||||
|
["Accessibility"] = (int)Accessibility,
|
||||||
|
["AccessibilityLocked"] = false,
|
||||||
|
["ImageName"] = ImageName,
|
||||||
|
["Description"] = "",
|
||||||
|
["PlayerId"] = Player.Id,
|
||||||
|
["TaggedPlayerIds"] = PlayerIds,
|
||||||
|
["CreatedAt"] = CreatedAt.ToString("O"),
|
||||||
|
["CheerCount"] = 0,
|
||||||
|
["CommentCount"] = 0
|
||||||
|
};
|
||||||
|
if (RoomId.HasValue)
|
||||||
|
{
|
||||||
|
keyValuePairs["RoomId"] = RoomId.Value;
|
||||||
|
}
|
||||||
|
return keyValuePairs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Globalization;
|
||||||
|
using static AGRoomRuntimeConfig;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class SubRoom
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
[BsonRef("rooms")]
|
||||||
|
public required Room Room { get; set; }
|
||||||
|
public required string LocationId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public bool IsSandbox { get; set; } = false;
|
||||||
|
public int MaxPlayers { get; set; } = 8;
|
||||||
|
public bool CanMatchmakeInto { get; set; } = false;
|
||||||
|
[BsonRef("subroomsaves")]
|
||||||
|
public SubRoomSave? CurrentSave { get; set; } = null;
|
||||||
|
public bool SupportsJoinInProgress { get; set; } = true;
|
||||||
|
public bool UseLevelBasedMatchmaking { get; set; } = false;
|
||||||
|
public bool UseAgeBasedMatchmaking { get; set; } = true;
|
||||||
|
public bool UseRecRoyaleMatchmaking { get; set; } = false;
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary(bool includeCurrentSave = false, bool includeMatchmaking = false)
|
||||||
|
{
|
||||||
|
Dictionary<string, object>? CurrentSavedic = null;
|
||||||
|
if (CurrentSave != null && includeCurrentSave)
|
||||||
|
{
|
||||||
|
CurrentSavedic = CurrentSave.ToDictionary();
|
||||||
|
}
|
||||||
|
RoomAccessibility Accessibility = RoomAccessibility.Unlisted;
|
||||||
|
if (CanMatchmakeInto)
|
||||||
|
{
|
||||||
|
Accessibility = RoomAccessibility.Public;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
Dictionary<string, object> data= new()
|
||||||
|
{
|
||||||
|
["RoomId"] = Room.Id,
|
||||||
|
["SubRoomId"] = Id,
|
||||||
|
["Accessibility"] = (int)Accessibility,
|
||||||
|
["CurrentSave"] = CurrentSavedic,
|
||||||
|
["IsSandbox"] = IsSandbox,
|
||||||
|
["MaxPlayers"] = MaxPlayers,
|
||||||
|
["Name"] = Name,
|
||||||
|
["UnitySceneId"] = LocationId
|
||||||
|
};
|
||||||
|
if (includeMatchmaking)
|
||||||
|
{
|
||||||
|
data["CanMatchmakeInto"] = CanMatchmakeInto;
|
||||||
|
data["SupportsJoinInProgress"] = SupportsJoinInProgress;
|
||||||
|
data["UseLevelBasedMatchmaking"] = UseLevelBasedMatchmaking;
|
||||||
|
data["UseAgeBasedMatchmaking"] = UseAgeBasedMatchmaking;
|
||||||
|
data["UseRecRoyaleMatchmaking"] = UseRecRoyaleMatchmaking;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
|
||||||
|
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
public class SubRoomSave
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
public required long SubRoomId { get; set; }
|
||||||
|
|
||||||
|
public required string DataBlob { get; set; }
|
||||||
|
public long? SavedByAccountId { get; set; } = null;
|
||||||
|
public PlatformType? SavedOnPlatform { get; set; } = null;
|
||||||
|
public DeviceClassType? SavedOnDeviceClass { get; set; } = null;
|
||||||
|
|
||||||
|
public string? Description { get; set; } = null;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public string? UnityAssetId { get; set; } = null;
|
||||||
|
public int PersistenceVersion { get; set; } = 0;
|
||||||
|
//UnityAssetId
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public Dictionary<string, object> ToDictionary()
|
||||||
|
{
|
||||||
|
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
Dictionary<string, object> data = new()
|
||||||
|
{
|
||||||
|
["SubRoomDataSaveId"] = Id,
|
||||||
|
["SubRoomId"] = SubRoomId,
|
||||||
|
["DataBlob"] = DataBlob,
|
||||||
|
["OMVersion"] = 0,
|
||||||
|
["PersistenceVersion"] = PersistenceVersion,
|
||||||
|
["SavedByAccountId"] = SavedByAccountId,
|
||||||
|
["SavedOnPlatform"] = SavedOnPlatform,
|
||||||
|
["SavedOnDeviceClass"] = SavedOnDeviceClass,
|
||||||
|
["Description"] = Description,
|
||||||
|
["CreatedAt"] = CreatedAt.ToString("O"),
|
||||||
|
["UnityAssetId"] = UnityAssetId
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using static DeluxeBackend.Enums;
|
||||||
|
namespace DeluxeBackend.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public class UnityAsset
|
||||||
|
{
|
||||||
|
public required AssetBundleType Target { get; set; }
|
||||||
|
public required AssetBundleVersion Version { get; set; }
|
||||||
|
public required string Filename { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UnityAssets
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public List<UnityAsset> Assets { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
+395
@@ -0,0 +1,395 @@
|
|||||||
|
using DeluxeBackend.Hubs;
|
||||||
|
using DeluxeBackend.Services;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Interactions;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using LiteDB;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.HttpLogging;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using Scalar.AspNetCore;
|
||||||
|
|
||||||
|
|
||||||
|
namespace DeluxeBackend
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private static readonly SemaphoreSlim FileLock = new SemaphoreSlim(1, 1);
|
||||||
|
private static Task<string> GenerateSignatureBytes(string signingKey, string uri, byte[] bodyBytes)
|
||||||
|
{
|
||||||
|
byte[] signingKeyBytes = Convert.FromBase64String(signingKey);
|
||||||
|
byte[] uriBytes = Encoding.ASCII.GetBytes(uri);
|
||||||
|
|
||||||
|
using (IncrementalHash incrementalHash = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA256, signingKeyBytes))
|
||||||
|
{
|
||||||
|
incrementalHash.AppendData(uriBytes);
|
||||||
|
|
||||||
|
if (bodyBytes != null && bodyBytes.Length != 0)
|
||||||
|
{
|
||||||
|
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
|
||||||
|
|
||||||
|
if (bodyBytes.Length > 2048)
|
||||||
|
{
|
||||||
|
//Console.WriteLine("Body length > 2048, doing large payload sampling.");
|
||||||
|
|
||||||
|
int sectionSize = bodyBytes.Length / 16;
|
||||||
|
|
||||||
|
//Console.WriteLine($"Appending the first 128 bytes of 16 sections each sized {sectionSize} bytes.");
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
int currentSection = i * sectionSize;
|
||||||
|
incrementalHash.AppendData(bodyBytes, currentSection, 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
incrementalHash.AppendData(bodyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Console.WriteLine("Body Length is 0, not appending any body data.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string base64Hash = Convert.ToBase64String(incrementalHash.GetHashAndReset());
|
||||||
|
return Task.FromResult(base64Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<string> GenerateResponseSignatureRsa(RSAParameters rsaParams, string url, int statusCode, byte[] bodyBytes)
|
||||||
|
{
|
||||||
|
byte[] urlBytes = Encoding.UTF8.GetBytes(url);
|
||||||
|
byte[] statusBytes = Encoding.UTF8.GetBytes(statusCode.ToString());
|
||||||
|
|
||||||
|
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
|
||||||
|
incrementalHash.AppendData(urlBytes);
|
||||||
|
incrementalHash.AppendData(statusBytes);
|
||||||
|
|
||||||
|
if (bodyBytes != null && bodyBytes.Length != 0)
|
||||||
|
{
|
||||||
|
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
|
||||||
|
|
||||||
|
if (bodyBytes.Length > 2048)
|
||||||
|
{
|
||||||
|
int sectionSize = bodyBytes.Length / 16;
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
int currentSection = i * sectionSize;
|
||||||
|
incrementalHash.AppendData(bodyBytes, currentSection, 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
incrementalHash.AppendData(bodyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] hashBytes = incrementalHash.GetHashAndReset();
|
||||||
|
|
||||||
|
using RSA rsa = RSA.Create();
|
||||||
|
rsa.ImportParameters(rsaParams);
|
||||||
|
byte[] signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
return Task.FromResult(Convert.ToBase64String(signatureBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
//JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var corsPolicyName = "AllowAll";
|
||||||
|
|
||||||
|
string base64Key = "RXq0pIk5tTHbKnvWVoUTGUWW3dWt5hdqcTyZ8V71lvI=";
|
||||||
|
builder.Configuration["JwtConfig:Key"] = base64Key;
|
||||||
|
|
||||||
|
builder.Services.AddHttpLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.LoggingFields = HttpLoggingFields.All;
|
||||||
|
logging.RequestHeaders.Add("Authorization");
|
||||||
|
logging.RequestHeaders.Add("Cookie");
|
||||||
|
logging.ResponseHeaders.Add("WWW-Authenticate");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.AddSingleton<ILiteDbService, LiteDbService>();
|
||||||
|
builder.Services.AddSingleton<IJwtService, JwtService>();
|
||||||
|
builder.Services.AddSingleton<IPasswordService, PasswordService>();
|
||||||
|
builder.Services.AddSingleton<INotificationService, NotificationService>();
|
||||||
|
builder.Services.AddSingleton<IMessageService, MessageService>();
|
||||||
|
builder.Services.AddSingleton<ICdnService, CdnService>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
|
||||||
|
{
|
||||||
|
GatewayIntents = GatewayIntents.All,
|
||||||
|
AlwaysDownloadUsers = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()));
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<DiscordBotService>();
|
||||||
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<DiscordBotService>());
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddControllers().AddJsonOptions(options => {
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||||
|
});
|
||||||
|
builder.Services.AddRazorPages();
|
||||||
|
builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(options => {
|
||||||
|
options.PayloadSerializerSettings.CheckAdditionalContent = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
|
builder.Services.AddSession(options =>
|
||||||
|
{
|
||||||
|
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.Cookie.IsEssential = true;
|
||||||
|
});
|
||||||
|
builder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy(name: corsPolicyName,
|
||||||
|
policy =>
|
||||||
|
{
|
||||||
|
policy.AllowAnyOrigin()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
#if DEBUG
|
||||||
|
builder.Services.AddOpenApi(options =>
|
||||||
|
{
|
||||||
|
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||||
|
{
|
||||||
|
document.Info.Title = "API";
|
||||||
|
document.Info.Version = "v1";
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
}).AddJwtBearer(options => {
|
||||||
|
options.RequireHttpsMetadata = false;
|
||||||
|
options.SaveToken = true;
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidIssuer = builder.Configuration["JwtConfig:Issuer"],
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["JwtConfig:Key"])),
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidateAudience = false
|
||||||
|
};
|
||||||
|
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
|
{
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
if (path.StartsWithSegments("/Notifications/hub/v1"))
|
||||||
|
{
|
||||||
|
var accessToken = context.Request.Query["access_token"];
|
||||||
|
if (!string.IsNullOrEmpty(accessToken)) context.Token = accessToken;
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
OnAuthenticationFailed = ctx =>
|
||||||
|
{
|
||||||
|
Console.WriteLine(ctx.Exception.ToString());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
BsonMapper.Global.RegisterType<DateOnly>(
|
||||||
|
serialize: value => value.ToDateTime(TimeOnly.MinValue),
|
||||||
|
deserialize: bson => DateOnly.FromDateTime(bson.AsDateTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
BsonMapper.Global.RegisterType<DateOnly?>(
|
||||||
|
serialize: value => value?.ToDateTime(TimeOnly.MinValue),
|
||||||
|
deserialize: bson => bson.IsNull ? null : DateOnly.FromDateTime(bson.AsDateTime)
|
||||||
|
);
|
||||||
|
BsonMapper.Global.RegisterType<System.Drawing.Color>(
|
||||||
|
serialize: (color) => color.ToArgb(),
|
||||||
|
deserialize: (bson) => System.Drawing.Color.FromArgb(bson.AsInt32)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseHttpLogging();
|
||||||
|
/*app.MapOpenApi();
|
||||||
|
app.MapScalarApiReference("/docs", options =>
|
||||||
|
{
|
||||||
|
options.HideDownloadButton = true;
|
||||||
|
options.Theme = ScalarTheme.DeepSpace;
|
||||||
|
options.Layout = ScalarLayout.Classic;
|
||||||
|
options.HideDarkModeToggle = true;
|
||||||
|
options.DarkMode = true;
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Use(async (context, next) =>//this is for when rr fucks the uri builder in 2018 builds
|
||||||
|
{
|
||||||
|
var path = context.Request.Path.Value;
|
||||||
|
if (!string.IsNullOrEmpty(path) && path.Contains("//"))
|
||||||
|
{
|
||||||
|
context.Request.Path = "/" + path.TrimStart('/');
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseCors(corsPolicyName);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
/*app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
string userAgent = context.Request.Headers["User-Agent"].ToString();
|
||||||
|
|
||||||
|
if (userAgent != "BestHTTP/Deluxe")
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalBodyStream = context.Response.Body;
|
||||||
|
|
||||||
|
using (var responseBodyMemoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
context.Response.Body = responseBodyMemoryStream;
|
||||||
|
|
||||||
|
await next();
|
||||||
|
|
||||||
|
string url = context.Request.Path + context.Request.QueryString;
|
||||||
|
int statusCode = context.Response.StatusCode;
|
||||||
|
|
||||||
|
var headersBuilder = new StringBuilder();
|
||||||
|
foreach (var header in context.Request.Headers.OrderBy(h => h.Key))
|
||||||
|
{
|
||||||
|
headersBuilder.Append($"{header.Key}:{header.Value}\n");
|
||||||
|
}
|
||||||
|
string serializedHeaders = headersBuilder.ToString();
|
||||||
|
|
||||||
|
responseBodyMemoryStream.Position = 0;
|
||||||
|
byte[] responseBodyBytes = responseBodyMemoryStream.ToArray();
|
||||||
|
|
||||||
|
string signature = await GenerateResponseSignatureRsa(
|
||||||
|
ServerConfig.RsaParams,
|
||||||
|
url,
|
||||||
|
statusCode,
|
||||||
|
responseBodyBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
context.Response.Headers["X-Server-Signature"] = signature;
|
||||||
|
|
||||||
|
responseBodyMemoryStream.Position = 0;
|
||||||
|
await responseBodyMemoryStream.CopyToAsync(originalBodyStream);
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
if (context.User.Identity?.IsAuthenticated == true && context.User.IsInRole("gameClient"))
|
||||||
|
{
|
||||||
|
if (HttpMethods.IsPost(context.Request.Method) || HttpMethods.IsPut(context.Request.Method))
|
||||||
|
{
|
||||||
|
if (!context.Request.Headers.TryGetValue("X-RNSIG", out var clientSignature))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Request.EnableBuffering();
|
||||||
|
|
||||||
|
byte[] bodyBytes;
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
await context.Request.Body.CopyToAsync(ms);
|
||||||
|
bodyBytes = ms.ToArray();
|
||||||
|
context.Request.Body.Position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string uri = context.Request.Path;
|
||||||
|
|
||||||
|
string expectedSignature = await GenerateSignatureBytes(ServerConfig.SigKey, uri, bodyBytes);
|
||||||
|
|
||||||
|
if (clientSignature != expectedSignature)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Signature Mismatch! Client: {clientSignature}, Expected: {expectedSignature}");
|
||||||
|
context.Response.StatusCode = 403;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
await next();
|
||||||
|
|
||||||
|
if (context.Response.StatusCode >= 400)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FileLock.WaitAsync();
|
||||||
|
|
||||||
|
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
string method = context.Request.Method;
|
||||||
|
string url = context.Request.Path + context.Request.QueryString;
|
||||||
|
int status = context.Response.StatusCode;
|
||||||
|
|
||||||
|
string logLine = $"[{timestamp}] {status} | {method} {url}{Environment.NewLine}";
|
||||||
|
|
||||||
|
await File.AppendAllTextAsync("error_urls.txt", logLine);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to write error log to file: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FileLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
app.MapHub<NotificationHub>("/Notifications/hub/v1");
|
||||||
|
if (ServerConfig.avatarItems.Count == 0)
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(Path.Combine("data", "AvatarItems.json"));
|
||||||
|
ServerConfig.avatarItems.AddRange(System.Text.Json.JsonSerializer.Deserialize<List<AvatarItemDto>>(json));
|
||||||
|
}
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user