Add remaining project files
This commit is contained in:
@@ -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)];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user