From: Fildrance Date: Thu, 28 Aug 2025 08:46:24 +0000 (+0300) Subject: Revert "Added button and manager for in game bug reports (Part 1)" (#39872) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=60ea135fd3b07c69519f1823ae55b19959fb37de;p=space-station-14.git Revert "Added button and manager for in game bug reports (Part 1)" (#39872) Revert "Added button and manager for in game bug reports (Part 1) (#35350)" This reverts commit a8d6dbc3241c880f846a7abba8bd330788df9fc1. --- diff --git a/Content.Client/UserInterface/Systems/BugReport/BugReportUIController.cs b/Content.Client/UserInterface/Systems/BugReport/BugReportUIController.cs deleted file mode 100644 index 7d45829bd2..0000000000 --- a/Content.Client/UserInterface/Systems/BugReport/BugReportUIController.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Content.Client.Gameplay; -using Content.Client.Resources; -using Content.Client.UserInterface.Controls; -using Content.Client.UserInterface.Systems.BugReport.Windows; -using Content.Client.UserInterface.Systems.MenuBar.Widgets; -using Content.Shared.BugReport; -using Content.Shared.CCVar; -using JetBrains.Annotations; -using Robust.Client.ResourceManagement; -using Robust.Client.UserInterface.Controllers; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Configuration; -using Robust.Shared.Network; -using Robust.Shared.Utility; - -namespace Content.Client.UserInterface.Systems.BugReport; - -[UsedImplicitly] -public sealed class BugReportUIController : UIController, IOnStateEntered, IOnStateExited -{ - [Dependency] private readonly IClientNetManager _net = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IResourceCache _resource = default!; - - // This is the link to the hotbar button - private MenuButton? BugReportButton => UIManager.GetActiveUIWidgetOrNull()?.ReportBugButton; - - // Don't clear this window. It needs to be saved so the input doesn't get erased when it's closed! - private BugReportWindow _bugReportWindow = default!; - - private ResPath Bug = new("/Textures/Interface/bug.svg.192dpi.png"); - private ResPath Splat = new("/Textures/Interface/splat.svg.192dpi.png"); - - public void OnStateEntered(GameplayState state) - { - SetupWindow(); - } - - public void OnStateExited(GameplayState state) - { - CleanupWindow(); - } - - public void LoadButton() - { - if (BugReportButton != null) - BugReportButton.OnPressed += ButtonToggleWindow; - } - - public void UnloadButton() - { - if (BugReportButton != null) - BugReportButton.OnPressed -= ButtonToggleWindow; - } - - private void SetupWindow() - { - if (BugReportButton == null) - return; - - _bugReportWindow = UIManager.CreateWindow(); - // This is to make sure the hotbar button gets checked and unchecked when the window is opened / closed. - _bugReportWindow.OnClose += () => - { - BugReportButton.Pressed = false; - BugReportButton.Icon = _resource.GetTexture(Bug); - }; - _bugReportWindow.OnOpen += () => - { - BugReportButton.Pressed = true; - BugReportButton.Icon = _resource.GetTexture(Splat); - }; - - _bugReportWindow.OnBugReportSubmitted += OnBugReportSubmitted; - - _cfg.OnValueChanged(CCVars.EnablePlayerBugReports, UpdateButtonVisibility, true); - } - - private void CleanupWindow() - { - _bugReportWindow.CleanupCCvars(); - - _cfg.UnsubValueChanged(CCVars.EnablePlayerBugReports, UpdateButtonVisibility); - } - - private void ToggleWindow() - { - if (_bugReportWindow.IsOpen) - _bugReportWindow.Close(); - else - _bugReportWindow.OpenCentered(); - } - - private void OnBugReportSubmitted(PlayerBugReportInformation report) - { - var message = new BugReportMessage { ReportInformation = report }; - _net.ClientSendMessage(message); - _bugReportWindow.Close(); - } - - private void ButtonToggleWindow(BaseButton.ButtonEventArgs obj) - { - ToggleWindow(); - } - - private void UpdateButtonVisibility(bool val) - { - if (BugReportButton == null) - return; - - BugReportButton.Visible = val; - } -} diff --git a/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml b/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml deleted file mode 100644 index d3d6570a41..0000000000 --- a/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml.cs b/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml.cs deleted file mode 100644 index a43ba15d68..0000000000 --- a/Content.Client/UserInterface/Systems/BugReport/Windows/BugReportWindow.xaml.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Client.Players.PlayTimeTracking; -using Content.Shared.BugReport; -using Content.Shared.CCVar; -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface.CustomControls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Configuration; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Client.UserInterface.Systems.BugReport.Windows; - -[GenerateTypedNameReferences] -public sealed partial class BugReportWindow : DefaultWindow -{ - [Dependency] private readonly IConfigurationManager _cfg = default!; - // TODO: Use SharedPlaytimeManager when its refactored out of job requirements - [Dependency] private readonly JobRequirementsManager _job = default!; - - // This action gets invoked when the user submits a bug report. - public event Action? OnBugReportSubmitted; - - private DateTime _lastIsEnabledUpdated; - private readonly TimeSpan _isEnabledUpdateInterval = TimeSpan.FromSeconds(1); - - // These are NOT always up to date. If someone disconnects and reconnects, the values will be reset. - // The only other way of getting updated values would be a message from client -> server then from server -> client. - // I don't think that is worth the added complexity. - private DateTime _lastBugReportSubmittedTime = DateTime.MinValue; - private int _amountOfBugReportsSubmitted; - - private readonly ConfigurationMultiSubscriptionBuilder _configSub; - - #region ccvar - - private bool _enablePlayerBugReports; - private int _minimumPlaytimeBugReports; - private int _minimumTimeBetweenBugReports; - private int _maximumBugReportsPerRound; - - private int _maximumBugReportTitleLength; - private int _minimumBugReportTitleLength; - private int _maximumBugReportDescriptionLength; - private int _minimumBugReportDescriptionLength; - - #endregion - - public BugReportWindow() - { - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - - _configSub = _cfg.SubscribeMultiple() - .OnValueChanged(CCVars.EnablePlayerBugReports, x => _enablePlayerBugReports = x, true) - .OnValueChanged(CCVars.MinimumPlaytimeInMinutesToEnableBugReports, x => _minimumPlaytimeBugReports = x, true) - .OnValueChanged(CCVars.MinimumSecondsBetweenBugReports, x => _minimumTimeBetweenBugReports = x, true) - .OnValueChanged(CCVars.MaximumBugReportsPerRound, x => _maximumBugReportsPerRound = x, true) - .OnValueChanged(CCVars.MaximumBugReportTitleLength, x => _maximumBugReportTitleLength = x, true) - .OnValueChanged(CCVars.MinimumBugReportTitleLength, x => _minimumBugReportTitleLength = x, true) - .OnValueChanged(CCVars.MaximumBugReportDescriptionLength, x => _maximumBugReportDescriptionLength = x, true) - .OnValueChanged(CCVars.MinimumBugReportDescriptionLength, x => _minimumBugReportDescriptionLength = x, true); - - // Hook up the events - SubmitButton.OnPressed += _ => OnSubmitButtonPressed(); - BugReportTitle.OnTextChanged += _ => HandleInputChange(); - BugReportDescription.OnTextChanged += _ => HandleInputChange(); - OnOpen += UpdateEnabled; - - HandleInputChange(); - UpdateEnabled(); - } - - private void OnSubmitButtonPressed() - { - var report = new PlayerBugReportInformation - { - BugReportTitle = BugReportTitle.Text, - BugReportDescription = Rope.Collapse(BugReportDescription.TextRope), - }; - OnBugReportSubmitted?.Invoke(report); - - _lastBugReportSubmittedTime = DateTime.UtcNow; - _amountOfBugReportsSubmitted++; - - BugReportTitle.Text = string.Empty; - BugReportDescription.TextRope = Rope.Leaf.Empty; - - HandleInputChange(); - UpdateEnabled(); - } - - /// - /// Deals with the user changing their input. Ensures that things that depend on what the user has inputted get updated - /// (E.g. the amount of characters they have typed) - /// - private void HandleInputChange() - { - var titleLen = BugReportTitle.Text.Length; - var descriptionLen = BugReportDescription.TextLength; - - var invalidTitleLen = titleLen < _minimumBugReportTitleLength || titleLen > _maximumBugReportTitleLength; - var invalidDescriptionLen = descriptionLen < _minimumBugReportDescriptionLength || descriptionLen > _maximumBugReportDescriptionLength; - - TitleCharacterCounter.Text = Loc.GetString("bug-report-window-submit-char-split", ("typed", titleLen), ("total", _maximumBugReportTitleLength)); - TitleCharacterCounter.FontColorOverride = invalidTitleLen ? Color.Red : Color.Green; - - DescriptionCharacterCounter.Text = Loc.GetString("bug-report-window-submit-char-split", ("typed", descriptionLen), ("total", _maximumBugReportDescriptionLength)); - - DescriptionCharacterCounter.FontColorOverride = invalidDescriptionLen ? Color.Red : Color.Green; - - SubmitButton.Disabled = invalidTitleLen || invalidDescriptionLen; - - PlaceholderCenter.Visible = descriptionLen == 0; - } - - /// - /// Checks if the bug report window should be enabled for this client. - /// - private bool IsEnabled([NotNullWhen(false)] out string? errorMessage) - { - errorMessage = null; - - if (!_enablePlayerBugReports) - { - errorMessage = Loc.GetString("bug-report-window-disabled-not-enabled"); - return false; - } - - if (TimeSpan.FromMinutes(_minimumPlaytimeBugReports) > _job.FetchOverallPlaytime()) - { - errorMessage = Loc.GetString("bug-report-window-disabled-playtime"); - return false; - } - - if (_amountOfBugReportsSubmitted >= _maximumBugReportsPerRound) - { - errorMessage = Loc.GetString("bug-report-window-disabled-submissions", ("num", _maximumBugReportsPerRound)); - return false; - } - - var timeSinceLastReport = DateTime.UtcNow - _lastBugReportSubmittedTime; - var timeBetweenBugReports = TimeSpan.FromSeconds(_minimumTimeBetweenBugReports); - - if (timeSinceLastReport <= timeBetweenBugReports) - { - var time = timeBetweenBugReports - timeSinceLastReport; - errorMessage = Loc.GetString("bug-report-window-disabled-cooldown", ("time", time.ToString(@"d\.hh\:mm\:ss"))); - return false; - } - - return true; - } - - // Update the state of the window to display either the bug report window or an error explaining why you can't submit a report. - private void UpdateEnabled() - { - var isEnabled = IsEnabled(out var errorMessage); - DisabledLabel.Text = errorMessage; - - DisabledLabel.Visible = !isEnabled; - BugReportContainer.Visible = isEnabled; - _lastIsEnabledUpdated = DateTime.UtcNow; - } - - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - - if (!Visible) // Don't bother updating if no one can see the window anyway. - return; - - if(DateTime.UtcNow - _lastIsEnabledUpdated > _isEnabledUpdateInterval) - UpdateEnabled(); - } - - public void CleanupCCvars() - { - _configSub.Dispose(); - } -} diff --git a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs index fb7c7f9d25..e314310bc0 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs +++ b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs @@ -1,6 +1,5 @@ using Content.Client.UserInterface.Systems.Actions; using Content.Client.UserInterface.Systems.Admin; -using Content.Client.UserInterface.Systems.BugReport; using Content.Client.UserInterface.Systems.Bwoink; using Content.Client.UserInterface.Systems.Character; using Content.Client.UserInterface.Systems.Crafting; @@ -25,7 +24,6 @@ public sealed class GameTopMenuBarUIController : UIController [Dependency] private readonly SandboxUIController _sandbox = default!; [Dependency] private readonly GuidebookUIController _guidebook = default!; [Dependency] private readonly EmotesUIController _emotes = default!; - [Dependency] private readonly BugReportUIController _bug = default!; private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull(); @@ -49,7 +47,6 @@ public sealed class GameTopMenuBarUIController : UIController _action.UnloadButton(); _sandbox.UnloadButton(); _emotes.UnloadButton(); - _bug.UnloadButton(); } public void LoadButtons() @@ -63,6 +60,5 @@ public sealed class GameTopMenuBarUIController : UIController _action.LoadButton(); _sandbox.LoadButton(); _emotes.LoadButton(); - _bug.LoadButton(); } } diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index 2c09666fdf..dc8972970a 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -93,15 +93,6 @@ HorizontalExpand="True" AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}" /> - -public sealed class BugReportManager : IBugReportManager, IPostInjectInit -{ - [Dependency] private readonly IServerNetManager _net = default!; - [Dependency] private readonly IEntityManager _entity = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTime = default!; - [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IAdminLogManager _admin = default!; - [Dependency] private readonly IGameMapManager _map = default!; - [Dependency] private readonly GithubApiManager _githubApiManager = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly ILogManager _log = default!; - - private ISawmill _sawmill = default!; - - /// - /// List of player NetIds and the number of bug reports they have submitted this round. - /// UserId -> (bug reports this round, last submitted bug report) - /// - private readonly Dictionary _bugReportsPerPlayerThisRound = new(); - - private BugReportLimits _limits = default!; - - private List _tags = []; - - private ConfigurationMultiSubscriptionBuilder _configSub = default!; - - public void Initialize() - { - _net.RegisterNetMessage(ReceivedPlayerBugReport); - - _limits = new BugReportLimits(); - - _configSub = _cfg.SubscribeMultiple() - .OnValueChanged(CCVars.MaximumBugReportTitleLength, x => _limits.TitleMaxLength = x, true) - .OnValueChanged(CCVars.MinimumBugReportTitleLength, x => _limits.TitleMinLength = x, true) - .OnValueChanged(CCVars.MaximumBugReportDescriptionLength, x => _limits.DescriptionMaxLength = x, true) - .OnValueChanged(CCVars.MinimumBugReportDescriptionLength, x => _limits.DescriptionMinLength = x, true) - .OnValueChanged(CCVars.MinimumPlaytimeInMinutesToEnableBugReports, x => _limits.MinimumPlaytimeToEnableBugReports = TimeSpan.FromMinutes(x), true) - .OnValueChanged(CCVars.MaximumBugReportsPerRound, x => _limits.MaximumBugReportsForPlayerPerRound = x, true) - .OnValueChanged(CCVars.MinimumSecondsBetweenBugReports, x => _limits.MinimumTimeBetweenBugReports = TimeSpan.FromSeconds(x), true) - .OnValueChanged(CCVars.BugReportTags, x => _tags = x.Split(",").ToList(), true); - } - - public void Restart() - { - // When the round restarts, clear the dictionary. - _bugReportsPerPlayerThisRound.Clear(); - } - - public void Shutdown() - { - _configSub.Dispose(); - } - - private void ReceivedPlayerBugReport(BugReportMessage message) - { - if (!_cfg.GetCVar(CCVars.EnablePlayerBugReports)) - return; - - var netId = message.MsgChannel.UserId; - var userName = message.MsgChannel.UserName; - var report = message.ReportInformation; - if (!IsBugReportValid(report, (NetId: netId, UserName: userName)) || !CanPlayerSendReport(netId, userName)) - return; - - var playerBugReportingStats = _bugReportsPerPlayerThisRound.GetValueOrDefault(netId); - _bugReportsPerPlayerThisRound[netId] = (playerBugReportingStats.ReportsCount + 1, DateTime.UtcNow); - - var title = report.BugReportTitle; - var description = report.BugReportDescription; - - _admin.Add(LogType.BugReport, LogImpact.High, $"{message.MsgChannel.UserName}, {netId}: submitted a bug report. Title: {title}, Description: {description}"); - - var bugReport = CreateBugReport(message); - - _githubApiManager.TryCreateIssue(bugReport); - } - - /// - /// Checks that the given report is valid (E.g. not too long etc...). - /// Logs problems if report is invalid. - /// - /// True if the report is valid, false there is an issue with the report. - private bool IsBugReportValid(PlayerBugReportInformation report, (NetUserId NetId, string UserName) userData) - { - var descriptionLen = report.BugReportDescription.Length; - var titleLen = report.BugReportTitle.Length; - - // These should only happen if there is a hacked client or a glitch! - if (titleLen < _limits.TitleMinLength || titleLen > _limits.TitleMaxLength) - { - _sawmill.Warning( - $"{userData.UserName}, {userData.NetId}: has tried to submit a bug report " - + $"with a title of {titleLen} characters, min/max: {_limits.TitleMinLength}/{_limits.TitleMaxLength}." - ); - return false; - } - - if (descriptionLen < _limits.DescriptionMinLength || descriptionLen > _limits.DescriptionMaxLength) - { - _sawmill.Warning( - $"{userData.UserName}, {userData.NetId}: has tried to submit a bug report " - + $"with a description of {descriptionLen} characters, min/max: {_limits.DescriptionMinLength}/{_limits.DescriptionMaxLength}." - ); - return false; - } - - return true; - } - - /// - /// Checks that the player sending the report is allowed to (E.g. not spamming etc...). - /// Logs problems if report is invalid. - /// - /// True if the player can submit a report, false if they can't. - private bool CanPlayerSendReport(NetUserId netId, string userName) - { - var session = _player.GetSessionById(netId); - var playtime = _playTime.GetOverallPlaytime(session); - if (_limits.MinimumPlaytimeToEnableBugReports > playtime) - return false; - - var playerBugReportingStats = _bugReportsPerPlayerThisRound.GetValueOrDefault(netId); - var maximumBugReportsForPlayerPerRound = _limits.MaximumBugReportsForPlayerPerRound; - if (playerBugReportingStats.ReportsCount >= maximumBugReportsForPlayerPerRound) - { - _admin.Add(LogType.BugReport, - LogImpact.High, - $"{userName}, {netId}: has tried to submit more than {maximumBugReportsForPlayerPerRound} bug reports this round."); - return false; - } - - var timeSinceLastReport = DateTime.UtcNow - playerBugReportingStats.ReportedDateTime; - var timeBetweenBugReports = _limits.MinimumTimeBetweenBugReports; - if (timeSinceLastReport <= timeBetweenBugReports) - { - _admin.Add(LogType.BugReport, - LogImpact.High, - $"{userName}, {netId}: has tried to submit a bug report. " - + $"Last bug report was {timeSinceLastReport:g} ago. The limit is {timeBetweenBugReports:g} minutes." - ); - return false; - } - - return true; - } - - /// - /// Create a bug report out of the given message. Add will extra metadata that could be useful, along with - /// the original text report from the user. - /// - /// The message from user. - /// A based of the user report. - private ValidPlayerBugReportReceivedEvent CreateBugReport(BugReportMessage message) - { - // todo: dont request entity system out of sim, check if you are in-sim before doing so. Bug report should work out of sim too. - var ticker = _entity.System(); - var metadata = new BugReportMetaData - { - Username = message.MsgChannel.UserName, - PlayerGUID = message.MsgChannel.UserData.UserId, - ServerName = _cfg.GetCVar(CCVars.AdminLogsServerName), - NumberOfPlayers = _player.PlayerCount, - SubmittedTime = DateTime.UtcNow, - BuildVersion = _cfg.GetCVar(CVars.BuildVersion), - EngineVersion = _cfg.GetCVar(CVars.BuildEngineVersion), - }; - - // Only add these if your in round. - if (ticker.Preset != null) - { - metadata.RoundTime = _timing.CurTime.Subtract(ticker.RoundStartTimeSpan); - metadata.RoundNumber = ticker.RoundId; - metadata.RoundType = Loc.GetString(ticker.CurrentPreset?.ModeTitle ?? "bug-report-report-unknown"); - metadata.Map = _map.GetSelectedMap()?.MapName ?? Loc.GetString("bug-report-report-unknown"); - } - - return new ValidPlayerBugReportReceivedEvent( - message.ReportInformation.BugReportTitle.Trim(), - message.ReportInformation.BugReportDescription.Trim(), - metadata, - _tags - ); - } - - void IPostInjectInit.PostInject() - { - _sawmill = _log.GetSawmill("BugReport"); - } - - private sealed class BugReportLimits - { - public int TitleMaxLength; - public int TitleMinLength; - public int DescriptionMaxLength; - public int DescriptionMinLength; - - public TimeSpan MinimumPlaytimeToEnableBugReports; - public int MaximumBugReportsForPlayerPerRound; - public TimeSpan MinimumTimeBetweenBugReports; - } -} diff --git a/Content.Server/BugReports/IBugReportEvents.cs b/Content.Server/BugReports/IBugReportEvents.cs deleted file mode 100644 index 3e79ff542e..0000000000 --- a/Content.Server/BugReports/IBugReportEvents.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Robust.Shared.Network; - -namespace Content.Server.BugReports; - -/// -/// This event stores information related to a player submitted bug report. -/// -public sealed class ValidPlayerBugReportReceivedEvent(string title, string description, BugReportMetaData metaData, List tags) : EventArgs -{ - /// - /// Title for the bug report. This is player controlled! - /// - public string Title = title; - - /// - /// Description for the bug report. This is player controlled! - /// - public string Description = description; - - /// - /// Metadata for bug report, containing data collected by server. - /// - public BugReportMetaData MetaData = metaData; - - public List Tags = tags; -} - -/// -/// Metadata for a bug report. Holds relevant data for bug reports that aren't directly player controlled. -/// -public sealed class BugReportMetaData -{ - /// - /// Bug reporter SS14 username. - /// - /// piggylongsnout - public required string Username; - - /// - /// The GUID of the player who reported the bug. - /// - public required NetUserId PlayerGUID; - - /// - /// Name of the server from which bug report was issued. - /// - /// DeltaV> - public required string ServerName; - - /// - /// Date and time on which player submitted report (NOT round time). - /// The time is UTC and based off the servers clock. - /// - public required DateTime SubmittedTime; - - /// - /// Time that has elapsed in the round. Can be null if bug was not reported during a round. - /// - public TimeSpan? RoundTime; - - /// - /// Round number during which bug report was issued. Can be null if bug was reported not during round. - /// - /// 1311 - public int? RoundNumber; - - /// - /// Type preset title (type of round that is being played). Can be null if bug was reported not during round. - /// - /// Sandbox - public string? RoundType; - - /// - /// The map being played. - /// - /// "Dev"> - public string? Map; - - /// - /// Number of players currently on server. - /// - public int NumberOfPlayers; - - /// - /// Build version of the game. - /// - public required string BuildVersion; - - /// - /// Engine version of the game. - /// - /// 253.0.0 - public required string EngineVersion; -} diff --git a/Content.Server/BugReports/IBugReportManager.cs b/Content.Server/BugReports/IBugReportManager.cs deleted file mode 100644 index 28264bc8c0..0000000000 --- a/Content.Server/BugReports/IBugReportManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Content.Server.BugReports; - -/// -/// Manager for validating client bug reports, issued in-game, and relaying creation of issue in tracker to dedicated api client. -/// -public interface IBugReportManager -{ - /// Will get called when the manager is first initialized. - public void Initialize(); - - /// - /// Will get called whenever the round is restarted. - /// Should be used to clean up anything that needs reset after each round. - /// - public void Restart(); - - /// - /// Will get called whenever the round is restarted. - /// Should be used to clean up anything that needs reset after each round. - /// - public void Shutdown(); -} diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 1f2d035a4a..df4af14f1e 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -3,7 +3,6 @@ using Content.Server.Administration; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Afk; -using Content.Server.BugReports; using Content.Server.Chat.Managers; using Content.Server.Connection; using Content.Server.Database; @@ -11,12 +10,13 @@ using Content.Server.Discord.DiscordLink; using Content.Server.EUI; using Content.Server.GameTicking; using Content.Server.GhostKick; -using Content.Server.Github; using Content.Server.GuideGenerator; using Content.Server.Info; using Content.Server.IoC; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Objectives; +using Content.Server.Players; using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Players.RateLimiting; @@ -111,10 +111,6 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); _voteManager.Initialize(); _updateManager.Initialize(); @@ -196,8 +192,6 @@ namespace Content.Server.Entry IoCManager.Resolve().Shutdown(); IoCManager.Resolve().Shutdown(); - - IoCManager.Resolve().Shutdown(); } private static void LoadConfigPresets(IConfigurationManager cfg, IResourceManager res, ISawmill sawmill) diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index cb5a1a4187..1dadca4c03 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -725,8 +725,6 @@ namespace Content.Server.GameTicking _banManager.Restart(); - _bugManager.Restart(); - _gameMapManager.ClearSelectedMap(); // Clear up any game rules. diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 290d363047..55bf51db02 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -1,6 +1,5 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; -using Content.Server.BugReports; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Server.Database; @@ -66,7 +65,6 @@ namespace Content.Server.GameTicking [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly ServerDbEntryManager _dbEntryManager = default!; - [Dependency] private readonly IBugReportManager _bugManager = default!; [ViewVariables] private bool _initialized; [ViewVariables] private bool _postInitialized; diff --git a/Content.Server/Github/Commands/TestGithubApiCommand.cs b/Content.Server/Github/Commands/TestGithubApiCommand.cs deleted file mode 100644 index 85ac798057..0000000000 --- a/Content.Server/Github/Commands/TestGithubApiCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Content.Server.Administration; -using Content.Server.Github.Requests; -using Content.Shared.Administration; -using Content.Shared.CCVar; -using Robust.Shared.Configuration; -using Robust.Shared.Console; - -namespace Content.Server.Github.Commands; - -/// -/// Simple command for testing if the GitHub api is set up correctly! It ensures that all necessary ccvars are set, -/// and will also create one new issue on the targeted repository. -/// -[AdminCommand(AdminFlags.Server)] -public sealed class TestGithubApiCommand : LocalizedCommands -{ - [Dependency] private readonly GithubApiManager _git = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - - public override string Command => Loc.GetString("github-command-test-name"); - - public override async void Execute(IConsoleShell shell, string argStr, string[] args) - { - var enabled = _cfg.GetCVar(CCVars.GithubEnabled); - var path = _cfg.GetCVar(CCVars.GithubAppPrivateKeyPath); - var appId = _cfg.GetCVar(CCVars.GithubAppId); - var repoName = _cfg.GetCVar(CCVars.GithubRepositoryName); - var owner = _cfg.GetCVar(CCVars.GithubRepositoryOwner); - - if (!enabled) - { - shell.WriteError(Loc.GetString("github-command-not-enabled")); - return; - } - - if (string.IsNullOrWhiteSpace(path)) - { - shell.WriteError(Loc.GetString("github-command-no-path")); - return; - } - - if (string.IsNullOrWhiteSpace(appId)) - { - shell.WriteError(Loc.GetString("github-command-no-app-id")); - return; - } - - if (string.IsNullOrWhiteSpace(repoName)) - { - shell.WriteError(Loc.GetString("github-command-no-repo-name")); - return; - } - - if (string.IsNullOrWhiteSpace(owner)) - { - shell.WriteError(Loc.GetString("github-command-no-owner")); - return; - } - - // Create two issues and send them to the api. - var request = new CreateIssueRequest - { - Title = Loc.GetString("github-command-issue-title-one"), - Body = Loc.GetString("github-command-issue-description-one"), - }; - - _git.TryMakeRequest(request); - - shell.WriteLine(Loc.GetString("github-command-finish")); - } -} diff --git a/Content.Server/Github/GithubApiManager.cs b/Content.Server/Github/GithubApiManager.cs deleted file mode 100644 index 44f164fdf0..0000000000 --- a/Content.Server/Github/GithubApiManager.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.Github.Requests; -using System.Threading.Tasks; -using Content.Server.BugReports; - -namespace Content.Server.Github; - -public sealed class GithubApiManager -{ - [Dependency] private readonly GithubBackgroundWorker _githubWorker = default!; - - public void Initialize() - { - Task.Run(() => _githubWorker.HandleQueue()); - } - - public bool TryCreateIssue(ValidPlayerBugReportReceivedEvent bugReport) - { - var createIssueRequest = ConvertToCreateIssue(bugReport); - return TryMakeRequest(createIssueRequest); - } - - public bool TryMakeRequest(IGithubRequest request) - { - return _githubWorker.Writer.TryWrite(request); - } - - private CreateIssueRequest ConvertToCreateIssue(ValidPlayerBugReportReceivedEvent bugReport) - { - var request = new CreateIssueRequest - { - Title = bugReport.Title, - Labels = bugReport.Tags, - }; - - var metadata = bugReport.MetaData; - - request.Body = Loc.GetString("github-issue-format", - ("description", bugReport.Description), - ("buildVersion", metadata.BuildVersion), - ("engineVersion", metadata.EngineVersion), - ("serverName", metadata.ServerName), - ("submittedTime", metadata.SubmittedTime), - ("roundNumber", metadata.RoundNumber.ToString() ?? ""), - ("roundTime", metadata.RoundTime.ToString() ?? ""), - ("roundType", metadata.RoundType ?? ""), - ("map", metadata.Map ?? ""), - ("numberOfPlayers", metadata.NumberOfPlayers), - ("username", metadata.Username), - ("playerGUID", metadata.PlayerGUID)); - - return request; - } -} diff --git a/Content.Server/Github/GithubBackgroundWorker.cs b/Content.Server/Github/GithubBackgroundWorker.cs deleted file mode 100644 index 06d85dd001..0000000000 --- a/Content.Server/Github/GithubBackgroundWorker.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Content.Server.Github.Requests; -using Content.Shared.CCVar; -using Robust.Shared.Configuration; - -namespace Content.Server.Github; - -public sealed class GithubBackgroundWorker -{ - [Dependency] private readonly GithubClient _client = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly ILogManager _log = default!; - - private ISawmill _sawmill = default!; - - private bool _enabled; - private readonly Channel _channel = Channel.CreateUnbounded(); - private readonly CancellationTokenSource _cts = new CancellationTokenSource(); - - public ChannelWriter Writer => _channel.Writer; - - public void Initialize() - { - _sawmill = _log.GetSawmill("github-ratelimit"); - _cfg.OnValueChanged(CCVars.GithubEnabled, val => Interlocked.Exchange(ref _enabled, val), true); - } - - public async Task HandleQueue() - { - var token = _cts.Token; - var reader = _channel.Reader; - while (!token.IsCancellationRequested) - { - await reader.WaitToReadAsync(token); - if (!reader.TryRead(out var request)) - continue; - - await SendRequest(request, token); - } - } - - // this should be called in BaseServer.Cleanup! - public void Shutdown() - { - _cts.Cancel(); - } - - /// - /// Directly send a request to the API. This does not have any rate limits checks so be careful! - /// Only use this if you have a very good reason to! - /// - /// The request to make. - /// Request cancellation token. - /// The direct HTTP response from the API. If null the request could not be made. - private async Task SendRequest(T request, CancellationToken ct) where T : IGithubRequest - { - if (!_enabled) - { - _sawmill.Info("Tried to make a github api request but the api was not enabled."); - return; - } - - try - { - await _client.TryMakeRequestSafe(request, ct); - } - catch (Exception e) - { - _sawmill.Error("Github API exception: {error}", e.ToString()); - } - } -} diff --git a/Content.Server/Github/GithubClient.cs b/Content.Server/Github/GithubClient.cs deleted file mode 100644 index ed7563dd3f..0000000000 --- a/Content.Server/Github/GithubClient.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Content.Server.Github.Requests; -using Content.Server.Github.Responses; -using Content.Shared.CCVar; -using JetBrains.Annotations; -using Robust.Shared.Configuration; - -namespace Content.Server.Github; - -/// -/// Basic implementation of the GitHub api. This was mainly created for making issues from users bug reports - it is not -/// a full implementation! I tried to follow the spec very closely and the docs are really well done. I highly recommend -/// taking a look at them! -///
-///
Some useful information about the api: -///
Api home page -///
Best practices -///
Rate limit information -///
Troubleshooting -///
-/// As it uses async, it should be called from background worker when possible, like . -public sealed class GithubClient -{ - [Dependency] private readonly ILogManager _log = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - private HttpClient _httpClient = default!; - - private ISawmill _sawmill = default!; - - // Token data for the GitHub app (This is used to authenticate stuff like new issue creation) - private (DateTime? Expiery, string Token) _tokenData; - - // Json web token for the GitHub app (This is used to authenticate stuff like seeing where the app is installed) - // The token is created locally. - private (DateTime? Expiery, string JWT) _jwtData; - - private const int ErrorResponseMaxLogSize = 200; - - private readonly JsonSerializerOptions _jsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - - // Docs say 10 should be the maximum. - private readonly TimeSpan _jwtExpiration = TimeSpan.FromMinutes(10); - private readonly TimeSpan _jwtBackDate = TimeSpan.FromMinutes(1); - - // Buffers because requests can take a while. We don't want the tokens to expire in the middle of doing requests! - private readonly TimeSpan _jwtBuffer = TimeSpan.FromMinutes(2); - private readonly TimeSpan _tokenBuffer = TimeSpan.FromMinutes(2); - - private string _privateKey = ""; - - #region Header constants - - private const string ProductName = "SpaceStation14GithubApi"; - private const string ProductVersion = "1"; - - private const string AcceptHeader = "Accept"; - private const string AcceptHeaderType = "application/vnd.github+json"; - - private const string AuthHeader = "Authorization"; - private const string AuthHeaderBearer = "Bearer "; - - private const string VersionHeader = "X-GitHub-Api-Version"; - private const string VersionNumber = "2022-11-28"; - - #endregion - - private readonly Uri _baseUri = new("https://api.github.com/"); - - #region CCvar values - - private string _appId = ""; - private string _repository = ""; - private string _owner = ""; - private int _maxRetries; - - #endregion - - public void Initialize() - { - _sawmill = _log.GetSawmill("github"); - _tokenData = (null, ""); - _jwtData = (null, ""); - - _cfg.OnValueChanged(CCVars.GithubAppPrivateKeyPath, OnPrivateKeyPathChanged, true); - _cfg.OnValueChanged(CCVars.GithubAppId, val => Interlocked.Exchange(ref _appId, val), true); - _cfg.OnValueChanged(CCVars.GithubRepositoryName, val => Interlocked.Exchange(ref _repository, val), true); - _cfg.OnValueChanged(CCVars.GithubRepositoryOwner, val => Interlocked.Exchange(ref _owner, val), true); - _cfg.OnValueChanged(CCVars.GithubMaxRetries, val => SetValueAndInitHttpClient(ref _maxRetries, val), true); - } - - private void OnPrivateKeyPathChanged(string path) - { - if (string.IsNullOrEmpty(path)) - return; - - if (!File.Exists(path)) - { - _sawmill.Error($"\"{path}\" does not exist."); - return; - } - - string fileText; - try - { - fileText = File.ReadAllText(path); - } - catch (Exception e) - { - _sawmill.Error($"\"{path}\" could not be read!\n{e}"); - return; - } - - var rsa = RSA.Create(); - try - { - rsa.ImportFromPem(fileText); - } - catch - { - _sawmill.Error($"\"{path}\" does not contain a valid private key!"); - return; - } - - _privateKey = fileText; - } - - private void SetValueAndInitHttpClient(ref T toSet, T value) - { - Interlocked.Exchange(ref toSet, value); - - var httpMessageHandler = new RetryHandler(new HttpClientHandler(), _maxRetries, _sawmill); - var newClient = new HttpClient(httpMessageHandler) - { - BaseAddress = _baseUri, - DefaultRequestHeaders = - { - { AcceptHeader, AcceptHeaderType }, - { VersionHeader, VersionNumber }, - }, - Timeout = TimeSpan.FromSeconds(15), - }; - - newClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(ProductName, ProductVersion)); - - Interlocked.Exchange(ref _httpClient, newClient); - } - - #region Public functions - - /// - /// The standard way to make requests to the GitHub api. This will ensure that the request respects the rate limit - /// and will also retry the request if it fails. Awaiting this to finish could take a very long time depending - /// on what exactly is going on! Only await for it if you're willing to wait a long time. - /// - /// The request you want to make. - /// Token for operation cancellation. - /// The direct HTTP response from the API. If null the request could not be made. - public async Task TryMakeRequestSafe(IGithubRequest request, CancellationToken ct) - { - if (!HaveFullApiData()) - { - _sawmill.Info("Tried to make a github api request but the api was not enabled."); - return null; - } - - if (request.AuthenticationMethod == GithubAuthMethod.Token && !await TryEnsureTokenNotExpired(ct)) - return null; - - return await MakeRequest(request, ct); - } - - private async Task MakeRequest(IGithubRequest request, CancellationToken ct) - { - var httpRequestMessage = BuildRequest(request); - - var response = await _httpClient.SendAsync(httpRequestMessage, ct); - - var message = $"Made a github api request to: '{httpRequestMessage.RequestUri}', status is {response.StatusCode}"; - if (response.IsSuccessStatusCode) - { - _sawmill.Info(message); - return response; - } - - _sawmill.Error(message); - var responseText = await response.Content.ReadAsStringAsync(ct); - - if (responseText.Length > ErrorResponseMaxLogSize) - responseText = responseText.Substring(0, ErrorResponseMaxLogSize); - - _sawmill.Error(message + "\r\n" + responseText); - - return null; - } - - /// - /// A simple helper function that just tries to parse a header value that is expected to be a long int. - /// In general, there are just a lot of single value headers that are longs so this removes a lot of duplicate code. - /// - /// The headers that you want to search. - /// The header you want to get the long value for. - /// Value of header, if found, null otherwise. - /// The headers value if it exists, null otherwise. - public static bool TryGetHeaderAsLong(HttpResponseHeaders? headers, string header, [NotNullWhen(true)] out long? value) - { - value = null; - if (headers == null) - return false; - - if (!headers.TryGetValues(header, out var headerValues)) - return false; - - if (!long.TryParse(headerValues.First(), out var result)) - return false; - - value = result; - return true; - } - - # endregion - - #region Helper functions - - private HttpRequestMessage BuildRequest(IGithubRequest request) - { - var json = JsonSerializer.Serialize(request, _jsonSerializerOptions); - var payload = new StringContent(json, Encoding.UTF8, "application/json"); - - var builder = new UriBuilder(_baseUri) - { - Port = -1, - Path = request.GetLocation(_owner, _repository), - }; - - var httpRequest = new HttpRequestMessage - { - Method = request.RequestMethod, - RequestUri = builder.Uri, - Content = payload, - }; - - httpRequest.Headers.Add(AuthHeader, CreateAuthenticationHeader(request)); - - return httpRequest; - } - - private bool HaveFullApiData() - { - return !string.IsNullOrWhiteSpace(_privateKey) && - !string.IsNullOrWhiteSpace(_repository) && - !string.IsNullOrWhiteSpace(_owner); - } - - private string CreateAuthenticationHeader(IGithubRequest request) - { - return request.AuthenticationMethod switch - { - GithubAuthMethod.Token => AuthHeaderBearer + _tokenData.Token, - GithubAuthMethod.JWT => AuthHeaderBearer + GetValidJwt(), - _ => throw new Exception("Unknown auth method!"), - }; - } - - // TODO: Maybe ensure that perms are only read metadata / write issues so people don't give full access - /// - /// Try to get a valid verification token from the GitHub api - /// - /// True if the token is valid and successfully found, false if there was an error. - private async Task TryEnsureTokenNotExpired(CancellationToken ct) - { - if (_tokenData.Expiery != null && _tokenData.Expiery - _tokenBuffer > DateTime.UtcNow) - return true; - - _sawmill.Info("Token expired - requesting new token!"); - - var installationRequest = new InstallationsRequest(); - var installationHttpResponse = await MakeRequest(installationRequest, ct); - if (installationHttpResponse == null) - { - _sawmill.Error("Could not make http installation request when creating token."); - return false; - } - - var installationResponse = await installationHttpResponse.Content.ReadFromJsonAsync>(_jsonSerializerOptions, ct); - if (installationResponse == null) - { - _sawmill.Error("Could not parse installation response."); - return false; - } - - if (installationResponse.Count == 0) - { - _sawmill.Error("App not installed anywhere."); - return false; - } - - int? installationId = null; - foreach (var installation in installationResponse) - { - if (installation.Account.Login != _owner) - continue; - - installationId = installation.Id; - break; - } - - if (installationId == null) - { - _sawmill.Error("App not installed in given repository."); - return false; - } - - var tokenRequest = new TokenRequest - { - InstallationId = installationId.Value, - }; - - var tokenHttpResponse = await MakeRequest(tokenRequest, ct); - if (tokenHttpResponse == null) - { - _sawmill.Error("Could not make http token request when creating token.."); - return false; - } - - var tokenResponse = await tokenHttpResponse.Content.ReadFromJsonAsync(_jsonSerializerOptions, ct); - if (tokenResponse == null) - { - _sawmill.Error("Could not parse token response."); - return false; - } - - _tokenData = (tokenResponse.ExpiresAt, tokenResponse.Token); - return true; - } - - // See: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app - private string GetValidJwt() - { - if (_jwtData.Expiery != null && _jwtData.Expiery - _jwtBuffer > DateTime.UtcNow) - return _jwtData.JWT; - - var githubClientId = _appId; - var apiPrivateKey = _privateKey; - - var time = DateTime.UtcNow; - var expTime = time + _jwtExpiration; - var iatTime = time - _jwtBackDate; - - var iat = ((DateTimeOffset) iatTime).ToUnixTimeSeconds(); - var exp = ((DateTimeOffset) expTime).ToUnixTimeSeconds(); - - const string headerJson = """ - { - "typ":"JWT", - "alg":"RS256" - } - """; - - var headerEncoded = Base64EncodeUrlSafe(headerJson); - - var payloadJson = $$""" - { - "iat":{{iat}}, - "exp":{{exp}}, - "iss":"{{githubClientId}}" - } - """; - - var payloadJsonEncoded = Base64EncodeUrlSafe(payloadJson); - - var headPayload = $"{headerEncoded}.{payloadJsonEncoded}"; - - var rsa = System.Security.Cryptography.RSA.Create(); - rsa.ImportFromPem(apiPrivateKey); - - var bytesPlainTextData = Encoding.UTF8.GetBytes(headPayload); - - var signedData = rsa.SignData(bytesPlainTextData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - var signBase64 = Base64EncodeUrlSafe(signedData); - - var jwt = $"{headPayload}.{signBase64}"; - - _jwtData = (expTime, jwt); - - _sawmill.Info("Generated new JWT."); - - return jwt; - } - - private string Base64EncodeUrlSafe(string plainText) - { - return Base64EncodeUrlSafe(Encoding.UTF8.GetBytes(plainText)); - } - - private string Base64EncodeUrlSafe(byte[] plainText) - { - return Convert.ToBase64String(plainText) - .TrimEnd('=') - .Replace('+', '-') - .Replace('/', '_'); - } - - #endregion -} diff --git a/Content.Server/Github/Requests/CreateIssueRequest.cs b/Content.Server/Github/Requests/CreateIssueRequest.cs deleted file mode 100644 index 92cee609fa..0000000000 --- a/Content.Server/Github/Requests/CreateIssueRequest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Net.Http; -using System.Text.Json.Serialization; - -namespace Content.Server.Github.Requests; - -/// -/// > -/// -public sealed class CreateIssueRequest : IGithubRequest -{ - [JsonIgnore] - public HttpMethod RequestMethod => HttpMethod.Post; - - [JsonIgnore] - public GithubAuthMethod AuthenticationMethod => GithubAuthMethod.Token; - - #region JSON fields - - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public required string Title; - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Body; - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Assignee; - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Milestone; - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public List Labels = []; - [JsonInclude, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public List Assignees = []; - - #endregion - - public string GetLocation(string owner, string repository) - { - return $"repos/{owner}/{repository}/issues"; - } -} diff --git a/Content.Server/Github/Requests/IGithubRequest.cs b/Content.Server/Github/Requests/IGithubRequest.cs deleted file mode 100644 index afc421722d..0000000000 --- a/Content.Server/Github/Requests/IGithubRequest.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Net.Http; -using System.Text.Json.Serialization; - -namespace Content.Server.Github.Requests; - -/// -/// Interface for all github api requests. -/// -/// -/// WARNING: You must add this JsonDerivedType for all requests that have json otherwise they will not parse properly! -/// -[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)] -[JsonDerivedType(typeof(CreateIssueRequest))] -[JsonDerivedType(typeof(InstallationsRequest))] -[JsonDerivedType(typeof(TokenRequest))] -public interface IGithubRequest -{ - /// - /// The kind of request method for the request. - /// - [JsonIgnore] - public HttpMethod RequestMethod { get; } - - /// - /// There are different types of authentication methods depending on which endpoint you are working with. - /// E.g. the app api endpoint mostly uses JWTs, while stuff like issue creation uses Tokens - /// - [JsonIgnore] - public GithubAuthMethod AuthenticationMethod { get; } - - /// - /// Location of the api endpoint for this request. - /// - /// Owner of the repository. - /// The repository to make the request. - /// The api location for this request. - public string GetLocation(string owner, string repository); -} - -public enum GithubAuthMethod -{ - JWT, - Token, -} diff --git a/Content.Server/Github/Requests/InstallationsRequest.cs b/Content.Server/Github/Requests/InstallationsRequest.cs deleted file mode 100644 index 4e75bbdc38..0000000000 --- a/Content.Server/Github/Requests/InstallationsRequest.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Net.Http; - -namespace Content.Server.Github.Requests; - -/// -/// > -/// -public sealed class InstallationsRequest : IGithubRequest -{ - public HttpMethod RequestMethod => HttpMethod.Get; - - public GithubAuthMethod AuthenticationMethod => GithubAuthMethod.JWT; - - public string GetLocation(string owner, string repository) - { - return "app/installations"; - } -} diff --git a/Content.Server/Github/Requests/TokenRequest.cs b/Content.Server/Github/Requests/TokenRequest.cs deleted file mode 100644 index f07764cdf0..0000000000 --- a/Content.Server/Github/Requests/TokenRequest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Net.Http; -using System.Text.Json.Serialization; - -namespace Content.Server.Github.Requests; - -/// -/// > -/// -public sealed class TokenRequest : IGithubRequest -{ - public HttpMethod RequestMethod => HttpMethod.Post; - - public GithubAuthMethod AuthenticationMethod => GithubAuthMethod.JWT; - - [JsonPropertyName("id")] - public required int InstallationId; - - public string GetLocation(string owner, string repository) - { - return $"/app/installations/{InstallationId}/access_tokens"; - } -} diff --git a/Content.Server/Github/Responses/InstallationResponse.cs b/Content.Server/Github/Responses/InstallationResponse.cs deleted file mode 100644 index ffc84a6f0c..0000000000 --- a/Content.Server/Github/Responses/InstallationResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Content.Server.Github.Responses; - -/// -/// Not all fields are filled out - only the necessary ones. If you need more just add them. -/// > -/// -public sealed class InstallationResponse -{ - public required int Id { get; set; } - - public required GithubInstallationAccount Account { get; set; } -} - -/// -public sealed class GithubInstallationAccount -{ - public required string Login { get; set; } -} - diff --git a/Content.Server/Github/Responses/TokenResponse.cs b/Content.Server/Github/Responses/TokenResponse.cs deleted file mode 100644 index 5b3748219c..0000000000 --- a/Content.Server/Github/Responses/TokenResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Content.Server.Github.Responses; - -/// -/// Not all fields are filled out - only the necessary ones. If you need more just add them. -/// > -/// -public sealed class TokenResponse -{ - public required string Token { get; set; } - - [JsonPropertyName("expires_at")] - public required DateTime ExpiresAt { get; set; } -} diff --git a/Content.Server/Github/RetryHttpHandler.cs b/Content.Server/Github/RetryHttpHandler.cs deleted file mode 100644 index 28dcff643b..0000000000 --- a/Content.Server/Github/RetryHttpHandler.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; -using System.Threading; -using System.Net; - -namespace Content.Server.Github; - -/// -/// Basic rate limiter for the GitHub api! Will ensure there is only ever one outgoing request at a time and all -/// requests respect the rate limit the best they can. -///
-///
Links to the api for more information: -///
Best practices -///
Rate limit information -///
-/// This was designed for the 2022-11-28 version of the API. -public sealed class RetryHandler(HttpMessageHandler innerHandler, int maxRetries, ISawmill sawmill) : DelegatingHandler(innerHandler) -{ - private const int MaxWaitSeconds = 32; - - /// Extra buffer time (In seconds) after getting rate limited we don't make the request exactly when we get more credits. - private const long ExtraBufferTime = 1L; - - #region Headers - - private const string RetryAfterHeader = "retry-after"; - - private const string RemainingHeader = "x-ratelimit-remaining"; - private const string RateLimitResetHeader = "x-ratelimit-reset"; - - #endregion - - protected override async Task SendAsync( - HttpRequestMessage request, - CancellationToken cancellationToken - ) - { - HttpResponseMessage response; - var i = 0; - do - { - response = await base.SendAsync(request, cancellationToken); - if (response.IsSuccessStatusCode) - return response; - - i++; - if (i < maxRetries) - { - var waitTime = CalculateNextRequestTime(response, i); - await Task.Delay(waitTime, cancellationToken); - } - } while (!response.IsSuccessStatusCode && i < maxRetries); - - return response; - } - - /// - /// Follows these guidelines but also has a small buffer so you should never quite hit zero: - ///
- /// - ///
- /// The last response from the API. - /// Number of current call attempt. - /// The amount of time to wait until the next request. - private TimeSpan CalculateNextRequestTime(HttpResponseMessage response, int attempt) - { - var headers = response.Headers; - var statusCode = response.StatusCode; - - // Specific checks for rate limits. - if (statusCode is HttpStatusCode.Forbidden or HttpStatusCode.TooManyRequests) - { - // Retry after header - if (GithubClient.TryGetHeaderAsLong(headers, RetryAfterHeader, out var retryAfterSeconds)) - return TimeSpan.FromSeconds(retryAfterSeconds.Value + ExtraBufferTime); - - // Reset header (Tells us when we get more api credits) - if (GithubClient.TryGetHeaderAsLong(headers, RemainingHeader, out var remainingRequests) - && GithubClient.TryGetHeaderAsLong(headers, RateLimitResetHeader, out var resetTime) - && remainingRequests == 0) - { - var delayTime = resetTime.Value - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - sawmill.Warning( - "github returned '{status}' status, have to wait until limit reset - in '{delay}' seconds", - response.StatusCode, - delayTime - ); - return TimeSpan.FromSeconds(delayTime + ExtraBufferTime); - } - } - - // If the status code is not the expected one or the rate limit checks are failing, just do an exponential backoff. - return ExponentialBackoff(attempt); - } - - private static TimeSpan ExponentialBackoff(int i) - { - return TimeSpan.FromSeconds(Math.Min(MaxWaitSeconds, Math.Pow(2, i))); - } -} diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 96124330f0..b4d999bef4 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -3,7 +3,6 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Administration.Notes; using Content.Server.Afk; -using Content.Server.BugReports; using Content.Server.Chat.Managers; using Content.Server.Connection; using Content.Server.Database; @@ -12,7 +11,6 @@ using Content.Server.Discord.DiscordLink; using Content.Server.Discord.WebhookMessages; using Content.Server.EUI; using Content.Server.GhostKick; -using Content.Server.Github; using Content.Server.Info; using Content.Server.Mapping; using Content.Server.Maps; @@ -61,7 +59,6 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); @@ -79,11 +76,9 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); } } } diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 388c44bb28..58a41a5f7a 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -464,7 +464,6 @@ public enum LogType /// Logs related to botany, such as planting and harvesting crops /// Botany = 100, - /// /// Artifact node got activated. /// @@ -479,9 +478,4 @@ public enum LogType /// Events relating to midi playback. /// Instrument = 103, - - /// - /// For anything relating to bug reports. - /// - BugReport = 104, } diff --git a/Content.Shared/BugReport/BugReportMessage.cs b/Content.Shared/BugReport/BugReportMessage.cs deleted file mode 100644 index 46976eda6e..0000000000 --- a/Content.Shared/BugReport/BugReportMessage.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Lidgren.Network; -using Robust.Shared.Network; -using Robust.Shared.Serialization; - -namespace Content.Shared.BugReport; - -/// -/// Message with bug report data, which should be handled by server and used to create issue on issue tracker -/// (or some other notification). -/// -public sealed class BugReportMessage : NetMessage -{ - public override MsgGroups MsgGroup => MsgGroups.Command; - - public PlayerBugReportInformation ReportInformation = new(); - - public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) - { - ReportInformation.BugReportTitle = buffer.ReadString(); - ReportInformation.BugReportDescription = buffer.ReadString(); - } - - public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) - { - buffer.Write(ReportInformation.BugReportTitle); - buffer.Write(ReportInformation.BugReportDescription); - } - - public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered; -} - -/// -/// Stores user specified information from a bug report. -/// -/// -/// Clients can put whatever they want here so be careful! -/// -public sealed class PlayerBugReportInformation -{ - public string BugReportTitle = string.Empty; - public string BugReportDescription = string.Empty; -} diff --git a/Content.Shared/CCVar/CCVars.BugReports.cs b/Content.Shared/CCVar/CCVars.BugReports.cs deleted file mode 100644 index 789ffc9a48..0000000000 --- a/Content.Shared/CCVar/CCVars.BugReports.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Robust.Shared.Configuration; - -namespace Content.Shared.CCVar; - -public sealed partial class CCVars -{ - /// - /// Allow users to submit bug reports. Will enable a button on the hotbar. See for - /// setting up the GitHub API! - /// - public static readonly CVarDef EnablePlayerBugReports = - CVarDef.Create("bug_reports.enable_player_bug_reports", false, CVar.SERVER | CVar.REPLICATED); - - /// - /// Minimum playtime that players need to have played to submit bug reports. - /// - public static readonly CVarDef MinimumPlaytimeInMinutesToEnableBugReports = - CVarDef.Create("bug_reports.minimum_playtime_in_minutes_to_enable_bug_reports", 120, CVar.SERVER | CVar.REPLICATED); - - /// - /// Maximum number of bug reports a user can submit per round. - /// - public static readonly CVarDef MaximumBugReportsPerRound = - CVarDef.Create("bug_reports.maximum_bug_reports_per_round", 5, CVar.SERVER | CVar.REPLICATED); - - /// - /// Minimum time between bug reports. - /// - public static readonly CVarDef MinimumSecondsBetweenBugReports = - CVarDef.Create("bug_reports.minimum_seconds_between_bug_reports", 120, CVar.SERVER | CVar.REPLICATED); - - /// - /// Maximum length of a bug report title. - /// - public static readonly CVarDef MaximumBugReportTitleLength = - CVarDef.Create("bug_reports.maximum_bug_report_title_length", 35, CVar.SERVER | CVar.REPLICATED); - - /// - /// Minimum length of a bug report title. - /// - public static readonly CVarDef MinimumBugReportTitleLength = - CVarDef.Create("bug_reports.minimum_bug_report_title_length", 10, CVar.SERVER | CVar.REPLICATED); - - /// - /// Maximum length of a bug report description. - /// - public static readonly CVarDef MaximumBugReportDescriptionLength = - CVarDef.Create("bug_reports.maximum_bug_report_description_length", 750, CVar.SERVER | CVar.REPLICATED); - - /// - /// Minimum length of a bug report description. - /// - public static readonly CVarDef MinimumBugReportDescriptionLength = - CVarDef.Create("bug_reports.minimum_bug_report_description_length", 10, CVar.SERVER | CVar.REPLICATED); - - /// - /// List of tags that are added to the report. Separate each value with ",". - /// - /// - /// IG report, Bug - /// - public static readonly CVarDef BugReportTags = - CVarDef.Create("bug_reports.tags", "IG bug report", CVar.SERVER | CVar.REPLICATED); -} diff --git a/Content.Shared/CCVar/CCVars.Github.cs b/Content.Shared/CCVar/CCVars.Github.cs deleted file mode 100644 index 77c1ffc2fe..0000000000 --- a/Content.Shared/CCVar/CCVars.Github.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Robust.Shared.Configuration; - -namespace Content.Shared.CCVar; - -public sealed partial class CCVars -{ - /// - /// Marker, for if the GitHub api is enabled. If it is not enabled, any actions that require GitHub API will be ignored. - /// To fully set up the API, you also need to set , , - /// and . - /// - public static readonly CVarDef GithubEnabled = - CVarDef.Create("github.github_enabled", true, CVar.SERVERONLY); - - /// - /// GitHub app private keys location. PLEASE READ THIS CAREFULLY!! - /// - /// - /// Its highly recommend to create a new (private) repository specifically for this app. This will help avoid - /// moderation issues and also allow you to ignore duplicate or useless issues. You can just transfer legitimate - /// issues from the private repository to the main public one. - /// - /// - /// Only create the auth token with the MINIMUM required access (Specifically only give it access to one - /// repository - and the minimum required access for your use case). - ///

If this token is only for forwarding issues then you should only need to grant read and write - /// permission to "Issues" and read only permissions to "Metadata". - ///
- ///
- /// Also remember to use the testgithubapi command to test if you set everything up correctly. - /// [Insert YouTube video link with walkthrough here] - ///
- /// - /// (If your on linux): /home/beck/key.pem - /// - public static readonly CVarDef GithubAppPrivateKeyPath = - CVarDef.Create("github.github_app_private_key_path", "", CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /// - /// The GitHub apps app id. Go to https://github.com/settings/apps/APPNAME to find the app id. - /// - /// - /// 1009555 - /// - public static readonly CVarDef GithubAppId = - CVarDef.Create("github.github_app_id", "", CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /// - /// Name of the targeted GitHub repository. - /// - /// - /// If your URL was https://github.com/space-wizards/space-station-14 the repo name would be "space-station-14". - /// > - public static readonly CVarDef GithubRepositoryName = - CVarDef.Create("github.github_repository_name", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /// - /// Owner of the GitHub repository. - /// - /// - /// If your URL was https://github.com/space-wizards/space-station-14 the owner would be "space-wizards". - /// - public static readonly CVarDef GithubRepositoryOwner = - CVarDef.Create("github.github_repository_owner", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /// - /// The maximum number of times the api will retry requests before giving up. - /// - public static readonly CVarDef GithubMaxRetries = - CVarDef.Create("github.github_max_retries", 3, CVar.SERVERONLY | CVar.CONFIDENTIAL); -} diff --git a/Resources/Locale/en-US/HUD/game-hud.ftl b/Resources/Locale/en-US/HUD/game-hud.ftl index d88403a6af..ea423f080a 100644 --- a/Resources/Locale/en-US/HUD/game-hud.ftl +++ b/Resources/Locale/en-US/HUD/game-hud.ftl @@ -7,4 +7,3 @@ game-hud-open-crafting-menu-button-tooltip = Open crafting menu. game-hud-open-actions-menu-button-tooltip = Open actions menu. game-hud-open-admin-menu-button-tooltip = Open admin menu. game-hud-open-sandbox-menu-button-tooltip = Open sandbox menu. -game-hud-open-bug-report-window-button-tooltip = Open bug report menu. diff --git a/Resources/Locale/en-US/bugreport/bug-report-report.ftl b/Resources/Locale/en-US/bugreport/bug-report-report.ftl deleted file mode 100644 index c6296c520f..0000000000 --- a/Resources/Locale/en-US/bugreport/bug-report-report.ftl +++ /dev/null @@ -1 +0,0 @@ -bug-report-report-unknown = unknown diff --git a/Resources/Locale/en-US/bugreport/bug-report-window.ftl b/Resources/Locale/en-US/bugreport/bug-report-window.ftl deleted file mode 100644 index 794014ca98..0000000000 --- a/Resources/Locale/en-US/bugreport/bug-report-window.ftl +++ /dev/null @@ -1,13 +0,0 @@ -bug-report-window-name = Create bug report -bug-report-window-explanation = Try to be as detailed as possible. If you have recreation steps, list them! -bug-report-window-disabled-not-enabled = Bug reports are currently disabled! -bug-report-window-disabled-playtime = You do not have enough playtime to submit a bug report! -bug-report-window-disabled-cooldown = You can submit a new bug report in {$time}. -bug-report-window-disabled-submissions = You have reached the maximum number of bug reports ({$num}) for this round. -bug-report-window-title-place-holder = Bug report title -bug-report-window-description-place-holder = Type bug report here -bug-report-window-submit-button-text = Submit -bug-report-window-submit-button-confirm-text = Click again to submit! -bug-report-window-submit-button-disclaimer = Your SS14 username and other in game information will be saved. - -bug-report-window-submit-char-split = {$typed}/{$total} diff --git a/Resources/Locale/en-US/github/github-api.ftl b/Resources/Locale/en-US/github/github-api.ftl deleted file mode 100644 index c439503b60..0000000000 --- a/Resources/Locale/en-US/github/github-api.ftl +++ /dev/null @@ -1,36 +0,0 @@ -github-command-test-name = testgithubapi - -cmd-testgithubapi-desc = This command makes an issue request to the github api. Remember to check the servers console for errors. -cmd-testgithubapi-help = Usage: testgithubapi - -github-command-not-enabled = The api is not enabled! -github-command-no-path = The key path is empty! -github-command-no-app-id = The app id is empty! -github-command-no-repo-name = The repository name is empty! -github-command-no-owner = The repository owner is empty! - -github-command-issue-title-one = This is a test issue! -github-command-issue-description-one = This is the description of the first issue. :) - -github-command-finish = Check your repository for a newly created issue. If you don't see any, check the server console for errors! - -github-issue-format = ## Description: - {$description} - - ## Meta Data: - Build version: {$buildVersion} - Engine version: {$engineVersion} - - Server name: {$serverName} - Submitted time: {$submittedTime} - - -- Round information -- - Round number: {$roundNumber} - Round time: {$roundTime} - Round type: {$roundType} - Map: {$map} - Number of players: {$numberOfPlayers} - - -- Submitter information -- - Player name: {$username} - Player GUID: {$playerGUID} diff --git a/Resources/Textures/Interface/bug.svg b/Resources/Textures/Interface/bug.svg deleted file mode 100644 index f79bfe3e04..0000000000 --- a/Resources/Textures/Interface/bug.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/Resources/Textures/Interface/bug.svg.192dpi.png b/Resources/Textures/Interface/bug.svg.192dpi.png deleted file mode 100644 index d901996bd6..0000000000 Binary files a/Resources/Textures/Interface/bug.svg.192dpi.png and /dev/null differ diff --git a/Resources/Textures/Interface/bug.svg.192dpi.png.yml b/Resources/Textures/Interface/bug.svg.192dpi.png.yml deleted file mode 100644 index 5c43e23305..0000000000 --- a/Resources/Textures/Interface/bug.svg.192dpi.png.yml +++ /dev/null @@ -1,2 +0,0 @@ -sample: - filter: true diff --git a/Resources/Textures/Interface/splat.svg b/Resources/Textures/Interface/splat.svg deleted file mode 100644 index 5a267c9d96..0000000000 --- a/Resources/Textures/Interface/splat.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Resources/Textures/Interface/splat.svg.192dpi.png b/Resources/Textures/Interface/splat.svg.192dpi.png deleted file mode 100644 index 9540031f04..0000000000 Binary files a/Resources/Textures/Interface/splat.svg.192dpi.png and /dev/null differ diff --git a/Resources/Textures/Interface/splat.svg.192dpi.png.yml b/Resources/Textures/Interface/splat.svg.192dpi.png.yml deleted file mode 100644 index dabd6601f7..0000000000 --- a/Resources/Textures/Interface/splat.svg.192dpi.png.yml +++ /dev/null @@ -1,2 +0,0 @@ -sample: - filter: true