[feat] Stalin manager
# Conflicts: # Content.Client/Entry/EntryPoint.cs # Content.Server/Entry/EntryPoint.cs # Content.Server/GameTicking/Commands/JoinGameCommand.cs # Content.Server/GameTicking/GameTicker.Lobby.cs # Content.Server/GameTicking/GameTicker.RoundFlow.cs # Content.Server/GameTicking/GameTicker.Spawning.cs
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string> 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()
|
||||
|
||||
@@ -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<JoinQueueManager>();
|
||||
IoCManager.Register<SponsorsManager>();
|
||||
IoCManager.Register<StalinManager>();
|
||||
//WD-EDIT
|
||||
}
|
||||
}
|
||||
|
||||
26
Content.Client/White/Stalin/StalinManager.cs
Normal file
26
Content.Client/White/Stalin/StalinManager.cs
Normal file
@@ -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<DiscordAuthResponse>(OnStalinResponse);
|
||||
}
|
||||
|
||||
public void RequestUri()
|
||||
{
|
||||
_netManager.ClientSendMessage(new DiscordAuthRequest());
|
||||
}
|
||||
|
||||
private void OnStalinResponse(DiscordAuthResponse message)
|
||||
{
|
||||
_uriOpener.OpenUri(message.Uri);
|
||||
}
|
||||
}
|
||||
@@ -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<MindContainerComponent>(args.Target, out var targetMindComp))
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<SponsorsManager>().Initialize();
|
||||
IoCManager.Resolve<JoinQueueManager>().Initialize();
|
||||
IoCManager.Resolve<TTSManager>().Initialize();
|
||||
IoCManager.Resolve<StalinManager>().Initialize();
|
||||
//WD-EDIT
|
||||
|
||||
_voteManager.Initialize();
|
||||
|
||||
@@ -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<IChatManager>();
|
||||
|
||||
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.");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ICommonSession>();
|
||||
var readyPlayerProfiles = new Dictionary<NetUserId, HumanoidCharacterProfile>();
|
||||
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
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Causes the given player to join the current game as observer ghost. See also <see cref="SpawnObserver"/>
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<JoinQueueManager>();
|
||||
IoCManager.Register<UtkaTCPWrapper>();
|
||||
IoCManager.Register<TTSManager>();
|
||||
IoCManager.Register<StalinManager>();
|
||||
// WD-EDIT
|
||||
}
|
||||
}
|
||||
|
||||
49
Content.Server/White/Stalin/Commands/EnableStalinBunker.cs
Normal file
49
Content.Server/White/Stalin/Commands/EnableStalinBunker.cs
Normal file
@@ -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 <bool>";
|
||||
|
||||
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<IChatManager>().DispatchServerAnnouncement(announce, Color.Red);
|
||||
}
|
||||
}
|
||||
38
Content.Server/White/Stalin/DiscordUserData.cs
Normal file
38
Content.Server/White/Stalin/DiscordUserData.cs
Normal file
@@ -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<string> Uids { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class DiscordUsersData
|
||||
{
|
||||
public Dictionary<string, DiscordUserData> Users { get; set; } = new();
|
||||
}
|
||||
|
||||
261
Content.Server/White/Stalin/StalinManager.cs
Normal file
261
Content.Server/White/Stalin/StalinManager.cs
Normal file
@@ -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<string, DiscordUserData> _registeredStalinCache = new();
|
||||
private readonly Dictionary<string, DateTime> _nextStalinAllowedCheck = new();
|
||||
private string _stalinApiUrl = string.Empty;
|
||||
private string _stalinAuthUrl = string.Empty;
|
||||
private float _minimalDiscordAccountAge = 0f;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<DiscordAuthRequest>(OnDiscordAuthRequest);
|
||||
_netManager.RegisterNetMessage<DiscordAuthResponse>();
|
||||
_chatManager = IoCManager.Resolve<IChatManager>();
|
||||
|
||||
_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<ICommonSession>().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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Запрашивает данные о привязки аккаунта к дискорду. Если аккаунт не привязан, возвращает null.
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <exception cref="NullReferenceException"></exception>
|
||||
/// <returns></returns>
|
||||
private async Task<DiscordUserData> 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<DiscordUserData>();
|
||||
|
||||
if (result!.Registered == false)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
return result!;
|
||||
}
|
||||
|
||||
private async Task<DiscordUsersData> RequestDiscordUsersDataAsync(List<ICommonSession> 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<Dictionary<string, DiscordUserData>>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,11 @@
|
||||
/// </summary>
|
||||
EditNotes = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// Commands for Meaty Ores.
|
||||
/// </summary>
|
||||
MeatyOre = 1 << 15,
|
||||
|
||||
/// <summary>
|
||||
/// Dangerous host permissions like scsi.
|
||||
/// </summary>
|
||||
|
||||
19
Content.Shared/White/SaltedYayca/DiscordAuthRequest.cs
Normal file
19
Content.Shared/White/SaltedYayca/DiscordAuthRequest.cs
Normal file
@@ -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)
|
||||
{ }
|
||||
}
|
||||
23
Content.Shared/White/SaltedYayca/DiscordAuthResponse.cs
Normal file
23
Content.Shared/White/SaltedYayca/DiscordAuthResponse.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public sealed class WhiteCVars
|
||||
/// URL of the TTS server API.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> TTSApiUrl =
|
||||
CVarDef.Create("tts.api_url", "http://127.0.0.1:2386", CVar.SERVERONLY);
|
||||
CVarDef.Create("tts.api_url", "", CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// TTS Volume
|
||||
@@ -90,4 +90,21 @@ public sealed class WhiteCVars
|
||||
public static readonly CVarDef<int> TTSMaxCacheSize =
|
||||
CVarDef.Create("tts.max_cash_size", 200, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Stalin
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<string> StalinSalt =
|
||||
CVarDef.Create("stalin.salt", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE);
|
||||
public static readonly CVarDef<string> StalinApiUrl =
|
||||
CVarDef.Create("stalin.api_url", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE);
|
||||
public static readonly CVarDef<string> StalinAuthUrl =
|
||||
CVarDef.Create("stalin.auth_url", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE);
|
||||
public static readonly CVarDef<bool> StalinEnabled =
|
||||
CVarDef.Create("stalin.enabled", false, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
public static readonly CVarDef<float> StalinDiscordMinimumAge =
|
||||
CVarDef.Create("stalin.minimal_discord_age_minutes", 30.0f, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user