From 265bb1fbf660692459bfcd64b612772812fc7312 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 4 Feb 2024 21:00:55 -0600 Subject: [PATCH 01/38] Fix broken xml tag --- Plugins/ProfanityDeterment/ProfanityDeterment.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 009d1d49..8334a992 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -1,7 +1,7 @@  - Library + Library net6.0 From 1dd125c5797b068e25588909d718aab4bfcb34fa Mon Sep 17 00:00:00 2001 From: yfbsei <61809667+yfbsei@users.noreply.github.com> Date: Tue, 13 Feb 2024 05:32:16 -0500 Subject: [PATCH 02/38] feat: add basic integration support for IW6x (#320) Co-authored-by: RaidMax Co-authored-by: Edo --- GameFiles/GameInterface/_integration_iw6.gsc | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 GameFiles/GameInterface/_integration_iw6.gsc diff --git a/GameFiles/GameInterface/_integration_iw6.gsc b/GameFiles/GameInterface/_integration_iw6.gsc new file mode 100644 index 00000000..f98bd9ca --- /dev/null +++ b/GameFiles/GameInterface/_integration_iw6.gsc @@ -0,0 +1,58 @@ +Init() +{ + thread Setup(); +} + +Setup() +{ + level endon( "game_ended" ); + waittillframeend; + + level waittill( level.notifyTypes.sharedFunctionsInitialized ); + + scripts\_integration_base::RegisterLogger( ::Log2Console ); + + level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired; + level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper; + level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper; + level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper; + level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper; + level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout; + + level notify( level.notifyTypes.gameFunctionsInitialized ); +} + +GetTotalShotsFired() +{ + return maps\mp\_utility::getPlayerStat( "mostshotsfired" ); +} + +SetDvarIfUninitializedWrapper( dvar, value ) +{ + SetDvarIfUninitialized( dvar, value ); +} + +WaitillNotifyOrTimeoutWrapper( _notify, timeout ) +{ + common_scripts\utility::waittill_notify_or_timeout( _notify, timeout ); +} + +Log2Console( logLevel, message ) +{ + Print( "[" + logLevel + "] " + message + "\n" ); +} + +IsBotWrapper( client ) +{ + return IsBot( client ); +} + +GetXuidWrapper() +{ + return self GetXUID(); +} + +WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 ) +{ + return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 ); +} From d325993670cc9f755d60320db810054c468f03f9 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 17 Feb 2024 15:27:38 -0600 Subject: [PATCH 03/38] Add missing permission check to interaction controller --- .../Controllers/InteractionController.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/WebfrontCore/Controllers/InteractionController.cs b/WebfrontCore/Controllers/InteractionController.cs index 8610219a..16a69c91 100644 --- a/WebfrontCore/Controllers/InteractionController.cs +++ b/WebfrontCore/Controllers/InteractionController.cs @@ -7,28 +7,27 @@ using SharedLibraryCore.Interfaces; namespace WebfrontCore.Controllers; -public class InteractionController : BaseController +public class InteractionController(IManager manager, IInteractionRegistration interactionRegistration) + : BaseController(manager) { - private readonly IInteractionRegistration _interactionRegistration; - - public InteractionController(IManager manager, IInteractionRegistration interactionRegistration) : base(manager) - { - _interactionRegistration = interactionRegistration; - } - [HttpGet("[controller]/[action]/{interactionName}")] public async Task Render([FromRoute]string interactionName, CancellationToken token) { - var interactionData = (await _interactionRegistration.GetInteractions(interactionName, token: token)).FirstOrDefault(); + var interactionData = (await interactionRegistration.GetInteractions(interactionName, token: token)).FirstOrDefault(); if (interactionData is null) { return NotFound(); } + if (Client.Level < interactionData.MinimumPermission) + { + return Unauthorized(); + } + ViewBag.Title = interactionData.Description; var meta = HttpContext.Request.Query.ToDictionary(key => key.Key, value => value.Value.ToString()); - var result = await _interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, meta: meta, token: token); + var result = await interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, meta: meta, token: token); if (interactionData.InteractionType == InteractionType.TemplateContent) { From b2865489068848143e57cb8e821849d321a666d8 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 17 Feb 2024 15:33:15 -0600 Subject: [PATCH 04/38] Add server status received event including raw response --- Application/RConParsers/BaseRConParser.cs | 1 + Application/RConParsers/StatusResponse.cs | 13 +++++++------ .../Events/Server/ServerStatusReceiveEvent.cs | 9 +++++++++ .../Events/IGameServerEventSubscriptions.cs | 6 ++++++ SharedLibraryCore/Interfaces/IStatusResponse.cs | 7 ++++++- SharedLibraryCore/Utilities.cs | 9 ++++++++- 6 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 SharedLibraryCore/Events/Server/ServerStatusReceiveEvent.cs diff --git a/Application/RConParsers/BaseRConParser.cs b/Application/RConParsers/BaseRConParser.cs index cf773dd1..ba238e6e 100644 --- a/Application/RConParsers/BaseRConParser.cs +++ b/Application/RConParsers/BaseRConParser.cs @@ -175,6 +175,7 @@ namespace IW4MAdmin.Application.RConParsers return new StatusResponse { + RawResponse = response, Clients = ClientsFromStatus(response).ToArray(), Map = GetValueFromStatus(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus), GameType = GetValueFromStatus(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus), diff --git a/Application/RConParsers/StatusResponse.cs b/Application/RConParsers/StatusResponse.cs index bd0a52b9..fdf21521 100644 --- a/Application/RConParsers/StatusResponse.cs +++ b/Application/RConParsers/StatusResponse.cs @@ -6,10 +6,11 @@ namespace IW4MAdmin.Application.RConParsers /// public class StatusResponse : IStatusResponse { - public string Map { get; set; } - public string GameType { get; set; } - public string Hostname { get; set; } - public int? MaxClients { get; set; } - public EFClient[] Clients { get; set; } + public string Map { get; init; } + public string GameType { get; init; } + public string Hostname { get; init; } + public int? MaxClients { get; init; } + public EFClient[] Clients { get; init; } + public string[] RawResponse { get; set; } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Events/Server/ServerStatusReceiveEvent.cs b/SharedLibraryCore/Events/Server/ServerStatusReceiveEvent.cs new file mode 100644 index 00000000..977b38a1 --- /dev/null +++ b/SharedLibraryCore/Events/Server/ServerStatusReceiveEvent.cs @@ -0,0 +1,9 @@ +using SharedLibraryCore.Interfaces; + +namespace SharedLibraryCore.Events.Server; + +public class ServerStatusReceiveEvent : GameServerEvent +{ + public IStatusResponse Response { get; set; } + public string RawData { get; set; } +} diff --git a/SharedLibraryCore/Interfaces/Events/IGameServerEventSubscriptions.cs b/SharedLibraryCore/Interfaces/Events/IGameServerEventSubscriptions.cs index d2b72a33..3ceabea8 100644 --- a/SharedLibraryCore/Interfaces/Events/IGameServerEventSubscriptions.cs +++ b/SharedLibraryCore/Interfaces/Events/IGameServerEventSubscriptions.cs @@ -73,6 +73,11 @@ public interface IGameServerEventSubscriptions /// static event Func ServerValueSetCompleted; + /// + /// Raised when a server's status response is received + /// + static event Func ServerStatusReceived; + static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token) { return coreEvent switch @@ -88,6 +93,7 @@ public interface IGameServerEventSubscriptions ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask, ServerValueSetRequestEvent serverValueSetRequestEvent => ServerValueSetRequested?.InvokeAsync(serverValueSetRequestEvent, token) ?? Task.CompletedTask, ServerValueSetCompleteEvent serverValueSetCompleteEvent => ServerValueSetCompleted?.InvokeAsync(serverValueSetCompleteEvent, token) ?? Task.CompletedTask, + ServerStatusReceiveEvent serverStatusReceiveEvent => ServerStatusReceived?.InvokeAsync(serverStatusReceiveEvent, token) ?? Task.CompletedTask, _ => Task.CompletedTask }; } diff --git a/SharedLibraryCore/Interfaces/IStatusResponse.cs b/SharedLibraryCore/Interfaces/IStatusResponse.cs index 6f53b2ff..800b68b1 100644 --- a/SharedLibraryCore/Interfaces/IStatusResponse.cs +++ b/SharedLibraryCore/Interfaces/IStatusResponse.cs @@ -31,5 +31,10 @@ namespace SharedLibraryCore.Interfaces /// active clients /// EFClient[] Clients { get; } + + /// + /// raw text data from the game server + /// + string[] RawResponse { get; } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 6995f270..9ef0681b 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -868,7 +868,14 @@ namespace SharedLibraryCore { try { - return await server.RconParser.GetStatusAsync(server.RemoteConnection, token); + var response = await server.RconParser.GetStatusAsync(server.RemoteConnection, token); + + server.Manager.QueueEvent(new ServerStatusReceiveEvent + { + Response = response + }); + + return response; } catch (TaskCanceledException) From 0f135337a956e8043e6c45d60e8813d7648d0b92 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 17 Feb 2024 15:38:48 -0600 Subject: [PATCH 05/38] Revert primary constructor change --- WebfrontCore/Controllers/InteractionController.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/WebfrontCore/Controllers/InteractionController.cs b/WebfrontCore/Controllers/InteractionController.cs index 16a69c91..a006feae 100644 --- a/WebfrontCore/Controllers/InteractionController.cs +++ b/WebfrontCore/Controllers/InteractionController.cs @@ -7,13 +7,19 @@ using SharedLibraryCore.Interfaces; namespace WebfrontCore.Controllers; -public class InteractionController(IManager manager, IInteractionRegistration interactionRegistration) - : BaseController(manager) +public class InteractionController : BaseController { + private readonly IInteractionRegistration _interactionRegistration; + + public InteractionController(IManager manager, IInteractionRegistration interactionRegistration) : base(manager) + { + _interactionRegistration = interactionRegistration; + } + [HttpGet("[controller]/[action]/{interactionName}")] public async Task Render([FromRoute]string interactionName, CancellationToken token) { - var interactionData = (await interactionRegistration.GetInteractions(interactionName, token: token)).FirstOrDefault(); + var interactionData = (await _interactionRegistration.GetInteractions(interactionName, token: token)).FirstOrDefault(); if (interactionData is null) { @@ -27,7 +33,7 @@ public class InteractionController(IManager manager, IInteractionRegistration in ViewBag.Title = interactionData.Description; var meta = HttpContext.Request.Query.ToDictionary(key => key.Key, value => value.Value.ToString()); - var result = await interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, meta: meta, token: token); + var result = await _interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, meta: meta, token: token); if (interactionData.InteractionType == InteractionType.TemplateContent) { From aa83d88c7748441100a8a5eef0a73c5128bd9ef6 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 24 Feb 2024 18:07:34 -0600 Subject: [PATCH 06/38] Add command execution backoff/timeout --- Application/IW4MServer.cs | 41 ++++++++++++++++++- SharedLibraryCore/PartialEntities/EFClient.cs | 5 +++ SharedLibraryCore/Utilities.cs | 8 ++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index bbde0c41..3e4dc228 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -25,6 +25,7 @@ using Serilog.Context; using static SharedLibraryCore.Database.Models.EFClient; using Data.Models; using Data.Models.Server; +using Humanizer; using IW4MAdmin.Application.Alerts; using IW4MAdmin.Application.Commands; using IW4MAdmin.Application.Plugin.Script; @@ -193,18 +194,54 @@ namespace IW4MAdmin Command command = null; if (E.Type == GameEvent.EventType.Command) { + if (E.Origin is not null) + { + var canExecute = true; + + if (E.Origin.CommandExecutionAttempts > 0) + { + var remainingTimeout = + E.Origin.LastCommandExecutionAttempt + + Utilities.GetExponentialBackoffDelay(E.Origin.CommandExecutionAttempts) - + DateTimeOffset.UtcNow; + + if (remainingTimeout.TotalSeconds > 0) + { + if (E.Origin.CommandExecutionAttempts < 2 || + E.Origin.CommandExecutionAttempts % 5 == 0) + { + E.Origin.Tell(_translationLookup["COMMANDS_BACKOFF_MESSAGE"] + .FormatExt(remainingTimeout.Humanize())); + } + + canExecute = false; + } + else + { + E.Origin.CommandExecutionAttempts = 0; + } + } + + E.Origin.LastCommandExecutionAttempt = DateTimeOffset.UtcNow; + E.Origin.CommandExecutionAttempts++; + + if (!canExecute) + { + return; + } + } + try { command = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration(), _commandConfiguration); } - catch (CommandException e) { ServerLogger.LogWarning(e, "Error validating command from event {@Event}", new { E.Type, E.Data, E.Message, E.Subtype, E.IsRemote, E.CorrelationId }); E.FailReason = GameEvent.EventFailReason.Invalid; } - + if (command != null) { E.Extra = command; diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index c5c764f4..64282eeb 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -120,6 +120,11 @@ namespace SharedLibraryCore.Database.Models [NotMapped] public string TimeSinceLastConnectionString => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture(); + public DateTimeOffset LastCommandExecutionAttempt { get; set; } = DateTimeOffset.MinValue; + + [NotMapped] + public int CommandExecutionAttempts { get; set; } + [NotMapped] // this is kinda dirty, but I need localizable level names public ClientPermission ClientPermission => new ClientPermission diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 9ef0681b..1ea1736a 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -1339,6 +1339,14 @@ namespace SharedLibraryCore return serviceCollection; } + + public static TimeSpan GetExponentialBackoffDelay(int retryCount, int staticDelay = 5) + { + var maxTimeout = TimeSpan.FromMinutes(2.1); + const double factor = 2.0; + var delay = Math.Min(staticDelay + Math.Pow(factor, retryCount - 1), maxTimeout.TotalSeconds); + return TimeSpan.FromSeconds(delay); + } public static void ExecuteAfterDelay(TimeSpan duration, Func action, CancellationToken token = default) => ExecuteAfterDelay((int)duration.TotalMilliseconds, action, token); From 27f299c932ea015d2ba5af9405e15a07e364a5f3 Mon Sep 17 00:00:00 2001 From: Edo Date: Tue, 9 Apr 2024 20:38:17 +0200 Subject: [PATCH 07/38] fix(s1x: parser): fix inconsistency in the parser of ex-XLabs clients This is backed up by an update on the s1-mod client that will be deployed in 10 minutes following an announcement telling every server owner to update --- Plugins/ScriptPlugins/ParserS1x.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/ScriptPlugins/ParserS1x.js b/Plugins/ScriptPlugins/ParserS1x.js index 14baa548..e7b5f998 100644 --- a/Plugins/ScriptPlugins/ParserS1x.js +++ b/Plugins/ScriptPlugins/ParserS1x.js @@ -16,7 +16,7 @@ var plugin = { rconParser.Configuration.CommandPrefixes.Kick = 'kickClient {0} "{1}"'; rconParser.Configuration.CommandPrefixes.Ban = 'kickClient {0} "{1}"'; rconParser.Configuration.CommandPrefixes.TempBan = 'kickClient {0} "{1}"'; - rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint'; + rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n'; rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n?(?:latched: \\"(.+)?\\"\\n?)?(.*)$'; rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$'; rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *'; From 1f825965826e88abb38da222ed1d8336afb7e5b8 Mon Sep 17 00:00:00 2001 From: xerxes-at <5236639+xerxes-at@users.noreply.github.com> Date: Tue, 7 May 2024 16:29:37 +0200 Subject: [PATCH 08/38] Fix increment / decrement switch. (#325) * Fix increment / decrement switch. Replace 'up' with 'increment' as it got renamed on the GSC part some time ago. --- Plugins/ScriptPlugins/GameInterface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/ScriptPlugins/GameInterface.js b/Plugins/ScriptPlugins/GameInterface.js index 19cf64db..ab2b3a2a 100644 --- a/Plugins/ScriptPlugins/GameInterface.js +++ b/Plugins/ScriptPlugins/GameInterface.js @@ -298,7 +298,7 @@ const plugin = { const parsedValue = parseInt(event.data['value']); const key = event.data['key'].toString(); if (!isNaN(parsedValue)) { - event.data['direction'] = 'up' ? + event.data['direction'] == 'increment' ? (await metaService.incrementPersistentMeta(key, parsedValue, clientId, token)).result : (await metaService.decrementPersistentMeta(key, parsedValue, clientId, token)).result; } From 34af7a332c0d39e848a292872e3e644eb1faa963 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 10:19:06 -0500 Subject: [PATCH 09/38] Update projects to .net 8 (#326) * Update codebase to target .NET 8.0 and improve JSON serialization This commit switches our target framework from .NET 6.0 to .NET 8.0 and replaces Newtonsoft.Json with System.Text.Json for serialization. The JsonConverter classes have been updated to support the new JSON model and some enhancements were applied to the codebase such as fixing a command property and updating various package references. * Align with Develop * Update SharedLibraryCore package version The version of the SharedLibraryCore package reference has been updated across multiple projects from '2024.2.4.85' to '2024.2.5.9'. Meanwhile, version within SharedLibraryCore.csproj has been changed from '2024.02.04.085' to '2024.01.01.1'. Changes also include removal of .NET 8 requirement notice and reenabling of status upload to master communicator. * Update properties in IRConParser and IRConParserConfiguration to be settable The properties in the `IRConParser` and `IRConParserConfiguration` interfaces were updated to include setters. Previously, the properties in these interfaces were read-only. This change allows for the modifications and extensions of properties defined, thereby bolstering flexibility for the handling of games and parsers. * Replace RestEase with Refit in API usage Refit has been implemented as a replacement for RestEase in all API calls. As such, all related code, parameters and imports have been adjusted to function with Refit. Logic has also been added to handle certain Refit-specific behaviours. Occurrences of the RestEase package have been removed from the project. * Enable auto-redirect in HttpClient The HttpClient instance used in Application/Main.cs has been modified to automatically follow redirect responses. This was accomplished by adding "AllowAutoRedirect = true" to the HttpClientHandler used when creating the HttpClient. --------- Co-authored-by: Amos --- .../API/GameLogServer/IGameLogServer.cs | 8 +- Application/API/GameLogServer/LogInfo.cs | 13 +- Application/API/Master/ApiInstance.cs | 12 +- Application/API/Master/ApiServer.cs | 25 +- Application/API/Master/IMasterApi.cs | 133 +++--- Application/Application.csproj | 15 +- Application/IO/GameLogEventDetection.cs | 2 +- Application/IO/GameLogReaderHttp.cs | 4 +- Application/Localization/Configure.cs | 9 +- Application/Main.cs | 20 +- Application/Misc/MasterCommunication.cs | 15 +- Application/Misc/SerializationHelpers.cs | 377 +++++++++++------- Application/Plugin/Script/ScriptPlugin.cs | 1 + Application/Plugin/Script/ScriptPluginV2.cs | 1 + Data/Data.csproj | 20 +- Integrations/Cod/Integrations.Cod.csproj | 4 +- .../Source/Integrations.Source.csproj | 2 +- .../AutomessageFeed/AutomessageFeed.csproj | 4 +- Plugins/LiveRadar/LiveRadar.csproj | 4 +- Plugins/Login/Login.csproj | 4 +- Plugins/Mute/Mute.csproj | 5 +- Plugins/Mute/Plugin.cs | 3 +- .../ProfanityDeterment.csproj | 4 +- Plugins/Stats/Stats.csproj | 4 +- Plugins/Welcome/Plugin.cs | 7 +- Plugins/Welcome/Welcome.csproj | 4 +- .../Configuration/ApplicationConfiguration.cs | 2 +- .../Configuration/CommandProperties.cs | 57 ++- SharedLibraryCore/Events/CoreEvent.cs | 2 +- .../Helpers/BuildNumberJsonConverter.cs | 75 +++- SharedLibraryCore/Interfaces/IGameServer.cs | 32 +- SharedLibraryCore/Interfaces/IManager.cs | 1 + SharedLibraryCore/Interfaces/IRConParser.cs | 17 +- .../Interfaces/IRConParserConfiguration.cs | 43 +- SharedLibraryCore/Localization/Layout.cs | 7 +- SharedLibraryCore/SharedLibraryCore.csproj | 35 +- .../Controllers/ConfigurationController.cs | 11 +- WebfrontCore/WebfrontCore.csproj | 8 +- 38 files changed, 558 insertions(+), 432 deletions(-) diff --git a/Application/API/GameLogServer/IGameLogServer.cs b/Application/API/GameLogServer/IGameLogServer.cs index 302c9040..a0eb960b 100644 --- a/Application/API/GameLogServer/IGameLogServer.cs +++ b/Application/API/GameLogServer/IGameLogServer.cs @@ -1,12 +1,12 @@ using System.Threading.Tasks; -using RestEase; +using Refit; namespace IW4MAdmin.Application.API.GameLogServer { - [Header("User-Agent", "IW4MAdmin-RestEase")] + [Headers("User-Agent: IW4MAdmin-RestEase")] public interface IGameLogServer { - [Get("log/{path}/{key}")] - Task Log([Path] string path, [Path] string key); + [Get("/log/{path}/{key}")] + Task Log(string path, string key); } } diff --git a/Application/API/GameLogServer/LogInfo.cs b/Application/API/GameLogServer/LogInfo.cs index 612d4cdc..0286d096 100644 --- a/Application/API/GameLogServer/LogInfo.cs +++ b/Application/API/GameLogServer/LogInfo.cs @@ -1,19 +1,16 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; +using System.Text.Json.Serialization; namespace IW4MAdmin.Application.API.GameLogServer { public class LogInfo { - [JsonProperty("success")] + [JsonPropertyName("success")] public bool Success { get; set; } - [JsonProperty("length")] + [JsonPropertyName("length")] public int Length { get; set; } - [JsonProperty("data")] + [JsonPropertyName("data")] public string Data { get; set; } - [JsonProperty("next_key")] + [JsonPropertyName("next_key")] public string NextKey { get; set; } } } diff --git a/Application/API/Master/ApiInstance.cs b/Application/API/Master/ApiInstance.cs index 01a98baf..c70fdca4 100644 --- a/Application/API/Master/ApiInstance.cs +++ b/Application/API/Master/ApiInstance.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using SharedLibraryCore.Helpers; namespace IW4MAdmin.Application.API.Master @@ -12,32 +12,32 @@ namespace IW4MAdmin.Application.API.Master /// /// Unique ID of the instance /// - [JsonProperty("id")] + [JsonPropertyName("id")] public string Id { get; set; } /// /// Indicates how long the instance has been running /// - [JsonProperty("uptime")] + [JsonPropertyName("uptime")] public int Uptime { get; set; } /// /// Specifies the version of the instance /// - [JsonProperty("version")] + [JsonPropertyName("version")] [JsonConverter(typeof(BuildNumberJsonConverter))] public BuildNumber Version { get; set; } /// /// List of servers the instance is monitoring /// - [JsonProperty("servers")] + [JsonPropertyName("servers")] public List Servers { get; set; } /// /// Url IW4MAdmin is listening on /// - [JsonProperty("webfront_url")] + [JsonPropertyName("webfront_url")] public string WebfrontUrl { get; set; } } } diff --git a/Application/API/Master/ApiServer.cs b/Application/API/Master/ApiServer.cs index 123a5852..4c2fe3ca 100644 --- a/Application/API/Master/ApiServer.cs +++ b/Application/API/Master/ApiServer.cs @@ -1,31 +1,28 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; +using System.Text.Json.Serialization; namespace IW4MAdmin.Application.API.Master { public class ApiServer { - [JsonProperty("id")] + [JsonPropertyName("id")] public long Id { get; set; } - [JsonProperty("ip")] + [JsonPropertyName("ip")] public string IPAddress { get; set; } - [JsonProperty("port")] + [JsonPropertyName("port")] public short Port { get; set; } - [JsonProperty("version")] + [JsonPropertyName("version")] public string Version { get; set; } - [JsonProperty("gametype")] + [JsonPropertyName("gametype")] public string Gametype { get; set; } - [JsonProperty("map")] + [JsonPropertyName("map")] public string Map { get; set; } - [JsonProperty("game")] + [JsonPropertyName("game")] public string Game { get; set; } - [JsonProperty("hostname")] + [JsonPropertyName("hostname")] public string Hostname { get; set; } - [JsonProperty("clientnum")] + [JsonPropertyName("clientnum")] public int ClientNum { get; set; } - [JsonProperty("maxclientnum")] + [JsonPropertyName("maxclientnum")] public int MaxClientNum { get; set; } } } diff --git a/Application/API/Master/IMasterApi.cs b/Application/API/Master/IMasterApi.cs index c96689f7..5c628fc6 100644 --- a/Application/API/Master/IMasterApi.cs +++ b/Application/API/Master/IMasterApi.cs @@ -1,79 +1,70 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using System.Threading.Tasks; using IW4MAdmin.Application.Plugin; -using Newtonsoft.Json; -using RestEase; +using Refit; using SharedLibraryCore.Helpers; -namespace IW4MAdmin.Application.API.Master +namespace IW4MAdmin.Application.API.Master; + +public class AuthenticationId { - public class AuthenticationId - { - [JsonProperty("id")] - public string Id { get; set; } - } - - public class TokenId - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - } - - public class VersionInfo - { - [JsonProperty("current-version-stable")] - [JsonConverter(typeof(BuildNumberJsonConverter))] - public BuildNumber CurrentVersionStable { get; set; } - - [JsonProperty("current-version-prerelease")] - [JsonConverter(typeof(BuildNumberJsonConverter))] - public BuildNumber CurrentVersionPrerelease { get; set; } - } - - public class ResultMessage - { - [JsonProperty("message")] - public string Message { get; set; } - } - - public class PluginSubscriptionContent - { - public string Content { get; set; } - public PluginType Type { get; set; } - } - - - /// - /// Defines the capabilities of the master API - /// - [Header("User-Agent", "IW4MAdmin-RestEase")] - public interface IMasterApi - { - [Header("Authorization")] - string AuthorizationToken { get; set; } - - [Post("authenticate")] - Task Authenticate([Body] AuthenticationId Id); - - [Post("instance/")] - [AllowAnyStatusCode] - Task> AddInstance([Body] ApiInstance instance); - - [Put("instance/{id}")] - [AllowAnyStatusCode] - Task> UpdateInstance([Path] string id, [Body] ApiInstance instance); - - [Get("version/{apiVersion}")] - Task GetVersion([Path] int apiVersion); - - [Get("localization")] - Task> GetLocalization(); - - [Get("localization/{languageTag}")] - Task GetLocalization([Path("languageTag")] string languageTag); - - [Get("plugin_subscriptions")] - Task> GetPluginSubscription([Query("instance_id")] Guid instanceId, [Query("subscription_id")] string subscription_id); - } + [JsonPropertyName("id")] public string Id { get; set; } +} + +public class TokenId +{ + [JsonPropertyName("access_token")] public string AccessToken { get; set; } +} + +public class VersionInfo +{ + [JsonPropertyName("current-version-stable")] + [JsonConverter(typeof(BuildNumberJsonConverter))] + public BuildNumber CurrentVersionStable { get; set; } + + [JsonPropertyName("current-version-prerelease")] + [JsonConverter(typeof(BuildNumberJsonConverter))] + public BuildNumber CurrentVersionPrerelease { get; set; } +} + +public class ResultMessage +{ + [JsonPropertyName("message")] public string Message { get; set; } +} + +public class PluginSubscriptionContent +{ + public string Content { get; set; } + public PluginType Type { get; set; } +} + +/// +/// Defines the capabilities of the master API +/// +[Headers("User-Agent: IW4MAdmin-RestEase")] +public interface IMasterApi +{ + [Post("/authenticate")] + Task Authenticate([Body] AuthenticationId Id); + + [Post("/instance/")] + Task> AddInstance([Body] ApiInstance instance, [Header("Authorization")] string authorization); + + [Put("/instance/{id}")] + Task> UpdateInstance(string id, [Body] ApiInstance instance, [Header("Authorization")] string authorization); + + [Get("/version/{apiVersion}")] + Task GetVersion(int apiVersion); + + [Get("/localization")] + Task> GetLocalization(); + + [Get("/localization/{languageTag}")] + Task GetLocalization(string languageTag); + + [Get("/plugin_subscriptions")] + Task> GetPluginSubscription([Query("instance_id")] Guid instanceId, + [Query("subscription_id")] string subscription_id); } diff --git a/Application/Application.csproj b/Application/Application.csproj index 858f10a5..1609bc45 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 false RaidMax.IW4MAdmin.Application 2020.0.0.0 @@ -21,20 +21,21 @@ IW4MAdmin.Application false + disable - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index eca6fb8c..54b8b9f6 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -82,7 +82,7 @@ namespace IW4MAdmin.Application.IO { if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Origin) == GameEvent.EventRequiredEntity.Origin && gameEvent.Origin.NetworkId != Utilities.WORLD_ID) { - gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId);; + gameEvent.Origin = _server.GetClientsAsList().First(_client => _client.NetworkId == gameEvent.Origin?.NetworkId); } if ((gameEvent.RequiredEntity & GameEvent.EventRequiredEntity.Target) == GameEvent.EventRequiredEntity.Target) diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index a585fa89..16a4437d 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -1,5 +1,4 @@ using IW4MAdmin.Application.API.GameLogServer; -using RestEase; using SharedLibraryCore; using SharedLibraryCore.Interfaces; using System; @@ -7,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Refit; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.IO @@ -25,7 +25,7 @@ namespace IW4MAdmin.Application.IO public GameLogReaderHttp(Uri[] gameLogServerUris, IEventParser parser, ILogger logger) { _eventParser = parser; - _logServerApi = RestClient.For(gameLogServerUris[0].ToString()); + _logServerApi = RestService.For(gameLogServerUris[0].ToString()); _safeLogPath = gameLogServerUris[1].LocalPath.ToBase64UrlSafeString(); _logger = logger; } diff --git a/Application/Localization/Configure.cs b/Application/Localization/Configure.cs index b2155e97..abe5b731 100644 --- a/Application/Localization/Configure.cs +++ b/Application/Localization/Configure.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; +using System.Text.Json; using Microsoft.Extensions.Logging; using SharedLibraryCore.Configuration; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -59,11 +60,11 @@ namespace IW4MAdmin.Application.Localization var localizationDict = new Dictionary(); - foreach (string filePath in localizationFiles) + foreach (var filePath in localizationFiles) { var localizationContents = File.ReadAllText(filePath, Encoding.UTF8); - var eachLocalizationFile = Newtonsoft.Json.JsonConvert.DeserializeObject(localizationContents); - if (eachLocalizationFile == null) + var eachLocalizationFile = JsonSerializer.Deserialize(localizationContents); + if (eachLocalizationFile is null) { continue; } @@ -72,7 +73,7 @@ namespace IW4MAdmin.Application.Localization { if (!localizationDict.TryAdd(item.Key, item.Value)) { - logger.LogError("Could not add locale string {key} to localization", item.Key); + logger.LogError("Could not add locale string {Key} to localization", item.Key); } } } diff --git a/Application/Main.cs b/Application/Main.cs index bcded032..d4c3e62b 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -5,7 +5,6 @@ using IW4MAdmin.Application.Meta; using IW4MAdmin.Application.Migration; using IW4MAdmin.Application.Misc; using Microsoft.Extensions.DependencyInjection; -using RestEase; using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; @@ -39,6 +38,7 @@ using ILogger = Microsoft.Extensions.Logging.ILogger; using IW4MAdmin.Plugins.Stats.Client.Abstractions; using IW4MAdmin.Plugins.Stats.Client; using Microsoft.Extensions.Hosting; +using Refit; using Stats.Client.Abstractions; using Stats.Client; using Stats.Config; @@ -94,15 +94,6 @@ namespace IW4MAdmin.Application Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("!!!! IMPORTANT !!!!"); - Console.WriteLine("The next update of IW4MAdmin will require .NET 8."); - Console.WriteLine("This is a breaking change!"); - Console.WriteLine( - "Please update the ASP.NET Core Runtime: https://dotnet.microsoft.com/en-us/download/dotnet/8.0"); - Console.WriteLine("!!!!!!!!!!!!!!!!!!!"); - Console.ForegroundColor = ConsoleColor.Gray; - await LaunchAsync(); } @@ -451,12 +442,12 @@ namespace IW4MAdmin.Application var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl; - var httpClient = new HttpClient + var httpClient = new HttpClient(new HttpClientHandler {AllowAutoRedirect = true}) { BaseAddress = masterUri, Timeout = TimeSpan.FromSeconds(15) }; - var masterRestClient = RestClient.For(httpClient); + var masterRestClient = RestService.For(httpClient); var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig); if (appConfig == null) @@ -469,10 +460,7 @@ namespace IW4MAdmin.Application // register override level names foreach (var (key, value) in appConfig.OverridePermissionLevelNames) { - if (!Utilities.PermissionLevelOverrides.ContainsKey(key)) - { - Utilities.PermissionLevelOverrides.Add(key, value); - } + Utilities.PermissionLevelOverrides.TryAdd(key, value); } // build the dependency list diff --git a/Application/Misc/MasterCommunication.cs b/Application/Misc/MasterCommunication.cs index 76fc0904..86adc1a6 100644 --- a/Application/Misc/MasterCommunication.cs +++ b/Application/Misc/MasterCommunication.cs @@ -1,5 +1,4 @@ using IW4MAdmin.Application.API.Master; -using RestEase; using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Helpers; @@ -9,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Refit; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.Misc @@ -28,6 +28,7 @@ namespace IW4MAdmin.Application.Misc private readonly int _apiVersion = 1; private bool _firstHeartBeat = true; private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30); + private string _authorizationToken; public MasterCommunication(ILogger logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager) { @@ -128,7 +129,7 @@ namespace IW4MAdmin.Application.Misc Id = _appConfig.Id }); - _apiInstance.AuthorizationToken = $"Bearer {token.AccessToken}"; + _authorizationToken = $"Bearer {token.AccessToken}"; } var instance = new ApiInstance @@ -153,22 +154,22 @@ namespace IW4MAdmin.Application.Misc WebfrontUrl = _appConfig.WebfrontUrl }; - Response response; + IApiResponse response; if (_firstHeartBeat) { - response = await _apiInstance.AddInstance(instance); + response = await _apiInstance.AddInstance(instance, _authorizationToken); } else { - response = await _apiInstance.UpdateInstance(instance.Id, instance); + response = await _apiInstance.UpdateInstance(instance.Id, instance, _authorizationToken); _firstHeartBeat = false; } - if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK) { - _logger.LogWarning("Non success response code from master is {StatusCode}, message is {Message}", response.ResponseMessage.StatusCode, response.StringContent); + _logger.LogWarning("Non success response code from master is {StatusCode}, message is {Message}", response.StatusCode, response.Error?.Content); } } } diff --git a/Application/Misc/SerializationHelpers.cs b/Application/Misc/SerializationHelpers.cs index bfb7e0b9..9d8a5f03 100644 --- a/Application/Misc/SerializationHelpers.cs +++ b/Application/Misc/SerializationHelpers.cs @@ -1,150 +1,255 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using SharedLibraryCore; +using SharedLibraryCore; using SharedLibraryCore.Database.Models; using System; using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; using Data.Models; using static SharedLibraryCore.Database.Models.EFClient; using static SharedLibraryCore.GameEvent; -namespace IW4MAdmin.Application.Misc +namespace IW4MAdmin.Application.Misc; + +public class IPAddressConverter : JsonConverter { - class IPAddressConverter : JsonConverter + public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(IPAddress)); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return IPAddress.Parse((string)reader.Value); - } + var ipAddressString = reader.GetString(); + return IPAddress.Parse(ipAddressString); } - class IPEndPointConverter : JsonConverter + public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options) { - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(IPEndPoint)); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - IPEndPoint ep = (IPEndPoint)value; - JObject jo = new JObject(); - jo.Add("Address", JToken.FromObject(ep.Address, serializer)); - jo.Add("Port", ep.Port); - jo.WriteTo(writer); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - JObject jo = JObject.Load(reader); - IPAddress address = jo["Address"].ToObject(serializer); - int port = (int)jo["Port"]; - return new IPEndPoint(address, port); - } - } - - class ClientEntityConverter : JsonConverter - { - public override bool CanConvert(Type objectType) => objectType == typeof(EFClient); - - public override object ReadJson(JsonReader reader, Type objectType,object existingValue, JsonSerializer serializer) - { - if (reader.Value == null) - { - return null; - } - - var jsonObject = JObject.Load(reader); - - return new EFClient - { - NetworkId = (long)jsonObject["NetworkId"], - ClientNumber = (int)jsonObject["ClientNumber"], - State = Enum.Parse(jsonObject["state"].ToString()), - CurrentAlias = new EFAlias() - { - IPAddress = (int?)jsonObject["IPAddress"], - Name = jsonObject["Name"].ToString() - } - }; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var client = value as EFClient; - var jsonObject = new JObject - { - { "NetworkId", client.NetworkId }, - { "ClientNumber", client.ClientNumber }, - { "IPAddress", client.CurrentAlias?.IPAddress }, - { "Name", client.CurrentAlias?.Name }, - { "State", (int)client.State } - }; - - jsonObject.WriteTo(writer); - } - } - - class GameEventConverter : JsonConverter - { - public override bool CanConvert(Type objectType) =>objectType == typeof(GameEvent); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jsonObject = JObject.Load(reader); - - return new GameEvent - { - Type = Enum.Parse(jsonObject["Type"].ToString()), - Subtype = jsonObject["Subtype"]?.ToString(), - Source = Enum.Parse(jsonObject["Source"].ToString()), - RequiredEntity = Enum.Parse(jsonObject["RequiredEntity"].ToString()), - Data = jsonObject["Data"].ToString(), - Message = jsonObject["Message"].ToString(), - GameTime = (int?)jsonObject["GameTime"], - Origin = jsonObject["Origin"]?.ToObject(serializer), - Target = jsonObject["Target"]?.ToObject(serializer), - ImpersonationOrigin = jsonObject["ImpersonationOrigin"]?.ToObject(serializer), - IsRemote = (bool)jsonObject["IsRemote"], - Extra = null, // fix - Time = (DateTime)jsonObject["Time"], - IsBlocking = (bool)jsonObject["IsBlocking"] - }; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var gameEvent = value as GameEvent; - - var jsonObject = new JObject - { - { "Type", (int)gameEvent.Type }, - { "Subtype", gameEvent.Subtype }, - { "Source", (int)gameEvent.Source }, - { "RequiredEntity", (int)gameEvent.RequiredEntity }, - { "Data", gameEvent.Data }, - { "Message", gameEvent.Message }, - { "GameTime", gameEvent.GameTime }, - { "Origin", gameEvent.Origin != null ? JToken.FromObject(gameEvent.Origin, serializer) : null }, - { "Target", gameEvent.Target != null ? JToken.FromObject(gameEvent.Target, serializer) : null }, - { "ImpersonationOrigin", gameEvent.ImpersonationOrigin != null ? JToken.FromObject(gameEvent.ImpersonationOrigin, serializer) : null}, - { "IsRemote", gameEvent.IsRemote }, - { "Extra", gameEvent.Extra?.ToString() }, - { "Time", gameEvent.Time }, - { "IsBlocking", gameEvent.IsBlocking } - }; - - jsonObject.WriteTo(writer); - } + writer.WriteStringValue(value.ToString()); + } +} + +public class IPEndPointConverter : JsonConverter +{ + public override IPEndPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + IPAddress address = null; + var port = 0; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); + switch (propertyName) + { + case "Address": + var addressString = reader.GetString(); + address = IPAddress.Parse(addressString); + break; + case "Port": + port = reader.GetInt32(); + break; + } + } + + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + } + + return new IPEndPoint(address, port); + } + + public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString("Address", value.Address.ToString()); + writer.WriteNumber("Port", value.Port); + writer.WriteEndObject(); + } +} + +public class ClientEntityConverter : JsonConverter +{ + public override EFClient Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + long networkId = default; + int clientNumber = default; + ClientState state = default; + var currentAlias = new EFAlias(); + int? ipAddress = null; + string name = null; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); // Advance to the value. + switch (propertyName) + { + case "NetworkId": + networkId = reader.GetInt64(); + break; + case "ClientNumber": + clientNumber = reader.GetInt32(); + break; + case "State": + state = (ClientState)reader.GetInt32(); + break; + case "IPAddress": + ipAddress = reader.TokenType != JsonTokenType.Null ? reader.GetInt32() : null; + break; + case "Name": + name = reader.GetString(); + break; + } + } + } + + currentAlias.IPAddress = ipAddress; + currentAlias.Name = name; + + return new EFClient + { + NetworkId = networkId, + ClientNumber = clientNumber, + State = state, + CurrentAlias = currentAlias + }; + } + + public override void Write(Utf8JsonWriter writer, EFClient value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber("NetworkId", value.NetworkId); + writer.WriteNumber("ClientNumber", value.ClientNumber); + writer.WriteString("State", value.State.ToString()); + + if (value.CurrentAlias != null) + { + writer.WriteNumber("IPAddress", value.CurrentAlias.IPAddress ?? 0); + writer.WriteString("Name", value.CurrentAlias.Name); + } + else + { + writer.WriteNull("IPAddress"); + writer.WriteNull("Name"); + } + + writer.WriteEndObject(); + } +} + +public class GameEventConverter : JsonConverter +{ + public override GameEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + var gameEvent = new GameEvent(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); + switch (propertyName) + { + case "Type": + gameEvent.Type = (EventType)reader.GetInt32(); + break; + case "Subtype": + gameEvent.Subtype = reader.GetString(); + break; + case "Source": + gameEvent.Source = (EventSource)reader.GetInt32(); + break; + case "RequiredEntity": + gameEvent.RequiredEntity = (EventRequiredEntity)reader.GetInt32(); + break; + case "Data": + gameEvent.Data = reader.GetString(); + break; + case "Message": + gameEvent.Message = reader.GetString(); + break; + case "GameTime": + gameEvent.GameTime = reader.TokenType != JsonTokenType.Null ? reader.GetInt32() : null; + break; + case "Origin": + gameEvent.Origin = JsonSerializer.Deserialize(ref reader, options); + break; + case "Target": + gameEvent.Target = JsonSerializer.Deserialize(ref reader, options); + break; + case "ImpersonationOrigin": + gameEvent.ImpersonationOrigin = JsonSerializer.Deserialize(ref reader, options); + break; + case "IsRemote": + gameEvent.IsRemote = reader.GetBoolean(); + break; + case "Time": + gameEvent.Time = reader.GetDateTime(); + break; + case "IsBlocking": + gameEvent.IsBlocking = reader.GetBoolean(); + break; + } + } + } + + return gameEvent; + } + + public override void Write(Utf8JsonWriter writer, GameEvent value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber("Type", (int)value.Type); + writer.WriteString("Subtype", value.Subtype); + writer.WriteNumber("Source", (int)value.Source); + writer.WriteNumber("RequiredEntity", (int)value.RequiredEntity); + writer.WriteString("Data", value.Data); + writer.WriteString("Message", value.Message); + if (value.GameTime.HasValue) + { + writer.WriteNumber("GameTime", value.GameTime.Value); + } + else + { + writer.WriteNull("GameTime"); + } + + if (value.Origin != null) + { + writer.WritePropertyName("Origin"); + JsonSerializer.Serialize(writer, value.Origin, options); + } + + if (value.Target != null) + { + writer.WritePropertyName("Target"); + JsonSerializer.Serialize(writer, value.Target, options); + } + + if (value.ImpersonationOrigin != null) + { + writer.WritePropertyName("ImpersonationOrigin"); + JsonSerializer.Serialize(writer, value.ImpersonationOrigin, options); + } + + writer.WriteBoolean("IsRemote", value.IsRemote); + writer.WriteString("Time", value.Time.ToString("o")); + writer.WriteBoolean("IsBlocking", value.IsBlocking); + + writer.WriteEndObject(); } } diff --git a/Application/Plugin/Script/ScriptPlugin.cs b/Application/Plugin/Script/ScriptPlugin.cs index b5da929f..49822c11 100644 --- a/Application/Plugin/Script/ScriptPlugin.cs +++ b/Application/Plugin/Script/ScriptPlugin.cs @@ -23,6 +23,7 @@ using SharedLibraryCore.Database.Models; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Interfaces; using ILogger = Microsoft.Extensions.Logging.ILogger; +using Reference = Data.Models.Reference; namespace IW4MAdmin.Application.Plugin.Script { diff --git a/Application/Plugin/Script/ScriptPluginV2.cs b/Application/Plugin/Script/ScriptPluginV2.cs index 26edcaa2..8ac8b2a6 100644 --- a/Application/Plugin/Script/ScriptPluginV2.cs +++ b/Application/Plugin/Script/ScriptPluginV2.cs @@ -26,6 +26,7 @@ using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces.Events; using ILogger = Microsoft.Extensions.Logging.ILogger; using JavascriptEngine = Jint.Engine; +using Reference = Data.Models.Reference; namespace IW4MAdmin.Application.Plugin.Script; diff --git a/Data/Data.csproj b/Data/Data.csproj index 75cbf585..412a8aca 100644 --- a/Data/Data.csproj +++ b/Data/Data.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 Debug;Release;Prerelease AnyCPU RaidMax.IW4MAdmin.Data @@ -9,16 +9,20 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles - - - - + + + + diff --git a/Integrations/Cod/Integrations.Cod.csproj b/Integrations/Cod/Integrations.Cod.csproj index 7d10e54a..de12b577 100644 --- a/Integrations/Cod/Integrations.Cod.csproj +++ b/Integrations/Cod/Integrations.Cod.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Integrations.Cod Integrations.Cod Debug;Release;Prerelease @@ -17,7 +17,7 @@ - + diff --git a/Integrations/Source/Integrations.Source.csproj b/Integrations/Source/Integrations.Source.csproj index cd174636..b33da35b 100644 --- a/Integrations/Source/Integrations.Source.csproj +++ b/Integrations/Source/Integrations.Source.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Integrations.Source Integrations.Source Debug;Release;Prerelease diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index 1a79d2e5..4a5a0ac7 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true Latest Debug;Release;Prerelease @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index a5a50206..a1630dfd 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true true false @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index b3a92171..2a48f854 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -2,7 +2,7 @@ Library - net6.0 + net8.0 false @@ -19,7 +19,7 @@ - + diff --git a/Plugins/Mute/Mute.csproj b/Plugins/Mute/Mute.csproj index 19e6bb03..cb66c4d4 100644 --- a/Plugins/Mute/Mute.csproj +++ b/Plugins/Mute/Mute.csproj @@ -1,18 +1,17 @@ - net6.0 + net8.0 enable enable MrAmos123 Library Debug;Release;Prerelease AnyCPU - IW4MAdmin.Plugins.Mute - + diff --git a/Plugins/Mute/Plugin.cs b/Plugins/Mute/Plugin.cs index fc75ea27..d1c36815 100644 --- a/Plugins/Mute/Plugin.cs +++ b/Plugins/Mute/Plugin.cs @@ -22,7 +22,7 @@ public class Plugin : IPluginV2 public const string MuteKey = "IW4MMute"; public static IManager Manager { get; private set; } = null!; public static Server.Game[] SupportedGames { get; private set; } = Array.Empty(); - private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"}; + private static readonly string[] DisabledCommands = [nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"]; private readonly IInteractionRegistration _interactionRegistration; private readonly IRemoteCommandService _remoteCommandService; private readonly MuteManager _muteManager; @@ -37,6 +37,7 @@ public class Plugin : IPluginV2 IManagementEventSubscriptions.Load += OnLoad; IManagementEventSubscriptions.Unload += OnUnload; + IManagementEventSubscriptions.ClientStateInitialized += OnClientStateInitialized; IGameServerEventSubscriptions.ClientDataUpdated += OnClientDataUpdated; diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 8334a992..9b3452fd 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -2,7 +2,7 @@ Library - net6.0 + net8.0 RaidMax.IW4MAdmin.Plugins.ProfanityDeterment @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 7aff2f0c..7d65d949 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -2,7 +2,7 @@ Library - net6.0 + net8.0 RaidMax.IW4MAdmin.Plugins.Stats @@ -17,7 +17,7 @@ - + diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index e5189f11..30d43870 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -6,8 +6,8 @@ using SharedLibraryCore.Database.Models; using System.Linq; using Microsoft.EntityFrameworkCore; using System.Net.Http; +using System.Text.Json; using System.Threading; -using Newtonsoft.Json.Linq; using Humanizer; using Data.Abstractions; using Data.Models; @@ -108,8 +108,9 @@ public class Plugin : IPluginV2 var response = await wc.GetStringAsync(new Uri( $"http://ip-api.com/json/{ip}?lang={Utilities.CurrentLocalization.LocalizationName.Split("-").First().ToLower()}")); - var responseObj = JObject.Parse(response); - response = responseObj["country"]?.ToString(); + + var json = JsonDocument.Parse(response); + response = json.RootElement.TryGetProperty("country", out var countryElement) ? countryElement.GetString() : null; return string.IsNullOrEmpty(response) ? Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_COUNTRY"] diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index de9255ef..5e856f32 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -2,7 +2,7 @@ Library - net6.0 + net8.0 RaidMax.IW4MAdmin.Plugins.Welcome @@ -20,7 +20,7 @@ - + diff --git a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs index 4e5c9c16..092fa7e3 100644 --- a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs +++ b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json.Serialization; using Data.Models.Misc; -using Newtonsoft.Json; using SharedLibraryCore.Configuration.Attributes; using SharedLibraryCore.Interfaces; using static Data.Models.Client.EFClient; diff --git a/SharedLibraryCore/Configuration/CommandProperties.cs b/SharedLibraryCore/Configuration/CommandProperties.cs index da524910..e56c0e31 100644 --- a/SharedLibraryCore/Configuration/CommandProperties.cs +++ b/SharedLibraryCore/Configuration/CommandProperties.cs @@ -1,41 +1,40 @@ using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; +using SharedLibraryCore.Helpers; using static Data.Models.Client.EFClient; using static SharedLibraryCore.Server; -namespace SharedLibraryCore.Configuration +namespace SharedLibraryCore.Configuration; + +/// +/// Config driven command properties +/// +public class CommandProperties { /// - /// Config driven command properties + /// Specifies the command name /// - public class CommandProperties - { - /// - /// Specifies the command name - /// - public string Name { get; set; } + public string Name { get; set; } - /// - /// Alias of this command - /// - public string Alias { get; set; } + /// + /// Alias of this command + /// + public string Alias { get; set; } - /// - /// Specifies the minimum permission level needed to execute the - /// - [JsonConverter(typeof(StringEnumConverter))] - public Permission MinimumPermission { get; set; } + /// + /// Specifies the minimum permission level needed to execute the + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public Permission MinimumPermission { get; set; } - /// - /// Indicates if the command can be run by another user (impersonation) - /// - public bool AllowImpersonation { get; set; } + /// + /// Indicates if the command can be run by another user (impersonation) + /// + public bool AllowImpersonation { get; set; } - /// - /// Specifies the games supporting the functionality of the command - /// - [JsonProperty(ItemConverterType = typeof(StringEnumConverter))] - public Game[] SupportedGames { get; set; } = Array.Empty(); - } + /// + /// Specifies the games supporting the functionality of the command + /// + [JsonConverter(typeof(GameArrayJsonConverter))] + public Game[] SupportedGames { get; set; } = Array.Empty(); } diff --git a/SharedLibraryCore/Events/CoreEvent.cs b/SharedLibraryCore/Events/CoreEvent.cs index 17d44b49..1891d64e 100644 --- a/SharedLibraryCore/Events/CoreEvent.cs +++ b/SharedLibraryCore/Events/CoreEvent.cs @@ -6,7 +6,7 @@ public abstract class CoreEvent { public Guid Id { get; } = Guid.NewGuid(); public Guid? CorrelationId { get; init; } - public object Source { get; init; } + public object Source { get; set; } public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow; public DateTimeOffset? ProcessedAt { get; set; } } diff --git a/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs b/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs index cdac5943..a5aae46a 100644 --- a/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs +++ b/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs @@ -1,27 +1,58 @@ using System; -using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace SharedLibraryCore.Helpers +namespace SharedLibraryCore.Helpers; + +/// +/// JSON converter for the build number +/// +public class BuildNumberJsonConverter : JsonConverter { - /// - /// JSON converter for the build number - /// - public class BuildNumberJsonConverter : JsonConverter + public override BuildNumber Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(string); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, - JsonSerializer serializer) - { - return BuildNumber.Parse(reader.Value.ToString()); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } + var stringValue = reader.GetString(); + return BuildNumber.Parse(stringValue); } -} \ No newline at end of file + + public override void Write(Utf8JsonWriter writer, BuildNumber value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} + +public class GameArrayJsonConverter : JsonConverter +{ + public override Server.Game[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + List games = []; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + break; + } + + var gameString = reader.GetString(); + var game = Enum.Parse(gameString); + games.Add(game); + } + + return games.ToArray(); + } + + public override void Write(Utf8JsonWriter writer, Server.Game[] value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var game in value) + { + writer.WriteStringValue(game.ToString()); + } + + writer.WriteEndArray(); + } +} + diff --git a/SharedLibraryCore/Interfaces/IGameServer.cs b/SharedLibraryCore/Interfaces/IGameServer.cs index e28bd2b1..6ae70bdf 100644 --- a/SharedLibraryCore/Interfaces/IGameServer.cs +++ b/SharedLibraryCore/Interfaces/IGameServer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; using Data.Models; @@ -19,6 +20,9 @@ namespace SharedLibraryCore.Interfaces /// Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null); + IPEndPoint ResolvedIpEndPoint { get; } + IRConParser RconParser { get; } + /// /// Execute a server command /// @@ -35,72 +39,72 @@ namespace SharedLibraryCore.Interfaces /// /// Task SetDvarAsync(string name, object value, CancellationToken token = default); - + /// /// Time the most recent match ended /// DateTime? MatchEndTime { get; } - + /// /// Time the current match started /// DateTime? MatchStartTime { get; } - + /// /// List of connected clients /// IReadOnlyList ConnectedClients { get; } - + /// /// Game code corresponding to the development studio project /// Reference.Game GameCode { get; } - + /// /// Indicates if the anticheat/custom callbacks/live radar integration is enabled /// bool IsLegacyGameIntegrationEnabled { get; } - + /// /// Unique identifier for the server (typically ip:port) /// string Id { get; } - + /// /// Network address the server is listening on /// string ListenAddress { get; } - + /// /// Network port the server is listening on /// int ListenPort { get; } - + /// /// Name of the server (hostname) /// string ServerName { get; } - + /// /// Current gametype /// string Gametype { get; } - + /// /// Game password (required to join) /// string GamePassword { get; } - + /// /// Number of private client slots /// int PrivateClientSlots { get; } - + /// /// Current map the game server is running /// Map Map { get; } - + /// /// Database id for EFServer table and references /// diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index 3f0b4c6e..8805f939 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -41,6 +41,7 @@ namespace SharedLibraryCore.Interfaces ILogger GetLogger(long serverId); IList GetServers(); + List Servers { get; } IList GetCommands(); IList GetMessageTokens(); IList GetActiveClients(); diff --git a/SharedLibraryCore/Interfaces/IRConParser.cs b/SharedLibraryCore/Interfaces/IRConParser.cs index deee71d6..6316276d 100644 --- a/SharedLibraryCore/Interfaces/IRConParser.cs +++ b/SharedLibraryCore/Interfaces/IRConParser.cs @@ -15,35 +15,35 @@ namespace SharedLibraryCore.Interfaces /// /// stores the game/client specific version (usually the value of the "version" DVAR) /// - string Version { get; } + string Version { get; set; } /// /// specifies the game name (usually the internal studio iteration ie: IW4, T5 etc...) /// - Game GameName { get; } + Game GameName { get; set; } /// /// indicates if the game supports generating a log path from DVAR retrieval /// of fs_game, fs_basepath, g_log /// - bool CanGenerateLogPath { get; } + bool CanGenerateLogPath { get; set; } /// /// specifies the name of the parser /// - string Name { get; } + string Name { get; set; } /// /// specifies the type of rcon engine /// eg: COD, Source /// - string RConEngine { get; } + string RConEngine { get; set; } /// /// indicates that the game does not log to the mods folder (when mod is loaded), /// but rather always to the fs_basegame directory /// - bool IsOneLog { get; } + bool IsOneLog { get; set; } /// /// retrieves the value of a given DVAR @@ -54,7 +54,8 @@ namespace SharedLibraryCore.Interfaces /// default value to return if dvar retrieval fails /// /// - Task> GetDvarAsync(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default); + Task> GetDvarAsync(IRConConnection connection, string dvarName, T fallbackValue = default, + CancellationToken token = default); /// /// set value of DVAR by name @@ -65,7 +66,7 @@ namespace SharedLibraryCore.Interfaces /// /// Task SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default); - + /// /// executes a console command on the server /// diff --git a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs index f3da4b2c..c6473199 100644 --- a/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs +++ b/SharedLibraryCore/Interfaces/IRConParserConfiguration.cs @@ -10,74 +10,74 @@ namespace SharedLibraryCore.Interfaces /// /// stores the command format for console commands /// - CommandPrefix CommandPrefixes { get; } + CommandPrefix CommandPrefixes { get; set; } /// /// stores the regex info for parsing get status response /// - ParserRegex Status { get; } + ParserRegex Status { get; set; } /// /// stores regex info for parsing the map line from rcon status response /// - ParserRegex MapStatus { get; } + ParserRegex MapStatus { get; set; } /// /// stores regex info for parsing the gametype line from rcon status response /// - ParserRegex GametypeStatus { get; } + ParserRegex GametypeStatus { get; set; } /// /// stores regex info for parsing hostname line from rcon status response /// - ParserRegex HostnameStatus { get; } + ParserRegex HostnameStatus { get; set; } /// /// stores regex info for parsing max players line from rcon status response /// - ParserRegex MaxPlayersStatus { get; } + ParserRegex MaxPlayersStatus { get; set; } /// /// stores the regex info for parsing get DVAR responses /// - ParserRegex Dvar { get; } + ParserRegex Dvar { get; set; } /// /// stores the regex info for parsing the header of a status response /// - ParserRegex StatusHeader { get; } + ParserRegex StatusHeader { get; set; } /// /// Specifies the expected response message from rcon when the server is not running /// - string ServerNotRunningResponse { get; } + string ServerNotRunningResponse { get; set; } /// /// indicates if the application should wait for response from server /// when executing a command /// - bool WaitForResponse { get; } + bool WaitForResponse { get; set; } /// /// indicates the format expected for parsed guids /// - NumberStyles GuidNumberStyle { get; } + NumberStyles GuidNumberStyle { get; set; } /// /// specifies simple mappings for dvar names in scenarios where the needed /// information is not stored in a traditional dvar name /// - IDictionary OverrideDvarNameMapping { get; } + IDictionary OverrideDvarNameMapping { get; set; } /// /// specifies the default dvar values for games that don't support certain dvars /// - IDictionary DefaultDvarValues { get; } + IDictionary DefaultDvarValues { get; set; } /// /// contains a setup of commands that have override timeouts /// - IDictionary OverrideCommandTimeouts { get; } + IDictionary OverrideCommandTimeouts { get; set; } /// /// specifies how many lines can be used for ingame notice @@ -87,29 +87,30 @@ namespace SharedLibraryCore.Interfaces /// /// specifies how many characters can be displayed per notice line /// - int NoticeMaxCharactersPerLine { get; } + int NoticeMaxCharactersPerLine { get; set; } /// /// specifies the characters used to split a line /// - string NoticeLineSeparator { get; } + string NoticeLineSeparator { get; set; } /// /// Default port the game listens to RCon requests on /// - int? DefaultRConPort { get; } + int? DefaultRConPort { get; set; } /// /// Default Indicator of where the game is installed (ex file path or registry entry) /// - string DefaultInstallationDirectoryHint { get; } + string DefaultInstallationDirectoryHint { get; set; } - ColorCodeMapping ColorCodeMapping { get; } + ColorCodeMapping ColorCodeMapping { get; set; } + + short FloodProtectInterval { get; set; } - short FloodProtectInterval { get; } /// /// indicates if diacritics (accented characters) should be normalized /// - bool ShouldRemoveDiacritics { get; } + bool ShouldRemoveDiacritics { get; set; } } } diff --git a/SharedLibraryCore/Localization/Layout.cs b/SharedLibraryCore/Localization/Layout.cs index a4b330e7..c7f58bb6 100644 --- a/SharedLibraryCore/Localization/Layout.cs +++ b/SharedLibraryCore/Localization/Layout.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Globalization; +using System.Text.Json.Serialization; using SharedLibraryCore.Interfaces; namespace SharedLibraryCore.Localization @@ -8,6 +9,8 @@ namespace SharedLibraryCore.Localization { private string localizationName; + public Layout() { } + public Layout(Dictionary set) { LocalizationIndex = new TranslationLookup @@ -27,7 +30,7 @@ namespace SharedLibraryCore.Localization } public TranslationLookup LocalizationIndex { get; set; } - public CultureInfo Culture { get; private set; } + [JsonIgnore] public CultureInfo Culture { get; private set; } } public class TranslationLookup : ITranslationLookup @@ -47,4 +50,4 @@ namespace SharedLibraryCore.Localization } } } -} \ No newline at end of file +} diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 081458e6..d77a87a6 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -2,9 +2,9 @@ Library - net6.0 + net8.0 RaidMax.IW4MAdmin.SharedLibraryCore - 2023.4.5.1 + 2024.01.01.1 RaidMax Forever None Debug;Release;Prerelease @@ -19,7 +19,7 @@ true MIT Shared Library for IW4MAdmin - 2023.4.5.1 + 2024.01.01.1 true $(NoWarn);1591 @@ -34,31 +34,30 @@ - + - - - - - - - - - - + + + + + + + + + - - true - Data.dll - + + true + Data.dll + diff --git a/WebfrontCore/Controllers/ConfigurationController.cs b/WebfrontCore/Controllers/ConfigurationController.cs index 784f3703..876c49e5 100644 --- a/WebfrontCore/Controllers/ConfigurationController.cs +++ b/WebfrontCore/Controllers/ConfigurationController.cs @@ -10,10 +10,9 @@ using SharedLibraryCore.Interfaces; using System.Linq; using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using WebfrontCore.ViewModels; namespace WebfrontCore.Controllers @@ -92,15 +91,15 @@ namespace WebfrontCore.Controllers try { - var file = JObject.Parse(content); + var jsonDocument = JsonDocument.Parse(content); } - catch (JsonReaderException ex) + catch (JsonException ex) { return BadRequest($"{fileName}: {ex.Message}"); } - var path = System.IO.Path.Join(Utilities.OperatingDirectory, "Configuration", - fileName.Replace($"{System.IO.Path.DirectorySeparatorChar}", "")); + var path = Path.Join(Utilities.OperatingDirectory, "Configuration", + fileName.Replace($"{Path.DirectorySeparatorChar}", "")); // todo: move into a service at some point if (!System.IO.File.Exists(path)) diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index 8ce9eb47..b8502413 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true true true @@ -46,9 +46,9 @@ - - - + + + From d786d7c336b8d453bda853b262b45c0f1d01ff63 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 11:49:53 -0500 Subject: [PATCH 10/38] Update server banner plugin for .net 8 --- Plugins/ScriptPlugins/ServerBanner.js | 45 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/Plugins/ScriptPlugins/ServerBanner.js b/Plugins/ScriptPlugins/ServerBanner.js index 0beb3da1..374f9a79 100644 --- a/Plugins/ScriptPlugins/ServerBanner.js +++ b/Plugins/ScriptPlugins/ServerBanner.js @@ -9,7 +9,7 @@ const serverOrderCache = []; const plugin = { author: 'RaidMax', - version: '1.0', + version: '1.1', name: 'Server Banner', serviceResolver: null, scriptHelper: null, @@ -26,6 +26,9 @@ const plugin = { this.manager = serviceResolver.resolveService('IManager'); this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']); this.webfrontUrl = serviceResolver.resolveService('ApplicationConfiguration').webfrontUrl; + + this.logger.logInformation('{Name} {Version} by {Author} loaded,', this.name, this.version, + this.author); }, onServerMonitoringStart: function (startEvent) { @@ -123,11 +126,13 @@ const plugin = { }, }; - plugin.manager.getServers().forEach(eachServer => { - if (eachServer.id === serverId) { - server = eachServer; + const servers = plugin.manager.servers; + for (let i = 0; i < servers.length; i++) { + if (servers[i].id === serverId) { + server = servers[i]; + break; } - }); + } if (serverLocationCache[server.listenAddress] === undefined) { plugin.onServerMonitoringStart({ @@ -280,7 +285,7 @@ const plugin = {
-
${server.serverName.stripColors()}
+
${status} @@ -298,6 +303,10 @@ const plugin = {
+ `; } @@ -310,7 +319,7 @@ const plugin = { style="background: url('https://raidmax.org/resources/images/icons/games/${gameCode}.jpg');">
-
${server.serverName.stripColors()}
+
${displayIp}:${server.listenPort}
${server.throttled ? '-' : server.clientNum}/${server.maxClients} Players
@@ -324,6 +333,10 @@ const plugin = { ${status}
+ `; }; @@ -346,22 +359,24 @@ const plugin = { interactionData.scriptAction = (_, __, ___, ____, _____) => { if (Object.keys(serverOrderCache).length === 0) { - plugin.manager.getServers().forEach(server => { + for (let i = 0; i < plugin.manager.servers.length; i++) { + const server = plugin.manager.servers[i]; plugin.onServerMonitoringStart({ server: server }); - }); + } } let response = '
'; Object.keys(serverOrderCache).forEach(key => { const servers = serverOrderCache[key]; - servers.forEach(eachServer => { + for (let i = 0; i < servers.length; i++) { + const eachServer = servers[i]; response += `
${eachServer.gameCode}
- ${eachServer.serverName.stripColors()} +
@@ -387,8 +402,12 @@ const plugin = {
 width="400" height="70" style="border-width: 0; overflow: hidden;">
</iframe>
-
`; - }); +
+ `; + } }); response += ''; From b31ef6a333180c93a944523a7e7e87ab9684a666 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 11:50:45 -0500 Subject: [PATCH 11/38] Remove some extra dispose calls on base config handler --- Application/IO/BaseConfigurationHandlerV2.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Application/IO/BaseConfigurationHandlerV2.cs b/Application/IO/BaseConfigurationHandlerV2.cs index fee9d14f..07e427a9 100644 --- a/Application/IO/BaseConfigurationHandlerV2.cs +++ b/Application/IO/BaseConfigurationHandlerV2.cs @@ -71,7 +71,6 @@ public class BaseConfigurationHandlerV2 : IConfigurationHand await using var fileStream = File.OpenRead(_path); readConfiguration = await JsonSerializer.DeserializeAsync(fileStream, _serializerOptions); - await fileStream.DisposeAsync(); _watcher.Register(_path, FileUpdated); if (readConfiguration is null) @@ -131,7 +130,6 @@ public class BaseConfigurationHandlerV2 : IConfigurationHand await using var fileStream = File.Create(_path); await JsonSerializer.SerializeAsync(fileStream, configuration, _serializerOptions); - await fileStream.DisposeAsync(); _configurationInstance = configuration; } catch (Exception ex) @@ -155,7 +153,6 @@ public class BaseConfigurationHandlerV2 : IConfigurationHand await using var fileStream = File.OpenRead(_path); var readConfiguration = await JsonSerializer.DeserializeAsync(fileStream, _serializerOptions); - await fileStream.DisposeAsync(); if (readConfiguration is null) { From 4b6f3e8851f2963ef234e12610709be418855534 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 11:52:42 -0500 Subject: [PATCH 12/38] Update nuget packages --- Application/Application.csproj | 6 +++--- Data/Data.csproj | 16 ++++++++-------- SharedLibraryCore/SharedLibraryCore.csproj | 8 ++++---- WebfrontCore/WebfrontCore.csproj | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Application/Application.csproj b/Application/Application.csproj index 1609bc45..f64967e3 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -25,14 +25,14 @@
- + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Data/Data.csproj b/Data/Data.csproj index 412a8aca..9944d083 100644 --- a/Data/Data.csproj +++ b/Data/Data.csproj @@ -9,20 +9,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles - - - - + + + +
diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index d77a87a6..e3065c65 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -34,18 +34,18 @@ - + - - + + - + diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index b8502413..ebfc8196 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -47,8 +47,8 @@ - - + + From dffcae8344a9178624c2a6bed4512b6daca959fd Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 12:18:33 -0500 Subject: [PATCH 13/38] Add GameLogEvent as catch all for unhandled log lines --- Application/EventParsers/BaseEventParser.cs | 5 +++-- SharedLibraryCore/Events/Game/GameLogEvent.cs | 6 ++++++ .../Interfaces/Events/IGameEventSubscriptions.cs | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 SharedLibraryCore/Events/Game/GameLogEvent.cs diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index 3f49be57..ff05d483 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -218,12 +218,13 @@ namespace IW4MAdmin.Application.EventParsers return GenerateDefaultEvent(logLine, gameTime); } - private static GameEvent GenerateDefaultEvent(string logLine, long gameTime) + private static GameLogEvent GenerateDefaultEvent(string logLine, long gameTime) { - return new GameEvent + return new GameLogEvent { Type = GameEvent.EventType.Unknown, Data = logLine, + LogLine = logLine, Origin = Utilities.IW4MAdminClient(), Target = Utilities.IW4MAdminClient(), RequiredEntity = GameEvent.EventRequiredEntity.None, diff --git a/SharedLibraryCore/Events/Game/GameLogEvent.cs b/SharedLibraryCore/Events/Game/GameLogEvent.cs new file mode 100644 index 00000000..b6213307 --- /dev/null +++ b/SharedLibraryCore/Events/Game/GameLogEvent.cs @@ -0,0 +1,6 @@ +namespace SharedLibraryCore.Events.Game; + +public class GameLogEvent : GameEventV2 +{ + public string LogLine { get; set; } +} diff --git a/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs b/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs index b4807712..986728fb 100644 --- a/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs +++ b/SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs @@ -86,6 +86,11 @@ public interface IGameEventSubscriptions ///
static event Func ScriptEventTriggered; + /// + /// Raised when game log prints a line that is not handled by any other cases + /// + static event Func GameLogEventTriggered; + static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token) { return coreEvent switch @@ -100,6 +105,7 @@ public interface IGameEventSubscriptions ClientCommandEvent clientCommandEvent => ClientEnteredCommand?.InvokeAsync(clientCommandEvent, token) ?? Task.CompletedTask, ClientMessageEvent clientMessageEvent => ClientMessaged?.InvokeAsync(clientMessageEvent, token) ?? Task.CompletedTask, GameScriptEvent gameScriptEvent => ScriptEventTriggered?.InvokeAsync(gameScriptEvent, token) ?? Task.CompletedTask, + GameLogEvent gameLogEvent => GameLogEventTriggered?.InvokeAsync(gameLogEvent, token) ?? Task.CompletedTask, _ => Task.CompletedTask }; } @@ -116,5 +122,6 @@ public interface IGameEventSubscriptions ClientMessaged = null; ClientEnteredCommand = null; ScriptEventTriggered = null; + GameLogEventTriggered = null; } } From 1596af154875d0ea618c55206989c060e534edee Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 17:02:04 -0500 Subject: [PATCH 14/38] implement functionality to dynamically populate property values from events that inherit from GameScriptEvent --- Application/ApplicationManager.cs | 4 +- Application/EventParsers/BaseEventParser.cs | 21 ++++- .../EventParsers/DynamicEventParser.cs | 5 +- .../Factories/GameScriptEventFactory.cs | 39 ++++++++++ Application/Main.cs | 2 + Data/Models/Vector3.cs | 30 +++++++- .../LiveRadar/Controllers/RadarController.cs | 3 +- Plugins/LiveRadar/Events/LiveRadarEvent.cs | 38 --------- .../LiveRadar/Events/LiveRadarScriptEvent.cs | 19 +++++ Plugins/LiveRadar/LiveRadar.csproj | 7 +- Plugins/LiveRadar/Plugin.cs | 14 ++-- .../LiveRadar/{RadarEvent.cs => RadarDto.cs} | 34 ++++---- .../Events/Game/GameScriptEvent.cs | 77 ++++++++++++++++++- .../Interfaces/Events/IGameScriptEvent.cs | 8 ++ .../Events/IGameScriptEventFactory.cs | 6 ++ 15 files changed, 232 insertions(+), 75 deletions(-) create mode 100644 Application/Factories/GameScriptEventFactory.cs delete mode 100644 Plugins/LiveRadar/Events/LiveRadarEvent.cs create mode 100644 Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs rename Plugins/LiveRadar/{RadarEvent.cs => RadarDto.cs} (65%) create mode 100644 SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs create mode 100644 SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 98e39512..4dfda257 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -105,7 +105,7 @@ namespace IW4MAdmin.Application ConfigHandler = appConfigHandler; StartTime = DateTime.UtcNow; PageList = new PageList(); - AdditionalEventParsers = new List { new BaseEventParser(parserRegexFactory, logger, _appConfig) }; + AdditionalEventParsers = new List { new BaseEventParser(parserRegexFactory, logger, _appConfig, serviceProvider.GetRequiredService()) }; AdditionalRConParsers = new List { new BaseRConParser(serviceProvider.GetRequiredService>(), parserRegexFactory) }; TokenAuthenticator = new TokenAuthentication(); _logger = logger; @@ -710,7 +710,7 @@ namespace IW4MAdmin.Application public IEventParser GenerateDynamicEventParser(string name) { - return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration()) + return new DynamicEventParser(_parserRegexFactory, _logger, ConfigHandler.Configuration(), _serviceProvider.GetRequiredService()) { Name = name }; diff --git a/Application/EventParsers/BaseEventParser.cs b/Application/EventParsers/BaseEventParser.cs index ff05d483..105e2ec7 100644 --- a/Application/EventParsers/BaseEventParser.cs +++ b/Application/EventParsers/BaseEventParser.cs @@ -8,6 +8,7 @@ using System.Linq; using Data.Models; using Microsoft.Extensions.Logging; using SharedLibraryCore.Events.Game; +using SharedLibraryCore.Interfaces.Events; using static System.Int32; using static SharedLibraryCore.Server; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -21,16 +22,18 @@ namespace IW4MAdmin.Application.EventParsers private readonly ILogger _logger; private readonly ApplicationConfiguration _appConfig; + private readonly IGameScriptEventFactory _gameScriptEventFactory; private readonly Dictionary _regexMap; private readonly Dictionary _eventTypeMap; public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, - ApplicationConfiguration appConfig) + ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory) { _customEventRegistrations = new Dictionary)>(); _logger = logger; _appConfig = appConfig; + _gameScriptEventFactory = gameScriptEventFactory; Configuration = new DynamicEventParserConfiguration(parserRegexFactory) { @@ -183,14 +186,28 @@ namespace IW4MAdmin.Application.EventParsers if (logLine.StartsWith("GSE;")) { - return new GameScriptEvent + var gscEvent = new GameScriptEvent { ScriptData = logLine, GameTime = gameTime, Source = GameEvent.EventSource.Log }; + return gscEvent; } + var split = logLine.Split(";", StringSplitOptions.RemoveEmptyEntries); + + if (split.Length > 1) + { + var createdEvent = _gameScriptEventFactory.Create(split[0], logLine.Replace(split[0], "")); + if (createdEvent is not null) + { + createdEvent.ParseArguments(); + return createdEvent as GameEventV2; + } + } + + if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey)) { return GenerateDefaultEvent(logLine, gameTime); diff --git a/Application/EventParsers/DynamicEventParser.cs b/Application/EventParsers/DynamicEventParser.cs index 8c8c0fb3..2a6378c9 100644 --- a/Application/EventParsers/DynamicEventParser.cs +++ b/Application/EventParsers/DynamicEventParser.cs @@ -1,5 +1,6 @@ using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Interfaces.Events; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.EventParsers @@ -10,7 +11,9 @@ namespace IW4MAdmin.Application.EventParsers ///
sealed internal class DynamicEventParser : BaseEventParser { - public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig) : base(parserRegexFactory, logger, appConfig) + public DynamicEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, + ApplicationConfiguration appConfig, IGameScriptEventFactory gameScriptEventFactory) : base( + parserRegexFactory, logger, appConfig, gameScriptEventFactory) { } } diff --git a/Application/Factories/GameScriptEventFactory.cs b/Application/Factories/GameScriptEventFactory.cs new file mode 100644 index 00000000..3ffb7985 --- /dev/null +++ b/Application/Factories/GameScriptEventFactory.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using SharedLibraryCore.Interfaces.Events; + +namespace IW4MAdmin.Application.Factories; + +public class GameScriptEventFactory : IGameScriptEventFactory +{ + private readonly IServiceProvider _serviceProvider; + private readonly Dictionary _gameScriptEventMap; + + public GameScriptEventFactory(IServiceProvider serviceProvider, IEnumerable gameScriptEventTypes) + { + _serviceProvider = serviceProvider; + + _gameScriptEventMap = gameScriptEventTypes + .ToLookup(kvp => kvp.EventName ?? kvp.GetType().Name.Replace("ScriptEvent", "")) + .ToDictionary(kvp => kvp.Key, kvp => kvp.First().GetType()); + } + + public IGameScriptEvent Create(string eventType, string logData) + { + if (string.IsNullOrEmpty(eventType) || !_gameScriptEventMap.TryGetValue(eventType, out var matchedType)) + { + return null; + } + + var newEvent = _serviceProvider.GetRequiredService(matchedType) as IGameScriptEvent; + + if (newEvent is not null) + { + newEvent.ScriptData = logData; + } + + return newEvent; + } +} diff --git a/Application/Main.cs b/Application/Main.cs index d4c3e62b..0362688b 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -39,6 +39,7 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions; using IW4MAdmin.Plugins.Stats.Client; using Microsoft.Extensions.Hosting; using Refit; +using SharedLibraryCore.Interfaces.Events; using Stats.Client.Abstractions; using Stats.Client; using Stats.Config; @@ -527,6 +528,7 @@ namespace IW4MAdmin.Application .AddSingleton(new ConfigurationWatcher()) .AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>)) .AddSingleton() + .AddSingleton() .AddSingleton(translationLookup) .AddDatabaseContextOptions(appConfig); diff --git a/Data/Models/Vector3.cs b/Data/Models/Vector3.cs index 991a8385..12b20f27 100644 --- a/Data/Models/Vector3.cs +++ b/Data/Models/Vector3.cs @@ -1,12 +1,13 @@ using System; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; + // ReSharper disable CompareOfFloatsByEqualityOperator #pragma warning disable CS0659 namespace Data.Models { - public class Vector3 + public class Vector3 : IParsable { [Key] public int Vector3Id { get; set; } public float X { get; protected set; } @@ -78,7 +79,7 @@ namespace Data.Models return Math.Sqrt((dx * dx) + (dy * dy)); } - + public static double ViewAngleDistance(Vector3 a, Vector3 b, Vector3 c) { double dabX = Math.Abs(a.X - b.X); @@ -111,5 +112,30 @@ namespace Data.Models public double Magnitude() => Math.Sqrt((X * X) + (Y * Y) + (Z * Z)); public double AngleBetween(Vector3 a) => Math.Acos(this.DotProduct(a) / (a.Magnitude() * this.Magnitude())); + + public static Vector3 Parse(string s, IFormatProvider provider) + { + return Parse(s); + } + + public static bool TryParse(string s, IFormatProvider provider, out Vector3 result) + { + result = new Vector3(); + + try + { + var parsed = Parse(s); + result.X = parsed.X; + result.Y = parsed.Y; + result.Z = parsed.Z; + return true; + } + catch + { + // ignored + } + + return false; + } } } diff --git a/Plugins/LiveRadar/Controllers/RadarController.cs b/Plugins/LiveRadar/Controllers/RadarController.cs index 397277a2..d711d87a 100644 --- a/Plugins/LiveRadar/Controllers/RadarController.cs +++ b/Plugins/LiveRadar/Controllers/RadarController.cs @@ -3,7 +3,6 @@ using SharedLibraryCore; using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using System.Linq; -using System.Threading.Tasks; using IW4MAdmin.Plugins.LiveRadar.Configuration; using Microsoft.AspNetCore.Http; @@ -82,7 +81,7 @@ namespace IW4MAdmin.Plugins.LiveRadar.Web.Controllers } var radarInfo = server.GetClientsAsList() - .Select(client => client.GetAdditionalProperty("LiveRadar")).ToList(); + .Select(client => client.GetAdditionalProperty("LiveRadar")).ToList(); return Json(radarInfo); } diff --git a/Plugins/LiveRadar/Events/LiveRadarEvent.cs b/Plugins/LiveRadar/Events/LiveRadarEvent.cs deleted file mode 100644 index 7121e582..00000000 --- a/Plugins/LiveRadar/Events/LiveRadarEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using SharedLibraryCore; -using SharedLibraryCore.Database.Models; -using SharedLibraryCore.Interfaces; -using System.Collections.Generic; -using SharedLibraryCore.Events.Game; -using EventGeneratorCallback = System.ValueTuple>; - -namespace IW4MAdmin.Plugins.LiveRadar.Events; - -public class Script : IRegisterEvent -{ - private const string EventLiveRadar = "LiveRadar"; - - private EventGeneratorCallback LiveRadar() - { - return (EventLiveRadar, EventLiveRadar, (eventLine, _, _) => - { - var radarEvent = new LiveRadarEvent - { - Type = GameEvent.EventType.Other, - Subtype = EventLiveRadar, - Origin = new EFClient { NetworkId = 0 }, - ScriptData = eventLine - }; - return radarEvent; - } - ); - } - - public IEnumerable Events => new[] { LiveRadar() }; -} - -public class LiveRadarEvent : GameScriptEvent -{ -} diff --git a/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs b/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs new file mode 100644 index 00000000..4522d04c --- /dev/null +++ b/Plugins/LiveRadar/Events/LiveRadarScriptEvent.cs @@ -0,0 +1,19 @@ +using Data.Models; +using SharedLibraryCore.Events.Game; + +namespace IW4MAdmin.Plugins.LiveRadar.Events; + +public class LiveRadarScriptEvent : GameScriptEvent +{ + public string Name { get; set; } + public Vector3 Location { get; set; } + public Vector3 ViewAngles { get; set; } + public string Team { get; set; } + public int Kills { get; set; } + public int Deaths { get; set; } + public int Score { get; set; } + public string Weapon { get; set; } + public int Health { get; set; } + public bool IsAlive { get; set; } + public int PlayTime { get; set; } +} diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index a1630dfd..a7001bb1 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -15,8 +15,11 @@ - - + + + + + diff --git a/Plugins/LiveRadar/Plugin.cs b/Plugins/LiveRadar/Plugin.cs index 467359b8..61231edb 100644 --- a/Plugins/LiveRadar/Plugin.cs +++ b/Plugins/LiveRadar/Plugin.cs @@ -36,6 +36,9 @@ public class Plugin : IPluginV2 public static void RegisterDependencies(IServiceCollection serviceCollection) { serviceCollection.AddConfiguration(); + + serviceCollection.AddSingleton(); // for identification + serviceCollection.AddTransient(); // for factory } public Plugin(ILogger logger, ApplicationConfiguration appConfig) @@ -51,7 +54,7 @@ public class Plugin : IPluginV2 private Task OnScriptEvent(GameScriptEvent scriptEvent, CancellationToken token) { - if (scriptEvent is not LiveRadarEvent radarEvent) + if (scriptEvent is not LiveRadarScriptEvent radarEvent) { return Task.CompletedTask; } @@ -83,14 +86,15 @@ public class Plugin : IPluginV2 : (originalBotGuid ?? "0").ConvertGuidToLong(NumberStyles.HexNumber); } - var radarUpdate = RadarEvent.Parse(scriptEvent.ScriptData, generatedBotGuid); + var radarDto = RadarDto.FromScriptEvent(radarEvent, generatedBotGuid); + var client = - radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarUpdate.Guid); + radarEvent.Owner.ConnectedClients.FirstOrDefault(client => client.NetworkId == radarDto.Guid); if (client != null) { - radarUpdate.Name = client.Name.StripColors(); - client.SetAdditionalProperty("LiveRadar", radarUpdate); + radarDto.Name = client.Name.StripColors(); + client.SetAdditionalProperty("LiveRadar", radarDto); } } diff --git a/Plugins/LiveRadar/RadarEvent.cs b/Plugins/LiveRadar/RadarDto.cs similarity index 65% rename from Plugins/LiveRadar/RadarEvent.cs rename to Plugins/LiveRadar/RadarDto.cs index bcbe7cea..4608043a 100644 --- a/Plugins/LiveRadar/RadarEvent.cs +++ b/Plugins/LiveRadar/RadarDto.cs @@ -1,13 +1,13 @@ using Data.Models; using SharedLibraryCore; -using System; -using System.Linq; +using IW4MAdmin.Plugins.LiveRadar.Events; + // ReSharper disable CompareOfFloatsByEqualityOperator #pragma warning disable CS0659 namespace IW4MAdmin.Plugins.LiveRadar; -public class RadarEvent +public class RadarDto { public string Name { get; set; } public long Guid { get; set; } @@ -26,7 +26,7 @@ public class RadarEvent public override bool Equals(object obj) { - if (obj is RadarEvent re) + if (obj is RadarDto re) { return re.ViewAngles.X == ViewAngles.X && re.ViewAngles.Y == ViewAngles.Y && @@ -39,23 +39,21 @@ public class RadarEvent return false; } - public static RadarEvent Parse(string input, long generatedBotGuid) + public static RadarDto FromScriptEvent(LiveRadarScriptEvent scriptEvent, long generatedBotGuid) { - var items = input.Split(';').Skip(1).ToList(); - - var parsedEvent = new RadarEvent() + var parsedEvent = new RadarDto { Guid = generatedBotGuid, - Location = Vector3.Parse(items[1]), - ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(), - Team = items[3], - Kills = int.Parse(items[4]), - Deaths = int.Parse(items[5]), - Score = int.Parse(items[6]), - Weapon = items[7], - Health = int.Parse(items[8]), - IsAlive = items[9] == "1", - PlayTime = Convert.ToInt32(items[10]) + Location = scriptEvent.Location, + ViewAngles = scriptEvent.ViewAngles.FixIW4Angles(), + Team = scriptEvent.Team, + Kills = scriptEvent.Kills, + Deaths = scriptEvent.Deaths, + Score = scriptEvent.Score, + Weapon =scriptEvent.Weapon, + Health = scriptEvent.Health, + IsAlive = scriptEvent.IsAlive, + PlayTime = scriptEvent.PlayTime }; return parsedEvent; diff --git a/SharedLibraryCore/Events/Game/GameScriptEvent.cs b/SharedLibraryCore/Events/Game/GameScriptEvent.cs index c14f4bc7..b1f8af99 100644 --- a/SharedLibraryCore/Events/Game/GameScriptEvent.cs +++ b/SharedLibraryCore/Events/Game/GameScriptEvent.cs @@ -1,6 +1,77 @@ -namespace SharedLibraryCore.Events.Game; +using System; +using System.Reflection; +using SharedLibraryCore.Interfaces.Events; -public class GameScriptEvent : GameEventV2 +namespace SharedLibraryCore.Events.Game; + +public class GameScriptEvent : GameEventV2, IGameScriptEvent { - public string ScriptData { get; init; } + public string ScriptData { get; set; } + public string EventName { get; } = null; + + public virtual void ParseArguments() + { + var arguments = ScriptData.Split(';', StringSplitOptions.RemoveEmptyEntries); + + var propIndex = 0; + foreach (var argument in arguments) + { + var parts = argument.Split(['='], 2); + PropertyInfo propertyInfo = null; + string rawValue; + + if (parts.Length == 2) // handle as key/value pairs + { + var propertyName = parts[0].Trim(); + rawValue = parts[1].Trim(); + propertyInfo = GetType().GetProperty(propertyName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly); + } + else + { + rawValue = argument; + + try + { + propertyInfo = + GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | + BindingFlags.DeclaredOnly)[ + propIndex]; + } + catch + { + // ignored + } + } + + if (propertyInfo is null) + { + continue; + } + + try + { + var method = propertyInfo.PropertyType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, + [typeof(string)]); + + var convertedValue = method is not null + ? method!.Invoke(null, [rawValue])! + : Convert.ChangeType(rawValue, propertyInfo.PropertyType); + + propertyInfo.SetValue(this, convertedValue); + } + catch (TargetInvocationException ex) when (ex.InnerException is FormatException && + propertyInfo.PropertyType == typeof(bool)) + { + propertyInfo.SetValue(this, rawValue != "0"); + } + + catch + { + // ignored + } + + propIndex++; + } + } } diff --git a/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs b/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs new file mode 100644 index 00000000..b6e5458a --- /dev/null +++ b/SharedLibraryCore/Interfaces/Events/IGameScriptEvent.cs @@ -0,0 +1,8 @@ +namespace SharedLibraryCore.Interfaces.Events; + +public interface IGameScriptEvent +{ + string ScriptData { get; set; } + string EventName { get; } + void ParseArguments(); +} diff --git a/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs b/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs new file mode 100644 index 00000000..77bb71ce --- /dev/null +++ b/SharedLibraryCore/Interfaces/Events/IGameScriptEventFactory.cs @@ -0,0 +1,6 @@ +namespace SharedLibraryCore.Interfaces.Events; + +public interface IGameScriptEventFactory +{ + IGameScriptEvent Create(string eventType, string logData); +} From dbb5a9117a71305a8b48a3f9b27f4068c33dbcfc Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 22 Jun 2024 17:20:05 -0500 Subject: [PATCH 15/38] update nuget packages --- DeploymentFiles/deployment-pipeline.yml | 2 +- Plugins/AutomessageFeed/AutomessageFeed.csproj | 2 +- Plugins/LiveRadar/LiveRadar.csproj | 7 ++----- Plugins/Login/Login.csproj | 2 +- Plugins/Mute/Mute.csproj | 2 +- Plugins/ProfanityDeterment/ProfanityDeterment.csproj | 2 +- Plugins/Stats/Stats.csproj | 2 +- Plugins/Welcome/Welcome.csproj | 2 +- SharedLibraryCore/SharedLibraryCore.csproj | 2 +- 9 files changed, 10 insertions(+), 13 deletions(-) diff --git a/DeploymentFiles/deployment-pipeline.yml b/DeploymentFiles/deployment-pipeline.yml index d684acae..4f1a9b7b 100644 --- a/DeploymentFiles/deployment-pipeline.yml +++ b/DeploymentFiles/deployment-pipeline.yml @@ -32,7 +32,7 @@ jobs: displayName: 'Install .NET Core 6 SDK' inputs: packageType: 'sdk' - version: '6.0.x' + version: '8.0.x' includePreviewVersions: true - task: NuGetToolInstaller@1 diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index 4a5a0ac7..f2da7417 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index a7001bb1..272db575 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -15,11 +15,8 @@ - - - - - + + diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 2a48f854..d503ff27 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -19,7 +19,7 @@ - + diff --git a/Plugins/Mute/Mute.csproj b/Plugins/Mute/Mute.csproj index cb66c4d4..6a5af319 100644 --- a/Plugins/Mute/Mute.csproj +++ b/Plugins/Mute/Mute.csproj @@ -11,7 +11,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 9b3452fd..41265fcd 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 7d65d949..d1db9b14 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -17,7 +17,7 @@ - + diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index 5e856f32..6f55c007 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -20,7 +20,7 @@ - + diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index e3065c65..23f4a876 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -19,7 +19,7 @@ true MIT Shared Library for IW4MAdmin - 2024.01.01.1 + 2024.06.22.1 true $(NoWarn);1591 From f8f6ca2c0dd5fad4c60e33ea12ec911f20eaad0a Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 23 Jun 2024 16:13:30 -0500 Subject: [PATCH 16/38] Reduce possibility of race condition reading updated config --- Application/ApplicationManager.cs | 7 ++++++- Application/IO/ConfigurationWatcher.cs | 23 ++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 4dfda257..718b8db9 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -26,6 +26,7 @@ using Data.Abstractions; using Data.Context; using Data.Models; using IW4MAdmin.Application.Configuration; +using IW4MAdmin.Application.IO; using IW4MAdmin.Application.Migration; using IW4MAdmin.Application.Plugin.Script; using Microsoft.Extensions.DependencyInjection; @@ -68,6 +69,7 @@ namespace IW4MAdmin.Application private readonly ClientService ClientSvc; readonly PenaltyService PenaltySvc; private readonly IAlertManager _alertManager; + private readonly ConfigurationWatcher _watcher; public IConfigurationHandler ConfigHandler; readonly IPageList PageList; private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); @@ -94,7 +96,8 @@ namespace IW4MAdmin.Application IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents, ICoreEventHandler coreEventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider, - ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable v2PLugins) + ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable v2PLugins, + ConfigurationWatcher watcher) { MiddlewareActionHandler = actionHandler; _servers = new ConcurrentBag(); @@ -102,6 +105,7 @@ namespace IW4MAdmin.Application ClientSvc = clientService; PenaltySvc = penaltyService; _alertManager = alertManager; + _watcher = watcher; ConfigHandler = appConfigHandler; StartTime = DateTime.UtcNow; PageList = new PageList(); @@ -529,6 +533,7 @@ namespace IW4MAdmin.Application Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]); await InitializeServers(); + _watcher.Enable(); IsInitialized = true; } diff --git a/Application/IO/ConfigurationWatcher.cs b/Application/IO/ConfigurationWatcher.cs index 979b7e60..8df130de 100644 --- a/Application/IO/ConfigurationWatcher.cs +++ b/Application/IO/ConfigurationWatcher.cs @@ -20,7 +20,6 @@ public sealed class ConfigurationWatcher : IDisposable }; _watcher.Changed += WatcherOnChanged; - _watcher.EnableRaisingEvents = true; } public void Dispose() @@ -31,30 +30,28 @@ public sealed class ConfigurationWatcher : IDisposable public void Register(string fileName, Action fileUpdated) { - if (_registeredActions.ContainsKey(fileName)) - { - return; - } - - _registeredActions.Add(fileName, fileUpdated); + _registeredActions.TryAdd(fileName, fileUpdated); } public void Unregister(string fileName) { - if (_registeredActions.ContainsKey(fileName)) - { - _registeredActions.Remove(fileName); - } + _registeredActions.Remove(fileName); + } + + public void Enable() + { + _watcher.EnableRaisingEvents = true; } private void WatcherOnChanged(object sender, FileSystemEventArgs eventArgs) { - if (!_registeredActions.ContainsKey(eventArgs.FullPath) || eventArgs.ChangeType != WatcherChangeTypes.Changed || + if (!_registeredActions.TryGetValue(eventArgs.FullPath, out var value) || + eventArgs.ChangeType != WatcherChangeTypes.Changed || new FileInfo(eventArgs.FullPath).Length == 0) { return; } - _registeredActions[eventArgs.FullPath].Invoke(eventArgs.FullPath); + value.Invoke(eventArgs.FullPath); } } From b003ba2b75fa58c9f648d6369ad4d6c87be9f292 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 23 Jun 2024 16:27:00 -0500 Subject: [PATCH 17/38] temporary fix for pomelo ef/.net 8 query query translation --- Application/QueryHelpers/ClientResourceQueryHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/QueryHelpers/ClientResourceQueryHelper.cs b/Application/QueryHelpers/ClientResourceQueryHelper.cs index f9bff938..d760391a 100644 --- a/Application/QueryHelpers/ClientResourceQueryHelper.cs +++ b/Application/QueryHelpers/ClientResourceQueryHelper.cs @@ -91,9 +91,9 @@ public class ClientResourceQueryHelper : IResourceQueryHelper clientAlias.Key.LastConnection) : iqGroupedClientAliases.OrderBy(clientAlias => clientAlias.Key.LastConnection); - var clientIds = iqGroupedClientAliases.Select(g => g.Key.ClientId) + var clientIds = await iqGroupedClientAliases.Select(g => g.Key.ClientId) .Skip(query.Offset) - .Take(query.Count); + .Take(query.Count).ToListAsync(); // todo: this change was for a pomelo limitation and may be addressed in future version // this pulls in more records than we need, but it's more efficient than ordering grouped entities var clientLookups = await clientAliases From e6272f610adc1b188003e29645865321832e60a0 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 25 Jun 2024 20:51:03 -0500 Subject: [PATCH 18/38] Move/add client getters to ScriptPluginExtensions --- Application/Extensions/ScriptPluginExtensions.cs | 14 ++++++++++++++ SharedLibraryCore/Server.cs | 1 + 2 files changed, 15 insertions(+) diff --git a/Application/Extensions/ScriptPluginExtensions.cs b/Application/Extensions/ScriptPluginExtensions.cs index b6a2c87c..bc6fd5e7 100644 --- a/Application/Extensions/ScriptPluginExtensions.cs +++ b/Application/Extensions/ScriptPluginExtensions.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; +using Data.Models.Client; using Data.Models.Client.Stats; using Microsoft.EntityFrameworkCore; +using SharedLibraryCore.Interfaces; namespace IW4MAdmin.Application.Extensions; @@ -25,4 +27,16 @@ public static class ScriptPluginExtensions { return set.Where(stat => clientIds.Contains(stat.ClientId) && stat.ServerId == (long)serverId).ToList(); } + + public static EFClient GetClientByNumber(this IGameServer server, int clientNumber) => + server.ConnectedClients.FirstOrDefault(client => client.ClientNumber == clientNumber); + + public static EFClient GetClientByGuid(this IGameServer server, string clientGuid) => + server.ConnectedClients.FirstOrDefault(client => client?.GuidString == clientGuid?.Trim().ToLower()); + + public static EFClient GetClientByXuid(this IGameServer server, string clientGuid) => + server.ConnectedClients.FirstOrDefault(client => client?.XuidString == clientGuid?.Trim().ToLower()); + + public static EFClient GetClientByDecimalGuid(this IGameServer server, string clientGuid) => + server.ConnectedClients.FirstOrDefault(client => client.NetworkId.ToString() == clientGuid?.Trim().ToLower()); } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 096794d4..44c7ade5 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -434,6 +434,7 @@ namespace SharedLibraryCore public abstract Task GetIdForServer(Server server = null); + [Obsolete("Use the ScriptPluginExtension helper")] public EFClient GetClientByNumber(int clientNumber) => GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber); } From dc46778c2171cc27e670a264516961987cb0cd54 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 25 Jun 2024 21:19:35 -0500 Subject: [PATCH 19/38] Create shared_library_nuget.yml --- .github/workflows/shared_library_nuget.yml | 44 ++++++++++++++++++++++ SharedLibraryCore/SharedLibraryCore.csproj | 5 --- 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/shared_library_nuget.yml diff --git a/.github/workflows/shared_library_nuget.yml b/.github/workflows/shared_library_nuget.yml new file mode 100644 index 00000000..269daef0 --- /dev/null +++ b/.github/workflows/shared_library_nuget.yml @@ -0,0 +1,44 @@ +name: Build and Pack SharedLibraryCore Nuget + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +jobs: + build_pack: + + runs-on: ubuntu-latest + + env: + buildConfiguration: 'Prerelease' + + steps: + - name: Make build number + id: generate_build_number + run: | + echo "build_num=$(date +'%Y.%m.%d').$(( $(date +'%H') + $(date +'%M') + $(date +'%S') + $(date +'%3N') ))" >> "$GITHUB_OUTPUT" + - name: Display build number + run: echo "$build_num" + env: + release_name: ${{ steps.generate_build_number.build_num }} + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build Data + run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} --no-restore + - name: Build SLC + run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{env.release_name}} --no-restore + - name: Pack SLC + run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} --version-suffix ${{env.release_name}} + + - name: Publish nuget package artifact + uses: actions/upload-artifact@v3 + with: + name: SharedLibraryCore.${{env.release_name}}.nupkg + path: '**/RaidMax.IW4MAdmin.SharedLibraryCore.${{env.release_name}}.nupkg' diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 23f4a876..9104cd43 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -59,11 +59,6 @@ Data.dll - - - - - $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage From c54ff5d095a996093b2ab7849a9d4cc489d40ade Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 26 Jun 2024 18:17:23 -0500 Subject: [PATCH 20/38] disable command throttling for privileged users --- Application/IW4MServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index 3e4dc228..129f239c 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -198,7 +198,7 @@ namespace IW4MAdmin { var canExecute = true; - if (E.Origin.CommandExecutionAttempts > 0) + if (E.Origin.CommandExecutionAttempts > 0 && E.Origin.Level < Permission.Trusted) { var remainingTimeout = E.Origin.LastCommandExecutionAttempt + From 9cdb2ca63e0116653c17335184861bfef57560ac Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 26 Jun 2024 18:31:37 -0500 Subject: [PATCH 21/38] update nuget pipeline update script --- .github/workflows/build_application.yml | 188 +++++++++++++ .github/workflows/shared_library_nuget.yml | 93 +++--- Application/Application.csproj | 14 - Application/BuildScripts/PostBuild.sh | 50 ++++ DeploymentFiles/PostPublish.ps1 | 9 - DeploymentFiles/deployment-pipeline.yml | 264 ------------------ DeploymentFiles/nuget-pipeline.yml | 55 ---- IW4MAdmin.sln | 9 +- .../AutomessageFeed/AutomessageFeed.csproj | 4 - Plugins/LiveRadar/LiveRadar.csproj | 6 +- Plugins/Login/Login.csproj | 4 - Plugins/Mute/Mute.csproj | 5 +- .../ProfanityDeterment.csproj | 4 - Plugins/Stats/Stats.csproj | 4 - Plugins/Welcome/Welcome.csproj | 4 - WebfrontCore/Properties/launchSettings.json | 10 - WebfrontCore/Views/Shared/_Layout.cshtml | 2 +- WebfrontCore/WebfrontCore.csproj | 9 +- WebfrontCore/bundleconfig.json | 4 +- WebfrontCore/compilerconfig.json | 14 - WebfrontCore/compilerconfig.json.defaults | 49 ---- 21 files changed, 312 insertions(+), 489 deletions(-) create mode 100644 .github/workflows/build_application.yml create mode 100644 Application/BuildScripts/PostBuild.sh delete mode 100644 DeploymentFiles/deployment-pipeline.yml delete mode 100644 DeploymentFiles/nuget-pipeline.yml delete mode 100644 WebfrontCore/Properties/launchSettings.json delete mode 100644 WebfrontCore/compilerconfig.json delete mode 100644 WebfrontCore/compilerconfig.json.defaults diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml new file mode 100644 index 00000000..88d3dac1 --- /dev/null +++ b/.github/workflows/build_application.yml @@ -0,0 +1,188 @@ +name: Application build + +on: + push: + branches: [ develop, release/pre, master ] + paths: + - Application/** + - .github/workflows/build_application.yml + pull_request: + branches: [ develop ] + paths: + - Application/** + +env: + releaseType: prerelease + +jobs: + make_version: + runs-on: ubuntu-latest + outputs: + build_num: ${{ steps.generate_build_number.outputs.build_num }} + + steps: + - name: Make build number + id: generate_build_number + run: | + build_num=$(date +'%Y.%-m.%-d').$(date +'%3N' | sed 's/^0*//') + echo "build_num=$build_num" >> $GITHUB_OUTPUT + echo "Build number is $build_num" + + build: + runs-on: ubuntu-latest + needs: [ make_version ] + + env: + solution: IW4MAdmin.sln + buildConfiguration: Prerelease + isPreRelease: false + + buildPlatform: Any CPU + outputFolder: ${{ github.workspace }}/Publish/Prerelease + buildNumber: ${{ needs.make_version.outputs.build_num }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore NuGet packages + run: dotnet restore ${{ env.solution }} + + - name: Preload external resources + run: | + echo "Build Configuration is ${{ env.buildConfiguration }}, Release Type is ${{ env.releaseType }}" + mkdir -p WebfrontCore/wwwroot/lib/open-iconic/font/css + curl -o WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss + sed -i 's#../fonts/#/font/#g' WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss + + - name: Build webfront + run: dotnet build WebfrontCore/WebfrontCore.csproj -c ${{ env.buildConfiguration }} /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64" + + - name: Compile SCSS files + run: | + dotnet tool install Excubo.WebCompiler --global + webcompiler -r WebfrontCore/wwwroot/css/src -o WebfrontCore/wwwroot/css/ -m disable -z disable + webcompiler WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss -o WebfrontCore/wwwroot/css/ -m disable -z disable + + - name: Bundle JS files + run: | + echo 'Getting dotnet bundle' + curl -o ${{ github.workspace }}/dotnet-bundle.zip https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip + echo 'Unzipping download' + unzip ${{ github.workspace }}/dotnet-bundle.zip -d ${{ github.workspace }}/bundle + echo 'Executing dotnet-bundle' + cd ${{ github.workspace }}/bundle + dotnet dotnet-bundle.dll clean ${{ github.workspace }}/WebfrontCore/bundleconfig.json + dotnet dotnet-bundle.dll ${{ github.workspace }}/WebfrontCore/bundleconfig.json + + - name: Build plugins + run: | + cd Plugins + find . -name "*.csproj" -print0 | xargs -0 -I {} dotnet publish {} -c ${{ env.buildConfiguration }} -o ../BUILD/Plugins /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64" /p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:Version=${{ env.buildNumber }} --no-restore + + - name: Build application + run: dotnet publish Application/Application.csproj -c ${{ env.buildConfiguration }} -o ${{ env.outputFolder }} /p:Version=${{ env.buildNumber }} /p:Configuration=${{ env.buildConfiguration }} /p:Platform="x64" --no-restore + + - name: Download translations + run: | + mkdir -p "${{ env.outputFolder }}/Localization" + localizations=("en-US" "ru-RU" "es-EC" "pt-BR" "de-DE") + for localization in "${localizations[@]}" + do + url="https://master.iw4.zip/localization/$localization" + filePath="${{ env.outputFolder }}/Localization/IW4MAdmin.$localization.json" + curl -s "$url" -o "$filePath" + done + + - name: Clean up publish files + run: | + chmod +x ${{ github.workspace }}/Application/BuildScripts/PostBuild.sh + bash ${{ github.workspace }}/Application/BuildScripts/PostBuild.sh ${{ env.outputFolder }} ${{ github.workspace }} + + - name: Generate start scripts + run: | + cat << EOF > "${{ env.outputFolder }}/StartIW4MAdmin.cmd" + @echo off + @title IW4MAdmin + set DOTNET_CLI_TELEMETRY_OPTOUT=1 + dotnet Lib\IW4MAdmin.dll + pause + EOF + + cat << EOF > "${{ env.outputFolder }}/StartIW4MAdmin.sh" + #!/bin/bash + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + dotnet Lib/IW4MAdmin.dll + EOF + + - name: Move extra content into publish directory + run: | + cp ${{ github.workspace }}/Plugins/ScriptPlugins/*.js ${{ env.outputFolder }}/Plugins/ + cp ${{ github.workspace }}/BUILD/Plugins/*.dll ${{ env.outputFolder }}/Plugins/ + mkdir ${{ env.outputFolder }}/wwwroot/css + cp ${{ github.workspace }}/WebfrontCore/wwwroot/css/global.min.css ${{ env.outputFolder }}/wwwroot/css/global.min.css + mkdir ${{ env.outputFolder }}/wwwroot/js + cp ${{ github.workspace }}/WebfrontCore/wwwroot/js/global.min.js ${{ env.outputFolder }}/wwwroot/js/global.min.js + rsync -a ${{ github.workspace }}/WebfrontCore/font/ ${{ env.outputFolder }}/wwwroot/font/ + rsync -a ${{ github.workspace }}/GameFiles/ ${{ env.outputFolder }}/ + rsync -a ${{ github.workspace }}/BUILD/Plugins/wwwroot/ ${{ env.outputFolder }}/wwwroot/ + + - name: Upload artifact for analysis + uses: actions/upload-artifact@v4 + with: + name: IW4MAdmin-${{ env.buildNumber }} + path: ${{ env.outputFolder }} + + release_github: + runs-on: ubuntu-latest + needs: [ make_version, build ] + permissions: + contents: write + environment: prerelease + if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' }} + + env: + buildNumber: ${{ needs.make_version.outputs.build_num }} + + steps: + - name: Download build + uses: actions/download-artifact@v4 + with: + name: IW4MAdmin-${{ env.buildNumber }} + path: ${{ github.workspace }} + + - name: Zip build + run: zip -r IW4MAdmin-${{ env.buildNumber }}.zip ${{ github.workspace }}/* + + - name: Make release + uses: ncipollo/release-action@v1 + with: + tag: ${{ env.buildNumber }}-${{ env.releaseType }} + name: IW4MAdmin ${{ env.buildNumber }} + draft: true + prerelease: true + body: Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating) + generateReleaseNotes: true + artifacts: ${{ github.workspace }}/*.zip + artifactErrorsFailBuild: true + + update_master_version: + runs-on: ubuntu-latest + needs: [ make_version, build, release_github ] + if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' }} + + env: + buildNumber: ${{ needs.make_version.outputs.build_num }} + + steps: + - name: Update master version + run: | + curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"current-version-${{ env.releaseType }}":"${{ env.buildNumber }}","jwt-secret": "${{ secrets.JWTSecret }}"}' \ + http://api.raidmax.org:5000/version diff --git a/.github/workflows/shared_library_nuget.yml b/.github/workflows/shared_library_nuget.yml index 269daef0..d4ef51c9 100644 --- a/.github/workflows/shared_library_nuget.yml +++ b/.github/workflows/shared_library_nuget.yml @@ -1,44 +1,73 @@ -name: Build and Pack SharedLibraryCore Nuget +name: SharedLibraryCore NuGet on: push: - branches: [ "develop" ] + branches: [ develop ] + paths: + - SharedLibraryCore/** + - .github/workflows/shared_library_nuget.yml pull_request: - branches: [ "develop" ] + branches: [ develop ] + paths: + - SharedLibraryCore/** + +env: + outputDirectory: ${{ github.workspace}}/nuget + buildConfiguration: Prerelease jobs: build_pack: - runs-on: ubuntu-latest - env: - buildConfiguration: 'Prerelease' + outputs: + build_num: ${{ steps.generate_build_number.outputs.build_num }} steps: - - name: Make build number - id: generate_build_number - run: | - echo "build_num=$(date +'%Y.%m.%d').$(( $(date +'%H') + $(date +'%M') + $(date +'%S') + $(date +'%3N') ))" >> "$GITHUB_OUTPUT" - - name: Display build number - run: echo "$build_num" - env: - release_name: ${{ steps.generate_build_number.build_num }} - - uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build Data - run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} --no-restore - - name: Build SLC - run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{env.release_name}} --no-restore - - name: Pack SLC - run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} --version-suffix ${{env.release_name}} + - name: Make build number + id: generate_build_number + run: | + build_num=$(date +'%Y.%-m.%-d').$(date +'%3N' | sed 's/^0*//') + echo "build_num=$build_num" >> $GITHUB_ENV + echo "build_num=$build_num" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build Data + run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.build_num }} --no-restore + - name: Build SLC + run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.build_num }} --no-restore + - name: Pack SLC + run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} -p:PackageVersion=${{ env.build_num }} -o ${{ env.outputDirectory }} - - name: Publish nuget package artifact - uses: actions/upload-artifact@v3 - with: - name: SharedLibraryCore.${{env.release_name}}.nupkg - path: '**/RaidMax.IW4MAdmin.SharedLibraryCore.${{env.release_name}}.nupkg' + - name: Publish nuget package artifact + uses: actions/upload-artifact@v4 + with: + name: SharedLibraryCore-${{ steps.generate_build_number.outputs.build_num }} + path: ${{ env.outputDirectory }}/*.nupkg + + publish: + runs-on: ubuntu-latest + + needs: [ build_pack ] + + steps: + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + name: SharedLibraryCore-${{ needs.build_pack.outputs.build_num }} + path: ${{ env.outputDirectory }} + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Publish NuGet package + run: | + for file in ${{ env.outputDirectory }}/*.nupkg; do + dotnet nuget push "$file" --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate + done diff --git a/Application/Application.csproj b/Application/Application.csproj index f64967e3..2204fdcf 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -73,18 +73,4 @@ - - - - - - - - - - - - - - diff --git a/Application/BuildScripts/PostBuild.sh b/Application/BuildScripts/PostBuild.sh new file mode 100644 index 00000000..f855be56 --- /dev/null +++ b/Application/BuildScripts/PostBuild.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +PublishDir="$1" +SourceDir="$2" + +if [ -z "$PublishDir" ] || [ -z "$SourceDir" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Deleting extra runtime files" +declare -a runtimes=("linux-arm" "linux-arm64" "linux-armel" "osx" "osx-x64" "win-arm" "win-arm64" "alpine-x64" "linux-musl-x64") +for runtime in "${runtimes[@]}"; do + if [ -d "$PublishDir/runtimes/$runtime" ]; then + rm -rf "$PublishDir/runtimes/$runtime" + fi +done + +echo "Deleting misc files" +if [ -f "$PublishDir/web.config" ]; then rm "$PublishDir/web.config"; fi +if [ -f "$PublishDir/libman.json" ]; then rm "$PublishDir/libman.json"; fi +rm -f "$PublishDir"/*.exe +rm -f "$PublishDir"/*.pdb +rm -f "$PublishDir"/IW4MAdmin + +echo "Setting up default folders" +mkdir -p "$PublishDir/Plugins" +mkdir -p "$PublishDir/Configuration" +mv "$PublishDir/DefaultSettings.json" "$PublishDir/Configuration/" + +mkdir -p "$PublishDir/Lib" +rm -f "$PublishDir/Microsoft.CodeAnalysis*.dll" +mv "$PublishDir"/*.dll "$PublishDir/Lib/" +mv "$PublishDir"/*.json "$PublishDir/Lib/" +mv "$PublishDir/runtimes" "$PublishDir/Lib/runtimes" +mv "$PublishDir/ru" "$PublishDir/Lib/ru" +mv "$PublishDir/de" "$PublishDir/Lib/de" +mv "$PublishDir/pt" "$PublishDir/Lib/pt" +mv "$PublishDir/es" "$PublishDir/Lib/es" +rm -rf "$PublishDir/cs" +rm -rf "$PublishDir/fr" +rm -rf "$PublishDir/it" +rm -rf "$PublishDir/ja" +rm -rf "$PublishDir/ko" +rm -rf "$PublishDir/pl" +rm -rf "$PublishDir/pt-BR" +rm -rf "$PublishDir/tr" +rm -rf "$PublishDir/zh-Hans" +rm -rf "$PublishDir/zh-Hant" +if [ -d "$PublishDir/refs" ]; then mv "$PublishDir/refs" "$PublishDir/Lib/refs"; fi diff --git a/DeploymentFiles/PostPublish.ps1 b/DeploymentFiles/PostPublish.ps1 index 0bcc9bbd..2ffa4f65 100644 --- a/DeploymentFiles/PostPublish.ps1 +++ b/DeploymentFiles/PostPublish.ps1 @@ -12,12 +12,3 @@ foreach($localization in $localizations) $response = Invoke-WebRequest $url -UseBasicParsing Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8 } - -$versionInfo = (Get-Command ("{0}\IW4MAdmin.exe" -f $PublishDir)).FileVersionInfo -$json = @{ -Major = $versionInfo.ProductMajorPart -Minor = $versionInfo.ProductMinorPart -Build = $versionInfo.ProductBuildPart -Revision = $versionInfo.ProductPrivatePart -} -$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII diff --git a/DeploymentFiles/deployment-pipeline.yml b/DeploymentFiles/deployment-pipeline.yml deleted file mode 100644 index 4f1a9b7b..00000000 --- a/DeploymentFiles/deployment-pipeline.yml +++ /dev/null @@ -1,264 +0,0 @@ -name: '$(Date:yyyy.M.d)$(Rev:.r)' - -trigger: - batch: true - branches: - include: - - release/pre - - master - - develop - paths: - exclude: - - '**/*.yml' - - '*.yml' - -pr: none - -pool: - vmImage: 'windows-2022' - -variables: - solution: 'IW4MAdmin.sln' - buildPlatform: 'Any CPU' - outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)' - releaseType: verified - buildConfiguration: Stable - isPreRelease: false - -jobs: - - job: Build_Deploy - steps: - - task: UseDotNet@2 - displayName: 'Install .NET Core 6 SDK' - inputs: - packageType: 'sdk' - version: '8.0.x' - includePreviewVersions: true - - - task: NuGetToolInstaller@1 - - - task: PowerShell@2 - displayName: 'Setup Pre-Release configuration' - condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) - inputs: - targetType: 'inline' - script: | - echo '##vso[task.setvariable variable=releaseType]prerelease' - echo '##vso[task.setvariable variable=buildConfiguration]Prerelease' - echo '##vso[task.setvariable variable=isPreRelease]true' - failOnStderr: true - - - task: NuGetCommand@2 - displayName: 'Restore nuget packages' - inputs: - restoreSolution: '$(solution)' - - - task: PowerShell@2 - displayName: 'Preload external resources' - inputs: - targetType: 'inline' - script: | - Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)' - md -Force lib\open-iconic\font\css - wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss - cd lib\open-iconic\font\css - (Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss - failOnStderr: true - workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot' - - - task: VSBuild@1 - displayName: 'Build projects' - inputs: - solution: '$(solution)' - msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber) /p:PackageVersion=$(Build.BuildNumber)' - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - - - task: PowerShell@2 - displayName: 'Bundle JS Files' - inputs: - targetType: 'inline' - script: | - Write-Host 'Getting dotnet bundle' - wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip - Write-Host 'Unzipping download' - Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath) - Write-Host 'Executing dotnet-bundle' - $(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json - $(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json - failOnStderr: true - workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore' - - - task: DotNetCoreCLI@2 - displayName: 'Publish projects' - inputs: - command: 'publish' - publishWebProjects: false - projects: | - **/WebfrontCore.csproj - **/Application.csproj - arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)' - zipAfterPublish: false - modifyOutputPath: false - - - task: PowerShell@2 - displayName: 'Run publish script 1' - inputs: - filePath: 'DeploymentFiles/PostPublish.ps1' - arguments: '$(outputFolder)' - failOnStderr: true - workingDirectory: '$(Build.Repository.LocalPath)' - - - task: BatchScript@1 - displayName: 'Run publish script 2' - inputs: - filename: 'Application\BuildScripts\PostPublish.bat' - workingFolder: '$(Build.Repository.LocalPath)' - arguments: '$(outputFolder) $(Build.Repository.LocalPath)' - failOnStandardError: true - - - task: PowerShell@2 - displayName: 'Download dos2unix for line endings' - inputs: - targetType: 'inline' - script: 'wget https://raidmax.org/downloads/dos2unix.exe' - failOnStderr: true - workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts' - - - task: CmdLine@2 - displayName: 'Convert Linux start script line endings' - inputs: - script: | - echo changing to encoding for linux start script - dos2unix $(outputFolder)\StartIW4MAdmin.sh - dos2unix $(outputFolder)\UpdateIW4MAdmin.sh - echo creating website version filename - @echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt - workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts' - - - task: CopyFiles@2 - displayName: 'Move script plugins into publish directory' - inputs: - SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins' - Contents: '*.js' - TargetFolder: '$(outputFolder)\Plugins' - - - task: CopyFiles@2 - displayName: 'Move binary plugins into publish directory' - inputs: - SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\' - Contents: '*.dll' - TargetFolder: '$(outputFolder)\Plugins' - - - task: CmdLine@2 - displayName: 'Move webfront resources into publish directory' - inputs: - script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot' - workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins' - failOnStderr: true - - - task: CmdLine@2 - displayName: 'Move gamescript files into publish directory' - inputs: - script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles' - workingDirectory: '$(Build.Repository.LocalPath)' - failOnStderr: true - - - task: ArchiveFiles@2 - displayName: 'Generate final zip file' - inputs: - rootFolderOrFile: '$(outputFolder)' - includeRootFolder: false - archiveType: 'zip' - archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip' - replaceExistingArchive: true - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip' - artifact: 'IW4MAdmin-$(Build.BuildNumber).zip' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish artifact for analysis' - inputs: - targetPath: '$(outputFolder)' - artifact: 'IW4MAdmin.$(buildConfiguration)' - publishLocation: 'pipeline' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish nuget package artifact' - inputs: - targetPath: '$(Build.Repository.LocalPath)/SharedLibraryCore/bin/$(buildConfiguration)/RaidMax.IW4MAdmin.SharedLibraryCore.$(Build.BuildNumber).nupkg' - artifact: 'SharedLibraryCore.$(Build.BuildNumber).nupkg' - publishLocation: 'pipeline' - - - task: FtpUpload@2 - condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop') - displayName: 'Upload zip file to website' - inputs: - credentialsOption: 'inputs' - serverUrl: '$(FTPUrl)' - username: '$(FTPUsername)' - password: '$(FTPPassword)' - rootDirectory: '$(Build.ArtifactStagingDirectory)' - filePatterns: '*.zip' - remoteDirectory: 'IW4MAdmin/Download' - clean: false - cleanContents: false - preservePaths: false - trustSSL: false - - - task: FtpUpload@2 - condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop') - displayName: 'Upload version info to website' - inputs: - credentialsOption: 'inputs' - serverUrl: '$(FTPUrl)' - username: '$(FTPUsername)' - password: '$(FTPPassword)' - rootDirectory: '$(Build.ArtifactStagingDirectory)' - filePatterns: 'version_$(releaseType).txt' - remoteDirectory: 'IW4MAdmin' - clean: false - cleanContents: false - preservePaths: false - trustSSL: false - - - task: GitHubRelease@1 - condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop') - displayName: 'Make GitHub release' - inputs: - gitHubConnection: 'github.com_RaidMax' - repositoryName: 'RaidMax/IW4M-Admin' - action: 'create' - target: '$(Build.SourceVersion)' - tagSource: 'userSpecifiedTag' - tag: '$(Build.BuildNumber)-$(releaseType)' - title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))' - assets: '$(Build.ArtifactStagingDirectory)/*.zip' - isPreRelease: $(isPreRelease) - releaseNotesSource: 'inline' - releaseNotesInline: 'Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating)' - changeLogCompareToRelease: 'lastNonDraftRelease' - changeLogType: 'commitBased' - - - task: PowerShell@2 - condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop') - displayName: 'Update master version' - inputs: - targetType: 'inline' - script: | - $payload = @{ - 'current-version-$(releaseType)' = '$(Build.BuildNumber)' - 'jwt-secret' = '$(JWTSecret)' - } | ConvertTo-Json - - - $params = @{ - Uri = 'http://api.raidmax.org:5000/version' - Method = 'POST' - Body = $payload - ContentType = 'application/json' - } - - Invoke-RestMethod @params diff --git a/DeploymentFiles/nuget-pipeline.yml b/DeploymentFiles/nuget-pipeline.yml deleted file mode 100644 index ff7ae71e..00000000 --- a/DeploymentFiles/nuget-pipeline.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: '$(Date:yyyy.M.d)$(Rev:.r)' - -pr: none - -pool: - vmImage: 'windows-2022' - -variables: - buildPlatform: 'Any CPU' - outputFolder: '$(Build.ArtifactStagingDirectory)\Publish\$(buildConfiguration)' - releaseType: verified - buildConfiguration: Stable - isPreRelease: false - -jobs: - - job: Build_Pack - steps: - - task: PowerShell@2 - displayName: 'Setup Build configuration' - condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/chore/nuget-pipeline')) - inputs: - targetType: 'inline' - script: | - echo '##vso[task.setvariable variable=releaseType]prerelease' - echo '##vso[task.setvariable variable=buildConfiguration]Prerelease' - echo '##vso[task.setvariable variable=isPreRelease]true' - failOnStderr: true - - - task: DotNetCoreCLI@2 - displayName: 'Build Data' - inputs: - command: 'build' - projects: '**/Data.csproj' - arguments: '-c $(buildConfiguration)' - - - task: DotNetCoreCLI@2 - displayName: 'Build SLC' - inputs: - command: 'build' - projects: '**/SharedLibraryCore.csproj' - arguments: '-c $(buildConfiguration) /p:Version=$(Build.BuildNumber)' - - - task: DotNetCoreCLI@2 - displayName: 'Pack SLC' - inputs: - command: 'pack' - packagesToPack: '**/SharedLibraryCore.csproj' - versioningScheme: 'byBuildNumber' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish nuget package artifact' - inputs: - targetPath: 'D:\a\1\a\RaidMax.IW4MAdmin.SharedLibraryCore.$(Build.BuildNumber).nupkg' - artifact: 'SharedLibraryCore.$(Build.BuildNumber).nupkg' - publishLocation: 'pipeline' diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 87038b44..cdeaf1c0 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -6,13 +6,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}" ProjectSection(SolutionItems) = preProject - DeploymentFiles\deployment-pipeline.yml = DeploymentFiles\deployment-pipeline.yml DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1 README.md = README.md version.txt = version.txt DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1 DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh - DeploymentFiles\nuget-pipeline.yml = DeploymentFiles\nuget-pipeline.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}" @@ -112,6 +110,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pluto IW5", "Pluto IW5", "{ GameFiles\AntiCheat\IW5\README.MD = GameFiles\AntiCheat\IW5\README.MD EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GithubActions", "GithubActions", "{DCCEED9F-816E-4595-8B74-D76A77FBE0BE}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build_application.yml = .github\workflows\build_application.yml + .github\workflows\shared_library_nuget.yml = .github\workflows\shared_library_nuget.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -461,6 +465,7 @@ Global {3EA564BD-3AC6-479B-96B6-CB059DCD0C77} = {AB83BAC0-C539-424A-BF00-78487C10753C} {866F453D-BC89-457F-8B55-485494759B31} = {AB83BAC0-C539-424A-BF00-78487C10753C} {603725A4-BC0B-423B-955B-762C89E1C4C2} = {AB83BAC0-C539-424A-BF00-78487C10753C} + {DCCEED9F-816E-4595-8B74-D76A77FBE0BE} = {8C8F3945-0AEF-4949-A1F7-B18E952E50BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87} diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index f2da7417..73ddb14f 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -13,8 +13,4 @@ - - - - diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index 272db575..403ebd2d 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -4,7 +4,7 @@ net8.0 true true - false + false false true 0.1.0.0 @@ -19,8 +19,4 @@ - - - - diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index d503ff27..5c775b5e 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -22,8 +22,4 @@ - - - - diff --git a/Plugins/Mute/Mute.csproj b/Plugins/Mute/Mute.csproj index 6a5af319..a227b7fe 100644 --- a/Plugins/Mute/Mute.csproj +++ b/Plugins/Mute/Mute.csproj @@ -13,8 +13,5 @@ - - - - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 41265fcd..0818d044 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -19,8 +19,4 @@ - - - - diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index d1db9b14..c8fafb0a 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -20,8 +20,4 @@ - - - - diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index 6f55c007..11c3a037 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -15,10 +15,6 @@ Latest - - - - diff --git a/WebfrontCore/Properties/launchSettings.json b/WebfrontCore/Properties/launchSettings.json deleted file mode 100644 index a35479d6..00000000 --- a/WebfrontCore/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61369/", - "sslPort": 0 - } - } -} \ No newline at end of file diff --git a/WebfrontCore/Views/Shared/_Layout.cshtml b/WebfrontCore/Views/Shared/_Layout.cshtml index b4c2361c..82e2cf25 100644 --- a/WebfrontCore/Views/Shared/_Layout.cshtml +++ b/WebfrontCore/Views/Shared/_Layout.cshtml @@ -15,7 +15,7 @@ - + diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index ebfc8196..302b51bf 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -33,19 +33,15 @@ - - - - + - @@ -78,7 +74,4 @@ - - - diff --git a/WebfrontCore/bundleconfig.json b/WebfrontCore/bundleconfig.json index bbce348f..5049558e 100644 --- a/WebfrontCore/bundleconfig.json +++ b/WebfrontCore/bundleconfig.json @@ -3,9 +3,9 @@ "outputFileName": "wwwroot/css/global.min.css", "inputFiles": [ "wwwroot/lib/halfmoon/css/halfmoon-variables.min.css", - "wwwroot/css/global.css", "wwwroot/lib/chart.js/dist/Chart.min.css", - "wwwroot/css/open-iconic.css" + "wwwroot/css/open-iconic-bootstrap-override.css", + "wwwroot/css/main.css" ] }, { diff --git a/WebfrontCore/compilerconfig.json b/WebfrontCore/compilerconfig.json deleted file mode 100644 index 63588138..00000000 --- a/WebfrontCore/compilerconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "outputFile": "wwwroot/css/global.css", - "inputFile": "wwwroot/css/src/main.scss" - }, - { - "outputFile": "wwwroot/css/src/profile.css", - "inputFile": "wwwroot/css/src/profile.scss" - }, - { - "outputFile": "wwwroot/css/open-iconic.css", - "inputFile": "wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" - } -] diff --git a/WebfrontCore/compilerconfig.json.defaults b/WebfrontCore/compilerconfig.json.defaults deleted file mode 100644 index c75eb7d5..00000000 --- a/WebfrontCore/compilerconfig.json.defaults +++ /dev/null @@ -1,49 +0,0 @@ -{ - "compilers": { - "less": { - "autoPrefix": "", - "cssComb": "none", - "ieCompat": true, - "strictMath": false, - "strictUnits": false, - "relativeUrls": true, - "rootPath": "", - "sourceMapRoot": "", - "sourceMapBasePath": "", - "sourceMap": false - }, - "sass": { - "includePath": "", - "indentType": "space", - "indentWidth": 2, - "outputStyle": "nested", - "Precision": 5, - "relativeUrls": true, - "sourceMapRoot": "", - "sourceMap": false - }, - "stylus": { - "sourceMap": false - }, - "babel": { - "sourceMap": false - }, - "coffeescript": { - "bare": false, - "runtimeMode": "node", - "sourceMap": false - } - }, - "minifiers": { - "css": { - "enabled": true, - "termSemicolons": true, - "gzip": false - }, - "javascript": { - "enabled": true, - "termSemicolons": true, - "gzip": false - } - } -} \ No newline at end of file From f02431f0fa37ee0a03190568605f6bab332de882 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 28 Jun 2024 15:32:36 -0500 Subject: [PATCH 22/38] create build pipeline --- .github/workflows/build_application.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 88d3dac1..3ff3d73e 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -124,13 +124,17 @@ jobs: run: | cp ${{ github.workspace }}/Plugins/ScriptPlugins/*.js ${{ env.outputFolder }}/Plugins/ cp ${{ github.workspace }}/BUILD/Plugins/*.dll ${{ env.outputFolder }}/Plugins/ - mkdir ${{ env.outputFolder }}/wwwroot/css + mkdir -p ${{ env.outputFolder }}/wwwroot/css cp ${{ github.workspace }}/WebfrontCore/wwwroot/css/global.min.css ${{ env.outputFolder }}/wwwroot/css/global.min.css - mkdir ${{ env.outputFolder }}/wwwroot/js + mkdir -p ${{ env.outputFolder }}/wwwroot/js cp ${{ github.workspace }}/WebfrontCore/wwwroot/js/global.min.js ${{ env.outputFolder }}/wwwroot/js/global.min.js - rsync -a ${{ github.workspace }}/WebfrontCore/font/ ${{ env.outputFolder }}/wwwroot/font/ - rsync -a ${{ github.workspace }}/GameFiles/ ${{ env.outputFolder }}/ - rsync -a ${{ github.workspace }}/BUILD/Plugins/wwwroot/ ${{ env.outputFolder }}/wwwroot/ + mkdir -p ${{ env.outputFolder }}/wwwroot/font + rsync -ar ${{ github.workspace }}/WebfrontCore/wwwroot/lib/open-iconic/font/fonts/ ${{ env.outputFolder }}/wwwroot/font + mkdir -p ${{ env.outputFolder }}/GameFiles + rsync -ar ${{ github.workspace }}/GameFiles/ ${{ env.outputFolder }}/GameFiles + mkdir -p ${{ env.outputFolder }}/wwwroot/images/ + rsync -ar ${{ github.workspace }}/WebfrontCore/wwwroot/images/ ${{ env.outputFolder }}/wwwroot/images/ + rsync -ar ${{ github.workspace }}/BUILD/Plugins/wwwroot/ ${{ env.outputFolder }}/wwwroot/ - name: Upload artifact for analysis uses: actions/upload-artifact@v4 From ae1faac8ab4e8b2facf763d2788c85146997f18d Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 28 Jun 2024 23:33:21 -0500 Subject: [PATCH 23/38] fix type reference for ClientPenaltyEvent --- SharedLibraryCore/Events/Management/ClientPenaltyEvent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedLibraryCore/Events/Management/ClientPenaltyEvent.cs b/SharedLibraryCore/Events/Management/ClientPenaltyEvent.cs index 54150e6b..3a5ee100 100644 --- a/SharedLibraryCore/Events/Management/ClientPenaltyEvent.cs +++ b/SharedLibraryCore/Events/Management/ClientPenaltyEvent.cs @@ -1,5 +1,5 @@ using Data.Models; -using Data.Models.Client; +using SharedLibraryCore.Database.Models; namespace SharedLibraryCore.Events.Management; From fbfbae0d943bb466d677b2b1df56809d7571bdb5 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 28 Jun 2024 23:51:54 -0500 Subject: [PATCH 24/38] set default webfront bind url for very first startup --- SharedLibraryCore/Configuration/ApplicationConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs index 092fa7e3..e8b72a32 100644 --- a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs +++ b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs @@ -22,7 +22,7 @@ namespace SharedLibraryCore.Configuration public bool EnableWebFront { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_BIND_URL")] - public string WebfrontBindUrl { get; set; } + public string WebfrontBindUrl { get; set; } = "http://0.0.0.0:1624"; [ConfigurationOptional] [LocalizedDisplayName("WEBFRONT_CONFIGURATION_MANUAL_URL")] From eec0a210058d46f7613b190059e1709e8a05f78f Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 00:00:38 -0500 Subject: [PATCH 25/38] update application build triggers --- .github/workflows/build_application.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 3ff3d73e..23a45f55 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -5,6 +5,10 @@ on: branches: [ develop, release/pre, master ] paths: - Application/** + - WebfrontCore/** + - Data/** + - SharedLibraryCore/** + - Plugins/** - .github/workflows/build_application.yml pull_request: branches: [ develop ] From 63e1f31c21ae909c5d1a63d42820c6cb344db74b Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 11:14:09 -0500 Subject: [PATCH 26/38] fix remote plugin loading --- Application/API/Master/IMasterApi.cs | 4 ++-- Application/Misc/RemoteAssemblyHandler.cs | 2 +- Application/Plugin/PluginImporter.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Application/API/Master/IMasterApi.cs b/Application/API/Master/IMasterApi.cs index 5c628fc6..b32d73f1 100644 --- a/Application/API/Master/IMasterApi.cs +++ b/Application/API/Master/IMasterApi.cs @@ -65,6 +65,6 @@ public interface IMasterApi Task GetLocalization(string languageTag); [Get("/plugin_subscriptions")] - Task> GetPluginSubscription([Query("instance_id")] Guid instanceId, - [Query("subscription_id")] string subscription_id); + Task> GetPluginSubscription([Query] string instance_id, + [Query] string subscription_id); } diff --git a/Application/Misc/RemoteAssemblyHandler.cs b/Application/Misc/RemoteAssemblyHandler.cs index 6b7671b1..afe2ec93 100644 --- a/Application/Misc/RemoteAssemblyHandler.cs +++ b/Application/Misc/RemoteAssemblyHandler.cs @@ -55,7 +55,7 @@ namespace IW4MAdmin.Application.Misc var decryptedContent = new byte[encryptedContent.Length]; var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id), IterationCount, HashAlgorithmName.SHA512); - var encryption = new AesGcm(keyGen.GetBytes(KeyLength)); + var encryption = new AesGcm(keyGen.GetBytes(KeyLength),TagLength); try { diff --git a/Application/Plugin/PluginImporter.cs b/Application/Plugin/PluginImporter.cs index b3292096..df3a54f6 100644 --- a/Application/Plugin/PluginImporter.cs +++ b/Application/Plugin/PluginImporter.cs @@ -167,7 +167,7 @@ namespace IW4MAdmin.Application.Plugin try { _pluginSubscription ??= _masterApi - .GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; + .GetPluginSubscription(_appConfig.Id, _appConfig.SubscriptionId).Result; return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription .Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray()); @@ -185,7 +185,7 @@ namespace IW4MAdmin.Application.Plugin try { _pluginSubscription ??= _masterApi - .GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; + .GetPluginSubscription(_appConfig.Id, _appConfig.SubscriptionId).Result; return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription .Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray()); From bcad270aaa6004a590032725b88d0f7fe531923b Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 14:38:06 -0500 Subject: [PATCH 27/38] update nuget action to require approval and set specific suffix depending on pr or push --- .github/workflows/build_application.yml | 4 ++ .github/workflows/shared_library_nuget.yml | 44 +++++++++++++++------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 23a45f55..8c3b4c49 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -14,6 +14,10 @@ on: branches: [ develop ] paths: - Application/** + - WebfrontCore/** + - Data/** + - SharedLibraryCore/** + - Plugins/** env: releaseType: prerelease diff --git a/.github/workflows/shared_library_nuget.yml b/.github/workflows/shared_library_nuget.yml index d4ef51c9..2956a48b 100644 --- a/.github/workflows/shared_library_nuget.yml +++ b/.github/workflows/shared_library_nuget.yml @@ -2,23 +2,23 @@ name: SharedLibraryCore NuGet on: push: - branches: [ develop ] + branches: [ develop, release/pre, master ] paths: - SharedLibraryCore/** + - Data/** - .github/workflows/shared_library_nuget.yml pull_request: branches: [ develop ] paths: - SharedLibraryCore/** + - Data/** env: outputDirectory: ${{ github.workspace}}/nuget - buildConfiguration: Prerelease jobs: - build_pack: + make_version: runs-on: ubuntu-latest - outputs: build_num: ${{ steps.generate_build_number.outputs.build_num }} @@ -27,38 +27,56 @@ jobs: id: generate_build_number run: | build_num=$(date +'%Y.%-m.%-d').$(date +'%3N' | sed 's/^0*//') - echo "build_num=$build_num" >> $GITHUB_ENV echo "build_num=$build_num" >> $GITHUB_OUTPUT + echo "Build number is $build_num" + + build_pack: + runs-on: ubuntu-latest + needs: [ make_version ] + + env: + buildNumber: ${{ needs.make_version.outputs.build_num }} + packageTag: ${{ github.event_name == 'pull_request' && -beta || -preview }} + buildConfiguration: Prerelease + + steps: - uses: actions/checkout@v4 + - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - name: Restore dependencies run: dotnet restore - - name: Build Data - run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.build_num }} --no-restore + + - name: Build data + run: dotnet build **/Data.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.buildNumber }} --no-restore + - name: Build SLC - run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.build_num }} --no-restore + run: dotnet build **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} /p:Version=${{ env.buildNumber }} --no-restore + - name: Pack SLC - run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} -p:PackageVersion=${{ env.build_num }} -o ${{ env.outputDirectory }} + run: dotnet pack **/SharedLibraryCore.csproj -c ${{env.buildConfiguration}} -p:PackageVersion=${{ env.buildNumber }}${{ env.packageTag }} -o ${{ env.outputDirectory }} --no-restore - name: Publish nuget package artifact uses: actions/upload-artifact@v4 with: - name: SharedLibraryCore-${{ steps.generate_build_number.outputs.build_num }} + name: SharedLibraryCore-${{ env.buildNumber }} path: ${{ env.outputDirectory }}/*.nupkg publish: runs-on: ubuntu-latest - needs: [ build_pack ] + needs: [ make_version, build_pack ] + environment: prerelease + if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' || github.ref == 'refs/heads/release/develop' }} steps: - - name: Download Artifact + - name: Download artifact uses: actions/download-artifact@v4 with: - name: SharedLibraryCore-${{ needs.build_pack.outputs.build_num }} + name: SharedLibraryCore-${{ needs.make_version.outputs.build_num }} path: ${{ env.outputDirectory }} - name: Setup .NET From accf5ba0438942fac1dff8c05337d84d7f8be977 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 14:40:22 -0500 Subject: [PATCH 28/38] fix missing quotes --- .github/workflows/shared_library_nuget.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/shared_library_nuget.yml b/.github/workflows/shared_library_nuget.yml index 2956a48b..289a57f7 100644 --- a/.github/workflows/shared_library_nuget.yml +++ b/.github/workflows/shared_library_nuget.yml @@ -36,7 +36,7 @@ jobs: env: buildNumber: ${{ needs.make_version.outputs.build_num }} - packageTag: ${{ github.event_name == 'pull_request' && -beta || -preview }} + packageTag: ${{ github.event_name == 'pull_request' && '-beta' || '-preview' }} buildConfiguration: Prerelease steps: @@ -70,7 +70,7 @@ jobs: needs: [ make_version, build_pack ] environment: prerelease - if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' || github.ref == 'refs/heads/release/develop' }} + if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release/pre' || github.ref == 'refs/heads/develop' }} steps: - name: Download artifact From 74ad200b8ea8095e1cad67a2652a9bcf3b263cc0 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 15:09:22 -0500 Subject: [PATCH 29/38] test generate sequential build number --- .github/workflows/shared_library_nuget.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/shared_library_nuget.yml b/.github/workflows/shared_library_nuget.yml index 289a57f7..109c0d10 100644 --- a/.github/workflows/shared_library_nuget.yml +++ b/.github/workflows/shared_library_nuget.yml @@ -26,7 +26,9 @@ jobs: - name: Make build number id: generate_build_number run: | - build_num=$(date +'%Y.%-m.%-d').$(date +'%3N' | sed 's/^0*//') + run_number=$(git log --since=$(date +'%Y-%m-%dT00:00:00') --oneline | grep -c 'workflow_run') + run_number=$((run_number + 1)) + build_num=$(date +'%Y.%-m.%-d').$(run_number) echo "build_num=$build_num" >> $GITHUB_OUTPUT echo "Build number is $build_num" From 451b25ffb26a7a06f796f60aa6c9839f94f6d9cf Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 16:54:06 -0500 Subject: [PATCH 30/38] simplify build script for local builds --- .gitignore | 1 + Application/Application.csproj | 4 ++ .../BuildScripts/DownloadTranslations.ps1 | 12 ---- Application/BuildScripts/PostBuild.bat | 17 ----- Application/BuildScripts/PostPublish.bat | 67 ------------------- Application/BuildScripts/PreBuild.bat | 6 -- Application/BuildScripts/PreBuild.ps1 | 59 ++++++++++++++++ Application/BuildScripts/PreBuild.sh | 63 +++++++++++++++++ DeploymentFiles/PostPublish.ps1 | 14 ---- IW4MAdmin.sln | 1 - 10 files changed, 127 insertions(+), 117 deletions(-) delete mode 100644 Application/BuildScripts/DownloadTranslations.ps1 delete mode 100644 Application/BuildScripts/PostBuild.bat delete mode 100644 Application/BuildScripts/PostPublish.bat delete mode 100644 Application/BuildScripts/PreBuild.bat create mode 100644 Application/BuildScripts/PreBuild.ps1 create mode 100644 Application/BuildScripts/PreBuild.sh delete mode 100644 DeploymentFiles/PostPublish.ps1 diff --git a/.gitignore b/.gitignore index 62c99e06..3c177de0 100644 --- a/.gitignore +++ b/.gitignore @@ -247,3 +247,4 @@ launchSettings.json *.db /Data/IW4MAdmin_Migration.db-shm /Data/IW4MAdmin_Migration.db-wal +bundle/ \ No newline at end of file diff --git a/Application/Application.csproj b/Application/Application.csproj index 2204fdcf..e9fea994 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -73,4 +73,8 @@ + + + + diff --git a/Application/BuildScripts/DownloadTranslations.ps1 b/Application/BuildScripts/DownloadTranslations.ps1 deleted file mode 100644 index 7286ca4b..00000000 --- a/Application/BuildScripts/DownloadTranslations.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -param ( - [string]$OutputDir = $(throw "-OutputDir is required.") -) - -$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE") -foreach($localization in $localizations) -{ - $url = "http://api.raidmax.org:5000/localization/{0}" -f $localization - $filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization - $response = Invoke-WebRequest $url -UseBasicParsing - Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8 -} diff --git a/Application/BuildScripts/PostBuild.bat b/Application/BuildScripts/PostBuild.bat deleted file mode 100644 index c57f8264..00000000 --- a/Application/BuildScripts/PostBuild.bat +++ /dev/null @@ -1,17 +0,0 @@ -set SolutionDir=%1 -set ProjectDir=%2 -set TargetDir=%3 -set OutDir=%4 -set Version=%5 - -echo Copying dependency configs -copy "%SolutionDir%WebfrontCore\%OutDir%*.deps.json" "%TargetDir%" -copy "%SolutionDir%SharedLibraryCore\%OutDir%*.deps.json" "%TargetDir%" - -if not exist "%TargetDir%Plugins" ( - echo "Making plugin dir" - md "%TargetDir%Plugins" -) - -xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\" -del "%TargetDir%Plugins\SQLite*" diff --git a/Application/BuildScripts/PostPublish.bat b/Application/BuildScripts/PostPublish.bat deleted file mode 100644 index 53c22068..00000000 --- a/Application/BuildScripts/PostPublish.bat +++ /dev/null @@ -1,67 +0,0 @@ -set PublishDir=%1 -set SourceDir=%2 -SET COPYCMD=/Y - -echo deleting extra runtime files -if exist "%PublishDir%\runtimes\linux-arm" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-arm' -if exist "%PublishDir%\runtimes\linux-arm64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-arm64' -if exist "%PublishDir%\runtimes\linux-armel" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-armel' -if exist "%PublishDir%\runtimes\osx" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\osx' -if exist "%PublishDir%\runtimes\osx-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\osx-x64' -if exist "%PublishDir%\runtimes\win-arm" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\win-arm' -if exist "%PublishDir%\runtimes\win-arm64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\win-arm64' -if exist "%PublishDir%\runtimes\alpine-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\alpine-x64' -if exist "%PublishDir%\runtimes\linux-musl-x64" powershell Remove-Item -Force -Recurse '%PublishDir%\runtimes\linux-musl-x64' - -echo deleting misc files -if exist "%PublishDir%\web.config" del "%PublishDir%\web.config" -if exist "%PublishDir%\libman.json" del "%PublishDir%\libman.json" -del "%PublishDir%\*.exe" -del "%PublishDir%\*.pdb" - -echo setting up default folders -if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration" -move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\" -if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\" -del "%PublishDir%\Microsoft.CodeAnalysis*.dll" /F /Q -move "%PublishDir%\*.dll" "%PublishDir%\Lib\" -move "%PublishDir%\*.json" "%PublishDir%\Lib\" -move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes" -move "%PublishDir%\ru" "%PublishDir%\Lib\ru" -move "%PublishDir%\de" "%PublishDir%\Lib\de" -move "%PublishDir%\pt" "%PublishDir%\Lib\pt" -move "%PublishDir%\es" "%PublishDir%\Lib\es" -rmdir /Q /S "%PublishDir%\cs" -rmdir /Q /S "%PublishDir%\fr" -rmdir /Q /S "%PublishDir%\it" -rmdir /Q /S "%PublishDir%\ja" -rmdir /Q /S "%PublishDir%\ko" -rmdir /Q /S "%PublishDir%\pl" -rmdir /Q /S "%PublishDir%\pt-BR" -rmdir /Q /S "%PublishDir%\tr" -rmdir /Q /S "%PublishDir%\zh-Hans" -rmdir /Q /S "%PublishDir%\zh-Hant" -if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs" - -echo making start scripts -@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd" -@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh" - -echo copying update scripts -copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1" -copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh" - -echo moving front-end library dependencies -if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font" -move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\" -if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib" -if not exist "%PublishDir%\wwwroot\css" mkdir "%PublishDir%\wwwroot\css" -move "WebfrontCore\wwwroot\css\global.min.css" "%PublishDir%\wwwroot\css\global.min.css" -if not exist "%PublishDir%\wwwroot\js" mkdir "%PublishDir%\wwwroot\js" -move "%SourceDir%\WebfrontCore\wwwroot\js\global.min.js" "%PublishDir%\wwwroot\js\global.min.js" -if not exist "%PublishDir%\wwwroot\images" mkdir "%PublishDir%\wwwroot\images" -xcopy "%SourceDir%\WebfrontCore\wwwroot\images" "%PublishDir%\wwwroot\images" /E /H /C /I - - -echo setting permissions... -cacls "%PublishDir%" /t /e /p Everyone:F diff --git a/Application/BuildScripts/PreBuild.bat b/Application/BuildScripts/PreBuild.bat deleted file mode 100644 index 876350c2..00000000 --- a/Application/BuildScripts/PreBuild.bat +++ /dev/null @@ -1,6 +0,0 @@ -set SolutionDir=%1 -set ProjectDir=%2 -set TargetDir=%3 - -echo D | xcopy "%SolutionDir%Plugins\ScriptPlugins\*.js" "%TargetDir%Plugins" /y -powershell -File "%ProjectDir%BuildScripts\DownloadTranslations.ps1" %TargetDir% \ No newline at end of file diff --git a/Application/BuildScripts/PreBuild.ps1 b/Application/BuildScripts/PreBuild.ps1 new file mode 100644 index 00000000..300f97e5 --- /dev/null +++ b/Application/BuildScripts/PreBuild.ps1 @@ -0,0 +1,59 @@ +param ( [string]$SolutionDir, [string]$OutputDir ) + +if (-not (Test-Path "$SolutionDir/WebfrontCore/wwwroot/font")) { + Write-Output "restoring web dependencies" + dotnet tool install Microsoft.Web.LibraryManager.Cli --global + Set-Location "$SolutionDir/WebfrontCore" + libman restore + Set-Location $SolutionDir + Copy-Item -Recurse -Force -Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/fonts" "$SolutionDir/WebfrontCore/wwwroot/font" +} + +if (-not (Test-Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss")) { + Write-Output "load external resources" + New-Item -ItemType Directory -Force -Path "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css" + Invoke-WebRequest -Uri "https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss" -OutFile "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" + (Get-Content "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss") -replace '../fonts/', '/font/' | Set-Content "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" +} + +Write-Output "compiling scss files" + +dotnet tool install Excubo.WebCompiler --global +webcompiler -r "$SolutionDir/WebfrontCore/wwwroot/css/src" -o WebfrontCore/wwwroot/css/ -m disable -z disable +webcompiler "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable + +if (-not (Test-Path "$SolutionDir/bundle/dotnet-bundle.dll")) { + New-Item -ItemType Directory -Force -Path "$SolutionDir/bundle" + Write-Output "getting dotnet bundle" + Invoke-WebRequest -Uri "https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip" -OutFile "$SolutionDir/bundle/dotnet-bundle.zip" + Write-Output "unzipping download" + Expand-Archive -Path "$SolutionDir/bundle/dotnet-bundle.zip" -DestinationPath "$SolutionDir/bundle" -Force +} + +Write-Output "executing dotnet-bundle" +Set-Location "$SolutionDir/bundle" +dotnet "dotnet-bundle.dll" clean "$SolutionDir/WebfrontCore/bundleconfig.json" +dotnet "dotnet-bundle.dll" "$SolutionDir/WebfrontCore/bundleconfig.json" +Set-Location $SolutionDir + +New-Item -ItemType Directory -Force -Path "$SolutionDir/BUILD/Plugins" +Write-Output "building plugins" +Set-Location "$SolutionDir/Plugins" +Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object { dotnet publish $_.FullName -o "$SolutionDir/BUILD/Plugins" --no-restore } +Set-Location $SolutionDir + +if (-not (Test-Path "$OutputDir/Localization")) { + Write-Output "downloading translations" + New-Item -ItemType Directory -Force -Path "$OutputDir/Localization" + $localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE") + foreach ($localization in $localizations) { + $url = "https://master.iw4.zip/localization/$localization" + $filePath = "$OutputDir/Localization/IW4MAdmin.$localization.json" + Invoke-WebRequest -Uri $url -OutFile $filePath -UseBasicParsing + } +} + +Write-Output "copying plugins to build dir" +New-Item -ItemType Directory -Force -Path "$OutputDir/Plugins" +Copy-Item -Recurse -Force -Path "$SolutionDir/BUILD/Plugins/*.dll" -Destination "$OutputDir/Plugins/" +Copy-Item -Recurse -Force -Path "$SolutionDir/Plugins/ScriptPlugins/*.js" -Destination "$OutputDir/Plugins/" diff --git a/Application/BuildScripts/PreBuild.sh b/Application/BuildScripts/PreBuild.sh new file mode 100644 index 00000000..18765093 --- /dev/null +++ b/Application/BuildScripts/PreBuild.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +SolutionDir="$1" +OutputDir="$2" +export PATH="$PATH:~/.dotnet/tools" + +if [ ! -d "$SolutionDir/WebfrontCore/wwwroot/lib/font" ]; then + echo restoring web dependencies + dotnet tool install Microsoft.Web.LibraryManager.Cli --global + cd "$SolutionDir/WebfrontCore" || exit + libman restore + cd "$SolutionDir" || exit + cp -r "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/fonts" "$SolutionDir/WebfrontCore/wwwroot/font" +fi + +if [ ! -f "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" ]; then + echo load external resources + mkdir -p "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css" + curl -o "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss + sed -i 's#../fonts/#/font/#g' "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" +fi + +echo compiling scss files + +dotnet tool install Excubo.WebCompiler --global +webcompiler -r "$SolutionDir/WebfrontCore/wwwroot/css/src" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable +webcompiler "$SolutionDir/WebfrontCore/wwwroot/lib/open-iconic/font/css/open-iconic-bootstrap-override.scss" -o "$SolutionDir/WebfrontCore/wwwroot/css/" -m disable -z disable + +if [ ! -f "$SolutionDir/bundle/dotnet-bundle.dll" ]; then + mkdir -p "$SolutionDir/bundle" + echo getting dotnet bundle + curl -o "$SolutionDir/bundle/dotnet-bundle.zip" https://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip + echo unzipping download + unzip "$SolutionDir/bundle/dotnet-bundle.zip" -d "$SolutionDir/bundle" +fi +echo executing dotnet-bundle +cd "$SolutionDir/bundle" || exit +dotnet dotnet-bundle.dll clean "$SolutionDir/WebfrontCore/bundleconfig.json" +dotnet dotnet-bundle.dll "$SolutionDir/WebfrontCore/bundleconfig.json" +cd "$SolutionDir" || exit + +mkdir -p "$SolutionDir/BUILD/Plugins" +echo building plugins +cd "$SolutionDir/Plugins" || exit +find . -name "*.csproj" -print0 | xargs -0 -I {} dotnet publish {} -o "$SolutionDir/BUILD/Plugins" --no-restore +cd "$SolutionDir" || exit + +if [ ! -d "$OutputDir/Localization" ]; then + echo downloading translations + mkdir -p "$OutputDir/Localization" + localizations=("en-US" "ru-RU" "es-EC" "pt-BR" "de-DE") + for localization in "${localizations[@]}" + do + url="https://master.iw4.zip/localization/$localization" + filePath="$OutputDir/Localization/IW4MAdmin.$localization.json" + curl -s "$url" -o "$filePath" + done +fi + +echo copying plugins to buld dir +mkdir -p "$OutputDir/Plugins" +cp -r "$SolutionDir/BUILD/Plugins/*.dll" "$OutputDir/Plugins/" +cp -r "$SolutionDir/Plugins/ScriptPlugins/*.js" "$OutputDir/Plugins/" diff --git a/DeploymentFiles/PostPublish.ps1 b/DeploymentFiles/PostPublish.ps1 deleted file mode 100644 index 2ffa4f65..00000000 --- a/DeploymentFiles/PostPublish.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -param ( - [string]$PublishDir = $(throw "-PublishDir is required.") -) - -md -Force ("{0}\Localization" -f $PublishDir) - -$localizations = @("en-US", "ru-RU", "es-EC", "pt-BR", "de-DE") -foreach($localization in $localizations) -{ - $url = "http://api.raidmax.org:5000/localization/{0}" -f $localization - $filePath = "{0}\Localization\IW4MAdmin.{1}.json" -f $PublishDir, $localization - $response = Invoke-WebRequest $url -UseBasicParsing - Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8 -} diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index cdeaf1c0..bf0f6aeb 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -6,7 +6,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}" ProjectSection(SolutionItems) = preProject - DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1 README.md = README.md version.txt = version.txt DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1 From 40f912542b8440771e3cacca21a9f59ad1ceea8c Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 17:01:59 -0500 Subject: [PATCH 31/38] only run local build script in debug --- Application/Application.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/Application.csproj b/Application/Application.csproj index e9fea994..cfade8ce 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -73,8 +73,8 @@ - - + + From ba633be0347304aea180e64bfd69baa4d98a99f7 Mon Sep 17 00:00:00 2001 From: Amos Date: Sun, 30 Jun 2024 03:50:00 +0100 Subject: [PATCH 32/38] Refactor MuteManager constructor and clean up code (#329) The MuteManager constructor within the Mute plugin has been refactored for better dependency injection. This change simplifies the class construction by directly initializing fields in the constructor parameters. Additionally, several minor code improvements have been made, including spelling corrections and replacing some conditional checks for readability. Other arrays or methods in the plugin are also revised for better maintainability and readability of the code. --- Plugins/Mute/Commands/MuteCommand.cs | 6 +- Plugins/Mute/Commands/MuteInfoCommand.cs | 6 +- Plugins/Mute/Commands/TempMuteCommand.cs | 16 ++-- Plugins/Mute/Commands/UnmuteCommand.cs | 6 +- Plugins/Mute/Mute.csproj | 1 + Plugins/Mute/MuteManager.cs | 30 +++----- Plugins/Mute/Plugin.cs | 95 +++++++++++++++--------- 7 files changed, 89 insertions(+), 71 deletions(-) diff --git a/Plugins/Mute/Commands/MuteCommand.cs b/Plugins/Mute/Commands/MuteCommand.cs index 82c4cd7e..0e894f54 100644 --- a/Plugins/Mute/Commands/MuteCommand.cs +++ b/Plugins/Mute/Commands/MuteCommand.cs @@ -20,8 +20,8 @@ public class MuteCommand : Command Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -32,7 +32,7 @@ public class MuteCommand : Command Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Commands/MuteInfoCommand.cs b/Plugins/Mute/Commands/MuteInfoCommand.cs index 4622a0ce..bba01a9e 100644 --- a/Plugins/Mute/Commands/MuteInfoCommand.cs +++ b/Plugins/Mute/Commands/MuteInfoCommand.cs @@ -21,14 +21,14 @@ public class MuteInfoCommand : Command Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Commands/TempMuteCommand.cs b/Plugins/Mute/Commands/TempMuteCommand.cs index 69a2999c..9275b107 100644 --- a/Plugins/Mute/Commands/TempMuteCommand.cs +++ b/Plugins/Mute/Commands/TempMuteCommand.cs @@ -7,10 +7,9 @@ using SharedLibraryCore.Interfaces; namespace IW4MAdmin.Plugins.Mute.Commands; -public class TempMuteCommand : Command +public partial class TempMuteCommand : Command { private readonly MuteManager _muteManager; - private const string TempBanRegex = @"([0-9]+\w+)\ (.+)"; public TempMuteCommand(CommandConfiguration config, ITranslationLookup translationLookup, MuteManager muteManager) : base(config, translationLookup) @@ -22,8 +21,8 @@ public class TempMuteCommand : Command Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -39,7 +38,7 @@ public class TempMuteCommand : Command Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) @@ -49,8 +48,8 @@ public class TempMuteCommand : Command gameEvent.Origin.Tell(_translationLookup["COMMANDS_DENY_SELF_TARGET"]); return; } - - var match = Regex.Match(gameEvent.Data, TempBanRegex); + + var match = TempBanRegex().Match(gameEvent.Data); if (match.Success) { var expiration = DateTime.UtcNow + match.Groups[1].ToString().ParseTimespan(); @@ -72,4 +71,7 @@ public class TempMuteCommand : Command gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_BAD_FORMAT"]); } + + [GeneratedRegex(@"([0-9]+\w+)\ (.+)")] + private static partial Regex TempBanRegex(); } diff --git a/Plugins/Mute/Commands/UnmuteCommand.cs b/Plugins/Mute/Commands/UnmuteCommand.cs index e59c4334..04361144 100644 --- a/Plugins/Mute/Commands/UnmuteCommand.cs +++ b/Plugins/Mute/Commands/UnmuteCommand.cs @@ -20,8 +20,8 @@ public class UnmuteCommand : Command Permission = EFClient.Permission.Moderator; RequiresTarget = true; SupportedGames = Plugin.SupportedGames; - Arguments = new[] - { + Arguments = + [ new CommandArgument { Name = translationLookup["COMMANDS_ARGS_PLAYER"], @@ -32,7 +32,7 @@ public class UnmuteCommand : Command Name = translationLookup["COMMANDS_ARGS_REASON"], Required = true } - }; + ]; } public override async Task ExecuteAsync(GameEvent gameEvent) diff --git a/Plugins/Mute/Mute.csproj b/Plugins/Mute/Mute.csproj index a227b7fe..3341c525 100644 --- a/Plugins/Mute/Mute.csproj +++ b/Plugins/Mute/Mute.csproj @@ -8,6 +8,7 @@ Library Debug;Release;Prerelease AnyCPU + IW4MAdmin.Plugins.Mute diff --git a/Plugins/Mute/MuteManager.cs b/Plugins/Mute/MuteManager.cs index 537465b1..e758d5eb 100644 --- a/Plugins/Mute/MuteManager.cs +++ b/Plugins/Mute/MuteManager.cs @@ -10,23 +10,15 @@ using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Plugins.Mute; -public class MuteManager +public class MuteManager( + ILogger logger, + IDatabaseContextFactory databaseContextFactory, + IMetaServiceV2 metaService, + ITranslationLookup translationLookup) { - private readonly IMetaServiceV2 _metaService; - private readonly ITranslationLookup _translationLookup; - private readonly ILogger _logger; - private readonly IDatabaseContextFactory _databaseContextFactory; + private readonly ILogger _logger = logger; private readonly SemaphoreSlim _onMuteAction = new(1, 1); - public MuteManager(ILogger logger, IDatabaseContextFactory databaseContextFactory, - IMetaServiceV2 metaService, ITranslationLookup translationLookup) - { - _logger = logger; - _databaseContextFactory = databaseContextFactory; - _metaService = metaService; - _translationLookup = translationLookup; - } - public static bool IsExpiredMute(MuteStateMeta muteStateMeta) => muteStateMeta.Expiration is not null && muteStateMeta.Expiration < DateTime.UtcNow; @@ -42,7 +34,7 @@ public class MuteManager var muteState = await ReadPersistentDataV1(client); clientMuteMeta = new MuteStateMeta { - Reason = muteState is null ? string.Empty : _translationLookup["PLUGINS_MUTE_MIGRATED"], + Reason = muteState is null ? string.Empty : translationLookup["PLUGINS_MUTE_MIGRATED"], Expiration = muteState switch { null => DateTime.UtcNow, @@ -149,7 +141,7 @@ public class MuteManager private async Task ExpireMutePenalties(EFClient client) { - await using var context = _databaseContextFactory.CreateContext(); + await using var context = databaseContextFactory.CreateContext(); var mutePenalties = await context.Penalties .Where(penalty => penalty.OffenderId == client.ClientId) .Where(penalty => penalty.Type == EFPenalty.PenaltyType.Mute || penalty.Type == EFPenalty.PenaltyType.TempMute) @@ -184,7 +176,7 @@ public class MuteManager } private async Task ReadPersistentDataV1(EFClient client) => TryParse( - (await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState) + (await metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value, out var muteState) ? muteState : null; @@ -195,7 +187,7 @@ public class MuteManager if (clientMuteMeta is not null) return clientMuteMeta; // Get meta from database and store in client if exists - clientMuteMeta = await _metaService.GetPersistentMetaValue(Plugin.MuteKey, client.ClientId); + clientMuteMeta = await metaService.GetPersistentMetaValue(Plugin.MuteKey, client.ClientId); if (clientMuteMeta is not null) client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta); return clientMuteMeta; @@ -204,6 +196,6 @@ public class MuteManager private async Task WritePersistentData(EFClient client, MuteStateMeta clientMuteMeta) { client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta); - await _metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId); + await metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId); } } diff --git a/Plugins/Mute/Plugin.cs b/Plugins/Mute/Plugin.cs index d1c36815..44ca8b2c 100644 --- a/Plugins/Mute/Plugin.cs +++ b/Plugins/Mute/Plugin.cs @@ -21,15 +21,14 @@ public class Plugin : IPluginV2 public const string MuteKey = "IW4MMute"; public static IManager Manager { get; private set; } = null!; - public static Server.Game[] SupportedGames { get; private set; } = Array.Empty(); + public static Server.Game[] SupportedGames { get; private set; } = []; private static readonly string[] DisabledCommands = [nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"]; private readonly IInteractionRegistration _interactionRegistration; private readonly IRemoteCommandService _remoteCommandService; private readonly MuteManager _muteManager; private const string MuteInteraction = "Webfront::Profile::Mute"; - public Plugin(IInteractionRegistration interactionRegistration, - IRemoteCommandService remoteCommandService, MuteManager muteManager) + public Plugin(IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService, MuteManager muteManager) { _interactionRegistration = interactionRegistration; _remoteCommandService = remoteCommandService; @@ -37,7 +36,6 @@ public class Plugin : IPluginV2 IManagementEventSubscriptions.Load += OnLoad; IManagementEventSubscriptions.Unload += OnUnload; - IManagementEventSubscriptions.ClientStateInitialized += OnClientStateInitialized; IGameServerEventSubscriptions.ClientDataUpdated += OnClientDataUpdated; @@ -73,7 +71,7 @@ public class Plugin : IPluginV2 return !DisabledCommands.Contains(command.GetType().Name) && !command.IsBroadcast; }); - _interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) => + _interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, _) => { if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value)) { @@ -81,16 +79,16 @@ public class Plugin : IPluginV2 } var clientMuteMetaState = - (await _muteManager.GetCurrentMuteState(new EFClient {ClientId = targetClientId.Value})) + (await _muteManager.GetCurrentMuteState(new EFClient { ClientId = targetClientId.Value })) .MuteState; var server = manager.GetServers().First(); - string GetCommandName(Type commandType) => - manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? ""; - return clientMuteMetaState is MuteState.Unmuted or MuteState.Unmuting ? CreateMuteInteraction(targetClientId.Value, server, GetCommandName) : CreateUnmuteInteraction(targetClientId.Value, server, GetCommandName); + + string GetCommandName(Type commandType) => + manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? string.Empty; }); return Task.CompletedTask; } @@ -109,9 +107,9 @@ public class Plugin : IPluginV2 } var networkIds = updateEvent.Clients.Select(client => client.NetworkId).ToList(); - var ingameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId)); + var inGameClients = updateEvent.Server.ConnectedClients.Where(client => networkIds.Contains(client.NetworkId)); - await Task.WhenAll(ingameClients.Select(async client => + await Task.WhenAll(inGameClients.Select(async client => { var muteMetaUpdate = await _muteManager.GetCurrentMuteState(client); if (!muteMetaUpdate.CommandExecuted) @@ -137,7 +135,7 @@ public class Plugin : IPluginV2 { var muteMetaSay = await _muteManager.GetCurrentMuteState(messageEvent.Origin); - if (muteMetaSay.MuteState == MuteState.Muted) + if (muteMetaSay.MuteState is MuteState.Muted) { // Let the client know when their mute expires. messageEvent.Origin.Tell(Utilities.CurrentLocalization @@ -160,16 +158,16 @@ public class Plugin : IPluginV2 switch (muteMetaJoin) { - case {MuteState: MuteState.Muted}: + case { MuteState: MuteState.Muted }: // Let the client know when their mute expires. state.Client.Tell(Utilities.CurrentLocalization .LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt( - muteMetaJoin is {Expiration: not null} + muteMetaJoin is { Expiration: not null } ? muteMetaJoin.Expiration.Value.HumanizeForCurrentCulture() : Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"], muteMetaJoin.Reason)); break; - case {MuteState: MuteState.Unmuting}: + case { MuteState: MuteState.Unmuting }: // Handle unmute of unmuted players. await _muteManager.Unmute(state.Client.CurrentServer, Utilities.IW4MAdminClient(), state.Client, muteMetaJoin.Reason ?? string.Empty); @@ -191,6 +189,29 @@ public class Plugin : IPluginV2 Values = (Dictionary?)null }; + var presetReasonInput = new + { + Name = "PresetReason", + Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_PRESET_REASON"], + Type = "select", + Values = (Dictionary?)new Dictionary + { + { string.Empty, string.Empty }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_ABUSIVE"] + }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_SPAMMING"] + }, + { + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"], + Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_REASON_OTHER"] + } + } + }; + var durationInput = new { Name = "Duration", @@ -198,16 +219,16 @@ public class Plugin : IPluginV2 Type = "select", Values = (Dictionary?)new Dictionary { - {"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()}, - {"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()}, - {"1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture()}, - {"6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture()}, - {"1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture()}, - {"p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"]} + { "5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture() }, + { "30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture() }, + { "1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture() }, + { "6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture() }, + { "1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture() }, + { "p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"] } } }; - var inputs = new[] {reasonInput, durationInput}; + var inputs = new[] { reasonInput, presetReasonInput, durationInput }; var inputsJson = JsonSerializer.Serialize(inputs); return new InteractionData @@ -216,10 +237,10 @@ public class Plugin : IPluginV2 Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"], DisplayMeta = "oi-volume-off", ActionPath = "DynamicAction", - ActionMeta = new() + ActionMeta = new Dictionary { - {"InteractionId", MuteInteraction}, - {"Inputs", inputsJson}, + { "InteractionId", MuteInteraction }, + { "Inputs", inputsJson }, { "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] @@ -228,7 +249,7 @@ public class Plugin : IPluginV2 "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] }, - {"ShouldRefresh", true.ToString()} + { "ShouldRefresh", true.ToString() } }, MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator, Source = Name, @@ -250,11 +271,14 @@ public class Plugin : IPluginV2 args.Add(duration); } - if (meta.TryGetValue(reasonInput.Name, out var reason)) + var definedReason = meta.TryGetValue(reasonInput.Name, out var reason) ? reason : string.Empty; + if (meta.TryGetValue(presetReasonInput.Name, out var presetReason) && string.IsNullOrWhiteSpace(definedReason)) { - args.Add(reason); + definedReason = presetReason; } + args.Add(definedReason); + var commandResponse = await _remoteCommandService.Execute(originId, targetId, muteCommand, args, server); return string.Join(".", commandResponse.Select(result => result.Response)); @@ -272,21 +296,20 @@ public class Plugin : IPluginV2 Type = "text", }; - var inputs = new[] {reasonInput}; + var inputs = new[] { reasonInput }; var inputsJson = JsonSerializer.Serialize(inputs); return new InteractionData { EntityId = targetClientId, - Name = Utilities.CurrentLocalization.LocalizationIndex[ - "WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"], + Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"], DisplayMeta = "oi-volume-high", ActionPath = "DynamicAction", - ActionMeta = new() + ActionMeta = new Dictionary { - {"InteractionId", MuteInteraction}, - {"Outputs", reasonInput.Name}, - {"Inputs", inputsJson}, + { "InteractionId", MuteInteraction }, + { "Outputs", reasonInput.Name }, + { "Inputs", inputsJson }, { "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex[ @@ -297,7 +320,7 @@ public class Plugin : IPluginV2 Utilities.CurrentLocalization.LocalizationIndex[ "WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] }, - {"ShouldRefresh", true.ToString()} + { "ShouldRefresh", true.ToString() } }, MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator, Source = Name, From b2c2ab03f3441ec0f4ee94c5fcb777df4f9a9179 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 22:17:18 -0500 Subject: [PATCH 33/38] fix issue with manual log path input on initial configuration --- .../Configuration/ServerConfiguration.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index 0a6da74f..369a3456 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -9,16 +9,9 @@ namespace SharedLibraryCore.Configuration { public class ServerConfiguration : IBaseConfiguration { - private readonly IList _rconParsers; + private readonly IList _rconParsers = new List(); private IRConParser _selectedParser; - public ServerConfiguration() - { - _rconParsers = new List(); - Rules = new string[0]; - AutoMessages = new string[0]; - } - [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_IP")] public string IPAddress { get; set; } @@ -29,10 +22,10 @@ namespace SharedLibraryCore.Configuration public string Password { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")] - public string[] Rules { get; set; } = new string[0]; + public string[] Rules { get; set; } = []; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")] - public string[] AutoMessages { get; set; } = new string[0]; + public string[] AutoMessages { get; set; } = []; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PATH")] [ConfigurationOptional] @@ -88,7 +81,7 @@ namespace SharedLibraryCore.Configuration var passwords = _selectedParser.TryGetRConPasswords(); if (passwords.Length > 1) { - var (index, value) = + var (index, _) = loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection(loc["SETUP_RCON_PASSWORD_MANUAL"], null, passwords.Select(pw => $"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}") @@ -113,9 +106,8 @@ namespace SharedLibraryCore.Configuration Password = loc["SETUP_SERVER_RCON"].PromptString(); } - AutoMessages = new string[0]; - Rules = new string[0]; - ManualLogPath = null; + AutoMessages = []; + Rules = []; return this; } From 57143b1acf65a9cd3e71ab0f4823a0e5654027d7 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 22:33:16 -0500 Subject: [PATCH 34/38] update build pipeline for sequential revision #s --- .github/workflows/build_application.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 8c3b4c49..b90b74fc 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -29,10 +29,15 @@ jobs: build_num: ${{ steps.generate_build_number.outputs.build_num }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Make build number id: generate_build_number run: | - build_num=$(date +'%Y.%-m.%-d').$(date +'%3N' | sed 's/^0*//') + today=$(date +%Y-%m-%d) + commit_count=$(git rev-list --count --since="$today 00:00:00" --until="$today 23:59:59") + build_num=$(date +'%Y.%-m.%-d').$(commit_count) echo "build_num=$build_num" >> $GITHUB_OUTPUT echo "Build number is $build_num" @@ -50,9 +55,6 @@ jobs: buildNumber: ${{ needs.make_version.outputs.build_num }} steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: From a24aaf10d4e84418e9d2a8d801c4ee6aa9fa1146 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sat, 29 Jun 2024 22:46:00 -0500 Subject: [PATCH 35/38] use cache method for build revision --- .github/workflows/build_application.yml | 70 ++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index b90b74fc..300d9e3b 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -23,21 +23,74 @@ env: releaseType: prerelease jobs: - make_version: + update_revision_number: runs-on: ubuntu-latest + outputs: - build_num: ${{ steps.generate_build_number.outputs.build_num }} + revision_number: ${{ steps.revision.outputs.revision_number }} steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v4 - + + - name: Restore cache + id: cache + uses: actions/cache@v2 + with: + path: cache_dir + key: revision-number + + - name: Get current date + id: date + run: echo "current_date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + + - name: Check and update revision number + id: revision + run: | + FILENAME=cache_dir/revision_number.txt + DATEFILE=cache_dir/previous_date.txt + + mkdir -p cache_dir + + if [ -f "$DATEFILE" ]; then + prev_date=$(cat "$DATEFILE") + rev_number=$(cat "$FILENAME") + else + prev_date="" + rev_number=0 + fi + + if [ "$current_date" = "$prev_date" ]; then + rev_number=$((rev_number + 1)) + else + rev_number=1 + fi + + echo "New revision number: $rev_number" + echo $rev_number > "$FILENAME" + echo $current_date > "$DATEFILE" + echo "revision_number=$rev_number" >> $GITHUB_OUTPUT + + - name: Save cache + uses: actions/cache@v2 + with: + path: cache_dir + key: revision-number + + make_version: + runs-on: ubuntu-latest + needs: [ update_revision_number ] + + outputs: + build_num: ${{ steps.generate_build_number.outputs.build_num }} + env: + revisionNumber: ${{ needs.update_revision_number.outputs.revision_number }} + + steps: - name: Make build number id: generate_build_number run: | - today=$(date +%Y-%m-%d) - commit_count=$(git rev-list --count --since="$today 00:00:00" --until="$today 23:59:59") - build_num=$(date +'%Y.%-m.%-d').$(commit_count) + build_num=$(date +'%Y.%-m.%-d').${{ env.revisionNumber }} echo "build_num=$build_num" >> $GITHUB_OUTPUT echo "Build number is $build_num" @@ -55,6 +108,9 @@ jobs: buildNumber: ${{ needs.make_version.outputs.build_num }} steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: From 5a33567c73911a86244f3230f4748ad157344cbc Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 30 Jun 2024 11:52:31 -0500 Subject: [PATCH 36/38] tweak game interface plugin for consistency --- Plugins/ScriptPlugins/GameInterface.js | 53 +++++++++++++------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/Plugins/ScriptPlugins/GameInterface.js b/Plugins/ScriptPlugins/GameInterface.js index ab2b3a2a..9e1bb33e 100644 --- a/Plugins/ScriptPlugins/GameInterface.js +++ b/Plugins/ScriptPlugins/GameInterface.js @@ -94,7 +94,7 @@ const plugin = { onServerValueSetCompleted: async function (serverValueEvent) { this.logger.logDebug('Set {dvarName}={dvarValue} success={success} from {server}', serverValueEvent.valueName, serverValueEvent.value, serverValueEvent.success, serverValueEvent.server.id); - + if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) { this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName); return; @@ -124,7 +124,7 @@ const plugin = { // loop restarts this.requestGetDvar(inDvar, serverValueEvent.server); }, - + onServerMonitoringStart: function (monitorStartEvent) { this.initializeServer(monitorStartEvent.server); }, @@ -162,7 +162,7 @@ const plugin = { serverState.enabled = true; serverState.running = true; serverState.initializationInProgress = false; - + // todo: this might not work for all games responseEvent.server.rconParser.configuration.floodProtectInterval = 150; @@ -233,7 +233,7 @@ const plugin = { // todo: refactor to mapping if possible if (event.eventType === 'ClientDataRequested') { - const client = server.getClientByNumber(event.clientNumber); + const client = server.connectedClients[event.clientNumber]; if (client != null) { this.logger.logDebug('Found client {name}', client.name); @@ -269,8 +269,9 @@ const plugin = { } } + let _; if (event.eventType === 'SetClientDataRequested') { - let client = server.getClientByNumber(event.clientNumber); + let client = server.connectedClients[event.clientNumber]; let clientId; if (client != null) { @@ -298,12 +299,12 @@ const plugin = { const parsedValue = parseInt(event.data['value']); const key = event.data['key'].toString(); if (!isNaN(parsedValue)) { - event.data['direction'] == 'increment' ? + _ = event.data['direction'] === 'increment' ? (await metaService.incrementPersistentMeta(key, parsedValue, clientId, token)).result : (await metaService.decrementPersistentMeta(key, parsedValue, clientId, token)).result; } } else { - const _ = (await metaService.setPersistentMeta(event.data['key'], event.data['value'], clientId, token)).result; + _ = (await metaService.setPersistentMeta(event.data['key'], event.data['value'], clientId, token)).result; } if (event.data['key'] === 'PersistentClientGuid') { @@ -342,34 +343,34 @@ const plugin = { if (typeof response !== 'string' && !(response instanceof String)) { response = JSON.stringify(response); } - + const max = 10; this.logger.logDebug(`response length ${response.length}`); - + let quoteReplace = '\\"'; // todo: may be more than just T6 if (server.gameCode === 'T6') { quoteReplace = '\\\\"'; } - + let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800); if (chunks.length > max) { this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`); chunks = chunks.slice(0, max); } this.logger.logDebug(`chunk size ${chunks.length}`); - + for (let i = 0; i < chunks.length; i++) { this.sendEventMessage(server, false, 'UrlRequestCompleted', null, null, - null, { entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]}); + null, {entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]}); } }); } - + if (event.eventType === 'RegisterCommandRequested') { this.registerDynamicCommand(event); } - + if (event.eventType === 'GetBusModeRequested') { if (event.data?.directory && event.data?.mode) { busMode = event.data.mode; @@ -433,10 +434,10 @@ const plugin = { }); } }); - + return; } - + const serverEvents = importNamespace('SharedLibraryCore.Events.Server'); const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server); requestEvent.delayMs = this.config.pollingRate; @@ -467,8 +468,8 @@ const plugin = { requestSetDvar: function (dvarName, dvarValue, server) { const serverState = servers[server.id]; - - if ( busMode === 'file' ) { + + if (busMode === 'file') { this.scriptHelper.requestNotifyAfterDelay(250, async () => { const io = importNamespace('System.IO'); try { @@ -493,7 +494,7 @@ const plugin = { }); } }) - + return; } @@ -526,7 +527,7 @@ const plugin = { } }, - parseUrlRequest: function(event) { + parseUrlRequest: function (event) { const url = event.data?.url; if (url === undefined) { @@ -556,8 +557,8 @@ const plugin = { const script = importNamespace('IW4MAdmin.Application.Plugin.Script'); return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict); }, - - registerDynamicCommand: function(event) { + + registerDynamicCommand: function (event) { const commandWrapper = { commands: [{ name: event.data['name'] || 'DEFAULT', @@ -571,9 +572,9 @@ const plugin = { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; } - + if (gameEvent.data === '--reload' && gameEvent.origin.level === 'Owner') { - this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, { name: gameEvent.extra.name }); + this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, {name: gameEvent.extra.name}); } else { sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, { args: gameEvent.data @@ -582,7 +583,7 @@ const plugin = { } }] } - + this.scriptHelper.registerDynamicCommand(commandWrapper); } }; @@ -920,6 +921,6 @@ const fileForDvar = (dvar) => { if (dvar === inDvar) { return busFileIn; } - + return busFileOut; } From 966a297132bab7fcc303cf2068bda86c6004973f Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 30 Jun 2024 11:53:05 -0500 Subject: [PATCH 37/38] update build pipeline for official release --- .github/workflows/build_application.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 300d9e3b..87149211 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -205,7 +205,7 @@ jobs: - name: Upload artifact for analysis uses: actions/upload-artifact@v4 with: - name: IW4MAdmin-${{ env.buildNumber }} + name: IW4MAdmin-${{ env.buildNumber }}-${{ env.releaseType }} path: ${{ env.outputFolder }} release_github: @@ -223,7 +223,7 @@ jobs: - name: Download build uses: actions/download-artifact@v4 with: - name: IW4MAdmin-${{ env.buildNumber }} + name: IW4MAdmin-${{ env.buildNumber }}-${{ env.releaseType }} path: ${{ github.workspace }} - name: Zip build @@ -234,7 +234,7 @@ jobs: with: tag: ${{ env.buildNumber }}-${{ env.releaseType }} name: IW4MAdmin ${{ env.buildNumber }} - draft: true + draft: false prerelease: true body: Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating) generateReleaseNotes: true From 8e11c89b6ed2cefa5c40f4bf829bc168cdf14dc0 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 30 Jun 2024 12:07:46 -0500 Subject: [PATCH 38/38] update build action --- .github/workflows/build_application.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_application.yml b/.github/workflows/build_application.yml index 87149211..c9aa6779 100644 --- a/.github/workflows/build_application.yml +++ b/.github/workflows/build_application.yml @@ -9,7 +9,6 @@ on: - Data/** - SharedLibraryCore/** - Plugins/** - - .github/workflows/build_application.yml pull_request: branches: [ develop ] paths: @@ -35,7 +34,7 @@ jobs: - name: Restore cache id: cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: cache_dir key: revision-number @@ -72,7 +71,7 @@ jobs: echo "revision_number=$rev_number" >> $GITHUB_OUTPUT - name: Save cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: cache_dir key: revision-number