diff --git a/Content.Client/Administration/Systems/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs index e0f84bc4f0..1a75df7308 100644 --- a/Content.Client/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs @@ -22,7 +22,7 @@ namespace Content.Client.Administration.Systems // Currently this is only the ViewVariables verb, but more admin-UI related verbs can be added here. // View variables verbs - if (_clientConGroupController.CanViewVar()) + if (_clientConGroupController.CanAdminMenu()) { var verb = new VvVerb() { diff --git a/Content.Client/Administration/UI/PermissionsEui.cs b/Content.Client/Administration/UI/PermissionsEui.cs index 4cddf8887e..9aa57e0f1d 100644 --- a/Content.Client/Administration/UI/PermissionsEui.cs +++ b/Content.Client/Administration/UI/PermissionsEui.cs @@ -278,7 +278,7 @@ namespace Content.Client.Administration.UI editButton.OnPressed += _ => OnEditRankPressed(kv); _menu.AdminRanksList.AddChild(editButton); - if (!_adminManager.HasFlag(rank.Flags)) + if (rank.Flags != AdminFlags.Host && !_adminManager.HasFlag(AdminFlags.Permissions) || rank.Flags == AdminFlags.Host && !_adminManager.HasFlag(AdminFlags.Host)) { editButton.Disabled = true; editButton.ToolTip = Loc.GetString("permissions-eui-do-not-have-required-flags-to-edit-rank-tooltip"); @@ -401,7 +401,15 @@ namespace Content.Client.Administration.UI { // Can only grant out perms you also have yourself. // Primarily intended to prevent people giving themselves +HOST with +PERMISSIONS but generalized. - var disable = !ui._adminManager.HasFlag(flag); + bool disable; + if (flag != AdminFlags.Host) + { + disable = !ui._adminManager.HasFlag(AdminFlags.Permissions); + } + else + { + disable = !ui._adminManager.HasFlag(AdminFlags.Host); + } var flagName = flag.ToString().ToUpper(); var group = new ButtonGroup(); diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 4e46053bd5..82b2e07b13 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -24,6 +24,7 @@ using Content.Client.Voting; using Content.Client.White.JoinQueue; using Content.Client.White.Sponsors; using Content.Shared.Ame; +using Content.Client.White.Stalin; using Content.Shared.Gravity; using Content.Shared.Localizations; using Robust.Client; @@ -76,6 +77,7 @@ namespace Content.Client.Entry //WD-EDIT [Dependency] private readonly SponsorsManager _sponsorsManager = default!; [Dependency] private readonly JoinQueueManager _queueManager = default!; + [Dependency] private readonly StalinManager _stalinManager = default!; //WD-EDIT public override void Init() @@ -140,6 +142,10 @@ namespace Content.Client.Entry _jobRequirements.Initialize(); _playbackMan.Initialize(); + //WD-EDIT + _stalinManager.Initialize(); + //WD-EDIT + //AUTOSCALING default Setup! _configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080); _configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffY", 720); diff --git a/Content.Client/Info/LinkBanner.cs b/Content.Client/Info/LinkBanner.cs index a30aa41376..4b9ed70087 100644 --- a/Content.Client/Info/LinkBanner.cs +++ b/Content.Client/Info/LinkBanner.cs @@ -1,6 +1,7 @@ using Content.Client.Changelog; using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Client.UserInterface.Systems.Guidebook; +using Content.Client.White.Stalin; using Content.Shared.CCVar; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -11,12 +12,14 @@ namespace Content.Client.Info { public sealed class LinkBanner : BoxContainer { + [Dependency] private readonly StalinManager _stalinManager = default!; private readonly IConfigurationManager _cfg; private ValueList<(CVarDef cVar, Button button)> _infoLinks; public LinkBanner() { + IoCManager.InjectDependencies(this); var buttons = new BoxContainer { Orientation = LayoutOrientation.Horizontal @@ -54,6 +57,15 @@ namespace Content.Client.Info buttons.AddChild(button); _infoLinks.Add((cVar, button)); } + + var saltedYaycaButton = new Button() {Text = "Привязать дискорд"}; + + saltedYaycaButton.OnPressed += _ => + { + _stalinManager.RequestUri(); + }; + + buttons.AddChild(saltedYaycaButton); } protected override void EnteredTree() diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 705d06d3ef..1b87016c3f 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -19,6 +19,7 @@ using Content.Client.Guidebook; using Content.Client.Replay; using Content.Client.White.JoinQueue; using Content.Client.White.Sponsors; +using Content.Client.White.Stalin; using Content.Shared.Administration.Managers; namespace Content.Client.IoC @@ -51,6 +52,7 @@ namespace Content.Client.IoC //WD-EDIT IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); //WD-EDIT } } diff --git a/Content.Client/White/Stalin/StalinManager.cs b/Content.Client/White/Stalin/StalinManager.cs new file mode 100644 index 0000000000..53e85f5711 --- /dev/null +++ b/Content.Client/White/Stalin/StalinManager.cs @@ -0,0 +1,26 @@ +using Content.Shared.White.SaltedYayca; +using Robust.Client.UserInterface; +using Robust.Shared.Network; + +namespace Content.Client.White.Stalin; + +public sealed class StalinManager +{ + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IUriOpener _uriOpener = default!; + + public void Initialize() + { + _netManager.RegisterNetMessage(OnStalinResponse); + } + + public void RequestUri() + { + _netManager.ClientSendMessage(new DiscordAuthRequest()); + } + + private void OnStalinResponse(DiscordAuthResponse message) + { + _uriOpener.OpenUri(message.Uri); + } +} diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 5a436d390a..3009610455 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -31,7 +31,7 @@ public sealed partial class AdminVerbSystem var player = actor.PlayerSession; - if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun)) + if (!_adminManager.HasAdminFlag(player, AdminFlags.MeatyOre) && !_adminManager.HasAdminFlag(player, AdminFlags.Fun)) return; if (!TryComp(args.Target, out var targetMindComp)) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index b476477ab7..51a58084cd 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -90,7 +90,7 @@ namespace Content.Server.Administration.Systems var player = actor.PlayerSession; - if (_adminManager.IsAdmin(player)) + if (_adminManager.HasAdminFlag(player, AdminFlags.Admin)) { Verb mark = new(); mark.Text = Loc.GetString("toolshed-verb-mark"); diff --git a/Content.Server/Administration/UI/PermissionsEui.cs b/Content.Server/Administration/UI/PermissionsEui.cs index 82ec10577d..9844f1c145 100644 --- a/Content.Server/Administration/UI/PermissionsEui.cs +++ b/Content.Server/Administration/UI/PermissionsEui.cs @@ -429,7 +429,9 @@ namespace Content.Server.Administration.UI private bool UserAdminFlagCheck(AdminFlags flags) { - return _adminManager.HasAdminFlag(Player, flags); + if (flags == AdminFlags.Host) + return _adminManager.HasAdminFlag(Player, AdminFlags.Host); + return _adminManager.HasAdminFlag(Player, AdminFlags.Permissions); } private bool CanTouchAdmin(Admin admin) diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index dc202b8f2e..8ace3d8001 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -32,6 +32,7 @@ using Robust.Shared.Utility; using Content.Server.UtkaIntegration; using Content.Server.White.JoinQueue; using Content.Server.White.Sponsors; +using Content.Server.White.Stalin; using Content.Server.White.TTS; namespace Content.Server.Entry @@ -111,6 +112,7 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); //WD-EDIT _voteManager.Initialize(); diff --git a/Content.Server/GameTicking/Commands/JoinGameCommand.cs b/Content.Server/GameTicking/Commands/JoinGameCommand.cs index 3276b91200..1eb352d8a8 100644 --- a/Content.Server/GameTicking/Commands/JoinGameCommand.cs +++ b/Content.Server/GameTicking/Commands/JoinGameCommand.cs @@ -1,7 +1,13 @@ +using Content.Server.Chat.Managers; using Content.Server.Station.Systems; +using Content.Server.White.Stalin; using Content.Shared.Administration; +using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Roles; +using Content.Shared.White; +using Robust.Server.Player; +using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Prototypes; @@ -12,6 +18,8 @@ namespace Content.Server.GameTicking.Commands { [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly StalinManager _stalinManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; public string Command => "joingame"; public string Description => ""; @@ -21,7 +29,7 @@ namespace Content.Server.GameTicking.Commands { IoCManager.InjectDependencies(this); } - public void Execute(IConsoleShell shell, string argStr, string[] args) + public async void Execute(IConsoleShell shell, string argStr, string[] args) { if (args.Length != 2) { @@ -46,6 +54,19 @@ namespace Content.Server.GameTicking.Commands return; } + var chatManager = IoCManager.Resolve(); + + if (_configurationManager.GetCVar(WhiteCVars.StalinEnabled)) + { + var allowEnterRequest = await _stalinManager.AllowEnter(player); + + if (!allowEnterRequest.allow) + { + chatManager.DispatchServerMessage(player, allowEnterRequest.errorMessage); + return; + } + } + if (ticker.RunLevel == GameRunLevel.PreRoundLobby) { shell.WriteLine("Round has not started."); diff --git a/Content.Server/GameTicking/GameTicker.Lobby.cs b/Content.Server/GameTicking/GameTicker.Lobby.cs index b31472ffa4..57a8f133b4 100644 --- a/Content.Server/GameTicking/GameTicker.Lobby.cs +++ b/Content.Server/GameTicking/GameTicker.Lobby.cs @@ -4,6 +4,7 @@ using Content.Server.Station.Components; using Robust.Shared.Network; using Robust.Shared.Player; using System.Text; +using Content.Shared.White; namespace Content.Server.GameTicking { @@ -168,6 +169,11 @@ namespace Content.Server.GameTicking return; } + if (_configurationManager.GetCVar(WhiteCVars.StalinEnabled)) + { + _chatManager.DispatchServerMessage(player, "Внимание, на сервере включен бункер. Если ваш аккаунт не был привязан к дискорду, то вы не сможете зайти в раунд. Для того чтобы привязать аккаунт - нажмите на кнопку ПРИВЯЗАТЬ АККАУНТ"); + } + var status = ready ? PlayerGameStatus.ReadyToPlay : PlayerGameStatus.NotReadyToPlay; _playerGameStatuses[player.UserId] = ready ? PlayerGameStatus.ReadyToPlay : PlayerGameStatus.NotReadyToPlay; RaiseNetworkEvent(GetStatusMsg(player), player.ConnectedClient); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 7444016510..9e85cb3f6d 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -22,6 +22,10 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Server.UtkaIntegration; using System.Threading.Tasks; +using Content.Server.White.Stalin; +using Content.Shared.Database; +using Content.Shared.White; +using Robust.Shared.Asynchronous; namespace Content.Server.GameTicking { @@ -32,6 +36,7 @@ namespace Content.Server.GameTicking //WD-EDIT [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; + [Dependency] private readonly StalinManager _stalinManager = default!; //WD-EDIT private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( @@ -178,7 +183,7 @@ namespace Content.Server.GameTicking return gridUids; } - public void StartRound(bool force = false) + public async void StartRound(bool force = false) { #if EXCEPTION_TOLERANCE try @@ -215,11 +220,25 @@ namespace Content.Server.GameTicking RaiseLocalEvent(startingEvent); var readyPlayers = new List(); var readyPlayerProfiles = new Dictionary(); + var stalinBunkerEnabled = _configurationManager.GetCVar(WhiteCVars.StalinEnabled); + + await _stalinManager.RefreshUsersData(); foreach (var (userId, status) in _playerGameStatuses) { if (LobbyEnabled && status != PlayerGameStatus.ReadyToPlay) continue; if (!_playerManager.TryGetSessionById(userId, out var session)) continue; + + if (stalinBunkerEnabled) + { + var playerData = await _stalinManager.AllowEnter(session, false); + + if (!playerData.allow) + { + _chatManager.DispatchServerMessage(session, $"{playerData.errorMessage}"); + continue; + } + } #if DEBUG DebugTools.Assert(_userDb.IsLoadComplete(session), $"Player was readied up but didn't have user DB data loaded yet??"); #endif diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index a81e70509d..7ace73e86f 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -12,6 +12,7 @@ using Content.Shared.Players; using Content.Shared.Preferences; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; +using Content.Shared.White; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -307,12 +308,22 @@ namespace Content.Server.GameTicking /// /// Causes the given player to join the current game as observer ghost. See also /// - public void JoinAsObserver(ICommonSession player) + public async void JoinAsObserver(ICommonSession player) { // Can't spawn players with a dummy ticker! if (DummyTicker) return; + if (_configurationManager.GetCVar(WhiteCVars.StalinEnabled)) + { + var allowEnterData = await _stalinManager.AllowEnter(player); + if (!allowEnterData.allow) + { + _chatManager.DispatchServerMessage(player, $"Вход в игру запрещен: {allowEnterData.errorMessage}"); + return; + } + } + PlayerJoinGame(player); SpawnObserver(player); } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 524563b2ef..7b3be4e694 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -22,6 +22,7 @@ using Content.Server.Worldgen.Tools; using Content.Server.UtkaIntegration; using Content.Server.White.JoinQueue; using Content.Server.White.Sponsors; +using Content.Server.White.Stalin; using Content.Server.White.TTS; using Content.Shared.Administration; using Content.Shared.Administration.Logs; @@ -69,6 +70,7 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); // WD-EDIT } } diff --git a/Content.Server/White/Stalin/Commands/EnableStalinBunker.cs b/Content.Server/White/Stalin/Commands/EnableStalinBunker.cs new file mode 100644 index 0000000000..196a462c26 --- /dev/null +++ b/Content.Server/White/Stalin/Commands/EnableStalinBunker.cs @@ -0,0 +1,49 @@ +using Content.Server.Administration; +using Content.Server.Chat.Managers; +using Content.Shared.Administration; +using Content.Shared.CCVar; +using Content.Shared.White; +using Robust.Shared.Configuration; +using Robust.Shared.Console; + +namespace Content.Server.White.Stalin.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed class EnableStalinBunker : IConsoleCommand +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + + public string Command => "stalinbunker"; + public string Description => "Enables the stalin bunker, like PaNIk bunker, but better"; + public string Help => "stalinBunker "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length > 1) + { + shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1))); + return; + } + + var enabled = _cfg.GetCVar(CCVars.PanicBunkerEnabled); + + if (args.Length == 0) + { + enabled = !enabled; + } + + if (args.Length == 1 && !bool.TryParse(args[0], out enabled)) + { + shell.WriteError(Loc.GetString("shell-argument-must-be-boolean")); + return; + } + + IoCManager.InjectDependencies(this); + + _cfg.SetCVar(WhiteCVars.StalinEnabled, enabled); + + var announce = Loc.GetString("stalin-panic-bunker", ("enabled", $"{enabled}")); + + IoCManager.Resolve().DispatchServerAnnouncement(announce, Color.Red); + } +} diff --git a/Content.Server/White/Stalin/DiscordUserData.cs b/Content.Server/White/Stalin/DiscordUserData.cs new file mode 100644 index 0000000000..d20dc540b7 --- /dev/null +++ b/Content.Server/White/Stalin/DiscordUserData.cs @@ -0,0 +1,38 @@ +using System.Buffers; +using System.Buffers.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Newtonsoft.Json.Converters; + +namespace Content.Server.White.Stalin; + +public sealed class DiscordUserData +{ + [JsonPropertyName("registered")] + public bool Registered { get; set; } + + [JsonPropertyName("created_at")] + public double UnixTimestamp { get; set; } + + public DateTime DiscordAge => UnixTimeStampToDateTime(UnixTimestamp); + + public static DateTime UnixTimeStampToDateTime( double unixTimeStamp ) + { + // Unix timestamp is seconds past epoch + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dateTime; + } +} + +public sealed class DiscordUsersDataRequest +{ + [JsonPropertyName("uuids")] + public List Uids { get; set; } = new(); +} + +public sealed class DiscordUsersData +{ + public Dictionary Users { get; set; } = new(); +} + diff --git a/Content.Server/White/Stalin/StalinManager.cs b/Content.Server/White/Stalin/StalinManager.cs new file mode 100644 index 0000000000..b018c063b2 --- /dev/null +++ b/Content.Server/White/Stalin/StalinManager.cs @@ -0,0 +1,261 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Content.Server.Chat.Managers; +using Content.Shared.CCVar; +using Content.Shared.White; +using Content.Shared.White.SaltedYayca; +using Newtonsoft.Json; +using Robust.Server.Player; +using Robust.Shared.Asynchronous; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Random; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Content.Server.White.Stalin; + +public sealed class StalinManager +{ + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly ITaskManager _taskManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + + private IChatManager _chatManager = default!; + + private readonly Dictionary _registeredStalinCache = new(); + private readonly Dictionary _nextStalinAllowedCheck = new(); + private string _stalinApiUrl = string.Empty; + private string _stalinAuthUrl = string.Empty; + private float _minimalDiscordAccountAge = 0f; + + public void Initialize() + { + _netManager.RegisterNetMessage(OnDiscordAuthRequest); + _netManager.RegisterNetMessage(); + _chatManager = IoCManager.Resolve(); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + _configurationManager.OnValueChanged(WhiteCVars.StalinApiUrl, newValue => _stalinApiUrl = newValue, true); + _configurationManager.OnValueChanged(WhiteCVars.StalinAuthUrl, newValue => _stalinAuthUrl = newValue, true); + _configurationManager.OnValueChanged(WhiteCVars.StalinDiscordMinimumAge, newValue => _minimalDiscordAccountAge = newValue, true); + } + + public async Task RefreshUsersData() + { + var players = Filter.GetAllPlayers().Cast().ToList(); + + var usersData = await RequestDiscordUsersDataAsync(players); + + if(usersData == null) return; + + foreach (var data in usersData.Users) + { + if(!data.Value.Registered) continue; + _registeredStalinCache[data.Key] = data.Value; + } + } + + public async Task<(bool allow, string errorMessage)> AllowEnter(ICommonSession session, bool requestIfNull = true) + { + var userId = session.UserId.ToString(); + if (_nextStalinAllowedCheck.TryGetValue(userId, out var nextAllowedCheckTime)) + { + if (DateTime.Now < nextAllowedCheckTime) + { + var timeoutTime = (int) ((nextAllowedCheckTime - DateTime.Now).TotalSeconds); + return (false, Loc.GetString("stalin-timeout", ("timeoutTime", timeoutTime))); + } + } + + var nextCheckTime = DateTime.Now.AddSeconds(_random.NextDouble(3,8)); + _nextStalinAllowedCheck[userId] = nextCheckTime; + + DiscordUserData responseData = null!; + if (!_registeredStalinCache.TryGetValue(userId, out responseData!) && requestIfNull) + { + responseData = await RequestDiscordUserDataAsync(session); + } + + if (responseData == null) + { + return (false, Loc.GetString("stalin-discord-doesnt-link")); + } + + var discordAge = GetDiscordAccountAge(responseData); + var discordAgeCheck = VerifyDiscordAge(discordAge); + + return (discordAgeCheck.passed, discordAgeCheck.errorMessage); + } + + private (bool passed, string errorMessage) VerifyDiscordAge(double discordAge) + { + if(discordAge < _minimalDiscordAccountAge) + { + long needed = (long)(_minimalDiscordAccountAge - discordAge); + return (false, Loc.GetString("stalin-discord-age-check-fail", ("needed", needed))); + } + + return (true, string.Empty); + } + + private double GetDiscordAccountAge(DiscordUserData data) + { + return (DateTime.Now - data.DiscordAge).TotalSeconds; + } + + private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if(!_cfg.GetCVar(WhiteCVars.StalinEnabled)) return; + + if (e.NewStatus != SessionStatus.Connected) return; + + var session = e.Session; + + if(string.IsNullOrEmpty(_stalinApiUrl)) + { + var sawmill = Logger.GetSawmill("stalin"); + sawmill.Log(LogLevel.Warning, "Stalin API URL is not set, skipping check."); + return; + } + + var discordUserData = await RequestDiscordUserDataAsync(session); + + if (discordUserData == null) + { + return; + } + + _registeredStalinCache[session.UserId.ToString()] = discordUserData; + } + + /// + /// Запрашивает данные о привязки аккаунта к дискорду. Если аккаунт не привязан, возвращает null. + /// + /// + /// + /// + private async Task RequestDiscordUserDataAsync(ICommonSession session) + { + using var client = new HttpClient(); + client.Timeout = TimeSpan.FromSeconds(5); + HttpResponseMessage response; + + try + { + response = await client.GetAsync($"{_stalinApiUrl}isconnected?uuid={session.UserId}"); + } + catch (Exception e) + { + _taskManager.RunOnMainThread(() => + { + _chatManager.DispatchServerMessage(session, Loc.GetString("stalin-request-failed", + ("error", e.InnerException!.ToString()))); + + var sawmill = Logger.GetSawmill("yayca"); + sawmill.Log(LogLevel.Warning, $"API отвалился, звоните Утке..."); + }); + + return null!; + } + + if (!response.IsSuccessStatusCode) + { + _taskManager.RunOnMainThread(() => + { + _chatManager.DispatchServerMessage(session, + Loc.GetString("stalin-request-failed", ("error", response.StatusCode))); + }); + + return null!; + } + + var result = await response.Content.ReadFromJsonAsync(); + + if (result!.Registered == false) + { + return null!; + } + return result!; + } + + private async Task RequestDiscordUsersDataAsync(List sessions) + { + using var client = new HttpClient(); + client.Timeout = TimeSpan.FromSeconds(5); + HttpResponseMessage response; + + try + { + var request = new DiscordUsersDataRequest() + { + Uids = sessions.Select(x => x.UserId.ToString()).ToList() + }; + + response = await client.PostAsJsonAsync(_stalinApiUrl + "isconnected", request); + } + catch (Exception e) + { + Console.WriteLine(e); + return null!; + } + + + var responseData = await response.Content.ReadFromJsonAsync>(); + + var usersData = new DiscordUsersData() + { + Users = responseData! + }; + + return usersData; + } + + private void OnDiscordAuthRequest(DiscordAuthRequest message) + { + + var playerSession = _playerManager.GetSessionByChannel(message.MsgChannel); + + var saltedYayca = GenerateDiscordAuthUri(playerSession.Name, playerSession.UserId.ToString()); + + var response = new DiscordAuthResponse() + { + Uri = saltedYayca + }; + + _netManager.ServerSendMessage(response, message.MsgChannel); + } + + private string GenerateDiscordAuthUri(string ckey, string uid) + { + using var sha1 = new SHA1Managed(); + + var saltBytes = Encoding.UTF8.GetBytes(_configurationManager.GetCVar(WhiteCVars.StalinSalt)); + var ckeyBytes = Encoding.UTF8.GetBytes(ckey); + var uidBytes = Encoding.UTF8.GetBytes(uid); + + var saltedBytes = ckeyBytes.Concat(uidBytes).Concat(saltBytes).ToArray(); + var hash = ToHexStr(sha1.ComputeHash(saltedBytes)); + + var request = WebUtility.UrlEncode($"{ckey}@{uid}@{hash}"); + return $"{_stalinAuthUrl}{request}"; + } + + private static string ToHexStr(byte[] hash) + { + StringBuilder hex = new StringBuilder(hash.Length * 2); + foreach (byte b in hash) + hex.AppendFormat("{0:x2}", b); + return hex.ToString(); + } +} diff --git a/Content.Shared/Administration/AdminFlags.cs b/Content.Shared/Administration/AdminFlags.cs index 05b7a45a46..845826e62f 100644 --- a/Content.Shared/Administration/AdminFlags.cs +++ b/Content.Shared/Administration/AdminFlags.cs @@ -89,6 +89,11 @@ /// EditNotes = 1 << 14, + /// + /// Commands for Meaty Ores. + /// + MeatyOre = 1 << 15, + /// /// Dangerous host permissions like scsi. /// diff --git a/Content.Shared/White/SaltedYayca/DiscordAuthRequest.cs b/Content.Shared/White/SaltedYayca/DiscordAuthRequest.cs new file mode 100644 index 0000000000..125be48910 --- /dev/null +++ b/Content.Shared/White/SaltedYayca/DiscordAuthRequest.cs @@ -0,0 +1,19 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.White.SaltedYayca; + + + +public sealed class DiscordAuthRequest : NetMessage +{ + public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered; + public override MsgGroups MsgGroup => MsgGroups.Core; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { } +} diff --git a/Content.Shared/White/SaltedYayca/DiscordAuthResponse.cs b/Content.Shared/White/SaltedYayca/DiscordAuthResponse.cs new file mode 100644 index 0000000000..e3af1fa6fe --- /dev/null +++ b/Content.Shared/White/SaltedYayca/DiscordAuthResponse.cs @@ -0,0 +1,23 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.White.SaltedYayca; + +public sealed class DiscordAuthResponse : NetMessage +{ + public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered; + public override MsgGroups MsgGroup => MsgGroups.String; + + public string Uri = string.Empty; + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + Uri = buffer.ReadString(); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.Write(Uri); + } +} diff --git a/Content.Shared/White/WhiteCVars.cs b/Content.Shared/White/WhiteCVars.cs index 60bb7dd8f7..14b8e146e1 100644 --- a/Content.Shared/White/WhiteCVars.cs +++ b/Content.Shared/White/WhiteCVars.cs @@ -76,7 +76,7 @@ public sealed class WhiteCVars /// URL of the TTS server API. /// public static readonly CVarDef TTSApiUrl = - CVarDef.Create("tts.api_url", "http://127.0.0.1:2386", CVar.SERVERONLY); + CVarDef.Create("tts.api_url", "", CVar.SERVERONLY); /// /// TTS Volume @@ -90,4 +90,21 @@ public sealed class WhiteCVars public static readonly CVarDef TTSMaxCacheSize = CVarDef.Create("tts.max_cash_size", 200, CVar.SERVERONLY | CVar.ARCHIVE); + + + /* + * Stalin + */ + + public static readonly CVarDef StalinSalt = + CVarDef.Create("stalin.salt", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE); + public static readonly CVarDef StalinApiUrl = + CVarDef.Create("stalin.api_url", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE); + public static readonly CVarDef StalinAuthUrl = + CVarDef.Create("stalin.auth_url", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE); + public static readonly CVarDef StalinEnabled = + CVarDef.Create("stalin.enabled", false, CVar.SERVERONLY | CVar.ARCHIVE); + public static readonly CVarDef StalinDiscordMinimumAge = + CVarDef.Create("stalin.minimal_discord_age_minutes", 30.0f, CVar.SERVERONLY | CVar.ARCHIVE); + }